Efficient Verification of Sequential and Concurrent C Programs

. There has been considerable progress in the domain of software veri(cid:12)cation over the last few years. This advancement has been driven, to a large extent, by the emergence of powerful yet automated abstraction techniques like predicate abstraction. However, the state space explosion problem in model checking remains the chief obstacle to the practical veri(cid:12)cation of real-world distributed systems. Even in the case of purely sequential programs, a crucial requirement to make predicate abstraction e(cid:11)ective is to use as few predicates as possible. This is because, in the worst case, the state space of the abstraction generated (and consequently the time and memory complexity of the abstraction process) is exponential in the number of predicates involved. In addition, for concurrent programs, the number of reachable states could grow exponentially with the number of components. We attempt to address these issues in the context of verifying concurrent (message-passing) C programs against safety speci(cid:12)cations. More speci(cid:12)cally, we present a fully automated compositional framework which combines two orthogonal abstraction techniques (predicate abstraction for data and action-guided abstraction for events) within a counterexample-guided abstraction re(cid:12)nement scheme. In this way, our algorithm incrementally increases the granularity of the abstractions until the speci(cid:12)cation is either established or refuted. Additionally, a key feature of our approach is that if a property can be proven to hold or not hold based on a given (cid:12)nite set of predicates P , the predicate re(cid:12)nement procedure we propose in this article (cid:12)nds automatically a minimal subset of P that is su(cid:14)cient for the proof. This, along with our explicit use of compositionality, delays the onset of state space explosion for as long as possible. We describe our approach in detail


Introduction
Critical infrastructures in several domains, such as medicine, power, telecommunications, transportation and finance are highly dependent on computers.Disruption or malfunction of services due to software failures (accidental or malicious) can have catastrophic effects, including loss of human life, disruption of commercial activities, and huge financial losses.The increased reliance of critical services on software infrastructure and the dire consequences of software failures have highlighted the importance of software reliability, and motivated systematic approaches for asserting software correctness.While testing is very successful for finding simple, relatively shallow errors, testing cannot guarantee that a program complies with its specification.Consequently, testing by itself is inadequate for critical applications, and needs to be complemented by automated verification.
Although software verification has been the subject of ambitious projects for several decades, and this research tradition has provided us with important fundamental notions of program semantics and structure, software verification tools have, until recently, not attained the level of practical applicability required by industry.Motivated by urgent industrial need, the success and maturity of formal methods in hardware verification, and by the arrival of new techniques such as predicate abstraction [31], several research groups [29,3,2,5,1,30] have started to develop a new generation of software verification tools.A common feature of all these tools is that they operate directly on programs written in a general purpose programming language like C or Java instead of those written in a more restricted modeling language like Promela [6].In addition, all of them are characterized by an extended model checking [18] algorithm which interacts with theorem provers and decision procedures to reason about software abstractions, in particular about abstractions of data types.
Our own tool magic (Modular Analysis of proGrams In C) [4,13,14,12] also belongs to this family and focuses on modular verification of C code.In general, C programs can be concurrent, i.e., consist of multiple communicating processes and/or threads.Therefore, in order to avoid confusion, we shall adopt the following terminology for the remainder of this article.We shall refer to an arbitrary C program as simply a program.Each process/thread of which the program is comprised will be referred to as a component.Thus, in particular, for a purely sequential C program, the two terms "program" and "component" are synonymous.Also we shall usually denote a program by Π and a component by C (with appropriate subscripts where applicable).
The general architecture of the magic tool has been presented recently [13] along with an in-depth description of its modular verification approach.In magic, both components and programs are described by labelled transition systems (LTSs), a form of state machines.The goal of MAGIC is to verify whether the implementation Π of a program is safely abstracted by its specification Spec (which is again expressed using an LTS), i.e., whether all possible behaviors of the implementation are subsumed by the specification.Several notions of safe abstraction have been proposed in the literature.In the context of this article, we use trace containment (denoted by ) as our notion of safe abstraction.In the rest of this paper, we shall use terms such as "conformance", "compliance", "safe abstraction", and "trace containment" synonymously.
Since a program can, in general, give rise to an infinite-state system, we will not directly check Π against Spec.Rather we will extract an intermediate abstract model A(Π) which is guaranteed by construction to safely abstract Π, and then verify whether A(Π) is, in turn, safely abstracted by Spec, i.e.,

Π A(Π) Spec
The evident problem in this approach is to find a good abstraction A(Π).If A(Π) is too close to the original system Π, then the computational cost for checking A(Π) Spec may be prohibitively expensive.On the other hand, if A(Π) is very coarse then it may well be that relevant features of Π are abstracted away, i.e., A(Π) Spec, even though Π Spec actually holds.However, an inspection of A(Π) Spec provides a counterexample CE .In general though, this counterexample may not be grounded on any real behavior of Π and consequently could be spurious.This motivates the use of a framework known as abstractverify-refine or counterexample-guided abstraction refinement (CEGAR) [19,38], which works as follows.
• Step 1 (Abstract).Create an abstraction A(Π) of the program which safely abstracts Π by construction.
• Step 2 (Verify).Verify that A(Π) Spec, i.e., A(Π) is safely abstracted by the specification Spec.If this is the case, the verification is successful.Otherwise, i.e., if A(Π) is not safely abstracted by Spec, we obtain a possibly spurious counterexample CE .Determine whether CE is spurious.If CE is not spurious, report the counterexample and stop; otherwise go to the next step.
• Step 3 (Refine).Use the spurious counterexample CE to refine the abstraction A(Π) so as to eliminate CE and go to step 1.
Despite the advent of automation via paradigms such as CEGAR, the biggest challenge in making model checking effective remains the problem of state space explosion.In the context of magic, this problem manifests itself in two forms.First, even in the purely sequential case, state explosion could occur during predicate abstraction.This is because the process of predicate abstraction, in the worst case, requires exponential time and memory in the number of predicates.Second, the state space size of a concurrent system increases exponentially with the number of components.Hence there is an obvious possibility of state space explosion for concurrent programs.In this article we present two orthogonal, yet smoothly integrated, techniques developed within magic to tackle the state explosion problem that can occur due to these factors while verifying both sequential and concurrent C programs.
Predicate Minimization.A fundamental underlying technique used in magic (as well as slam [8] and blast [33]) is predicate abstraction [31].Given a (possibly infinite-state) component C and a set of predicates P, verification with predicate abstraction consists of constructing and analyzing an automaton A, a conservative abstraction of C relative to P. However, as mentioned before, the process of constructing A is in the worst case exponential, both in time and space, in |P|.Therefore a crucial point in deriving efficient algorithms based on predicate abstraction is the choice of a small set of predicates.In other words, one of the main challenges in making predicate abstraction effective is identifying a small set of predicates that are sufficient for determining whether a property holds or not.The first technique we present is aimed at finding automatically such a minimal set from a given set of candidate predicates [12].
Compositional Two-level Abstraction Refinement.The second technique we consider is aimed at solving the state space explosion problem resulting from concurrent systems.We propose a fully automated compositional two-level CE-GAR scheme to verify that a parallel composition Π = C 1 || . . .||C n of n sequential C components is safely abstracted by its specification Spec [14].The basic idea is to extract as small a model as possible from Π by employing two orthogonal abstraction schemes.To this end we use predicate abstraction to handle data and an action-guided abstraction to handle events.Each type of abstraction is also associated with a corresponding refinement scheme.The actionguided abstraction-refinement loop is embedded naturally within the predicate abstraction-refinement cycle, yielding a two-level framework.In addition, abstraction, counterexample validation and refinement can each be carried out component-wise, making our scheme compositional and scalable.More precisely, the steps involved in the two-level CEGAR algorithm presented in this article can be summarized as follows: • Step 2 : Verification.By construction, the extracted model MA exhibits all of the original system's behaviors, and usually many more.We now check whether MA Spec.If successful, we conclude that our original system Π is safe.The verification procedure is fully automated, and requires no user input beyond supplying the C programs, assumptions about the environment, and the specification to be verified.We have implemented the algorithm within magic and have carried out a number of case studies, which we report here.To our knowledge, our algorithm is the first to invoke CEGAR over more than a single abstraction refinement scheme (and in particular over action-based abstractions), and also the first to combine CEGAR with fully automatic compositional reasoning for concurrent systems.In summary, the crucial features of our approach consist of the following: • We leverage two very different kinds of abstraction to reduce a parallel composition of sequential C programs to a very coarse parallel composition of finite-state processes.The first abstraction partitions the (potentially infinite) state space according to the possible values of state variables, whereas the second abstraction lumps these resulting states together according to the events that they can communicate.
• We invoke a predicate minimization algorithm to compute a minimal set of predicates sufficient to validate or invalidate the specification.
• A counterexample-guided abstraction refinement scheme incrementally refines these abstractions until the right granularity is achieved to decide whether the specification holds or not.We note that while termination cannot of course be guaranteed, all of our experimental examples could be handled without requiring human input.
• Our use of compositional reasoning, grounded in standard process algebraic techniques, enables us to perform most of our analysis component by com-ponent, without ever having to construct global state spaces except at the highest (most abstract) level.
The experiments we have carried out range over a variety of sequential and concurrent examples, and indicate that both the techniques we present, either combined or separately, increase the capacity of magic to verify large C programs.For example, in some cases, predicate minimization can improve the time consumption of magic by over two orders of magnitude and the memory consumption by over an order of magnitude.With the smaller examples we find that our two-level approach constructs models that are 2 to 11 times smaller than those generated by predicate abstraction alone.These ratios increase dramatically as we consider larger and larger examples.In some of our instances magic constructs models that are more than two orders of magnitude smaller than those created by mere predicate abstraction.Full details are presented in Section 9.
The rest of this article is organized as follows.In Section 2 we discuss related work.In section 3 we present some preliminary definitions.In Section 4 we formally define the two-level CEGAR algorithm.In Section 5 we describe the process of constructing the LTS MP i from the component C i using predicate abstraction.In Section 6 we define the process of checking if a counterexample is valid at the level of the C i 's.We also present our approach of refining the appropriate MP i if the counterexample is found to be spurious.Recall that this is achieved by constructing a minimal set of predicates sufficient to eliminate spurious counterexamples.In Section 7 we present the action-guided abstraction used to obtain MA i from MP i .In Section 8 we define the process of checking if a counterexample is valid at the level of the MP i 's.We also show how to refine the appropriate MA i by constructing a finer state aggregation if the counterexample is found to be spurious.Finally, we present experimental evaluation of our ideas in Section 9 and conclude in Section 10.

