Chapter 6

Testing and verification


A program’s life cycle is the sequence of phases it goes
through from its conception to its ultimate demise.

A new program is created in the development stage.

Software maintenance is the process of modifying a
program in order to enhance it or fix problems.

Historically, computer scientists have developed and used
various software development process models, including:

Build-and-Fix

Waterfall model

Iterative process
Software Engineering


The build-and-fix approach does not address the overall
quality of a program.
Build-and-Fix
Write
program
Modify
program
Release


The waterfall model is a linear process in which one stage
is followed by the next.
Waterfall model
Establish
requirements
Create
design
Implement
code
Test
system
Release


An iterative process is one that allows a software
developer to cycle through different development activities.
Iterative process
Establish
requirements
Create
design
Implement
code
Test
system
Release

The iterative process allows
backtracking to correct problems.


The iterative process leads to the following evolutionary
development process.
Evolutionary Development
Establish
requirements
Architectural
design
Establish
refinement
scope
System
test
Identify
relationships
Unit and
Integration
test
Identify
classes &
objects
Implementation
Detailed
design
Release
Refinement
cycle
Specific methods
and algorithms
General structure
of the system


The development of a class involves design, specification,
implementation, and testing.

Testing is a fundamental part of building a software
system.

Testing is an activity with the goal of determining whether
or not an implementation functions as intended:

Testing attempts to determine if the implementation is correct.
Testing


For any interesting problem, we cannot exhaustively test
all cases.

Testing is effective at showing errors in your
implementation, but really doesn’t show that the
implementation is correct.

Testing consists of two phases:

Test activities are determined and test data selected.

Test design

The test is conducted and test results compared with
expected results.
Testing (cont.)


We will consider two forms of testing:

Functional (or System) Testing

Testing an entire system to make sure that it conforms to the
customer’s requirements

Black box testing

Unit and Integration Testing

Unit testing involves testing individual system components to
verify that they perform correctly

Integration testing involves the interaction between individual
system components to make sure that they interact correctly

White box testing and Gray box testing
Testing (cont.)


Systems testing

Black box testing

Test design generally begins with the analysis of:

functional specifications of the system

system requirements

ways in which the system will be used

“use cases”
Functional Testing


The other form of testing is unit testing.

Recall that we test a class we are implementing to
increase our confidence that it behaves as expected.

Testing is part of the job of implementing the class.

Programmers often spend more time debugging than
writing code. As such, the best way to improve
productivity is to spend more effort on design.

Once the design is implemented, the best way to reduce
debugging time is to adopt an aggressive testing
regimen.

Incremental test-driven implementation is an effective
means of reducing the time required to track down and
correct bugs.
Unit Testing


One form of unit testing is white box testing.

White box testing derives its test cases based on the
implementation.

Test cases are defined by considering as many of the
paths through the program as possible.

The ultimate white box test is the execution of every
possible path through the program.

In general, it is not feasible to execute every path so
we will consider some less complete testing criteria:

Decision coverage

Condition coverage

Multiple condition coverage
White Box Testing


For each decision, you should add test cases, one for
the true case and one for the false case.

Unfortunately, this approach does not guarantee a
complete set of test cases:

If a program has no decisions then this approach would
produce no test cases.

If a program has complex conditions then this approach
may not be adequate. Consider the following if statement:
if ((a < 0) && (b < 10)) {

}
Decision Coverage


Each decision outcome is exercised by the test cases:

a = 2, b = 5

a = -1, b = 5

Note that this does not exercise the condition on b.
Decision Coverage (cont.)


For each decision, you should add test cases such that
each condition in the decision takes on all possible
outcomes at least once.

If a program has complex conditions then this
approach still may not be adequate. Consider the
following if-else statement:
if ((a < 0) && (b < 10)) {

}
else {

}
Condition Coverage


Each condition in the decision is exercised by the test
cases:

a = -1, b = 11

a = 5, b = 5

Note that this does not cause the else clause to
execute.

One way to avoid the problems with decision coverage
and condition coverage is to combine these methods:

Each condition in a decision takes on all possible
outcomes at least once and each decision takes on all
possible outcomes at least once.

