Induction and Semantics in Dafny

Similar documents
Note that in this definition, n + m denotes the syntactic expression with three symbols n, +, and m, not to the number that is the sum of n and m.

Incremental Proof Development in Dafny

Harvard School of Engineering and Applied Sciences CS 152: Programming Languages

Programming Languages Fall 2013

An Annotated Language

Programming Languages Fall 2014

Goals: Define the syntax of a simple imperative language Define a semantics using natural deduction 1

3.7 Denotational Semantics

A CRASH COURSE IN SEMANTICS

Lecture 1 Contracts : Principles of Imperative Computation (Fall 2018) Frank Pfenning

Semantics with Applications 3. More on Operational Semantics

In Our Last Exciting Episode

axiomatic semantics involving logical rules for deriving relations between preconditions and postconditions.

Lecture 1 Contracts. 1 A Mysterious Program : Principles of Imperative Computation (Spring 2018) Frank Pfenning

Reasoning About Imperative Programs. COS 441 Slides 10

Programming Languages Third Edition

Lecture 3: Recursion; Structural Induction

CS422 - Programming Language Design

Formal Semantics of Programming Languages

Formal Semantics of Programming Languages

6. Hoare Logic and Weakest Preconditions

CMSC 330: Organization of Programming Languages

Lecture Notes on Contracts

CIS 500 Software Foundations Fall October 2

Chapter 3. The While programming language

Now that we have keys defined for the maps, we can start talking about maps. The first of these are Total Maps. Total Maps are defined as follows:

Program Analysis: Lecture 02 Page 1 of 32

CS 161 Computer Security

AXIOMS OF AN IMPERATIVE LANGUAGE PARTIAL CORRECTNESS WEAK AND STRONG CONDITIONS. THE AXIOM FOR nop

Checks and Balances - Constraint Solving without Surprises in Object-Constraint Programming Languages: Full Formal Development

Application: Programming Language Semantics

Recursively Enumerable Languages, Turing Machines, and Decidability

Operational semantics questions and answers

CS 3512, Spring Instructor: Doug Dunham. Textbook: James L. Hein, Discrete Structures, Logic, and Computability, 3rd Ed. Jones and Barlett, 2010

CIS 500 Software Foundations Midterm I

CIS 500 Software Foundations. Final Exam. May 3, Answer key

COMP 507: Computer-Aided Program Design

CS4215 Programming Language Implementation. Martin Henz

Lecture 5 - Axiomatic semantics

CMSC 330: Organization of Programming Languages. Formal Semantics of a Prog. Lang. Specifying Syntax, Semantics

AXIOMS FOR THE INTEGERS

Lecture Notes on Program Equivalence

Typing Control. Chapter Conditionals

3.2 Big-Step Structural Operational Semantics (Big-Step SOS)

Testing, Debugging, and Verification

Foundations, Reasoning About Algorithms, and Design By Contract CMPSC 122

1 Variations of the Traveling Salesman Problem

Introduction to Axiomatic Semantics

Outline. Introduction. 2 Proof of Correctness. 3 Final Notes. Precondition P 1 : Inputs include

Lectures 20, 21: Axiomatic Semantics

2 Introduction to operational semantics

Foundations: Syntax, Semantics, and Graphs

PROGRAM ANALYSIS & SYNTHESIS

Lecture 1: Overview

Lecture 15 : Review DRAFT

Formal semantics of loosely typed languages. Joep Verkoelen Vincent Driessen

Exercises on Semantics of Programming Languages

Semantics via Syntax. f (4) = if define f (x) =2 x + 55.

MoreIntro.v. MoreIntro.v. Printed by Zach Tatlock. Oct 07, 16 18:11 Page 1/10. Oct 07, 16 18:11 Page 2/10. Monday October 10, 2016 lec02/moreintro.

CSCI.6962/4962 Software Verification Fundamental Proof Methods in Computer Science (Arkoudas and Musser) Sections p.

Introduction to dependent types in Coq

P1 Engineering Computation

Abstract Interpretation

CSCI.6962/4962 Software Verification Fundamental Proof Methods in Computer Science (Arkoudas and Musser) Section 17.2

Repetition Through Recursion

Warm-Up Problem. 1. What is the definition of a Hoare triple satisfying partial correctness? 2. Recall the rule for assignment: x (assignment)

CONVENTIONAL EXECUTABLE SEMANTICS. Grigore Rosu CS422 Programming Language Design

CIS 500: Software Foundations

Lecture 4: examples of topological spaces, coarser and finer topologies, bases and closed sets

CS152: Programming Languages. Lecture 11 STLC Extensions and Related Topics. Dan Grossman Spring 2011

Lecture 2: SML Basics

CSE505, Fall 2012, Midterm Examination October 30, 2012

Forward Assignment; Strongest Postconditions

Program Verification. Program Verification 307/434

Z Notation. June 21, 2018

