6. Hoare and Weakest Preconditions

Program Verification

ETH Zurich, Spring Semester 2017 Alexander J. Summers 130 Program Correctness • There are many notions of correctness properties for a given program • is the program guaranteed to reach a certain program point (e.g. terminate)? • when the program reaches this point, are certain values guaranteed? • could the program encounter runtime errors / raise certain exceptions? • will the program leak memory / secret data, etc.? • To build a verifier, we need clearly defined correctness criteria • We will focus on a classic correctness notion: partial correctness • A program is partially correct with respect to pre-/post-conditions 1/ 2 iff: All executions of starting from states satisfying 1 are free of runtime errors, and, any such executions which terminate will do so in states satisfying 2 • For non-terminating executions, we still guarantee absence of runtime errors • The above notion is succinctly expressed using a Hoare triple: 1 2

131 A Small Imperative Language • Program variables , , ,… (can be assigned to) • Expressions , , ,… (we won’t fix a specific syntax for now) • e.g. includes boolean/arithmetic operators, mathematical functions • assume a standard type system (no subtyping/casting); we omit the details • we’ll typically write , 1, 2,… for boolean-typed expressions • expression evaluation assumed to be side-effect-free for all expressions • Assertions used in specifications, Hoare triples, etc. • for now, assertions are just the same as boolean expressions • later in the course, we’ll want specifications richer than program expressions • Statements (see subsequent slides) • We define a small language here; may extend the syntax later

132 Standard Statements • Our statement language includes the following standard constructs: • (does nothing when executed) • (assignment: changes value of )

• 1 2 (sequential composition: execute 1 followed by 2) • 1 2 (execute 1 if evaluates to true; 2 otherwise) • (repeatedly execute 1 while evaluates to true) • A runtime state is a mapping from variables to values • We assume a small-step operational semantics (not formalised here) • A runtime configuration is a pair of a statement and a program state • A trace is either an infinite sequence of runtime configurations, or is a finite sequence of runtime configurations appended with one of the following: a single runtime state (the final state), the symbol error, the symbol magic

133 Verification and Non-Deterministic Statements • We add the following statements to those of the previous slide: • (traces for which is false at this point end here with error) • (traces for which is false at this point end here with magic)

• 1 2 (non-deterministic choice: execute either 1 or 2 from here) • (non-deterministically assign an arbitrary value to variable ) • A failing trace is one ending in error • The former two statements filter traces (fewer outgoing than ingoing) • The latter two statements split traces (more outgoing than ingoing) • this is characteristic of a non-deterministic language • We write to denote that is true in ( defines a model of )

134 Trace Semantics in Pictures

error error

1 2

magic magic

1

2 3

135 I • Hoare Logic is a standard proof style for correctness proofs • Proofs are derivation trees, built by instantiating derivation rule schemas • Every derivation tree has a Hoare triple as its root • Rule schemas must be instantiated for particular statements, assertions etc. • Rule schemas for standard statements (partial correctness): • we write for (capture-avoiding) substitution of for each free in • we omit while loops for now (coming soon…)

1 1 2 2 2 3 (skip) (ass) (seq) 1 1 2 3

1 1 2 1 2 2 (if) 1 1 2 2

136 Hoare Logic II • Rule schemas for verification and non-deterministic statements:

1 1 2 1 2 2 (nondet) (havoc) 1 1 2 2

(assert) (assume) 1 1 1 1

• The rule of consequence allows reasoning in terms of the semantics of the assertions (this rule is very important!) : denotes free variables; variables modified) 1 2 2 3 3 4 (conseq) 1 4

137 Hoare Logic (Alternative Rules) • Consider the following alternative rules

(havoc-alt)

1 (assert-alt) (assume-alt) 1 1 1 1

• Exchanging (any of) these rules for their counterparts on the previous slide doesn’t change the derivable Hoare triples (see exercises) • The alternative rules are “forward oriented”: it is easier to understand them from left-to-right (those on previous slide are “backward-oriented”) • You may find one formulation more intuitive than another

