A SAT characterization of
boolean-program correctness
K. Rustan M. Leino
Microsoft Research, Redmond, WA
14 Nov 2002
IFIP WG 2.4 meeting, Schloβ Dagstuhl, Germany
Motivation
This program has performed an illegal operation.
If the problem persists, please contact the vendor.
The SLAM toolkit
Tom Ball, Sriram Rajamani, et al., Microsoft Research
Properties of interest:
Device driver
x (C “program)
resource is locked”
y t>0
Abstraction (boolean program)
APIERR DevGetStatus(LPSTATUS status)
{
unsigned int t;
struct _Info info;
for (i = 0; i < SPIGOT_COUNT; i++)
{
APIERR err;
info.cbSize = sizeof(struct _Info);
info.iSpigot = i;
err = W_GetStatus(&info);
if (err != APIERR_Success)
{
return err;
}
t |= info.fStatus & SPIGOT_ALIVE;
}
status->result = t | 0x2055;
return APIERR_Success;
}
APIERR DevGetStatus(LPSTATUS status)
{
unsigned int t;
struct _Info info;
for (i = 0; i < SPIGOT_COUNT; i++)
{
APIERR err;
info.cbSize = sizeof(struct _Info);
info.iSpigot = i;
err = W_GetStatus(&info);
if (err != APIERR_Success)
{
return err;
}
t |= info.fStatus & SPIGOT_ALIVE;
}
status->result = t | 0x2055;
return APIERR_Success;
}
APIERR W_GetStatus(struct _Info * pinfo)
{
APIERR err;
bool fChanged = TRUE;
do
{
err = DevRegisterColumn(pinfo, TRUE);
if (err != APIERR_Success)
{
return err;
}
if (pinfo->huwMagi < 10)
{
fChanged = FALSE;
}
else
{
err = DevReleaseColumn(pinfo);
}
} while (fChanged);
return DevReleaseColumn(pinfo);
}
APIERR W_GetStatus(struct _Info * pinfo)
{
APIERR err;
bool fChanged = TRUE;
do
{
err = DevRegisterColumn(pinfo, TRUE);
if (err != APIERR_Success)
{
return err;
}
if (pinfo->huwMagi < 10)
{
fChanged = FALSE;
}
else
{
err = DevReleaseColumn(pinfo);
}
} while (fChanged);
return DevReleaseColumn(pinfo);
}
y := false;
assert x;
x := true;
x := x y;
The SLAM toolkit
Tom Ball, Sriram Rajamani, et al., Microsoft Research
Device driver (C program)
Abstraction (boolean program)
APIERR DevGetStatus(LPSTATUS status)
{
unsigned int t;
struct _Info info;
for (i = 0; i < SPIGOT_COUNT; i++)
{
APIERR err;
info.cbSize = sizeof(struct _Info);
info.iSpigot = i;
err = W_GetStatus(&info);
if (err != APIERR_Success)
{
return err;
}
t |= info.fStatus & SPIGOT_ALIVE;
}
status->result = t | 0x2055;
return APIERR_Success;
}
APIERR DevGetStatus(LPSTATUS status)
{
unsigned int t;
struct _Info info;
for (i = 0; i < SPIGOT_COUNT; i++)
{
APIERR err;
info.cbSize = sizeof(struct _Info);
info.iSpigot = i;
err = W_GetStatus(&info);
if (err != APIERR_Success)
{
return err;
}
t |= info.fStatus & SPIGOT_ALIVE;
}
status->result = t | 0x2055;
return APIERR_Success;
}
APIERR W_GetStatus(struct _Info * pinfo)
{
APIERR err;
bool fChanged = TRUE;
do
{
err = DevRegisterColumn(pinfo, TRUE);
if (err != APIERR_Success)
{
return err;
}
if (pinfo->huwMagi < 10)
{
fChanged = FALSE;
}
else
{
err = DevReleaseColumn(pinfo);
}
} while (fChanged);
return DevReleaseColumn(pinfo);
}
APIERR W_GetStatus(struct _Info * pinfo)
{
APIERR err;
bool fChanged = TRUE;
do
{
err = DevRegisterColumn(pinfo, TRUE);
if (err != APIERR_Success)
{
return err;
}
if (pinfo->huwMagi < 10)
{
fChanged = FALSE;
}
else
{
err = DevReleaseColumn(pinfo);
}
} while (fChanged);
return DevReleaseColumn(pinfo);
}
y := false;
assert
assertx;
x
x := true;
x := x y;
The SLAM toolkit
Tom Ball, Sriram Rajamani, et al., Microsoft Research
Device driver (C program)
Abstraction (boolean program)
APIERR DevGetStatus(LPSTATUS status)
{
unsigned int t;
struct _Info info;
for (i = 0; i < SPIGOT_COUNT; i++)
{
APIERR err;
info.cbSize = sizeof(struct _Info);
info.iSpigot = i;
err = W_GetStatus(&info);
if (err != APIERR_Success)
{
return err;
}
t |= info.fStatus & SPIGOT_ALIVE;
}
status->result = t | 0x2055;
return APIERR_Success;
}
APIERR DevGetStatus(LPSTATUS status)
{
unsigned int t;
struct _Info info;
for (i = 0; i < SPIGOT_COUNT; i++)
{
APIERR err;
info.cbSize = sizeof(struct _Info);
info.iSpigot = i;
err = W_GetStatus(&info);
if (err != APIERR_Success)
{
return err;
}
t |= info.fStatus & SPIGOT_ALIVE;
}
status->result = t | 0x2055;
return APIERR_Success;
}
APIERR W_GetStatus(struct _Info * pinfo)
{
APIERR err;
bool fChanged = TRUE;
do
{
err = DevRegisterColumn(pinfo, TRUE);
if (err != APIERR_Success)
{
return err;
}
if (pinfo->huwMagi < 10)
{
fChanged = FALSE;
}
else
{
err = DevReleaseColumn(pinfo);
}
} while (fChanged);
return DevReleaseColumn(pinfo);
}
APIERR W_GetStatus(struct _Info * pinfo)
{
APIERR err;
bool fChanged = TRUE;
do
{
err = DevRegisterColumn(pinfo, TRUE);
if (err != APIERR_Success)
{
return err;
}
if (pinfo->huwMagi < 10)
{
fChanged = FALSE;
}
else
{
err = DevReleaseColumn(pinfo);
}
} while (fChanged);
return DevReleaseColumn(pinfo);
}
y := false;
assert
assertx;
x
x := true;
real error
x := x y;
The SLAM toolkit
Tom Ball, Sriram Rajamani, et al., Microsoft Research
Device driver (C program)
Abstraction (boolean program)
APIERR DevGetStatus(LPSTATUS status)
{
unsigned int t;
struct _Info info;
for (i = 0; i < SPIGOT_COUNT; i++)
{
APIERR err;
info.cbSize = sizeof(struct _Info);
info.iSpigot = i;
err = W_GetStatus(&info);
if (err != APIERR_Success)
{
return err;
}
t |= info.fStatus & SPIGOT_ALIVE;
}
status->result = t | 0x2055;
return APIERR_Success;
}
APIERR DevGetStatus(LPSTATUS status)
{
unsigned int t;
struct _Info info;
for (i = 0; i < SPIGOT_COUNT; i++)
{
APIERR err;
info.cbSize = sizeof(struct _Info);
info.iSpigot = i;
err = W_GetStatus(&info);
if (err != APIERR_Success)
{
return err;
}
t |= info.fStatus & SPIGOT_ALIVE;
}
status->result = t | 0x2055;
return APIERR_Success;
}
APIERR W_GetStatus(struct _Info * pinfo)
{
APIERR err;
bool fChanged = TRUE;
do
{
err = DevRegisterColumn(pinfo, TRUE);
if (err != APIERR_Success)
{
return err;
}
if (pinfo->huwMagi < 10)
{
fChanged = FALSE;
}
else
{
err = DevReleaseColumn(pinfo);
}
} while (fChanged);
return DevReleaseColumn(pinfo);
}
APIERR W_GetStatus(struct _Info * pinfo)
{
APIERR err;
bool fChanged = TRUE;
do
{
err = DevRegisterColumn(pinfo, TRUE);
if (err != APIERR_Success)
{
return err;
}
if (pinfo->huwMagi < 10)
{
fChanged = FALSE;
}
else
{
err = DevReleaseColumn(pinfo);
}
} while (fChanged);
return DevReleaseColumn(pinfo);
}
y := false;
assert x;
x := true;
x := x y;
infeasible path
The SLAM toolkit
Tom Ball, Sriram Rajamani, et al., Microsoft Research
Properties of interest:
Device driver (C program)
x “resource is locked”
y t>0
z p ≠ NULL
Abstraction (boolean program)
APIERR DevGetStatus(LPSTATUS status)
{
unsigned int t;
struct _Info info;
for (i = 0; i < SPIGOT_COUNT; i++)
{
APIERR err;
info.cbSize = sizeof(struct _Info);
info.iSpigot = i;
err = W_GetStatus(&info);
if (err != APIERR_Success)
{
return err;
}
t |= info.fStatus & SPIGOT_ALIVE;
}
status->result = t | 0x2055;
return APIERR_Success;
}
APIERR DevGetStatus(LPSTATUS status)
{
unsigned int t;
struct _Info info;
for (i = 0; i < SPIGOT_COUNT; i++)
{
APIERR err;
info.cbSize = sizeof(struct _Info);
info.iSpigot = i;
err = W_GetStatus(&info);
if (err != APIERR_Success)
{
return err;
}
t |= info.fStatus & SPIGOT_ALIVE;
}
status->result = t | 0x2055;
return APIERR_Success;
}
APIERR W_GetStatus(struct _Info * pinfo)
{
APIERR err;
bool fChanged = TRUE;
do
{
err = DevRegisterColumn(pinfo, TRUE);
if (err != APIERR_Success)
{
return err;
}
if (pinfo->huwMagi < 10)
{
fChanged = FALSE;
}
else
{
err = DevReleaseColumn(pinfo);
}
} while (fChanged);
return DevReleaseColumn(pinfo);
}
APIERR W_GetStatus(struct _Info * pinfo)
{
APIERR err;
bool fChanged = TRUE;
do
{
err = DevRegisterColumn(pinfo, TRUE);
if (err != APIERR_Success)
{
return err;
}
if (pinfo->huwMagi < 10)
{
fChanged = FALSE;
}
else
{
err = DevReleaseColumn(pinfo);
}
} while (fChanged);
return DevReleaseColumn(pinfo);
}
y := false;
assert x;
x := true;
z := true;
x := x y;
if (z) …
Boolean programs
Prog ::= var Id* ; Block*
Block ::= LabelId : Stmt* goto LabelId*
Stmt ::= Id := Expr
| assert Expr
| assume Expr
Expr ::= false | true | Id | Expr
| Expr Expr | Expr Expr
Example
var x, y;
A: x := true; goto B
B: assert x; x := x y; goto B or C
C:
A: x := true
B: assert x;
x := x y
C:
Semantics: Weakest preconditions
For any statement S and postcondition Q,
wp(S,Q) characterizes those pre-states
from which execution is guaranteed:
not to go wrong, and
either the execution doesn’t terminate or it
terminates in a state satisfying Q
S
wp(S,Q)
Q
Semantics: Weakest preconditions
wp(x := E, Q) = Q[x:=E]
wp(assert E, Q) = E Q
wp(assume E, Q) = E Q
wp(skip, Q) = Q
wp(S;T, Q) = wp(S, wp(T,Q))
wp(goto labels, Q) =
( L labels :: wp(L,Q))
Semantics of blocks
What I write:
For any block:
var w;
…
What I really mean:
L: S; goto labels
…
introduce a boolean function L, such that:
L(w) =
(w :: L(w) =
wp(S, (Glabels :: G(w)))
wp(S, (Glabels :: G(w))) )
or equivalently:
L = (λw :: wp(S, (Glabels :: G(w))) )
Example
A(x,y)
B(x,y)
C(x,y)
C(x,y)
=
=
=
=
B(true,y)
wp(x
:= true, B(x,y))
xwp(assert
B(x y,x;y)x:=
C(x
x y,
y, y)
trueB(x,y) C(x,y))
wp(skip, true)
A: x := true
B: assert x;
x := x y
C:
Equations with multiple solutions
A, B, C :
A(x,y) = B(true, y)
B(x,y) = x B(xy, y) C(xy, y)
C(x,y) = true
Solution 0:
A(x,y) =
The unknowns
false
B(x,y) = false
C(x,y) = true
We want the weakest solution
Solution 1:
A(x,y) = y
B(x,y) = x y
C(x,y) = true
Weakest solution
A,B,C : A(x,y) = B(true, y)
B(x,y) = x B(xy, y) C(xy, y)
C(x,y) = true
Weakest solution
A,B,C : A = (λx,y :: B(true, y))
B = (λx,y :: x B(xy, y) C(xy, y))
C = (λx,y :: true)
F(A,B,C)
G(A,B,C)
H(A,B,C)
Weakest solution/fixpoint
(λx,y :: B(true, y))
A,B,C : A = F(A,B,C)
B = G(A,B,C)
(λx,y :: x B(xy, y) C(xy, y))
Weakest solution of A,B,C
C = H(A,B,C)
(λx,y :: true)
where
F = (λx,y :: B(true, y))
F(A,B,C)
F
G(A,B,C)
H(A,B,C)
G = (λx,y :: xG
B(xy, y) H
C(xy, y))
H = (λx,y :: true)
Weakest fixpoint of F,G,H
Program correctness
A program with variables w and start block A
is correct iff:
(w :: A(w))
That is, the program has an error iff:
A(w)
is satisfiable.
boolean equations, satisfiability
SAT
functions, weakest solutions
SAT
Equations over a closed set of “terms”
Using the definitions:
A(x,y) = B(true, y)
B(x,y) = x B(xy, y) C(xy, y)
C(x,y) = true
We produce:
A(x,y) = B(true, y)
B(true, y) = true B(truey, y) C(truey, y)
B(truey, y) = truey B(trueyy, y) C(trueyy, y)
C(truey, y) = true
Point functions
A function:
f(w) = f(false) f(w)
can be expressed as two point functions :
ffalse = ffalse ffalse
ftrue = ffalse ftrue
Point-function equations
A set of equations:
f : f(w) = f(false) f(w)
can be expressed as:
ffalse, ftrue : ffalse = ffalse ffalse
ftrue = ffalse ftrue
A fixpoint theorem
Given a function F on a complete lattice,
if F is continuous, then its weakest fixpoint
exists and is given by:
Fk( T )
for some natural number k.
lattice height
T
k is the fixpoint depth of F
fixpoint depth lattice height
F k( T )
Computing fixpoints: outward
T
Suppose fixpoint depth of F is 3
{ apply F }
F(T)
{ apply F }
produced in previous step
F(F(T))
{ apply F }
F(F(F(T)))
{ apply F }
F(F(F(F(T))))
equal to each other—weakest fixpoint of F
Computing fixpoints: inward
Suppose fixpoint depth of F is 3
{ replace with F() }
F()
{ replace with F() }
F(F())
produced in previous step
{ replace with F() }
F(F(F()))
no need for further applications of F
{ replace with T }
F(F(F(T)))
weakest fixpoint of F
Multiple unknowns
a,b :
a = F(a,b)
b = G(a,b)
Suppose fixpoint depths of F,G are 2,1
Weakest solution for a is:
a00
F(a10
, b10
)
applications of G , b ))
F(F(a20, Number
b20 of enclosing
), G(a
11
11
F(F(T , G(a21, b21)), G(F(a21, b21), T ))
F(F(T
, G(T
, T of)),
Number
of enclosing
applications
F G(F(T , T ), T ))
Special instance
Lattice of booleans has height 1
If F returns a boolean, then F’s fixpoint
depth is at most 1
and so F’s weakest fixpoint is F(T)
Back to our problem
Using the definitions:
A(x,y) = B(true, y)
B(x,y) = x B(xy, y) C(xy, y)
C(x,y) = true
We produce:
Ax,y = Btrue,y
Btrue,y = true Btruey,y Ctruey,y
By,y = y Byy,y Cyy,y
true
Back to our problem
Using the definitions:
A(x,y) = B(true, y)
B(x,y) = x B(xy, y) C(xy, y)
C(x,y) = true
We produce:
Ax,y = Btrue,y
Btrue,y = true Btruey,y Ctruey,y
By,y = y true Cyy,y
Cy,y = true
Leibniz constraints
Being a function means, for any f:
(w,w’ :: (w=w’) (f(w)=f(w’)))
“Leibniz’s rule”
So when we have:
Btrue,y = …
By,y = …
we also generate the following Leibniz constraint :
(true=y) (y=y) (Btrue,y = By,y)
SAT formula
From the closed set of terms:
a
b
b’
c
Ax,y = Btrue,y
Btrue,y = true Btruey,y Ctruey,y
By,y = y true Cyy,y
Cy,y = true
we produce the following SAT equations:
negated start symbol:
a
a=b
A(x,y)
b = true b’ c
b’ = y true c
c = true
Leibniz constraint
y (b=b’)
Summary
Boolean program, whose semantics is defined by
weakest solution of weakest-precondition equations
Translate to SAT problem:
Instantiate equations to get a closed set of terms
Replace nested recursive instantiations by true
Conjoin negated start symbol and Leibniz constraints
Write point functions as propositional variables
Check for satisfiability
Performance? Heuristics?
Are Leibniz constraints really needed?
Better encoding of procedures?
Complexity
With N blocks and K variables:
each boolean function has K arguments, each
a boolean expression
K
there are 22 different boolean expressions
So, there are
K
K
2
N·2 ·
different terms
Suppose each of the 2K initial states were
considered individually:
each boolean-function argument can then be
folded into one of the 2 boolean constants
Then, there are only 2K·N·2K different terms
Symbolic vs. explicit-state checking
The following equality (and others?) can
be exploited heuristically to try to get a
good balance:
f(P, Q, R)
=
(P f(true, Q, R)) (P f(false, Q, R))
Summary
Boolean program, whose semantics is defined by
weakest solution of weakest-precondition equations
Translate to SAT problem:
Instantiate equations to get a closed set of terms
Replace nested recursive instantiations by true
Conjoin negated start symbol and Leibniz constraints
Write point functions as propositional variables
Check for satisfiability
Performance heuristics: symbolic vs. explicit-state
Other heuristics?
Are Leibniz constraints really needed?
Better encoding of procedures?
© Copyright 2026 Paperzz