approximating the shuffle of context-free languages to find bugs

48
IT 11 062 Examensarbete 30 hp Augusti 2011 Approximating the Shuffle of Context-free Languages to Find Bugs in Concurrent Recursive Programs Jari Stenman Institutionen för informationsteknologi Department of Information Technology

Upload: others

Post on 03-Feb-2022

1 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Approximating the Shuffle of Context-free Languages to Find Bugs

IT 11 062

Examensarbete 30 hpAugusti 2011

Approximating the Shuffle of Context-free Languages to Find Bugs in Concurrent Recursive Programs

Jari Stenman

Institutionen för informationsteknologiDepartment of Information Technology

Page 2: Approximating the Shuffle of Context-free Languages to Find Bugs
Page 3: Approximating the Shuffle of Context-free Languages to Find Bugs

Teknisk- naturvetenskaplig fakultet UTH-enheten Besöksadress: Ångströmlaboratoriet Lägerhyddsvägen 1 Hus 4, Plan 0 Postadress: Box 536 751 21 Uppsala Telefon: 018 – 471 30 03 Telefax: 018 – 471 30 00 Hemsida: http://www.teknat.uu.se/student

Abstract

Approximating the Shuffle of Context-free Languagesto Find Bugs in Concurrent Recursive Programs

Jari Stenman

Concurrent programming in traditional imperative languages is hard. The huge number of possible thread interleavings makes it difficult to find and correct bugs. We consider the reachability problem for concurrent recursive programs, which, in general, is undecidable. These programs have a natural model in systems of pushdown automata, which recognize context-free languages. We underapproximate the shuffle of context-free languages corresponding to single threads. The shuffle of two languages is the language you get by taking every pair of words in their cross-product and interleaving them in a way that preserves the order within the original words. We intersect this language with the language of erroneous runs, and get a context-free language. If this language is nonempty, the concurrent program contains an error. We implement a prototype tool using this technique, and use it to find errors in some example program, including a Windows NT Bluetooth driver. We believe that our approach complements context-bounded model checking, which finds errors only up to a certain number of context switches.

Tryckt av: Reprocentralen ITCIT 11 062Examinator: Anders JanssonÄmnesgranskare: Parosh AbdullaHandledare: Mohamed Faouzi Atig

Page 4: Approximating the Shuffle of Context-free Languages to Find Bugs
Page 5: Approximating the Shuffle of Context-free Languages to Find Bugs

Contents

1 Introduction 5

2 Background 6

2.1 Nondeterministic finite automata . . . . . . . . . . . . . . . . . . 62.2 Pushdown automata . . . . . . . . . . . . . . . . . . . . . . . . . 82.3 Pushdown automata from recursive programs . . . . . . . . . . . 92.4 Context-free grammars . . . . . . . . . . . . . . . . . . . . . . . . 122.5 Context-free grammars from pushdown automata . . . . . . . . . 132.6 Converting to 2NF . . . . . . . . . . . . . . . . . . . . . . . . . . 142.7 Minimizing Context-free grammars . . . . . . . . . . . . . . . . . 14

2.7.1 Removal of non-generating variables . . . . . . . . . . . . 142.7.2 Removal of non-reachable variables . . . . . . . . . . . . . 152.7.3 Removal of ε-productions . . . . . . . . . . . . . . . . . . 16

2.8 Deciding emptiness . . . . . . . . . . . . . . . . . . . . . . . . . . 172.9 Intersecting context-free grammars with finite automata . . . . . 17

3 The shuffle operation 18

4 Shuffle grammars 22

4.1 Shuffle up to 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224.2 Shuffle up to k . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

5 Implementation 25

6 Examples 27

6.1 Simple counter . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286.2 Bluetooth driver . . . . . . . . . . . . . . . . . . . . . . . . . . . 316.3 Mozilla bug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386.4 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

7 Discussion and conclusions 43

8 Related work 44

3

Page 6: Approximating the Shuffle of Context-free Languages to Find Bugs

List of Figures

1 NFA recognizing {(ab)n |n ∈ N} . . . . . . . . . . . . . . . . . . . 82 PDA recognizing {anbn |n ∈ N} . . . . . . . . . . . . . . . . . . . 93 Example program . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 Example control flow graph . . . . . . . . . . . . . . . . . . . . . 115 Rules of resulting PDA . . . . . . . . . . . . . . . . . . . . . . . . 126 The NFA R . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207 An overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218 Example shuffle . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249 Simple counter: Simple recursive counter program . . . . . . . . 2810 Simple counter: Control flow graph . . . . . . . . . . . . . . . . . 2911 Simple counter: New control flow graphs . . . . . . . . . . . . . . 3012 Simple counter: transitions . . . . . . . . . . . . . . . . . . . . . 3013 Simple counter: NFA characterizing valid runs . . . . . . . . . . 3114 Bluetooth driver: adder() and stopper() . . . . . . . . . . . . . . 3215 Bluetooth driver: inc() and dec() . . . . . . . . . . . . . . . . . . 3316 Bluetooth driver: Control flow graphs for adder and stopper . . . 3417 Bluetooth driver: Control flow graphs for dec and inc . . . . . . 3518 Bluetooth driver: Counter transitions . . . . . . . . . . . . . . . 3619 Bluetooth driver: Adder transitions . . . . . . . . . . . . . . . . . 3720 Bluetooth driver: Stopper transitions . . . . . . . . . . . . . . . . 3721 Bluetooth driver: Ordering of stop-flag . . . . . . . . . . . . . . 3822 Bluetooth driver: Ordering of stopped . . . . . . . . . . . . . . . 3823 Bluetooth driver: Synchronization of stop-driver . . . . . . . . . 3924 Bluetooth driver: Synchronization of conditionals . . . . . . . . . 3925 Bluetooth driver: Synchronization of counter updates . . . . . . 4026 Bluetooth driver: Error traces . . . . . . . . . . . . . . . . . . . . 4027 Mozilla: Simplified code from Mozilla Application Suite . . . . . 4128 Mozilla: Transitions of P1 . . . . . . . . . . . . . . . . . . . . . . 4229 Mozilla: Transitions of P2 . . . . . . . . . . . . . . . . . . . . . . 4230 Mozilla: Enforcing lock semantics . . . . . . . . . . . . . . . . . . 4331 Mozilla: Enforcing variable semantics . . . . . . . . . . . . . . . . 4332 Mozilla: Error traces . . . . . . . . . . . . . . . . . . . . . . . . . 43

4

Page 7: Approximating the Shuffle of Context-free Languages to Find Bugs

1 Introduction

It is widely acknowledged that concurrent programming in traditional sequentialprogramming languages is hard. Programmers’ disposition towards sequentialthinking, coupled with the huge amount of potential interleaving runs in a con-current program, lead to errors that are very difficult to find and fix. At thesame time, concurrent programming is becoming more and more important,since the number of cores in our devices is increasing.

We are seeing extensive research to counter the difficulties associated withconcurrent programming. This research is done in many different areas, in-cluding programming language design, compiler design, testing and verification.Our focus is on the verification part; to ensure high-quality concurrent software,we need efficient bug-finding and verification tools.

We are interested in checking safety properties for concurrent recursive pro-grams. This reduces to the control-point reachability problem, which is un-decidable even for boolean programs (i.e. where all variables have a booleandomain) [11]. Therefore, we cannot formally verify the absence of errors, butwe can still have useful procedures that can detect some errors. One approachto this problem is called context-bounded model checking [9]. Context-boundedmodel checking explores all possible interleaving runs up to a constant number kcontext-switches. The technique is sound and precise up to the bound, meaningthat all errors in the first k context-switches are detected, and all reported er-rors are real errors. The intuition is that the majority of errors manifest withina small number of context-switches [7].

We are trying to complement context-bounded model checking with a tech-nique that detects errors regardless of the number of context-switches. Insteadof bounding the number of context-switches, we bound the granularity of theapproximation of interleaving runs. This technique is based on grammars. Theidea is that we take a program consisting of several threads, and generate aformal grammar which produces an approximation of all possible interleavingruns of the threads. We can then check if this grammar includes properties thatwe want to avoid.

The rest of this report is structured as follows. In section 2, we give thenecessary theoretical background. In sections 3 and 4, we describe how thetechnique works. Section 5 contains a short description of the implementation.In section 6, we demonstrate the technique on 3 example programs with varyingcharacteristics. We discuss the results in section 7. Finally, section 8 discusses

5

Page 8: Approximating the Shuffle of Context-free Languages to Find Bugs

some related work.

2 Background