But this still does not guarantee coverage of all
possible outcomes of all possible combinations of
conditions.
Condition Coverage (cont.)


Multiple condition coverage is a more general criteria
which covers the problems with decision and condition
coverage.

Define enough test cases so that every possible
combination of condition outcomes and all points of
entry in a program are invoked at least once.

Consider the following code segment:
if ((a > 1) && (b == 0)) {
statement
1
}
if ((a == 2) || (c > 1)) {
statement
2
}
Multiple Condition Coverage

Multiple Condition Coverage
(cont.)

All possible combinations
of condition outcomes in
each decision can be
covered with the
following eight cases:
1. a > 1, b == 0
2. a > 1, b != 0
3. a <= 1, b == 0
4. a <= 1, b != 0
5. a == 2, c > 1
6. a == 2, c <= 1
7. a != 2, c > 1
8. a != 2, c <= 1
false false
true false
false true
true true
b == 0 a > 1

These cases come directly
from truth tables:
false false
true false
false true
true true
c > 1 a == 2


These eight cases can be covered by the following four
cases:
1. a == 2, b == 0, c == 2
⇒ Covers cases 1 and 5
2. a == 2, b == 1, c == 1
⇒ Covers cases 2 and 6
3. a == 1, b == 0, c == 2
⇒ Covers cases 3 and 7
4. a == 1, b == 1, c == 1
⇒ Covers cases 4 and 8
Multiple Condition Coverage
(cont.)
1. a > 1, b == 0
2. a > 1, b != 0
3. a <= 1, b ==
0
4. a <= 1, b !=
0
5. a == 2, c > 1
6. a == 2, c <=
1
7. a != 2, c > 1
8. a != 2, c <=
1


Notice that we still haven’t executed all paths through
the segment:
1. a == 2, b == 0, c == 2
⇒ Executes statement
1
and executes statement
2
2. a == 2, b == 1, c == 1
⇒ Skips statement
1
and executes statement
2
3. a == 1, b == 0, c == 2
⇒ Skips statement
1
and executes statement
2
4. a == 1, b == 1, c == 1
⇒ Skips statement
1
and skips statement
2
The path “executes statement
1
and skips
statement
2
” is not executed.
Multiple Condition Coverage
(cont.)


Another form of unit testing is gray box testing.

Gray box testing is also referred to as data-driven
testing:

Not concerned with the internal structure of the method.

Test data is derived solely from method specifications.

Look for circumstances in which the method does not
behave according to its specifications.

Let’s consider 3 approaches for gray box testing:

Equivalence Partitioning

Boundary Value Analysis

Error Guessing
Gray Box Testing


Partition the domain of input values into groups called
equivalence classes.

Equivalence classes are chosen such that testing one
element in the class is equivalent to testing any other
element in the class.

Valid equivalence classes represent valid inputs to the
method.

Invalid equivalence classes represent all other inputs
(i.e., those that violate the precondition)

Invalid inputs are program errors and should not be tested.

No rules for identifying equivalence classes, but there
are some guidelines.
Equivalence Partitioning


If an input condition specifies a range of values, identify
a valid equivalence class for that range.

If an input condition specifically lists the number of
values, identify one valid equivalence class for these
values.

If an input condition specifies a set of input values and
there is reason to believe that each is handled differently
by the program, identify one valid equivalence class for
each element in the set.

If an input condition specifies a “must be” situation,
identify a valid equivalence class for it.

If there is any reason to believe that elements in an
equivalence class are not handled in an identical manner
by the program, split the equivalence class into smaller
equivalence classes.
Equivalence Partitioning (cont.)


Divide the input space into equivalence classes and
select a test case from each class.

Boundary value analysis focuses on selecting data near
the edges of conditions on both input space and output
space.

General guidelines:

If an input or output condition specifies a range of values,
write test cases for the ends of the range.

If an input or output condition specifies a number of values,
write test cases for the minimum and maximum number of
values.

If the input or output of a procedure is an ordered set,
focus attention on the first and last elements of the set.
Boundary Value Analysis


Error guessing is largely based on intuition and
experience as to frequently encountered errors.

Examples of error guessing situations:

Null strings

0 numeric values

Numeric values nearly exceeding computer storage
capability

