C言語静的解析ツールと Ruby 1.9 trunk

35
C言語静的解析ツールと Ruby 1.9 trunk 池上 大介 @ikegami_ _

description

Ruby 1.9 trunk のソースコードに、既存の静的解析ツール(cppcheck, splint, BLAST, Frama-C)を適用した/適用しようと試みた実験の紹介です。

Transcript of C言語静的解析ツールと Ruby 1.9 trunk

Page 1: C言語静的解析ツールと Ruby 1.9 trunk

C言語静的解析ツールとRuby 1.9 trunk

池上 大介 @ikegami_ _

Page 2: C言語静的解析ツールと Ruby 1.9 trunk

自己紹介

• 2003 誤り訂正符号で博士号

• 2003-2010 仕様記述やプログラムの静的検証に従事

• ツールを Haskell で作るのが仕事

• C 言語に対するツールを使うのは今回が初めて

• 10/27 - 11/10 の 2 週間の調査

• Ruby/Mathematica, Ruby/Ming, RushCheck, Karatsuba

@ikegami_ _

Page 3: C言語静的解析ツールと Ruby 1.9 trunk

なにそれ楽しそう

匿名希望さんとのやりとり

Page 4: C言語静的解析ツールと Ruby 1.9 trunk

結論 1/2• 既存の軽量でない C++ 検証ツール

• BLAST

• Frama-C

• 使いにくい

• ぼくがアホなだけ/検証項目を人間が書く

• ツールの要求するマシンパワーが足りない

• 現状の CIL の限界(GCC 拡張をパースできない)

Page 5: C言語静的解析ツールと Ruby 1.9 trunk

結論 2/2• もっともな指摘

• 多すぎない警告

• cppcheck が妥当(C/C++ に対応)

• Emacs + Flymake

• Vim + ?

• Vim + QuickFix + errormaker

←二つを満たすのは難しい

Page 6: C言語静的解析ツールと Ruby 1.9 trunk

セーブした瞬間にエラーがでる→嬉しい

Emacs + Flymake + cppcheck

Page 7: C言語静的解析ツールと Ruby 1.9 trunk

静的解析ツール• 軽量:すばやく動く・簡単に使える

• cppcheck

• splint

• 軽量でない:遅い・マニュアル分厚い

• BLAST

• Frama-C

Page 8: C言語静的解析ツールと Ruby 1.9 trunk

軽量静的解析とは• コンパイラの -Wall フラグみたいな

• ソースコードをパース

• ソースコードを理解

• 問題を発見

• しかるべきメッセージを出力

• すばやく解析

Page 9: C言語静的解析ツールと Ruby 1.9 trunk

軽量でない静的解析ツール• division by zero

• ループを unroll

• if 分岐を非決定的実行

• 算術

• ポインタ解析

• Call flow graph

• 抽象化

• assert 文の解析

• コメントによる

assertion

あとで議論しましょう(?)

Page 10: C言語静的解析ツールと Ruby 1.9 trunk

cppcheck

• 軽量 written in C++

• C/C++ を静的解析

• プリプロセス

• Tokenize

• Run all checks - pattern matching of the tokens

http://sourceforge.net/apps/trac/cppcheck/

Page 11: C言語静的解析ツールと Ruby 1.9 trunk

cppcheck を ruby に

• ruby-1.9 trunk revision 33685 (2011-11-09 取得)

• compile.cを除く 77 files を 2:01:02.55 で解析

• error と指摘した 6 箇所

• compile.c を 54:46.94s で解析

• 仮想環境で走らせたので、本当はもっと速いはず

Page 12: C言語静的解析ツールと Ruby 1.9 trunk

cppcheck を Ruby に

[hash.c:2351]: (error) Memory leak: str[io.c:5264]: (error) fflush() called on input stream "stdin" may result in undefined behaviour[regcomp.c:5524]: (error) Memory leak: new_reg[vm_dump.c:831]: (error) Possible null pointer dereference: vm - otherwise it is redundant to check if vm is null at line 778[vm_dump.c:834]: (error) Possible null pointer dereference: vm - otherwise it is redundant to check if vm is null at line 778[vm_dump.c:835]: (error) Possible null pointer dereference: vm - otherwise it is redundant to check if vm is null at line 778

