計算機言語システム論 10/02 論文サーベイ
DESCRIPTION
計算機言語システム論 10/02 論文サーベイ. 吉野 寿宏 ( 米澤研究室 D1). 今回の論文. M. Naik, A. Aiken, J. Whaley. Effective Static Race Detection for Java . PLDI ’06. Race detection を行うシステム Chord を実装した http://www.stanford.edu/~mhn/chord.html にて入手可 Chord を用いて実際のアプリケーションの race condition を発見し、バグの修正につながった. - PowerPoint PPT PresentationTRANSCRIPT
計算機言語システム論 10/02
論文サーベイ吉野 寿宏
( 米澤研究室 D1)
今回の論文
M. Naik, A. Aiken, J. Whaley. Effective Static Race Detection for Java. PLDI ’06. Race detection を行うシステム Chord を実装
した http://www.stanford.edu/~mhn/chord.html にて入手
可 Chord を用いて実際のアプリケーションの ra
ce condition を発見し、バグの修正につながった
背景 :Race Detection
Race ( 競合 ) とは何か? マルチスレッドのプログラムが同一のメモリ領域
に対して同時に ( 同期なしに ) 参照を行うこと しかも少なくとも一つが書き込みアクセスである
ような場合
Race はバグの元である プログラムの invariant ( 常に成り立っているべき
条件 ) を壊すおそれがある スケジューリングなどの状況により発生し、再現
が難しい
An Example of Race
JdbF からの例
public class ConnectionSource { private Connection conn; private boolean used; public Connection getConnection() throws SQLException { if (!used) { used = true; return conn; } throw new SQLException(...); }};
ロックをかけていない スレッド
1 2
2 2 スレッドがスレッドがConnection Connection をを持ってしまう!持ってしまう!
既存研究 (1)
動的な検出 既存研究の多くはほとんどこちらの手法 Happens-before relation[L. Lamport 1978]
時間順に起こらなければならない制約を表す半順序 False negative が非常に多い ( 検出精度が悪い )
Lockset algorithm スレッドの保持しているロックを動的に管理 lock/unlock 以外の同期手法を使うと false positive
が出る
既存研究 (2)
静的な検出 本論文はこちらの手法に分類される 型検査器によるシステム
同期に関する挙動を型などで表す方式 しかし型を自動的に推論することは非常に難しい
モデル検査器によるシステム Path sensitive な解析をするのでさまざまな同期機
構に対応できる しかし open program には対応できない
静的 Lockset algorithm
Static Race Detection はどうあるべきか?
5 つの指標を掲げている 正確さ
False positive ( 誤検知 ) の確率は十分に低いか? スケーラビリティ
大規模なプログラムも現実的な時間で解析できるか? 幅広い同期機構への対応
現実に用いられている同期機構に対応できるか? それ自身で閉じていないプログラム ( ライブラリ
やデバイスドライバ等 ) への対応
( デバッグのための ) 反例生成の枠組み
Chord
中核は k-object sensitivity[Whaley, Lam 2004] によるエリアス解析 BDD (Binary Decision Diagram) を用いて効率化
bddbddb[Lam, Whaley 2005] による実装 全体として 4 つのステージ
Reachable-/Aliasing-/Escaping-/Unlocked-Pairs Computation
いずれも実行箇所のペア ( 別スレッドで同時に実行される箇所を抽出したいことに注意 ) を扱う
各ステージで、 race にならないものを枝狩りしていく Flow-insensitive な解析 ⇒ Unsound な手法
False positive/false negative が出る可能性がある
Chord: システム構成図
Open Program の扱い
それ自体では完結しないプログラムは、補完してから検証の必要がある ライブラリ等は、自身がどのように呼び出され
るかわからない (missing caller) テスト用の harness を生成してやる必要
またプログラムが依存するライブラリの挙動が不明な場合もある (missing callee)
Java の native method など Chord では一部をモデル化し、それ以外は nop と等
価として扱っている ( このあたりも unsoundness の要因 )
Test Harness Synthesis
プログラムを解析するにあたって、エントリポイント (main) を自動生成 チェックしたいインターフェイス (I とする ) に含ま
れるメソッドの引数をローカル変数として準備 参照型の変数に、非決定的に同じ型の値を代入 I のそれぞれのメソッドを、上で準備した値を用い
て非決定的に呼び出し、結果を型の合う変数に代入
詳細は後ほど例を出します
Synchronization Idioms
Java にはいくつかの同期手法がある synchronized(expr) { … }
自然に扱うことができる fork/join synchronization
Flow sensitive な ( プログラムの場所を考慮する )解析が必要
Chord は flow insensitive なので、ユーザが annotation をつけてやる必要がある
オブジェクトのモニタ (Object#wait/notify) あまり重要ではない、と著者らは書いている
Stage 1:Reachable-Pairs Computation
Original-Pairs Computation
まず最初に、プログラムの中で同じ変数を使う可能性のある場所をすべて列挙する インスタンス変数 f 、 static 変数 g 、配列 a への
read/write アクセスを行う場所の組 Soot framework[R. Vallée-Rai et al. 1999] により求める インスタンス変数は、同一インスタンスの中のみを考慮
すればよい
しかしこれはものすごく大雑把な解析⇒ 後段の解析により refine してい
く
Original-Pairs Computation
Original-Pairs Computation: 例public class A { int f;
public A() { this.f = 0; } private int rd() { return this.f; } private int wr(int x) { this.f = x; return x; } public int get() { return this.rd(); } public synchronized int inc() { int t = this.rd() + (new A()).wr(1); return this.wr(t); }}
writeread
OriginalPairs ={(fr, fw), (fw, fw), (fr, f’w), (f’w, f’w), (fw, f’w)}
Reachable-Pairs Computation
プログラムのエントリポイントから到達可能でなければ race は起こらない Race を起こす組は、 main からどこかスレッド
を立ち上げる箇所を経由して到達可能なはず
到達可能性により Original-Pairs を枝狩り コールグラフ ( 呼び出し - 呼び出されの関係をグ
ラフにしたもの ) の解析による
Call Graph
遷移関係の定義
は call site と context から、そこで呼ばれるメソッドと context の組を与える関数
Ifork は、スレッドを立ち上げる (thread-spawning) call site の集合
→ と ⇒ の違いは、スレッドの起動が起こりうるかどうか
→n 、⇒ n も帰納的に定義できる
Computation of Reachable Pairs
Call Graph の関係 ( の反射推移閉包 ) を用いて、次のように計算される
は main から到達可能な thread-spawning call site とコンテクストの集合
は call site ( またはメモリアクセス ) からそれが含まれるメソッドを返す関数
ReachablePairs が求める集合
public class A { public int get() { return this.rd(); } public synchronized int inc() { int t = this.rd() + (new A()).wr(1); return this.wr(t); }
static public void main() { A a; if(*) a = new A(); if(*) a.get(); if(*) a.inc(); }}
Reachable-Pairs Computation: 例
ReachablePairs ={(fr, fw), (fw, fw), (fr, f’w), (f’w, f’w), (fw, f’w)}
コンストラクタの race は検出しな
いので消える
Test Harness
public class A { public int get() { return this.rd(); } public synchronized int inc() { int t = this.rd() + (new A()).wr(1); return this.wr(t); }
static public void main() { A a; if(*) a = new A(); if(*) a.get(); if(*) a.inc(); }}
Reachable-Pairs Computation: 例
ReachablePairs ={(fr, fw), (fw, fw), (fr, f’w), (f’w, f’w), (fw, f’w)}
ReachablePairs ={(fr, fw), (fw, fw), (fr, f’w), (f’w, f’w), (fw, f’w)}
ReachablePairs ={(fr, fw), (fw, fw), (fr, f’w), (f’w, f’w), (fw, f’w)}
Stage 2:Aliasing-Pairs Computation
Aliasing-Pairs Computation
同じ変数実体に同時にアクセスしていない限り race は起こらない あるオブジェクトの異なるインスタンスにある同一名の変
数 エリアスしていない配列へのアクセス
エリアスしていてもインデックスが異なれば race にはならないが、ここでは保守的にインデックスは省いて考えている
Reachable-Pairs として計算されたものからエリアス解析により枝狩り k-object sensitive alias analysis[Milanova et al. 2002] の手法をここ
では用いる Flow insensitive, context/object/field sensitive analysis Inclusion-base analysis
エリアス解析 (Alias Analysis)
オブジェクトへの参照が同じ場所を指しているかどうかを静的に解析する手法 参照 = ポインタ (C, C++) 、参照 (C++, Java, C
# ...)
たとえば、次のコードは安全か?void swap(int* x, int* y) { *x ^= *y ^= *x ^= *y; }
x == y だったら *x も *y も 0 になってしまう! そうならないことを保証するために、エリアス解析
が有効
Stage 3:Escaping-Pairs Computation
Escaping-Pairs Computation
複数のスレッドに共有されていないデータは race を引き起こさない Aliasing-Pairs をさらに枝狩りできる
データ共有のためには 2 つの場合がある メソッド呼び出し (thread-spawning) における引数
explicit な呼び出し (java.lang.Runnable#start) implicit な呼び出し (main から )
オブジェクトのフィールドとしての共有
Stage 4:Unlocked-Pairs Computation
Unlocked-Pairs Computation
( 共通の ) ロックをとっている組は race を引き起こさない Call graph と alias analysis から導出できる しかし計算量等の観点から、いくつか unsound
な仮定が必要になる 次で説明
Unlocked-Pairs Computation
UnlockedPairs’’ のほうが正確 しかし UnlockedPairs’’ はすべてのパスについて計算する
必要があるので計算量が膨大 UnlockedPairs’’ ⊆ UnlockedPairs’ になっている ( 証明済 )
つまり、 false positive が出る可能性がある
May-alias の解析しかしていない 「正しく」やるためには、 must-alias (True と判断された
場合は必ず alias) の解析が必要 ⇒ Chord が race-free と結論づけた場合でも、ひょっとすると別のオブジェクトに対して同期しているかもしれない
False negative が出る可能性がある しかし実験中にそのような不都合は起きなかった、とのこと
後処理
最後まで残った変数アクセスの組を race の可能性があるものとして報告 そこに至るまでのパスを反例として出力 どの変数、またはオブジェクトが race を起こ
しているのかを表示する
しかし最終的に本当のバグによるものかを判断するには人手が必要
public class A { int f;
public A() { this.f = 0; } private int rd() { return this.f; } private int wr(int x) { this.f = x; return x; } public int get() { return this.rd(); } public synchronized int inc() { int t = this.rd() + (new A()).wr(1); return this.wr(t); }}
Escaping-Pairs ={(fr, fw), (fw, fw)}
Unlocked-Pairs Computation: 例
inc() は synchronized メソッドなので、同時に到達することはない
Chord の評価実験
実際に広く使われているプログラムを検証
実験結果 (1)
速度 646KLoC を 30 分弱で検査完了 大規模プロジェクトでも使用に耐える
アノテーションはほとんど必要なかった 手動でつけてもそれほど
苦にはならない
実験結果 (2)
発見された race False alarm が出ている箇所はさほど多くない
実際開発者に報告して修正がされたケースも Apache commons-pool における 17 箇所のバグの
指摘に対し、実際に 5 箇所に修正パッチが出た
まとめ
静的解析により race を見つけ出すための画期的な手法を提案した bddbddb を用いてシステムを実装した 広く使われている ( マルチスレッド ) プログラ
ムを用いてベンチマークを行った
かなり巨大なプログラムでも現実的時間内で解析可能であることが確かめられた
False positive の数が少ないことが確かめられた 実際にバグの解決に役立った事例もある