c言語静的解析ツールと ruby 1.9 trunk

Post on 18-Dec-2014

2.898 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

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

TRANSCRIPT

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

池上 大介 @ikegami_ _

自己紹介

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

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

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

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

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

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

@ikegami_ _

なにそれ楽しそう

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

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

• BLAST

• Frama-C

• 使いにくい

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

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

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

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

• 多すぎない警告

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

• Emacs + Flymake

• Vim + ?

• Vim + QuickFix + errormaker

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

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

Emacs + Flymake + cppcheck

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

• cppcheck

• splint

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

• BLAST

• Frama-C

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

• ソースコードをパース

• ソースコードを理解

• 問題を発見

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

• すばやく解析

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

• ループを unroll

• if 分岐を非決定的実行

• 算術

• ポインタ解析

• Call flow graph

• 抽象化

• assert 文の解析

• コメントによる

assertion

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

cppcheck

• 軽量 written in C++

• C/C++ を静的解析

• プリプロセス

• Tokenize

• Run all checks - pattern matching of the tokens

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

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 で解析

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

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 箇所のエラー(?)

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 限定のメモリリークだった!

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

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

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

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

• cppcheck と比べて冗長な警告

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

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

http://www.splint.org/

splint hash.c

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

• 397 個の警告

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

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

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

splint regcomp.c

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

• 737 個の警告

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

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 }

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 を忘れている?

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 してはいけない(?)

静的解析はfalse positive との戦い

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

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

• ユーザが assert() を書く

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

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

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

escape 解析って何?

#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⇒「はい、代入されてます :-)」

←ここで代入される

#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⇒「はい、代入されてます :-)」

ruby 1.9 trunk に適用可能?

• for や while といったループ

• if の分岐

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

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

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

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

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

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

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

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

• value plug-in

• users plug-in

http://frama-c.com/

←軽量静的解析

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;}

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;

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では発見できず(時間切れ)

Frama-C users plug-in

• 軽量な callee の調査

• 関数間解析の基本

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 ==========

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

• false positive との戦い

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

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

• 手動 annotation による静的解析

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

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

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

• ruby-1.9 trunk revision 33685 に適用

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

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

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

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

top related