6 箇所のエラー(?)

Page 13: C言語静的解析ツールと Ruby 1.9 trunk

hash.c の該当箇所

2351 } /* 関数 ruby_setenv の終端 */

2303 str = malloc(len += strlen(value) + 2);

このあと str を free している形跡がない?

[hash.c:2351]: (error) Memory leak: str

2287 #elif defined __sun

Solaris 限定のメモリリークだった!

Page 14: C言語静的解析ツールと Ruby 1.9 trunk

io.c の該当箇所

5264 fflush(stdin); /* is it really needed? */

[io.c:5264]: (error) fflush() called on input stream "stdin" may result in undefined behaviour

Q. How can I flush pending input so that a user's typeahead isn't read at the next prompt? Will fflush(stdin) work?A. fflush is defined only for output streams. (omit)

comp.lang.c FAQ list · Question 12.26a

Page 15: C言語静的解析ツールと Ruby 1.9 trunk

splint• 軽量/軽量でない written in C

• 時間不足で調査が不十分(すみません)

• 自動検証 & コメントの annotation による検証

• cppcheck と比べて冗長な警告

• いくつかのファイルがパースできない

• cont.c gc.c random.c thread_pthread.h(ナンデ?)

http://www.splint.org/

Page 16: C言語静的解析ツールと Ruby 1.9 trunk

splint hash.c

• ruby-1.9 trunk revision 33685 (2011-11-09 取得)

• 397 個の警告

• header をすべて(?)渡す必要がある

• Solaris のバグは Solaris 上の configure の結果をもらってこないといけない

• cppcheck が見つけた hash.c のメモリリークは x86 では見つからない

Page 17: C言語静的解析ツールと Ruby 1.9 trunk

splint regcomp.c

• ruby-1.9 trunk revision 33685 (2011-11-09 取得)

• 737 個の警告

• 全部見て回るのは時間的に無理• 適当に拾った

Page 18: C言語静的解析ツールと Ruby 1.9 trunk

splint regcomp.cregcomp.c:180:10: Only storage uslist->us->target (type struct _Node *) derived from released storage is not released (memory leak): uslist->us(omit)

176 static void 177 unset_addr_list_end(UnsetAddrList* uslist) 178 { 179 if (IS_NOT_NULL(uslist->us)) 180 xfree(uslist->us); 181 }

Page 19: C言語静的解析ツールと Ruby 1.9 trunk

176 static void 177 unset_addr_list_end(UnsetAddrList* uslist) 178 { 179 if (IS_NOT_NULL(uslist->us)) 180 xfree(uslist->us); 181 } typedef struct {

int offset; struct _Node* target;} UnsetAddr;

typedef struct { int num; int alloc; UnsetAddr* us;} UnsetAddrList;

uslist->us->target の free を忘れている?

Page 20: C言語静的解析ツールと Ruby 1.9 trunk

183 static int 184 unset_addr_list_add(UnsetAddrList* uslist, int offset, struct _Node* node) 185 { 186 UnsetAddr* p; 187 int size; 188 189 if (uslist->num >= uslist->alloc) { 190 size = uslist->alloc * 2; 191 p = (UnsetAddr* )xrealloc(uslist->us, sizeof(UnsetAddr) * size); 192 CHECK_NULL_RETURN_MEMERR(p); 193 uslist->alloc = size; 194 uslist->us = p; 195 } 196 197 uslist->us[uslist->num].offset = offset; 198 uslist->us[uslist->num].target = node; 199 uslist->num++; 200 return 0; 201 }

↑ free してはいけない(?)

Page 21: C言語静的解析ツールと Ruby 1.9 trunk

静的解析はfalse positive との戦い

Page 22: C言語静的解析ツールと Ruby 1.9 trunk

BLAST• with CIL (ビルドには OCaml が必要)