Collections or files that are empty or contain only one value

Collections containing all the same value

Collections in some ordered form
Error Guessing


Overall strategy for gray box testing:
1. Analyze preconditions and postconditions to make a list
of the external conditions, both input and output.
2. Divide each of these external conditions into valid
equivalence classes.
3. Use boundary value analysis to identify test cases for
these equivalence classes. A test case may cover
more that one equivalence class.
4. Use error guessing to add other classes and test
cases.
5. For each test case, identify the expected output.
6. Test the procedure and compare the expected output
with the actual output.
Combined Strategy


Now let’s try to apply some of the ideas we just discussed
about testing to the design and implementation of our
classes.

We’ll focus on gray box testing.

We’ll employ incremental test-driven implementation:

Implementation proceeds in relatively small increments.

Code a little, test a little.

Each time you add a new bit of functionality, you test it
immediately.

Write a test for a feature before implementing the feature.

Let’s consider the process of testing the TrafficSignal
class.
Testing Summary


We want to create a TrafficSignal instance and
exercise it by querying it and giving it commands.

To do this, we need another object to act as client to the
TrafficSignal.

We will call this class TrafficSignalTest and put it in
the same package as the TrafficSignal class.

Since it does not need to be accessed from outside the
package, we do not need to make TrafficSignalTest
public.

The only property of TrafficSignalTest is the
TrafficSignal to be tested.
Testing TrafficSignal


Stubbed implementation of 
TrafficSignal
Left blank
Left blank
Dummy return value
public class TrafficSignal {
public static final int GREEN = 0;
public static final int YELLOW = 1;
public static final int RED = 2;
private int light;
public TrafficSignal () {
}
public int light () {
return 0;
}
public void change () {
}
}
Testing TrafficSignal (cont.)


We start by defining a new class named
TrafficSignalTest:
/**
* A tester for the class TrafficSignal.
*/
class TrafficSignalTest {
private TrafficSignal signal; // the object to test
/**
* Create a TrafficSignalTest.
*/
public TrafficSignalTest () {
signal = new TrafficSignal();
}
/**
* Run the test.
*/
public void runTest () {
}
}
Testing TrafficSignal (cont.)


We’ll finish this class by completing runTest() later,
but first let’s create a driver class Test:
/**
* A simple test system for the class TrafficSignal.
*/
class Test {
/**
* Run the test.
*/
public static void main (String[] argv) {
TrafficSignalTest test;
test = new TrafficSignalTest();
test.runTest();
}
}
Testing TrafficSignal (cont.)


We could eliminate the variable and assignment, and
write the main() method as a single statement:
/**
* A simple test system for the class TrafficSignal.
*/
class Test {
/**
* Run the test.
*/
public static void main (String[] argv) {
(new TrafficSignalTest()).runTest();
}
}
Testing TrafficSignal (cont.)


Let’s begin writing runTest() by testing the initial state.
Although we could write all of our tests inline in
runTest(), the testing task is much more manageable if
we put each test in a separate method.
/**
* Run the test.
*/
public void runTest () {
testInitialState();
}
/**
* Test the TrafficSignal's initial state.
*/
private void testInitialState () {
System.out.println("testInitialState:");
System.out.println("Initial light: " + signal.light());

}
Testing TrafficSignal (cont.)

Testing TrafficSignal (cont.)


Now let’s add a method to test the state changes.
/**
* Run the test.
*/
public void runTest () {
testInitialState();
testChange();
}
/**
* Test the method change.
*/
private void testChange () {
System.out.println("testChange:");
System.out.println("Starting light: " + signal.light());
signal.change();
System.out.println("After 1 change: " + signal.light());
signal.change();
System.out.println("After 2 changes: " + signal.light());
signal.change();
System.out.println("After 3 changes: " + signal.light());
}
Testing TrafficSignal (cont.)


The TrafficSignal class is fairly trivial so there are
not a lot of cases to consider.

Note that the methodology of designing the test cases
(via the Test class) along with the TrafficSignal
class promotes a test-driven incremental design.

Further note that by defining the test cases based on the
specification before implementing the method, we can
avoid being biased by the implementation.
Testing TrafficSignal (cont.)



