Counterexample-guided abstraction refinement for linear programs ...

3 downloads 5172 Views 2MB Size Report
Linear Program (without arrays), and refinement searches for new array indices. .... (namely BLAST and SATABS) on a significant set of benchmark problems. .... form select(a,e) occurring in these formulæ is select(a2,i2) (with i2 = 1 given by.
Autom Softw Eng DOI 10.1007/s10515-013-0132-0

Counterexample-guided abstraction refinement for linear programs with arrays Alessandro Armando · Massimo Benerecetti · Jacopo Mantovani

Received: 28 June 2012 / Accepted: 19 August 2013 © Springer Science+Business Media New York 2013

Abstract Predicate abstraction refinement is one of the leading approaches to software verification. The key idea is to abstract the input program into a Boolean Program (i.e. a program whose variables range over the Boolean values only and model the truth values of predicates corresponding to properties of the program state), and refinement searches for new predicates in order to build a new, more refined abstraction. Thus Boolean programs are commonly employed as a simple, yet useful abstraction. However, the effectiveness of predicate abstraction refinement on programs that involve a tight interplay between data-flow and control-flow is still to be ascertained. We present a novel counterexample guided abstraction refinement procedure for Linear Programs with arrays, a fragment of the C programming language where variables and array elements range over a numeric domain and expressions involve linear combinations of variables and array elements. In our procedure the input program is abstracted w.r.t. a family of sets of array indices, the abstraction is a Linear Program (without arrays), and refinement searches for new array indices. We use Linear Programs as the target of the abstraction (instead of Boolean programs) as they allow to express complex correlations between data and control. Thus, unlike the approaches based on predicate abstraction, our approach treats arrays precisely.

B

A. Armando ( ) · J. Mantovani University of Genova, Genova, Italy e-mail: [email protected] J. Mantovani e-mail: [email protected] A. Armando Security & Trust, FBK-IRST, Trento, Italy e-mail: [email protected] M. Benerecetti University of Napoli “Federico II”, Napoli, Italy e-mail: [email protected]

Autom Softw Eng

This is an important feature as arrays are ubiquitous in programming. We provide a precise account of the abstraction, Model Checking, and refinement processes, discuss their implementation in the EUREKA tool, and present a detailed analysis of the experimental results confirming the effectiveness of our approach on a number of programs of interest. 1 Introduction The sophistication of software systems and the safety requirements posed by the increasingly complex environments they operate in call for higher and higher degrees of reliability. These requirements must be achieved precisely (i.e. not only at the design level, but also in the actual implementation by taking into account both the data and the control flows) meeting at the same time stringent time-to-market constraints. This calls for automated and effective verification techniques in the software development cycle. In this context Software Model Checking is one of the most promising techniques for automatic program analysis as witnessed by its adoption within advanced software development tools in some key application areas, e.g. the development of device drivers (Ball et al. 2006). As the name suggests, Software Model Checking owes its origins to Model Checking (Clarke 2000), a powerful technique which can perform a complete and fully automatic exploration of all the possible behaviors of the system by using data structures and algorithms that support a symbolic representation of the state space. Model Checking has been remarkably successful in the analysis of hardware and more generally of finite state systems (e.g. communication protocols). The application of Model Checking to software is considerably more difficult as programs are inherently infinite-state, and more sophisticated solutions are therefore needed. CounterExample-Guided Abstraction Refinement (CEGAR for short) (Clarke et al. 2000; Ball and Rajamani 2001) has emerged as the leading approach to Software Model Checking. Given a program P as input, it amounts to the iterative application of the following steps:  is generated from P . By construction every – Abstraction: An abstract program P . However, some traces of P  execution trace of P is also an execution trace of P may not correspond to any trace of P .  is model-checked. If P  is found to be – Model Checking: The abstract program P safe (i.e. correct w.r.t. a given set of properties), so is the original program P and , then it is given as input this is reported to the user. If an error trace is found in P to the next step.  found in the previous step is simulated on P – Simulation: The error trace of P in order to determine its feasibility, i.e. if it corresponds to an error trace of P . The feasibility check is usually carried out with the aid of a theorem prover. If the trace is feasible, then the error trace is reported to the user, otherwise the procedure continues with the next step. – Refinement: The information gathered from the theorem prover during the simulation step (typically a proof of the unfeasibility of the trace found by the model checker) is used to generate a new abstract program which (i) is more precise than the previous abstraction, and (ii) does not contain the spurious error trace found by the Model Checking phase.

Autom Softw Eng

In the most common approaches to CEGAR (e.g. SLAM (Ball and Rajamani 2001), BLAST (Henzinger et al. 2002b), SATABS (Clarke et al. 2005)) a program is abstracted w.r.t. a set of predicates yielding a Boolean Program (i.e. a program whose variables model the truth values of predicates expressing properties over program states), and the refinement searches for new predicates to build a more precise abstraction. The framework described above (known as predicate abstraction refinement) is very effective on important application domains such as operating systems device drivers. As an example, Ball and Rajamani (2001) report on the results obtained by applying it to Windows NT device drivers and dispatch routines, and (Henzinger et al. 2002a) analyse the correctness of both Linux and Windows NT device drivers. Properties of interest include locking/unlocking behaviors, absence of NULL pointer dereferences, correct I/O request packet (IRP) completion specification. It is worth mentioning that device drivers are inherently control-intensive (i.e. control flow largely prevails over data flow), and most of the properties of interest are related to the control: locking behaviors, deadlock freedom, and the like. The effectiveness of predicate abstraction refinement on other kinds of software is still to be ascertained. As a matter of fact, predicate abstraction refinement performs poorly when applied to data intensive programs (e.g. programs performing non trivial manipulations of numeric variables and/or arrays): the abstractions that are built are too coarse, too many iterations of the abstraction refinement loop are usually required, and often the refinement phase fails to determine new Boolean variables needed to build a more accurate model. In this paper we present an abstraction refinement procedure for the interprocedural analysis of Linear Programs with arrays. Linear Programs with arrays naturally correspond to an important fragment of virtually all imperative programming languages where variables and array elements range over a numeric domain and expressions involve linear combinations of variables and array elements. Our procedure is targeted towards the verification of reachability properties that can be specified by adding assertions to the program. Unlike most common approaches based on predicate abstraction refinement, our procedure uses array indices instead of predicates: the input program is abstracted w.r.t. a family of sets of array indices, the abstraction is a Linear Program (without arrays), and refinement searches for new array indices. Thus while predicate abstraction uses Boolean programs as the target of the abstraction, in our approach we use Linear Programs for the same purpose. This is particularly attractive as Linear Programs can directly and concisely represent complex correlations among program variables and a small number of iterations of the abstraction refinement loop usually suffice to either prove or disprove that the input program enjoys the desired properties. Let P be a Linear Program with arrays, and let R be a function mapping arrays to sets of their indices. Our counterexample guided abstraction refinement procedure amounts to the iterated application of the following steps. – Abstraction. Let a be an array and [k1 , . . . , kn ] be a permutation of the elements  is built from the input program P so that— in R(a) ∈ R. A Linear Program P besides numeric variables—only the elements a[k1 ], . . . , a[kn ] of array a in P  for each are faithfully modeled. This is done by introducing a variable a ki in P concrete element a[ki ] in P (1 ≤ i ≤ n).

Autom Softw Eng

 resulting from the ab– Model Checking. The Linear Program (without arrays) P straction step is then model-checked by using an interprocedural data-flow analysis  is found to be safe, then the computation stops inspired by Reps et al. (1995). If P reporting that P is safe. Otherwise, an abstract error trace τ is computed.  found in the previous step is symbolically – Simulation. The error trace τ of P executed in P in order to check its feasibility. This is done by building a set of quantifier-free formulæ Φ(τ ) (called trace formulæ) whose satisfiability (w.r.t. the union of the theory of arrays and Linear Arithmetic) guarantees the executability of τ in P . If Φ(τ ) is found to be satisfiable, then τ is reported to be an error trace of P and the procedure halts, otherwise the proof of unsatisfiability Π of Φ(τ ) found is fed to the next phase. – Refinement. The proof of unsatisfiability Π is inspected and R extended in such a way to rule out τ from the execution traces of the corresponding refined program. The contributions of this paper are manyfold: (i) we give a comprehensive account of our CEGAR procedure, which includes a detailed presentation of the abstraction, Model Checking, simulation, and refinement steps; (ii) we detail the proofs of soundness and completeness of the above steps; (iii) we describe EUREKA, a prototype, yet complete, implementation of our procedure; finally (iv) we present the results obtained by running EUREKA and other state-of-the-art software model checkers (namely BLAST and SATABS) on a significant set of benchmark problems. The experimental results indicate that EUREKA automatically analyses programs on which the other tools (based on predicate abstraction refinement) either fail, provide incorrect answers, or perform poorly. Some of the work presented in this paper has already been published in a preliminary form: the Model Checking phase is described in Armando et al. (2005), while both the Abstraction and the Refinement phases are described in Armando et al. (2007). This paper not only gives a definitive and comprehensive account of this work, but also provides a number of original and previously unpublished contributions: (i) a description of the path-sensitive, interprocedural data-flow analysis of (abstract) Linear Programs; (ii) proofs of soundness of the abstraction, soundness and completeness of the data-flow analysis and of the Model Checking, of the refinement, as well as of the overall CEGAR procedure; (iii) extended experimental results that include new, real-world benchmark problems. Structure of the paper Section 2 explains our CEGAR procedure with the help of a running example. Section 3 describes syntax and semantics of Linear Programs with (and without) arrays. Sections 4, 5, 6, 7, and 8 detail abstraction, data-flow analysis, Model Checking, simulation, and refinement respectively. Section 9 describes the EUREKA tool, and Sect. 10 gives a detailed account of the experimental results. Finally, Sect. 11 we discuss the related work, and in Sect. 12 we draw some concluding remarks. The appendix includes the proofs of the theorems and the lemmas presented.

Autom Softw Eng 0 ) and its refinement (P 1 ) Table 1 A simple program (P ), the initial abstraction (P

2 Model checking linear programs with arrays Our approach to model checking Linear Programs with arrays is based on the idea of abstracting away all variables and array elements from the initial program, and then incrementally refining the abstract program obtained in this way by including program variables and array elements as suggested by the refinement process. Let P be the Linear Program with arrays in the leftmost column of Table 1. We 0 by replacing every occurrence of array exstart by abstracting P into program P pressions with the symbol u (denoting an arbitrary value of numeric type) and by replacing every assignment to array elements with a skip statement (;). (For the sake of simplicity in our example we do not abstract away the program variable i which 0 .) therefore occurs in P 0 we get the execution trace By applying a model checker for Linear Programs to P 1, 2, 3, 4, 5, 3, 4, 5, 3, 6, 0 witnessing the violation of the assertion at line 6. Node 0 is reached if and only if an assertion fails. This trace corresponds to the execution of two iterations of the while loop (lines 3–5) which leaves variable i with value 2 and therefore leads to a violation of the assertion at line 6. The feasibility check of the above trace w.r.t. P is done by generating a set of quantifier-free formulæ whose satisfying valuations correspond to all possible executions of the sequence of statements of P corresponding to the trace under consideration. This is done by first putting the trace in Single Assignment Form Aho et al. (1986), then by properly renaming while statements with assume statements. Particularly, each statement of the form while(b) in the trace is renamed to assume(b) if its successor is the successor of while(b) in the true branch of the original program, and, vice versa, each statement of the form while(b) in the trace is renamed to assume(!b) if its successor is the successor of while(b) in the false branch of the original program. Finally, quantifier-free formulæ are generated that encode the behavior of the statements. Table 2 shows the sequence of the original statements, the trace in Single Assignment Form, and the associated formulæ for the above trace. The resulting set of formulæ is then fed to a theorem prover. If it is found unsatisfiable (w.r.t. a suitably defined background theory) then the trace is not executable in P , whereas if it is found satisfiable then we can conclude that the trace is also executable in P . In our example the set of formulæ (see rightmost column in Table 2) is found to be unsatisfiable. The formulæ that contributed to the proof of unsatisfiability are those associated with steps 1, 2, 4, 5, and 6. Moreover, the only term of the

Autom Softw Eng Table 2 Checking the trace for feasibility Step

Line

Original statement

Renamed statement

Formula

1

[1]

a[1] = 9;

a1 [1] = 9;

a1 = store(a0 , 1, 9)

2

[2]

i = 0;

i1 = 0;

i1 = 0

3

[3]

while(a[i] != 9);

assume(a1 [i1 ] != 9);

select(a1 , i1 ) = 9

4

[4]

a[i] = 2 * i;

a2 [i1 ] = 2 * i1 ;

a2 = store(a1 , i1 , 2 ∗ i1 ) i2 = i1 + 1

5

[5]

i = i + 1;

i2 = i1 + 1;

6

[3]

while(a[i] != 9);

assume(a2 [i2 ] != 9);

select(a2 , i2 ) = 9

7

[4]

a[i] = 2 * i;

a3 [i2 ] = 2 * i2 ;

a3 = store(a2 , i2 , 2 ∗ i2 ) i3 = i2 + 1

8

[5]

i = i + 1;

i3 = i2 + 1;

9

[3]

while(a[i] != 9);

assume(!(a3 [i3 ] != 9));

¬(select(a3 , i3 ) = 9)

10

[6]

assert(i 0) ? y1 + 1 : u , y2 + 1 and InScopeP (i) = {x, y1 , y2 }. Hence, e = ((y1 > 0) ? y1 + 1 : u), y2 + 1 , y = y1 , y2 and z = x . Then,     γ y , e

= y1 = y1

+ 1 ∧ y2 = y2

+ 1 ∧ y1

> 0   ∨ ∃u1 . y1 = u1 ∧ y2 = y2

+ 1 ∧ ¬y1

> 0 .

Autom Softw Eng

This can be simplified to γ (y , e

) = ((y1 = y1

+ 1 ∧ y2 = y2

+ 1 ∧ y1

> 0) ∨ (y2 = y2

+ 1 ∧ ¬y1

> 0)). Therefore, the new set of path edges added to ΔsSuccP (i) is represented by the ADLC     λx, y1 , y2 , x , y1 , y2 .∃x

, y1

, y2

. Δi x, y1 , y2 , x

, y1

, y2

   y1 = y1

+ 1 ∧ y2 = y2

+ 1 ∧ y1

> 0 ∧ x = x

  ∨ y2 = y2

+ 1 ∧ ¬y1

> 0 ∧ x = x

. Example 5 Let si be the statement if(y > 0 && y == 2 * u) and InScopeP (i) = {x, y}, then (after some simplifications) β + (b ) = ∃u1 .(y > 0 ∧ y = 2 ∗ u1 ) and β – (b ) = ∃u1 .(y > 0 ∧ ¬y = 2 ∗ u1 ) ∨ (¬y > 0). The new set of path edges added to ΔTsuccP (i) is represented by the following ADLC λx.x .y.y .∃u1 .(Δi ( x, y , x , y )  (y > 0 ∧ y = 2 ∗ u1 ∧ x = x)), while the set of path edges added to ΔFsuccP (i) is represented by the following ADLC     λx.x .y.y .∃u1 . Δi x, y1 , y2 , x , y1 , y2      y > 0 ∧ ¬y = 2 ∗ u1 ∧ x = x ∨ ¬y > 0 ∧ x = x . Let Δ ∈ A(P ), we define [[Δ]] as the NP –indexed family of sets [[Δ]]i such that [[Δ]]i = [[Δi ]], for all i ∈ NP . The following result provides the foundation of our symbolic model checking procedure for linear programs. Theorem 5 (Soundness and Completeness of the Symbolic Data-flow Analysis) Let P be a linear program and Δ, Δ1 ∈ A(P ). The following fact holds: Δ_Δ1 if and only if [[Δ]]⇒[[Δ1 ]]. Let _∗ denote the reflexive and transitive closure of _. We can now state the symbolic counterpart of Corollary 1. Corollary 2 Let P be a linear program, x = InScopeP (1) and Δ0 ∈ A(P ) be defined so that Δ01 = λxx .x = x and Δ0j = λww .⊥ (where w = InScopeP (j )) for all j ∈ NP \ {1}. The following fact holds: for all i ∈ NP , i is reachable if and only if Δi  λww .⊥, for some Δ such that Δ0 _∗ Δ. This result states that a semi-decision procedure for reachability can be obtained by systematically exploring the computation tree associated with the _ relation. Obviously it does not imply the computability of the _∗ relation nor the termination of the procedure. Termination of the procedure can be traded for precision of the analysis. For instance, widening techniques (Cousot and Halbwachs 1978) can be used to compute over-approximations of the sets of reachable edges, but this may obviously lead the procedure to report false positives. 7 Checking trace feasibility We now turn our attention to the problem of determining whether a trace τ = i0 · · · in  is also a trace of the corresponding concrete program P . of the abstract program P

Autom Softw Eng Table 3 Generation of trace formulæ sik

φ(sik )

Condition

if(b), assert(b);, while(b);, assume(b);

{b}

if ik+1 = TsuccP (ik )

if(b), assert(b);, while(b);

{¬b} {xj +1 = e }

if ik+1 = FsuccP (ik )

xj +1 = e; aj +1 [e1 ] = e2 ;

{aj +1 = store(aj , e1 , e2 )}

;



We show how this problem can be reduced to the problem of determining the satisfiability of a set of quantifier-free formulæ (henceforth called trace formulæ) in the decidable theory resulting from the combination of Linear Arithmetic and the theory of arrays. By Linear Arithmetic we mean standard arithmetic (over D) with addition (i.e. +) and the usual relational operators (e.g. =, , ≥) but without multiplication. (We recall that only multiplication by a constant is allowed as a notational shorthand for a repeated addition.) The theory of arrays we consider models arrays as data structures representing arbitrary associations of elements to a set of indices. Let INDEX, ELEM and ARRAY be sorts for indices, elements, and arrays (resp.), and select : ARRAY × INDEX → ELEM and store : ARRAY × INDEX × ELEM → ARRAY be function symbols. We also assume that the language of the theory of arrays includes a conditional term constructor that allows for terms of the form (w ? t1 : t2 ), for every formula w and terms t1 and t2 . Then the following is a concise presentation of the theory of arrays:     ∀a, i, j, e. select store(a, i, e), j = j = i ? e : select(a, j ) (1) In the sequel we will denote Linear Arithmetic with T0 and the union of Linear Arithmetic with the theory of arrays with T1 . Let si1 · · · sin the sequence of statements associated with τ = i0 · · · in . The sequence of statements is put in Single Assignment Form (Aho et al. 1986), i.e. the program variables are renamed in such a way that each variable is assigned exactly once in the resulting program. This is done in the following way. Let v be a program variable and i a program location. We define ν(v, i) to be the number of assignments made to v prior to location i. Let e be a program expression. With (e) we denote the expression obtained from e by substituting every variable v in e with vν(v,i) . Every assignment to a variable x at a given location i, say x = e;, is replaced by vν(x,i)+1 = (e);. Every assignment to an array element, say a[e1 ] = e2 ;, is replaced by aν(a,i)+1 [(e1 )] = (e2 );. Every condition b (also called guard) is replaced by (b). The set of trace formulæ for τ w.r.t. P is the set of quantifier-free formulæ Φ(τ, P ) = nk=1 φ(sik ), where φ(sik ) is defined in Table 3. We define ΦTi (τ, P ) = Ti ∪ Φ(τ, P ) for i = 0, 1. The following theorem holds: Theorem 6 Let P0 be a linear program and let P1 be a linear program with arrays, then τ ∈ traces(Pi ) if and only if ΦTi (τ, Pi ) is satisfiable, for i = 0, 1.

Autom Softw Eng

 found by In the AR procedure of Fig. 1 the task of checking whether the trace for P the model-checker is also a trace for P is jointly carried out by the functions encode and decide. If the variable Trace is set to τ , then the function call encode(Trace, P ) at line 5 computes and returns the set of trace formulæ Φ(τ, P ). If the variable Formula is set to the set of trace formulæ Φ(τ, P ), then the function call decide(Formula) at line 6 checks the satisfiability of ΦT1 (τ, P ). If ΦT1 (τ, P ) is unsatisfiable, then decide(Formula) returns a proof of this fact in a sequent calculus for first order logic with equality, i.e. it returns a proof of the sequent ΦT1 (τ, P )  ⊥, namely a tree whose root is labeled by the sequent ΦT1 (τ, P )  ⊥ and whose leaves are labeled by sequents of the form ΦT1 (τ, P )  ϕ with ϕ ∈ ΦT1 (τ, P ). 8 Refinement  ∈ abstract(P , V , R) with V ⊆ VP and R ⊆ RP , let τ be a trace of P  such Let P that ΦT1 (τ, P ) is unsatisfiable and let Π be a proof of ΦT1 (τ, P )  ⊥. The procedure refine(τ, Π, V , R) computes V and R such that V , R ≺ V , R and  ) for all P  ∈ abstract(P , V , R ). From this it is easy to conclude that τ∈ / traces(P

  P  P . This fact, which is formally stated and proved below, is key to establish the completeness of the AR procedure of Fig. 1. The definition of the procedure refine(τ, Π, V , R) is given in Fig. 3. The procedure exploits the fact that every term of the form select(a, e) occurring in Π has a  only if e = k for k ∈ R (a). corresponding representation in the abstract program P

The set V is obtained by extending V with all the program variables occurring in Π . The computation of R is based on the idea of turning Π into a proof of the  ). This is done in step 1 by adding to the premises of unsatisfiability of ΦT0 (τ, P each leaf sequent ΦT1 (τ, P )  ϕ of Π a formula Q(e, a) for each term of the form select(ak , e) occurring inϕ. Informally a formula of the form Q(e, a) is a place holder for the formula k∈R (a) e = k. However, since R (a) is unknown at this stage, we use Q(e, a) in place of its expanded version k∈R (a) e = k. The sequent tree obtained in this way is then updated by re-applying all the inference rules of Π on the new leaf sequents. This leaves us with a sequent tree Π whose root sequent is of the form ΦT1 (τ, P ), Q(e1 , ak1 ), . . . , Q(eq , akq )  ⊥, where aki ∈ AP for i = 1, . . . , q. We are then left with the problem of defining R in such a way that (2) holds. This is the task of step 3 of the procedure. Notice that (2) always ad grows (linearly) with mits R = RP as (trivial) solution. However, as the size of P

the cardinality of R , we are interested in finding a solution R with the smallest possible cardinality. Since this problem is intractable in the general case, an alternative approach which works well in practice is to choose R in such a way that R (aj ) = R(aj ) ∪ {ei : ki = j and i = 1, . . . , q} if e1 , . . . , eq are all numerals and R = RP otherwise. The following result states that if V and R are computed as described above, then  ). the sequent tree Π can be turned into a proof of the unsatisfiability of ΦT0 (τ, P

 From this it readily follows the unsatisfiability of ΦT0 (τ, P ). Lemma 3 The sequent tree Π computed at step 2 of the refine(τ, Π, V , R) proce ) dure of Fig. 3 can be transformed into a proof of the unsatisfiability of ΦT0 (τ, P

Autom Softw Eng

procedure refine(τ, Π, V , R) 1. V ← V ∪ {x ∈ VP : xj occurs in Π for some j ≥ 0}; 2. Π ← the sequent tree obtained from Π by (a) replacing every leaf sequent ΦT1 (τ, P )  ϕ with ΦT1 (τ, P ), {Q(e, a) : select(a, e) occurs in ϕ}  ϕ, where Q is a newly introduced binary predicate symbol added to the signature of T1 and (b) updating the sequents associated with the non-leaves nodes of the proof by re-applying all the inference rules. 3. Let ΦT1 (τ, P ), Q(e1 , a), . . . , Q(eq , a)  ⊥ be the root node of Π . Choose an R such that R ⊆ R and    |= ΦT0 τ, P ej = k (2) k∈R (a)

 = abstract(P , V , R ) and j = 1, . . . , q. for all P 4. return V , R Fig. 3 The refinement procedure

Fig. 4 The sequent tree corresponding to a proof of the unsatisfiability of ΦT1 (τ, P ), where P is the program of Table 1. Note that we omitted the formulae ΦT1 (τ, P ) on the left hand sides of the sequents

 ∈ abstract(P , V , R ). Hence ΦT0 (τ, P  ) is unsatisfiable for all P  ∈ for all P

abstract(P , V , R ). Example 6 If τ is the trace of Table 2 relative to the program P of Table 1, then ΦT1 (τ, P ) comprises the set of formulae in the rightmost column of Table 2. The sequent tree corresponding to a proof of the unsatisfiability of ΦT1 (τ, P ) after applying step 2 of the refine procedure and omitting the formulae ΦT1 (τ, P ) on the left hand sides of the sequents is as in Fig. 4. In this case—as we anticipated in Sect. 2—it thus suffices to refine the program using an R such that R (a) = {1}.  ∈ The following result states the key properties of the refinement process: if P



abstract(P , V , R ), where V and R are the sets of variables returned by the pro and V , R ≺ V , R   is a refinement of P cedure refine(τ, Π, V , R), then P VP , RP .

Autom Softw Eng

 ∈ abstract(P , V , R), τ ∈ traces(P ) such that ΦT1 (τ, P ) is unTheorem 7 Let P  ∈ abstract(P , V , R ), where V satisfiable, Π be a proof of ΦT1 (τ, P )  ⊥ and P and R are the sets of variables returned by the procedure refine(τ, Π, V , R). Then  and V , R ≺ V , R  VP , RP .   P P We are now in the position to prove the soundness and (relative) completeness of the AR procedure. Corollary 3 (Soundness of the AR Procedure) Let V ⊆ VP and R ⊆ RP . If AR(P , V , R) returns SAFE, then P has no error trace. Corollary 4 (Relative Completeness of the AR Procedure) Let V ⊆ VP and R ⊆ RP . If P has no error trace and all the calls to the model-check procedure terminate, then AR(P , V , R) terminates and returns SAFE.

9 The EUREKA tool The ideas presented in the previous sections have been implemented in the EUREKA tool. EUREKA is a software Model Checker for Linear Programs with arrays targeted towards the verification of reachability properties, which can be specified by adding assertions to the program. By inserting appropriate assertions within the program, EUREKA is able to check properties over bounded arrays. Global properties of arrays, like, e.g., sortedness, can be encoded in at least two ways: either by inserting the assertion checking the property for each element (or pair of elements) within a loop; or by using an additional numerical variable, say idx, initialized with the non deterministic value u, to index of the array in a single assert. For instance, sortedness of an array A of size 10 can be expressed by an assertion of the form assert(idx=10-1 || A[idx]=, =,