Checking Observational Purity of Procedures

Verifying whether a procedure is observationally pure is useful in many software engineering scenarios. An observationally pure procedure always returns the same value for the same argument, and thus mimics a mathematical function. The problem is challenging when procedures use private mutable global variables, e.g., for memoization of frequently returned answers, and when they involve recursion. We present a novel verification approach for this problem. Our approach involves encoding the procedure's code as a formula that is a disjunction of path constraints, with the recursive calls being replaced in the formula with references to a mathematical function symbol. Then, a theorem prover is invoked to check whether the formula that has been constructed agrees with the function symbol referred to above in terms of input-output behavior for all arguments. We evaluate our approach on a set of realistic examples, using the Boogie intermediate language and theorem prover. Our evaluation shows that the invariants are easy to construct manually, and that our approach is effective at verifying observationally pure procedures.


Introduction
A procedure in an imperative programming language is said to be observationally pure (OP) if for each specific argument value it has a specific return value, across all possible sequences of calls to the procedure, irrespective of what other code runs between these calls. In other words, the input-output behavior of an OP procedure mimics a mathematical function.
Any procedure whose code is deterministic and does not read any pre-existing state other than its arguments is trivially OP. However, it is common for procedures, especially ones in libraries, to update and read global variables, typically to optimize their own behavior, while still mimicking mathematical functions in terms of their input-output behavior. In this paper, we focus on the problem of checking observational purity of procedures that read and write global variables, especially in the presence of recursion, which makes the problem harder.

Motivating Example
We use the example procedure 'factCache' in Listing 1.1 as our running example. It returns n! for the given argument n, and caches the return value for the last argument provided to it. It uses two "private" global variables to implement the cachingg, and lastN. g is initialized to -1, and after the first call to the procedure onwards is used to store the return value from the most recent call. lastN is used to store the argument received in the most recent call. Clearly this procedure is OP, and mimics the input-output behavior of a regular factorial procedure that does not cache any results.

Proposed Approach
Our verification approach is based on Floyd-Hoare logic. In order to verify a recursive procedure inductively, typically a specification of the procedure would need to be provided. The first idea for such a specification would be a full functional specification of the procedure. That is, if someone specifies that factCache mimics n!, then the verifier could replace Line 10 in the code with 'g = n * (n-1)!'. This, on paper, is sufficient to assert that Line 12 always assigns n! to 'result'. However, to establish that Line 8 also does the same, an invariant would need to be provided that describes the possible values of g before any invocation to the procedure. In our example, a suitable invariant would be '(g = -1) ∨ (g = lastN!)'. The verifier would also need to verify that at the procedure's exit the invariant is re-established. Lines 10-12, with the recursive call replaced by (n-1)!, suffices on paper to re-establish the invariant.
The candidate approach mentioned above, while being plausible, suffers from two weaknesses. The first is that a human would need to guess the mathematical expression that is implemented by the given procedure. This may not be easy when the procedure is complex. Second, the underlying theorem prover would need to prove complex arithmetic properties, e.g., that n * (n-1)! is equal to n!. Complex proofs such as this may be out of bounds for many existing theorem provers.
Our key insight is to sidestep the challenges mentioned by introducing a function symbol, say factCache, and replacing the recursive call for the purposes of verification with this function symbol. Intuitively, factCache represents the mathematical function that the given procedure mimics if the procedure is OP. In our example, Line 10 would become 'g = n * factCache(n-1)'. This step needs no human involvement. The approach needs an invariant; however, in a novel manner, we allow the invariant also to refer to factCache. In our example, a suitable invariant would be '(g = -1) ∨ (g = lastN * factCache(lastN-1))'. This sort of invariant is relatively easy to construct; e.g., a human could arrive at it just by looking at Line 2 and with a local reasoning on Lines 10 and 11. Given this invariant, (a) a theorem prover could infer that the condition in Line 7 implies that Line 8 necessarily copies the value of 'n * factCache(n-1)' into 'result'. Due to the transformation to Line 10 mentioned above, (b) the theorem prover can infer that Line 12 also does the same. Note that since these two expressions are syntactically identical, a theorem prover can easily establish that they are equal in value. Finally, since Line 6 is reached under a different condition than Lines 8 and 12, the verifier has finished establishing that the procedure always returns the same expression in n for any given value of n.
Similarly, using the modified Line 10 mentioned above and from Line 11, the prover can re-establish that g is equal to 'lastN * factCache(lastN -1)' when control reaches Line 12. Hence, the necessary step of proving the given invariant to be a valid invariant is also complete.
Note, the effectiveness of the approach depends on the nature of the given invariant. For instance, if the given invariant was '(g = -1) ∨ (g = lastN!)', which is also technically correct, then the theorem prover may not be able to establish that in Lines 8 and 12 the variable 'g' always stores the same expression in n. However, it is our claim that in fact it is the invariant '(g = -1) ∨ (g = lastN * factCache(lastN-1))' that is easier to infer by a human or by a potential tool, as justified by us two paragraphs above.