SOFTWARE ENGINEERING DESIGN I

CISC-124. Casting. // this would fail because we can t assign a double value to an int // variable

Programming Languages and Techniques (CIS120)

15 212: Principles of Programming. Some Notes on Induction

Definition: A context-free grammar (CFG) is a 4- tuple. variables = nonterminals, terminals, rules = productions,,

Formal Systems II: Applications

Type Checking. Outline. General properties of type systems. Types in programming languages. Notation for type rules.

Softwaretechnik. Lecture 03: Types and Type Soundness. Peter Thiemann. University of Freiburg, Germany SS 2008

HOL DEFINING HIGHER ORDER LOGIC LAST TIME ON HOL CONTENT. Slide 3. Slide 1. Slide 4. Slide 2 WHAT IS HIGHER ORDER LOGIC? 2 LAST TIME ON HOL 1

Proving Properties on Programs From the Coq Tutorial at ITP 2015

Outline. General properties of type systems. Types in programming languages. Notation for type rules. Common type rules. Logical rules of inference

Chapter 3. Describing Syntax and Semantics

Meeting14:Denotations

Introduction to Programming, Aug-Dec 2006

Chapter 3 (part 3) Describing Syntax and Semantics

Formal Semantics of Programming Languages

Introduction to Denotational Semantics. Brutus Is An Honorable Man. Class Likes/Dislikes Survey. Dueling Semantics

14.1 Encoding for different models of computation

Introduction to Denotational Semantics. Class Likes/Dislikes Survey. Dueling Semantics. Denotational Semantics Learning Goals. You re On Jeopardy!

Solutions to the Second Midterm Exam

CSCI.6962/4962 Software Verification Fundamental Proof Methods in Computer Science (Arkoudas and Musser) Chapter p. 1/27

Handout 9: Imperative Programs and State

Variables. Substitution

Meeting13:Denotations

Harvard School of Engineering and Applied Sciences CS 152: Programming Languages

Transcription:

15-414 Lecture 11 1 Instructor: Matt Fredrikson Induction and Semantics in Dafny TA: Ryan Wagner Encoding the syntax of Imp Recall the abstract syntax of Imp: a AExp ::= n Z x Var a 1 + a 2 b BExp ::= true false a 1 = a 2 a 1 a 2 b b 1 b 2 c Com ::= skip x := a c 1 ; c 2 if b then c 1 else c 2 while b do c We can encode this in Dafny using inductive datatypes. Intuitively, values of inductive datatypes can be seen as finite trees, where nodes correspond to constructors with zero or more arguments. Constructors can take parameters, and when the type of a parameter is the inductive datatype itself, the corresponding node is internal. We begin by declaring types for variables and values, which we will represent using strings and integers, respectively. The remaining definition defines inductive datatypes for AExp, BExp, and Com. 1 type Var = string 2 type Val = int 3 4 datatype AExp = N( Val ) 5 V( Var ) 6 Plus (AExp, AExp ) 7 datatype BExp = B( bool ) 8 Less (AExp, AExp ) 9 Eq(AExp, AExp ) 10 Not ( BExp ) 11 And ( BExp ) 12 datatype Com = Skip 13 Assign (Var, AExp ) 14 Seq (Com, Com ) 15 If(BExp, Com, Com ) 16 While (BExp, Com ) To see how we might use these types, consider the expression (5 + x) < y: 1 var b := Less ( Plus (N (5), V("x")), V("y")); 1

15-414 Lecture 11 2 To encode a simple program: We would construct the type: while (5 + x) < y do x := x + 1 1 var b := Less ( Plus (N (5), V("x")), V("y")); 2 var c := While (b, Assign ("x", Plus (V("x"), N (1) ))); Inference Rules Now let s implement the big-step semantics. Recall that our semantics defined an environment as a mapping from variables to their values, and a configuration as a pair consisting of a command and an environment. Additionally, we discussed derivations, which we can model as a finite sequence of configurations. 1 type Env = map < Var, Val > 2 type Config = ( Com, Env ) 3 type Derivation = seq < Config > Let s start implementing the inference rules themselves by covering the rules for the expressions. Const n, σ a n Var x, σ a σ(x) Add a 1, σ a n 1 a 2, σ a n 2 a 1 + a 2, σ a n 1 + Z n 2 We know that evaluating expressions always terminates, so we can model these rules with a single function. We ll use a match expression to split into rules based on the abstract syntax of the expression passed in. 1 function bigstep_ aexp ( a: AExp, s: Env ): Val 2 { 3 match (a) { 4 case N( n) => n 5 case V( x) => if x in s then s[ x] else 0 6 case Plus (a0, a1) => bigstep_aexp (a0, s) + bigstep_aexp (a1, s) 7 } 8 } The only aspect of this function that may be unexpected is the rule for variable evaluation. Previously, we ve assumed that the environment will have a mapping for every possible variable, but we made no assumptions about what the mapping could be. Here we first check to see that the referenced variable is in the environment, and if it isn t, the function returns zero. This introduces an additional assumption that we haven t made before, which is that all variables start off initialized to zero. Alternatively, we could have written the function with a precondition that requires any environment passed in to contain all mappings for all variables mentioned in the expression: 2