public int compute(int A, int B) {
int x = 0;
int m = A;
int n = B;
while (n > 0) {
if ((n % 2) != 0) {
x = x + m;
}
m = 2 * m;
n = n / 2;
}
return x;
}
/**
**
** @ensure result == A * B
**/
@require B >= 0
Verification

Consider the following simple Java method:
Is this method correct?

Ahmes inscribed the
“Rhind Papyrus”
(~1850 BC)
“… a thorough study
of all things, insight
into all that exists,
knowledge of all
obscure secrets …”
Egyptian mathematics
Egyptian Numbering System

Contained a passage
written in Greek,
demonic, and
hieroglyphics
Thomas Young, a British
physicist, and Jean
Francois Champollion, a
French Egyptologist,
collaborated to decipher
the hieroglyphic and
demotic texts by
comparing them with
the known Greek text
Rosetta Stone
Deciphering the Rhind Papyrus

2468 490
4936 4936 245
9872 122
1974 4 1974 4 61
6318 08 6318 08 1
3159 04 3159 04 3
1579 52 1579 52 7
7897 6 7897 6 15
3948 8 30
1234 1234 981
Remainder “Doubled” Multiplier
“Halved”
Multiplicand
1210554
Egyptian Multiplication



public int compute(int A, int B) {
int x = 0;
int m = A;
int n = B;
while (n > 0) {
if ((n % 2) != 0) {
x = x + m;
}
m = 2 * m;
n = n / 2;
}
return x;
}
/**
**
** @ensure result == A * B
**/
@require B >= 0
Egyptian Multiplication (cont.)


Attempts to provide a more precise specification of
the behavior of a method.

Such a method specification includes two parts:

Any preliminary conditions that are required for
the method to execute properly, known as the
method’s precondition.

The changes that result from executing the
method, known the method’s postcondition.
Program Correctness


Taken together, the precondition and postcondition
form a software contract. This contract states the
following:
IF the calling code ensures the precondition is
true at the time it calls the method
THEN the postcondition is guaranteed to be true at
the time the method completes execution
Program Correctness (cont.)


Preconditions and postconditions are logical
expressions which describe states of the program
variables.

Given a method, the postcondition describes the
expected outcome of its execution.

The role of the precondition is to identify
requirements for using the method so as to ensure
that the postcondition is met.

Violating a precondition should be considered a logic
error.
Program Correctness (cont.)


To be valid, the precondition should define a state
from which the method can produce the
postcondition.

It is reasonable to ask, what precondition will
produce the desired postcondition?

To answer this, we will construct such a precondition
starting with the postcondition.
Program Correctness (cont.)


To construct a precondition for a method, we start
with the postcondition and work backward to
successively compute the precondition for each
statement of the method.

At each step, we seek the broadest precondition
that produces the desired postcondition.

The weakest precondition of a statement (or
method) is the least restrictive precondition that will
guarantee the validity of the associated
postcondition for that statement (or method).
Constructing Preconditions


Consider the following code segment with
postcondition:

The assertion {sum > 1} is the postcondition
for the statement sum = 2 * x + 1;
Possible preconditions are {x > 10}, {x >
50}, and {x > 1000}.
The weakest precondition is {x > 0}.
sum = 2 * x + 1;
{sum > 1}
Constructing Preconditions (cont.)


The basis for our technique is the
assignment axiom, which allows us to
perform a backward substitution.

Let V = E be a general assignment statement
and Q be its postcondition. Formally, the
assignment axiom is stated:
which means that the constructed weakest
precondition is Q with all instances of V
replaced by E.
( )
,
V E
wp V E Q Q
÷
· ·
The Assignment Axiom


Consider the following assignment statement
with postcondition:

The weakest precondition is computed by
substituting b / 2 – 1 for a into the assertion
{a < 10}, thus
b / 2 – 1 < 10
b / 2 < 11
b < 22
So {b < 22} is the constructed weakest
precondition.
a = b / 2 - 1;
{a < 10}
The Assignment Axiom (cont.)


Consider the following assignment
statement with postcondition:

Using the assignment axiom produces {x
> 3} for the constructed weakest
precondition.
x = x - 3;
{x > 0}
The Assignment Axiom (cont.)


