何故モデル検査が必要なのか?
• 多くのソフトウェアが分散処理・並行処理を基本としている
– 複数の計算主体が並行動作をしながら共有資源を用いて機能を実現している
• システム全体が安全な動作をしない可能性が高い
– 資源を奪い合う競合状態に陥る可能性が高い
– 非決定的な振る舞いについて、誤りがテストなどでは現れない可能性が高い
1
非決定的な振舞
• 複数スレッド間の並列動作
• 複数機器間の並列動作
• ネットワーク環境の変化
2
複数スレッド間の並列動作
3
int a;
(ロックは適切にとっているものとする.)
スレッド間共有データ
while (true) {if (a < 10) { a++; }
}
スレッド1
while (true) {a--;
}
a = 10;thread1.run();thread2.run();while (true) { b = 100/a; }
スレッド2
スレッド0
テストでは...
• 実行環境によっては,スレッド1だけが,非常に負荷の高いCPUに割り当てられ,スレッド0や2ばかりが動く,ということもあり得る.
• しかし,テストではそのような環境が再現できるとは限らない.
4
a 10 10 9 9 10 10 9 8 9
スレッド 1 2 0 1 0 2 2 1 0
モデル検査では
• すべての場合を探索するので,エラーがおこるのなら (原理的には) 必ず見つかる.
5
10 9
th0,th1
th2
th1
th0
th2
th1... 1
th2
th1
th0
th2
th10
th0
err
モデル検査• 状態機械の状態空間を網羅的に探索することにより、与えら
れた性質が正しいかどうかを判定する検証方法
• 従来の形式手法との比較
– モデル検査
• 完全な自動化が可能
–専門的な知識が必要ない
– 定理証明の手法を応用した検証方法
• 数理論理学の知識が必要
• 取り扱える状態数
– 104 から 105 (初期)
– 10120 (現在)
• 問題点
– 探索空間のサイズ(state explosion problem)6
古典的モデル検査と現代的モデル検査
• 古典的モデル検査
– 対象: 仕様書,プロトコルなど
– 設計段階など,「上流工程」でモデル検査を適用.
– 仕様書などから検査器にかけられるモデルを (ほとんどの場合) 手動で構築.
• 現代的モデル検査
– 対象: プログラムソースコードなど
– テスト段階など,「下流工程」でモデル検査を適用.ほとんどデバッグの一種.
– ソースコードからモデルを自動的に抽出.7
ソースコードモデル検査: 代表的なツール
• Java– Java Pathfinder (NASA)– Bandera (Kansas State Uni.)– Bogor (Kansas State Uni.)– JNuke (ETH)
• C/C++– Static Driver Verifier (Microsoft)– ZING (Microsoft)– BLAST (U. of Berkeley)– MAGIC (CMU)
8
Java Path Finder (JPF)• NASA Ames Research Center
– W. Visser らによって開発された
• 2005 年 4 月以降は sourceforge.net で公開
• Java のバイトコードから状態を生成してモデル検査
• 反例を出力
– 全ての実行系列について探索するので、違反が発見されるまでの経路を出力できる
• 拡張可能
– プロパティクラス、リスナークラスによりユーザが定義する特性の検証が可能
• 独自の VM– 初期の JPF は PROMELA/SPIN に変換していたが、独自の VM に
より実行するように改良された。 Java.awt、java.net には非対応9
JPFの動作原理 (1)
• Javaのクラスファイルは,Java Virtual Machine (JVM) によって解釈され,実行される.
11
*.class*.jar
Java Virtual Machine
....x ++;
....
(実際にはバイトコード)
a: 0b: 3x: 4 5y: -2
JPFの動作原理 (2)
• JPFは,自分のJVMを持っている.通常のJVMと同様に,クラスファイルを解釈できる.
• さらに,後戻り (backtrack) したり,スレッドスケジュールを外から与えることもできる.
12
*.class*.jar
Java Virtual Machine for JPF
....x ++;
....
(実際にはバイトコード)
a: 0b: 3x: 4 5y: -2
JPFの動作原理 (3)
• JPFの中心は,サーチエンジン.これが,JVM for JPF を駆動する.
13
サーチエンジンJVM
for JPF
次のバイトコードを実行せよ
このような状態になった.
これは以前の状態と同じだ.
後戻りして,スケジューリングを変えよ
次のバイトコードを実行せよ
JPFの動作原理 (4)
• 実際には,状態量が増えすぎないように様々な工夫が行われている.
– 同定可能な範囲でできるだけコンパクトな状態
– スレッド相互間に干渉のない部分は一気に実行 (半順序簡約)
• サーチエンジン,JVMも含め,すべてjavaで書かれている.
14
JPF構成図
15
最上位構造
• 2つの中心的な概念: SearchとJVM
• JVM: 状態生成 (右図)
• Search: JVMのドライバ+性質の評価
17
JPFの入手とバージョン
• 入手可能なバージョン: v3 と v4– v3: 安定版.ソースで配布.配布サイトではv1.0aと書かれている.J2SE1.4 に対応.(Java5では動作しない.)
– v4: 開発版.ソースで配布.頻繁に更新.v3と比べ,基本的な構成は同じ.使い勝手が向上している.Java5に対応.(J2SE1.4では動作しない.)
• ここでは v3 を使用する.http://sourceforge.net/projects/javapathfinder/ からjavapathfinder‐src‐libs‐1.0‐7‐oct‐05.zip をダウンロードして展開.(インストール指示書参照)
19
JPF の利用方法についての注意
• Java のバージョン
– J2SE 1.4 のみに対応.
– J2SE 1.5 (Java5.0)以降は使わないので注意すること
• Cygwin を用いる.(http://www.cygwin.com/)
• JPF のインストール先
– C:¥JPF¥jpf release
• 設定方法 (次スライド以降)
– 環境変数の設定
– jpf (C:¥JPF¥jpf release¥bin)20
環境変数の設定• 以下のディレクトリからファイルbashrcをダウンロードする.
http://hagi.is.s.u‐tokyo.ac.jp/pub/staff/hagiya/kougiroku/0521JPF_tanabe/
• 以下の2つの行の内容を確認し,必要なら書き直す.
• 次のコマンドを実行 (先頭のドットに注意).
• bashrcファイルの内容を ~/.bashrc に追記しておけば,次回から以上の操作は不要.(他の講義の設定と衝突しないかどうか確認すること.)
21
JAVA_HOME="C:¥usr¥local¥java¥j2sdk1.4.2_12"JPF_HOME="C:¥JPF¥jpf release"
$ . bashrc
22
# bashrc file for JPF#
# Set appropriate directories#JAVA_HOME="C:¥usr¥local¥java¥j2sdk1.4.2_12"JAVA_HOME="C:¥j2sdk1.4.2_14"#JPF_HOME="C:¥JPF¥jpf release"JPF_HOME="C:¥jpfv3¥release"
# Do not edit the following
JAVA_HOME_UNIX=`cygpath -u "${JAVA_HOME}"`JPF_HOME_UNIX=`cygpath -u "${JPF_HOME}"`export CLASSPATH="${JPF_HOME}¥build¥jpf;${JPF_HOME}¥lib;${JPF_HOME}¥examples;."export PATH="${JPF_HOME_UNIX}/bin:${JAVA_HOME_UNIX}/bin:/bin:/usr/bin:/usr/local/bin:${PATH}”
スクリプト jpf の置き換え• スクリプト ${JPF_HOME}/bin/jpf を,以下のディレクトリにある jpf で置き換える.念のため,オリジナルの jpf は保存しておく.
http://hagi.is.s.u‐tokyo.ac.jp/pub/staff/hagiya/kougiroku/0521JPF_tanabe/
$ diff jpf jpf‐original‐
7d6
< JPF_HOME_UNIX=`cygpath ‐m "${JPF_HOME}"`
37,38c36,37
< # JAVA_HOME=`cygpath ‐‐path ‐‐windows "$JAVA_HOME"`
< # CLASSPATH=`cygpath ‐‐path ‐‐windows "$CLASSPATH"`
‐‐‐
> JAVA_HOME=`cygpath ‐‐path ‐‐windows "$JAVA_HOME"`
> CLASSPATH=`cygpath ‐‐path ‐‐windows "$CLASSPATH"`
59,60c58
< # echo java $JVM_FLAGS ‐classpath "$CP" gov.nasa.jpf.JPF +jpf.basedir="$JPF_HOME_UNIX" $@
< java $JVM_FLAGS ‐classpath "$CP" gov.nasa.jpf.JPF +jpf.basedir="$JPF_HOME_UNIX" $@
‐‐‐
> java $JVM_FLAGS ‐classpath "$CP" gov.nasa.jpf.JPF +jpf.basedir="$JPF_HOME" $@ 23
JPFの基本機能
配布された (何もカスタマイズしない) 状態でできること
• デッドロックの検出
• アサーション違反の検出
• 捕捉されない例外の検出
24
最初の例(Deadlock.java)‐デッドロックの検出
25
$ cd "${JPF_HOME}"
$ javac examples/deadlock/Deadlock.java
$ java deadlock.Deadlock
…
途中でプログラムが停止
$ jpf deadlock.Deadlock
…
エラー出力については後のスライドで説明
26
1 package deadlock;
/*** This example shows a simple deadlock.
5 */public class Deadlock implements Runnable {
/*** A name for the thread.*/
10 String name;
/*** A reference to the other Deadlock object running as a separate thread.*/
15 Deadlock other;
public Deadlock (String name) {this.name = name;
}20public static void main (String[] args) {
Deadlock o1 = new Deadlock("A");Deadlock o2 = new Deadlock("B");
25 o1.other = o2;o2.other = o1;
27
Thread t1 = new Thread(o1);Thread t2 = new Thread(o2);
30t1.start();t2.start();
}
35 public void run () {while (true) {System.out.println(name + " cycle start");synchronized (this) {other.foo();
40 }
System.out.println(name + " cycle end");}
}45
synchronized void foo () {System.out.println(name + ".foo() was called");}
50 }
プログラムの説明
• プログラムの並列実行 31, 32
• 自分自身のロックを獲得 38
• 相手オブジェクトのメソッド foo() を呼ぶ 39
• ロックを開放する前に相手オブジェクトからメソッドを呼ばれるとデッドロックに陥る
28
29
1 C:¥jpf deadlock.DeadlockA cycle startB.foo() was calledA cycle end
5 A cycle startA cycle startB.foo() was calledA cycle endA cycle start
10 B.foo() was calledA cycle endA cycle startB.foo() was calledA cycle end
15 B cycle startA cycle startB.foo() was calledA cycle endA cycle end
20 A cycle start------------------------------------ thread stacksThread: Thread-0
at deadlock.Deadlock.run(deadlock¥Deadlock.java:57)
Thread: Thread-1at deadlock.Deadlock.run(deadlock¥Deadlock.java:57)
------------------------------------ end thread stacks
スレッド#0
スレッド#1
行番号 other.foo()
30
1Deadlock
----------------------------------- path to error (length: 88)Transition #0 Thread #0
5 deadlock¥Deadlock.java:40 Deadlock o1 = new Deadlock("A");deadlock¥Deadlock.java:35 public Deadlock (String name) {deadlock¥Deadlock.java:36 this.name = name;deadlock¥Deadlock.java:37 }deadlock¥Deadlock.java:40 Deadlock o1 = new Deadlock("A");
10 deadlock¥Deadlock.java:41 Deadlock o2 = new Deadlock("B");deadlock¥Deadlock.java:35 public Deadlock (String name) {deadlock¥Deadlock.java:36 this.name = name;deadlock¥Deadlock.java:37 }deadlock¥Deadlock.java:41 Deadlock o2 = new Deadlock("B");
15 deadlock¥Deadlock.java:43 o1.other = o2;deadlock¥Deadlock.java:44 o2.other = o1;deadlock¥Deadlock.java:46 Thread t1 = new Thread(o1);deadlock¥Deadlock.java:46 Thread t1 = new Thread(o1);deadlock¥Deadlock.java:47 Thread t2 = new Thread(o2);
20(snipped)
deadlock¥Deadlock.java:56 synchronized (this) {Transition #86 Thread #1
25 deadlock¥Deadlock.java:56 synchronized (this) {deadlock¥Deadlock.java:57 other.foo();
Transition #87 Thread #2deadlock¥Deadlock.java:57 other.foo();
------------------------------------ end error path
===================================1 Error Found: Deadlock
===================================
デッドロックとなる理由
31
synchronized (this) {
other.foo()
}
スレッド1
synchronized (this) {
other.foo()
}
t2
スレッド2
#85,86
#69,80
t1
#86
#87
(比較) デッドロックとはならない場合
32
synchronized (this) {
other.foo()
}
スレッド1
synchronized (this) {
other.foo();
}
t2
スレッド2
t1
t2t1
Javaアサーション
• 指定したboolean式の値がtrueになるべきであるという表明.
• 実行時に式の値がfalseになると,実行時エラー(java.lang.AssertionError)になる.
• 形式: assert p : m– p : チェック対象のboolean型の式
– m : エラーメッセージ「: m」の部分は省略可能.
• コンパイルオプション: javac -source 1.4• 実行時オプション: java -ea
33
JPFとアサーション
• 通常の実行時には,アサーションの位置に制御が渡ったときに,チェックが行われる.→ その実行によって通らないパスでは,チェックが行われない.
• JPFによる実行時には,すべてのパスでチェックが行われる.
34
例: AssertionTest1.java
• 2つのスレッド a1 と a2
• a1 が先に start. a2が後から start
• run の内容: 自分自身を「登録する」
• 登録の内容: at1 が空いていたら at1 に,そうでなかったらat2に代入.
• 両方登録が終わったとき,a1 == at1 になるか?
35
AssertionTest1.java
36
at1 = null; at2 = null;AssertionTest a1 = new AssertionTest(10);
AssertionTest a2 = new AssertionTest(20);
a1.start();a2.start();assert at1.getVal() < at2.getVal();
スレッド0
at1 = a1;at2 = a2;
スレッド1 スレッド2
null pointer exception
テストで検出できる.
JPFも,エラーを報告する.
JPFエラートレース
37
----------------------------------- path to error (length: 3)Transition #0 Thread #0(略)egs¥AssertionTest1.java:31 a1.start();egs¥AssertionTest1.java:32 a2.start();
Transition #1 Thread #0egs¥AssertionTest1.java:33 assert at1.getVal() < at2.getVal() : "unexpected order";
Transition #2 Thread #0egs¥AssertionTest1.java:33 assert at1.getVal() < at2.getVal() : "unexpected order";
------------------------------------ end error path
------------------------------------ thread stacksThread: Thread-0
at egs.AssertionTest1.run(egs¥AssertionTest1.java:25)
Thread: Thread-1at egs.AssertionTest1.run(egs¥AssertionTest1.java:25)
------------------------------------ end thread stacks
===================================1 Error Found: uncaught exception
===================================
AssertionTest2.java (1)
38
at1 = null; at2 = null;AssertionTest a1 = new AssertionTest(10);
AssertionTest a2 = new AssertionTest(20);
a1.start();a2.start();if (at2 != null) { ... }
if (at2 != null) {assert at1.getVal() < at2.getVal();
}
スレッド0
at1 = a1;// at1.getVal() == 10 at2 = a2;
// at2.getVal() == 20
スレッド1 スレッド2
assertion は成功
1CPUマシンでは,こうなることが多い(?)しかし,状況によっては.....
AssertionTest2.java (2)
39
at1 = null; at2 = null;AssertionTest a1 = new AssertionTest(10);
AssertionTest a2 = new AssertionTest(20);
a1.start();a2.start();if (at2 != null) { ... }
if (at2 != null) {assert at1.getVal() < at2.getVal();
}
スレッド0
at2 = a1;// at2.getVal() == 10
at1 = a2;// at1.getVal() == 20
スレッド1 スレッド2
assertion は失敗
テストでは検出できるとは限らない.
JPFでは,検出できる.
JPFエラートレース
40
----------------------------------- path to error (length: 20)Transition #0 Thread #0(略)egs¥AssertionTest2.java:31 a1.start();egs¥AssertionTest2.java:32 a2.start();
(略)Transition #7 Thread #2egs¥AssertionTest2.java:12 if (at1 == null) {at1 = at; }egs¥AssertionTest2.java:11 synchronized(lockObj) {
(略)Transition #11 Thread #1egs¥AssertionTest2.java:13 else {at2 = at; }egs¥AssertionTest2.java:11 synchronized(lockObj) {
(略)Transition #19 Thread #0egs¥AssertionTest2.java:18 public int getVal() { return val; }egs¥AssertionTest2.java:36 assert at1.getVal() < at2.getVal() : "unexpected order";
(略)------------------------------------ end error path
===================================1 Error Found: uncaught exception
===================================