Lecture 13: Programming by Contract

advertisement
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
Download