有界モデル検証(cbmc)というC C++のテスト方法があるらしいのだが、ほとんどが英語情報のためか、普及していない。
すくなくとも自分のみのまわりの開発者はつかっていない。
もったいない。
ちょっと使ってみて、こういう検証方法だと理解したことを書いておく。
cbmc は http://www.cprover.org/cbmc/ で開発されている、有界モデル検証ツールで、Windows, Linux, Mac OSXで使用できる。
ちょっとやってみながら考える
cbmcのチュートリアルにのっていた、file1.c に対して、
int puts(const char *s) { } int main(int argc, char **argv) { int i; if(argc>=1) puts(argv[2]); }
cbmc を実行してみよう。これは、配列境界チェックオプション(–bounds-check )と、ポインタチェックオプション(–pointer-checkfile)を有効にして、そのプロパティを表示(–show-properties)している。
cbmc file1.c --show-properties --bounds-check --pointer-checkfile
そうするとつぎのような出力がでた。
file1.c: Parsing Converting Type-checking file1 Generating GOTO Program Adding CPROVER library Function Pointer Removal Partial Inlining Generic Property Instrumentation Property main.1: file file1.c line 6 function main dereference failure: pointer NULL !(2l + argv == ((char **)NULL)) in "argv[(signed long int)2]" Property main.2: file file1.c line 6 function main dereference failure: pointer invalid !INVALID-POINTER(argv) in "argv[(signed long int)2]" Property main.3: file file1.c line 6 function main dereference failure: deallocated dynamic object !(POINTER_OBJECT(argv) == POINTER_OBJECT(__CPROVER_deallocated)) in "argv[(signed long int)2]" Property main.4: file file1.c line 6 function main dereference failure: dead object !(POINTER_OBJECT(argv) == POINTER_OBJECT(__CPROVER_dead_object)) in "argv[(signed long int)2]" Property main.5: file file1.c line 6 function main dereference failure: dynamic object bounds !(16l + POINTER_OFFSET(argv) < 0) && __CPROVER_malloc_size >= 24ul + (unsigned long int)POINTER_OFFSET(argv) || !(POINTER_OBJECT(argv) == POINTER_OBJECT(__CPROVER_malloc_object)) in "argv[(signed long int)2]" Property main.6: file file1.c line 6 function main dereference failure: object bounds !(16l + POINTER_OFFSET(argv) < 0) && OBJECT_SIZE(argv) >= 24 + POINTER_OFFSET(argv) || DYNAMIC_OBJECT(argv) in "argv[(signed long int)2]"
これの、例えば プロパティ1は、pointer NULL という問題を検出することにして、それを条件文に変換していると考えられる。
dereference failure: pointer NULL #問題 !(2l + argv == ((char **)NULL)) #問題を条件文へ翻訳したもの
つまりこの6個の組み合わせが解くべき問題ということになる。
この問題を解くために、シンボリック実行を行なう。シンボリック実行とは通常の実行とは異なり、変数に値をいれずに実行することだそうである。
とはいっても、先の出力に
file1.c: Parsing Converting Type-checking file1 Generating GOTO Program Adding CPROVER library Function Pointer Removal Partial Inlining
とあるようにfile1.c を等価な解析しやすいソースコードに変換してから実行しているはず。
そして条件分岐にくるたびにどちらのパスにはいるかで、ある程度値が絞られてくるんだと思う。
実際に解析を行なってみる。
cbmc file1.c --bounds-check --pointer-check
こういった出力が得られる。
file file1.c: Parsing
Converting
Type-checking file1
Generating GOTO Program
Adding CPROVER library
Function Pointer Removal
Partial Inlining
Generic Property Instrumentation
Starting Bounded Model Checking
size of program expression: 46 steps
simple slicing removed 8 assignments
Generated 6 VCC(s), 5 remaining after simplification
Passing problem to propositional reduction
Running propositional reduction
Post-processing
Solving with MiniSAT 2.2.0 with simplifier
947 variables, 2715 clauses
SAT checker: negated claim is SATISFIABLE, i.e., does not hold
Runtime decision procedure: 0.016s
Building error trace
Counterexample:
State 3 file <built-in-additions> line 35 thread 0
----------------------------------------------------
__CPROVER_memory_leak=NULL (0000000000000000000000000000000000000000000000000000000000000000)
State 4 file <built-in-additions> line 78 thread 0
----------------------------------------------------
__CPROVER_pipe_count=0 (00000000000000000000000000000000)
State 5 file <built-in-additions> line 22 thread 0
----------------------------------------------------
__CPROVER_threads_exited=__CPROVER_threads_exited#1 (?)
State 6 file <built-in-additions> line 23 thread 0
----------------------------------------------------
__CPROVER_next_thread_id=0 (0000000000000000000000000000000000000000000000000000000000000000)
State 7 file <built-in-additions> line 57 thread 0
----------------------------------------------------
__CPROVER_rounding_mode=0 (00000000000000000000000000000000)
State 8 file <built-in-additions> line 30 thread 0
----------------------------------------------------
__CPROVER_deallocated=NULL (0000000000000000000000000000000000000000000000000000000000000000)
State 9 file <built-in-additions> line 31 thread 0
----------------------------------------------------
__CPROVER_dead_object=NULL (0000000000000000000000000000000000000000000000000000000000000000)
State 10 file <built-in-additions> line 32 thread 0
----------------------------------------------------
__CPROVER_malloc_object=NULL (0000000000000000000000000000000000000000000000000000000000000000)
State 11 file <built-in-additions> line 33 thread 0
----------------------------------------------------
__CPROVER_malloc_size=0 (0000000000000000000000000000000000000000000000000000000000000000)
State 12 file <built-in-additions> line 34 thread 0
----------------------------------------------------
__CPROVER_malloc_is_new_array=FALSE (0)
State 16 thread 0
----------------------------------------------------
argv'[1]=irep("(\"nil\")")[1] (?)
State 19 thread 0
----------------------------------------------------
argc=1 (00000000000000000000000000000001)
State 20 thread 0
----------------------------------------------------
argv=argv' (0000001000000000000000000000000000000000000000000000000000000000)
State 21 file file1.c line 4 function main thread 0
----------------------------------------------------
i=0 (00000000000000000000000000000000)
Violated property:
file file1.c line 6 function main
dereference failure: object bounds
!(16l + POINTER_OFFSET(argv) < 0) && OBJECT_SIZE(argv) >= 24 + POINTER_OFFSET(argv) || DYNAMIC_OBJECT(argv)
VERIFICATION FAILEDどうやら、有界モデル検証が実行されている(Starting Bounded Model Checking)もよう。
この問題を解くのに、MiniSATを使用しているとのこと。
このチェッカーは充足可能性に異義を唱えたもよう(SAT checker: negated claim is SATISFIABLE)
その後、エラートレースを作成している、たぶん。
充足可能性問題、SAT、はてどこかで聞いた語だなと思ったら、数学ガールの乱択アルゴリズムにのっていた、はず。
http://www.hyuki.com/girl/random.html で確認すると、9章 強く、正しく、美しくが該当しますね。はい、載っていました。
ということは、充足可能性問題のソルバーが MiniSAT 2.2.0 で、これを解いたということですね。
それで式全体が偽になるパターン:反例(counterexample)をエラートレースして示しているわけですか。なるほどー。