Salient Aspects Of Our Approach
This paper makes two significant contributions. First, it tackles the circularity problem that arises due to the use of a presumed-to-be OP procedure in assertions and invariants and the use of these invariants in proving the procedure to be OP. This requires us to prove the soundness of an approach that verifies observational purity as well the validity of invariants simultaneously (as they cannot be decoupled).
Secondly, as we show, a direct approach to this verification problem (which we call the existential approach) reduces it to a problem of verifying that a logical formula is a tautology. The structure of the generated formula, however, makes the resulting theorem prover instances hard. We show how a conservative approximation can be used to convert this hard problem into an easier problem of checking satisfiability of a quantifier-free formula, which is something within the scope of state-of-the-art theorem provers.
The most closely related previous approaches are by Barnett et al. [1,2], and by Naumann [3]. These approaches check observational purity of procedures that maintain mutable global state. However, none of these approaches use a function symbol in place of recursive calls or within invariants. Therefore, it is not clear L ∈ Lib ::= g := c P P ∈ Proc ::= p (x) { S; return y } S ∈ Stmt ::= x := e | x := p(y) | S ; S | if (e) then S else S e ∈ Expr ::= c | x | e op e | unop e op ∈ Ops ::= + | -| / | * | % | > | < | == | ∧ | ∨ unop ∈ UnOps ::= ¬ x, y ∈ LocalId ∪ GlobalId, g ∈ GlobalId, c ∈ V, p ∈ ProcId that these approaches can verify recursive procedures. Barnett et al., in fact, state "there is a circularity -it would take a delicate argument, and additional conditions, to avoid unsoundness in this case". To the best of our knowledge ours is the first paper to show that it is feasible to check observational purity of procedures that maintain mutable global state for optimization purposes and that make use of recursion. Being able to verify that a procedure is OP has many potential applications. The most obvious one is that OP procedures can be memoized. That is, inputoutput pairs can be recorded in a table, and calls to the procedure can be elided whenever an argument is seen more than once. This would not change the semantics of the overall program that calls the procedure, because the procedure always returns the same value for the same argument (and mutates only private global variables). Another application is that if a loop contains a call to an OP procedure, then the loop can be parallelized (provided the procedure is modified to access and update its private global variables in a single atomic operation).
The rest of this paper is structured as follows. Section 2 introduces the core programming language that we address. Section 3 provides formal semantics for our language, as well as definitions of invariants and observational purity. Section 4 describes our approach formally. Section 5 discusses an approach for generating an invariant automatically in certain cases. Section 6 describes evaluation of our approach on a few realistic examples. Section 7 describes related work.

Language Syntax
In this paper, we assume that the input to the purity checker is a library consisting of one or more procedures, with shared state consisting of one or more variables that are private to the library. We refer to these variables as "global" variables to indicate that they retain their values across multiple invocations of the library procedures, but they cannot be accessed or modified by procedures outside the library (that is, the clients of the library).
In Fig. 1, we present the syntax of a simple programming language that we address in this paper. Given the foundational focus of this work, we keep the programming language very simple, but the ideas we present can be generalized. A return statement is required in each procedure, and is permitted only as the last statement of the procedure. The language does not contain any looping construct. Loops can be modelled as recursive procedures. The formal parameters of a procedure are readonly and cannot be modified within the procedure. We omit types from the language. We permit only variables of primitive types. In particular, the language does not allow pointers or dynamic memory allocation. Note that expressions are pure in this language, and a procedure call is not allowed in an expression. Each procedure call is modelled as a separate statement.
For simplicity of presentation, without loss of conceptual generality, we assume that the library consists of a single (possibly recursive) procedure, with a single formal parameter. In the sequel, we will use the symbol p (as a metavariable) to represent this library procedure, p (as a metavariable) to represent the name of this procedure, and will assume that the name of the formal parameter is n. If the procedure is of the form "p (n) { S; return r }", we refer to r as the return variable, and refer to "S; return r" as the procedure body and denote it as body(p). The library also contains, outside of the procedure's code, a sequence of initializing declarations of the global variables used in the procedure, of the form "g1 := c1; . . .; gN := cN". These initializations are assumed to be performed once during any execution of the client application, just before the first call to the procedure p is placed by the client application.
Finally, a note about terminology: throughout this paper we use the word 'procedure' to refer to the library procedure p, and use the word 'function' to refer to a mathematical function.