15-414 Lecture 11 3 1 function bigstep_ aexp ( a: AExp, s: Env ): Val 2 requires forall v: Var :: v in free_ vars ( a) == > v in s 3 { 4 match (a) { 5 case N( n) => n 6 case V( x) => if x in s then s[ x] else 0 7 case Plus (a0, a1) => bigstep_aexp (a0, s) + bigstep_aexp (a1, s) 8 } 9 } We would then need to define a function free vars, and whenever we called bigstep aexp, we d need to make sure that this precondition were met. To simplify things today, we ll use the former approach, and assume everything starts out initialized to zero. Moving on to the rules for BExp, we have the following rules. True true, σ b true False false, σ b false Eq a 1, σ a n 1 a 2, σ a n 2 a 1 = a 2, σ a n 1 = Z n 2 Less a 1, σ a n 1 a 2, σ a n 2 a 1 < a 2, σ a n 1 < Z n 2 NotTrue b, σ b true b, σ b false NotFalse b, σ b false b, σ b true And b 1, σ b v 1 b 2, σ b v 2 b 1 b 2, σ b v 1 v 2 Using the same reasoning we did for the arithmetic expressions, we arrive at the following function. There are no surprises here, we use the same approach as before. 1 function bigstep_ bexp ( b: BExp, s: Env ): bool 2 { 3 match (b) { 4 case B( v) => v 5 case Less (a0, a1) => bigstep_aexp (a0, s) < bigstep_aexp (a1, s) 6 case Eq(a0, a1) => bigstep_aexp (a0, s) == bigstep_aexp (a1, s) 7 case Not (op) =>! bigstep_bexp (op, s) 8 case And (b0, b1) => bigstep_bexp (b0, s) && bigstep_bexp (b1, s) 9 } 10 } 3

15-414 Lecture 11 4 Recall the rules for big-step evaluation of commands: Asgn a, σ a n x := a, σ σ[x n] Skip skip, σ σ Seq c 1, σ 1 σ 1 c 2, σ 1 σ 2 c 1 ; c 2, σ 1 σ 2 IfTrue b, σ b true c 1, σ σ 2 if b then c 1 else c 2, σ σ 2 IfFalse b, σ b false c 2, σ σ 2 if b then c 1 else c 2, σ σ 2 WhileFalse b, σ b false while b do c, σ σ WhileTrue b, σ b true c, σ σ while b do c, σ σ while b do c, σ σ If we were to implement these in the same way that we did for expressions, we would run into trouble with termination. Unlike expressions, commands need not terminate, so the function that we wrote would not be defined on certain inputs. Instead, we ll define the semantics for commands using a predicate, which you ll recall is just a Boolean-valued function. The predicate will take as arguments a command c, environments s and s, and a natural number k. We want the predicate to return true whenever evaluating c starting in s reduces to s with a derivation containing at most k steps. 1 predicate bigstep ( c: Com, s: Env, s : Env, k: nat ) 2 decreases c,k 3 { 4 match (c) { 5 case Skip => 6 k == 1 && s == s 7 case Assign (v, a) => 8 k == 1 && s[ v := bigstep_ aexp (a, s)] == s 9 case Seq (c1, c2) => 10 exists k : nat, s : Env :: 11 0 < k < k && 12 bigstep (c1, s, s, k ) && 13 bigstep (c2, s, s, k-k ) 14 case If(b, c1, c2) => 15 if( bigstep_bexp (b, s)) then 16 bigstep (c1, s, s, k) 17 else 18 bigstep (c2, s, s, k) 19 case While (b, c ) => 20 if( bigstep_bexp (b, s)) then 21 exists s : Env, k : nat :: 0 < k < k && 22 bigstep (c, s, s, k ) && 23 bigstep (c, s, s, k-k ) 24 else 25 k == 1 && s == s 26 } 27 } Unlike our functions expression semantics, this function does not have a case for each rule. Instead, it has a case for each syntactic form of Com, and composes recursive calls in a way that faithfully 4

