Formal techniques for getting software right: some old ideas and some new tools Applied Formal...

Post on 28-Mar-2015

215 views 1 download

Tags:

Transcript of Formal techniques for getting software right: some old ideas and some new tools Applied Formal...

Formal techniques for getting software right: some old ideas and some new tools

Applied Formal Methods Research Group

2012-11-02

David Lightfoot: dlightfoot@brookes.ac.uk

Department of Computing and Communication Technologies

Oxford Brookes University

England

Abstract

Getting software right is difficult and the more so if we don’t have a clear idea of what it is supposed to do before we start writing it.

There are some simple ideas that were devised nearly fifty years ago that help in specifying formally what software is to do, that is, by the use of simple discrete mathematics.

These ideas are gaining acceptance in programming languages and modern languages, such as Eiffel and Spec# (‘spec sharp’) now contain features to make use of these ideas.

Motivating example: hotel

What does ‘available’ mean?

Check rooms available at affordable hotel:

‘18 rooms available’

Book (expensive) flight.

Try to book rooms:

‘Sorry, no rooms available’

Problem!

Check rooms available at affordable hotel, again:

‘18 rooms available’

What is the explanation?

Motivating example: hotel

Hotel closed for Christmas break!

So, no rooms booked, hence

‘18 rooms available’

But hotel closed, so

‘Sorry, no rooms available’

What does ‘available’ mean?

We need specifications!

What does ‘available’ mean?

Here’s another one: (from an exam paper):

‘Write a Pascal program to return a pointer to the element in the linked list ls that has key value x:

function Find(ls: Pointer; x: integer): Pointer;’

•What if there isn’t one? Can we assume there is?•Which one, if there are several?

Answer: return NIL. Why?

What’s the requirement?

‘Write a program to input a three integer numbers, each of them is between 1 and 1000, and determine whether the first number is the multiple of the other two numbers.

Print a message to tell if the first number is the multiple of the other two numbers. [22 marks]’

8 of the 22 marks were for checking that the three integer numbers were in range 1 to 1000 and writing a message. (But it says ‘each of them is …’).

(and no marks for checking that a well formed integer was input)

Campaign for clear specifications

In order to write correct programs we need clear specifications:•A program is syntactically correct with respect to its language definition•It is logically/semantically correct with respect to its specification

We can check syntactic correctness with a (correct) compiler.

We can check logical correctness by:•testing•use of suitable notation and of provers

State-based specification

Both at the system level and at program level we can specify by a state-based approach:•Observe state of system before an operation•Observe state of system after an operation

This puts emphasis on what is achieved, not how it is achieved.

Specification notations, such as:

Z, VDM, B, Event B …

Not a new idea: Professor Sir Tony Hoare

Formerly Head of Programming Research Group (PRG), Oxford University, EnglandSenior Researcher, Microsoft Research, Cambridge, England

Hoare, C.A.R.: An axiomatic basis for computer programming. Communications of the ACM 12 (1969) 576-580 [WH66]

N. Wirth and C. A. R. Hoare. A contribution to the development of Algol. Comm. ACM, 9(6):413-432, June 1966

Also famous as deviser of Quicksort.

‘Hoare triple’

{ pre }

prog

{ post }

Starting in a before-state where the Boolean expression pre holds, running the program prog results in an after-state where the Boolean expression post holds.

pre is ‘precondition’

post is ‘postcondition’

Example

{ x >= 0 }

IntegerSqrt