Related Work
Predicate abstraction [31] was introduced as a means to conservatively transform infinite-state systems into finite-state ones, so as to enable the use of finitary techniques such as model checking [17,15].It has since been widely used-see, for instance [8,33,23,27,24,42,9,26].
The formalization of the more general notion of abstraction first appeared in [25].We distinguish between exact abstractions, which preserve all properties of interest of the system, and conservative abstractions-used in this paperwhich are only guaranteed to preserve 'undesirable' properties of the system (e.g., [37,20]).The advantage of the latter is that they usually lead to much greater reductions in the state space than their exact counterparts.However, conservative abstractions in general require an iterated abstraction refinement mechanism (such as CEGAR [19]) in order to establish specification satisfaction.
The abstractions we use on finite-state processes essentially lump together states that have the same set of enabled actions, and gradually refine these partitions according to reachable successor states.Our refinement procedure can be seen as an atomic step of the Paige-Tarjan algorithm [45], and therefore yields successive abstractions which converge in a finite number of steps to the bisimulation quotient of the original process.
Counterexample-guided abstraction refinement [19,38], or CEGAR, is an iterative procedure whereby spurious counterexamples for a specification are repeatedly eliminated through incremental refinements of a conservative abstraction of the system.Both the abstraction and refinement techniques for such systems, as applied in [19,38], are essentially different than the predicate abstraction approach we follow.For example, the abstraction in [38] is done by assigning non-deterministic values to selected sets of variables, while refinement corresponds to gradually returning to the original definition of these variables.CEGAR has been used in many cases, some non-automated [43], others at least partly automated [8,10,46,39,33].The problem of finding small sets of predicates has also been investigated in the context of hardware designs in [21,16].
Compositionality, which features crucially in our work, is broadly concerned with the preservation of properties under substitution of components in concurrent systems.It has been most extensively studied in process algebra (e.g., [36,41,47]), particularly in conjunction with abstraction.Bensalem et al. [11] have presented a compositional framework for (non-automated) CEGAR over databased abstractions.Their approach differs from ours in that communication takes place through shared variables (rather than blocking message-passing), and abstractions are refined by eliminating spurious transitions, rather than by splitting abstract states.
A technique closely related to compositionality is that of assume-guarantee reasoning [40,34].It was originally developed to circumvent the difficulties associated with generating exact abstractions, and has recently been implemented as part of a fully automated and incremental verification framework [22].
Among the works most closely resembling ours we note the following.The Bandera project [24] offers tool support for the automated verification of Java programs based on abstract interpretation; there is no automated CEGAR and no explicit compositional support for concurrency.Pȃsȃreanu et al. [46] have imported Bandera-derived abstractions into an extension of Java PathFinder which incorporates CEGAR.However, once again no use is made of compositionality, and only a single level of abstraction is considered.Stoller [48] has implemented another tool in Java PathFinder which explicitly supports concurrency; it uses datatype abstraction on the first level, and partial order reduction with aggregation of invisible transitions on the second level.Since all abstractions are exact it does not require the use of CEGAR.The slam project [5,8,9] has been very successful in analyzing interfaces written in C. It is built around a single-level predicate abstraction and automated CEGAR treatment, and offers no explicit compositional support for concurrency.Lastly, the blast project [1, 33,32] proposes a single-level lazy (on-the-fly) predicate abstraction scheme together with CEGAR and thread-modular assume-guarantee reasoning.The blast frame-work is based on shared variables rather than message-passing as the communication mechanism.

Background
This section gives background information about the MAGIC framework and our abstraction-refinement-based software verification algorithm.
Definition 1 (Labeled Transition Systems).A labeled transition system (LTS) M is a quadruple (S, init, Act, T ), where (i) S is a finite non-empty set of states, (ii) init ∈ S is the initial state, (iii) Act is a finite set of actions (alphabet), and (iv) T ⊆ S × Act × S is the transition relation.
We assume that there is a distinguished state STOP ∈ S which has no outgoing transitions, i.e., ∀s ∈ S, ∀a ∈ Act, (STOP, a, s ) ∈ T .If (s, a, s ) ∈ T , then (s, s ) will be referred to as an a-transition and will be denoted by s a −→ s .For a state s and action a, we define Succ(s, a) = {s : s a −→ s }.Action a is said to be enabled at state s iff Succ(s, a) = ∅.For s ∈ S we write export(s) = {a ∈ Act | ∃t ∈ S s a −→ t} to denote the set of actions enabled in state s.
Actions.In accordance with existing practice, we use actions to denote externally visible behaviors of systems being analyzed, e.g., acquiring a lock.Actions are atomic, and are distinguished simply by their names.Since we are analyzing C, a procedural language, we model the termination of a procedure (i.e., a return from the procedure) by a special class of actions called return actions.Every return action r is associated with a unique return value RetV al(r).Return values are either integers or void.We denote the set of all return actions whose return values are integers by IntRet and the special return action whose return value is void by VoidRet.All actions which are not return actions are called basic actions.A distinguished basic action τ denotes the occurrence of an unobservable internal event.
Definition 2 (Traces).A trace π is a finite (possibly empty) sequence of actions.We define the language L(M ) of the LTS M to be the set of all traces a 1 . . .a n ∈ Act * such that, for some sequence s 0 . . .s n of states of M (with =⇒ t}; this represents the set of states reachable through π from some state in Q. Definition 4 (Projection).Let π ∈ Act * be a trace over Act, and let Act be another (not necessarily disjoint) alphabet.The projection π Act of π on Act is the sub-trace of π obtained by simply removing all actions in π that are not in Act .