To get started, we need to define some basic notions of automata and formallanguage theory.

Definition 1 (Alphabets, words and languages). An alphabet is a finite setof symbols. A word over an alphabet is a finite sequence of symbols from thatalphabet. The length |w| of a word w is the number of symbols in it. We denotethe empty word (which has length 0) by ε. The concatenation of two wordsw1 and w2 is the word w1w2. We have, for all words w, that wε = εw = w.The n-th power of a word w is the word w...w︸ ︷︷ ︸

n times

. A language is a set of words

over the same alphabet. If Σ is an alphabet, the language denoted by Σ∗ is theset of all words over Σ.

For example, let Σ = {0, 1} be an alphabet. Then Σ∗ = {ε, 0, 1, 00, 11, 01, 10, ...}.Note that for any alphabet Σ, we have ε ∈ Σ∗. When we take the n-th powerof a word w = a1...ak, we write an1 when k = 1 and (a1...ak)n when k > 1.The parantheses show which part is to be repeated; they are not symbols. Forexample, ab2 = abb, but (ab)2 = abab. For any word w, w0 = ε.

We are now going to introduce the formal constructions that we are goingto use. First, we define a simple model of computation called nondeterministicfinite automata. Then, we will define two equivalent models of computationthat are more powerful; pushdown automata and context-free grammars.

2.1 Nondeterministic finite automata

Nondeterministic finite automata are a class of very simple abstract machines.They are made up by a states, one of them marked initial and some of themmarked final, and labelled transitions between these states. An automatonbegins in a unique initial state and moves to other states according to its tran-sitions. When it takes a transition, it reads the symbol that labels that tran-sition. When the automaton ends up in some final state, it may either acceptthe sequence of symbols it read to get to that state, or it may continue readingsymbols. Any nondeterministic finite automaton has a corresponding language;the set of words it accepts. It happens that the set of languages recognized bythese automata forms a very important subset of formal languages.

6

Page 9: Approximating the Shuffle of Context-free Languages to Find Bugs

Definition 2 (Nondeterministic finite automata). A nondeterministic finite

automaton (NFA) is a tuple R = (Q,Σ,∆, q0, F ), where Q is a finite set ofstates, Σ is a finite input alphabet, ∆ ⊆ Q× Σ×Q is a set of transition rules,q0 ∈ Q is an initial state and F ⊆ Q is a set of final states.

Definition 3. A configuration of an NFA R is a tuple (q, w), where q ∈ Q is astate and w ∈ Σ∗ represents the remaining input.

In order to define the formal semantics of nondeterministic finite automata,we introduce a transition relation `R on configurations of R.

Definition 4. Let R = (Q,Σ,∆, q0, F ) be an NFA and let (q1, aw) and (q2, w)be configurations of R. Then (q1, aw) `R (q2, w) if (q1, a, q2) ∈ ∆.

We can now formally define what it means for an automaton to accept aword.

Definition 5. Let `∗R denote the reflexive transitive closure of `R. We saythat an NFA R = (Q,Σ,∆, q0, F ) accepts a word w ∈ Σ∗ if (q0, w) `∗R (f, ε),where f ∈ F . The language of R, denoted L(R), is the set of words acceptedby R, i.e. {w |w ∈ Σ∗, and there exists f ∈ F s.t. (q0, w) `∗R (f, ε)}. We saythat R recognizes L(R).

The set of languages recognized by nondeterministic finite automata can beshown to be equal to the set of languages produced by something called regulargrammars [13]. Therefore, we call these languages regular.

Definition 6. A language is regular if it is recognized by some NFA.

Remark 2.1. Most definitions of NFA include ε-transitions, i.e. transitionsthat read ε. For practical reasons, we don’t allow these trasnitions. Regardlessof definition, the resulting automata recognize the same languages. In fact, theyare both equivalent to deterministic finite automata (DFA), in the sense thatyou can construct a DFA that recognizes the same language as any NFA, andvice versa [13].

We can present NFAs in a more intuitive way with graphs. Figure 1 shows thegraphs representation of the NFA ({q0, q1}, {a, b}, {(q0, a, q1), (q1, b, q0)}, q0, {q0}).The initial state is marked with an incoming arrow, and the final states aremarked with a double border. The NFA recognizes the language {ε, ab, abab, ...}.

7

Page 10: Approximating the Shuffle of Context-free Languages to Find Bugs

q0 q1

a

b

Figure 1: NFA recognizing {(ab)n |n ∈ N}

2.2 Pushdown automata

Nondeterministic finite automata are widely used and have many practical ap-plications. However, NFA generally fail to recognize languages with recursivestructure. The canonical example is the language {anbn |n ∈ N} of stringswith a finite number a’s followed by the same number of b’s. We need push-down automata, a class of more powerful abstract machines, to recognize theselanguages.

Pushdown automata are similar to NFAs in many aspects. The main differ-ence is the addition of a stack. The contents of the stack influence the decisionsof the automaton. The automaton can change the contents of the stack by pop-ping from and/or pushing to the stack while perfoming transitions. Unlike ourNFA, the automaton can also make ε-transitions, i.e. transitions which don’tread an input symbol.

There are two different, but equivalent, modes of acceptance for pushdownautomata; accepting by final state and accepting by empty stack. Our pushdownautomata are going to accept by empty stack. This decision affects the time andspace complexity of converting pushdown automata to context-free grammars.

Definition 7 (Pushdown automata). A pushdown automaton (PDA) is atuple P = (P,Γ,Σ,∆, p0, γ0), where P is a finite set of states, Γ is a finite stackalhabet, Σ is a finite input alphabet, p0 ∈ P is an initial initial state and γ0 isan initial stack symbol. The set of rules ∆ is a finite set of transition rules,each of the form 〈p, γ〉 a

↪−→ 〈p′, w〉, where p, p′ ∈ P, γ ∈ Γ, w ∈ Γ≤2, a ∈ Σ∪{ε}.

Definition 8 (Configurations). A configuration of a PDA P is a triple (p, w, α),where p ∈ P is a state, w ∈ Σ∗ represents the remaining input and α ∈ Γ∗ rep-resents the current stack contents.

To define the semantics of pushdown automata, we first introduce a transi-tion relation `P on configurations of P.

8

Page 11: Approximating the Shuffle of Context-free Languages to Find Bugs

q0ε q1

a, ε→ 1

b, 1→ ε

b, 1→ ε

Figure 2: PDA recognizing {anbn |n ∈ N}

Definition 9 (Transitions). Given a PDA P, we have (p, aw, σα) ` (p′, w, γα)if 〈p, σ〉 a

↪−→ 〈p′, γ〉 ∈ ∆ for some p, p′ ∈ P , σ, γ ∈ Γ ∪ {ε} and a ∈ Σ ∪ {ε}.

Definition 10 (Acceptance). Let `∗P denote the reflexive transitive closure of`P . We say that P accepts a word w ∈ Σ∗ if (p0, w, γ0) `∗P (p, ε, ε), for somep ∈ P . The language of P, denoted L(P) is the set of all words accepted by P,i.e. {w |w ∈ Σ∗ and there exists a p ∈ F s.t. (p0, w, γ0) `∗P (p, ε, ε)}.

Like NFAs, PDAs can be nicely represented with graphs. Figure 2 shows thegraphs representation of the PDA ({q0, q1}, {a, b}, {1}, {〈q1, ε〉

a↪−→ 〈q1, 1〉〈q1, 1〉

b↪−→

〈q2, ε〉〈q2, 1〉b

↪−→ 〈q2, ε〉}, q0, ε). The initial state is again marked with an incom-ing arrow. This arrow is labelled with the initial stack symbol. In this case, theinitial stack will be empty. A transition from a state p to a state q is labelledwith a string σ, γ → w, where σ ∈ Σ, γ ∈ Γ, w ∈ Γ≤2, denoting that the PDAcontains the transition rule 〈p, γ〉 σ

↪−→ 〈q, w〉. This particular PDA recognizesthe language {ε, ab, aabb, aaabbb, ...}.

2.3 Pushdown automata from recursive programs

Pushdown automata serve as a natural model for sequential recursive programs(for example, programs written in Java) with finite variable domains [12]. Thestates of the pushdown automaton correspond to valuations of the global vari-ables, and the stack contains the current values of the local variables and theprogram pointer.

Assume that we have a program represented by a control flow graph, con-sisting of a set of nodes Loc, which represent the program locations, a set ofstatements Stmnt, and a set of transitions Trans ⊆ Loc× Stmnt× Loc whichrepresent all the possible actions of the program. When there are several func-tions in the program, the graph consists of several disjoint subgraphs, eachcorresponding to the behaviour of one particular function. We assume thatStmnt include statements for function calls.