A Semantic Definition of Purity
In this section, we formalize the input-output semantics of the procedure p as a relation p , where n p r indicates that an invocation of p with input n may return a result of r. The procedure is then defined to be observationally pure if the relation p is a (partial) function: that is, if n p r 1 and n p r 2 , then The object of our analysis is the set of procedures in the library, or, actually, a single-procedure library (for simplicity of presentation), but not the entire (client) application. The result of our analysis is valid for any client program that uses the procedure/library. The only assumptions we make are: (a) The shared state used by the library (the global variables) are private to the library and cannot be modified by the rest of the program, and (b) The client invokes the library procedures sequentially: no concurrent or overlapping invocations of the library procedures by a concurrent client are permitted.
The following semantic formalism is motivated by the above observations. It can be seen as the semantics of the so-called "most general sequential client" of procedure p, which is the program: while (*) x = p (random()); . The executions (of p) produced by this program include all possible executions (of p) produced by all sequential clients.
Let G denote the set of global variables. Let L denote the set of local variables. Let V denote the set of numeric values (that the variables can take). An element ρ g ∈ Σ G = G ֒→ V maps global variables to their values. An element ρ ℓ ∈ Σ L = L ֒→ V maps local variables to their values. We define a local continuation to be a statement sequence ending with a return statement. We use a local continuation to represent the part of the procedure body that still remains to be executed. Let Σ C represent the set of local continuations. The set of runtime states (or simply, states) is defined to be (Σ C × Σ L ) * × Σ G , where the first component represents a runtime stack, and the second component the values of global variables. We denote individual states using symbols σ, σ 1 , σ i , etc. The runtime stack is a sequence, each element of which is a pair (S, ρ ℓ ) consisting of the remaining procedure fragment S to be executed and the values of local variables ρ ℓ . We write (S, ρ ℓ )γ to indicate a stack where the topmost entry is (S, ρ ℓ ) and γ represents the remaining part of the stack.
We say that a state ((S, ρ ℓ )γ, ρ g ) is an entry-state if its location is at the procedure entry point (i.e., if S is the entire body of the procedure), and we say that it is an exit-state if its location is at the procedure exit point (i.e., if S consists of just a return statement).
A procedure p determines a single-step execution relation → p , where σ 1 → p σ 2 indicates that execution proceeds from state σ 1 to state σ 2 in a single step. Fig. 2 defines this semantics. The semantics of evaluation of a side-effect-free expression is captured by a relation (ρ, e) ⇓ v, indicating that the expression e evaluates to value v in an environment ρ (by environment, we mean an element of Σ L × Σ G ). We omit the definition of this relation, which is straightforward. We use the notation ρ 1 ⊎ ρ 2 to denote the union of two disjoint maps ρ 1 and ρ 2 .
Note that most rules captures the usual semantics of the language constructs. The last two rules, however, capture the semantics of the most-general sequential client explained previously: when the call stack is empty, a new invocation of the procedure may be initiated (with an arbitrary parameter value).
Note that all the following definitions are parametric over a given procedure p. E.g., we will use the word "execution" as shorthand for "execution of p".
We define an execution (of p) to be a sequence of states σ 0 σ 1 · · · σ n such that σ i → p σ i+1 for all 0 ≤ i < n. Let σ init denote the initial state of the library; i.e., this is the element of Σ G that is induced by the sequence of initializing declarations of the library, namely, "g1 := c1; . . .; gN := cN" We say that an execution σ 0 σ 1 · · · σ n is a feasible execution if σ 0 = σ init . Note, intuitively, a feasible execution corresponds to the sequence of states visited within the library across all invocations of the library procedure over the course of a single execution of the most-general client mentioned above; also, since the most-general client supplies a random parameter value to each invocation of p, in general multiple feasible executions of the library may exist. We say that a state σ is reachable if there exists an execution π = σ init σ 1 · · · σ.
We define a trace (of p) to be a substring π = σ 0 · · · σ n of a feasible execution such that: (a) σ 0 is entry-state (b) σ n is an exit-state, and (c) σ n corresponds to the return from the invocation represented by σ 0 . In other words, a trace is a state sequence corresponding to a single invocation of the procedure. A trace may contain within it nested sub-traces due to recursive calls, which are themselves

