470 likes | 596 Vues
This paper presents an innovative approach to localizing the causes of errors in counterexample traces generated by model checking. Unlike traditional methods that yield a single error trace, this technique allows the generation of multiple distinct error traces, enabling effective identification of underlying error causes. By defining a "cause" as portions of an error trace that are absent in any correct trace, the method leverages unreachable program fragments for better error diagnosis. Experimental results demonstrate the efficiency of our algorithm in the SLAM toolkit context, improving error detection and characterization.
E N D
From Symptom to Cause: Localizing Errors in Counterexample Traces Mayur Naik, Purdue University Thomas Ball, Microsoft Research Sriram K. Rajamani, Microsoft Research
Model Checking + Fully automatic: Does not require the user to provide annotations. + Transparent: Produces a source-level error trace (counterexample). −An error trace represents a symptom of the error as opposed to its cause. − State-of-the-art model checkers report only one error trace.
The Problem • How do we localize the cause of the errorin an error trace? • How do we produce multiple error traces having distinct causes? Note: Problem is relevant to other error-detection techniques as well.
What is a “Cause”? • We define a “cause” to be those parts of an error trace not contained in any correct trace. • The program fragments containing the cause are rendered unreachable. • The model checker is invoked again to produce additional error traces.
Example main() { AcquireLock(); if (...) ReleaseLock(); else { ... } AcquireLock(); if (...) ReleaseLock(); else { ... } return; }
Error #1: Lock acquired in succession main() { AcquireLock(); if (...) ReleaseLock(); else { ... } AcquireLock(); if (...) ReleaseLock(); else { ... } return; }
Correct Trace Computation main() { AcquireLock(); if (...) ReleaseLock(); else { ... } AcquireLock(); if (...) ReleaseLock(); else { ... } return; }
Error Cause Localization main() { AcquireLock(); if (...) ReleaseLock(); else { ... } AcquireLock(); if (...) ReleaseLock(); else { ... } return; }
Unreachable from entry of main in future runs of the model checker Inserthalt Error Recovery main() { AcquireLock(); if (...) ReleaseLock(); else { ... } AcquireLock(); if (...) ReleaseLock(); else { ... } return; }
Error #2: Lock held on exit main() { AcquireLock(); if (...) ReleaseLock(); else { halt; ... } AcquireLock(); if (...) ReleaseLock(); else { ... } return; }
Correct Trace Computation main() { AcquireLock(); if (...) ReleaseLock(); else { halt; ... } AcquireLock(); if (...) ReleaseLock(); else { ... } return; }
Error Cause Localization main() { AcquireLock(); if (...) ReleaseLock(); else { halt; ... } AcquireLock(); if (...) ReleaseLock(); else { ... } return; }
Unreachable from entry of main in future runs of the model checker Inserthalt Error Recovery main() { AcquireLock(); if (...) ReleaseLock(); else { halt; ... } AcquireLock(); if (...) ReleaseLock(); else { ... } return; }
Final (Error-Free) Program main() { AcquireLock(); if (...) ReleaseLock(); else { halt; ... } AcquireLock(); if (...) ReleaseLock(); else { halt; ... } return; }
Our Results • A technique that exploits correct traces for error cause localization. • Efficient algorithm for computing correct traces. • Experimental results in the context of the SLAM toolkit.
Transitions and Edges 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 AcquireLock();
Transitions and Edges 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 AcquireLock(); <(3, L), (5, U)>
Transitions and Edges 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 AcquireLock(); project(<(3, L), (5, U)>) = (3, 5)
High-Level Algorithm while truedo switch ModelCheck(G, ve) of //ve is of the form assert(e) case FAILURE(T): let C = GetCorrectTransitions(G, ve) and K = project(T) \ project(C) in if K = Ø then break for each (vi, vj) in K do insert a halt statement between vi and vj case SUCCESS: break
Computing Correct Transitions 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 AcquireLock(); (1,U) (2,L) (3,L) (4,L) ve≡assert(s==U) (5,U) (5,L) Reachable state-space computed by model checker <(1, U), (2, L)> <(2, L), (4, L)> <(4, L), (5, L)> Transitions in Error Trace (T)
Computing Correct Transitions 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 AcquireLock(); (1,U) (2,L) (3,L) (4,L) ve≡assert(s==U) (5,U) (5,L) <(1, U), (2, L)> <(2, L), (4, L)> <(4, L), (5, L)> Transitions in Error Trace (T) Correct Transitions (C)
Computing Correct Transitions 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 AcquireLock(); (1,U) (2,L) (3,L) (4,L) ve≡assert(s==U) (5,U) (5,L) <(1, U), (2, L)> <(2, L), (4, L)> <(4, L), (5, L)> <(3, L), (5, U)> Transitions in Error Trace (T) Correct Transitions (C)
Computing Correct Transitions 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 AcquireLock(); (1,U) (2,L) (3,L) (4,L) ve≡assert(s==U) (5,U) (5,L) <(1, U), (2, L)> <(2, L), (4, L)> <(4, L), (5, L)> <(2, L), (3, L)> <(3, L), (5, U)> Transitions in Error Trace (T) Correct Transitions (C)
Computing Correct Transitions 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 AcquireLock(); (1,U) (2,L) (3,L) (4,L) ve≡assert(s==U) (5,U) (5,L) <(1, U), (2, L)> <(2, L), (4, L)> <(4, L), (5, L)> <(1, U), (2, L)> <(2, L), (3, L)> <(3, L), (5, U)> Transitions in Error Trace (T) Correct Transitions (C)
(1,U) (1,U) (2,L) (2,L) (3,L) (4,L) (5,U) (5,L) (6,U) (6,L) Example 1: An omission error 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 ... 6 AcquireLock(); ... <(1, U), (2, L)> <(2, L), (4, L)> <(4, L), (5, L)> <(5, L), (6, L)> <(1, U), (2, L)> <(2, L), (3, L)> <(3, L), (5, U)> <(5, U), (6, U)> Transitions in Error Trace (T) Correct Transitions (C)
(1,U) (1,U) (2,L) (2,L) (3,L) (4,L) (5,U) (5,L) (6,U) (6,L) Error Cause Localization 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 ... 6 AcquireLock(); ... <(1, U), (2, L)> <(2, L), (4, L)> <(4, L), (5, L)> <(5, L), (6, L)> <(1, U), (2, L)> <(2, L), (3, L)> <(3, L), (5, U)> <(5, U), (6, U)> K = project (T) \ project (C) Transitions in Error Trace (T) Correct Transitions (C)
(1,U) (1,U) (2,L) (2,L) (3,L) (4,L) (5,U) (5,L) (6,U) (6,L) Error Cause Localization 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 ... 6 AcquireLock(); ... <(1, U), (2, L)> <(2, L), (4, L)> <(4, L), (5, L)> <(5, L), (6, L)> <(1, U), (2, L)> <(2, L), (3, L)> <(3, L), (5, U)> <(5, U), (6, U)> K = project (T) \ project (C) Transitions in Error Trace (T) Correct Transitions (C)
(1,U) (1,U) (2,L) (2,L) (3,L) (4,L) (5,U) (5,L) (6,U) (6,L) Error Cause Localization 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 ... 6 AcquireLock(); ... <(1, U), (2, L)> <(2, L), (4, L)> <(4, L), (5, L)> <(5, L), (6, L)> <(1, U), (2, L)> <(2, L), (3, L)> <(3, L), (5, U)> <(5, U), (6, U)> K = project (T) \ project (C) Transitions in Error Trace (T) Correct Transitions (C)
(1,U) (2,L) (3,L) (5,U) (6,U) Error Cause Localization 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 ... 6 AcquireLock(); ... (1,U) (2,L) (4,L) (5,L) (6,L) <(1, U), (2, L)> <(2, L), (4, L)> <(4, L), (5, L)> <(5, L), (6, L)> <(1, U), (2, L)> <(2, L), (3, L)> <(3, L), (5, U)> <(5, U), (6, U)> K = project (T) \ project (C) = { (2, 4), (4, 5) } Transitions in Error Trace (T) Correct Transitions (C)
(1,(S,S)) (2,(S,S)) (4,(S,S)) (5,(S,F)) (6,(S,F)) Error Trace Example 2: A variable-value error main() { 1 int status = S; 2 if (*) 3 status = foo(); else { 4 foo(); 5 status = S; } 6 assert(status==x); } (1,(S,S)) (2,(S,S)) (3,(S,S)) (6,(S,S)) (6,(F,F)) enum { S, F } x = S; int foo() { if (*) x = S; else x = F; return x; } Correct Traces Program state is of the form (status, x)
(1,(S,S)) (1,(S,S)) (2,(S,S)) (2,(S,S)) (3,(S,S)) (4,(S,S)) (4,(S,S)) (5,(S,S)) (5,(S,F)) (6,(S,S)) (6,(S,F)) (6,(F,F)) Correct Traces Error Trace Example 2: A variable-value error main() { 1 int status = S; 2 if (*) 3 status = foo(); else { 4 foo(); 5 status = S; } 6 assert(status==x); } enum { S, F } x = S; int foo() { if (*) x = S; else x = F; return x; } Program state is of the form (status, x)
(1,(S,S)) (1,(S,S)) (2,(S,S)) (2,(S,S)) (3,(S,S)) (4,(S,S)) (4,(S,S)) (5,(S,S)) (5,(S,F)) (6,(S,S)) (6,(S,F)) (6,(F,F)) Correct Traces Error Trace Error Cause Localization main() { 1 int status = S; 2 if (*) 3 status = foo(); else { 4 foo(); 5 status = S; } 6 assert(status==x); } enum { S, F } x = S; int foo() { if (*) x = S; else x = F; return x; } K = project (T) \ project (C) = Ø
High-Level Algorithm while truedo switch ModelCheck(G, ve) of //ve is of the form assert(e) case FAILURE(T): let C = GetCorrectTransitions(G, ve) and K = project(T) \ project(C) in if K = Ø then break for each (vi, vj) in K do insert a halt statement between vi and vj case SUCCESS: break
High-Level Algorithm while truedo switch ModelCheck(G, ve) of //ve is of the form assert(e) case FAILURE(T): let C = GetCorrectTransitions(G, ve) and K = project(T \ C) in if K = Ø then break for each (vi, vj) in K do insert a halt statement between vi and vj case SUCCESS: break
(1,(S,S)) (1,(S,S)) (2,(S,S)) (2,(S,S)) (3,(S,S)) (4,(S,S)) (4,(S,S)) (5,(S,S)) (5,(S,F)) (6,(S,S)) (6,(S,F)) (6,(F,F)) Correct Traces Error Trace Example 2: A variable-value error main() { 1 int status = S; 2 if (*) 3 status = foo(); else { 4 foo(); 5 status = S; } 6 assert(status==x); } enum { S, F } x = S; int foo() { if (*) x = S; else x = F; return x; } Program state is of the form (status, x)
(1,(S,S)) (2,(S,S)) (3,(S,S)) (4,(S,S)) (5,(S,S)) (6,(S,S)) (6,(F,F)) Correct Traces Error Cause Localization main() { 1 int status = S; 2 if (*) 3 status = foo(); else { 4 foo(); 5 status = S; } 6 assert(status==x); } (1,(S,S)) (2,(S,S)) (4,(S,S)) (5,(S,F)) (6,(S,F)) enum { S, F } x = S; int foo() { if (*) x = S; else x = F; return x; } Error Trace K = project (T \ C) = { (4, 5), (5, 6) }
(1,U) (1,U) (2,L) (2,L) (3,L) (4,L) (5,U) (5,L) (6,U) (6,L) Example 1: An omission error 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 ... 6 AcquireLock(); ... <(1, U), (2, L)> <(2, L), (4, L)> <(4, L), (5, L)> <(5, L), (6, L)> <(1, U), (2, L)> <(2, L), (3, L)> <(3, L), (5, U)> <(5, U), (6, U)> Transitions in Error Trace (T) Correct Transitions (C)
(1,U) (1,U) (2,L) (2,L) (3,L) (4,L) (5,U) (5,L) (6,U) (6,L) Error Cause Localization 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 ... 6 AcquireLock(); ... <(1, U), (2, L)> <(2, L), (4, L)> <(4, L), (5, L)> <(5, L), (6, L)> <(1, U), (2, L)> <(2, L), (3, L)> <(3, L), (5, U)> <(5, U), (6, U)> K = project (T \ C) Transitions in Error Trace (T) Correct Transitions (C)
(1,U) (1,U) (2,L) (2,L) (3,L) (4,L) (5,U) (5,L) (6,U) (6,L) Error Cause Localization 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 ... 6 AcquireLock(); ... <(1, U), (2, L)> <(2, L), (4, L)> <(4, L), (5, L)> <(5, L), (6, L)> <(1, U), (2, L)> <(2, L), (3, L)> <(3, L), (5, U)> <(5, U), (6, U)> K = project (T \ C) Transitions in Error Trace (T) Correct Transitions (C)
(1,U) (1,U) (2,L) (2,L) (3,L) (4,L) (5,U) (5,L) (6,U) (6,L) Error Cause Localization 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 ... 6 AcquireLock(); ... <(1, U), (2, L)> <(2, L), (4, L)> <(4, L), (5, L)> <(5, L), (6, L)> <(1, U), (2, L)> <(2, L), (3, L)> <(3, L), (5, U)> <(5, U), (6, U)> K = project (T \ C) Transitions in Error Trace (T) Correct Transitions (C)
(1,U) (1,U) (2,L) (2,L) (3,L) (4,L) (5,U) (5,L) (6,U) (6,L) Error Cause Localization 1 AcquireLock(); 2 if (...) 3 ReleaseLock(); else { 4 ... } 5 ... 6 AcquireLock(); ... <(1, U), (2, L)> <(2, L), (4, L)> <(4, L), (5, L)> <(5, L), (6, L)> <(1, U), (2, L)> <(2, L), (3, L)> <(3, L), (5, U)> <(5, U), (6, U)> K = project (T \ C) = { (2, 4), (4, 5), (5, 6) } Transitions in Error Trace (T) Correct Transitions (C)
Limitations • Control-based approach fails to localize the cause when every edge in the error trace is contained in some correct trace. • Transition-based approach localizes the cause to a suffix of the error trace. • Model Imprecision: Infeasible paths can misguide error cause localization using either approach.
Related Work Multiple Counterexamples • Verisim [Bhargavan et al., TSE ’02] Error Cause Localization • Explaining counterexamples [Jin et al., TACAS ’02] • Explaining type errors [Wand, POPL ’86; Johnson & Walz, POPL ’86; Beaven & Stansifer, LOPLAS ’93; Duggan & Bent, SCP ’96; Chitil, ICFP ’01; Tip & Dinesh, TOSEM ’01] • Program Slicing [Weiser, TSE ’84] • Algorithmic Debugging [Shapiro, Ph.D. thesis ’82] • Delta Debugging [Zeller, FSE ’99] Anomaly Detection • Static: Meta-Level Compilation [Hallem et al., PLDI ’02] • Dynamic: Daikon [Ernst et al., TSE ’01], DIDUCE [Hangal & Lam, ICSE ’02]
Conclusions • We have presented a technique for localizing the causes of errors in counterexample traces. • A combination of the control-based and transition-based approaches appears promising. • Our technique is quite general and should be applicable to error detection tools based on data-flow analysis as well.