9

Page 12: Approximating the Shuffle of Context-free Languages to Find Bugs

boolean i;

function main(){f()if(i=true){

return}else{

return}

}

function f(){i := true

}

Figure 3: Example program

For example, consider program in Figure 2.3, written in C-like pseudo-code.This program does not do anything, but we will use it as an example, sinceit contains an assignment, a function call and a conditional statement. Thecontrol flow graph of this program is illustrated in Figure 2.3. Assume that thecontrol flow graph contains “empty” locations that return transitions can go to.

We will now translate the control flow graph to a pushdown automaton. Theinput alphabet of the PDA is the set Stmnt of program statements, and it hasas states the set of valuations of global variables. For this particular example,we have that the set of states is {itrue, ifalse}. In general, if the global variableshave domains D1, ..., Dn, the set of states will be D1 × ...×Dn.

If there are no local variables, the rules of the PDA will be of form

〈g1, n1〉a↪−→ 〈g2, ns〉

where g1, g2 are valuations of global variables, a ∈ Stmnt is a statement andn1 ∈ Loc, ns ∈ Loc≤2 are locations in the control flow graph. If there are localvariables, the rules will instead be of form

〈g1, (l1, n1)〉 a↪−→ 〈g2, (l2, ns)〉

10

Page 13: Approximating the Shuffle of Context-free Languages to Find Bugs

m0

m1

m2 m4

f1

f0

call f

i = true

return

i = false

return

i := true

return

Figure 4: Example control flow graph

where l1 and l2 are valuations of local variables.The translation works as follows. For each transition (ni, a, nj) ∈ Trans:

• If a modifies a global variable, add the rule

〈g1, (l, ni)〉a↪−→ 〈g2, (l, nj)〉

where g2 is the effect of a on g1, for every valuation of local variables.

• If a modifies a local variable, add the rule

〈g, (l1, ni)〉a↪−→ 〈g, (l2, nj)〉

where l2 is the effect of a on l1, for every valuation of global variables.

• If a is a conditional, add the rule

〈g, (l, ni)〉a↪−→ 〈g, (l, nj)〉

for every valuation of global and local variables which is consistent withthe semantics of a.

11

Page 14: Approximating the Shuffle of Context-free Languages to Find Bugs

〈ifalse,m0〉call f↪−→ 〈ifalse, f0m0〉 〈itrue,m0〉

call f↪−→ 〈itrue, f0m0〉

〈ifalse, f0〉i:=true↪−→ 〈itrue, f1〉 〈itrue, f0〉

i:=true↪−→ 〈itrue, f1〉

〈ifalse, f1〉return↪−→ 〈ifalse, ε〉 〈itrue, f1〉

return↪−→ 〈itrue, ε〉

〈ifalse,m2〉return↪−→ 〈ifalse, ε〉 〈itrue,m2〉

return↪−→ 〈itrue, ε〉

〈ifalse,m3〉return↪−→ 〈ifalse, ε〉 〈itrue,m3〉

return↪−→ 〈itrue, ε〉

〈ifalse,m1〉i=false↪−→ 〈ifalse,m3〉 〈itrue,m1〉

i=true↪−→ 〈itrue,m2〉

Figure 5: Rules of resulting PDA

• If a is a function call, add the rule

〈g, (l, ni)〉a↪−→ 〈g, (l, f0nj)〉

where f0 is the entry point of the called function, for each valuation ofglobal and local variables.

• If a is a return statement, add the rule

〈g, (l, ni)〉a↪−→ 〈g, (l, ε)〉

for each valuation of global and local variables.

In the case we don’t have local variables, the translation is similar, butsimpler. Figure 5 shows the transition rules of the PDA corresponding to ourexample program. This automaton has two states, one where i is true, andone where i is false. The initial stack content is m0, the entry point of theprogram. The automaton performs the actions of the program, using the stackfor keeping track of the current location and previous return locations. Thisparticular program has only one possible run; call f, i:=true, return, i:=true,return. This is the only word that the PDA accepts.

2.4 Context-free grammars

Definition 11 (Context-free grammars). A context-free grammar (CFG) istuple G = (V,Σ, P, S), where V and Σ are disjoint finite sets of variable symbolsand terminal symbols, respectively, P is a set of production rules, and S ∈ V isthe start variable. A production rule is of the form A→ α, where A ∈ V andα ∈ (V ∪ Σ)∗. A is called the head of the rule.

12

Page 15: Approximating the Shuffle of Context-free Languages to Find Bugs

We define the notion of production by defining a relation ⇒G on wordsw ∈ (V ∪ Σ)∗.

Definition 12 (Production). Let u, x, v, w ∈ (V ∪ Σ)∗. We define ⇒G by

uxv ⇒G uwv ⇐⇒ x→ w ∈ P.

Let ⇒∗G denote the reflexive transitive closure of ⇒G. We say that G produces

a word w ∈ Σ∗ if S ⇒∗G w. The language of G, denoted L(G), is the set{w |w ∈ Σ∗, S ⇒∗G w} of all words that G produces. We also say that G producesthat language.

Definition 13. A language L is called context-free if it is produced by somecontext-free grammar.

We will now state a fundamental theorem that relates pushdown automataand context-free grammars. It says that pushdown automata and context-freegrammars are equivalent, in the sense that they accept and produce exactly thesame languages.

Theorem 2.2. For any language K over Σ, there exists a PDA P s.t. L(P) =K if and only if there exists a CFG G s.t. L(G) = K.

Proof. There is a constructive proof in most introductory textbooks, e.g. [13,4].

2.5 Context-free grammars from pushdown automata

Assume that we have a pushdown automaton P = (P,Γ,Σ,∆, p0, γ0). We canconvert P to an equivalent context-free grammar G via the following construc-tion. The non-terminals of G are of form (q, γ, q′). Intuitively, a non-terminal(q, γ, q′) produces everything that P accepts while going from state q with γ ontop of the stack to q′ with an empty stack.

• For any transition rule 〈p, γ〉 a↪−→ 〈q, γ1γ2〉, where γ1, γ2 ∈ Γ, add produc-

tion rules {(p, γ, p′)→ a(q, γ1, q′)(q′, γ2, p

′) | p′, q′ ∈ P,Reach(p, q′), Reach(q′, p′)},where P is the set of states in P and Reach(p, q) means state q is reach-able from state p (if q is not reachable from p, P cannot accept anythingbetween those states).

• For any transition rule 〈p, γ〉 a↪−→ 〈q, γ′〉, where γ′ ∈ Γ, add production

rules {(p, γ, p′)→ a(q, γ′, p′) | p′ ∈ P, }.

13

Page 16: Approximating the Shuffle of Context-free Languages to Find Bugs

• For any transition rule 〈p, γ〉 a↪−→ 〈q, ε〉, add a production rule (p, γ, q)→

a.

• Add production rules {S → (p0, γ0, p) | p ∈ P,Reach(p0, p)}, where p0 isthe initial state and γ0 is the initial stack symbol of P, and let S be thestart variable.

2.6 Converting to 2NF

For practical reasons, we only consider grammars which are in Binary NormalForm (2NF). A grammar is in 2NF if the right hand side of every productioncontains at most 2 symbols. Any context-free grammar can be converted to anequivalent context-free grammar in 2NF by expanding productions that containmore than 2 symbols in their right hand side. For example, the productionS → ABC can be expanded to S → AS′ and S′ → BC.

2.7 Minimizing Context-free grammars

The cost of performing most operations on context-free grammars grows withthe number of rules in the grammar. Therefore, it makes sense to always keepour grammars as small as possible. We do this by removing useless variablesand their associated rules. It is also useful to have the grammars in a formwhere there are no ε-producing rules, except for S → ε, where S is the startvariable, in the case the language contains ε.

There are two kinds of useless variables: variables that can’t generate any-thing, and variables that are not reachable from the start variable.

2.7.1 Removal of non-generating variables

The procedure for removing non-generating variables is essentially a fixpointcomputation of the set Gen of generating variables. Let G = (V, T, P, S) be thegrammar in question. We define

Gen0 = {x |x ∈ V and there exists x→ a ∈ P s.t. a ∈ T}

and

14

Page 17: Approximating the Shuffle of Context-free Languages to Find Bugs

Genn+1 ={x |x ∈ V and there exists x→ a1...ak ∈ P

s.t. for each ai, either ai ∈ T or ai ∈ Genn}. n ∈ N

