Skip to content

Commit 08dfd8d

Browse files
author
owen-jones-diffblue
authored
Merge pull request #4288 from owen-jones-diffblue/owen/symex-filter-value-sets
Filter value sets in symex
2 parents 0184c36 + 0544470 commit 08dfd8d

File tree

25 files changed

+753
-121
lines changed

25 files changed

+753
-121
lines changed
Binary file not shown.
Binary file not shown.
Binary file not shown.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
CORE
2+
virtual_filter_value_sets.class
3+
--show-vcc --function virtual_filter_value_sets.test_with_deref
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
java::B\.f:\(\)I#return_value!0#.* = 9$
7+
--
8+
java::B\.f:\(\)I#return_value!0#.* = .*byte_extract_.*_endian
9+
^warning: ignoring
10+
--
11+
When B.f is called on `c.a_field`, it is guarded by a conditional which implies
12+
that `c.a_field` must be a B. Symex should filter the value set for `c.a_field`
13+
to remove anything which doesn't satisfy that condition. It will then be able
14+
to determine that the return value of B.f is 9. If it thinks that `c.a_field`
15+
might contain an A then it will create a more complicated expression for the
16+
return value of B.f, which will include byte_extract_little_endian or
17+
byte_extract_big_endian, as this is what it will produce when trying to read
18+
the field `int flag` from a class that doesn't have any fields.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
CORE
2+
virtual_filter_value_sets.class
3+
--show-vcc --function virtual_filter_value_sets.test_without_deref
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
java::B\.f:\(\)I#return_value!0#.* = 9$
7+
--
8+
java::B\.f:\(\)I#return_value!0#.* = .*byte_extract_.*_endian
9+
^warning: ignoring
10+
--
11+
When B.f is called on `a_or_b`, it is guarded by a conditional which implies
12+
that `a_or_b` must be a B. Symex should filter the value set for `a_or_b` to
13+
remove anything which doesn't satisfy that condition. It will then be able
14+
to determine that the return value of B.f is 9. If it thinks that `a_or_b`
15+
might contain an A then it will create a more complicated expression for the
16+
return value of B.f, which will include byte_extract_little_endian or
17+
byte_extract_big_endian, as this is what it will produce when trying to read
18+
the field `int flag` from a class that doesn't have any fields.
Binary file not shown.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
class A {
2+
public int f() { return 1; }
3+
};
4+
5+
class B extends A {
6+
public int flag;
7+
8+
public int f() { return flag; }
9+
};
10+
11+
class Container {
12+
public A a_field;
13+
}
14+
15+
class virtual_filter_value_sets {
16+
public static void test_without_deref(boolean nondet_bool) {
17+
A a = new A();
18+
19+
B b = new B();
20+
b.flag = 9;
21+
22+
A a_or_b = nondet_bool ? a : b;
23+
int retval = a_or_b.f();
24+
25+
assert false;
26+
}
27+
28+
public static void test_with_deref(boolean nondet_bool) {
29+
A a = new A();
30+
31+
B b = new B();
32+
b.flag = 9;
33+
34+
Container c = new Container();
35+
c.a_field = nondet_bool ? a : b;
36+
int retval = c.a_field.f();
37+
38+
assert false;
39+
}
40+
}

