470 likes | 481 Views
Weakest Precondition of Unstructured Programs. Short Presentation. Preliminaries. Program verification systems typically transform a program into a logical expression which is then sent to a theorem prover. Preliminaries.
E N D
Weakest Precondition of Unstructured Programs Short Presentation
Preliminaries • Program verification systems typically transform a program into a logical expression which is then sent to a theorem prover
Preliminaries • Program verification systems typically transform a program into a logical expression which is then sent to a theorem prover • The logical expression represents the weakest precondition of the program relative to its specification • when and if the theorem prover is able to prove the expression, then the program is considered correct
Preliminaries • Program verification systems typically transform a program into a logical expression which is then sent to a theorem prover • The logical expression represents the weakest precondition of the program relative to its specification • when and if the theorem prover is able to prove the expression, then the program is considered correct • computing such a logical expression for an imperative, structured program is straightforward • there are issues having to do with loops and the efficiency both of the computation and of the complexity of the formula with respect to the theorem prover
Preliminaries • Program verification systems typically transform a program into a logical expression which is then sent to a theorem prover • The logical expression represents the weakest precondition of the program relative to its specification • when and if the theorem prover is able to prove the expression, then the program is considered correct • computing such a logical expression for an imperative, structured program is straightforward • there are issues having to do with loops and the efficiency both of the computation and of the complexity of the formula with respect to the theorem prover • in the followings, it is presented a novel approach for computing the weakest precondition of an unstructured program that is sound even in the presence of loops
Static Program Verifier • the typical architecture of a static program verifier takes as input a program and its specification, generates from these a verification condition – a first-order logical formula whose validity implies that the program meets the specification and then passes the verification condition to a theorem prover
Static Program Verifier • the typical architecture of a static program verifier takes as input a program and its specification, generates from these a verification condition – a first-order logical formula whose validity implies that the program meets the specification and then passes the verification condition to a theorem prover • the engineering of the verification condition has a large impact on the proving task presented to the theorem prover (the formula shouldn’t be larger than it needs to be) • Spec# is a programming environment that relies on a static program verifier (the framework is developed at Microsoft Research)
Static Program Verifier • the typical architecture of a static program verifier takes as input a program and its specification, generates from these a verification condition – a first-order logical formula whose validity implies that the program meets the specification and then passes the verification condition to a theorem prover • the engineering of the verification condition has a large impact on the proving task presented to the theorem prover (the formula shouldn’t be larger than it needs to be) • Spec# is a programming environment that relies on a static program verifier (the framework is developed at Microsoft Research) • the verification condition generation in Spec# proceed in stages, as well • the starting point is a general control-flow graph (natural choice since Spec# static program verifier uses as its input language the intermediate language of the .NET virtual machine, whose branch instructions can give rise to any control flow)
Spec# Environment • Spec# (developed at Microsoft Research) has a static program verifier • Spec# • - an extension of C# with preconditions, postconditions, non-null types, checked exceptions, loop invariants, object invariants and other reliability features • - accompanied by a compiler that emits runtime checks • - accompanied by a static program verifier backed by an automatic theorem prover
Spec# Environment • Spec# (developed at Microsoft Research) has a static program verifier • Spec# • - an extension of C# with preconditions, postconditions, non-null types, checked exceptions, loop invariants, object invariants and other reliability features • - accompanied by a compiler that emits runtime checks • - accompanied by a static program verifier backed by an automatic theorem prover • the program verifier works by translating the Spec# source code into a guarded commands program, which is then further translated into verification conditions that are passed to the theorem prover • now, back to the verification condition generation in Spec#
Generation of VC in Spec# • - The verification condition generation in Spec# proceed in stages, as well • the starting point is a general control-flow graph (natural choice since Spec# static program verifier uses as its input language the intermediate language of the .NET virtual machine, whose branch instructions can give rise to any control flow)
Generation of VC in Spec# • - The verification condition generation in Spec# proceed in stages, as well • the starting point is a general control-flow graph (natural choice since Spec# static program verifier uses as its input language the intermediate language of the .NET virtual machine, whose branch instructions can give rise to any control flow • using standard compilation techniques that duplicate instructions to eliminate multiple entry points to loops, the general control-flow graph is transformed into a reducible one
Generation of VC in Spec# • - The verification condition generation in Spec# proceed in stages, as well • the starting point is a general control-flow graph (natural choice since Spec# static program verifier uses as its input language the intermediate language of the .NET virtual machine, whose branch instructions can give rise to any control flow • using standard compilation techniques that duplicate instructions to eliminate multiple entry points to loops, the general control-flow graph is transformed into a reducible one • the loops are eliminated, producing an acyclic control-flow graph that is correct if the original program is correct
Generation of VC in Spec# • - The verification condition generation in Spec# proceed in stages, as well • the starting point is a general control-flow graph (natural choice since Spec# static program verifier uses as its input language the intermediate language of the .NET virtual machine, whose branch instructions can give rise to any control flow • using standard compilation techniques that duplicate instructions to eliminate multiple entry points to loops, the general control-flow graph is transformed into a reducible one • the loops are eliminated, producing an acyclic control-flow graph that is correct if the original program is correct • a single-assignment transformation to the acyclic program turns it into a passive program (changing the assignments statements into assume statements)
Generation of VC in Spec# • - The verification condition generation in Spec# proceed in stages, as well • the starting point is a general control-flow graph (natural choice since Spec# static program verifier uses as its input language the intermediate language of the .NET virtual machine, whose branch instructions can give rise to any control flow • using standard compilation techniques that duplicate instructions to eliminate multiple entry points to loops, the general control-flow graph is transformed into a reducible one • the loops are eliminated, producing an acyclic control-flow graph that is correct if the original program is correct • a single-assignment transformation to the acyclic program turns it into a passive program (changing the assignments statements into assume statements) • finally, we apply the weakest precondition and we generate the verification condition!!!
Programs and Correctness • Program : a chunk of code that is to be verified • Language : follows the following grammar • Some constructs : • assert C : statement causes an error to be reported if the condition C cannot be shown to always hold • assume C : statement causes the verifier to consider only those program executions which either do not reach this statement or satisfy the condition C • havoc x : statement assigns an arbitrary value to the variable
Programs and Correctness • A program gives rise to a set of execution traces (sequences of program states) • A trace is either infinite, or it ends in termination or ends in error or ends in infeasibility • in other words: the trace ends in termination if there are no successors to choose from, ends in error if an assert statements evaluates to false, and ends in infeasibility if an assume statements evaluates to false
Programs and Correctness • A program gives rise to a set of execution traces (sequences of program states) • A trace is either infinite, or it ends in termination or ends in error or ends in infeasibility • in other words: the trace ends in termination if there are no successors to choose from, ends in error if an assert statements evaluates to false, and ends in infeasibility if an assume statements evaluates to false • A program is correct if none of its traces ends in error • the definition does not say anything about the final state of terminating executions • a correct program can have traces that end in infeasibility • a correct program can include neverending executions
Example in Spec# • Let us consider the following Spec# source program:
Example in Spec# • The control-flow graph corresponding to this method is encoded as follows, where we have used a variable r to denote the result value:
Generation of VC in Spec# • - Getting back to the verification condition generation in Spec#: • the starting point is a general control-flow graph (natural choice since Spec# static program verifier uses as its input language the intermediate language of the .NET virtual machine, whose branch instructions can give rise to any control flow • using standard compilation techniques that duplicate instructions to eliminate multiple entry points to loops, the general control-flow graph is transformed into a reducible one • the loops are eliminated, producing an acyclic control-flow graph that is correct if the original program is correct • a single-assignment transformation to the acyclic program turns it into a passive program (changing the assignments statements into assume statements) • finally, we apply the weakest precondition and we generate the verification condition!!!
Elimination of Loops • The third step in VC generation is the transformation from a reducible control-flow graph into an acyclic control-flow graph • a reducible control-flow graph is one where it is possible to identify a unique loop head for each loop
Elimination of Loops • The third step in VC generation is the transformation from a reducible control-flow graph into an acyclic control-flow graph • a reducible control-flow graph is one where it is possible to identify a unique loop head for each loop • In order to identify the loops, we begin by finding all of the back edges • it is the existence of a back edge that uniquely identifies a loop • a back-edge is an edge in the control-flow graph whose tail (target) dominates its head (source), with one node dominating another node when all paths to the latter pass through the former • the loop header L for a back edge B is the target of the edge • each natural loop is identified by the pair (L, B)
Elimination of Loops • In order to identify the loops, we begin by finding all of the back edges • we remove all back edges to cut the loops, thus transforming the graph into an acyclic one • but in order for a loop body to represent an arbitrary loop iteration, we must make sure that the values of any variables modified within the loop have a value that they might hold on any iteration of the loop • for each natural loop (L, B), we collect into a set H(L, B) the variables that are updated by any statement in any block in the loop • for each loop target in H(L, B), we introduce a havoc statement and insert it at the beginning of L, before any of the existing statements in that block
Elimination of Loops • In order to identify the loops, we begin by finding all of the back edges • we remove all back edges to cut the loops, thus transforming the graph into an acyclic one • but in order for a loop body to represent an arbitrary loop iteration, we must make sure that the values of any variables modified within the loop have a value that they might hold on any iteration of the loop • for each natural loop (L, B), we collect into a set H(L, B) the variables that are updated by any statement in any block in the loop • for each loop target variable in H(L, B), we introduce a havoc statement and insert it at the beginning of L, before any of the existing statements in that block • wiping out all knowledge of the value a variable might hold may cause the theorem prover to be unable to prove the verification condition (induces an overapproximation of the original program and loses too much precision) • therefore, each loop will have a loop invariant…
Elimination of Loops • Loop invariants are encoded as a prefix of assert statements at the beginning of the loop header’s code block (the assert cannot be validated if any of the variables they mention are in H(L,B) • instead, we introduce a copy of this sequence of statements into each predecessor node of L (including the node that is the source of the back edge) • since the assertions are now checked just before the jump to the loop header, we change the statements into assume statements in L itself • we process loop invariants in this way before adding the havoc statements and cutting the back edges • the resulting havoc followed by the assume statements have the effect of retaining, about the loop targets, the information in the loop invariant • This is a conservative approximation: it increases the code size (when introducing a copy of some code at the source of each edge instead of having a single copy at its target), but the correctness of the program is not affected
Elimination of Loops • for example, starting with the following control-flow graph (previously introduced program): • after cutting back edges, the loop free program is:
Elimination of Loops • the loop free program:
Generation of VC in Spec# • - Getting back to the verification condition generation in Spec#: • the starting point is a general control-flow graph (natural choice since Spec# static program verifier uses as its input language the intermediate language of the .NET virtual machine, whose branch instructions can give rise to any control flow • using standard compilation techniques that duplicate instructions to eliminate multiple entry points to loops, the general control-flow graph is transformed into a reducible one • the loops are eliminated, producing an acyclic control-flow graph that is correct if the original program is correct • a single-assignment transformation to the acyclic program turns it into a passive program (changing the assignments statements into assume statements) • finally, we apply the weakest precondition and we generate the verification condition!!!
Passification • The loop free program is transformed into a passive program in two stages: • the program is rewriting into a single-assignment form • the program’s assignments are removed
Passification - DSA • Step 1: single assignment form (DSA) • we convert a loop-free program into DSA form by noting that after each update to a variable, its value must be understood relative to the newly updated state by identifying each updated value as a new incarnation of the variable • for instance: the assignment • is replaced with the assignment
Passification - DSA • Step 1: single assignment form (DSA) • we convert a loop-free program into DSA form by noting that after each update to a variable, its value must be understood relative to the newly updated state by identifying each updated value as a new incarnation of the variable • for instance: the assignment • is replaced with the assignment • All variables read by the statement are replaced by their current incarnation • At the beginning of the program, an initial incarnation is created for each program variable • the algorithm that perform all these replacements, processes the graph in a topologically sorted order
Passification - DSA • Step 1: single assignment form (DSA) • for straight-line code, it is simple to iterate over the sequence of statements, replacing all of the variables with their current incarnations
Passification - DSA • Step 1: single assignment form (DSA) • for straight-line code, it is simple to iterate over the sequence of statements, replacing all of the variables with their current incarnations • problems arise at join points: a node may be “inheriting” conflicting incarnations (see the example with a if-then-else block) • solved with a new incarnation, for both then and else blocks • a new incarnation is potentially created for each variable at every join point (leading to more incarnations than strictly necessary) <- solved by reusing predecessors’ incarnations
Passification - DSA • Step 1: single assignment form (DSA) • for straight-line code, it is simple to iterate over the sequence of statements, replacing all of the variables with their current incarnations • problems arise at join points: a node may be “inheriting” conflicting incarnations (see the example with a if-then-else block) • solved with a new incarnation, for both then and else blocks • a new incarnation is potentially created for each variable at every join point (leading to more incarnations than strictly necessary) <- solved by reusing predecessors’ incarnations • due to this solution, we use DSA and not SSA
Passification - assume • Step 2 : replace all assignment statements by assume statements • The assignment : • is replaced with • - This is possible because the value of xi is not used prior to its definition (so xi had the desired value all along)
Passification • the loop free program: • is transformed into the passive program:
Passification • the passive form of the program:
Generation of VC in Spec# • - Getting back to the verification condition generation in Spec#: • the starting point is a general control-flow graph (natural choice since Spec# static program verifier uses as its input language the intermediate language of the .NET virtual machine, whose branch instructions can give rise to any control flow • using standard compilation techniques that duplicate instructions to eliminate multiple entry points to loops, the general control-flow graph is transformed into a reducible one • the loops are eliminated, producing an acyclic control-flow graph that is correct if the original program is correct • a single-assignment transformation to the acyclic program turns it into a passive program (changing the assignments statements into assume statements) • finally, we apply the weakest precondition and we generate the verification condition!!!
Weakest Precondition • we apply weakest precondition to the passive form of the program and get the verification condition • By definition, for any statement S and predicate Q on the post-state of S, the weakest precondition of S with respect to Q, written wp(S,Q) is a predicate that characterizes all pre-states of S from which no execution will go wrong and from which every terminating execution ends in a state satisfying Q
Weakest Precondition • we apply weakest precondition to the passive form of the program and get the verification condition • By definition, for any statement S and predicate Q on the post-state of S, the weakest precondition of S with respect to Q, written wp(S,Q) is a predicate that characterizes all pre-states of S from which no execution will go wrong and from which every terminating execution ends in a state satisfying Q • for our (passive) language constructs, the weakest precondition is defined as follows:
Weakest Precondition • in a structured program, the central problem to be overcome in computing weakest preconditions is that of the choice statement (arbitrarily choosing to execute S or T): • the problem is that the duplication of Q in the right-hand side introduces redundancy (the theorem prover would need to process Q twice)
Weakest Precondition • in a structured program, the central problem to be overcome in computing weakest preconditions is that of the choice statement (arbitrarily choosing to execute S or T): • the problem is that the duplication of Q in the right-hand side introduces redundancy (the theorem prover would need to process Q twice) • passive programs satisfy a property that lets this wp equation be formulated in a way that significantly reduces redundancy • this alternate form is called weak liberal precondition (wlp) and produces verification condition whose size is quadratic in the size of the passive programs • wlp applies to structured programs, so to extend it for unstructured programs, there will be some preprocessing steps
Weakest Precondition • the unstructured programs do not have the structured choice statement (they have goto statements) and the control-flow graph explodes into a tree (and lose all the sharing that the control-flow graph representation affords) • therefore, for every goto block, it is introduced an auxiliary variable that is true it the program is in a state from which all executions beginning from that block are correct • where Succ(A) denotes the set of successors of A so that the second argument to wp is the conjunction of B_ok for each block B in that set • - For example…
Weakest Precondition • for the program (if-then-else statement, actually): • The block equation for Then is: • Each block contributes one block equation, call their conjunction R, and the program’s verification condition is:
Weakest Precondition • the passive form of the program: • computing the wp, the set of block equations are:
Weakest Precondition • the block equations : • finally the verification condition is: