-
Notifications
You must be signed in to change notification settings - Fork 38
Examples
Consider the program test.c
:
extern void __CRAB_assume (int);
extern void __CRAB_assert(int);
extern int __CRAB_nd(void);
int main() {
int k = __CRAB_nd();
int n = __CRAB_nd();
__CRAB_assume (k > 0);
__CRAB_assume (n > 0);
int x = k;
int y = k;
while (x < n) {
x++;
y++;
}
__CRAB_assert (x >= y);
__CRAB_assert (x <= y);
return 0;
}
Clam provides a Python script called clam.py
. Type the command:
clam.py test.c
Important: the first thing that clam.py
does is to compile
the C program into LLVM bitcode by using Clang. Since Crab-llvm is
based on LLVM 10, the version of clang must be 10 as well.
If the above command succeeds, then the output should be something like this:
Invariants for main
_1:
/**
INVARIANTS: ({}, {})
**/
_2 =* ;
_3 =* ;
_4 = (-_2 <= -1);
zext _4:1 to _call:32;
_6 = (-_3 <= -1);
zext _6:1 to _call1:32;
x.0 = _2;
y.0 = _2;
goto _x.0;
/**
INVARIANTS: ({}, {_call -> [0, 1], _call1 -> [0, 1], _2-x.0<=0, y.0-x.0<=0, x.0-_2<=0, y.0-_2<=0, _2-y.0<=0, x.0-y.0<=0})
**/
_x.0:
/**
INVARIANTS: ({}, {_call -> [0, 1], _call1 -> [0, 1], _2-x.0<=0, y.0-x.0<=0, _2-y.0<=0, x.0-y.0<=0})
**/
goto __@bb_1,__@bb_2;
__@bb_1:
assume (-_3+x.0 <= -1);
goto _10;
_10:
/**
INVARIANTS: ({}, {_call -> [0, 1], _call1 -> [0, 1], _2-x.0<=0, y.0-x.0<=0, _2-y.0<=0, x.0-y.0<=0, x.0-_3<=-1, _2-_3<=-1, y.0-_3<=-1})
**/
_11 = x.0+1;
_br2 = y.0+1;
x.0 = _11;
y.0 = _br2;
goto _x.0;
/**
INVARIANTS: ({}, {_call -> [0, 1], _call1 -> [0, 1], _br2-y.0<=0, _11-y.0<=0, _2-y.0<=-1, x.0-y.0<=0, x.0-_3<=0, _2-_3<=-1, y.0-_3<=0, _11-_3<=0, _br2-_3<=0, x.0-_11<=0, _2-_11<=-1, y.0-_11<=0, _br2-_11<=0, y.0-_br2<=0, _2-_br2<=-1, x.0-_br2<=0, _11-_br2<=0, _11-x.0<=0, _br2-x.0<=0, _2-x.0<=-1, y.0-x.0<=0})
**/
__@bb_2:
assume (_3-x.0 <= 0);
y.0.lcssa = y.0;
x.0.lcssa = x.0;
goto _y.0.lcssa;
_y.0.lcssa:
/**
INVARIANTS: ({}, {_call -> [0, 1], _call1 -> [0, 1], _2-x.0<=0, y.0-x.0<=0, _3-x.0<=0, y.0.lcssa-x.0<=0, x.0.lcssa-x.0<=0, _2-y.0<=0, x.0-y.0<=0, _3-y.0<=0, y.0.lcssa-y.0<=0, x.0.lcssa-y.0<=0, y.0-y.0.lcssa<=0, _2-y.0.lcssa<=0, x.0-y.0.lcssa<=0, _3-y.0.lcssa<=0, x.0.lcssa-y.0.lcssa<=0, x.0-x.0.lcssa<=0, _2-x.0.lcssa<=0, y.0-x.0.lcssa<=0, _3-x.0.lcssa<=0, y.0.lcssa-x.0.lcssa<=0})
**/
_14 = (y.0.lcssa-x.0.lcssa <= 0);
zext _14:1 to _call3:32;
assert (-_call3 <= -1);
_16 = (-y.0.lcssa+x.0.lcssa <= 0);
zext _16:1 to _call4:32;
assert (-_call4 <= -1);
@V_17 = 0;
return @V_17;
/**
INVARIANTS: ({_14 -> true; _16 -> true}, {_call -> [0, 1], _call1 -> [0, 1], _call3 -> [1, 1], _call4 -> [1, 1], @V_17 -> [0, 0], _2-x.0<=0, y.0-x.0<=0, _3-x.0<=0, y.0.lcssa-x.0<=0, x.0.lcssa-x.0<=0, _2-y.0<=0, x.0-y.0<=0, _3-y.0<=0, y.0.lcssa-y.0<=0, x.0.lcssa-y.0<=0, y.0-y.0.lcssa<=0, _2-y.0.lcssa<=0, x.0-y.0.lcssa<=0, _3-y.0.lcssa<=0, x.0.lcssa-y.0.lcssa<=0, x.0-x.0.lcssa<=0, _2-x.0.lcssa<=0, y.0-x.0.lcssa<=0, _3-x.0.lcssa<=0, y.0.lcssa-x.0.lcssa<=0})
**/
It shows the Control-Flow Graph analyzed by Crab together with the
invariants inferred for function main
that hold at the entry and and
the exit of each basic block.
Note that Clam does not provide a translation from the basic block identifiers and variable names to the original C program. The reason is that Clam does not analyze C but instead the corresponding LLVM bitcode generated after compiling the C program with Clang. To help users understanding the invariants Clam provides an option to visualize the CFG of the function described in terms of the LLVM bitcode:
clam.py test.c --llvm-view-cfg
and you should see a screen with a similar CFG to this one:
Since we are interested at the relationships between x
and y
after
the loop, the LLVM basic block of interest is _y.0.lcssa
and the
variables are x.0.lcssa
and y.0.lcssa
, which are simply renamings
of the loop variables x.0
and y.0
, respectively.
With this information, we can look back at the invariants inferred by our tool and see the linear constraints:
x.0.lcssa-y.0.lcssa<=0, ... , y.0.lcssa-x.0.lcssa<=0
that implies the desired invariant x.0.lcssa
= y.0.lcssa
.
Consider the next program:
extern int __CRAB_nd(void);
int a[10];
int main (){
int i;
for (i=0;i<10;i++) {
if (__CRAB_nd ())
a[i]=0;
else
a[i]=5;
}
int res = a[i-1];
return res;
}
and type
clam.py test.c --crab-track=sing-mem --crab-opt=add-invariants --crab-opt-invariants-loc=all -o test.crab.bc
llvm-dis test.crab.bc
The content of test.crab.bc
should be similar to:
define i32 @main() #0 {
entry:
br label %loop.header
loop.header: ; preds = %loop.body, %entry
%i.0 = phi i32 [ 0, %entry ], [ %_br2, %loop.body ]
%crab_2 = icmp ult i32 %i.0, 11
call void @verifier.assume(i1 %crab_2) #2
%_br1 = icmp slt i32 %i.0, 10
br i1 %_br1, label %loop.body, label %loop.exit
loop.body: ; preds = %loop.header
call void @verifier.assume(i1 %_br1) #2
%crab_14 = icmp ult i32 %i.0, 10
call void @verifier.assume(i1 %crab_14) #2
%_5 = call i32 (...)* @__CRAB_nd() #2
%_6 = icmp eq i32 %_5, 0
%_7 = sext i32 %i.0 to i64
%_. = getelementptr inbounds [10 x i32]* @a, i64 0, i64 %_7
%. = select i1 %_6, i32 5, i32 0
store i32 %., i32* %_., align 4
%_br2 = add nsw i32 %i.0, 1
br label %loop.header
loop.exit: ; preds = %loop.header
%_11 = add nsw i32 %i.0, -1
%_12 = sext i32 %_11 to i64
%_13 = getelementptr inbounds [10 x i32]* @a, i64 0, i64 %_12
%_ret = load i32* %_13, align 4
%crab_23 = icmp ult i32 %_ret, 6
call void @verifier.assume(i1 %crab_23) #2
ret i32 %_ret
}
The special thing about the above LLVM bitcode is the existence of
@verifier.assume
instructions. For instance, the instruction
@verifier.assume(i1 %crab_2)
indicates that %i.0
is between 0 and
10 at the loop header. Also, @verifier.assume(i1 %crab_23)
indicates
that the result of the load instruction at block loop.exit
is
between 0 and 5.