Fig. 2.
A small-step operational semantics for our language, represented as a relation σ1 →p σ2. Note that a state σi is a configuration of the form ((S, ρ ℓ )γ, ρg) where S captures the statements to be executed in the current procedure, ρ ℓ assigns values to local variables in the current procedure, γ is the call-stack (excluding the current procedure), and ρg assigns values to global variables.
traces. Given a trace π = σ 0 · · · σ n , we define initial(π) to be σ 0 , final(π) to be σ n , input(π) to be value of the input parameter in initial(π), and output(π) to be the value of the return variable in final(π).
We define the relation p to be {(input(π), output(π)) | π is a trace of p}.
Definition 1 (Observational Purity). A procedure p is said to be observationally pure if the relation p is a (partial) function: that is, if for all n, r 1 , r 2 , if n p r 1 and n p r 2 , then r 1 = r 2 .
Logical Formula and Invariants. Our methodology makes use of logical formulae for different purposes, including to express a given invariant. Our logical formulae use the local and global variables in the library procedure as free variables, use the same operators as allowed in our language, and make use of universal as well as existential quantification. Given a formula ϕ, we write ρ |= ϕ to denote that ϕ evaluates to true when its free variables are assigned values from the environment ρ.
As discussed in Section 1.2, one of our central ideas is to allow the names of the library procedures to be referred to in the invariant; e.g., our running example becomes amenable to our analysis using an invariant such as '(g = -1) ∨ (g = lastN * factCache(lastN-1))'. We therefore allow the use of library procedure names (in our simplified presentation, the name p) as free variables in logical formulae. Correspondingly, we let each environment ρ map each procedure name to a mathematical function in addition to mapping variables to numeric values, and extend the semantics of ρ |= ϕ by substituting the values of both variables and procedure names in ϕ from the environment ρ.
Given an environment ρ, a procedure name p, and a mathematical function f , we will write ρ[p → f ] to indicate the updated environment that maps p to the value f and maps every other variable x to its original value ρ[x]. We will write (ρ, f ) |= ϕ to denote that ρ[p → f ] |= ϕ.
Definition 2 (Observational Purity wrt an Invariant). Given an invariant ϕ inv , a library procedure p is said to satisfy pure(ϕ inv ) if there exists a function f such that for every trace π of p, output(π) = f (input(π)) and (π, f ) |= ϕ inv .
It is easy to see that if procedure p satisfies pure(ϕ inv ) wrt any given candidate invariant ϕ inv , then p is observationally pure as per Definition 1.
In this section we provide two different approaches that, given a procedure p and a candidate invariant ϕ inv , use a theorem prover to check conservatively whether procedure p satisfies pure(ϕ inv ).