Definition 5 (Parallel Composition
In other words, components must synchronize on shared actions and proceed independently on local actions.This notion of parallel composition originates from CSP.The following results are well-known [36,47] and we state them here without proof: Theorem 1.
1. Parallel composition is associative and commutative as far as the accepted language is concerned.Thus, in particular, no bracketing is required when combining more than two LTSs.2. Let M 1 , . . ., M n and M 1 , . . ., M n be LTSs with every pair of LTSs M i , M i sharing the same alphabet Act i = Act i .If, for each 1 i n, we have In other words, parallel composition preserves language containment.3. Let M 1 , . . ., M n be LTSs with respective alphabets Act 1 , . . ., Act n , and let π be any trace.Then π ∈ L(M 1 || . . .||M n ) iff, for each 1 i n, we have π Act i ∈ L(M i ).In other words, whether a trace belongs to a parallel composition of LTSs can be checked by projecting and examining the trace on each individual component separately.
Procedure Abstraction.At present, magic supports the full syntax of ANSI C1 .Also, it can handle a set of procedures with a DAG-like call graph as one procedure by inlining.Therefore, for simplicity, we assume that a software component consists of a single (non-recursive) procedure.In other words, in our approach, procedures and components are synonymous.As mentioned before, magic allows specifications to be supplied for components (i.e., procedures), as well as for programs.As the goal of magic is to verify whether a C program is safely abstracted by its specification, the need for program specifications is obvious.
Procedure specifications serve another crucial purpose.They are used as assumptions while constructing component models via predicate abstraction.
More precisely, suppose we are trying to construct MP i from component C i .In general C i may invoke several library routines and it is quite common for the actual implementation of these routines to be unavailable during verification.In such situations, a specification for these routines is used instead to construct MP i conservatively.
In practice, it often happens that a procedure performs quite different tasks for various calling contexts, i.e., values of its parameters and global variables.In our approach, this phenomenon is accounted for by allowing multiple specification LTSs for a single procedure.The selection among these LTSs is achieved by guards, i.e., formulas, which describe the conditions on the procedure's parameters and globals under which a certain LTS is applicable.This gives rise to the notion of procedure abstraction (PA).Formally, a PA for a procedure C is a tuple d, l where: • d is the declaration for C, as it appears in a C header file.
• l is a finite list g 1 , M 1 , . . ., g k , M k where each g i is a guard formula ranging over the parameters and globals of C, and each M i is an LTS.
The procedure abstraction expresses that C conforms to one LTS chosen among the M i 's.More precisely, C conforms to M i if the corresponding guard g i evaluates to true over the actual arguments and globals passed to C. We require that the guard formulas g i be mutually exclusive and complete (i.e., cover all possibilities) so that the choice of M i is always unambiguously defined.Since PAs are used as assumptions during model construction, they are often referred to as assumption PAs.
In this article we only consider procedures that terminate by returning.In particular we do not handle constructs like setjmp and longjmp.Furthermore, since LTSs are used to model procedures, any LTS (S, init, Act, T ) must obey the following condition: ∀s ∈ S, s a −→ STOP iff a is a return action.
Program Abstraction.Just as a PA encapsulates a component's specification, a program's specification is expressed through a program abstraction.Program abstractions are defined in a similar manner as PAs.Formally, let Π be a program consisting of n components C 1 , . . ., C n .Then a program abstraction for Π is a tuple d, l where: • l is a finite list g 1 , M 1 , . . ., g k , M k where each g i = g 1 i , . . ., g n i such that g j i is a guard formula ranging over the parameters and globals of C j , and each M i is an LTS.
Without loss of generality we will assume throughout this paper that our target program abstraction contains only one guard Guard 1 , . . ., Guard n and one LTS Spec.To achieve the result in full generality, the described algorithm can be iterated over each element of the target abstraction.

Concurrency and Communication.
We consider a concurrent version of the C programming language in which a fixed number of sequential components C 1 , . . ., C n are run concurrently on independent platforms.Each component C i has an associated alphabet of actions Act i , and can communicate a particular event a in its alphabet only if all other programs having a in their alphabets are willing to synchronize on this event.Actions are realized using calls to library routines.Programs have local variables but no shared variables.In other words, we are assuming blocking message-passing (i.e., 'send' and 'receive' statements) as the sole communication mechanism.When there is no communication, the components execute asynchronously.

Two-Level Counterexample-Guided Abstraction Refinement
Consider a concurrent C program Π = C 1 || . . .||C n and a specification Spec.Our goal is to verify that the concurrent C program Π is safely abstracted by the LTS Spec.Since we use trace containment as our notion of conformance, the concurrent program meets its specification iff L(Π) ⊆ L(Spec).
Theorem 1 forms the basis of our compositional approach to verification.We first invoke predicate abstraction to reduce each (infinite-state) program C i into a finite LTS (or process) MP i having the same alphabet as C i .The initial abstraction is created with a relatively small set of predicates, and further predicates are then added as required to refine the MP i 's and eliminate spurious counterexamples.This procedure may add a large number of predicates, yielding an abstract model with a potentially huge state space.We therefore seek to further reduce each MP i into a smaller LTS MA i , again having the same alphabet as C i .Both abstractions are such that they maintain the language containment L(C i ) ⊆ L(MP i ) ⊆ L(MA i ).Theorem 1 then immediately yields the rule: The converse need not hold: it is possible for a trace π / ∈ L(Spec) to belong to L(MA 1 || . . .||MA n ) but not to L(Π).Such a spurious counterexample is then eliminated, either by suitably refining the MA i 's (if π / ∈ L(MP 1 || . . .||MP n )), or by refining the MP i 's (and subsequently adjusting the MA i 's to reflect this change).The chief property of our refinement procedure (whether at the MA i or the MP i level) is that it purges the spurious counterexample by restricting the accepted language yet maintains the invariant , where primed terms denote refined processes.Note that, according to Theorem 1, we can check whether π ∈ L(MP 1 || . . .||MP n ) and whether π ∈ L(Π) one component at a time, without it ever being necessary to construct the full state spaces of the parallel compositions.This iterated process forms the basis of our two-level CEGAR algorithm.
We describe this algorithm in Figure 1.The predicate abstraction and refinement procedures are detailed in Section 5 and Section 6.We present our action-guided abstraction and refinement steps (marked † and ‡ respectively) in Section 7 and Section 8.
do predicate abstraction refinement of MP j to eliminate π † adjust or create new abstraction MAj else ‡ let 1 ≤ j ≤ n be an index such that π Act j ∈ L(MPj) do action-guided refinement of MAj to eliminate π endrepeat.