• 反例駆動モデル検査による安全性検証

• ユーザが assert() を書く

• プログラムを抽象モデルに自動変換

• assert に反するかどうかを判定

http://mtc.epfl.ch/software-tools/blast/index-epfl.php

Page 23: C言語静的解析ツールと Ruby 1.9 trunk

escape 解析って何?

Page 24: C言語静的解析ツールと Ruby 1.9 trunk

#include <assert.h>int watched; /* a global variable */void foo(int i) { watched = i; }void bar(){  int j;

  foo(j);  assert(j == watched);  /* assert(j != watched); */}

% gcc -E -I ${BLAST_INCLUDE} -main bar target.c% pblast.opt target.i -main bar⇒「はい、代入されてます :-)」

←ここで代入される

Page 25: C言語静的解析ツールと Ruby 1.9 trunk

#include <assert.h>int *watched;

void foo(int *p) { watched = p; }

void bar(){  int i, *j;  i = 1;  j = &i;  foo(j);  assert(j == watched);  /* assert(j != watched); */}

ポインタ解析も可能

% gcc -E -I ${BLAST_INCLUDE} -main bar target.c% pblast.opt target.i -main bar⇒「はい、代入されてます :-)」

Page 26: C言語静的解析ツールと Ruby 1.9 trunk

ruby 1.9 trunk に適用可能?

• for や while といったループ

• if の分岐

• などが解析不能になる原因

• やってみないとわからない世界

• 問題を人間が簡単にしてやる必要?

• 時間と手間と報われない労力

Page 27: C言語静的解析ツールと Ruby 1.9 trunk

Frama-C• with CIL (ビルドには OCaml が必要)

• C の静的解析フレームワーク

• 解析プラグインの寄せ集め

• 軽量/軽量でない両方のプラグイン

• value plug-in

• users plug-in

http://frama-c.com/

←軽量静的解析

Page 28: C言語静的解析ツールと Ruby 1.9 trunk

division by zerovoid foo(int x, int y){ int z = x / y; /* y should not be zero */ return;}

int main(int argc, char **argv){ int x = 1, y = 0; foo(x, y); return 0;}

Page 29: C言語静的解析ツールと Ruby 1.9 trunk

Frama-C value plug-in

% frama-c -val foo.c[value] Analyzing a complete application starting at main【略】foo.c:3:[kernel] warning: division by zero: assert y ≢ 0;

Page 30: C言語静的解析ツールと Ruby 1.9 trunk

division by zero• ruby trunk revision no. 33685

• bignum.c

• 1044 ds[k] = (BDIGIT)(num / hbase);

• util.c

• 数カ所に存在• 331 n = (r - l + size) / size;

Frama-C value pluginでは発見できず(時間切れ)

Page 31: C言語静的解析ツールと Ruby 1.9 trunk

Frama-C users plug-in

• 軽量な callee の調査

• 関数間解析の基本

Page 32: C言語静的解析ツールと Ruby 1.9 trunk

void foo(void) {}void bar(void) {foo();}

int main(void){ bar(); return 0;} % frama-c -users foo.c

[kernel] preprocessing with "gcc -C -E -I. foo.c"【中略】[users] ====== DISPLAYING USERS ====== bar: foo main: foo bar ====== END OF USERS ==========

Page 34: C言語静的解析ツールと Ruby 1.9 trunk

静的解析の限界• 全自動軽量静的解析

• false positive との戦い

• 状態爆発するのでメモリが必要

• でかい構文木相手なので CPU も必要

• 手動 annotation による静的解析

• どこにどんな annotation を書くか?

• Frama-C + jessie plug-in → Coq で証明

Page 35: C言語静的解析ツールと Ruby 1.9 trunk

まとめ再び• 2 週間の C 言語の静的解析ツール調査

• ruby-1.9 trunk revision 33685 に適用

• 軽量 cppcheck/splint は適用が簡単

• escape 解析は軽量静的解析では無理

• BLAST/Frama-C の適用は時間と考察が必要

• 労力が報われない可能性