regression/cbmc/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ add_test_pl_tests(
55
add_test_pl_profile(
66
"cbmc-paths-lifo"
77
"$<TARGET_FILE:cbmc> --paths lifo"
8-
"-C;-X;thorough-paths;-X;smt-backend;-s;paths-lifo"
8+
"-C;-X;thorough-paths;-X;smt-backend;-X;paths-lifo-expected-failure;-s;paths-lifo"
99
"CORE"
1010
)
1111

regression/cbmc/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ test-cprover-smt2:
77
@../test.pl -e -p -c "../../../src/cbmc/cbmc --cprover-smt2" -X broken-smt-backend
88

99
test-paths-lifo:
10-
@../test.pl -e -p -c "../../../src/cbmc/cbmc --paths lifo" -X thorough-paths -X smt-backend
10+
@../test.pl -e -p -c "../../../src/cbmc/cbmc --paths lifo" -X thorough-paths -X smt-backend -X paths-lifo-expected-failure
1111

1212
tests.log: ../test.pl test
1313

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1+
#include <assert.h>
2+
13
static void noop() { }
24

35
int main(int argc, char **argv) {
46

57
int x;
68
int *maybe_null = argc & 1 ? &x : 0;
79

8-
// Should work (guarded by assume):
10+
// There are two features of symex that might exclude null pointers: local
11+
// safe pointer analysis (LSPA for the rest of this file) and value-set
12+
// filtering (i.e. goto_symext::try_filter_value_sets()).
13+
14+
// Should be judged safe by LSPA and value-set filtering (guarded by assume):
915
int *ptr1 = maybe_null;
1016
__CPROVER_assume(ptr1 != 0);
1117
int deref1 = *ptr1;
1218

13-
// Should work (guarded by else):
19+
// Should be judged safe by LSPA and value-set filtering (guarded by else):
1420
int *ptr2 = maybe_null;
1521
if(ptr2 == 0)
1622
{
@@ -20,14 +26,15 @@ int main(int argc, char **argv) {
2026
int deref2 = *ptr2;
2127
}
2228

23-
// Should work (guarded by if):
29+
// Should be judged safe by LSPA and value-set filtering (guarded by if):
2430
int *ptr3 = maybe_null;
2531
if(ptr3 != 0)
2632
{
2733
int deref3 = *ptr3;
2834
}
2935

30-
// Shouldn't work yet despite being safe (guarded by backward goto):
36+
// Should be judged unsafe by LSPA and safe by value-set filtering
37+
// (guarded by backward goto):
3138
int *ptr4 = maybe_null;
3239
goto check;
3340

@@ -41,34 +48,58 @@ int main(int argc, char **argv) {
4148

4249
end_test4:
4350

44-
// Shouldn't work yet despite being safe (guarded by confluence):
51+
// Should be judged unsafe by LSPA and safe by value-set filtering
52+
// (guarded by confluence):
4553
int *ptr5 = maybe_null;
4654
if(argc == 5)
4755
__CPROVER_assume(ptr5 != 0);
4856
else
4957
__CPROVER_assume(ptr5 != 0);
5058
int deref5 = *ptr5;
5159

52-
// Shouldn't work (unsafe as only guarded on one branch):
60+
// Should be judged unsafe by LSPA (due to suspicion write to ptr5 might alter
61+
// ptr6) and safe by value-set filtering:
5362
int *ptr6 = maybe_null;
54-
if(argc == 6)
55-
__CPROVER_assume(ptr6 != 0);
56-
else
57-
{
58-
}
63+
__CPROVER_assume(ptr6 != 0);
64+
ptr5 = 0;
5965
int deref6 = *ptr6;
6066

61-
// Shouldn't work due to suspicion write to ptr6 might alter ptr7:
67+
// Should be judged unsafe by LSPA (due to suspicion noop() call might alter
68+
// ptr7) and safe by value-set filtering:
6269
int *ptr7 = maybe_null;
6370
__CPROVER_assume(ptr7 != 0);
64-
ptr6 = 0;
71+
noop();
6572
int deref7 = *ptr7;
6673

67-
// Shouldn't work due to suspicion noop() call might alter ptr8:
68-
int *ptr8 = maybe_null;
69-
__CPROVER_assume(ptr8 != 0);
70-
noop();
71-
int deref8 = *ptr8;
74+
// Should be judged safe by LSPA and unsafe by value-set filtering (it
75+
// isn't known what symbol *ptr_ptr8 refers to, so null can't be removed
76+
// from a specific value set):
77+
int r8_a = 1, r8_b = 2;
78+
int *q8_a = argc & 2 ? &r8_a : 0;
79+
int *q8_b = argc & 4 ? &r8_b : 0;
80+
int **ptr8 = argc & 8 ? &q8_a : &q8_b;
81+
__CPROVER_assume(*ptr8 != 0);
82+
int deref8 = **ptr8;
83+
84+
// Should be judged safe by LSPA and unsafe by value-set filtering (it
85+
// isn't known what symbol *ptr_ptr9 refers to, so null can't be removed
86+
// from a specific value set):
87+
int r9_a = 1, r9_b = 2;
88+
int *q9_a = argc & 2 ? &r9_a : 0;
89+
int *q9_b = argc & 4 ? &r9_b : 0;
90+
int **ptr9 = argc & 8 ? &q9_a : &q9_b;
91+
if(*ptr9 != 0)
92+
int deref9 = **ptr9;
93+
94+
// Should be judged unsafe by LSPA and value-set filtering
95+
// (unsafe as only guarded on one branch):
96+
int *ptr10 = maybe_null;
97+
if(argc == 10)
98+
__CPROVER_assume(ptr10 != 0);
99+
else
100+
{
101+
}
102+
int deref10 = *ptr10;
72103

73104
assert(0);
74105
}

regression/cbmc/symex_should_exclude_null_pointers/test.desc

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@ main.c
33
--show-vcc
44
^EXIT=0$
55
^SIGNAL=0$
6-
ptr4\$object
7-
ptr5\$object
8-
ptr6\$object
9-
ptr7\$object
10-
ptr8\$object
6+
ptr10\$object
117
--
12-
ptr[1-3]\$object
8+
ptr[1-9]\$object
139
^warning: ignoring
1410
--
1511
ptrX\$object appearing in the VCCs indicates symex was unsure whether the pointer had a valid
1612
target, and uses the $object symbol as a referred object of last resort. ptr1-3 should be judged
17-
not-null by symex, so their $object symbols do not occur.
13+
not-null by local safe pointer analysis, so their $object symbols do not occur. ptr4-7 are
14+
not caught by local safe pointer analysis but they are judged safe by value-set filtering, i.e.
15+
goto_symext::try_filter_value_sets(). ptr8-9 are judged safe by local safe pointer analysis but
16+
not value-set filtering. ptr10 is not judged safe by either because it is not safe.
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
#include <assert.h>
2+
3+
static void noop()
4+
{
5+
}
6+
7+
int main(int argc, char **argv)
8+
{
9+
__CPROVER_assume(argc == 5);
10+
11+
int a = 2;
12+
int b = 1;
13+
int *ptr_to_a_or_b = argv[0][0] == '0' ? &a : &b;
14+
15+
// Should work (value-set filtered by assume):
16+
int *p1 = ptr_to_a_or_b;
17+
__CPROVER_assume(p1 != &a);
18+
int c1 = *p1;
19+
20+
int *p2 = ptr_to_a_or_b;
21+
__CPROVER_assume(*p2 != 2);
22+
int c2 = *p2;
23+
24+
// Should work (value-set filtered by else):
25+
int c3 = 0;
26+
int *p3 = ptr_to_a_or_b;
27+
if(p3 == &a)
28+
{
29+
}
30+
else
31+
{
32+
c3 = *p3;
33+
}
34+
35+
int c4 = 0;
36+
int *p4 = ptr_to_a_or_b;
37+
if(*p4 == 2)
38+
{
39+
}
40+
else
41+
{
42+
c4 = *p4;
43+
}
44+
45+
// Should work (value-set filtered by if):
46+
int c5 = 0;
47+
int *p5 = ptr_to_a_or_b;
48+
if(p5 != &a)
49+
{
50+
c5 = *p5;
51+
}
52+
53+
int c6 = 0;
54+
int *p6 = ptr_to_a_or_b;
55+
if(*p6 != 2)
56+
{
57+
c6 = *p6;
58+
}
59+
60+
// Should work (value-set filtered by assume before a backward goto):
61+
int *p7 = ptr_to_a_or_b;
62+
goto check7;
63+
64+
divide7:
65+
int c7 = *p7;
66+
goto end_test7;
67+
68+
check7:
69+
__CPROVER_assume(p7 != &a);
70+
goto divide7;
71+
72+
end_test7:
73+
74+
int *p8 = ptr_to_a_or_b;
75+
goto check8;
76+
77+
divide8:
78+
int c8 = *p8;
79+
goto end_test8;
80+
81+
check8:
82+
__CPROVER_assume(*p8 != 2);
83+
goto divide8;
84+
85+
end_test8:
86+
87+
// Should work (value-set filtered by confluence of if and else):
88+
int *p9 = ptr_to_a_or_b;
89+
if(argv[1][0] == '0')
90+
__CPROVER_assume(p9 != &a);
91+
else
92+
__CPROVER_assume(p9 != &a);
93+
int c9 = *p9;
94+
95+
int *p10 = ptr_to_a_or_b;
96+
if(argv[2][0] == '0')
97+
__CPROVER_assume(*p10 != 2);
98+
else
99+
__CPROVER_assume(*p10 != 2);
100+
int c10 = *p10;
101+
102+
// Should work (value-set filtered by assume, write through an unrelated
103+
// pointer has no effect):
104+
int c = 0;
105+
int *ptr_to_c = &c;
106+
107+
int *p11 = ptr_to_a_or_b;
108+
__CPROVER_assume(p11 != &a);
109+
*ptr_to_c = 3;
110+
int c11 = *p11;
111+
112+
int *p12 = ptr_to_a_or_b;
113+
__CPROVER_assume(*p12 != 2);
114+
*ptr_to_c = 4;
115+
int c12 = *p12;
116+
117+
// Should work (value-set filtered by assume, function call has no effect):
118+
int *p13 = ptr_to_a_or_b;
119+
__CPROVER_assume(p13 != &a);
120+
noop();
121+
int c13 = *p13;
122+
123+
int *p14 = ptr_to_a_or_b;
124+
__CPROVER_assume(*p14 != 2);
125+
noop();
126+
int c14 = *p14;
127+
128+
// Shouldn't work (unsafe as value-set only filtered by if on one branch):
129+
int *p15 = ptr_to_a_or_b;
130+
if(argv[3][0] == '0')
131+
__CPROVER_assume(p15 != &a);
132+
else
133+
{
134+
}
135+
int c15 = *p15;
136+
137+
int *p16 = ptr_to_a_or_b;
138+
if(argv[4][0] == '0')
139+
__CPROVER_assume(*p16 != 2);
140+
else
141+
{
142+
}
143+
int c16 = *p16;
144+
145+
assert(0);
146+
}

0 commit comments

Comments
 (0)