Skip to content

Commit 2bd790d

Browse files
authored
Merge pull request #5404 from martin-cs/feature/ai-local-history
Add an abstract interpreter 'history' that gives (local) per-path analysis and / or loop unwinding
2 parents 8a99f23 + e30da66 commit 2bd790d

File tree

23 files changed

+994
-10
lines changed

23 files changed

+994
-10
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#include <assert.h>
2+
3+
int main(int argc, char **argv)
4+
{
5+
int branch;
6+
int x = 0;
7+
8+
if(branch)
9+
{
10+
x = 1;
11+
}
12+
else
13+
{
14+
x = -1;
15+
}
16+
17+
// Merging a constant domain here will make this unprovable
18+
assert(x != 0);
19+
20+
// Some subtle points here...
21+
// The history tracks paths, it is up to the domain to track the
22+
// path conditon / consequences of that path.
23+
//
24+
// We have two paths:
25+
// branch == ?, x == 1
26+
// branch == 0, x == -1
27+
//
28+
// As the domain in the first case can't express branch != 0
29+
// we will wind up with three paths here, including a spurious
30+
// one with branch == 0, x == 1.
31+
if(branch)
32+
{
33+
assert(x == 1);
34+
}
35+
else
36+
{
37+
assert(x == -1);
38+
}
39+
40+
// Working around the domain issues...
41+
// (This doesn't increase the number of paths)
42+
if(x == 1)
43+
{
44+
x--;
45+
}
46+
else
47+
{
48+
x++;
49+
}
50+
51+
// Should be true in all 3 paths
52+
assert(x == 0);
53+
54+
return 0;
55+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CORE
2+
main.c
3+
--verify --recursive-interprocedural --branching 0 --constants --one-domain-per-history
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^\[main.assertion.1\] .* assertion x != 0: SUCCESS$
7+
^\[main.assertion.2\] .* assertion x == 1: SUCCESS$
8+
^\[main.assertion.3\] .* assertion x == -1: FAILURE \(if reachable\)$
9+
^\[main.assertion.4\] .* assertion x == 0: SUCCESS$
10+
--
11+
^warning: ignoring
12+
--
13+
This demonstrates the "lazy merging" that comes from tracking the history of branching
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#include <assert.h>
2+
3+
int main(int argc, char **argv)
4+
{
5+
int branching_array[5];
6+
int x = 1;
7+
8+
if(branching_array[0])
9+
{
10+
++x;
11+
}
12+
// Two paths...
13+
assert(x > 0);
14+
15+
if(branching_array[1])
16+
{
17+
++x;
18+
}
19+
// Four paths...
20+
assert(x > 0);
21+
22+
if(branching_array[2])
23+
{
24+
++x;
25+
}
26+
// Eight paths...
27+
assert(x > 0);
28+
29+
if(branching_array[3])
30+
{
31+
++x;
32+
}
33+
// Paths merge so there will be some paths that will set x to \top
34+
// and so this will be flagged as unknown
35+
assert(x > 0);
36+
// In principle it would be possible to merge paths in such a way
37+
// that the those with similar domains are merged and this would be
38+
// able to be proved. The local_control_flow_history code doesn't
39+
// do this though.
40+
41+
return 0;
42+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CORE
2+
main.c
3+
--verify --recursive-interprocedural --branching 12 --constants --one-domain-per-history
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^\[main.assertion.1\] .* assertion x > 0: SUCCESS$
7+
^\[main.assertion.2\] .* assertion x > 0: SUCCESS$
8+
^\[main.assertion.3\] .* assertion x > 0: SUCCESS$
9+
^\[main.assertion.4\] .* assertion x > 0: UNKNOWN$
10+
--
11+
^warning: ignoring
12+
--
13+
This demonstrates hitting the path limit and the merging of paths
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include <assert.h>
2+
3+
int main(int argc, char **argv)
4+
{
5+
int total = 0;
6+
int n = 50;
7+
int i;
8+
9+
for(i = 0; i < n; ++i)
10+
{
11+
total += i;
12+
}
13+
14+
assert(total == (n * (n - 1) / 2));
15+
16+
return 0;
17+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main.c
3+
--verify --recursive-interprocedural --loop-unwind 0 --constants --one-domain-per-history
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^\[main.assertion.1\] .* assertion total == \(n \* \(n - 1\) / 2\): SUCCESS$
7+
--
8+
^warning: ignoring
9+
--
10+
A basic test of loop unwinding.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#include <assert.h>
2+
3+
int main(int argc, char **argv)
4+
{
5+
int total;
6+
int n;
7+
int i;
8+
9+
total = 0;
10+
n = 8;
11+
for(i = 0; i < n; ++i)
12+
{
13+
total += i;
14+
}
15+
16+
// Unknown due to the limit on unwindings
17+
assert(total == (n * (n - 1) / 2));
18+
19+
// Condense down to one path...
20+
21+
total = 0;
22+
n = 16;
23+
for(i = 0; i < n; ++i)
24+
{
25+
total += i;
26+
}
27+
28+
// Creates a merge path but only one user of it
29+
assert(total == (n * (n - 1) / 2));
30+
31+
total = 0;
32+
n = 32;
33+
for(i = 0; i < n; ++i)
34+
{
35+
total += i;
36+
}
37+
38+
// Provable
39+
assert(total == (n * (n - 1) / 2));
40+
41+
return 0;
42+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
CORE
2+
main.c
3+
--verify --recursive-interprocedural --loop-unwind 17 --constants --one-domain-per-history
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^\[main.assertion.1\] .* assertion total == \(n \* \(n - 1\) / 2\): SUCCESS$
7+
^\[main.assertion.2\] .* assertion total == \(n \* \(n - 1\) / 2\): SUCCESS$
8+
^\[main.assertion.3\] .* assertion total == \(n \* \(n - 1\) / 2\): UNKNOWN$
9+
--
10+
^warning: ignoring
11+
--
12+
A basic test of the loop unwinding limit.
13+
You might think this has an off-by-one error but to process a loop 16 times
14+
you have to visit the loop guard 17 times. Setting the loop limit to 16 will
15+
cause the last two visits to merge loosing the precision needed to prove the
16+
conjecture.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <assert.h>
2+
3+
int main(int argc, char **argv)
4+
{
5+
int total;
6+
int n;
7+
int i;
8+
int branching[4];
9+
10+
total = 0;
11+
n = 4;
12+
for(i = 0; i < n; ++i)
13+
{
14+
if(branching[i])
15+
{
16+
total += i;
17+
}
18+
}
19+
20+
assert(total <= (n * (n - 1) / 2));
21+
22+
return 0;
23+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
main.c
3+
--verify --recursive-interprocedural --loop-unwind-and-branching 32 --constants --one-domain-per-history
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^\[main.assertion.1\] .* assertion total <= \(n \* \(n - 1\) / 2\): SUCCESS$
7+
--
8+
^warning: ignoring
9+
--
10+
Test tracking all local control-flow history.
11+
Note the exponential rise in the number of paths!
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#include <assert.h>
2+
3+
#define CODE_BLOCK \
4+
int i; \
5+
float flotal = 0; \
6+
for(i = 0; i < n; ++i) \
7+
{ \
8+
flotal += i; \
9+
} \
10+
assert(flotal == (n * (n - 1) / 2))
11+
12+
void do_the_loop(int n)
13+
{
14+
CODE_BLOCK;
15+
}
16+
17+
int main(int argc, char **argv)
18+
{
19+
int j;
20+
21+
// Will give unknown unless the bound is over 25
22+
for(j = 0; j < 5; j++)
23+
{
24+
int n = 5;
25+
CODE_BLOCK;
26+
}
27+
28+
// Paths in the loop will merge but that is OK
29+
// because the value of n is the important bit
30+
for(j = 0; j < 1000; j++)
31+
{
32+
int n = 10;
33+
do_the_loop(n);
34+
}
35+
36+
return 0;
37+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
main.c
3+
--verify --recursive-interprocedural --loop-unwind-and-branching 12 --constants --one-domain-per-history
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^\[main.assertion.1\] .* assertion flotal == \(n \* \(n - 1\) / 2\): UNKNOWN$
7+
^\[do_the_loop.assertion.1\] .* assertion flotal == \(n \* \(n - 1\) / 2\): SUCCESS$
8+
--
9+
^warning: ignoring
10+
--
11+
Demonstrate the function local behaviour of this history

src/analyses/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ SRC = ai.cpp \
2424
is_threaded.cpp \
2525
local_bitvector_analysis.cpp \
2626
local_cfg.cpp \
27+
local_control_flow_history.cpp \
2728
local_may_alias.cpp \
2829
local_safe_pointers.cpp \
2930
locals.cpp \

src/analyses/ai.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,14 @@ bool ai_baset::visit(
303303
if(to_l == goto_program.instructions.end())
304304
continue;
305305

306-
new_data |=
307-
visit_edge(function_id, p, function_id, to_l, ns, working_set);
306+
new_data |= visit_edge(
307+
function_id,
308+
p,
309+
function_id,
310+
to_l,
311+
ai_history_baset::no_caller_history,
312+
ns,
313+
working_set); // Local steps so no caller history needed
308314
}
309315
}
310316

@@ -316,11 +322,13 @@ bool ai_baset::visit_edge(
316322
trace_ptrt p,
317323
const irep_idt &to_function_id,
318324
locationt to_l,
325+
trace_ptrt caller_history,
319326
const namespacet &ns,
320327
working_sett &working_set)
321328
{
322329
// Has history taught us not to step here...
323-
auto next = p->step(to_l, *(storage->abstract_traces_before(to_l)));
330+
auto next =
331+
p->step(to_l, *(storage->abstract_traces_before(to_l)), caller_history);
324332
if(next.first == ai_history_baset::step_statust::BLOCKED)
325333
return false;
326334
trace_ptrt to_p = next.second;
@@ -366,6 +374,8 @@ bool ai_baset::visit_edge_function_call(
366374
p_call,
367375
calling_function_id,
368376
l_return,
377+
ai_history_baset::
378+
no_caller_history, // Not needed as we are skipping the function call
369379
ns,
370380
working_set);
371381
}
@@ -439,6 +449,7 @@ bool ai_baset::visit_function_call(
439449
p_call,
440450
calling_function_id,
441451
l_return,
452+
ai_history_baset::no_caller_history, // Would be the same as p_call...
442453
ns,
443454
working_set);
444455
}
@@ -480,6 +491,8 @@ bool ai_recursive_interproceduralt::visit_edge_function_call(
480491
p_call,
481492
callee_function_id,
482493
l_begin,
494+
ai_history_baset::
495+
no_caller_history, // Not needed as p_call already has the info
483496
ns,
484497
catch_working_set);
485498

@@ -525,6 +538,7 @@ bool ai_recursive_interproceduralt::visit_edge_function_call(
525538
p_end,
526539
calling_function_id,
527540
l_return,
541+
p_call, // To allow function-local history
528542
ns,
529543
working_set);
530544
}

src/analyses/ai.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,11 @@ class ai_baset
252252

253253
locationt n = std::next(l);
254254

255-
auto step_return = p->step(n, *(storage->abstract_traces_before(n)));
255+
auto step_return = p->step(
256+
n,
257+
*(storage->abstract_traces_before(n)),
258+
ai_history_baset::no_caller_history);
259+
// Caller history not needed as this is a local step
256260

257261
return storage->abstract_state_before(step_return.second, *domain_factory);
258262
}
@@ -469,6 +473,7 @@ class ai_baset
469473
trace_ptrt p,
470474
const irep_idt &to_function_id,
471475
locationt to_l,
476+
trace_ptrt caller_history,
472477
const namespacet &ns,
473478
working_sett &working_set);
474479

0 commit comments

Comments
 (0)