Then Gen is defined as

Gen =⋃i∈N

Geni

Since G is finite, we have Genn+1 = Genn, i.e. Gen = Genn, for some n.Naturally, Gen can be computed by iteratively computing Gen1, Gen2, ..., Genn.We can now remove all rules which mention variables that are not in Gen.

Proposition 2.3. The computation of Gen takes O(|P |3) time.

Proof. Consider an arbitrary context-free grammar G = (V, T, P, S) in 2NF. Inthe worst case, all production rules have different variables in their head, allgenerating, but we only discover one at a time. Assume that the grammarcontains no superfluous variables, i.e. |V | = |P |.

When we compute Genn+1 for some n, we go through |P | production rules.For each rule, we check if the body of that rule contains only generating vari-ables, which is O(|P |), since |V | = |P |. So the computation of Genn+1 isO(|P |2). When we compute Gen|P |, we will have done O(|P |2) operations |P |times.

2.7.2 Removal of non-reachable variables

The other variables that are useless are the one which are not reachable fromthe start variable, and the computation is similar to the one for non-generatingvariables. Again, assume we have the grammar G = (V, T, P, S). Define

Reach0 = {S}

and

Reachn+1 ={x |x ∈ V and there exists y → a1...ak ∈ P

s.t. x = ai for some i and y ∈ Reachn}

15

Page 18: Approximating the Shuffle of Context-free Languages to Find Bugs

Then, again, Reach is defined as

Reach =⋃i∈N

Reachi

After computing Reach, we can remove all rules which mention variablesthat are not in this set.

Proposition 2.4. The computation of Reach takes O(|P |3) time.

Proof. Analogous to the proof of Proposition 2.3.

2.7.3 Removal of ε-productions

We say that a context-free grammar is ε-reduced if ε does not occur in anyproduction except for the production S → ε, where S is the start variable.

Definition 14. A variable A is nullable if A⇒∗G ε.

Definition 15. Let G = (V,Σ, P, S) be a CFG and let N be the set of nullablevariables in V . We say that G is ε-reduced if N ⊆ {S}.

Any context-free grammar can be converted to an equivalent ε-reducedcontext-free grammar. The procedure for finding nullable variables is similarto the one for finding non-generating variables.

Let

Null0 = {x |x ∈ V, x→ ε ∈ P}

Now define

Nulln+1 = {x |x ∈ V, x→ a1a2 ∈ P, a1 ∈ En, a2 ∈ En ∪ {ε}}

The set of nullable variables E is given by

Null =⋃n∈N

Nulln

We can now remove the nullable variables by expanding the rules in whichthey occur. Assume that our grammar is in 2NF. We expand the rules in thefollowing way.

For a production rule r = X → w,X ∈ V,w ∈ (V ∪ Σ)≤2, let Expand(r)be the set of production rules that we can obtain by removing all different

16

Page 19: Approximating the Shuffle of Context-free Languages to Find Bugs

occurences of variables v ∈ Null. For example, Expand(A → BC) = {A →B,A→ C,A→ BC,A→ ε} if both B and C are nullable. We can then removeε-productions and self rules. For a production rule r, let Remove be defined by

Remove(r) =

∅ if r = X → ε for some X ∈ V .

∅ if r = X → X for some X ∈ V .

r otherwise.

Then the new set of production rules without ε-productions is the set

P ′ =⋃r′∈E

Remove(r′) where E =⋃r∈P

Expand(r)

Proposition 2.5. The computation of Null is O(|P |3).

Proof. The proof is similar to the proof of Proposition 2.3.

2.8 Deciding emptiness

The emptiness problem for context-free grammars is defined as follow.

Definition 16 (The emptiness problem for CFGs). Given a CFG G, is it truethat L(G) = ∅?

Theorem 2.6. The emptiness problem for CFGs is decidable in polynomialtime.

Proof. Given a CFG G, let G′ = (V ′,Σ′, P ′, S′) be the CFG that we obtainby removing non-generating and non-reachable variables (in that order). Thenit is easy to see that L(G′) = ∅ ⇐⇒ P ′ = ∅. Since the removal of non-generating and non-reachable variables doesn’t change the language of the gram-mar, L(G) = ∅ ⇐⇒ P ′ = ∅. Removing non-generating and non-reachablevariables can both be done in polynomial time (Propositions 2.3 and 2.4).

2.9 Intersecting context-free grammars with finite automata

We begin with a well-known but important observation.

Proposition 2.7. The intersection between a context-free language and a reg-ular language is a context-free language [4].

17

Page 20: Approximating the Shuffle of Context-free Languages to Find Bugs

Assume that we have a context-free language, represented by a context-freegrammar G = (V, T, P, S), and a regular language, represented by a nondeter-ministic finite automaton R = (Q,Σ,∆, q0, F ). We can construct a CFG G′

that generates the intersection of these languages by creating variables An,mfor each variable A in G and pair of states n,m in R. The idea is that An,mproduces everything that is both produced by G and accepted by R between n

and m. The construction works as follows. Assume G is in 2NF.

1. Let Vvar = {An,m |A ∈ V, n,m ∈ Q} and Vter = {tn,m | (n, t,m) ∈ ∆}.

2. Let P1 = {vn,m → sn,m | v ∈ V, n,m ∈ Q, v → s ∈ P}.

3. Let P2 = {vn,m → sn,is′i,m | v ∈ V, n,m, i ∈ Q, v → ss′ ∈ P}.

4. Let Pε = {vn,n → ε |n ∈ Q, v → ε ∈ P}.

5. Assume S′ is disjunct from all the variables in V and Vvar. Let Pstart ={S′ → vq0,m |m ∈ F}.

6. Then, G′ = ({S′} ∪ Vvar ∪ Vter, T ∩ Σ, P1 ∪ P2 ∪ Pε ∪ Pstart, S′)

Proposition 2.8. The computation of the intersection grammar G′ is O(|V ||Q|3).

Proof. The costly parts are the computations of P1 and P2. The computa-tion of P1 is O(|V ||Q|2) (the number of new rules we need to create) and thecomputation of P2 is O(|V ||Q|3).

The intersection grammar is usually bloated and should be minimized byremoving useless variables and ε-productions.

3 The shuffle operation

Now we introduce the notion of shuffle. The shuffle of two languages is thelanguage you get by taking every pair of words and shuffling them together ina way that preserves the order within the original words.

Definition 17. Let L1 and L2 be two languages with alphabets Σ1 and Σ2,respectively, and let ai, i ∈ N denote symbols in Σ1 ∪ Σ2. Then, the shuffle of

18

Page 21: Approximating the Shuffle of Context-free Languages to Find Bugs

L1 and L2 is the language

{a1...an | {1...n} can be partitioned into {S1, S2} and there are monotonic functions

f : {1...|S1|} → S1, g : {1...|S2|} → S2

such that af(1)...af(|S1|) ∈ L1 and ag(1)...ag(|S2|) ∈ L2

}We will denote the shuffle of L1 and L2 by L1 ttL2.

The shuffle is useful because it corresponds to the set of interleavings ofprograms. Assume that we have two languages L1 and L2, representing possibleexecutions of programs P1 and P2, respectively. Then L1 ttL2 is the languagethat represents all possible interleaving executions of P1 and P2. For example,

{12, 3}tt{a, bc} = {12a, 1a2, a12, 12bc, 1b2c, 1bc2, b12c, b1c2, bc12, 3a, a3, 3bc, b3c, bc3}

In general, the idea is as follows.

1. Take the programs in question, and convert them to grammars.

2. Shuffle the languages of these grammars to get the language containingall interleaving executions of the programs.

3. Intersect this language with a language that describes bad executions.

4. Check the intersection for emptiness. If it is empty, there are no badexecutions. Otherwise, the concurrent system contains some error.

One condition is critical for this method to work; the emptiness of the inter-section must be decidable. This means that the intersection language must be atmost context-free, since the emptiness problem for context-sensitive languagesis undecidable. It is well known that context-free languages are not closed un-der intersection. However, we know that the intersection of a context-free anda regular language is context-free. So if the set of bad interleaving executionsis a regular language 1 and the shuffle is context-free, emptiness is decidable.Unfortunately, since we are dealing with recursive programs, which give rise tocontext-free languages, the shuffle is generally not context-free.

Theorem 3.1. The context-free languages are not closed under shuffle.1It turns out that most interesting properties can be described by regular grammars

19

Page 22: Approximating the Shuffle of Context-free Languages to Find Bugs

s0 ...

s1

s2

(a1, 1)

(a1, 2)

