Programming by Contract CSE 111 3/23/2016 Copyright W. Howden 1 Contracts • Client and Supplier • Contract: – if client satisfies certain preconditions, then the supplier will produce a product satisfying certain postconditions 3/23/2016 Copyright W. Howden 2 Design by Contract • All of the components (classes, methods and procedures) have a stated contract that they will fulfill • Benefits – reusable components – correct interfaces when integrating components – predictable results 3/23/2016 Copyright W. Howden 3 Software Contracts • Methods and procedures • precondition: required input properties • postconditions: guaranteed output properties, provided precondition is satisfied • Classes • method contracts • class invariants – properties that are true on completion of constructor and true before and after each method application 3/23/2016 Copyright W. Howden 4 Assertions • Expected properties of program state (values of variables) • Used to specify preconditions, postconditions and class invariants • Also used for intermediate assertions – statements about what is expected to be true at intermediate points in a programs execution 3/23/2016 Copyright W. Howden 5 Assertions and Preconditions • Assertion should appear as first line in a method • Asserts that whenever execution reaches this location, this property must be true 3/23/2016 Copyright W. Howden 6 Assertions and Postconditions • Executable dynamic testing assertions should appear before each return statement • State that the asserted property is true whenever the program returns from that location • Static assertions can be put after the return points 3/23/2016 Copyright W. Howden 7 Assertions and Class Invariants • Should appear as postconditions in constructors and all other methods • Should appear as preconditions in all methods • For public methods, input should be checked directly and not by an assertion – assertions can be turned off after debugging 3/23/2016 Copyright W. Howden 8 Assertion Languages • Static analysis – non-executable • informal prose statements or formal statements in a formal logic • Dynamic analysis • executable expressions that return T or F • executed during testing, but turned off during production use 3/23/2016 Copyright W. Howden 9 Validating Contracts • Static: – prove that if the precondition holds then when the program reaches termination, the postcondition holds • Dynamic: – use executable assertions for pre and postconditions. Run tests. If a postcondition does not hold when the precondition holds, the contract is not valid. If a precondition does not hold either a test is faulty or the program and/or precondition need to be corrected 3/23/2016 Copyright W. Howden 10 Lecture Topics • Static analysis(verification) of method/procedure and class contracts – sometimes called “proofs of correctness” • Dynamic analysis (testing) with Java assertions – pre and postconditions – class invariants, etc. 3/23/2016 Copyright W. Howden 11 Why Learn About Proofs? • For new complex logic, invaluable check against reasoning flaws • Even if never formally done, provides a way of looking at a program that can guide code reading, or informal analysis • Are the means of proving properties of standard algorithms (e.g. sorting) 3/23/2016 Copyright W. Howden 12 Beams and Girders • Proved algorithms and state models are the “beams and girders” of a software construction project • These are the pieces with proved properties that we can rely on • Abstract, so must be translated into programming language code • Construction involves using these to build a specific application product 3/23/2016 Copyright W. Howden 13 Informal and Formal Proofs • Informal • “precise” state assertions, but not in a formal logical language • similar to the language of informal mathematics • Formal • uses formal semantics for programming language statements • very detailed, and hard to construct 3/23/2016 Copyright W. Howden 14 Partial Correctness and Termination • Suppose that A1 is the precondition for a method m and An is the postcondition. • Correctness: m is totally correct if it is partially correct and it always terminates • Definition of partial correctness: • if A1 is true at the beginning of an execution and if m is executed and terminates, then An will be true at the end of the execution 3/23/2016 Copyright W. Howden 15 Proof Technique • Add intermediate assertions Ai to the method. – breaks down proof into easily handled chunks • Make sure that each loop has at least one intermediate assertion • For each pair of assertions (X,Y) where there is a subpath p from X to Y, which has no other assertions on it, prove its verification condition: “if X is true at the beginning of p, and p is executed, then Y will be true at the end of p” 3/23/2016 Copyright W. Howden 16 Proof Method Validity? • Consider any execution path P • P can be broken up into a sequence of subpaths p, which go from one assertion to the next with no assertion in between • If all of the verification conditions for the subpaths are true, we can join them together to provide a proof for the whole path 3/23/2016 Copyright W. Howden 17 Verification Example • Program multiplies two numbers x and y by adding up y x times • Input (precondition) assertion Pre, output (postcondition) assertion Post, intermediate loop invariant assertion IA 3/23/2016 Copyright W. Howden 18 3/23/2016 Copyright W. Howden 19 Verification Conditions for Example • Prove that 1. if Pre is true then IA will be true 2. if IA is true and Count = 0 then Post will be true 3. If IA is true and Count 0 then IA will be true 3/23/2016 Copyright W. Howden 20 Proof of Verification Condition 3 Suppose that we are at IA and x*y = Product + Count*Y is true. If we go around the loop, Product is incremented by y, and Count is decremented by 1. This means the expression on the right would get larger by y, and then smaller by y, leaving its value the same. So if the relationship was true before the loop, it will also be true after the loop. 3/23/2016 Copyright W. Howden 21 Termination Proofs • If there are no loops in m, and all called methods are terminate, then m must terminate • For loops, look for a counter that approaches an upper (lower) limit and is incremented (decremented) on every path through the loop 3/23/2016 Copyright W. Howden 22 Termination Proof for Multiply Example • Count initialized to x >=0 • Loop terminates if Count == 0 • For each iteration Count is decremented by 1 so loop must terminate • Note: • if Precondition x>=0 is removed from example and input x can be negative, algorithm will not always terminate (i.e. when x < 0), but program is still partially correct 3/23/2016 Copyright W. Howden 23 Termination Bug Example • Application: MS Zune MM player • Failure: December 31, 2008, device would not boot – infinite loop • Faulty procedure – Input = day count relative to origin year 1980. Code suppose to figure out current year. – e.g. if days = 500 it must be 1982 3/23/2016 Copyright W. Howden 24 Zune Related Code year = ORIGINYEAR; /* = 1980 */ while (days > 365 ) { if isLeapYear(year)) { if (days > 366) { days -= 366; year += 1; } } else { days -= 365; year += 1; } } 3/23/2016 Copyright W. Howden 25 (Attempted) Termination Proof • Decremented counter: days • Termination condition: days <= 365 • When traversing loop, if year is a leap year, and days (remaining) is = 366, then the loop is traversed with no changes to the counter (days) • i.e. there is a path through the loop on which the counter is not decremented, 3/23/2016 Copyright W. Howden 26 Examples from DS • isMember() checks to see if DB in-memory vector has an entry with a given name. Our assertions capture what the program is supposed to do • deleteMember() is more complex 3/23/2016 Copyright W. Howden 27 isMember() with Verification Assertions public boolean isMember(String name) { /** Pre: for j = 0 to numberMembers-1 membersData[j].name != null /** numberMembers -1 >=0 for (int i=0; i <= numberMembers-1; ++i) { /** for j = 0 to i-1 membersData[j].name != name /** i <= numberMembers-1 if (name.equals(membersData[i].name)) { return true; /** Post 1: return = true /** for j = 0 to i-1 membersData[j].name != name /** membersData[i].name = name } /** return false; /** Post 2: return = false /** for j = 0 to numberMembers-1 membersData[j].name != name 3/23/2016 Copyright W. Howden 28 Verification of IsMember() • It is fairly simple to reason that for all pairs of assertions, if the first is true and you reach the next one, it is true also • Hence if the precondition holds, and the program terminates, the postcondition will • So the contract is valid • But we also need to consider termination 3/23/2016 Copyright W. Howden 29 Termination of isMember() • Termination is easy because the loop counts up to a limit from 0 and the precondition /** numberMembers -1 >=0 requires the limit to be larger than the initial value • Note: this precondition clause was added during analysis!! • I had not thought of this 3/23/2016 Copyright W. Howden 30 deleteMember() public boolean deleteMember(String name) { int i = 0; while (!(name.equals(membersData[i].name)) & (i <= numberMembers-1)) { ++i; } if (i <= numberMembers-1) { for (int j = i; j< numberMembers-1; ++j) { membersData[j]=membersData[j+1]; } --numberMembers; return true; } else { return false; } } 3/23/2016 Copyright W. Howden 31 deleteMember() Pre and Post Conditions notation: x’ means new value of x, versus original or previous value contents stands for the collection of items in the designated data structure Precondition /* for k = 1 to numberMembers-1 membersData[k] non null, membersData.name field is a string /* name is of type string /* numberMembers >=0 Postcondition /** if there exists some k, 1 <= k <= numberMembers – 1 such that memberData[k].name = name /** numberMembers’ = numberMembers – 1 and /** contents(memberData[j]’, 1<= j <= numberMembers-1’) = contents(memberData[j], 1<= j <= numberMembers) ~ memberData[k] /** if there does not exist some k, 1 <= k <= numberMembers – 1 such that memberData[k].name = name /** numberMembers’ = numberMembers /** for j = 1 to numberMembers-1, membersData[j]’ = membersData[j] /** return false 3/23/2016 Copyright W. Howden 32 Intermediate Assertions • All intermediate assertions except – return/exit assertion for second return – final assertion to summarize both return assertions 3/23/2016 Copyright W. Howden 33 public boolean deleteMember(String name) { int i = 0; while (!(name.equals(membersData[i].name)) & (i <= numberMembers-1)) { /** for j = 0 to i, name != membersData[j].name /** i <= numberMembers-1 /** for j = 0 to membersData – 1, membersData[j]’ = membersData[j] ++i; } if (i <= numberMembers-1) { /** for j = 0 to i-1, name != membersData[j].name /** i <= numberMembers-1 /** name == membersData[i].name for (int j = i; j< numberMembers-1; ++j) { membersData[j]=membersData[j+1]; /** for k = 1 to i-1, membersData[k]’ =membersData[k]; /** for k = i to j, membersData[k]’ = membersData[k+1] } /** for k = 1 to i-1, membersData[k]’ =membersData[k]; /** for k = i to numberMembers-2, membersData[k]’ = membersData[k+1] --numberMembers; return true; } else { } return false; } 3/23/2016 Copyright W. Howden 34 Sample Reasoning About First Pre-Return Assertion • First part follows directly from the intermediate assertion in the loop • Second part follows from the second intermediate assertion plus the following • if the loop was entered, then the final value of j on exit must have been numberMembers-2 • if we did not enter the loop then i must have been numberMembers-1 so it is vacuously true 3/23/2016 Copyright W. Howden 35 deleteMember() with Second Pre-Return Assertion public boolean deleteMember(String name) { int i = 0; while (!(name.equals(membersData[i].name)) & (i <= numberMembers-1)) { ++i; } if (i <= numberMembers-1) { for (int j = i; j< numberMembers-1; ++j) { membersData[j]=membersData[j+1]; } --numberMembers; return true; } else /** for j = 0 to numberMembers - 1, memberData[j].name != name { return false; } } 3/23/2016 Copyright W. Howden 36 Sample Reasoning about Second Pre-Return Assertion • If we have the case where i is not <= numberMembers-1 then it must be > numberMembers-1 • If we use the above along with the first while-loop intermediate assertion, we can get the second pre-return assertion 3/23/2016 Copyright W. Howden 37 Final “Intermediate” Assertions • Assertion to go just after last “}” /** return = true /** i <= numberMembers-1 /** membersData[i].name = name /** for k = 1 to i-1 membersData[k]’ =membersData[k]; /** for k = i to numberMembers-2 membersData[k]’ = membersData[k+1] /** numberMembers’ = nuumberMembers - 1; /* or /** return false /** not (i <= numberMembers-1) /** for k = 1 to numberMembers-1 membersData[k].name != name /** for k = 1 to i-1 membersData[k]’ =membersData[k]; /** numberMembers’ = numberMembers 3/23/2016 Copyright W. Howden 38 Continued Reasoning • The two pre return intermediate assertions imply the final intermediate assertion • The final intermediate assertion gives us the Postcondition 3/23/2016 Copyright W. Howden 39 Oops? • Consider the precondition /* for k = 1 to numberMembers-1 membersData[k] is non null, and membersData.name is a string /* name is of type string /* numberMembers >=0 • Now look at the first loop while (!(name.equals(membersData[i].name)) & (i <= numberMembers-1)) { ++i; } • It is possible to reference membersData[i] with i = numberMembers, and the precondition does not guarantee that it has a non-null value! • This is an actual bug that causes a failure 3/23/2016 Copyright W. Howden 40 Moral of the Story • Proofs of correctness forces you to think through the logic of a complex algorithm, but it is error prone for dealing with the details of an actual program • In this case, assume that all vectors are infinite, that we can compare a given value with a non-value, etc., and the problem goes away 3/23/2016 Copyright W. Howden 41 Dynamic Analysis • Use Java assertions and testing to verify contracts • assert Expression1 ; • assert Expression1 : Expression2 ; – If Expression1 is false then system will throw an error. Value of Expression2 is returned • can be turned off and on using a command line switch 3/23/2016 Copyright W. Howden 42 Inserting Assertions Preconditions • Precondition is first line of code, consisting of expression in input parameters • Public methods should check input and throw an illegal argument exception, rather than have this checked with an assertion • methods should protect themselves from bad data even after testing has been completed 3/23/2016 Copyright W. Howden 43 Inserting Assertions Postconditions • Should occur before each return • Use a function to facilitate the situation where there are multiple returns, each requiring the postcondtion 3/23/2016 Copyright W. Howden 44 Inserting Assertions – Class Invariants • True before and after each method • Insert in methods as postconditions • Class state changed by method call? – No: not necessary to check invariant at beginning of methods – Yes: (e.g. class has public class variables) then include invariant in methods as a precondition 3/23/2016 Copyright W. Howden 45 Using Assertions with Testing • Construct tests using methods such as – black box: normal and oddball inputs – coverage: make sure all statements or branches executed at least once • Do not have to manually observe outputs to determine if tests passed if we can rely on the postconditions to catch bad output 3/23/2016 Copyright W. Howden 46 DS Example - IsMember • No precondition assertions, but insert code to confirm validity of input data • For output check need to essentially rewrite the code inside a method called by the postcondition assertion – Define a method called noMatch(). 3/23/2016 Copyright W. Howden 47 Technical Details • Defined the method “noMatch()” inside a new inner class called memberDataProps • Want to only create the memberDataProps instance when assertions are turned on (say during testing) so create with an assignment inside a dummy assertion 3/23/2016 Copyright W. Howden 48 public boolean isMember(String name) { class memberDataProps { public boolean noMatch(memberData[] m, int length) { for (i= 0; length-1; ++i;) { if (name.equals(m[i].name)) return false; } return true; } } memberDataProps checker; assert ((checker = new memberDataProps()) != null); if (numberMembers-1 < 0) throw new IllegalArgumentException(“Out of range parameter”); for (int i=0; i <= numberMembers-1; ++i) { if membersData[i] == null throw new IllegalArgumentException(“Null param”); } for (int i=0; i <= numberMembers-1; ++i) { if (name.equals(membersData[i].name)) { assert name.equals(membersData[i].name); return true; } } assert checker.noMatch(membersData, numberMembers); return false; } 3/23/2016 Copyright W. Howden 49 Useful? • Will use of assertions here lead to defect detection? • Having to essentially rewrite the method inside the noMatch() method seems shaky. – maybe it would have the same bugs as the code 3/23/2016 Copyright W. Howden 50 deleteMember() • Could use postconditions that: • for false, check that vector items are the same, and numberMembers is unchanged • for true, check that the deleted element not in the list, and the contents are the same except for deleted element. Also numberMembers is decremented • Document expected loop bounds invariant. • With a good set of tests this is what will find the problem 3/23/2016 Copyright W. Howden 51 Assignment 11 • Choose two classes from your phase 1 that you are going to reuse (select non trivial examples) – Prove their correctness using informal assertions – Construct executable assertions and run tests against them 3/23/2016 Copyright W. Howden 52