Skip to content

Commit 9fd3434

Browse files
committed
Use local-safe-pointers analysis to improve Symex pointer resolution
This uses local_safe_pointerst to determine when symex dereferences a pointer that cannot be null. When it does the null result is excluded from the possible values, and therefore a $invalid_object reference may be excluded from the result of dereferencing such a pointer. This can improve constant propagation.
1 parent 8078569 commit 9fd3434

File tree

13 files changed

+230
-12
lines changed

13 files changed

+230
-12
lines changed
Binary file not shown.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE
2+
test.class
3+
--function test.main
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^VERIFICATION FAILED$
7+
Null pointer check: FAILURE$
8+
assertion at file test\.java line 21 .*: SUCCESS$
9+
--
10+
^warning: ignoring
11+
--
12+
JBMC should reports failure, as it might dereference a null pointer, but as Java
13+
is a safe language we should statically determine that if we reach the assertion
14+
it cannot be violated.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
public class test {
2+
3+
public int field;
4+
5+
public test(int value) {
6+
7+
field = value;
8+
9+
}
10+
11+
public static void main(boolean unknown) {
12+
13+
test t = new test(12345);
14+
test maybe_t = unknown ? null : t;
15+
16+
// t could still be null, but symex ought to notice that as the Java frontend introduces
17+
// checks before all pointer dereference operations, it can statically eliminate
18+
// the assertion below.
19+
20+
int field_value = maybe_t.field;
21+
assert field_value == 12345;
22+
23+
}
24+
25+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
test.class
3+
--show-vcc --function test.main
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
--
7+
^warning: ignoring
8+
assertion at file test\.java line 22
9+
--
10+
Because symex should statically determine the assertion can't be violated there should not be a goal for it
11+
by the time --show-vccs prints the equation.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
static void noop() { }
2+
3+
int main(int argc, char **argv) {
4+
5+
int x;
6+
int *maybe_null = argc & 1 ? &x : 0;
7+
8+
// Should work (guarded by assume):
9+
int *ptr1 = maybe_null;
10+
__CPROVER_assume(ptr1 != 0);
11+
int deref1 = *ptr1;
12+
13+
// Should work (guarded by else):
14+
int *ptr2 = maybe_null;
15+
if(ptr2 == 0)
16+
{
17+
}
18+
else
19+
{
20+
int deref2 = *ptr2;
21+
}
22+
23+
// Should work (guarded by if):
24+
int *ptr3 = maybe_null;
25+
if(ptr3 != 0)
26+
{
27+
int deref3 = *ptr3;
28+
}
29+
30+
// Shouldn't work yet despite being safe (guarded by backward goto):
31+
int *ptr4 = maybe_null;
32+
goto check;
33+
34+
deref:
35+
int deref4 = *ptr4;
36+
goto end_test4;
37+
38+
check:
39+
__CPROVER_assume(ptr4 != 0);
40+
goto deref;
41+
42+
end_test4:
43+
44+
// Shouldn't work yet despite being safe (guarded by confluence):
45+
int *ptr5 = maybe_null;
46+
if(argc == 5)
47+
__CPROVER_assume(ptr5 != 0);
48+
else
49+
__CPROVER_assume(ptr5 != 0);
50+
int deref5 = *ptr5;
51+
52+
// Shouldn't work (unsafe as only guarded on one branch):
53+
int *ptr6 = maybe_null;
54+
if(argc == 6)
55+
__CPROVER_assume(ptr6 != 0);
56+
else
57+
{
58+
}
59+
int deref6 = *ptr6;
60+
61+
// Shouldn't work due to suspicion write to ptr6 might alter ptr7:
62+
int *ptr7 = maybe_null;
63+
__CPROVER_assume(ptr7 != 0);
64+
ptr6 = 0;
65+
int deref7 = *ptr7;
66+
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;
72+
73+
assert(0);
74+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
CORE
2+
main.c
3+
--show-vcc
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
ptr4\$object
7+
ptr5\$object
8+
ptr6\$object
9+
ptr7\$object
10+
ptr8\$object
11+
--
12+
ptr[1-3]\$object
13+
^warning: ignoring
14+
--
15+
ptrX\$object appearing in the VCCs indicates symex was unsure whether the pointer had a valid
16+
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.

src/goto-symex/goto_symex.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Author: Daniel Kroening, [email protected]
1212
#ifndef CPROVER_GOTO_SYMEX_GOTO_SYMEX_H
1313
#define CPROVER_GOTO_SYMEX_GOTO_SYMEX_H
1414

15+
#include <analyses/local_safe_pointers.h>
16+
1517
#include <util/options.h>
1618
#include <util/message.h>
1719

@@ -465,6 +467,8 @@ class goto_symext
465467
void rewrite_quantifiers(exprt &, statet &);
466468

467469
path_storaget &path_storage;
470+
471+
std::unordered_map<irep_idt, local_safe_pointerst> safe_pointers;
468472
};
469473

470474
#endif // CPROVER_GOTO_SYMEX_GOTO_SYMEX_H

src/goto-symex/symex_dereference.cpp

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,22 @@ void goto_symext::dereference_rec(
236236
if(expr.operands().size()!=1)
237237
throw "dereference takes one operand";
238238

239+
bool expr_is_not_null = false;
240+
241+
if(state.threads.size() == 1)
242+
{
243+
const irep_idt &expr_function = state.source.pc->function;
244+
if(!expr_function.empty())
245+
{
246+
dereference_exprt to_check = to_dereference_expr(expr);
247+
state.get_original_name(to_check);
248+
249+
expr_is_not_null =
250+
safe_pointers.at(expr_function).is_safe_dereference(
251+
to_check, state.source.pc);
252+
}
253+
}
254+
239255
exprt tmp1;
240256
tmp1.swap(expr.op0());
241257

@@ -246,7 +262,12 @@ void goto_symext::dereference_rec(
246262
symex_dereference_statet symex_dereference_state(*this, state);
247263

248264
value_set_dereferencet dereference(
249-
ns, state.symbol_table, options, symex_dereference_state, language_mode);
265+
ns,
266+
state.symbol_table,
267+
options,
268+
symex_dereference_state,
269+
language_mode,
270+
expr_is_not_null);
250271

251272
// std::cout << "**** " << format(tmp1) << '\n';
252273
exprt tmp2=

src/goto-symex/symex_function_call.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,11 @@ void goto_symext::symex_function_call_code(
222222

223223
state.dirty.populate_dirty_for_function(identifier, goto_function);
224224

225+
auto emplace_safe_pointers_result =
226+
safe_pointers.emplace(identifier, local_safe_pointerst{});
227+
if(emplace_safe_pointers_result.second)
228+
emplace_safe_pointers_result.first->second(goto_function.body);
229+
225230
const bool stop_recursing=get_unwind_recursion(
226231
identifier,
227232
state.source.thread_nr,

src/goto-symex/symex_main.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,15 @@ void goto_symext::initialize_entry_point(
132132

133133
INVARIANT(
134134
!pc->function.empty(), "all symexed instructions should have a function");
135-
state.dirty.populate_dirty_for_function(
136-
pc->function, get_goto_function(pc->function));
135+
136+
const goto_functiont &entry_point_function = get_goto_function(pc->function);
137+
138+
auto emplace_safe_pointers_result =
139+
safe_pointers.emplace(pc->function, local_safe_pointerst{});
140+
if(emplace_safe_pointers_result.second)
141+
emplace_safe_pointers_result.first->second(entry_point_function.body);
142+
143+
state.dirty.populate_dirty_for_function(pc->function, entry_point_function);
137144

138145
symex_transition(state, state.source.pc);
139146
}

0 commit comments

Comments
 (0)