(an, 1)

(an, 2)

Figure 6: The NFA R

Proof. The proof is by contradiction. Assume, contrarily, that the context-freelanguages are closed under shuffle. So given arbitrary context-free grammarsG1 and G2, L(G1)ttL(G2) is context-free. Assume that G1 and G2 have thesame terminal alphabet Σ. Let G′1 and G′2 be grammars that are like G1 and G2

except that G′1 has the terminal alphabet Σ1 = Σ×{1} and G′2 has the terminalalphabet Σ2 = Σ×{2}, with all production rules changed accordingly. In otherwords, Σ1 and Σ2 are tagged versions of Σ.

Now, consider the shuffle G′1 ttG′2 intersected with the NFA R, shown inFigure 6, where a1, ..., an ∈ Σ. Call this intersection G′. The NFA consistsof n + 1 states and 2n transitions, and accepts all words that are composedof symbols taken alternatingly from some words w′1 ∈ L(G′1) and w′2 ∈ L(G′2),starting with w′1. Now take the corresponding words w1 ∈ L(G1) and w2 ∈L(G2). From the construction of R, w1 = w2.

This means that we can substitute (a, 1) with a and (a, 2) with ε in thealphabet and production rules of G′ to get a grammar G′′ that recognizes theintersection L(G1) ∩ L(G2). Since the intersection between a context-free and aregular language is context-free, G′ and G′′ are context-free. But we know thatthe context-free languages are not closed under intersection [4].

Since context-free languages are not closed under shuffle, we cannot hope to

20

Page 23: Approximating the Shuffle of Context-free Languages to Find Bugs

Approximateshuffle

G1 G1

PDA → CFG PDA → CFG

P1 P2

Program→ PDA

Program→ PDA

Program 1 Program 2

S

Intersect

S ∩R

R

Empty?No bugsfound

Bugs found!yes no

Figure 7: An overview

21

Page 24: Approximating the Shuffle of Context-free Languages to Find Bugs

construct a context-free grammar that generates the whole shuffle language. In-stead, given two CFG’s G1 and G2, we construct a third CFG S which underap-

proximates the shuffle L(G1)ttL(G1), meaning that L(S) ⊆ L(G1)ttL(G1).This means that the procedure we use is not complete, i.e. it can be used to findbugs but not to verify their absence. Figure 7 gives an overview of the process.

4 Shuffle grammars

We begin with a simple example of how one might underapproximate the shuffleof two context-free languages.

4.1 Shuffle up to 1

Assume that G1 = (V1,Σ1, P1, S1) and G2 = (V2,Σ2, P2, S2) are context-freegrammars in 2NF. We will construct a context-free grammar S that approxi-mates the shuffle L(G1)ttL(G2). The important part of S is the set of produc-tion rules. For each pair of productions rules (p1, p2) ∈ P1×P2, we create a setof rules that approximate the shuffle of p1 and p2. Assume that S ∈ (Σ1 ∪ V1)and T ∈ (Σ2 ∪ V2). Then, the variable (S, T ) stands for the shuffle of S and T ,i.e. producing (approximately) the shuffle of what S produces in G1 and whatT produces in G2. Each pair of rules (p1, p2) yields a set of rules, depending onwhat p1 and p2 look like, which we add to S:

• If p1 = S → A and p2 = T → X, then add the rules

(S, T )→AX

(S, T )→XA

• If p1 = S → AB and p2 = T → X, then add the rules

(S, T )→A(B,X)

(S, T )→(A,X)B

22

Page 25: Approximating the Shuffle of Context-free Languages to Find Bugs

• If p1 = S → A and p2 = T → XY , then add the rules

(S, T )→X(A, Y )

(S, T )→(A,X)Y

• If p1 = S → AB and p2 = T → XY , then add the rules

(S, T )→A(B, T )

(S, T )→X(S, Y )

(S, T )→(A,X)(B, Y )

(S, T )→(A, T )B

(S, T )→(S,X)Y

The union of all these sets makes up the set of production rules in S. Theset of terminals in S is the union of the terminals in G1 and G2, and the setof variables is {(S, T ) |S ∈ (Σ1 ∪ V1), T ∈ (Σ2 ∪ V2)}. Naturally, the startvariable of S is (S1, S2). We call S shuffle up to 1 of G1 and G2, and denote itby G1 tt 1G2. In Chapter 6 We will discuss some examples of how we use theshuffle up to 1 to find bugs in concurrent systems.

4.2 Shuffle up to k

It is clear that G1 tt 1G2, being a context-free grammar, does not produce allof G1 ttG2 for arbitrary context-free grammars G1 and G2. In order to improveour approximation, we must see where tt 1 fails.

Consider the example shown in Figure 8. We have two grammars G1 and G2,with start symbols A and B, respectively, and the top level rules of G1 tt 1G2.Variables are in upper case and terminals are in lower case.

Now consider the word w = a1b1a2a3b2b3a4b4. Clearly, w ∈ L(G1)ttL(G2),since it is a shuffle of the words a1a2a3a4 ∈ L(G1) and b1b2b3b4 ∈ L(G2). How-ever, w 6∈ L(G1 tt 1G2). Why is this? Let’s look at the top level rules ofG1 tt 1G2, which can be directly applied on the start symbol (A,B). The firstand second rules cannot produce w, since the first rule produces a string thatstart with a1a2 and the second rule produces one that start with b1b2. The

23

Page 26: Approximating the Shuffle of Context-free Languages to Find Bugs

G1

A −→ X1X2

X1 −→ a1a2

X2 −→ a3a4

G2

B −→ Y1Y2

Y1 −→ b1b2

Y2 −→ b3b4

G1 tt 1G2)

(A,B) −→ X1(X2, B)(A,B) −→ Y1(A, Y2)(A,B) −→ (X1, Y1)(X2, Y2)(A,B) −→ (X1, B)X2

(A,B) −→ (A, Y1)Y2

...

Figure 8: Example shuffle

24

Page 27: Approximating the Shuffle of Context-free Languages to Find Bugs

fourth and fifth rules cannot produce w either, since they cannot produce astring that ends with a4b4. This leaves the third rule. The third rule producesthe concatenation of two strings, the first produced by (X1, Y1) and the secondby (X2, Y2). Both of these string will be of length 4. But the first 4 symbolsof w includes a3, which can only be produced by X2 in G1. Thus, none of therules can produce w, so w 6∈ G1 tt 1G2.

Recall the definition of the shuffle up to 1. Using this notion of shuffling,the head of a production rule in the resulting grammar was a tuple (w1, w2) s.t.|w| ≤ 1, |w2| ≤ 1. As we could see, the problem with this approximation is thatwe cannot “break down” the words that w1 and w2 produce. In order to dothat, we generalize this notion by defining the shuffle up to k. Intuitively, whenwe shuffle up to k, the heads of the production rules are tuples of sequences ofsymbols, each of length up to k.

Definition 18. The shuffle up to k of two context-free grammars G1 =(V1,Σ1, P1, S1) and G2 = (V2,Σ2, P2, S2) is the grammar Shufflek(G1,G2) =(V ≤k1 × V ≤k2 ,Σ1 ∪ Σ2, Pshuffle, (S1, S2)), where Pshuffle is the set of produc-tion rules {(w1, w2) → (u1, v1)(u2, v2) |w1, u1, u2 ∈ (V1 ∪ Σ1)≤k, w2, v1, v2 ∈(V2 ∪ Σ2)≤k such that w1 ⇒G1 u1u2, w1, ⇒G2 v1v2}.

5 Implementation

We implemented a prototype tool using this technique in Haskell. The imple-mentation is relatively simple, using straight-forward representations of gram-mars and automata. It currently supports the shuffle up to 1.

We use the following representation for NFAs, PDAs and CFGs. Here, Set isa type from Data.Set, which implements pure set operations based on balancedbinary trees.

25

Page 28: Approximating the Shuffle of Context-free Languages to Find Bugs

data NFA a b = Nfa

{ states’ :: Set a

, transitions’ :: Set (a, b, a)

, initial’ :: a

, final’ :: Set a

} deriving Show

data PDA a b c = Pda

{ pdaname :: String

, states :: Set a

, transitions :: Set (a, b, c, a, [b])

, stackEps :: b

, inputEps :: c

, initial :: (a, b)

} deriving (Show, Eq)

data CFG a = Cfg

{ cfgname :: String

, start :: Symbol a

, rules :: Set (Symbol a, [Symbol a])

} deriving (Show, Eq)

We have the following core funtions. These functions implement the algo-rithms described in Section 2.