(z*z <= x and x < (z+1)*(z+1)

Precondition means:

IntegerSqrt is only applicable to non-negative x

Postcondition means

z has been set to the ‘integer square-root’ of x

Specification of a function

function IntegerSqrt(x: integer): integer;

{ pre: x >= 0

post: result*result <= x and x < (result+1)*(result+1) }

result is here a special key word denoting the result of a function

Techniques for programming

Hoare developed techniques based on axioms (rules) for devising programs that can be shown (proved) to satisfy their specifications.

Assignment axiom

(* P[var/exp ] *)

var := exp

(* P *)

If P is true of var after the assignment, then P must have been true of exp before.

P[var/exp ] means P, with all (free) occurrences of var replaced by exp.

Composition axiom

if we can prove (* P *) S1 (* Q *) and we can prove (* Q *) S2 (* R *)

___________then it follows that (* P *) S1 ; S2 (* R *)  If doing S1 when P is true makes Q true and doing S2 when Q is true makes R true, then doing S1 ; S2 when P is true makes R true.

Selection axiom

(* P & guard *) S1 (* Q *) (* P & ¬guard *) S2 (* Q *)___________(* P *) if guard then S1 else S2 end (* Q *)

If performing S1 when P is true and guard is true makes Q true and performing S2 when P is true and guard is false makes Q true, Then performing

if guard then S1 else S2 end when P is true makes Q true.

Repetition axiom

(* pre *)initialisation(* invariant *)while guard do (* invariant & guard *) body (* invariant *)end(* invariant & ~guard => post *)

Dijkstra

E.W.Dijkstra observed that rather than proving an existing program correct, it is easier to develop a program hand-in-hand with showing it to be correct.

Developed weakest-precondition, wp notation

For example:

wp(var := exp, P) is P[var/exp ]

Also famous for P and V and Travelling-Salesman problem, …

What’s the precondition?: a real example

Recent student work:

Required to implement undo.

Keep stack of visited nodes.

Undo then means pop from stack.

In method undo, do we need to guard with:

if(!stack.isEmpty()) node = stack.pop(); ?

Or do we have precondition: !stack.isEmpty(),

so can just implement undo as:

node = stack.pop(); ?

Design by Contract™

Hoare’s ideas given a business flavour by Bertrand Meyer, currently Professor of Software Engineering at ETH Zurich.

Precondition and postcondition are expressed as a contract between the supplier and the client of a service.

Also designer of Eiffel programming language.

Eiffel contains facilities for run-time checking of precondition and postcondition…

IntegerSqrt as contract: supplier’s view

Supplier is writer of IntegerSqrt

Supplier’s expectation:

x >= 0

Supplier’s obligation:

must deliver result such that

result*result <= x and x < (result+1)*(result+1)

IntegerSqrt as contract: client’s view

Client is user of IntegerSqrt

Client’s expectation:

obtains result such that

result*result <= x and x < (result+1)*(result+1)

Client’s obligation:

give x such that

x >= 0

integerSqrt in Eiffel

integerSqrt (x: INTEGER) : INTEGER-- return the integer square-root of non-negative x

requirenon-negative_x:

x >= 0ensure

result is floor of square-root of x:result*result <= x and x < (result+1)*(result+1)

Embodied in Eiffel

The ideas of pre- and post-conditions are embodied in the keywords require and ensure in the Eiffel programming language.

Eiffel compilers embed code to test these at run time (can be selectively turned on and off by compiler switches).

Failure leads to diagnostic information being displayed.

Use of assert

We can simulate the effects of require and ensure by use of assert statement, provided by many programming-language implementations:

assert(b), where b is a Boolean expression.•If b is true, all is well•If b is false, program halted and message displayed.

Sometimes assert has a second parameter, which is message to be displayed.

Example of use of assert

function IntegerSqrt(x: integer): integer;

begin

assert (x >= 0);

calculate result

assert (result*result <= x and x < (result+1)*(result+1) );

return result;

end;

A new style of programming: ‘offensive’!

Practical context for the ‘formal’, Hoare style.

An ‘attitude’ – the opposite of ‘defensive programming’ – Meyer calls it ‘offensive programming!’.

Pre- and post-conditions are used to specify a (business) contract between the supplier of a software component and the component’s clients.

Design by Contract by Example, Richard Mitchell and Jim McKim, Addison Wesley, 2002

Since ‘Design by Contract’ is a trademark, it is often referred to as 'Programming by Contract'.

New style

With Design by Contract™ we don’t check preconditions:

Responsibility of client to ensure.

Example:function DaysEarlier(y1, m1, d1, y2, m2, d2: integer); integer;

{ return number of days by which

date y1-m1-d1 is earlier than date y2-m2-d2}

This formalised as …

function DaysEarlier(y1, m1, d1, y2, m2, d2: integer); integer;

{ require

y1-m1-d1 denotes a date and

y2-m2-d2 denotes a date

ensure

result is number of days by which

y1-m1-d1 is earlier than y2-m2-d2 }

‘Defensive’ programming

Always check parameters.

Problem, what if there is nothing to be done in case of error?

What is Sqrt(-16.0)? (real square-root, not complex)

What do you think of this (real) example?function Sqrt(x: real): real;

begin

if x < 0 then x := -x:

calculate square-root of x

DaysEarlier: ‘defensive’ version

function DaysEarlier(y1, m1, d1, y2, m2, d2: integer); integer;

{ return number of days by which

y1-m1-d1 is earlier than y2-m2-d2

or -999 if either or both not a valid date}

days := DaysEarlier(2012, 11, 02, 1943, 06, 31);

if days = -999 then writeln(‘One or both dates not valid’)

else writeln(‘days earlier is’, days);

Says ‘One or both dates not valid’! What is wrong?

Problem

days := DaysEarlier(2012, 05, 21, 2010, 02, 07);

if days = -999 then writeln(‘One or both dates not valid’)

else writeln(‘days earlier is’, days);

Says ‘One or both dates not valid’! What is wrong?

True story: Took three weeks to solve!

Java Modeling Language: JML

The ideas of Design by Contract and Eiffel are extended and made available for Java in

JML: Java Modeling Language

http://www.eecs.ucf.edu/~leavens/JML/

Lots of @ and \.

Includes quantifiers (, , , …)

Following examples are from:An overview of JML tools and applications

Lilian Burdy1, Yoonsik Cheon2, David R. Cok3, Michael D. Ernst4, Joseph R. Kiniry5, Gary T.Leavens6?, K. Rustan M. Leino7, Erik Poll51

Software Tools for Technology Transfer

JML: Special comments

JML is simply extending Java

uses special comments with lots of @, \

Keywords are

requires

ensures

\ result

\ old(x)

\ forall

JML example

/*@ requires amount >= 0;

@ assignable balance;

@ ensures balance == \old(balance) - amount

@ && \result == balance;

@*/

int debit(int amount) throws PurseException {

if (amount <= balance) {

balance -= amount;

return balance;

} else { throw new PurseException("overdrawn by " + amount);

}

}

JML quantifiers

/*@ requires 0 < mb && 0 <= b && b <= mb

@ && p != null && p.length == 4

@ && (\forall int i; 0 <= i && i < 4;

@ 0 <= p[i] && p[i] <= 9);

@ assignable MAX_BALANCE, balance, pin;

@ ensures MAX_BALANCE == mb && balance == b

@ && (\forall int i; 0 <= i && i < 4; p[i] == pin[i]);

@*/

Purse(int mb, int b, byte[] p) {

MAX_BALANCE = mb; balance = b; pin = (byte[]) p.clone();

‘Symbol abuse’?

Provers

Showing that assertions (requires, ensures ..) are true when program runs only demonstrates correctness for chosen test cases.

(Dijkstra famously observed; testing can show presence of errors, not their absence’)

Much better is to use software tools called provers, that reason about whether program matches its specification.

These exist for JML and for Spec#.

Spec#

‘Spec sharp’: based on the C# programming language.

http://research.microsoft.com/en-us/projects/specsharp/

Similar to JML, but neater syntax, since a new language based on C#; not just C# with special comments.

Spec# is available (free of charge) as a plug-in to Visual Studio™.

Spec# keywords

requires

ensures

result

old(x)

forall

exists

Simple example of Spec#

int max(int x, int y)

ensures (result == x && x >= y) || (result == y && y >= x);

{

if (x >= y) return x;

else return y;

}

Note: no requires, because no precondition.

Is this correct?

What about when x == y?

Easy way to use provers

http://rise4fun.com/specsharp/

What if it is wrong?

int max(int x, int y)

ensures (result == x && x > y) || (result == y && y > x);

{

if (x >= y) return x;

else return y;

}

What about when x == y?

Result from Spec#

Unsatisfied postcondition

Example with a loop: DivMod

void DivMod(int x, int y, ref int q, ref int r)

requires x >= 0 && y > 0;

ensures x == q*y + r && 0 <= r && r < y;

{ r= x; q= 0;

while (r >= y)

invariant x == q*y + r && 0 <= r;

{

r= r - y; q= q + 1;

}

}

invariant will be true at this point every time round.

Spec# happy

Quantifiers and ranges in Spec#

forall

exists

Ranges

int i in (m..n) means: all i m <= i <= n

int i in (m:n) means: all i m <= i < n

Second form useful; saves lots of -1’s

What do you think of this?

static int Min(int[] a)

requires a != null && a.Length != 0;

ensures a == old(a) && (forall {int i in (0:a.Length); result <= a[i]};

{ int min = 0;

for (int j = 0; j < a.Length; j++)

invariant forall {int i in (0:j); min <= a[i]};

if (a[j] < min)

min = a[j];

return min;

}

Example with quantifiers

int First(int[] a, int x)

ensures forall{k in (0: result); a[k] != x} &&

(result == -1 || 0 <= result && result < a.Length && a[result] == x);

{

int i;

i = 0;

while (i != a.Length && a[i] != x)

invariant 0 <= i && i <= a.Length && forall{k in (0: i); a[k] != x};

{

i++;

}

if (i == a.Length) return -1; else return i;

}

Further useful feature of Spec#

What does this do?

float AverageLength(string [ ] s)

{

int sum = 0;

for (int i = 0; i < s.Length; i++)

sum += s[i].Length;

return float(sum)/s.Length;

}

What is it precondition?

AverageLength

Returns average length of the strings in array of strings s.

Precondition? s.Length > 0float AverageLength(string [] s)

requires s.Length > 0;

{ int sum = 0;

for (int i = 0; i < s.Length; i++)

sum += s[i].Length;

return float(sum)/s.Length;

}

Anything else?

Precondition of AverageLength

s.Length > 0

But arrays implemented by pointers in Java, C# …

So we need to add

s != null

s != null

float AverageLength(string [] s)

requires s != null &&

s.Length > 0;

{ int sum = 0;

for (int i = 0; i < s.Length; i++)

sum += s[i].Length;

return float(sum)/s.Length;

}

s[j] != null

But strings implemented by pointers in Java, C# …

So we need to add

s[i] != null for all the indexes of s

forall{int j in (0: s.Length); s[j] != null};

s[j] != null

float AverageLength(string [] s)

requires

s != null &&

forall{int j in (0: s.Length); s[j] != null} &&

s.Length > 0;

{ int sum = 0;

for (int i = 0; i < s.Length; i++)

sum += s[i].Length;

return float(sum)/s.Length;

}

Non-null pointers

Very often we require a pointer not to be null.

Easy to forget;

Untidy to check or to add as precondition.

Special syntax in Spec# !

float AverageLength(string ! [] ! s)

requires s.Length > 0;

! means must be non-null type.

Enforced by compiler.

Proving termination

What we have shown so far is partial correctness.; program satisfies its specification, if it terminates.

We prove termination by finding a relationship between the variables on a loop that is initially non-negative and that is strictly reduced by each iteration.

No support for this in Spec#, but can make our own by introducing auxiliary variables to hold value of bound.

Example of bound in Spec#

void DivMod(int x, int y, ref int q, ref int r)

requires x >= 0 && y > 0;

ensures x == q*y + r && 0 <= r && r < y;

{ int bound; r= x; q= 0;

while (r >= y)

invariant x == q*y + r && 0 <= r &&

bound = r && bound >= 0;

{ r= r - y; q= q + 1;

assert(r < bound);

}

}

Unresolved so far (by me, at least)…

assert does run-time check. How to check termination without resorting to run-time check.

How to specify processes that modify a structure (such as sorting). Need to be able to use old (or equivalent) inside the body (old only useable in postcondition).

Summary

Techniques are not new – 1960’s

Not widely known outside universities.

Not even known to nuclear-physicist researchers in England.

Now taken seriously by Microsoft in Spec# and similar projects and in Eiffel and JML.

Advantages:

Can get programs right much earlier in development hence much less time in debugging.

Reduced danger of erroneous programs.

Current use

• JKU Linz course: Romanian-speaking Hungarian students.

• Politechnika Warszawska• P00401 Formal Software Engineering• U08186 Advanced Object-Oriented Programming

Contact information

www.brookes.ac.uk

David Lightfoot

Email: dlightfoot@brookes.ac.uk

Telephone: +44 18 65 48 45 39

School of Technology

Oxford Brookes University

Wheatley Campus

Oxford OX33 1HX

United Kingdom

Dziękuję!