The weakest precondition for a block of
statements can be constructed by
successively backing over each of the
statements in the block (starting with the last
statement) and using the constructed
weakest precondition of each statement as
the postcondition to its preceding statement.

The following Sequence Rule defines the
constructed weakest precondition for a pair of
statements:


Repeated applications of the Sequence Rule
can be used for blocks containing more than
2 statements.
( ) ( ) ( )
1 2 1 2
; , , , wp C C Q wp C wp C Q ·
Sequential Statements


Consider the following sequence of
statements with postcondition:

The constructed weakest precondition for the
last statement is:
{y + 3 < 10}
{y < 7}
y = 3 * x + 1;
x = y + 3;
{x < 10}
Sequential Statements (cont.)


This becomes the postcondition to the first
statement:
The constructed weakest precondition for the
last statement is:
{3 * x + 1 < 7}
{x < 2}
y = 3 * x + 1;
{y < 7}
Sequential Statements (cont.)


In practice, when deriving the precondition,
we frequently write out the statements and
the postcondition leaving blank lines between
each statement. We work backwards to fill in
the blank lines with the successive
preconditions as shown below:


y = 3 * x + 1;

x = y + 3;
{x < 10}
Sequential Statements (cont.)

y = 3 * x + 1;
{y < 7}
x = y + 3;
{x < 10}
{x < 2}
y = 3 * x + 1;
{y < 7}
x = y + 3;
{x < 10}


Consider the following code segment with
postcondition:

It is supposed to swap the values of x and y.

Let’s verify that it does.
temp = x;
x = y;
y = temp;
{x == b && y == a}
Sequential Statements (cont.)

temp = x;
x = y;

y = temp;
{x == b && y == a}
temp = x;
x = y;
{x == b && temp == a}
y = temp;
{x == b && y == a}

temp = x;
{y == b && temp == a}
x = y;
{x == b && temp == a}
y = temp;
{x == b && y == a}

The construction would proceed as follows:
Sequential Statements (cont.)
{y == b && x == a}
temp = x;
{y == b && temp == a}
x = y;
{x == b && temp == a}
y = temp;
{x == b && y == a}
The constructed weakest
precondition is:
{y == b && x == a}
Comparing this with the
postcondition, we see that the code
does, indeed, swap the values of x
and y.


Consider the following code segment with
postcondition:
Let’s construct the weakest precondition
that produces the stated postcondition.
Working backward:
y = 4;
z = x + y;
{z = 7}

y = 4;

z = x + y;
{z == 7}

y = 4;
{x + y == 7}
z = x + y;
{z == 7}
{x + 4 == 7}
y = 4;
{x + y == 7}
z = x + y;
{z == 7}
{x == 3}
y = 4;
{x + y == 7}
z = x + y;
{z == 7}
The constructed
weakest
precondition is {x
== 3}.
Sequential Statements (cont.)


Suppose we have the statement:
{P}
if (B) {
S
1
}
else {
S
2
}
{Q}
Conditional Statements


The if statement can take us from P to Q
along either of two paths:
truecase: P → B → S
1
→ Q
falsecase: P → ~B → S
2
→ Q
When working backward to construct P, we
need to take into account each of these
paths.
Conditional Statements (cont.)


For truecase, backing over S
1
leads to a
precondition, call it P
1
.

For execution to reach that point, the condition B
must have been true.

Backing over it gives us P
1
∧ B as a condition of
executing the true path.

For falsecase, backing over S
2
leads to a
precondition, call it P
2
.

For execution to reach that point, the condition B
must have been false (i.e., ~B must have been
true).

Backing over ~B gives us P
2
∧ ~B as a condition of
executing the false path.
Conditional Statements (cont.)


To cover both paths, we want:
P = (P
1
∧ B) ∨ (P
2
∧ ~B)
as the precondition.

The following Conditional Rule defines the
constructed weakest precondition for a
conditional statement:
Conditional Statements (cont.)
( ) ( ) ( ) ( ) ( ) ( )
1 2 1 2
, , , wp B C C Q wp C Q B wp C Q B · · v · ¬ if else