15-414 Lecture 11 5 accounts for each rule. Notice that our termination metric refers to two variables, c and k. This is necessary because some cases only reduce the size of the expression (e.g., if), and some only reduce the size of the derivation (e.g., while) when they recursively call the semantics. Using the semantics Proving facts about specific programs Now that we have an implementation of the semantics, we can get started applying them to reason about Imp programs. First we ll take a look at proving things about specific programs given as Com types. Suppose that we re given the program: while 0 < x do x := x 1 and we d like to show that staring out in an environment σ = [x 2] leads to a final environment σ = [x 0]. Although this might be obvious to us by simple inspection of the program, to prove that this is the case, we ll need to make use of the semantics. Formally, we d like to show that, while 0 < x do x := x 1, [x 2] [x 0] Before seeing how to do this using Dafny, let s review how to do it on paper first. Recall that we can prove specific derivations by finding a proof tree that applies the relevant inference rules for our judgement. In this case, we know that the last rule applied in such a tree will be WhileTrue, because the command is a while loop and in the environment σ = [x 2], 0 < x, σ b true. To the root of the proof tree will look as follows: WhileTrue T 1 T 2 T 3 while 0 < x do x := x 1, [x 2] [x 0] By inspecting the rule WhileTrue, we know that T 1 will need to end with the conclusion 0 < x, [x 2] b true, T 2 will need to end with the conclusion x := x 1, [x 2] σ, and T 3 will need to end with while 0 < x do x := x 1, σ [x 0]. To finish the proof, we need to construct T 1, T 2, T 3. We now need to derive the tree T 1, which is an inequality comparison. We see that: Less Const 0, [x 2] a 0 0 < x, [x 2] b true Var x, [x 2] a 2 Moving on to T 2, which is an assignment, we need to apply Asgn. By inspecting the command and inital environment, we know that σ, the final environment, must be equal to [x 1]. We justify this as follows: Asgn Plus Var Const x, [x 2] a 2 1, [x 2] a 1 x 1, [x 2] a 1 x := x 1, [x 2] [x 1] 5

15-414 Lecture 11 6 Moving on to T 3, we now know that σ = [x 1]. Again, we re looking at a loop, and we know that 0 < x, [x 1] b true, so T 3 will be rooted by WhileTrue. WhileTrue T 4 T 5 T 6 while 0 < x do x := x 1, [x 1] [x 0] We re going to re-use much of our work from T 1, T 2, and T 3, keeping in mind that the initial environment has changed to [x 1]. Starting with T 4 : Less Const 0, [x 1] a 0 0 < x, [x 1] b true Var x, [x 1] a 1 Continuing with T 5, we find that the next environment is [x 0]: Asgn Plus Var Const x, [x 1] a 1 1, [x 1] a 1 x 1, [x 1] a 0 x := x 1, [x 1] [x 0] Now that we get to T 6, we have to re-consider the while loop. We won t apply WhileTrue this time, because the initial environment maps x to 0. Now we can apply WhileFalse: Asgn Const Var 0, [x 0] a 0 0, [x 0] a 0 Less 0 < x, [x 0] b false while 0 < x do x := x 1, [x 0] [x 0] This completes the proof, because there are no further assumptions that need to be discharged. Now we ll see how to do this in Dafny. We prove things about functions and predicates in Dafny using lemmas. You can think of lemmas as methods that are not meant to be compiled, so their utility draws from the specifications that we give them. Dafny lemmas can be later invoked to prove to the verifier that a particular property holds for some objects. For the present example, recall that we want to show that, while 0 < x do x := x 1, [x 2] [x 0] In our implementation, bigstep represents the relation, and we represent environments as maps, so [x 2] is represented by map["x" := 2] and [x 0] by map["x" := 0]. The program that we re reasoning about is a value of type Com, which we can write as: 1 While ( Less (N (0), V("x")), Assign ("x", Plus (V("x"), N( -1)))) Additionally, from our earlier proof, we know that this program evaluates on its initial environment in three steps (including the exit from the loop). Putting this together, our lemma will start out with: 6

15-414 Lecture 11 7 1 lemma loop_ to_ zero () 2 ensures bigstep ( While ( Less (N (0), V("x")), Assign ("x", Plus (V("x"), N( -1)))), 3 map ["x" := 2], 4 map ["x" := 0], 5 3) Notice that we use the postcondition of the lemma, specified using the ensures syntax, to state the property that we re trying to prove. If we had any assumptions or premises to work with, i.e., if we were proving a statement of the form F G, then we could have used a requires annotation to specify this in our lemma. We ll now write the proof of this property in the body of the lemma, much as we d write a method. But we ll start out by defining some variables as shorthand for the main components of our property: 1 var c := While ( Less (N (0), V("x")), Assign ("x", Plus (V("x"), N( -1)))); 2 var b := Less (N (0), V("x")); 3 var body := Assign ("x", Plus (V("x"), N( -1))); 4 var a := Plus (V("x"), N( -1)); 5 var s := map ["x" := 2]; 6 var s := map ["x" := 0]; We re now ready to begin proving the lemma. To prove this property, we need to show that the rules, which we encoded in the predicate bigstep, give us a way to reach our desired conclusion. Our lemma has no requires annotations, so we don t have any premises to start out with other than true. This means we we need to show that: true ( while 0 < x do x := x 1, [x 2] [x 0]) To do this, we ll use a calculational proof. You ve seen these before, but probably not named as such: starting from an initial premise (in our case, true), we ll provide a chain of implications that leads to the desired conclusion. You ve probably written proofs of the form: P F 1. Q Dafny has a special syntax for calculational proofs, which looks like the following: 1 calc == > { 2 P; 3 { justification } 4 F1; 5 { justification } 6... 7 { justification } 8 Q; 9 } The symbol ==> tells Dafny that we re going to show a sequence of right-implications. The justifications are optional when Dafny is able to prove that each step follows from its predecessor, but if this is not the case, then you may need to fill these in with appropriate assert statements or call existing lemmas to help Dafny prove your claim. Calculational proofs in Dafny are somewhat generic, so we could have also used the symbol <==, which would reverse the chain using left-implications to work from the conclusion back to the 7