26

Page 29: Approximating the Shuffle of Context-free Languages to Find Bugs

-- Constructors

makeNFA :: (Ord b, Ord a) =>

[a] -> [(a, b, a)] -> a -> [a] -> NFA a b

makePDA :: (Ord b, Ord c, Ord a, Enum a) =>

String -> [a] -> b -> c -> [(a, b, c, a, [b])]

-> (a, b) -> PDA a b c

makeCFG :: Ord a =>

String -> Symbol a -> [(Symbol a, [Symbol a])]

-> CFG a

-- Conversion from PDA to CFG

toStringCFG :: (Show b, Show c, Show a, Eq c, Eq b, Enum a) =>

PDA a b c -> CFG String

-- Simplification

removeNonGenerating :: Ord a => CFG a -> CFG a

removeNonReachable :: Ord a => CFG a -> CFG a

removeUseless :: Ord a => CFG a -> CFG a

removeEpsilon :: Ord a => CFG a -> CFG a

to2NF :: CFG String -> CFG String

normalizeCFG :: CFG String -> CFG String

-- Shuffling

shuffleGrammars :: CFG String -> CFG String -> CFG String

-- Intersection with NFA

intersectNFA :: (Show a, Ord a) =>

CFG String -> NFA a String -> CFG String

6 Examples

This section contains some examples of the type of programs our prototype isable to handle. We use different modelling techniques for each program, whichaffects the performance. The first program is a simple example that demonstatestwo techniques; the modelling of counters and communication via shared vari-

27

Page 30: Approximating the Shuffle of Context-free Languages to Find Bugs

function s(){if(i<3){i := i + 1s()

}

return}

Figure 9: Simple counter: Simple recursive counter program

ables. Note that this program does not contain any bugs. The second exampledemonstrates modelling counters and message passing. This program containsan error [10], which we detect. The third example demonstrates communicationvia message passing and a more efficient way to synchronize the messages. Thisprogram also contains an error [6], which we detect.

6.1 Simple counter

Assume we have a program that has a counter variable i with an infinite range.The pushdown automaton describing this program would then have an infinitenumber of states. To solve this problem, we simulate the counter with anotherpushdown automaton. Consider the example program in Figure 9 to demon-strate this technique.

Assume that i is a global counter variable with initial value 0. It is straight-forward to see that the function s calls itself recursively 3 times, increasing thecounter to 3. The control flow graph in Figure 10 describes this program.

To model this transition system with pushdown automata, we need to havetwo processes, one storing the counter value and the other storing the call stack.The easiest way to construct this kind of system is to do a straightforwardtranslation of the control flow graph into a PDA that keeps track of the call stack.Then, synchronize this PDA with another PDA which contains the countervalue. These processes communicate via message passing. For example, thecounter PDA could only take the transition i < 3 if the value of i was actuallyless that than 3. This transition would be synchronized with the call stack

28

Page 31: Approximating the Shuffle of Context-free Languages to Find Bugs

s0

s1

s2

s3

sf

i < 3

i ≥ 3

i+ +

call s

return

Figure 10: Simple counter: Control flow graph

PDA’s i < 3-transition, ensuring that the system of 2 PDAs behaves as theoriginal program. This is communication via message passing. The two otherexamples will use message passing.

For this example, we will use communication via shared varianbles. The ideais that we have a shared variable which contains the current state. We constructtwo new control flow graphs: one which contains the statements related to thecounter variable i, and on which contains the other statements (except return).Additionally, these systems will contain statements that guess the movementof the other system. Figure 11 describes these systems. The guess statementsare marked in grey. A guess statement (n,m) means that the PDA in questionguesses that the other PDA will update the shared variable from n to m.

Figure 12 shows the transitions of the corresponding counter PDA. The valueof the counter is the number of 1s on the stack. The symbol ⊥ represents theempty stack, in which case the counter is 0. Note that the marked transitionsdo not conform to the definition of a PDA; they are a convenient shorthandfor several transitions with states inbetween. We have also not included thestatements from the original control flow graph. We do not need to make thesestatements visible in order to capture the behaviour of the program. This greatlyreduces the size of the resulting shuffle grammar.

We convert the two PDA to CFGs and shuffle them to obtain a grammarwhich produces the interleaving executions. This shuffle grammar will still pro-duce words that are not valid runs of the concurrent system, since we have not

29

Page 32: Approximating the Shuffle of Context-free Languages to Find Bugs

s0

Counter

s1

s2

s3

sf

i < 3

i ≥ 3

i+ +

(s2, s0)

(s3, sf )

s0

Call stack

s1

s2

s3

sf

(s0, s3)

(s0, s2)

call s

Figure 11: Simple counter: New control flow graphs

〈s0,⊥〉ε

↪−→ 〈s1,⊥〉 〈s0, 1⊥〉ε

↪−→ 〈s1, 1⊥〉〈s0, 11⊥〉 ε

↪−→ 〈s1, 11⊥〉 〈s0, 111〉 ε↪−→ 〈s3, 111〉

〈s1,⊥〉ε

↪−→ 〈s2, 1⊥〉 〈s1, 1〉ε

↪−→ 〈s2, 11〉

〈s2, 1〉(s2,s0)↪−→ 〈s0, 1〉 〈s3, 1〉

(s3,sf )↪−→ 〈sf , ε〉

〈sf , 1〉ε

↪−→ 〈sf , ε〉 〈sf ,⊥〉ε

↪−→ 〈sf , ε〉

Figure 12: Simple counter: transitions

30

Page 33: Approximating the Shuffle of Context-free Languages to Find Bugs

s0 s2

s3 sf

(s0, s2)

(s2, s0)(s0, s3)

(s3, sf )

Figure 13: Simple counter: NFA characterizing valid runs

synchronized them. The synchronization is done on the grammar level. To getonly the valid runs, we intersect the shuffle grammar with the NFA shown inFigure 13. The resulting grammar produces the only valid run of the system:

(s0, s2)(s2, s0)(s0, s2)(s2, s0)(s0, s2)(s2, s0)(s0, s3)(s3, sf )

6.2 Bluetooth driver

In this example, we look at a version of a Windows NT Bluetooth driver de-scribed in [10, 2, 14]. The driver has two types of threads; adders and stoppers.It keeps count of the number of threads that are executing the driver via acounter variable pendingIO, which is initialized to 1. The stoppers’ task is tostop the driver. They set the field stopFlag to true, decrease pendingIO, waitfor an event stopEvent which signals that no other threads are working in thedriver and finally stop the driver by setting stopped to true. The adders performthe I/O operations. They try to increment pendingIO. If this is succesful, theyassert that the driver is not stopped by checking that stopped is false, performsome I/O operations and then decrement pendingIO. When pendingIO reaches0, the stopEvent event is set to true.

This system is faulty. In [10], a bug is reported which involves one adderthread, one stopper thread and two context switches. The error occurs whenthe adder thread runs until it calls inc. Before it checks for stopFlag, thestopper thread runs until the end, and stops the driver. Then, the adder threadcontinues running and reaches the error label. We are going to try to find thisbug. To do this, we first model this system with pushdown automata.

We translate the program to a set of 3 pushdown automata; one for the

31

Page 34: Approximating the Shuffle of Context-free Languages to Find Bugs

boolean stopFlag, stopped, stopEventint pendingIO

function adder(){int statusstatus := inc()if(status = 0){if(stopped){error

}else{// perform I/O

}}dec()return

}

function stopper(){stopFlag := truedec()while (!stopEvent){// wait

}stopped := true

}

Figure 14: Bluetooth driver: adder() and stopper()

32

Page 35: Approximating the Shuffle of Context-free Languages to Find Bugs

function inc(){if(stopFlag){return -1

}atomic{pendingIO++

}return 0

}

function dec(){int iatomic{pendingIO--i := pendingIO

}if(i=0){stopEvent := true

}return

}

Figure 15: Bluetooth driver: inc() and dec()

33

Page 36: Approximating the Shuffle of Context-free Languages to Find Bugs

a0

a1

a2

a3 a5

a6

a4 s2

s1

s0

s3

s4

call inc

status = 0

stopped !stopped

status < 0

call decreturn

return

stop-flag

call dec

stop-driver

stopped

return

Figure 16: Bluetooth driver: Control flow graphs for adder and stopper

34

Page 37: Approximating the Shuffle of Context-free Languages to Find Bugs

d0

d1

d2

d3

d4

i - -

zero !zero

stop-driver

return

return

i1 i2

i0

i3

i5

i6

!stop-flagstop-flag

i ++