Verification Condition Generation
We first describe an adaptation of standard verification-condition generation techniques that we use as a common first step in both our approaches. Given a procedure p, a candidate invariant ϕ inv , our goal is to compute a pair (ϕ post , ϕ vc ) where ϕ post is a postcondition describing the state that exists after an execution of body(p) starting from a state that satisfies ϕ inv , and ϕ vc is a verificationcondition that must hold true for the execution to satisfy its invariants and assertions.
We first transform the procedure body as below to create an internal representation that is input to the postcondition and verification condition generator. In the internal representation, we allow the following extra forms of statements (with their usual meaning): havoc(x), assume e, and assert e.
1. For any assignment statement "x := e" where e contains x, we introduce a new temporary variable t and replace the assignment statement with "t := e; x := t". 2. For every procedure invocation "x := p(y)", we first ensure that y is a local variable (by introducing a temporary if needed). We then replace the statement by the code fragment "assert ϕ inv ; havoc(g1); ... havoc(gN); assume ϕ inv ∧ x = p(y)", where g1 to gN are the global variables. Note that the function call has been eliminated, and replaced with an "assume" expression that refers to the function symbol p. In other words, there are no function calls in the transformed procedure. 3. We replace the "return x" statement by "assert ϕ inv ".
Let TB(p, ϕ inv ) denote the transformed body of procedure p obtained as above.
We then compute postconditions as formally described in Fig. 3. This lets us compute for each program point ℓ in the procedure, a condition ϕ ℓ that describes what we expect to hold true when execution reaches ℓ if we start executing the procedure in a state satisfying ϕ inv and if every recursive invocation of the procedure also terminates in a state satisfying ϕ inv . We compute this using the standard rules for the postcondition of a statement. For an assignment statement "x := e", we use existential quantification over x to represent the value of x prior to the execution of the statement. If we rename these existentially quantified variables with unique new names, we can lift all the existential quantifiers to the outermost level. When transformed thus, the condition ϕ ℓ takes the form ∃x 1 · · · x n .ϕ, where ϕ is quantifier-free and x 1 , · · · , x n denote intermediate values of variables along the execution path from procedure-entry to program point ℓ.

Fig. 3. Generation of verification-condition and postcondition.
We compute a verification condition ϕ vc that represents the conditions we must check to ensure that an execution through the procedure satisfies its obligations: namely, that the invariant holds true at every call-site and at procedureexit. Let ℓ denote a call-site or the procedure-exit. We need to check that ϕ ℓ ⇒ ϕ inv holds. Thus, the generation verification condition essentially consists of the conjunction of this check over all call-sites and procedure-exit.
Finally, the function postvc computes the postcondition and verification condition for the entire procedure as shown in Fig. 3. Note that this adds the check that the initial state too must satisfy ϕ inv as the basis condition for induction. init(p) is basically the formula "g1 = c1 ∧ . . . gN = cN" (see Section 2).
Example We now illustrate the postcondition and verification condition generated from our factorial example presented in Listing 1.1. Listing 1.2 shows the example expressed in our language and transformed as described earlier (using function TB), using a supplied candidate invariant ϕ inv . Fig. 4 illustrates the computation of postcondition and verification condition from this transformed example. In this figure, we use ϕ pre cs to denote the precondition computed to hold just before the recursive callsite, and ϕ post cs to denote the postcondition computed to hold just after the recursive callsite. The postcondition ϕ post (at the end of the procedure body) is itself a disjunction of three path-conditions representing execution through the three different paths in the program. In this illustration, we have simplified the logical conditions by omitting useless existential quantifications (that is, any quantification of the form ∃x.ψ where x does not occur in ψ). Note that the existentially quantified g and lastN in ϕ post cs denote the values of these globals before the recursive call. Similarly, the existentially quantified g and lastN in ϕ path

Approach 1: Existential Approach
Let p be a procedure with input parameter n and return variable r. Let postvc(p, ϕ inv ) = (ϕ post , ϕ vc ). Let ψ e denote the formula ϕ vc ∧ (ϕ post ⇒ (r = p(n))). Let x denote the sequence of all free variables in ψ e except for p. We define ea(p, ϕ inv ) to be the formula ∀x.ψ e .
In this approach, we use a theorem prover to check whether ea(p, ϕ inv ) is satisfiable. As shown by the following theorem, satisfiability of ea(p, ϕ inv ) establishes that p satisfies pure(ϕ inv ).
The proof is by contradiction. Let π be the shortest trace that does not satisfy at least one of the two conditions (a) and (b).
Let us first consider a trace π without any sub-traces (i.e., without a procedure call). Consider any transition σ i → p σ i+1 in π caused by an assignment statement S. Our verification-condition generation produces a precondition ϕ pre . This lets us prove (via induction over i), that if (initial(π), f ) satisfies ϕ inv , then (σ i+1 , f ) satisfies ϕ post S . We now consider a trace π that contains a sub-trace π ′ = σ i · · · σ j corresponding to a procedure call statement S. Our verification-condition generation produces a precondition ϕ pre S , a postcondition ϕ post S and a conjunct ϕ pre S ⇒ ϕ inv in ϕ vc for S. We extend the induction over i to handle recursive calls as below. Our inductive hypothesis guarantees that (σ i , f ) satisfies ϕ pre S . Further, we know that [p → f ] is a satisfying assignment for ea(p, ϕ inv ), which includes the conjunct ϕ pre S ⇒ ϕ inv . Hence, it follows that (σ i , f ) satisfies ϕ inv . Since π ′ is a shorter trace than π, we can assume that it satisfies conditions (a) and (b). This is sufficient to guarantee that (σ j , f ) satisfies ϕ post S . Thus, we can establish (final(π), f ) satisfies ϕ post and ϕ inv . Further, since ea(p, ϕ inv ) includes the conjunct ϕ post ⇒ (r = p(n)), it follows that output(π) = f (input(π)).