138 Hoare Triples - Properties • is semantically valid, written , iff: For all states such that , there are no failing traces starting from , and for all such traces ending in final states :

• equivalently, the program 1 2 has no failing traces • A triple is provable, written , iff there exists a derivation tree with as its root • The derivation rules presented are sound (only prove valid triples): • For all , 1, 2, if 1 2 then 1 2 • Under certain conditions these derivation rules are also complete: • If the assertion language is able to express all weakest preconditions (explained soon) then: for all , 1, 2, if 1 2 then 1 2 • in some sense, this tells us we are not “missing” proof rules from the system

139 Hoare Logic - Example • Suppose we want to prove the triple

• A triple 1 2 is provable, written 1 2 , iff there exists a derivation tree with 1 2 as its root • This doesn’t tell us how to construct such a derivation tree, in general • We have different choices for how to construct derivation trees, e.g.: • (we typically don’t draw the rule premises which are not Hoare triples) (ass) (ass) (conseq) (conseq)

• This illustrates redundancy in the proof system • The redundancy is useful when constructing proofs by hand (flexible) • When trying to automate proofs, it is less desirable (enumerate derivations?)

140 Weakest Preconditions - Idea • Suppose we want to prove a particular triple • This would require us to find suitable intermediate assertions • e.g. to prove 1 1 2 2 we will need assertion(s) used as postcondition of 1 / precondition of 2, which also satisfy the appropriate proof rules • We can orient the search for these assertions, and for a derivation • For example, starting from our postcondition, work backwards through • At each sub-statement, find the precondition for this statement which works • We should always choose this precondition to be as logically weak as possible • This idea is formalised by a weakest precondition function

• is the intended postcondition (e.g. 2 above); the statement in question • name stands for weakest “liberal” precondition: it ignores termination • dual strongest postcondition notion exists; we focus on weakest preconditions

141 Weakest Preconditions – Desired Properties • We’d like a function returning a predicate on states s.t. : • Expressibility: For all and , is expressible as an assertion • Soundness: For all and , it is guaranteed that

• Minimality: For all , 1, 2, if 1 2 then 1 • Computability: For all and , is computable (ideally, efficiently) • We can now explain the condition for completeness (3 slides ago): • Soundness + Minimality semantically define a unique predicate on states • Our assertion language is able to express all weakest preconditions iff, for any and , there exists some 1 whose meaning is equivalent to this predicate • Suppose we had a suitably-defined notion

• We could build a program verifier to check validity of triples 1 2 by computing 2 and then checking the entailment 1 2

142 Weakest Preconditions – Definition I • Attempt to define by “reading” derivation rules backwards • We want to find the weakest way to fill the in: • For example, consider the following three derivation rules:

1 1 2 2 2 3 (skip) (ass) (seq) 1 1 2 3 • We can “read off” three suitable cases of the definition: • •

• 1 2 1 2 • What about the other statements of our language?

143 Weakest Preconditions – Definition II • Consider the other loop-free statements of our language:

1 1 2 1 2 2 1 1 2 1 2 2 (if) (nondet) 1 1 2 2 1 1 2 2

(havoc)

(assert) (assume) 1 1 1 1

• 1 2 1 2 • 1 2 1 2 •

• 1 1 • 1 1

144 Weakest Preconditions – Loops • The standard Hoare Logic rule for loop constructs is the following:

I I (while-Hoare) I I • The assertion is called a loop • In general, our current postcondition won’t be a suitable • The rule doesn’t give us a direct definition of • The obstacle is in finding an appropriate loop invariant for the above rule • We might imagine unrolling the loop to “define” as:

• This definition is not effectively computable for general loops: it tries to compute an “infinite” assertion (may not even be an expressible assertion)

