50.530: Software Engineering

Download Report

Transcript 50.530: Software Engineering

50.530: Software Engineering
Sun Jun
SUTD
Week 9: Hoare Logic
Testing is Limited
• Delta Debugging is based on testing.
• The bug localization methods are based on
testing.
• The specification mining methods are based
on testing.
• The dynamic race detection methods are
based on testing.
“Testing shows the presence, not the absence of bugs.”
---Dijkstra
a test which shows a bug
the initial state
C
A
the behaviors we wanted
the behaviors we have
Example
Initially: x = y = 0
thread 1 {
x = random.nextInt(0, 100000);
count++;
}
thread 2 {
if (x==3771) {
ERROR;
}
}
Testing would unlikely spot the error.
But humans do, why?
Program verification is to show the absence of
bugs based on logic.
Verification. Why?
• There are systems which simply cannot afford any
error, e.g., control software for nuclear plants, space
missions, high-speed trains, cars (e.g., for cruise
control), etc.
• Some systems are much more secure if they are
verified, e.g., many security problems are really
software bugs.
• All systems are better off if they could be verified.
Plus, the problem is really challenging and interesting.
Verification is Hard
Example: the Halting problem
• The problem is to determine, given any
program* and an input to the program,
whether the program will eventually halt
when run with that input.
• Turing proved no algorithm can exist which
will always correctly decide whether, for a
given arbitrary program and its input, the
program halts when run with that input.
*in a programming language which is equivalent to a Turing machine.
Example
float sumUp (float[] array, int length) {
float result = 0.0;
int i = 0 ;
while (i < length) {
result += array[i];
i++;
}
return result;
}
How do we prove this
program is correct?
What exactly do I want
this program to do?
Function Specification
• We do need a specification in order to verify.
• Assume the specification is based predicate
logic.
• A predicate is a Boolean function over
program state, i.e., an expression that returns
a Boolean value.
– e.g., x = 3, y > x, x>0 => y+z = w, s = sum(x1, x2,
x3), for all i in 0..n-1, a[i] > a[i-1]
Function Specification
• A function specification for a method usually
consists of a pre-condition and a postcondition.
• Example:
– given a semi-prime, your program outputs its
prime factors
What if the pre-condition is not
satisfied, e.g., you are given a number
which is not a semi-prime?
Correctness
• Partial Correctness: If the pre-condition is
satisfied and the method terminates, it is
guaranteed to satisfy the post-condition.
• Total Correctness: If the pre-condition is
satisfied, it is guaranteed that the method
terminates and satisfies the post-condition.
Function Specification: Example
{length >= 0 && array.length = length}
float sumUp (float[] array, int length) {
float result = 0.0;
pre-condition
int i = 0 ;
while (i < length) {
result += array[i];
i++;
}
return result;
}
{result = sum(array[j] for all j in 0..length)}
post-condition
C. A. R. Hoare, Communications of the ACM 1969
AN AXIOMATIC BASIS FOR
COMPUTER PROGRAMMING
Hoare Triples
{Pre}Program{Post}
If we start in a state
where Pre is true and
execute Program, then
Program will terminate
in a state where Post is
true.
Examples:
{true} x:=5 {x=5}
{x=y} x := x+3 {x=y+3}
{x=a} if(x<0)then x:=-x {x=|a|}
Questions
Are the following Hoare triples?
{x<0}
while (x != 0) {
x:= x-1;
}
{true}
{false} x := 3 {x = 8}
{x>-1} x:=x*2+3 {x>1}
Strongest Post-conditions
The following are all valid Hoare triples.
• {x = 5} x := x * 2 {true}
• {x = 5} x := x * 2 {x > 0}
• {x = 5} x := x * 2 {x = 10}
All are true, but which one is the most useful, if we know x
= 5 is satisfied before the program executes.
Definition: If {Pre} Program {Post} and Post ⇒ Post’ for all
Post’ such that {Pre} Program {Post’}, then Post is the
strongest post-condition sp(Program, Pre) of Program with
respect to Pre.
Question
What is sp(x:=x*2+3, {x>-1})?
Weakest Pre-conditions
The following are all valid Hoare triples.
• {x = 5 && y = 10} z := x / y {z < 1}
• {x < y && y > 0} z := x / y {z < 1}
• {y ≠ 0 && x / y < 1} z := x / y {z < 1}
All are true, but which one is the most useful (so that it
allows us to invoke the program in the most general
condition) if we know z < 1 is satisfied after the program?
Definition: If {Pre} Program {Post} and Pre’ ⇒ Pre for all Pre’
such that {Pre’} program {Post}, then Pre is the weakest
precondition wp(Program,Post) of Program with respect to
Post.
Question
What is wp(if(x<0)then x:=-x, x=|a|)?
Forward Analysis
Theorem: {Pre} Program {Post} holds if and only if
sp(Program,Pre) ⇒ Post .
In other words, a Hoare Triple is valid if the postcondition is weaker than the strongest postcondition we can infer.
Example: Since sp(x := x * 2, x=5) ≡ x=10,
{x = 5} x := x * 2 {x > 0} holds.
Backward Analysis
Theorem: {Pre} Program {Post} holds if and only if
Pre ⇒ wp(Program,Post).
In other words, a Hoare Triple is valid if the
precondition is stronger than the weakest precondition we can infer.
Example: Since wp(z := x / y, z < 1) ≡ y ≠ 0 && x / y <
1, {x < y && y > 0} z := x / y {z < 1} is valid.
Example
float sumUp (float[] array, int length) {
float result = 0.0;
How do we prove the following?
int i = 0 ;
while (i < length) {
result += array[i];
i++;
}
{length >= 0 && array.length = length}
sumUp(array, length)
{result = sum(array[j] for all j in 0..length-1)}
return result;
}
We need systematic ways to deal with assignments, loops, conditionals, etc.
Axiomatic Approach
• Hoare logic rules:
– For each kind of program (like assignment, loop,
etc.), define a rule so that we can compute the
weakest pre-condition for a given post-condition
or the strongest post-condition for a given precondition.
Hoare Logic Rules: Assignment
wp(x := E, post) ≡ post[E/x]
where post [E/x] is the predicate obtained by replacing x with E
in post.
Example:
wp(x := 3, x + y > 0) ≡ (x+y)[3/x] > 0
≡3+y>0
Another way to understand this rule:
≡
y
>
-3
for any pre, it must satisfy
pre && x = 3 implies x+y>0
Exercise 1
What is wp(x := 3*y + z, x * y - z > 0)?
Hoare Logic Rules: Assignment
sp(x := E, pre) ≡ x = E[oldx/x] && pre [oldx/x]
where oldx is a fresh variable representing the old value of x;
pre[oldx/x] first renames x to oldx to avoid conflict.
Example:
sp(x := 3, x + y > 0) ≡ oldx+y > 0 && x = 3
sp(x := 3x, x + y > 0)
≡ oldx+y > 0 && x = 3oldx
≡ x + 3y > 0
Hoare Logic Rules: Sequence
wp(program1; program2, post) ≡
wp(program1, wp(program2, post))
We first get the weakest pre-condition of program2 with respect
to post and then use that the post-condition for program1.
Example:
wp(x := x + 1; y := x + y, y > 5)
≡ wp(x := x+1, wp(y:=x+y, y>5))
≡ wp(x := x+1, x+y>5)
≡ x+y > 4
Exercise 2
What is wp(x := 3*y + z; x = 5, x * y - z > 0)?
Hoare Logic Rules: Sequence
sp(program1; program2, pre) ≡
sp(program2, sp(program1, pre))
We first get the strongest post-condition of program1 and then
use that the pre-condition for program2.
Example:
sp(x := x + 1; y := x + y, y > 5)
≡ ???
Hoare Logic Rules: Conditional
wp(if B then Program1 else Program2; post) ≡
B => wp(program1, post) && !B => wp(program2, post)
Example:
wp(if x > 0 then y := z else y := -z, y > 5)
≡ x > 0 => wp(y:=z, y>5) && x <= 0 => wp(y:=-z, y>5)
≡ x > 0 => z > 5 && x <= 0 => -z>5
Hoare Logic Rules: Conditional
sp(if B then Program1 else Program2; pre) ≡
sp(program1, B && pre) || sp(program2, !B && pre)
Example:
sp(if x > 0 then y := z else y := -z, y > 5)
≡ ???
Hoare Logic Rules: Loops
How do we find the following?
wp(
while (i < x) {
f = f*i;
i := i+1;
},
f = x!
)
This is the ONE step that can’t be automated.
Similarly for calculating the strongest post-condition.
Hoare Logic Rules: Loops
{pre}while B do program{post} if there exists an invariant inv
such that the following are satisfied:
(1) pre => inv
(2) {inv && B} program {inv}
(3) inv && !B => post
and the loop terminates
*** (1): the pre-condition satisfies the invariant
(2): the invariant remains valid after executing the loop body
when B is satisfied
(3): the invariant and !B is strong enough to imply the postcondition
Are we missing something?
Big View
B
!B
post
pre
inv
one iteration
{inv && B}program{inv}
Example
{length >= 0 && array.length = length}
float result = 0.0;
int i = 0 ;
while (i < length) {
result += array[i];
i++;
}
{result = sum(array[j] for all j in 0..length-1)}
Apply strongest post-condition
calculation
{length >= 0 && array.length = length
&& i = 0 && result = 0.0}
while (i < length) {
result += array[i];
i++;
}
{result = sum(array[j] for all j in 0..length-1)}
Example: Finding Invariants
{length >= 0 && array.length = length && i = 0 && result = 0.0}
while (i < length) {
result += array[i];
i++;
}
{result = sum(array[j] for all j in 0..length-1)}
Proof:
Let inv be 0 <= i <= length && result = sum(array[j] for all j in 0..i-1);
1. inv is true right before the loop starts, i.e.,
length >= 0 && array.length = length && i = 0 && result = 0.0
=>
0 <= i <= length and result = sum(array[j] for all j in 0..i-1)
How do we find a loop invariant?
Example: Finding Invariants
{length >= 0 && array.length = length && i = 0 && result = 0.0}
while (i < length) {
result += array[i];
i++;
}
{result = sum(array[j] for all j in 0..length-1)}
Proof:
2. inv is a loop invariant, i.e.,
{0 <= i <= length and result = sum(array[j] for all j in 0..i-1) && i < length}
result += array[i];
i++;
{0 <= i <= length and result = sum(array[j] for all j in 0..i-1)}
Example: Finding Invariants
{length >= 0 && array.length = length && i = 0 && result = 0.0}
while (i < length) {
result += array[i];
i++;
}
{result = sum(array[j] for all j in 0..length-1)}
Proof:
3. inv and i >= length implies the post-condition, i.e.,
0 <= i <= length and result = sum(array[j] for all j in 0..i-1) && i >= length
=>
i = length and result = sum(array[j] for all j in 0..i-1)
Hence, the program is partially correct.
We will prove termination later
Invariant Intuition
• For code without loops, we apply the Hoare triple
rules to get the weakest pre-condition or the
strongest post-condition.
• For code with loops, we are doing one proof of
correctness for multiple loop iterations
– Don’t know how many iterations there will be
– Need our proof to cover all of them
– The invariant expresses a general condition that is
true for every execution, but is still strong enough to
give us the post-condition we need.
Termination
• Find a variant function v such that:
– v is an upper bound on the number of loops
remaining
– (pre && B) ⇒ v > 0: the variant function evaluates
to a finite integer value greater than zero at the
beginning of the loop
– {inv && B && oldv=v} program {v < oldv}: the
value of the variant function decreases each time
the loop body executes.
Example: Finding Variant
Variant function: length-i
• length-i is an upper
bound on the number
of iterations.
• It is positive initially.
• It decrease every time.
Hence the algorithm is
terminating and the Hoare
triple holds. Finally!
{length >= 0 && array.length = length
&& i = 0 && result = 0.0}
while (i < length) {
result += array[i];
i++;
}
{result = sum(array[j] for all j in 0..length-1)}
Exercise 3
• Prove the following.
{K > 0 and M > 0}
static int gcd(int K, int M) {
int k= K;
int m = M;
while(k!=m) {
if (k > m) {k = k-m;}
else {m = m-k;}
}
return k;
}
{the returned value is GCD of the inputs}
Proving Real Programs
• How do we apply this kind of proving to realworld programs with classes, inheritance,
higher-order functions, etc.?
– On source code level, transform a program to a
form which has only simple constructs like above.
– Or work on the assembly code.
Byron Cook et al. Communications of the ACM 2011
PROVING PROGRAM TERMINATION
Undeciability
the halting problem
“The problem is undecidable.” (1936)
“Forget about it then.”
“But that’s like the termination problem.”
“Proving termination is not always impossible.”
The problem
The Halting Problem: “using only a finite amount of
time, determine whether a given program will
always finish running or could execute forever.”
The Halting Problem: “using only a finite amount of
time, determine whether a given program will
always finish running or could execute forever or
answer unknown otherwise – the less unknown the
better.”
Turing’s Method
• Find a ranking function f, which maps a
program state to the domain of a wellfounded relation
– By definition, there is no infinite descending chain
in a well-founded relation.
• Show that the “rank” decrease according to
the relation along every possible step of the
algorithm.
Example
x = input();
y = input();
while (x > 0 and y > 0) do {
if (input() == 1) {
x = x-1;
y = y+1;
}
else {
y = y-1;
}
}
Assume that x and y are
integers in math.
How do we prove that this
loop terminates?
Example
Proof: Let f be 2x+y. The set of (bounded) integer forms a well-founded
relation.
At the beginning of the loop: 2x+y is some positive integer (since x >0 and y >
0). The following Hoare triple holds.
{2x+y = V}
if (input() = 1) {
x = x-1;
y = y+1;
}
else {
y = y-1;
}
{2x+y = V’ && V’ < V}
Finish the proof by showing the
Hoare triple holds.
Good News:
Bad News:
Every loop that terminates
has a ranking function.
We don’t know if a loop is
terminating or not.
For some loops, the
ranking function could be
complicated.
How do we go about searching for this ranking function?
Exercise 4: Terminating?
while (x > 0) {
x--;
}
while (x > 0) {
x := x-y;
y++;
}
Assume x >= 0 and y >= 0
while (x > 0 || y > 0) {
x--; y--;
}
while (x > 0) {
y=x;
x--;
while (y>0) {
y--;
}
if (x == 300) {
y=20;
}
}
while (x > 0) {
y=x;
while (y>0) {
y--;
}
}
while (x > 0) {
y=x;
while (y>0) {
y--;
}
x--;
}
Example
x = input();
y = input();
while (x > 0 and y > 0) do {
if (input() = 1) {
x = x-1;
y = input();
}
else {
y = y-1;
}
}
Intuitively, is this loop
always terminating? And
Why?
What is the ranking
function?
No function into the natural numbers
exists that suffices to prove termination.
Disjunctive Termination Arguments
One Ranking Function
• For 60+ years, people
have been devoted to
methods on finding one
ranking function
automatically.
• Hard to find in many
cases.
• Once found, it is easy to
check the validity of the
argument.
Multiple Ranking Functions
• Recent trend
• Easier to find, because it
can be expressed in small,
mutually independent
pieces.
• It is much more difficult
to validate.
Example
x = input();
y = input();
while (x > 0 and y > 0) do {
if (input() = 1) {
x = x-1;
y = input();
}
else {
y = y-1;
}
}
Disjunctive Termination
Argument (two ranking
functions: x and y):
“x goes down by at least 1
and is larger than 0
OR
y goes down by at least 1
and is larger than 0”
How do we use this argument to prove termination?
Disjunctive Termination Argument
Theorem: If every ranking function maps a program state to the
domain of a well-founded relation and the rank decreases through
all possible unrolling of the loop, then the loop terminates.
It is not sufficient to prove that the rank decreases through
one iteration.
Example
x = input();
y = input();
while (x > 0 and y > 0) do {
if (input() = 1) {
x = x-1;
y = y+1;
}
else {
x = x+1
y = y-1;
}
}
Is this terminating?
The following is true for
every one iteration:
“x goes down by at least 1
and is larger than 0
OR
y goes down by at least 1
and is larger than 0”
Intuition
One ranking function:
Since the “rank” decreasing
every time, eventually it will
stop and the loop
terminates.
Multiple ranking functions:
Every iteration at least one
“rank” decrease, but some
other rank might increase
and the decrement might be
un-done later.
If we show that at least one
“rank” decrease for any
arbitrary number of
iterations, then the loop
terminates.
How do we show that something holds through arbitrary number of iterations?
Termination as Assertion Checking
copied := false
x = input(); y = input();
while (x > 0 and y > 0) do {
if (copied) {
assert((x <= oldx-1 && oldx >0 ) || (y <= oldy-1 && oldy >0 ))
}
if (input() == 1) {
copied = true; oldx = x; oldy = y;
}
if (input() = 1) { x = x-1; y = y+1; }
else { x = x+1; y = y-1; }
}
Would you agree that if the assertion is
always true, we proved that x or y is
decreasing through arbitrary number of
iterations?
Example: Assertion Checking
if (y >= 1) {
while (x >0) {
assert(y >= 1);
x = x – y;
}
}
If we know that the
assertion is true, then it is
implied that y >= 1 is true
(at that location) through
arbitrary number of
iterations.
Bad News: Assertion checking in general is undecidable.
Good News: There are tools for assertion checking.
***We will discuss how to do assertion checking in later classes.
State-Of-the-Art
• Many tools for proving termination
– Terminator, T2, ARMC, Aprove, etc.
• Latest empirical study:
– 449 benchmarks (including Windows device
drivers, the Apache web server, the PostgreSQL
server, etc.) ranging from hundreds LOC to 30K
LOC.
– 260 are known to be terminating; 181 nonterminating;
Empirical Study
Summary
• Termination checking is not always impossible.
• It is not always possible either (see examples
soon).
• So far we are able to handle programs of size
30K LOC or less.
• Serious researchers are needed in this area.
Further Challenges
c = head
while (c != null) {
if (c.next != null && c.next.data == 5) {
c.next = c.next.next;
}
c = c.next
}
How would you argue that the program is terminating or not?
How would you know whether the linked list is acyclic or not?
There are no integers here. How would you find the function automatically?
Integers are NOT “Integers”
x := 10
while x > 0 do {
x := x + 2;
}
x := 10
while x > 9 do {
x := x – 2^32;
}
Are these terminating?
Source Code vs Binary Code
Java Programs
Is it terminating here?
Bytecode
Is it terminating here?
JVM
Is it terminating here?
Physical Machine
Is it terminating here?
Concurrency + Termination
Thread 1
while x > 0 {
x := x-1;
lock(mu);
b := x;
unlock(mu);
}
Thread 2
while y > 0 {
lock(mu);
y := b;
unlock(mu);
}
How do we prove both threads are terminating?
Collatz Program
while (x > 1) {
if (x is divisible by 2) {
x := x / 2;
}
else {
x := 3x+1;
}
}
Is this program always terminating or not?
If you solve it, you will be well-known.
Exercise 5
x = input();
while (x >= 0) {
y := 1;
while (y < x) {
y = 2*y;
}
x = x – 1;
}
Show the above is terminating.