Approach 2: Impurity Witness Approach
The existential approach presented in the previous section has a drawback. Checking satisfiability of ea(p, ϕ inv ) is hard because it contains universal quantifiers and existing theorem provers do not work well enough for this approach. We now present an approximation of the existential approach that is easier to use with existing theorem provers. This new approach, which we will refer to as the impurity witness approach, reduces the problem to that of checking whether a quantifier-free formula is unsatisfiable, which is better suited to the capabilities of state-of-the-art theorem provers. This approach focuses on finding a counterexample to show that the procedure is impure or it violates the candidate invariant.
Let p be a procedure with input parameter n and return variable r. Let postvc(p, ϕ inv ) = (ϕ post , ϕ vc ). Let ϕ post α denote the formula obtained by replacing every free variable x other than p in ϕ post by a new free variable x α . Define ϕ post β similarly. Define iw(p, ϕ inv ) to be the formula (¬ϕ vc )∨(ϕ post α ∧ϕ post β ∧(n α = n β ) ∧ (r α = r β )).
The impurity witness approach checks whether iw(p, ϕ inv ) is satisfiable. This can be done by separately checking whether ¬ϕ vc is satisfiable and whether (ϕ post α ∧ϕ post β ∧(n α = n β )∧(r α = r β )) is satisfiable. As formally defined, ϕ vc and ϕ post contain embedded existential quantifications. As explained earlier, these existential quantifiers can be moved to the outside after variable renaming and can be omitted for a satisfiability check. (A formula of the form ∃x.ψ is satisfiable iff ψ is satisfiable.) As usual, these existential quantifiers refer to intermediate values of variables along an execution path. Finding a satisfying assignment to these variables essentially identifies a possible execution path (that satisfies some other property).
Proof. We prove the contrapositive.
We say that a pair of feasible executions (π 1 , π 2 ) is an impurity witness if there is a trace π a in π 1 and a trace π b in π 2 such that π a and π b have the same input value but different return values. Otherwise, we say that (π 1 , π 2 ) is pure. We extend this notion and say that a single execution π is pure if (π, π) is pure.
We say that a function f is compatible with a set of executions Π if for every trace π ∈ Π, output(π) = f (input(π)).
We say that a pure feasible execution π is a ϕ inv -violation witness if there is some function f that is compatible with {π} such that (π, f ) ϕ inv . Otherwise, we say that π satisfies the invariant. Note that this definition introduces a conservative approximation as we explain later.
If p does not satisfy pure(ϕ inv ), then there exists a minimal impurity witness or a minimal ϕ inv -violation witness. Note that our definition of ϕ inv -violation witness is a conservative approximation and the converse of the preceding claim does not hold. A ϕ inv -violation witness does not mean that p does not satisfy pure(ϕ inv ).
We establish the above result as below. If we have a trace that satisfies the invariant and the set of its sub-traces are pure, then the valuations assigned to variables by the trace satisfies ϕ post . Thus, if (π 1 , π 2 ) are a minimal impurity witness, let π a and π b be the two traces in π 1 and π 2 that are incompatible. We can assign values to variables in ϕ post α from π a , and assign values to variables in ϕ post β from π b to get a satisfying assignment for (ϕ post α ∧ϕ post β ∧(n α = n β )∧(r α = r β )).
If π is a minimal ϕ inv -violation witness, let π ′ be the trace that contains the invariant violation. Assigning values to variables as in π ′ produces a satisfying assignment for ¬ϕ vc .

Generating the Invariant
We now describe a simple but reasonably effective semi-algorithm for generating a candidate invariant automatically from the given procedure. Our approach of Section 4 can be used with a manually provided invariant or the candidate invariant generated by this semi-algorithm (whenever it terminates).
The invariant-generation approach is iterative and computes a sequence of progressively weaker candidate invariants I 0 , I 1 , · · · and terminates if and when I m ≡ I m+1 , at which point I m is returned as the candidate invariant. The initial candidate invariant I 0 captures the initial values of the global variable. In iteration k, we apply a procedure similar to the one described in Section 4 and compute the strongest conditions that hold true at every program point if the execution of the procedure starts in a state satisfying I k−1 and if every recursive invocation terminates in a state satisfying I k−1 . We then take the disjunction of the conditions computed at the points before the recursive call-sites and at the end of the procedure, and existentially quantify all local variables. We refer to the resulting formula as Next(I k−1 , TB(p, I k−1 )). We take the disjunction of this formula with I k−1 and simplify it to get I k .
In the following formalization of this semi-algorithm, we exploit the fact that the assert statements are added precisely at every recursive callsite and end of procedure and these are the places where we take the conditions to be disjuncted.

Evaluation
We have implemented our OP checking approach as a prototype using the Boogie framework [4], and have evaluated the approach using this implementation on several examples. The objective of this evaluation was primarily a sanity check, to test how our approach does on a set of OP as well as non-OP procedures.
We tried several simple non-OP programs, and our implementation terminated with a "no" answer on all of them. We also tried the approach on several OP procedures: (1) the 'factCache' running example, (2) a version of a factorial procedure that caches all arguments seen so far and their corresponding return values in an array, (3) a version of factorial that caches only the return value for argument value 19 in a scalar variable, (4) a recursive procedure that returns the n th Fibonacci number and caches all its arguments and corresponding return values seen so far in an array, and (5) a "matrix chain multiplication" (MCM) procedure. The last example is based on dynamic programming, and hence naturally uses a table to memoize results for sub-problems. Here, observational purity implies that the procedure always returns the same solution for a given sub-problem, whether a hit was found in the table or not. The appendix of a technical report associated with this paper depicts all the procedures mentioned above as created by us directly in Boogie's language, as well as the invariants that we supplied manually (in SMT2 format).
It is notable that our "existential approach" causes the theorem prover to not scale to even simple examples. The "impurity witness" approach terminated on all the examples mentioned above with a "yes" answer, with the theorem prover taking less than 1 second on each example. it would take a delicate argument, and additional conditions, to avoid unsoundness in this case". Our idea of allowing the function symbol in the invariant to represent the recursive call allows recursive procedures to be checked, and also simplifies the specification of the invariant in many cases.
Cok et al. [5] generalize the work of Barnett et al.'s work, and suggest classifying procedures into categories "pure", "secret", and "query". The "query" procdures are observationally pure. Again, recursive procedures are not addressed.
Naumann [3] proposes a notion of observational purity that is also the same as ours. Their paper gives a rigorous but manual methodology for proving the observational purity of a given procedure. Their methodology is not similar to ours; rather, it is based finding a weakly pure procedure that simulates the given procedure as far as externally visible state changes and the return value are concerned. They have no notion of an invariant that uses a function symbol that represents the procedure, and they don't explicitly address the checking of recursive procedures.
There exists a significant body of work on identifying differences between two similar procedures. For instance, differential assertion checking [6] is a representative from this body, and is for checking if two procedures can ever start from the same state but end in different states such that exactly one of the ending states fails a given assertion. Their approach is based on logical reasoning, and accommodates recursive procedures. Our impurity witness approach has some similarity with their approach, because it is based on comparing the given procedure with itself. However, our comparison is stricter, because in our setting, starting with a common argument value but from different global states that are both within the invariant should not cause a difference in the return value. Furthermore, technically our approach is different because we use an invariant that refers to a function symbol that represents the procedure being checked, which is not a feature of their invariants. Partush et al. [7] solve a similar problem as differential assertion checking, but using abstract interpretation instead of logical reasoning.
There is a substantial body of work on checking if a procedure is pure, in the sense that it does not modify any objects that existed before the procedure was invoked, and does not modify any global variables. Salcianu et al. [8] describe a static analysis to check purity. Various tools exist, such as JML [9] and Spec# [10], that use logical techniques based on annotations to prove procedures as pure. Purity is a more restrictive notion than observational purity; procedures such as our 'factCache' example are observationally pure, but not pure because they use as well as update state that persists between calls to the procedure.