145 Loops – Adding Invariants • With hindsight, it’s not surprising that we don’t get an effective

• checking validity of 1 2 for such a language is typically undecidable (even for assertion languages with decidable entailment) • if were computable for general loops, we’d have an effective (!) • We will require loops to be annotated with loop invariants • these annotations could be manual, generated by a static analysis tool, etc. • We change the syntax for loops, and write • Here, is the declared loop invariant for the while loop • Replacing the usual rule (previous slide), we use the following one: I I (while-inv) I I I

146 Weakest Preconditions for Annotated Loops • For this annotated language, we can give a definition for loops:

I I (while-inv) I I I •

where is the set of variables modified in , and are fresh variable names • This may seem hard to understand directly, but informally: • The first conjunct insists that the invariant I holds before the loop • The next conjunct (inside the ) corresponds to checking the loop body • The last conjunct (inside the ) corresponds to guaranteeing that the desired postcondition will hold after the loop (if the loop terminates)

147 Eliminating Annotated Loops • A while loop with loop invariant can be desugared as follows: • Let 1, 2 ,…, n be the (perhaps zero) variables modified by • Then I is rewritten into the program: • I 1 2 n I I

I • The first I statement checks the loop invariant holds initially • The s model side-effects of an unbounded number of loop iterations • The next line checks that the loop invariant is preserved by each loop iteration • The final line models termination, if we ever leave the loop, I will hold • Try applying to this program and compare with the previous slide • you should get an equivalent formula to definition for annotated loops

148 Approximation via Loop Invariants • The desugaring on the previous slide requires checking that: • the declared loop invariant is preserved starting from states satisfying I • the remainder of the program is correct starting from states satisfying I • in both cases, this might include states which are never reached by the loop • e.g. • invariant is not re-established from all states in which • the statement may fail for some states in which • But on execution, the invariant will always hold and the will succeed • The loop invariant is used to over-approximate the loop behaviour • Our is complete with respect to provable triples ( 1 2 ) • It is not complete with respect to valid triples ( 1 2 ) • Both the proof system and are only complete with precise loop invariants

149 Wlp Summary • For annotated programs, our definition is summarised by

1 2 1 2 1 1 1 1 1 2 1 2 1 2 1 2 I I I I I • With respect to our desired properties, this definition is sound, and complete with respect to provable triples ( ) • but not necessarily with respect to valid triples: depends on loop invariants • The definition returns an assertion; it is expressible and computable

150 Hoare Logic and Weakest Preconditions - Summary • We have seen Hoare Logic: a means of proving program properties • proofs consist of derivation trees – flexible for manual proof efforts • Automating the checking of Hoare triples via weakest preconditions • these provide a means of directing the proof search (working backwards) • For recursion (loops, here), we require annotated loop invariants • approximate the recursive behaviour of a loop, according to the invariant • Resulting computable notion of weakest precondition w.r.t. invariants • we reduce program correctness to checking entailment between assertions • This idea allows the implementation of a program verifier • User provides specification and e.g. loop invariants in some assertion syntax • For suitable assertion syntaxes, we can check entailments with an SMT solver

151 Hoare Logic and Weakest Preconditions – References • Hoare Logic: • Assigning meanings to programs. R. W. Floyd (1967) • An axiomatic basis for computer programming. Hoare, C. A. R. (1969) • Soundness and Completeness of an System for Program Verification. Stephen A. Cook (1978)

• Weakest Preconditions: • Guarded commands, nondeterminacy and formal derivation of programs. Edsger W. Dijkstra (1975) • Avoiding exponential explosion: generating compact verification conditions. Cormac Flanagan, James B. Saxe (2001) • Weakest-precondition of unstructured programs. Mike Barnett, K. Rustan M. Leino (2005)

• Other teaching material: • Formal Methods and . Peter Müller (ETH Zurich) • Synthesis, Analysis, and Verification. Viktor Kuncak (EPFL)

152