Predicate Abstraction
Let Spec = (S S , init S , Act S , T S ) and the assumption PAs be {PA 1 , . . ., PA k }.In this section we show how to extract MP i from C i using the assumption PAs, the guard Guard i and the predicates.This is a brief description of the model construction technique described by Chaki et al. [13].Since in this section we shall be dealing with a single component, we shall refer to C i , MP i and Guard i as simply C, MP and Guard respectively.The extraction of MP relies on several principles: • Every state of MP models a state during the execution of C; consequently every state is composed of a control component and a data component.
• The control components intuitively represent values of the program counter, and are formally obtained from the control flow graph (CFG) of C.
• The data components are abstract representations of the memory state of C.These abstract representations are obtained using predicate abstraction.
• The transitions between states in MP are derived from the transitions in the CFG, taking into account the assumption PAs and the predicate abstraction.This process involves reasoning about C expressions, and will therefore require the use of a theorem prover.
Without loss of generality, we can assume that there are only five kinds of statements in C: assignments, call-sites, if-then-else branches, goto and return.Note that call-sites correspond to library routines called by C whose code is unavailable and therefore cannot be inlined.We can also assume, without loss of generality, that all expression in C are side-effect free.For simplicity, we assume the absence of indirect function calls and pointer dereferences in the left hand sides (LHS's) of assignments2 .We denote by Stmt the set of statements of C and by Expr the set of all C expressions over the variables of C.
As a running example of C, we use the C program shown in Figure 2. It invokes two library routines do a and do b.Let the guard and LTS list in the assumption PA for do a be true, Do A .This means that under all invocation conditions, do a is safely abstracted by the LTS Do A. Similarly the guard and LTS list in the assumption PA for do b is true, Do B .The LTSs Do A and Do B are described in Figure 3.We also use Guard = true and Spec = Spec (shown in Figure 3).-S CF is a set of states. - S CF contains a distinguished FINAL state.The transitions between states reflect the flow of control between their labeling statements: L(I CF ) is the initial statement of C and (s 1 , s 2 ) ∈ T CF iff one of the following conditions hold: -L(s 1 ) is an assignment or call-site with L(s 2 ) as its unique successor.
-L(s 1 ) is a goto with L(s 2 ) as its target.
-L(s 1 ) is a branch with L(s 2 ) as its then or else successor.
-L(s 1 ) is a return statement and s 2 = FINAL.
Example 1.The CFA of our example program is shown in Figure 4.Each nonfinal state is labeled by the corresponding statement label (the FINAL state is labeled by FINAL).Henceforth we will refer to each CFA state by its label.Therefore the states of the CFA in Figure 4 are S0, ..., S9, FINAL with S0 being the initial state.
Predicate inference.Since the construction of MP from C involves predicate abstraction, it is parameterized by a set of predicates P. We will often indicate this explicitly by referring to MP as MP(P).In particular we will write MP(P 1 ) and MP(P 2 ) to denote the LTSs obtained via predicate abstraction from C using two sets of predicates P 1 and P 2 .The main challenge in predicate abstraction is to identify the set P that is necessary for proving the given property.In our framework we require P to be a subset of the branch statements in C. Therefore we sometimes refer to P or subsets of P simply as a set of branches.
The construction of MP associates with each state s of the CFA a finite subset of Expr derived from P, denoted by P s .The process of constructing the P s 's from P is known as predicate inference and is described by the algorithm PredInfer in Figure 5.Note that P FINAL is always ∅.
Fig. 4. The CFA for our example program.Each non-FINAL state is labeled the same as its corresponding statement.The initial state is labeled S0.The states are also labeled with inferred predicates when P = {p1, p2} where p1 = (y < 10) and p2 = (y > 5).Note that, in general, the set of inferred predicates are not necessarily equal at all states of the CFA.Rather they are computed as the weakest preconditions of the inferred predicates at their successor states.
The algorithm uses a procedure for computing the weakest precondition WP [35,28,8] of a predicate p relative to a given statement.Consider a C assignment statement a of the form v = e.Let ϕ be a C expression.Then the weakest precondition of ϕ with respect to a, denoted by WP[a]{ϕ}, is obtained from ϕ by replacing every occurrence of v in ϕ with e.Note that we need not consider the case where a pointer appears in the LHS of a since we have disallowed such constructs from appearing in C.
The weakest precondition is clearly an element of Expr as well.The purpose of predicate inference is to create P s 's that lead to a very precise abstraction of the program relative to the predicates in P. Intuitively, this is how it works.Let s, t ∈ S CF be such that L(s) is an assignment statement and (s, t) ∈ T CF .Suppose a predicate p t gets inserted in P t at some point during the execution of PredInfer and suppose p s = WP[L(s)]{p t }.Now consider any execution state of C where the control has reached L(t) after the execution of L(s).It is obvious that p t will be true in this state iff p s was true before the execution of L(s).In terms of the CFA, this means that the value of p t after a transition from s to t can be determined precisely on the basis of the value of p s before the transition.This motivates the inclusion of p s in P s .The cases in which L(s) is not an assignment statement can be explained analogously.
Note that PredInfer may not terminate in the presence of loops in the CFA.However, this does not mean that our approach is incapable of handling C programs containing loops.In practice, we force termination of PredInfer by limiting the maximum size of any P s .Using the resulting P s 's, we can compute the states and transitions of the abstract model as described in the next section.Irrespective of whether PredInfer was terminated forcefully or not, MP is guaranteed  to be a safe abstraction of C. We have found this approach to be very effective in practice.A similar algorithm was proposed by Dams and Namjoshi [26].So far we have described a method for computing the CFA and a set of predicates associated with each state of the CFA.The states of MP correspond to the different states of the CFA along with various possible valuations of the predicates inferred at these states.We now define the notions of a predicate valuation and its concretization formally.
Definition 6 (Predicate valuation and concretization).For a CFA node s suppose P s = {p 1 , . . ., p k }.Then a valuation of P s is a Boolean vector v 1 , . . ., v k .Let V s be the set of all valuations of P s .The predicate concretization function As mentioned before, the states of MP correspond to the states of the CFA and possible valuations of the predicates associated with these states.In other words, one can view the states of MP as being obtained by splitting up each state s of the CFA in accordance with various valuations of the predicate set P s inferred at s.We now define this process more formally.
Definition 7 (Splitting up states of the CFA).Each state s ∈ S CF gives rise to a set of states of MP, denoted by S s .In addition, MP has a unique initial state INIT.The definition of S s consists of the following sub-cases: • If L(s) is an assignment, branch, goto or return statement, then S s = {s} × V s .
• Suppose L(s) is a call-site for a library routine lib and g 1 , P 1 , . . ., g n , P n is the guard and LTS list in the assumption PA for lib.For 1 ≤ i ≤ n, let In the rest of this article we shall refer to states of MP of the form (s, V ) as normal states.Also we shall call states of MP of the form (s, V, c) as inlined states since these states can be thought of as arising due to inlining of assumed PAs at call-sites.We are now ready to define MP precisely.Alphabet of MP.So far we have defined every component of MP except Act I and T I .In this section we shall describe Act I and in the next section present how T I can be computed.Intuitively the alphabet of MP contains every event in the alphabet of Spec along with the events in the alphabets of inlined LTSs.

Definition 8 (MP)
Formally, let CallSites ⊆ S CF be the set of states of the CFA that are labelled by call-sites.Let cs be an arbitrary element of CallSites such that L(cs) is a callsite for library routine lib.Suppose g 1 , P 1 , . . ., g n , P n is the guard and LTS list in the assumption PA for lib.For 1 ≤ i ≤ n, let P i = (S i , init i , Act i , T i ).Then we define the function CallAct as: CallAct(cs) = ∪ n i=1 Act i .Once we have defined the function CallAct, Act I is simply equal to (∪ cs∈CallSites CallAct(cs))∪ Act S .
Computing T I .We now describe how to compute T I .Intuitively, we add a transition between two abstract states unless we can prove that there is no transition between their corresponding concrete states.If we cannot prove this, we say that the two states (or the two formulas representing them) are admissible.As we shall soon see, this problem can be reduced to the problem of deciding whether ¬(ψ 1 ∧ ψ 2 ) is valid, where ψ 1 and ψ 2 are first order formulas over the integers.Solving it, therefore, requires the use of a theorem prover.In general the problem is known to be undecidable.However, for our purposes it is sufficient that the theorem prover be sound and always terminate.Several publicly available theorem provers (such as Simplify [44]) have this characteristic.
Formally, given arbitrary formulas ψ 1 and ψ 2 , we say that the formulas are admissible if the theorem prover returns false or unknown on ¬(ψ 1 ∧ ψ 2 ).We denote this by Adm(ψ 1 , ψ 2 ).Otherwise the formulas are inadmissible, denoted by ¬Adm(ψ 1 , ψ 2 ).The computation of T I consists of several sub-cases and we shall consider each separately.
• Transitions from INIT.Recall that Guard represents guard qualifying the initial states.First, we add a transition (INIT, τ, (I CF , V )) to T I iff Adm(Γ ICF (V ), Guard ).
• Handling return statements.((s, V ), a, STOP) ∈ T I iff L(s) is a return statement, a is a return action, and either (i) L(s) returns the expression e, a ∈ IntRet and Adm(Γ s (V ), (e == RetV al(a))), or (ii) L(s) returns void and a = VoidRet.If L(s) returns the expression e but condition (i) above is not applicable for any a ∈ IntRet, we add ((s, V ), VoidRet, STOP) to T I .This ensures that from every "return" state there is at least one return action to STOP, and if an applicable return action cannot be determined, VoidRet is used as the default3 .
• Handling call-sites.Suppose L(s 1 ) is a call-site for a library routine lib and g 1 , P 1 , . . ., g n , P n is the guard and LTS list in the assumption PA for lib.Also, let (s Then for 1 ≤ i ≤ n, we do the following: 1.Let g i be the guard obtained from g i by replacing every parameter of lib by the corresponding argument passed to it at L(s 1 ).If Adm(g i , Γ s1 (V 1 )), then let P i = (S i , init i , Act i , T i ) and proceed to step 2, otherwise move on to the next i.

Add a transition ((s
3. For each transition (s, a, t) ∈ T i where t = STOP, add a transition ((s 4. If L(s 1 ) is a call-site with an assignment, i.e., of the form x = lib(...), then: -For each transition (s, VoidRet, STOP) -For each transition (s, a, STOP) 5. If L(s 1 ) is a call-site without an assignment, i.e., of the form lib(...), then for each transition (s, a, STOP) Clearly, |S I | is exponential in |P|, as are the worst case space and time complexities of constructing MP.Given a counterexample CE to the trace containment check, i.e., a trace of MP that is not a trace of Spec, we have to perform the following steps: (i) check if CE is a real counterexample, i.e., whether it is a concrete trace of C, and (ii) if CE is not a real counterexample, construct a minimal set of predicates that will prevent CE , along with every other spurious counterexample discovered so far, from arising in future iterations of the two-level CEGAR loop.In this section, we present precisely how these two steps are performed by magic [12].We begin with the definition of a counterexample.

Definition 9 (Counterexample).
A counterexample is a finite sequence ŝ1 , a 1 , ŝ2 , a 2 , . . ., a n−1 , ŝn such that: Counterexample checking The CECheck algorithm, described in Figure 7, takes C and a counterexample CE as inputs and returns true if CE is a valid counterexample of C. Intuitively it computes the weakest precondition of CE and then checks if this weakest precondition is satisfiable.This is a backward traversal based algorithm.There is an equivalent algorithm [10] that is based on a forward traversal and uses strongest postconditions instead of weakest preconditions.
Checking counterexample elimination As we shall see in the next section, the process of predicate minimization requires us to solve the following problem: given a spurious counterexample CE and a set of branches P, determine if P eliminates CE .This can be achieved in two broad steps: (i) construct MP(P) and (ii) determine if there exists a counterexample CE of MP(P) such that CE is consistent with CE .Algorithm CEEliminate, described in Figure 8, formally presents how these two steps can be performed.Note that, in practice, CEEliminate can proceed in an on-the-fly manner without constructing the full MP(P) upfront.
To understand CEEliminate we need to understand the concept of consistency between states.Let P 1 and P 2 be two sets of branches of C. Let MP(P 1 ) = S 1 , init 1 , Act I , T 1 and MP(P 2 ) = S 2 , init 2 , Act I , T 2 be the two LTSs obtained by predicate abstraction using P 1 and P 2 respectively.Let s 1 ∈ S 1 and s 2 ∈ S 2 be two arbitrary states of MP(P 1 ) and MP(P 2 ) respectively.Recall that either s 1 = INIT or s 1 = STOP or s 1 is of the form (s, V ) or of the form (s, V, c) where s is a state of the CFA, V ∈ V s is a predicate valuation and c is an inlined LTS state.The same holds true for s 2 as well.
Intuitively s 1 and s 2 are consistent if they differ at most in their predicate valuations.Formally, s 1 and s 2 are said to be consistent (denoted by Cons(s 1 , s 2 )) iff one of the conditions hold: - Minimizing the eliminating set In this section we solve the following problem: given a set of spurious counterexamples T and a set of candidate predicates P, find a minimal set P min ⊆ P which eliminates all the counterexamples in T .Note that, in our context, T will contain every spurious counterexample encountered so far in the CEGAR loop, while P will be all the branches of C. We present a three step algorithm for solving this problem.First, find a mapping T → 2 2 P between each counterexample in T and the set of subsets P that eliminate it.This can be achieved by iterating through every P sub ⊆ P and CE ∈ T , using CEEliminate to determine if P sub can eliminate CE .This approach is exponen-Input: Spurious counterexample CE = ŝ1, a1, ŝ2, a2, . . ., an−1, ŝn , set of predicates P Output: true if CE is eliminated by P and false otherwise Compute MP(P) = SP , IP , ActP , TP Variable: X, Y of type subset of SP Initialize: tial in | P| but below we list several ways to reduce the number of attempted combinations4 : -Limit the size of attempted combinations to a small constant, e.g. 5, assuming that most counterexamples can be eliminated by a small set of predicates.-Stop after reaching a certain size of combinations if any eliminating solutions have been found.-Break up the CFG into blocks and only consider combinations of predicates within blocks (keeping combinations in other blocks fixed).-Use data flow analysis to only consider combinations of related predicates.
-For any CE ∈ T , if a set P sub eliminates CE , ignore all supersets of P sub with respect to CE (as we are seeking a minimal solution).
Second, encode each predicate p i ∈ P with a new Boolean variable p b i .We use the terms 'predicate' and 'the Boolean encoding of the predicate' interchangeably.Third, derive a Boolean formula σ, based on the predicate encoding, that represents all the possible combinations of predicates that eliminate the elements of T .We use the following notation in the description of σ.Let CE ∈ T be a counterexample: -k CE denotes the number of sets of predicates that eliminate CE .We use the same notation for the conjunction of the predicates in this set.
The formula σ is defined as follows: For any satisfying assignment to σ, the predicates whose Boolean encodings are assigned true are sufficient for eliminating all elements of T .
From the various possible satisfying assignments to σ, we look for the one with the smallest number of positive assignments.This assignment represents the minimal number of predicates that are sufficient for eliminating T .Since σ includes disjunctions, it cannot be solved directly with a 0-1 ILP solver.We therefore use PBS [7], a solver for Pseudo Boolean Formulas.
A pseudo-Boolean formula is of the form represents one of the inequality or equality relations ({<, ≤, >, ≥, =}).Each such constraint can be expanded to a CNF formula (hence the name pseudo-Boolean), but this expansion can be exponential in n. PBS does not perform this expansion, but rather uses an algorithm designed in the spirit of the Davis-Putnam-Loveland algorithm that handles these constraints directly.PBS accepts as input standard CNF formulas augmented with pseudo-Boolean constraints.Given an objective function in the form of pseudo-Boolean formula, PBS finds an optimal solution by repeatedly tightening the constraint over the value of this function until it becomes unsatisfiable.That is, it first finds a satisfying solution and calculates the value of the objective function according to this solution.It then adds a constraint that the value of the objective function should be smaller by one 5 .This process is repeated until the formula becomes unsatisfiable.The objective function in our case is to minimize the number of chosen predicates (by minimizing the number of variables that are assigned true): Example 5. Suppose that the counterexample CE 1 is eliminated by either {p 1 , p 3 , p 5 } or {p 2 , p 5 } and that the counterexample CE 2 can be eliminated by either {p 2 , p 3 } or {p 4 }.The objective function is min 5 i=1 p b i and is subject to the constraint: )) The minimal satisfying assignment in this case is Other techniques for solving this optimization problem are possible, including minimal hitting sets and logic minimization.The PBS step, however, has not been a bottleneck in any of our experiments.

Action-Guided Abstraction
We now present a CEGAR on LTSs [14].Let MP = S I , init I , Act I , T I be an LTS obtained via predicate abstraction from a component C, as described in Section 5. We first create an LTS MA 0 = S 0 A , init 0 A , Act I , T 0 A such that (i) L(MP) ⊆ L(MA 0 ) and (ii) MA 0 contains at most as many states as MP (and typically many fewer).Given an abstraction MA = S A , init A , Act I , T A of MP and a spurious trace π ∈ L(MA) \ L(MP), our refinement procedure produces a refined abstraction MA = S A , init A , Act I , T A such that (i) L(MP) ⊆ L(MA ) ⊂ L(MA), (ii) π / ∈ L(MA ), and (iii) MA contains at most as many states as MP.It is important to note that we require throughout that MP, MA 0 , MA, and MA all share the same alphabet.We also remark that iterating this refinement procedure must converge in a finite number of steps to an LTS that accepts the same language as MP.
Let us write B = S B , init B , Act I , T B to denote a generic abstraction of MP.States of B are called abstract states, whereas states of MP are called concrete states.In the context of action-guided abstraction, abstract states are always disjoint sets of concrete states that partition S I , and our abstraction refinement step corresponds precisely to a refinement of the partition.For s ∈ S I a concrete state, the unique abstract state of B to which s belongs is denoted by [s] B .
In any abstraction B that we generate, a partition S B of the concrete states of MP uniquely determines the abstract model B: the initial state init B of B is simply [init I ] B , and for any pair of abstract states u, v ∈ S B and any action a ∈ Act I , we include a transition u a −→ v ∈ T B iff there exist concrete states s ∈ u and t ∈ v such that s a −→ t.This construction is an instance of an existential abstraction [20].It is straightforward to show that it is sound, i.e., that L(MP) ⊆ L(B) holds for any abstraction B.
The initial partition S 0 A of concrete states identifies two states s, t ∈ S I if they share the same set of immediately enabled actions: Again, this uniquely defines our initial abstraction MA 0 , the construction marked † in Figure 1 (c.f.Section 4).

Action-Guided Refinement
In order to describe the refinement step, we need an auxiliary definition.Given an abstract state u ∈ S B and an action a ∈ Act I , we construct a refined partition S B = Split(S B , u, a) of S I which agrees with S B outside of u, but distinguishes concrete states in u if they have different abstract a-successors in S B .More precisely, for any s This refined partition uniquely defines a new abstraction, which we write Abs(Split(S B , u, a)).Note that in order to compute the transition relation of Abs(Split(S B , u, a)) it suffices to adjust only those transitions in T B that have u either as a source or a target.
The refinement step takes as input a spurious counterexample π ∈ L(MA) \ L(MP) and returns a refined abstraction MA which does not accept π.This is achieved by repeatedly splitting states of MA along abstract paths which accept π.The algorithm in Figure 9 (marked ‡ in Figure 1) describes this procedure in detail.Theorem 2. The algorithm described in Figure 9 is correct and always terminates.
Proof.We first note that it is obvious that whenever the algorithm terminates it returns an abstraction MA with π / ∈ L(MA ).It is equally clear, since MA is obtained via successive refinements of MA, that L(MP) ⊆ L(MA ) ⊂ L(MA).It remains to show that every splitting operation performed by the algorithm results in a proper partition refinement; termination then follows from the fact that the set of states of MP is finite.
Observe that, since π / ∈ L(MP), Reach(MP, init, π) = ∅, and therefore the inner while loop always terminates.At that point, we claim that (i) there is an abstract transition u j−2 aj−1 −→ u j−1 ; (ii) there are some concrete states in u j−2 reachable (in MP) from init; and (iii) none of these reachable concrete states have concrete a j−1 -successors in u j−1 .Note that (ii) follows from the fact that the inner loop is entered with reachable states = {init}, whereas (i) and (iii) are immediate.Because of the existential definition of the abstract transition relation, we conclude that u j−2 contains two kinds of concrete states: some having concrete a j−1 -successors in u j−1 , and some not.Splitting the state u j−2 according to action a j−1 therefore produces a proper refinement.
We remark again that each splitting operation corresponds to a unit step of the Paige-Tarjan algorithm [45].Iterating our refinement procedure therefore converges to the bisimulation quotient of MP.Note however that, unlike the Paige-Tarjan algorithm, our refinement process is counterexample driven and not aimed at computing the bisimulation quotient.In practical verification instances, we usually stop well before reaching this quotient.
We stress that the CEGAR algorithm described in Figure 1 never invokes the above abstraction refinement routine with the full parallel composition MA = MA 1 || . . .||MA n as input.Indeed, this would be very expensive, since the size of the global state space grows exponentially with the number of concurrent processes.It is much cheaper to take advantage of compositionality: by Theorem 1, The advantage of this approach follows from the fact that the computational effort required to identify MA i grows only linearly with the number of concurrent components.

Experimental Evaluation
We implemented our technique inside magic and experimented with three broad goals in mind.The first goal was to check the effectiveness of predicate minimization by itself on purely sequential benchmarks.The second goal was to compare the overall effectiveness of the proposed two-level CEGAR approach, particularly insofar as memory usage is concerned.The third goal was to verify the effectiveness of our action-guided abstraction scheme by itself.We carried out experiments with a wide range of benchmarks, both sequential and concurrent.Each benchmark consisted of an implementation (a C program) and a specification (provided separately as an LTS).All of the experiments were carried out on an AMD Athlon 1600 XP machine with 900 MB RAM running RedHat 7.1.
Input: Set of predicates P Output: Subset of P that eliminates all spurious counterexamples so far Variable: X of type set of predicates LOOP: Create a random ordering p1, . . ., p k of P For i = 1 to k do X := P \ {pi} If X can eliminate every spurious counterexample seen so far P := X Goto LOOP Return P Fig. 10.Greedy predicate minimization algorithm.

Predicate Minimization Results.
In this section we describes our results in the context of the first of the three goals mentioned above, i.e., checking the effectiveness of predicate minimization by itself.We also present results comparing our predicate minimization scheme with a greedy predicate minimization strategy implemented on top of magic.In each iteration, this greedy strategy first adds predicates sufficient to eliminate the spurious counterexample to the predicate set P. Then it attempts to reduce the size of the resulting P by using the algorithm described in Figure 10.The advantage of this approach is that it requires only a small overhead (polynomial) compared to Sample-and-Eliminate, but on the other hand it does not guarantee an optimal result.Further, we performed experiments with Berkeley's blast [33] tool.blast also takes C programs as input, and uses a variation of the standard CEGAR loop based on lazy abstraction, but without minimization.Lazy abstraction refines an abstract model while allowing different degrees of abstraction in different parts of a program, without requiring recomputation of the entire abstract model in each iteration.Laziness and predicate minimization are, for the most part, orthogonal techniques.In principle a combination of the two might produce better results than either in isolation.Benchmarks.We used two kinds of benchmarks.A small set of relatively simple benchmarks were derived from the examples supplied with the blast distribution and regression tests for magic.The difficult benchmarks were derived from the C source code of OpenSSL-0.9.6c, several thousand lines of code implementing the SSL protocol used for secure transfer of information over the Internet.A critical component of this protocol is the initial handshake between a server and a client.We verified different properties of the main routines that implement the handshake.The names of benchmarks that are derived from the server routine and client routine begin with ssl-srvr and ssl-clnt respectively.In all our benchmarks, the properties are satisfied by the implementation.Results Summary. Figure 11 summarizes the comparison of our predicate minimization strategy with the greedy approach while Figure 12 summarizes its comparison with BLAST and MAGIC without predicate minimization.Time consumptions for all experiments are given in seconds.The column Iter reports the number of iterations through the CEGAR loop necessary to complete the proof.Predicates are listed differently for the two tools.For blast, the first number is the total number of predicates discovered and used and the second number is the number of predicates active at any one point in the program (due to lazy abstraction this may be smaller).In order to force termination we imposed a limit of three hours on the running time.We denote by '*' in the Time column examples that could not be solved in this time limit.In these cases the other columns indicate relevant measurements made at the point of forceful termination.
For magic, the first number is the total number of expressions used to prove the property, i.e., | ∪ s∈SCF P s |.The number of predicates (the second number) may be smaller, as magic combines multiple mutually exclusive expressions (e.g., x == 1, x < 1, and x > 1) into a single, possibly non-binary predicate, having a number of values equal to the number of expressions (plus one, if the expressions do not cover all possibilities.)The final number for magic is the size of the final P. For experiments in which memory usage was large enough to be a measure of state space size rather than overhead, we also report memory usage (in megabytes).
The first magic results are for the magic tool operating in the standard refinement manner: in each iteration, predicates sufficient to eliminate the spurious counterexample are added to the predicate set.The second magic results are for the greedy predicate minimization strategy.The last magic results are for predicate minimization.Rather than solving the full optimization problem, we simplified the problem as described in section 6.In particular, for each trace we only considered the first 1,000 combinations and only generated 20 eliminating combinations.The combinations were considered in increasing order of size.After all combinations of a particular size had been tried, we checked whether at least one eliminating combination had been found.If so, no further combinations were tried.In the smaller examples we observed no loss of optimality due to these restrictions.We also studied the effect of altering these restrictions on the larger benchmarks and we report on our findings later.
For the smaller benchmarks, the various abstraction refinement strategies do not differ markedly.However, for our larger examples, taken from the SSL source code, the refinement strategy is of considerable importance.Predicate minimization, in general, reduced verification time (though there were a few exceptions to this rule, the average running time was considerably lower than for the other techniques, even with the cutoff on the running time).Moreover, predicate minimization reduced the memory needed for verification, which is an even more important bottleneck.Given that the memory was cutoff in some cases for other techniques before verification was complete, the results are even more compelling.
The greedy approach kept memory use fairly low, but almost always failed to find near-optimal predicate sets and converged much slower than the usual monotonic refinement or predicate minimization approaches.Further, it is not clear how much final memory usage would be improved by the greedy strategy if it were allowed to run to completion.Another major drawback of the greedy approach is its unpredictability.We observed that on any particular example, the greedy strategy might or might not complete within the time limit in different executions.Clearly, the order in which this strategy tries to eliminate predicates in each iteration is very critical to its success.Given that the strategy performs poorly on most of our benchmarks using a random ordering, more sophisticated ordering techniques may perform better.We leave this issue for future research.Next we experimented with different values of MAXSUB (the value of MAX-ELM was set equal to MAXSUB).The results we obtained are summarized in Figure 14.It appears that, at least for our benchmarks, increasing MAXSUB leads only to increased execution time without reduced memory consumption or number of predicates.The additional number of combinations attempted or constraints allowed does not lead to improved optimality.The most probable reason is that, as shown by our results, even though we are trying more combinations, the actual number or maximum size of eliminating combinations generated does not increase significantly.It would be interesting to investigate whether this is a feature of most real-life programs.If so, it would allow us, in most cases, to achieve near optimality by trying out only a small number of combinations or only combinations of small size.

Two-Level CEGAR Results
In this section we present our results with regard to the effectiveness of our proposed two-level and only action-guided CEGAR schemes.To this end, we carried out experiments on 36 benchmarks, of which 26 were sequential programs and 10 were concurrent programs.Each example was verified twice, once with only the low-level abstraction, and once with the full two-level algorithm.Tests that used only the low-level predicate abstraction refinement scheme are marked by PredOnly in our results tables, whereas tests that also incorporated our LTS action-guided abstraction refinement procedure are marked by BothAbst.Both schemes started out with the same initial sets of predicates.For each experiment we measured several quantities: (i) the size of the final state space on which the property was proved/disproved, 6 (ii) the number of predicate refinement iterations required, (iii) the number of LTS refinement iterations required, (iv) the total number of refinement iterations required, and (v) the total time required.In the tables summarizing our results, these measurements are reported in columns named respectively St, PIt, LIt, It and T. Note that predicate minimization was turned on during all the experiments described in this section.
Unix Kernel Benchmarks.The first set of examples was designed to examine how our approach works on a wide spectrum of implementations.We chose ten code fragments from the Linux Kernel 2.4.0.Corresponding to each code fragment we constructed a specification from the Linux manual pages.For example, the specification in socket-y7 states that the socket system call either properly allocates internal data structures for a new socket and returns 1, or fails to do so and returns an appropriate negative error value.The summary of our results on these examples is presented in Figure 15.  to design a set of 26 benchmarks.However, unlike the previous OpenSSL benchmarks, some of these benchmarks were concurrent and comprised of both a client and a server component executing in parallel.The specifications were derived from the official SSL design documents.For example, the specification for 'ssl-1' states that the handshake is always initiated by the client.

OpenSSL
The first 16 examples are sequential implementations, examining different properties of SrvrCode and ClntCode separately.Each of these examples contains about 350 comment-free LOC.The results for these are summarized in Figure 16.The remaining 10 examples test various properties of SrvrCode and ClntCode when executed together.These examples are concurrent and consist of about 700 LOC.All OpenSSL benchmarks other than srvr-7 passed the property.The results are summarized in Figure 17.In terms of state space size, the two-level refinement scheme outperforms the one-level scheme by factors ranging from 2 to 136.The savings for the concurrent examples are significantly higher than for the sequential ones.We expect these savings to increase with the number of concurrent components in the implementation.
Although our goal of reducing the size of the state space was achieved, our implementation of the two-level algorithm shows an increase in time over that of the one-level scheme.However, we believe that this situation can be redressed through engineering optimizations of magic.For instance, not only is magic currently based on explicit state enumeration, but also in each iteration it performs the entire verification from scratch.As is evident from our results, the majority of iterations involve LTS refinement.Since the latter only induces a local change in the transition system, the refined model is likely to differ marginally from the previous one.Therefore much of the work done during verification in the previous iteration could be reused.We plan to investigate the possibility of doing incremental verification and will report on our findings in the final version of this article.

Conclusions and Future Work
Despite significant research and advancement, automated verification of concurrent programs remains an elusive goal.In this paper we presented an approach to automatically and compositionally verify concurrent C programs against safety specifications.These concurrent implementations consist of several sequential C programs which communicate via blocking message-passing.Our approach is an instantiation of the CEGAR paradigm, and incorporates two levels of abstraction.The first level uses predicate abstraction to handle data while the second level aggregate states according to the values of observable events.In addition, our predicate refinement scheme is aimed at discovering a minimal set of predicates that suffice to prove/disprove the property of interest.Experimental results with our tool magic suggest that this scheme effectively combats the state space explosion problem.In all our benchmarks, the two-level algorithm achieved significant reductions in state space (in one case by over two orders of magnitude) compared to the single-level predicate abstraction scheme.The reductions in the number of predicates required (and thereby in the time and memory consumption) due to our predicate minimization technique were also very encouraging.
We are currently engaged in extending magic to handle the proprietary implementation of a large industrial controller for a metal casting plant.This code consists of over 30,000 lines of C and incorporates up to 25  memory, without sacrificing compositionality, is therefore one of our priorities.Not only will this enable us to test our tool on the many available sharedmemory-based benchmarks, but it will also allow us to compare magic with other similar tools (such as blast) which also use shared memory for communication.
We also wish to investigate the possibility of performing incremental verification in the context of action-guided abstraction refinement.Since the successive MA i 's obtained during this process can be expected to differ only marginally from each other, we expect incremental model checking to speed up the verification process by a significant factor.In addition, we are working on extending MAGIC to handle state/event based LTL-like specifications.Lastly, we intend to explore the possibility of adapting the two-level CEGAR scheme to different types of conformance relations such as simulation and bisimulation, so as to handle a wider range of specifications.
an−→ s n .We refer to the underlying sequence of states s 0 . . .s n as the path in M corresponding to the trace a 1 . . .a n .Definition 3 (Reachability).For a trace π = a 1 . . .a n ∈ Act * and s, t ∈ S two states of M , we write s π =⇒ t to indicate that t is reachable from s through π, i.e., that there exist states s 0 . . .s n with s = s 0 and t = s n , such that s 0 a1 −→ s 1 a2 −→ . . .an −→ s n .Given a state s ∈ S and a trace π ∈ Act * , we let Reach(M, s, π) = {t ∈ S | s π =⇒ t} stand for the set of states reachable from s through π.We overload this notation by setting, for a set of states

Fig. 3 .
Fig. 3.The LTSs in the assumption PAs for do a and do b.The return action with return value void is denoted by return{}.

k i=1 p vi i where p true i = p i and p false i =Example 3 .
¬p i .As a special case, if P s = ∅, then V s = {⊥} and Γ s (⊥) = true.Consider the CFA described in Example 1 and the inferred predicates as explained in Example 2. Recall that P S1 = {(y < 10), (y > 5)}.Suppose Formally, MP is an LTS S I , init I , Act I , T I where:-S I = ∪ s∈SCF S s ∪ {INIT} is the set of states.-init I = INIT is the initial state.-ActI is the alphabet.-TI ⊆ S I × Act I × S I is the transition relation.

Example 4 .
Recall the CFA from Example 1 and the predicates corresponding to CFA nodes discussed in Example 2. The MP's obtained with P = ∅ and P = {(y < 10), (y > 5)} are shown in Figure 6(a) and 6(b) respectively.

Fig. 8 .
Fig.8.Algorithm CEEliminate to check if a spurious counterexample can be eliminated.

Fig. 14 .
Fig.14.Results for optimality.SUB = MAXSUB, It is the number of iterations, T is the total number of eliminating subsets generated, M is the maximum size of subsets tried, and G is the maximum size of eliminating subsets generated.

Fig. 15 .
Fig. 15.Summary of results for Linux Kernel code.LOC and Description denote the number of lines of code and a brief description of the benchmark source code.The measurements for PIter and LIter have been omitted because they are insignificant.

•
Step 3: Refinement.Otherwise, we must examine the counterexample obtained to determine whether it is valid or not.This validity check is again performed in two stages: first at the level of the MP i 's and, if that succeeds, at the level of C i 's.It is also important to note that this validation can be carried out component-wise, without it ever being necessary to construct in full the large state spaces of the lower-level parallel systems (either the MP i 's or the C i 's).A valid counterexample shows Spec to be violated and thus terminates the procedure.Otherwise, a (component-specific) refinement of the appropriate abstracted system is carried out, eliminating the spurious counterexample, and we proceed with a new iteration of the verification cycle.Depending on the counterexample, the refinement process could have two possible outcomes:• Case 3.1: Predicate Refinement.It produces a new set of predicates, leading to a refined MP i .In this case the new iteration of the two-level CEGAR loop starts with step 1.1 above.It is important to note that the predicate minimization algorithm mentioned above is invoked each time a new set of predicates is generated, leading to a smooth integration of the two techniques we present in this article.
• Case 3.2: Action-Guided Refinement.The refinement yields a finer state aggregation, leading to a refined MA i .In this case the new iteration of the two-level CEGAR loop starts with step 1.2 above.
Cn and specification Spec Output: 'C1|| . . .||Cn satisfies Spec' or counterexample π ∈ L(C1|| . . .||Cn) \ L(Spec) predicate abstraction: create LTSs MP 1, . . ., MPn with L(Ci) ⊆ L(MPi) † action-guided abstraction: create LTSs MA1, . . ., MAn with L(MPi) ⊆ L(MAi) for 1 ≤ i ≤ n, let Act i be the alphabet of MPi and MAi repeat if L(MA1|| . . .||MAn) ⊆ L(Spec) return 'C1|| . . .||Cn satisfies Spec' Input: Set of branch statements P Output: Set of Ps's associated with each CFA state Initialize: ∀s ∈ SCF , Ps := ∅ Forever do For each s ∈ SCF do If L(s) is an assignment statement and L(s ) is its successor For each p ∈ P s add WP[L(s)]{p } to Ps Else if L(s) is a branch statement with condition c If L(s) ∈ P, add c to Ps If L(s ) is a 'then' or 'else' successor of L(s), Ps := Ps ∪ P s Else If L(s) is a call-site or a 'goto' statement with successor L(s ) Ps := Ps ∪ P s Else If L(s) returns expression e and r ∈ Act S ∩ IntRet Add the expression (e == RetV al(r)) to Ps If no Ps was modified in the 'for' loop, exit The server and client routines have roughly 350 lines each but, as our results indicate, are non-trivial to verify.Note that all these benchmarks involved purely sequential C code.Results for blast and magic with different refinement strategies.'*' indicates run-time longer than 3 hours.'×' indicates negligible values.Best results are emphasized.
Benchmarks.The next set of examples was aimed at verifying larger pieces of code.Once again we used OpenSSL handshake implementation Fig. 16.Summary of results for sequential OpenSSL examples.
concurrent threads which communicate through shared variables.Adapting magic to handle shared Fig. 17.Summary of results for concurrent OpenSSL examples.