Technologies for finding errors in object-oriented software

Technologies for finding errors
in object-oriented software
K. Rustan M. Leino
Microsoft Research, Redmond, WA
Lecture 2
Summer school on Formal Models of Software
4 Sep 2003, Tunis, Tunisia
Review: Procedural
language constructs
•
•
•
•
6 primitive commands
Many shorthands
Arrays are variables with structure
Procedure declarations and
specification
Procedural
Object-oriented
• x := E
• x: T
• P(x,y,z)
• o.f := E
• o: T
• o.m(y,z)
Object types and subtyping
•
•
•
•
D
set of type names
typeof : object  D
<:
partial order on D
istype(o, T) = typeof(o) <: T
Note:
T <: U ⇒
(istype(o, T) ⇒ istype(o, U))
An object-oriented
programming notation
C ::=
|
|
|
|
|
|
|
w := E
assert P
var w in C end
C0 ; C1
if P then C0 else C1 end
o.f := E
x := new(T)
w := o.m(E0, E1)
Object fields are maps
• Java: class T extends U { ... f: X ... }
• Here: T ∈ D
T <: U
f: T  X
• x := o.f
• o.f := E
=
=
=
x := f[o]
f[o] := E
f := store(f, o, E)
Aliasing (pointer sharing)
• (o.f := 12 ;
p.g := 14 ;
assert o.f = 12).true ≡ true
• (o.f := 12 ;
p.f := 14 ;
assert o.f = 12).true ≡ o≠p
Allocation
• alloc : object  bool
• x := new(T) =
change x such that
typeof(x) = T ∧ ¬alloc[x] ;
alloc[x] := true
Example
• (o.f := 12 ;
p := new(T);
p.f := 14 ;
assert o.f = 12).true ≡ alloc[o]
Methods declarations and
method implementations
method T :: m(x,y,z) returns (r,s,t)
requires P modifies w ensures Q
= proc m(x,y,z) receiver
returns
(r,s,t)
parameter
(“this”, “current”, “self”)
spec assert istype(x, T) ; w:[P, Q]
mimpl U :: m(x,y,z) returns (r,s,t) is C
= impl m(x,y,z) returns (r,s,t) is
assume istype(x, U) ; C
Method call
• w := o.m(E0, E1) =
w := m(o, E0, E1)
Example: union-find
m
element
n
b
a
equivalence
class
c
d
k
representative
element
p
q
l
o
r
e
f
i
h
g
j
Example: union-find
find(c) = a
m
n
b
a
c
k
d
p
q
l
o
r
e
f
i
h
g
j
Example: union-find
m
b
a
n
union(p, h):
c
k
d
p
q
l
o
r
e
f
i
h
g
j
Example, specification
class UnionFind <: Object
field nClasses, nElements, ...
method UnionFind :: init(uf, size)
requires 0 ≦ size
modifies uf.nClasses, uf.nElements, ...
ensures uf.nClasses = uf.nElements = size
method UnionFind :: find(uf, c) returns (r)
requires 0 ≦ c < uf.nElements
ensures 0 ≦ r < uf.nClasses
method UnionFind :: union(uf, c, d)
requires 0 ≦ c ≦ uf.nElements ∧
0 ≦ d ≦ uf.nElements
modifies uf.nClasses
ensures uf.nClasses = uf.nClasses0 ∨
uf.nClasses = uf.nClasses0 - 1
Example, client
var uf, r0, r1, r2 in
uf := new(UnionFind) ;
uf.init(12) ;
uf.union(3, 8) ;
uf.union(8, 6) ;
uf.union(10, 11) ;
r0 := uf.find(3) ;
r1 := uf.find(5) ;
r2 := uf.find(6) ;
assert r0 ≠ r1 ;
assert r0 = r2
end
Example, implementation
class StandardUnionFind <: UnionFind
mimpl StandardUnionFind :: find(uf, c) returns (r) is …
class FastUnionFind <: UnionFind
mimpl FastUnionFind :: find(uf, c) returns (r) is …
What's missing?
•
•
•
•
•
•
null
type casts
types of parameters
types of fields
properties of allocation
...
null
New definitions:
• istype(o, T) =
o = null ∨ typeof(o) <: T
• o.f := E =
assert o ≠ null ;
f[o] := E
Type casts
• x := typecast(o, T) =
assert istype(o, T) ;
x := o
Example: binary method
class T <: Object
method T :: equal(x, y) returns (b)
requires typeof(x) = typeof(y)
class U <: T
mimpl U :: equal(x, y) returns (b) is
var yy in
yy := typecast(y, U) ;
// compare x and yy ...
end
Types of parameters
method OutputStream :: putText(wr, s) …
method print(t: T, wr: OutputStream) …
method T :: print(t, wr)
requires istype(wr, OutputStream)
Types of fields
field T :: f: U
// class T { … f: U … }
(∀f, T, U ・
isField(f, T, U) 
(∀o ・ istype(f[o], U)))
Types of fields
field T :: f: U
// class T { … f: U … }
(∀f, T, U ・
isField(f, T, U) 
(∀o ・ istype(o, T) ⇒
istype(f[o], U)))
Initially: assume isField(f, T, U)
Whenever f is changed:
assume isField(f, T, U)
More about allocation
• initially, for every parameter x:
assume alloc[x]
• mimpl T :: m(x) is
var y in
y := new(T) ;
assert x ≠ y
end
Even more about allocation
• mimpl T :: m(x) is
var y in
y := new(T) ;
assert x.f ≠ y
end
Even more about allocation
• mimpl T :: m(x) is
var y in
y := new(T) ;
assert x.f ≠ y
end
• isField(f, T, U, a) 
… ∧ (∀ o ・ a[o] ⇒ a[f[o]] )
• Initially and whenever f or alloc is changed:
assume isField(f, T, U, alloc)
Exercise
• Prove the following program correct:
method p(x) modifies x.f
method m(x) modifies x.f
mimpl m(x) is
var y in
x.p() ;
y := new(T) ;
assert x.f ≠ y
end
Strengthening specifications
class T <: Object
method T :: m(x, y, z)
requires P modifies w ensures Q
class U <: T
method U :: m(x, y, z)
requires P modifies w ensures Q ∧ R
... u.m(y, z) ; assert R ...
?
Strengthening specifications
class T <: Object
method T :: m(x, y, z)
requires P modifies w ensures Q
class U <: T
method U :: n(x, y, z)
requires P modifies w ensures Q ∧ R
mimpl U :: m(x, y, z) is x.n(y, z)
... u.n(y, z) ; assert R ...
Two-state postconditions
• ensures x.f0 < x.f
= ensures f0[x] < f[x]
= ensures select(f0, x) < select(f, x)
Modifies and objects
• modifies x.f =
modifies f
ensures (∀o ・ o.f = o.f0 ∨ o = x)
Exercise
class T <: Object
field f
method T :: m(x, y, z)
requires P modifies x.f ensures Q
class U <: T
field g
method U :: m(x, y, z)
requires P modifies x.f, x.g ensures Q
?
What else is missing?
•
•
•
•
Information hiding
Correctness of data representations
Programming methodology
...