Thread-modular Abstraction Refinement Tom Henzinger Ranjit Jhala Rupak Majumdar [UC Berkeley] Shaz Qadeer [Microsoft Research]
Introduction Model Checking Software –“Little theorems about big programs” –“automatic”, Path sensitive properties –Limited to sequential programs Thread-modular Reasoning –Efficiently decompose checks –Requires manual (or divine) intervention TAR: Thread-modular Abstraction Refinement –Eliminate the divine using abstraction-refinement –Safety checking for concurrent programs
The story so far... Analyzing Sequential programs –BLAST/SLAM/… –Iterative Abstraction-Refinement [Kurshan ’93] YES SAFE explanation NO! (Trace) BUG feasible Seed Abstraction Program Why infeasible ? infeasible Refine Abstract Is model safe ? Check
… and what of Concurrent Programs ? Shared Memory (multithreaded) Message Passing Hard to analyze ! –Interleavings / State explosion One approach: Thread-modular analysis –Analyse threads separately –Compose analyses [Jones ’83, CALVIN (FQS ’02), Assume-Guarantee]
The Problem boxes = threads white denotes shared variables Safety checking: Is an ERROR state reachable ?
Thread-modular analysis (take1) safe
Thread-modular analysis (take1) safe If only… ! Threads are correct in constrained environments
Second Attempt: Summaries “Summarize” each thread’s behavior Use/verify summaries (circular)
safe Use Summaries (“Assume”) safe
Verify Summaries (“Guarantee”) µ µ safe
Thread-modular analysis (take 2) µ safe µ
Our Contribution Problem with TM Reasoning: –Divining (small) summaries ? ? Algorithm TAR –Compute/use/verify summaries –Using iterative abstraction-refinement
An Example: Race Detection Producer { 1: while (*) { 2: while (flag) {}; 3: data = newdata(); 4: flag = true; } Consumer { 1: while (*) { 2: while (!flag) {}; 3: read = data; 4: flag = false; } Shared variables: data, flag, P, C Error states: P Æ C Initial states: : P Æ : C ( Æ : flag) P´P´ ´ C
An Example: Race Detection Producer { 1: while (*) { 2: while (flag) {}; 3: data = newdata(); 4: flag = true; } Consumer { 1: while (*) { 2: while (!flag) {}; 3: read = data; 4: flag = false; } P´P´ ´ C Correctness Invariant: –Producer ensures: P ) : flag –Consumer ensures: C ) flag
S Producer { : flag ! (flag’ Ç : flag’) Æ : P’ | : flag ! : flag’ Æ P’ } Summaries S Consumer { flag ! (flag’ Ç: flag’) Æ : C’ | flag ! flag’ Æ C’ } Consumer { 1: while (*) { 2: while (!flag) {}; 3: read = data; 4: flag = false; } Producer { 1: while (*) { 2: while (flag) {}; 3: data = newdata(); 4: flag = true; } Summary: Set of (present state, next state) pairs
Producer { 1: while (*) { 2: while (flag) {}; 3: data = newdata(); 4: flag = true; } Checking Safety [CALVIN] [use] Sequential program: Producer+ use BLAST/SLAM/ESC/… [verify] Every action of Producer+ is in S Producer Where do summaries come from? Producer+{ 1: while (*) { 2: while (flag) {}; 3: data = newdata(); 4: flag = true; } while(*){ s Consumer ();} safe µ
Abstraction & Reachability Abstraction gives finite state space Conservative – Abstraction safe ) System safe – Too coarse ) spurious counterexample Initial Error
Refinement Using “spurious” error traces
Refinement Using “spurious” error traces –Add information to rule out spurious trace –e.g. Track more variables or predicates Repeat reachability –Till safe or real trace is found
Abstraction & Reachability Using “spurious” error traces –Add information to rule out spurious trace –e.g. Track more variables or predicates Repeat reachability –Till safe or real trace is found safe
To Summarize Nodes labeled by abstract states Each parent-child pair ! (present, next) pair –Quantify out local state (e.g. program counter) –Take pairs where global state changes Reachability Tree ’
Producer+{ 1: while (*) { 2: while (flag) {}; 3: data = newdata(); 4: flag = true; } while(*){ s Consumer ();} Tying up the threads ;; Refine using “spurious” error traces Not yet the reachable set! Summarize
Refined System ;; safe Fixpoint
Running TAR on Example Producer { 1: while (*) { 2: while (flag) {}; 3: data = newdata(); 4: flag = true; } Consumer { 1: while (*) { 2: while (!flag) {}; 3: read = data; 4: flag = false; } Shared variables: data, flag, P, C Error states: P Æ C Initial states: : P Æ : C Æ : flag P´P´ ´ C
; Summary: : P Æ : C ! : P’ Æ C’ : P Æ C ! : P’ Æ : C’ Running TAR 1 ; Init: : P Æ : C Error: P Æ C Abs: P, C Reach: : P Æ : C P Æ : C Reach: : P Æ : C : P Æ C Summary: : P Æ : C ! P’ Æ : C’ P Æ : C ! : P’ Æ : C’ Consumer { 1: while (*) { 2: while (!flag) {}; 3: read = data; 4: flag = false; } Producer { 1: while (*) { 2: while (flag) {}; 3: data = newdata(); 4: flag = true; } P Æ C P´P´ ´ C
Summary: C Æ flag ! : C’ Æ flag’ : C Æ flag ! : C’ Æ : flag’ ! C’ Æ flag ; Summary: Running TAR 2 ; Init: : P Æ : C Æ : flag Error: P Æ C Abs: P, C, flag Reach: : P Æ : C Æ : flag P Æ : C Æ : flag : P Æ : C Æ flag Reach: : P Æ : C Æ : flag Summary: : P Æ: flag ! P‘ Æ : flag’ ! : P’ Æ flag’ P Æ : flag ! : P Æ: flag Consumer { 1: while (*) { 2: while (!flag) {}; 3: read = data; 4: flag = false; } Producer { 1: while (*) { 2: while (flag) {}; 3: data = newdata(); 4: flag = true; } : P Æ C Æ flag : C Æ flag : C Æ : flag P´P´ ´ C Only change if : flag Only change if flag : P Fixpoint Track flag : flagflag
; Summary: flag ! ( flag’ Ç : flag’) Æ : C’ ! flag’ Æ C’ Running TAR 2 ; Reach: P Æ : C Æ : flag : P Reach: : P Æ C Æ flag : C Summary: : flag ! (flag’ Ç : flag’) Æ : P’ ! : flag Æ P’ Consumer { 1: while (*) { 2: while (!flag) {}; 3: read = data; 4: flag = false; } Producer { 1: while (*) { 2: while (flag) {}; 3: data = newdata(); 4: flag = true; } P´P´ ´ C safe Fixpoint SUMMARIES LEARNT !
Bells and Whistles Havoc Abstractions: –Track only when the environment changes a variable Not what new value it changes it to –For every global x, x denotes states where thread writes x Summary: if ( x ) then x = * No explicit global variables –Sharing via pointers that escape
Race Detection w/ Pointers Producer { p = &buf; while (*) { while (p->flag) {}; p->data = newdata(); p->flag = true; p = p->next; } Consumer { q = &buf; while (*) { while (!q->flag) {}; read = q->data; q->flag = false; q = q->next; } data flag
Conclusions The moral … –TAR can check concurrent software –w/o (really) exploring all interleavings The devil … –Shared memory via pointers –Explicating local state Need to track some local state of “other” threads –Approximate Counterexample analysis Implemented TAR in BLAST –Race checking for drivers (each client is a thread) –Linux/Windows drivers 1-10 Kloc Looking for examples and properties …
BLAST Berkeley Lazy Abstraction Software * Tool
safe Use Summaries (“Assume”) safe
Verify Summaries (“Guarantee”) µ µ safe