Let’s construct the weakest precondition for the
following conditional:
if (x > y) {
y = x - 1;
}
else {
y = x + 1;
}
{y > 0}
The truecase:
produces the precondition {x – 1 > 0}
which simplifies to {x > 1}.
Backing over the if condition, we get
{(x > 1) && (x > y)}.
y = x - 1;
{y > 0}
The falsecase:
produces the precondition {x + 1 > 0}
which simplifies to {x > -1}.
Backing over the negative of the if
condition, we get {(x > -1) && (x <=
y)}.
y = x + 1;
{y > 0}
The disjoin of these is
{((x > 1) && (x > y)) || ((x > -1) && (x <= y))}
which serves as a precondition of the if structure.
Conditional Statements (cont.)


Let’s construct a precondition which ensures the
correctness of the following code segment:
if (n >= 5) {
y = n * n;
}
else {
y = n + 1;
}
{y == 36}
The truecase:
produces the precondition {n * n ==
36} which simplifies to {n == 6 || n
== -6}.
Backing over the if condition, we get
{(n == 6 || n == -6) && (n >= 5)}
which simplifies to {n == 6}.
y = n * n;
{y == 36}
The falsecase:
produces the precondition {n + 1 ==
36} which simplifies to {n == 35}.
Backing over the negtive of the if
condition, we get {(n == 35) && (n <
5)}.
This is always false.
y = n + 1;
{y == 36}
Thus, the constructed precondition is
{(n == 6) || false}, which simplifies
to
{n == 6}.
Conditional Statements (cont.)


Show that the following code segment assigns the
maximum of x and y to max:
We need to establish a postcondition that reflects
the requirement that, after execution, the value
of max is the larger of x and y. This can be stated
as:
(x >= y && max == x) || (x < y && max == y)
if (x >= y) {
max = x;
}
else {
max = y;
}
Conditional Statements (cont.)

The truecase:
produces the precondition {(x >= y && x == x) || (x < y && x == y)}
which simplifies to {(x >= y && true) || false} and further simplifies to
{x >= y}.
Backing over the if condition, we get {(x >= y) && (x >= y)} which
simplifies to {x >= y}.
max = x;
{(x >= y && max == x) || (x < y && max == y)}
if (x >= y) {
max = x;
}
else {
max = y;
}
{(x >= y && max == x) || (x < y && max == y)}
Conditional Statements (cont.)

The falsecase:
produces the precondition {(x >= y && y == x) || (x < y && y == y)}
which simplifies to {(y == x) || (x < y && true)} and further simplifies
to {y >= x}.
Backing over the negative of the if condition, we get {(y >= x) && (x <
y)} which simplifies to {x < y}.
max = y;
{(x >= y && max == x) || (x < y && max == y)}
if (x >= y) {
max = x;
}
else {
max = y;
}
{(x >= y && max == x) || (x < y && max == y)}
Conditional Statements (cont.)

if (x >= y) {
max = x;
}
else {
max = y;
}
{(x >= y && max == x) || (x < y && max == y)}
Combining the results of each pass, we get:
{(x >= y) || (x < y)}
Since this is always true, it means that the code
segment produces the postcondition for all values of
x and y.
We write the precondition as simply true.
Conditional Statements (cont.)


Suppose we have the statement
where the guard G is a logical expression and S
is the body of the loop.
The while statement can take us from P to Q
along any of a number of different paths,
depending on how many times the condition G is
true, including the path that goes directly from P
to Q when the condition G is initially false.
{P}
while (G) {
S
}
{Q}
Logical Pretest Loops


Executing a while loop involves repeatedly
evaluating the guard G and executing the body S
until the guard becomes false, at which time
control passes to the statement after the loop.
int i = 0;
int s = 1;
while (i < 10)
{
s = s * 2;
i = i + 1;
}

Logical Pretest Loops (cont.)

The condition that stops the loop
is called the terminating
condition and is normally more
specific than the negative of the
guard. Consider the code
segment to the right.

The negative of the guard is
i >= 10, but the terminating
condition is i == 10.


To compute a precondition, we reverse execute
the loop for several passes in an attempt to
determine what must be true before the loop can
be properly executed.

To begin the backward execution, we choose
the condition that must be true after the loop
has terminated.