status := −1

status := 0

return

return

Figure 17: Bluetooth driver: Control flow graphs for dec and inc

35

Page 38: Approximating the Shuffle of Context-free Languages to Find Bugs

〈s1,⊥〉i++↪−→ 〈s1, 1⊥〉 〈s1, 1〉

i++↪−→ 〈s1, 11〉

〈s1, 1〉i−−↪−→ 〈s1, ε〉 〈s1,⊥〉

zero↪−→ 〈s1,⊥〉

〈s1, 1〉!zero↪−→ 〈s1, 1〉 〈s1, 1〉

ε↪−→ 〈s2, ε〉

〈s1,⊥〉ε

↪−→ 〈s2, ε〉 〈s2, 1〉ε

↪−→ 〈s2, ε〉〈s2,⊥〉

ε↪−→ 〈s2, ε〉 〈s0,⊥〉

ε↪−→ 〈s1, 1⊥〉

Figure 18: Bluetooth driver: Counter transitions

control flow of the adder, one for the control flow of the stopper, and one whichsimulates the counter. These automata communicate via message passing, i.e.the recognized languages of these automata contain messages that we must syn-chronize. We do this by intersecting with a set of NFA that filter out interleavedruns which do respect the semantics of the program.

Figure 18 shows the transitions for the counter automaton. Its stack alpha-bet is {1,⊥}, where ⊥ represents an empty stack. It is also the initial stacksymbol. The state s0 is the initial state. To initialize, the automaton pushes 1on the stack. Since our pushdown automata accept by empty stack, the counterautomaton has ε-transitions to a final state, in which it pops everything of thestack.

Figure 19 shows the transitions for the adder. It has the initial state s0,which corresponds to status < 0, and initial stack symbol add0. The state s1corresponds to status = 0. We have combined some transitions of the controlflow graph. For example, if the automaton is in state s1, and the top of thestack is add1, it take a stopped-transition to add6, which corresponds to both a3

and a6 in the control flow graph. The transitions of the stopper automaton areshown in Figure 20. It has s0 as initial state, and stop0 as initial stack symbol.

We are now going to try to find a run of this system in which the error labeloccurs. First, we translate the pushdown automata to context-free grammars.This gives us three grammars, btadd, btstop, and btctr, which we then shuffletogether. First, we shuffle the adder and stopper to get G1 = btadd tt 1btstop.When we perform this shuffle, we tag the terminal symbols with the name of thegrammar producing them. For example, a terminal a in btadd would be renamed< a, btadd >. We then filter out non-valid runs by performing a sequence ofNFA intersections. We could intersect with a single NFA that characterizes allvalid runs, but it turns out that due to the high complexity of the intersection(it is cubic in the number of NFA states; see Proposition 2.8), it is better to

36

Page 39: Approximating the Shuffle of Context-free Languages to Find Bugs

〈s0, add0〉ε

↪−→ 〈s0, inc0add1〉 〈s0, add1〉ε

↪−→ 〈s0, dec0add6〉〈s0, add6〉

ε↪−→ 〈s2, 〉 〈s1, add1〉

!stopped↪−→ 〈s1, dec0add6〉

〈s1, add1〉stopped↪−→ 〈s1, add3〉 〈s1, add3〉

error↪−→ 〈s2, ε〉

〈s0, inc0〉stop−flag↪−→ 〈s0, inc1〉 〈s0, inc1〉

ε↪−→ 〈s1, ε〉

〈s0, inc0〉!stop−flag↪−→ 〈s0, inc2〉 〈s0, inc2〉

i++↪−→ 〈s0, ε〉

〈s0, dec0〉i−−↪−→ 〈s0, dec1〉 〈s0, dec1〉

zero↪−→ 〈s0, dec2〉

〈s0, dec2〉stop−driver

↪−→ 〈s0, ε〉 〈s0, dec1〉non−zero↪−→ 〈s0, dec3〉

〈s0, dec3〉ε

↪−→ 〈s0, ε〉 〈s1, dec0〉i−−↪−→ 〈s1, dec1〉

〈s1, dec1〉zero↪−→ 〈s1, dec2〉 〈s1, dec2〉

stop−driver↪−→ 〈s1, ε〉

〈s1, dec1〉non−zero↪−→ 〈s1, dec3〉 〈s1, dec3〉

ε↪−→ 〈s1, ε〉

Figure 19: Bluetooth driver: Adder transitions

〈s0, stop0〉stop−flag↪−→ 〈s0, stop1〉 〈s0, stop1〉

ε↪−→ 〈s0, dec0stop2〉

〈s0, stop2〉stop−driver

↪−→ 〈s0, stop3〉 〈s0, stop3〉stopped↪−→ 〈s0, ε〉

〈s0, dec0〉i−−↪−→ 〈s0, dec1〉 〈s0, dec1〉

zero↪−→ 〈s0, dec2〉

〈s0, dec2〉stop−driver

↪−→ 〈s0, ε〉 〈s0, dec1〉non−zero↪−→ 〈s0, dec3〉

〈s0, dec3〉ε

↪−→ 〈s0, ε〉

Figure 20: Bluetooth driver: Stopper transitions

37

Page 40: Approximating the Shuffle of Context-free Languages to Find Bugs

s0 s1s2< stop-flag, btstop >< stop-flag, btadd >

?

Figure 21: Bluetooth driver: Ordering of stop-flag

s0 s1s2< stopped, btstop >< stopped, btadd >

?

Figure 22: Bluetooth driver: Ordering of stopped

intersect with a larger number of small NFAs. We intersect G1 with the NFAshown in Figures 21, 22 and 23. In these NFA, the star transition representsa set of transitions; nameley, transitions labelled by all terminals that are notexplicitly shown in the figure. This intersection yields a grammar G2, whichproduces semantically valid runs w.r.t. the interaction between the adder andstopper. To synchronize the counter statements, we shuffle G2 and btctr, toproduce G3 = G2 tt 1btctr. We intersect this grammar with the NFA shown inFigures 24 and 25. The result is a grammar that produces only valid runs of theconcurrent system. We finally intersect this grammar with the NFA describingbad runs, shown in Figure 26. The resulting grammar is non-empty, whichmeans that we have found a bug in the system. We can generate the shortestword in the language of this grammar:

< stop−flag,G2 >< i−−, btctr >< i−−,G2 >< zero, btctr >< zero,G2 >< stop−driver,G2 >< stop−driver,G2 >< stopped,G2 >< stop−flag,G2 >< stopped,G2 ><

error,G2 >

This is the same bug reported in [10, 2]. We also tried to find a bug reportedin [2] for a second version of the driver [14], requiring 3 processes and 4 con-text switches. Unfortunately, after intersecting with the NFA ensuring correctsemantics, the language was empty. This means that the shuffle up to 1 is notenough to detect this bug.

6.3 Mozilla bug

This example consists of a real-world bug [6] in the Mozilla Application Suite,caused by an incorrect use of locks. We show a simplified version of the code inquestion in Figure 27.

38

Page 41: Approximating the Shuffle of Context-free Languages to Find Bugs

s0 s1s2

< stop-driver, btstop >

< stop-driver, btadd >< stop-driver, btadd >

< stop-driver, btstop >

?

Figure 23: Bluetooth driver: Synchronization of stop-driver

s0s3

s2

s1

s4

< zero,G1 >

<!zero,G1 >

<!zero, btctr >

< zero, btctr >

< zero, btctr >

<!zero, btctr >

<!zero,G1 >

< zero,G1 >

?

Figure 24: Bluetooth driver: Synchronization of conditionals

39

Page 42: Approximating the Shuffle of Context-free Languages to Find Bugs

s0s3

s2

s1

s4

< i+ +,G1 >

< i−−,G1 >

< i−−, btctr >

< i+ +, btctr >

< i+ +, btctr >

< i−−, btctr >

< i−−,G1 >

< i+ +,G1 >

?

Figure 25: Bluetooth driver: Synchronization of counter updates

s0 s1< error,G3 >

?

?

Figure 26: Bluetooth driver: Error traces

40

Page 43: Approximating the Shuffle of Context-free Languages to Find Bugs

function thread1(){lock(l)gScript:=aspt

unlock(l)// ...lock(l)if(gScript==null){error

}else {// do stuff

}unlock(l)

}

function thread2(){lock(l)gScript:=nullunlock(l)

}

Figure 27: Mozilla: Simplified code from Mozilla Application Suite

41

Page 44: Approximating the Shuffle of Context-free Languages to Find Bugs

〈s0, f0〉lock↪−→ 〈s0, f1〉 〈s0, f1〉