15-414 Lecture 11 8 premise: 1 calc <== { 2 Q 3 { justification } 4 Fn 5 { justification } 6... 7 { justification } 8 P 9 } When we worked out the proof for the current example by hand, we actually proceeded backwards from the conclusion to applications of axioms. We ll follow that example when we work this in Dafny, making use of calc <== to do so. Starting with the conclusion bigstep(c, s, s, 3), which corresponds to the root of our proof tree from before, we know that x, [x 2] 2, so the head of the loop (0 < x) evaluates to true. We also know that to reach this conclusion, we ll need to look at the part of the semantics that deals with while loops: 1 case While (b, c ) => 2 if( bigstep_bexp (b, s)) then 3 exists s : Env, k : nat :: 0 < k < k && 4 bigstep (c, s, s, k ) && 5 bigstep (c, s, s, k-k ) 6 else 7 k == 1 && s == s In particular, the branch of the if expression corresponding to the positive evaluation of the guard b. This says that to show big-step evaluation from s to s in 3 steps, we need to show the existence of an intermediate state s and natural number k that takes the body from s to s in k steps, and then takes the rest of the loop from s to s in k-k steps. Because the body subtracts 1 from x, and x takes the value 2 in s, we know that the intermediate state must be s["x" := 1]. Furthermore, the rule for assignment consumes 1 step, so we know that k must be 1. This lets us write: 1 calc <== { 2 bigstep (c, s, s, 3); 3 // def. of while 4 bigstep_ bexp (b, s) && 5 bigstep (body, s, s["x" := 1], 1) && 6 bigstep (c, s["x" := 1], s, 2); 7 } Although Dafny doesn t complain about any of the steps in our calculation, we re not done yet, as Dafny still doesn t agree that the lemma s postcondition is satisfied. This means that we need to justify the last line of our calculation. In particular, we need to show that evaluating the loop body starting in s leads to a new state where x takes the value 1, and why it s safe to assume that executing c in that new state leads to s in 2 steps. We get the former from the semantics of the plus operator (which, recall, is implemented in bigstep aexp), and the latter we ll need to justify in subsequent steps: 8

15-414 Lecture 11 9 1 calc <== { 2 bigstep (c, s, s, 3); 3 // def. of while 4 bigstep_ bexp (b, s) && 5 bigstep (body, s, s["x" := 1], 1) && 6 bigstep (c, s["x" := 1], s, 2); 7 // def. of plus, def. of while 8 bigstep_aexp (a, s) == 1 && bigstep (c, s["x" := 1], s, 2); 9 } Again, we need to prove the last line, as Dafny cannot do it on its own. We ll begin with the second conjunct, as it is the more difficult of the two. Using reasoning similar to what we did for the second line, we can write: 1 calc <== { 2 bigstep (c, s, s, 3); 3 // def. of while 4 bigstep_ bexp (b, s) && 5 bigstep (body, s, s["x" := 1], 1) && 6 bigstep (c, s["x" := 1], s, 2); 7 // def. of plus, def. of while 8 bigstep_aexp (a, s) == 1 && bigstep (c, s["x" := 1], s, 2); 9 // def. of while 10 bigstep_bexp (b, s["x" := 1]) && 11 bigstep (body, s["x" := 1], s, 1) && 12 bigstep (c, s, s, 1); 13 } Again, we used the true case of the definition of semantics for while to derive this step. We know that executing the body x := x 1 in the environment [x 1] leads to [x 0], which is the environment s mentioned in our goal. Notice that Dafny accepts this line in the calculational proof without asking for justification of the conjunct bigstep aexp(a, s) == 1. This means that we don t need to prove it Dafny can do so on its own, in contrast to our by-hand proof, where completeness required a worked-out justification. Continuing just as before, Dafny isn t able to prove that evaluating the body results in the postenvironment s. Previously, we dealt with this by telling Dafny that bigstep aexp(a, s) == 1. In this case, we point out that bigstep aexp(a, s["x" := 1]) == 0: 9

15-414 Lecture 11 10 1 calc <== { 2 bigstep (c, s, s, 3); 3 // def. of while 4 bigstep_ bexp (b, s) && 5 bigstep (body, s, s["x" := 1], 1) && 6 bigstep (c, s["x" := 1], s, 2); 7 { assert bigstep_ aexp (a, s) == 1; } 8 // def. of plus, def. of while 9 bigstep_aexp (a, s) == 1 && bigstep (c, s["x" := 1], s, 2); 10 // def. of while 11 bigstep_bexp (b, s["x" := 1]) && 12 bigstep (body, s["x" := 1], s, 1) && 13 bigstep (c, s, s, 1); 14 // def. of assign, seq 15 bigstep_ aexp (a, s[" x" := 1]) == 0; 16 } At this point, it seems like we ve given Dafny everything it needs to know, but it still thinks we re not done, as it tells us that the lemma postcondition might not hold. Let c = while 0 < x do x := x 1. Looking at the body of the lemma, we ve just shown that: x 1, [x 1] a 0 0 < x, [x 1] b true x := x 1, [x 1] [x 0] c, [x 0] a [x 0] c, [x 1] [x 0] 0 < x, [x 2] b true x := x 1, [x 2] [x 1] c, [x 1] a [x 0] c, [x 2] [x 0] The conclusion of this calculation corresponds to the postcondition of our lemma. However, Dafny can only use it to conclude that the postcondition holds if it believes that the premise is true. We can point this out by adding a corresponding assertion after the calculation: 1 calc <== { 2 bigstep (c, s, s, 3); 3 // def. of while 4 bigstep_ bexp (b, s) && 5 bigstep (body, s, s["x" := 1], 1) && 6 bigstep (c, s["x" := 1], s, 2); 7 { assert bigstep_ aexp (a, s) == 1; } 8 // def. of plus, def. of while 9 bigstep_aexp (a, s) == 1 && bigstep (c, s["x" := 1], s, 2); 10 // def. of while 11 bigstep_bexp (b, s["x" := 1]) && 12 bigstep (body, s["x" := 1], s, 1) && 13 bigstep (c, s, s, 1); 14 // def. of assign, seq 15 bigstep_ aexp (a, s[" x" := 1]) == 0; 16 } 17 assert bigstep_ aexp (a, s[" x" := 1]) == 0; Alternatively, we could add another line to the calculation that Dafny will certainly believe to be true, i.e., true. This should force it to attempt a proof that the first real line of the calculation is valid. Putting all of this together, the full lemma becomes: 10

15-414 Lecture 11 11 1 lemma loop_ to_ zero () 2 ensures bigstep ( While ( Less (N (0), V("x")), Assign ("x", Plus (V("x"), N( -1)))), 3 map ["x" := 2], 4 map ["x" := 0], 5 3) 6 { 7 var c := While ( Less (N (0), V("x")), Assign ("x", Plus (V("x"), N( -1)))); 8 var b := Less (N (0), V("x")); 9 var body := Assign ("x", Plus (V("x"), N( -1))); 10 var a := Plus (V("x"), N( -1)); 11 var s := map ["x" := 2]; 12 var s := map ["x" := 0]; 13 14 calc <== { 15 bigstep (c, s, s, 3); 16 // def. of while 17 bigstep_ bexp (b, s) && 18 bigstep (body, s, s["x" := 1], 1) && 19 bigstep (c, s["x" := 1], s, 2); 20 { assert bigstep_ aexp (a, s) == 1; } 21 // def. of plus, def. of while 22 bigstep_aexp (a, s) == 1 && bigstep (c, s["x" := 1], s, 2); 23 // def. of while 24 bigstep_bexp (b, s["x" := 1]) && 25 bigstep (body, s["x" := 1], s, 1) && 26 bigstep (c, s, s, 1); 27 // def. of assign, seq 28 bigstep_ aexp (a, s[" x" := 1]) == 0; 29 true ; 30 } 31 } Proving semantic properties Now let s return to the fact that the big-step semantics for Imp are deterministic, which we discussed in lecture. Recall that we proved the property: σ, σ 1, σ 2, c.( c, σ σ 1 c, σ σ 2 ) σ 1 = σ 2 In other words, for a given command, if we begin evaluating in any environment, then the environment we end with (if any) is unique. We proved this by induction on derivations, because induction on syntax is not well-founded. To prove this using our Dafny semantics, we ll begin by writing the lemma: 1 lemma bigstep_determ (c: Com, s0: Env, s1: Env, s1 : Env, k1: nat, k2: nat ) 2 requires bigstep (c, s0, s1, k1) 3 requires bigstep (c, s0, s1, k2) 4 ensures s1 == s1 5 ensures k1 == k2 There are several differences between this lemma from the previous one. First of all, bigstep determ takes arguments. In particular, a subset of its arguments correspond to the universally-quantified variables σ, σ 1, σ 2, and c from the statement we re trying to prove. When lemmas take arguments 11

15-414 Lecture 11 12 that are mentioned in their postconditions, they have to prove that the postcondition holds for whatever values are passed in as long as they satisfy the lemma s preconditions, which is like making a universal statement. Second, bigstep determ has two requires annotations, which together correspond to the antecedent of our proof goal. The last two arguments to bigstep determ correspond to the length of the derivations that we assume to exist. As we will see, including these as arguments gives us a well-founded induction later on. Alternatively, we could have defined a predicate: 1 predicate bigstep_ deriv ( c: Com, d: Derivation ) We could define bigstep big-step semantics, i.e., deriv to return true whenever d is a valid derivation of c under the 1 d [0].0 == c && 2 forall i :: 0 <= i < d -1 == > bigstep (d[i].0, d[i].1, d[i +1].1, d -i) We might then have written the lemma: 1 lemma bigstep_ determ ( c: Com, d: Derivation, d : Derivation ) 2 requires 0 < d && 0 < d 3 requires c == d [0].0 && c == d [0].0 4 requires bigstep_ deriv (c, d) 5 requires bigstep_ deriv (c, d ) 6 ensures d[ d -1].1 == d [ d -1].1 7 ensures d == d However, to avoid introducing new predicates and functions, we ll continue with the proof using our existing definitions. We ll proceed by induction, using a match on c to guide our coverage of the possible derivations. Starting with the base case corresponding to skip: 1 match (c) { 2 case Skip => 3 assert s0 == s1 == s1 ; Moving to assignment, we add the case: 1 case Assign (v, a) => 2 assert s0[ v := bigstep_ aexp (a, s0)] == s1 == s1 ; Notice that Dafny accepts this assertion. In lecture, we needed to prove that evaluation of expressions is deterministic. Dafny can deduce this automatically, which you can check by writing the lemma that verifies without assistance: 1 lemma aexp_determ (a: AExp, s: Env, n1: Val, n2: Val ) 2 requires bigstep_ aexp (a, s) == n1 3 requires bigstep_ aexp (a, s) == n2 4 ensures n1 == n2 5 {} The case for Seq is a bit more involved. Because we can assume that c 1 ; c 2, σ 0 σ 1, there must 12

15-414 Lecture 11 13 be a derivation that looks like: T 1 T 2 Seq c 1, σ 0 σ 0 c 2, σ 0 σ 1 c 1 ; c 2, σ 0 σ 1 Similarly for the second evaluation: Seq T 3 c 1, σ 0 σ 0 c 1 ; c 2, σ 0 σ 1 T 4 c 2, σ 0 σ 1 We can apply the inductive hypothesis on T 1, T 2, T 3, and T 4 to conclude that σ 0 = σ 0 σ 1 = σ 1. To write this in Dafny, we say the following: 1 case Seq (c1, c2) => 2 var s0, k1 : 0 < k1 < k1 && 3 bigstep (c1, s0, s0, k1 ) && 4 bigstep (c2, s0, s1, k1 -k1 ); 5 var s0, k2 : 0 < k2 < k2 && 6 bigstep (c1, s0, s0, k2 ) && 7 bigstep (c2, s0, s1, k2 -k2 ); 8 bigstep_determ (c1, s0, s0, s0, k1, k2 ); 9 assert s0 == s0 ; 10 bigstep_determ (c2, s0, s1, s1, k1 -k1, k2 -k2 ); 11 assert s1 == s1 ; We use some new syntax here, namely the assign-such-that statement: 1 var x : p( x) and thus If p is a predicate, then you should think of this statement as returning an arbitrary value for x, such that it satisfies p(x). We use this to reason about the intermediate environments s0 (corresponding to σ 0 ) and s0 (corresponding to σ 0 ), as well as the step indices k1 and k2 needed by bigstep. In order to use the assign-such-that statement, Dafny must be able to determine that at least one value satisfying the right hand side exists. In this case, we know that such values exist because we know that bigstep(c,s0,s1,k1) and bigstep(c,s0,s1,k2) are true, and the semantic definition for Seq is: 1 case Seq (c1, c2) => 2 exists k : nat, s : Env :: 3 0 < k < k && bigstep (c1, s, s, k ) && bigstep (c2, s, s, k-k ) The next part of the proof for Seq also uses a new concept. Namely, it calls the lemma recursively: 1 bigstep_determ (c1, s0, s0, s0, k1, k2 ); Important: This is how we apply the inductive hypothesis in Dafny. Notice that we re applying this to smaller derivations than the ones our current preconditions give us: we know that 0 < k1 < k1 and 0 < k2 < k2. By making this call on smaller arguments, the postcondition given by the lemma is now available to Dafny as an assertion would be. The following line that asserts s0 == s0 points this out for the reader s benefit, but is not strictly necessary; however, it is considered 13

15-414 Lecture 11 14 good form in this course to make your proofs explicit and easy to follow. We close out the case for Seq by making another inductive call on the second evaluation. The next case covers if commands. Like the previous case, it makes an inductive call for each branch of the statment: 1 case If(b, c1, c2) => 2 if( bigstep_bexp (b, s0)) { 3 bigstep_determ (c1, s0, s1, s1, k1, k2); 4 assert s1 == s1 ; 5 } else { 6 bigstep_determ (c2, s0, s1, s1, k1, k2); 7 assert s1 == s1 ; 8 } Finally, we come to while. As we ve discussed in lecture, semantically a while loop is like an if command composed with a seq. In lecture we said that in this case we could reason as follows. If the guard evaluates to true, then we can assume derivations: WhileTrue T 1 b, σ 0 b true T 2 T 3 c, σ 0 σ 0 while b do c, σ 0 σ 1 while b do c, σ 0 σ 1 WhileTrue T 4 b, σ 0 b true T 5 T 6 c, σ 0 σ 0 while b do c, σ 0 σ 1 while b do c, σ 0 σ 2 Applying the inductive hypothesis twice leads us to conclude that σ 0 = σ 0 and σ 1 = σ 1. As this is very similar to the case for Seq, our proof in Dafny follows form: 1 case While (b, c ) => 2 if( bigstep_bexp (b, s0)) { 3 var s0, k1 : 0 < k1 < k1 && 4 bigstep (c, s0, s0, k1 ) && 5 bigstep (c, s0, s1, k1 -k1 ); 6 var s0, k2 : 0 < k2 < k2 && 7 bigstep (c, s0, s0, k2 ) && 8 bigstep (c, s0, s1, k2 -k2 ); 9 bigstep_determ (c, s0, s0, s0, k1, k2 ); 10 assert s0 == s0 ; 11 bigstep_determ (c, s0, s1, s1, k1 -k1, k2 -k2 ); 12 assert s1 == s1 ; We use assign-such-that, with a such-that condition coming directly from the big-step definition of while, to obtain intermediate states s0, s0 and step indices k1, k2. We then apply the inductive hypothesis twice by making two recursive calls to the lemma, and conclude the WhileTrue case. WhileFalse is straigtforward, as it has the same semantics as skip. 1 } else { 2 assert s0 == s1 == s1 ; 3 } Although we ve completed all of the relevant cases, Dafny does not accept our proof. The problem is well-foundedness, which requires providing a termination metric that the verifier will understand. 14

15-414 Lecture 11 15 For this proof, we need a compound termination metric, as one case fails to decrease the step indices k1, k2 (namely, the case for If), and another fails to decrease the command (i.e., While). We combine all three for our termination metric, on which Dafny will impose a lexicographic ordering. Putting all this together, we have the following lemma with proof: 1 lemma bigstep_determ (c: Com, s0: Env, s1: Env, s1 : Env, k1: nat, k2: nat ) 2 decreases c, k1, k2 3 requires bigstep (c, s0, s1, k1) 4 requires bigstep (c, s0, s1, k2) 5 ensures s1 == s1 6 ensures k1 == k2 7 { 8 match (c) { 9 case Skip => 10 assert s0 == s1 == s1 ; 11 case Assign (v, a) => 12 assert s0[ v := bigstep_ aexp (a, s0)] == s1 == s1 ; 13 case Seq (c1, c2) => 14 var s0, k1 : 0 < k1 < k1 && 15 bigstep (c1, s0, s0, k1 ) && 16 bigstep (c2, s0, s1, k1 -k1 ); 17 var s0, k2 : 0 < k2 < k2 && 18 bigstep (c1, s0, s0, k2 ) && 19 bigstep (c2, s0, s1, k2 -k2 ); 20 bigstep_determ (c1, s0, s0, s0, k1, k2 ); 21 assert s0 == s0 ; 22 bigstep_determ (c2, s0, s1, s1, k1 -k1, k2 -k2 ); 23 assert s1 == s1 ; 24 case If(b, c1, c2) => 25 if( bigstep_bexp (b, s0)) { 26 bigstep_determ (c1, s0, s1, s1, k1, k2); 27 assert s1 == s1 ; 28 } else { 29 bigstep_determ (c2, s0, s1, s1, k1, k2); 30 assert s1 == s1 ; 31 } 32 case While (b, c ) => 33 if( bigstep_bexp (b, s0)) { 34 var s0, k1 : 0 < k1 < k1 && 35 bigstep (c, s0, s0, k1 ) && 36 bigstep (c, s0, s1, k1 -k1 ); 37 var s0, k2 : 0 < k2 < k2 && 38 bigstep (c, s0, s0, k2 ) && 39 bigstep (c, s0, s1, k2 -k2 ); 40 bigstep_determ (c, s0, s0, s0, k1, k2 ); 41 assert s0 == s0 ; 42 bigstep_determ (c, s0, s1, s1, k1 -k1, k2 -k2 ); 43 assert s1 == s1 ; 44 } else { 45 assert s0 == s1 == s1 ; 46 } 47 } 48 } 15