We know that after the loop has terminated, the
postcondition Q and the terminating condition
(which we call T) must be true.

Thus we start backing over the loop with the
condition Q ∧ T.

Backing over the loop produces a condition
that must be satisfied in order for the program
to forward execute that pass of the loop.
The Reverse Execution Model


As a result of backing over the body of the loop,
at the top of the body, the condition Q ∧ T will
have been transformed into some other condition
(which we will call A
1
).

We know the guard G must also have been
true in order for execution to have reached the
top statement of the loop body, thus backing
over the guard means the condition A
1
∧ G
must have been true prior to that pass over
the loop body.

A
1
∧ G serves as a precondition for the last
forward pass and a postcondition to the second
backward pass over the loop.
The Reverse Execution Model
(cont.)


Executing the second backward pass results
in a condition A
2
at the top of the body.

Backing over the guard means A
2
∧ G must
have been true prior to the program
executing that forward pass over the loop.

Repeated backward executions yields
successive conditions A
1
∧ G , A
2
∧ G, A
3
∧ G ,
A
4
∧ G , A
5
∧ G , …

Each of these conditions must have been
true before the program could have
executed the corresponding forward pass
through the loop.

From these conditions we determine the
general pattern.
The Reverse Execution Model
(cont.)


The general pattern defines a condition which
we call a loop invariant.

A loop invariant, I, is an assertion within
a loop, evaluated prior to the loop guard,
which is true every time execution reaches
that position in the loop.

The invariant must be true every time the
while loop guard is encountered.

It is an assertion that is a precondition to
each execution of the body and must also
be true when the guard is encountered on
the terminating pass.

That is, it must be true when the guard is
true or the terminating condition is true.
The Reverse Execution Model
(cont.)


The following Loop Rule defines the precondition
construction process for a conditional statement:

The following recursive definition defines the loop
invariant I
k
assuming the loop has a single
terminating condition τ:
( ) ( ) ( ) ( )
,
k
wp B C Q B Q B · I · v ·¬ while
( )
( )
1
, 0
,
n
n
wp C Q if n
wp C B otherwise
t

¹ · ·
¹
I ·
'
I ·
¹
¹
Logical Pretest Loops (cont.)


public int compute(int A, int B) {
int x = 0;
int m = A;
int n = B;
while (n > 0) {
if ((n % 2) != 0) {
x = x + m;
}
m = 2 * m;
n = n / 2;
}
return x;
}
/**
**
** @ensure result == A * B
**/
@require old.B >= 0
result == A * B
x == A * B
Loop invariant:
(x == (A*B)–(m*n)) && (n >= 0)
(x == (A*B)–(m*n)) && (n >= 0)
(x == (A*B)–(m*B)) && (B >= 0)
(x == (A*B)–(A*B)) && (B >= 0)
(0 == (A*B)–(A*B)) && (B >= 0)
(0 == (old.A * old.B)–(old.A * old.B)) && (B >= 0) (0 == 0) && (old.B >= 0) (old.B >= 0)
@require B >= 0
Logical Pretest Loops (cont.)


public int compute(int N) {
if (N < 0) {
N = -N;
}
int m = 0;
int x = 0;
int y = 1;
while (m < N) {
x = x + y;
y = y + 2;
m = m + 1;
}
return x;
}
/**
**
** @ensure result == N * N
**/
@require true
result == N * N
x == N * N
Loop invariant:
(x == N*N–(N–m)*y–(N–m)*(N–m–1)) && (N >= m)
(x == N*N–(N–m)*y–(N–m)*(N–m–1)) && (N >= m)
How do we find an appropriate loop invariant?
(x == N*N–(N–m)–(N–m)*(N–m–1)) && (N >= m)
(0 == N*N–(N–m)–(N–m)*(N–m–1)) && (N >= m)
(0 == N*N–N–N*(N–1)) && (N >= 0) (0 == (N*N–N*N) && (N >= 0)
((N < 0) && (-N >= 0)) || (!(N < 0) && (N >= 0)) (N < 0) || (N >= 0)
true && (N >= 0) N >= 0
true
true
Logical Pretest Loops (cont.)

Sign up to vote on this title
UsefulNot useful