g:=aspt↪−→ 〈s0, f2〉

〈s0, f2〉unlock↪−→ 〈s0, f3〉 〈s0, f3〉

ε↪−→ 〈s0, f4〉

〈s0, f4〉lock↪−→ 〈s0, f5〉 〈s0, f5〉

null↪−→ 〈s0, f6〉

〈s0, f6〉error↪−→ 〈s0, f7〉 〈s0, f5〉

!null↪−→ 〈s0, f7〉

〈s0, f7〉unlock↪−→ 〈s0, ε〉

Figure 28: Mozilla: Transitions of P1

〈s0, n0〉lock↪−→ 〈s0, n1〉 〈s0, n1〉

g:=null↪−→ 〈s0, n2〉

〈s0, n2〉unlock↪−→ 〈s0, ε〉

Figure 29: Mozilla: Transitions of P2

The first thread consists of two atomic blocks. An error occurs if threadtwo starts running between these atomic blocks and sets gScript to null. Wetranslate these two threads to two PDA, P1 and P2, whose transitions are shownin Figures 28 and 29. We shuffle these grammars together to obtain the shufflegrammar S = P1 tt 1P2.

In this example, we try another approach to shared variables. The idea isthat if we have a variable with very small domains (e.g. a boolean variable),we can shuffle the languages of the programs sharing that variable, and thenintersect the shuffle with a NFA that tracks the state of the variable and allowscertain statements only in certain states. For example, the conditional v!=0)could only be read in a NFA state which represents that v is not 0.

We intersect the grammar S with the NFA in Figures 30, 31 and 32. Theseenforce correct locking, track the variable g, and give us only runs which containthe error label. The resulting grammar is non-empty; it produces (assuming wetagged the terminals) the word

< lock,P1 >< g := aspt,P1 >< unlock,P1 >< lock,P2 >< g := null,P2 ><

unlock,P2 >< lock,P1 >< null,P1 >< error,P1 >< unlock,P1 >

6.4 Results

Table 1 contains the timing results for constructing the final intersection gram-mar and checking emptiness. The tests were performed on a 2.5 Ghz Intel Xeonwith 6GB of RAM.

42

Page 45: Approximating the Shuffle of Context-free Languages to Find Bugs

s0 s1s2

< lock,P1 >

< unlock,P1 >< lock,P2 >

< unlock,P2 >

? ??

Figure 30: Mozilla: Enforcing lock semantics

s0 s1

g := aspt

g := null

null !null

? ?

Figure 31: Mozilla: Enforcing variable semantics

s0 s1error

?

?

Figure 32: Mozilla: Error traces

Program TimeSimple Counter 3.2sBluetooth 3m40sMozilla 1.7s

Table 1: Results

7 Discussion and conclusions

Our prototype tool implements the shuffle up to 1. We have shown that thiscrude underapproximation is sufficient to detect bugs in some concurrent pro-grams. However, our current prototype has several limitations, beside the factthat we only support k = 1:

• Manual translation: We manually translate the concurrent systems topushdown automata. This is a time consuming process which is only

43

Page 46: Approximating the Shuffle of Context-free Languages to Find Bugs

feasible for small programs. The lack of automatic translation is the majorshortcoming of the tool. If we want to build a usable tool which works onreal-world programs, we need to automate the translation.

• Grammar representation: We currently represent a grammar straight-forwardly as a start variable and a set of production rules. This is notfeasible for larger programs. In order to become competitive, we need tofind an efficient symbolic representation, so that we don’t need to storeevery production rule explicitly.

• NFA intersection: The algorithm for NFA intersection results in unrea-sonably large grammars for all but the smallest NFA. This is not a problemif the possible synchonization statements can be synchronized with smallNFA. For larger programs, the number of production rules might explodebecause of the intersection. This problem might be solved by a symbolicrepresentation.

• Variable ranges: Currently, we can only handle variables with a smallrange. Each variable’s range affects the size of the PDA and, consequently,the size of the resulting grammar. For boolean programs, this is nota problem. For other types of programs, we cannot hope to build thecorresponding PDA due to its size. Once again, an efficient symbolicrepresentation could solve this.

We believe that this method complements bounded model checking, whichfinds all errors within some constant k context-switches. Using our technique,we can find errors for an arbitrary number of context-switches. As we increasek, the shuffle up to k gets more fine-grained.

There are two challenges to making this technique useful in practice; one mi-nor and one major. The minor challenge is to automate the process of extractinga grammar from a program. This amounts to automating the translation fromsource code to PDA. The major challenge is to find a suitable symbolic repre-sentation that is both concise and supports efficient grammar operations.

8 Related work

Bouajjani, Esparza and Touli [1] use over-approximations of context-free lan-guages to verify the non-reachability of error states. Given two context-free

44

Page 47: Approximating the Shuffle of Context-free Languages to Find Bugs

languages L1 and L2, one computes over-approximations A1 and A2 in a classof languages that are closed under intersection, and for which emptiness is de-cidable. If it is the case that A1 ∩A2 = ∅, then L1 ∩ L2 = ∅.

Chaki et al. [2] extend this approach with two CEGAR [3, 5] schemes; onewhich derives and refines abstract systems of pushdown automata from C code,and one which automatically refines the over-approximations A1 and A2 in caseA1 ∩A2 6= ∅.

KISS [10] under-approximates the behavior of a concurrent program by asequential program, but cannot handle more than 2 context switches. Still, thislow bound has been enough to detect race conditions in windows device drivers.

The model checker Zing [8] uses procedure summaries to check assertionson multithreaded recursive programs. This approach allows analysis results forprocedures to be reused. However, since the underlying problem is undecidable,Zing is not guaranteed to terminate.

Qadeer and Rehof [9] introduce a context-bounded model checking techniquethat is both sound and complete up to the bound, even for an unbounded numberof threads.

Moped [12, 14] is a model checker for sequential and concurrent pushdownautomata. It uses an efficient BDD-based symbolic representation. Its concur-rent analysis is based on context-bounded model checking.

References

[1] A. Bouajjani, J. Esparza, and T. Touili. A generic approach to the staticanalysis of concurrent programs with procedures. In ACM SIGPLAN No-tices, volume 38, pages 62–73. ACM, 2003.

[2] S. Chaki, E. Clarke, N. Kidd, T. Reps, and T. Touili. Verifying concurrentmessage-passing c programs with recursive calls. Tools and Algorithms forthe Construction and Analysis of Systems, pages 334–349, 2006.

[3] E. Clarke, O. Grumberg, S. Jha, Y. Lu, and H. Veith. Counterexample-guided abstraction refinement. In Computer Aided Verification, pages 154–169. Springer, 2000.

[4] J.E. Hopcroft, R. Motwani, and J.D. Ullman. Introduction to automatatheory, languages, and computation, volume 3. Addison-wesley Reading,MA, 1979.

45

Page 48: Approximating the Shuffle of Context-free Languages to Find Bugs

[5] RP Kurshan. Computer-aided verification of coordinating processes: theautomata-theoretic approach. Princeton Univ Pr, 1994.

[6] S. Lu, J. Tucek, F. Qin, and Y. Zhou. Avio: detecting atomicity violationsvia access interleaving invariants. ACM SIGPLAN Notices, 41(11):37–48,2006.

[7] M. Musuvathi and S. Qadeer. Iterative context bounding for systematictesting of multithreaded programs. In PLDI’07, pages 446–455. ACM, 2007.

[8] S. Qadeer, S.K. Rajamani, and J. Rehof. Summarizing procedures in con-current programs. In ACM SIGPLAN Notices, volume 39, pages 245–255.ACM, 2004.

[9] S. Qadeer and J. Rehof. Context-bounded model checking of concurrentsoftware. Tools and Algorithms for the Construction and Analysis of Sys-tems, pages 93–107, 2005.

[10] S. Qadeer and D. Wu. Kiss: keep it simple and sequential. ACM SIGPLANNotices, 39(6):14–24, 2004.

[11] G. Ramalingam. Context-sensitive synchronization-sensitive analysis is un-decidable. ACM Trans. Program. Lang. Syst., 22(2):416–430, 2000.

[12] S. Schwoon. Model-checking pushdown systems. PhD thesis, TechnischeUniversitat Munchen, Universitatsbibliothek, 2002.

[13] M. Sipser. Introduction to the theory of computation. 1996.

[14] Dejvuth Suwimonteerabuth. Reachability in Pushdown Systems: Algo-rithms and Applications. PhD thesis, Technische Universitat Munchen,2009.

46