Skip to content

Commit f33b210

Browse files
committed
Strengthen local_safe_pointers to handle common Java operations
The local-safe-pointers analysis can already inform symex that certain pointers are known not to be null at particular program points. This strengthens the analysis to spot more cases such that it can determine no null pointer is dereferenced in the new regression test `jbmc/clean_derefs`, which exercises many common Java operations including array accesses. The specific improvements to local-safe-pointers: * Look through typecasts. This was already done for GOTO instructions, but now works for ASSUME as well. * Search for not-null expressions using base_type_eq instead of just irept::operator<. This means that when symex uses namespacet::follow to remove a symbol_typet that does not prevent local-safe-pointers from noting it is not null in a particular context.
1 parent 70887e2 commit f33b210

File tree

9 files changed

+188
-66
lines changed

9 files changed

+188
-66
lines changed
310 Bytes
Binary file not shown.
1.09 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
public class Test {
3+
4+
public int field;
5+
6+
public Test(int f) { field = f; }
7+
8+
public static void main(int nondet) {
9+
10+
// This test creates and uses objects in a few different contexts to check
11+
// that in all cases goto-symex realises that the dereferenced object cannot
12+
// be null (due to the NullPointerException check that guards all derefs),
13+
// therefore symex never generates a reference to any $failed_object symbol
14+
// that for a C program would stand in for an unknown or invalid object such
15+
// as a dereferenced null pointer.
16+
17+
Test t1 = new Test(nondet);
18+
Test t2 = new Test(nondet);
19+
20+
Test[] tarray = new Test[5];
21+
tarray[0] = null;
22+
tarray[1] = t2;
23+
tarray[2] = t1;
24+
25+
t1.field++;
26+
(nondet == 10 ? t1 : t2).field--;
27+
tarray[nondet % 5].field++;
28+
(nondet == 20 ? t1 : tarray[2]).field++;
29+
30+
Container c1 = new Container(t1);
31+
Container c2 = new Container(t2);
32+
Container c3 = new Container(null);
33+
34+
c1.t.field++;
35+
(nondet == 30 ? c1 : c3).t.field++;
36+
(nondet == 40 ? c2.t : tarray[3]).field--;
37+
38+
assert false;
39+
40+
}
41+
42+
}
43+
44+
class Container {
45+
46+
public Container(Test _t) { t = _t; }
47+
48+
public Test t;
49+
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
Test.class
3+
--function Test.main --show-vcc
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
--
7+
invalid
8+
--
9+
This checks that no invalid_object references are generated -- these would be created if
10+
symex thought it might follow a null or other invalid pointer, whereas Java dereferences
11+
should always be checked not-null beforehand.

src/analyses/local_safe_pointers.cpp

+82-56
Original file line numberDiff line numberDiff line change
@@ -13,66 +13,61 @@ Author: Diffblue Ltd
1313

1414
#include <util/expr_iterator.h>
1515
#include <util/format_expr.h>
16+
#include <util/base_type.h>
1617

17-
/// If `expr` is of the form `x != nullptr`, return x. Otherwise return null
18-
static const exprt *get_null_checked_expr(const exprt &expr)
19-
{
20-
if(expr.id() == ID_notequal)
21-
{
22-
const exprt *op0 = &expr.op0(), *op1 = &expr.op1();
23-
if(op0->type().id() == ID_pointer &&
24-
*op0 == null_pointer_exprt(to_pointer_type(op0->type())))
25-
{
26-
std::swap(op0, op1);
27-
}
28-
29-
if(op1->type().id() == ID_pointer &&
30-
*op1 == null_pointer_exprt(to_pointer_type(op1->type())))
31-
{
32-
while(op0->id() == ID_typecast)
33-
op0 = &op0->op0();
34-
return op0;
35-
}
36-
}
37-
38-
return nullptr;
39-
}
40-
41-
/// Return structure for `get_conditional_checked_expr`
18+
/// Return structure for `get_null_checked_expr` and
19+
/// `get_conditional_checked_expr`
4220
struct goto_null_checkt
4321
{
44-
/// If true, the given GOTO tests that a pointer expression is non-null on the
45-
/// taken branch; otherwise, on the not-taken branch.
22+
/// If true, the given GOTO/ASSUME tests that a pointer expression is non-null
23+
/// on the taken branch or passing case; otherwise, on the not-taken branch
24+
/// or on failure.
4625
bool checked_when_taken;
4726

4827
/// Null-tested pointer expression
4928
exprt checked_expr;
5029
};
5130

52-
/// Check if a GOTO guard expression tests if a pointer is null
53-
/// \param goto_guard: expression to check
31+
/// Check if `expr` tests if a pointer is null
32+
/// \param expr: expression to check
5433
/// \return a `goto_null_checkt` indicating what expression is tested and
5534
/// whether the check applies on the taken or not-taken branch, or an empty
5635
/// optionalt if this isn't a null check.
57-
static optionalt<goto_null_checkt>
58-
get_conditional_checked_expr(const exprt &goto_guard)
36+
static optionalt<goto_null_checkt> get_null_checked_expr(const exprt &expr)
5937
{
60-
exprt normalized_guard = goto_guard;
38+
exprt normalized_expr = expr;
6139
bool checked_when_taken = true;
62-
while(normalized_guard.id() == ID_not || normalized_guard.id() == ID_equal)
40+
while(normalized_expr.id() == ID_not || normalized_expr.id() == ID_equal)
6341
{
64-
if(normalized_guard.id() == ID_not)
65-
normalized_guard = normalized_guard.op0();
42+
if(normalized_expr.id() == ID_not)
43+
normalized_expr = normalized_expr.op0();
6644
else
67-
normalized_guard.id(ID_notequal);
45+
normalized_expr.id(ID_notequal);
6846
checked_when_taken = !checked_when_taken;
6947
}
7048

71-
const exprt *checked_expr = get_null_checked_expr(normalized_guard);
72-
if(!checked_expr)
73-
return {};
74-
else
75-
return goto_null_checkt { checked_when_taken, *checked_expr };
49+
if(normalized_expr.id() == ID_notequal)
50+
{
51+
const exprt *op0 = &normalized_expr.op0(), *op1 = &normalized_expr.op1();
52+
while(op0->id() == ID_typecast)
53+
op0 = &op0->op0();
54+
while(op1->id() == ID_typecast)
55+
op1 = &op1->op0();
56+
57+
if(op0->type().id() == ID_pointer &&
58+
*op0 == null_pointer_exprt(to_pointer_type(op0->type())))
59+
{
60+
std::swap(op0, op1);
61+
}
62+
63+
if(op1->type().id() == ID_pointer &&
64+
*op1 == null_pointer_exprt(to_pointer_type(op1->type())))
65+
{
66+
return { { checked_when_taken, *op0 } };
67+
}
68+
}
69+
70+
return {};
7671
}
7772

7873
/// Compute safe dereference expressions for a given GOTO program. This
@@ -82,7 +77,8 @@ get_conditional_checked_expr(const exprt &goto_guard)
8277
/// \param goto_program: program to analyse
8378
void local_safe_pointerst::operator()(const goto_programt &goto_program)
8479
{
85-
std::set<exprt> checked_expressions;
80+
std::set<exprt, base_type_comparet> checked_expressions(
81+
base_type_comparet{ns});
8682

8783
for(const auto &instruction : goto_program.instructions)
8884
{
@@ -91,11 +87,23 @@ void local_safe_pointerst::operator()(const goto_programt &goto_program)
9187
checked_expressions.clear();
9288
// Retrieve working set for forward GOTOs:
9389
else if(instruction.is_target())
94-
checked_expressions = non_null_expressions[instruction.location_number];
90+
{
91+
auto findit = non_null_expressions.find(instruction.location_number);
92+
if(findit != non_null_expressions.end())
93+
checked_expressions = findit->second;
94+
else
95+
{
96+
checked_expressions =
97+
std::set<exprt, base_type_comparet>(base_type_comparet{ns});
98+
}
99+
}
95100

96101
// Save the working set at this program point:
97102
if(!checked_expressions.empty())
98-
non_null_expressions[instruction.location_number] = checked_expressions;
103+
{
104+
non_null_expressions.emplace(
105+
instruction.location_number, checked_expressions);
106+
}
99107

100108
switch(instruction.type)
101109
{
@@ -114,26 +122,35 @@ void local_safe_pointerst::operator()(const goto_programt &goto_program)
114122
// Possible checks:
115123
case ASSUME:
116124
{
117-
const exprt *checked_expr;
118-
if((checked_expr = get_null_checked_expr(instruction.guard)) != nullptr)
125+
if(auto assume_check = get_null_checked_expr(instruction.guard))
119126
{
120-
checked_expressions.insert(*checked_expr);
127+
if(assume_check->checked_when_taken)
128+
checked_expressions.insert(assume_check->checked_expr);
121129
}
122130
break;
123131
}
124132

125133
case GOTO:
126134
if(!instruction.is_backwards_goto())
127135
{
128-
if(auto conditional_check =
129-
get_conditional_checked_expr(instruction.guard))
130-
{
131-
auto &taken_checked_expressions =
132-
non_null_expressions[instruction.get_target()->location_number];
133-
taken_checked_expressions = checked_expressions;
136+
// Copy current state to GOTO target:
137+
138+
auto target_emplace_result =
139+
non_null_expressions.emplace(
140+
instruction.get_target()->location_number, checked_expressions);
141+
INVARIANT(
142+
target_emplace_result.second,
143+
"this is a forward analysis; target should not be visited yet");
134144

145+
if(auto conditional_check = get_null_checked_expr(instruction.guard))
146+
{
147+
// Add the GOTO condition to either the target or current state,
148+
// as appropriate:
135149
if(conditional_check->checked_when_taken)
136-
taken_checked_expressions.insert(conditional_check->checked_expr);
150+
{gh
151+
target_emplace_result.first->second.insert(
152+
conditional_check->checked_expr);
153+
}
137154
else
138155
checked_expressions.insert(conditional_check->checked_expr);
139156

@@ -157,7 +174,7 @@ void local_safe_pointerst::operator()(const goto_programt &goto_program)
157174
/// operator())
158175
/// \param ns: global namespace
159176
void local_safe_pointerst::output(
160-
std::ostream &out, const goto_programt &goto_program, const namespacet &ns)
177+
std::ostream &out, const goto_programt &goto_program)
161178
{
162179
forall_goto_program_instructions(i_it, goto_program)
163180
{
@@ -199,7 +216,7 @@ void local_safe_pointerst::output(
199216
/// operator())
200217
/// \param ns: global namespace
201218
void local_safe_pointerst::output_safe_dereferences(
202-
std::ostream &out, const goto_programt &goto_program, const namespacet &ns)
219+
std::ostream &out, const goto_programt &goto_program)
203220
{
204221
forall_goto_program_instructions(i_it, goto_program)
205222
{
@@ -251,3 +268,12 @@ bool local_safe_pointerst::is_non_null_at_program_point(
251268
tocheck = &tocheck->op0();
252269
return findit->second.count(*tocheck) != 0;
253270
}
271+
272+
bool local_safe_pointerst::base_type_comparet::operator()(
273+
const exprt &e1, const exprt &e2) const
274+
{
275+
if(base_type_eq(e1, e2, ns))
276+
return false;
277+
else
278+
return e1 < e2;
279+
}

src/analyses/local_safe_pointers.h

+40-5
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,45 @@ Author: Diffblue Ltd
2323
/// possibly-aliasing operations are handled pessimistically.
2424
class local_safe_pointerst
2525
{
26-
std::map<unsigned, std::set<exprt>> non_null_expressions;
26+
/// Comparator that regards base_type_eq expressions as equal, and otherwise
27+
/// uses the natural (operator<) ordering on irept.
28+
/// An expression is base_type_eq another one if their types, and types of
29+
/// their subexpressions, are identical except that one may use a symbol_typet
30+
/// while the other uses that type's expanded (namespacet::follow'd) form.
31+
class base_type_comparet
32+
{
33+
const namespacet &ns;
34+
35+
public:
36+
explicit base_type_comparet(const namespacet &ns)
37+
: ns(ns)
38+
{
39+
}
40+
41+
base_type_comparet(const base_type_comparet &other)
42+
: ns(other.ns)
43+
{
44+
}
45+
46+
base_type_comparet &operator=(const base_type_comparet &other)
47+
{
48+
INVARIANT(&ns == &other.ns, "base_type_comparet: clashing namespaces");
49+
return *this;
50+
}
51+
52+
bool operator()(const exprt &e1, const exprt &e2) const;
53+
};
54+
55+
std::map<unsigned, std::set<exprt, base_type_comparet>> non_null_expressions;
56+
57+
const namespacet &ns;
58+
59+
public:
60+
local_safe_pointerst(const namespacet &ns)
61+
: ns(ns)
62+
{
63+
}
2764

28-
public:
2965
void operator()(const goto_programt &goto_program);
3066

3167
bool is_non_null_at_program_point(
@@ -38,11 +74,10 @@ class local_safe_pointerst
3874
return is_non_null_at_program_point(deref.op0(), program_point);
3975
}
4076

41-
void output(
42-
std::ostream &stream, const goto_programt &program, const namespacet &ns);
77+
void output(std::ostream &stream, const goto_programt &program);
4378

4479
void output_safe_dereferences(
45-
std::ostream &stream, const goto_programt &program, const namespacet &ns);
80+
std::ostream &stream, const goto_programt &program);
4681
};
4782

4883
#endif // CPROVER_ANALYSES_LOCAL_SAFE_POINTERS_H

src/goto-instrument/goto_instrument_parse_options.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -297,17 +297,17 @@ int goto_instrument_parse_optionst::doit()
297297

298298
forall_goto_functions(it, goto_model.goto_functions)
299299
{
300-
local_safe_pointerst local_safe_pointers;
300+
local_safe_pointerst local_safe_pointers(ns);
301301
local_safe_pointers(it->second.body);
302302
std::cout << ">>>>\n";
303303
std::cout << ">>>> " << it->first << '\n';
304304
std::cout << ">>>>\n";
305305
if(cmdline.isset("show-local-safe-pointers"))
306-
local_safe_pointers.output(std::cout, it->second.body, ns);
306+
local_safe_pointers.output(std::cout, it->second.body);
307307
else
308308
{
309309
local_safe_pointers.output_safe_dereferences(
310-
std::cout, it->second.body, ns);
310+
std::cout, it->second.body);
311311
}
312312
std::cout << '\n';
313313
}

src/goto-symex/symex_function_call.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ void goto_symext::symex_function_call_code(
223223
state.dirty.populate_dirty_for_function(identifier, goto_function);
224224

225225
auto emplace_safe_pointers_result =
226-
safe_pointers.emplace(identifier, local_safe_pointerst{});
226+
safe_pointers.emplace(identifier, local_safe_pointerst{ns});
227227
if(emplace_safe_pointers_result.second)
228228
emplace_safe_pointers_result.first->second(goto_function.body);
229229

src/goto-symex/symex_main.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ void goto_symext::initialize_entry_point(
136136
const goto_functiont &entry_point_function = get_goto_function(pc->function);
137137

138138
auto emplace_safe_pointers_result =
139-
safe_pointers.emplace(pc->function, local_safe_pointerst{});
139+
safe_pointers.emplace(pc->function, local_safe_pointerst{ns});
140140
if(emplace_safe_pointers_result.second)
141141
emplace_safe_pointers_result.first->second(entry_point_function.body);
142142

0 commit comments

Comments
 (0)