Skip to content

Commit a3e7ada

Browse files
authored
Merge pull request diffblue#396 from diffblue/bugfix/nondet_returns_issue
[SEC-377] Fixed exception where nondet calls were directly returned
2 parents edf9898 + ee92382 commit a3e7ada

File tree

5 files changed

+249
-64
lines changed

5 files changed

+249
-64
lines changed

cbmc/src/java_bytecode/replace_java_nondet.cpp

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,25 @@ static bool is_assignment_from(
155155
is_typecast_with_id(rhs, identifier);
156156
}
157157

158+
/// Return whether the instruction is a return, and the rhs is a symbol or
159+
/// typecast expression with the specified identifier.
160+
/// \param instr: A goto program instruction.
161+
/// \param identifier: Some identifier.
162+
/// \return True if the expression is a typecast with one operand, and the
163+
/// typecast's identifier matches the specified identifier.
164+
static bool is_return_with_variable(
165+
const goto_programt::instructiont &instr,
166+
const irep_idt &identifier)
167+
{
168+
if(!instr.is_return())
169+
{
170+
return false;
171+
}
172+
const auto &rhs = to_code_return(instr.code).return_value();
173+
return is_symbol_with_id(rhs, identifier) ||
174+
is_typecast_with_id(rhs, identifier);
175+
}
176+
158177
/// Given an iterator into a list of instructions, modify the list to replace
159178
/// 'nondet' library functions with CBMC-native nondet expressions, and return
160179
/// an iterator to the next instruction to check. It's important to note that
@@ -169,7 +188,10 @@ static bool is_assignment_from(
169188
/// obj = (<type-of-obj>)return_tmp0;
170189
///
171190
/// We're going to replace all of these lines with
172-
/// return_tmp0 = NONDET(<type-of-obj>)
191+
/// obj = NONDET(<type-of-obj>)
192+
///
193+
/// In the situation of a direct return, the end result should be:
194+
/// return NONDET(<type-of-obj>)
173195
/// \param goto_program: The goto program to modify.
174196
/// \param target: A single step of the goto program which may be erased and
175197
/// replaced.
@@ -228,49 +250,60 @@ static goto_programt::targett check_and_replace_target(
228250
// Look for the assignment of the temporary return variable into our target
229251
// variable.
230252
const auto end = goto_program.instructions.end();
231-
auto assignment_instruction = std::find_if(
253+
auto target_instruction = std::find_if(
232254
next_instr,
233255
end,
234256
[&return_identifier](const goto_programt::instructiont &instr) {
235257
return is_assignment_from(instr, return_identifier);
236258
});
237259

238-
INVARIANT(
239-
assignment_instruction != end,
240-
"failed to find assignment of the temporary return variable into our "
241-
"target variable");
242-
243-
// Assume that the LHS of *this* assignment is the actual nondet variable
244-
const auto &code_assign = to_code_assign(assignment_instruction->code);
245-
const auto nondet_var = code_assign.lhs();
246-
const auto source_loc = target->source_location;
260+
// If we can't find an assign, it might be a direct return.
261+
if(target_instruction == end)
262+
{
263+
target_instruction = std::find_if(
264+
next_instr,
265+
end,
266+
[&return_identifier](const goto_programt::instructiont &instr) {
267+
return is_return_with_variable(instr, return_identifier);
268+
});
269+
}
247270

248-
// Erase from the nondet function call to the assignment
249-
const auto after_matching_assignment = std::next(assignment_instruction);
250271
INVARIANT(
251-
after_matching_assignment != end,
252-
"goto_program missing END_FUNCTION instruction");
272+
target_instruction != end,
273+
"failed to find return of the temporary return variable or assignment of "
274+
"the temporary return variable into a target variable");
253275

254276
std::for_each(
255-
target,
256-
after_matching_assignment,
257-
[](goto_programt::instructiont &instr) { // NOLINT (*)
277+
target, target_instruction, [](goto_programt::instructiont &instr) {
258278
instr.make_skip();
259279
});
260280

261-
const auto inserted = goto_program.insert_before(after_matching_assignment);
262-
inserted->make_assignment();
263-
side_effect_expr_nondett inserted_expr(nondet_var.type());
264-
inserted_expr.set_nullable(
265-
instr_info.get_nullable_type() ==
266-
nondet_instruction_infot::is_nullablet::TRUE);
267-
inserted->code = code_assignt(nondet_var, inserted_expr);
268-
inserted->code.add_source_location() = source_loc;
269-
inserted->source_location = source_loc;
281+
if(target_instruction->is_return())
282+
{
283+
const auto &nondet_var =
284+
to_code_return(target_instruction->code).return_value();
285+
286+
side_effect_expr_nondett inserted_expr(nondet_var.type());
287+
inserted_expr.set_nullable(
288+
instr_info.get_nullable_type() ==
289+
nondet_instruction_infot::is_nullablet::TRUE);
290+
target_instruction->code = code_returnt(inserted_expr);
291+
}
292+
else if(target_instruction->is_assign())
293+
{
294+
// Assume that the LHS of *this* assignment is the actual nondet variable
295+
const auto &nondet_var = to_code_assign(target_instruction->code).lhs();
296+
297+
side_effect_expr_nondett inserted_expr(nondet_var.type());
298+
inserted_expr.set_nullable(
299+
instr_info.get_nullable_type() ==
300+
nondet_instruction_infot::is_nullablet::TRUE);
301+
target_instruction->code = code_assignt(nondet_var, inserted_expr);
302+
}
270303

271304
goto_program.update();
272305

273-
return after_matching_assignment;
306+
return std::next(target_instruction);
274307
}
275308

276309
/// Checks each instruction in the goto program to see whether it is a method
Binary file not shown.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
public class Main {
2+
public void replaceNondetAssignment() {
3+
Object temp = org.cprover.CProver.nondetWithoutNull();
4+
}
5+
6+
public void replaceNondetAssignmentUnbox() {
7+
int temp = org.cprover.CProver.nondetWithoutNull();
8+
}
9+
10+
public void replaceNondetAssignmentImplicitCast() {
11+
Integer temp = org.cprover.CProver.nondetWithoutNull();
12+
}
13+
14+
public void replaceNondetAssignmentExplicitCast() {
15+
Integer temp = (Integer)org.cprover.CProver.nondetWithoutNull();
16+
}
17+
18+
public void replaceNondetAssignmentAddition() {
19+
int temp = ((int)org.cprover.CProver.nondetWithoutNull()) + 3;
20+
}
21+
22+
public void replaceNondetUnused() {
23+
org.cprover.CProver.nondetWithoutNull();
24+
}
25+
26+
public int replaceNondetReturnUnboxed() {
27+
return org.cprover.CProver.nondetWithoutNull();
28+
}
29+
30+
public Object replaceNondetReturn() {
31+
return org.cprover.CProver.nondetWithoutNull();
32+
}
33+
34+
public Integer replaceNondetReturnWithImplicitCast() {
35+
return org.cprover.CProver.nondetWithoutNull();
36+
}
37+
38+
public Integer replaceNondetReturnWithExplicitCast() {
39+
return (Integer)org.cprover.CProver.nondetWithoutNull();
40+
}
41+
42+
public Integer replaceNondetReturnAddition() {
43+
return ((int)org.cprover.CProver.nondetWithoutNull()) + 3;
44+
}
45+
}

cbmc/unit/java_bytecode/java_replace_nondet/replace_nondet.cpp

Lines changed: 143 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ void validate_method_removal(
2929
{
3030
bool method_removed = true, replacement_nondet_exists = false;
3131

32-
// Quick loop to check that our method call has been replaced.
32+
// Loop over our instructions to make sure the nondet java method call has
33+
// been removed and that we can find an assignment/return with a nondet
34+
// as it's right-hand side.
3335
for(const auto &inst : instructions)
3436
{
37+
// Check that our NONDET(<type>) exists on a rhs somewhere.
3538
if(inst.is_assign())
3639
{
3740
const code_assignt &assignment = to_code_assign(inst.code);
@@ -45,6 +48,21 @@ void validate_method_removal(
4548
}
4649
}
4750

51+
if(inst.is_return())
52+
{
53+
const code_returnt &ret_expr = to_code_return(inst.code);
54+
if(ret_expr.return_value().id() == ID_side_effect)
55+
{
56+
const side_effect_exprt &see =
57+
to_side_effect_expr(ret_expr.return_value());
58+
if(see.get_statement() == ID_nondet)
59+
{
60+
replacement_nondet_exists = true;
61+
}
62+
}
63+
}
64+
65+
// And check to see that our nondet method call has been removed.
4866
if(inst.is_function_call())
4967
{
5068
const code_function_callt &function_call =
@@ -70,57 +88,151 @@ void validate_method_removal(
7088
REQUIRE(replacement_nondet_exists);
7189
}
7290

73-
TEST_CASE(
74-
"Load class with a generated java nondet method call, run remove returns "
75-
"both before and after the nondet statements have been removed, check "
76-
"results are as expected.",
91+
void load_and_test_method(
92+
const std::string &method_signature,
93+
goto_functionst &functions,
94+
journalling_symbol_tablet &symbol_table)
95+
{
96+
// Find the method under test.
97+
const std::string function_name = "java::Main." + method_signature;
98+
goto_functionst::goto_functiont &goto_function =
99+
functions.function_map.at(function_name);
100+
101+
goto_model_functiont model_function(
102+
symbol_table, functions, function_name, goto_function);
103+
104+
// Emulate some of the passes that we'd normally do before replace_java_nondet
105+
// is called.
106+
remove_instanceof(goto_function, symbol_table);
107+
108+
remove_virtual_functions(model_function);
109+
110+
// Then test both situations.
111+
THEN(
112+
"Code should work when remove returns is called before "
113+
"replace_java_nondet.")
114+
{
115+
remove_returns(model_function, [](const irep_idt &) { return false; });
116+
117+
replace_java_nondet(model_function);
118+
119+
validate_method_removal(goto_function.body.instructions);
120+
}
121+
122+
THEN(
123+
"Code should work when remove returns is called after "
124+
"replace_java_nondet.")
125+
{
126+
replace_java_nondet(model_function);
127+
128+
remove_returns(model_function, [](const irep_idt &) { return false; });
129+
130+
validate_method_removal(goto_function.body.instructions);
131+
}
132+
}
133+
134+
SCENARIO(
135+
"Testing replace_java_nondet correctly replaces CProver.nondet method calls.",
77136
"[core][java_bytecode][replace_nondet]")
78137
{
79-
GIVEN("A class with a call to CProver.nondetWithoutNull()")
138+
GIVEN("A class that holds nondet calls.")
80139
{
81-
symbol_tablet raw_symbol_table = load_java_class(
82-
"Main", "./java_bytecode/java_replace_nondet", "Main.replaceNondet");
140+
// Load our main class.
141+
symbol_tablet raw_symbol_table =
142+
load_java_class("Main", "./java_bytecode/java_replace_nondet");
83143

84144
journalling_symbol_tablet symbol_table =
85145
journalling_symbol_tablet::wrap(raw_symbol_table);
86146

147+
// Convert bytecode into goto.
87148
null_message_handlert null_output;
88149
goto_functionst functions;
89150
goto_convert(symbol_table, functions, null_output);
90151

91-
const std::string function_name = "java::Main.replaceNondet:()V";
92-
goto_functionst::goto_functiont &goto_function =
93-
functions.function_map.at(function_name);
152+
WHEN("A method assigns a local Object variable with nondetWithoutNull.")
153+
{
154+
load_and_test_method(
155+
"replaceNondetAssignment:()V", functions, symbol_table);
156+
}
94157

95-
goto_model_functiont model_function(
96-
symbol_table, functions, function_name, goto_function);
158+
// Currently causes an abort, needs to be re-enabled when logic is fixed.
97159

98-
remove_instanceof(goto_function, symbol_table);
160+
// WHEN(
161+
// "A method assigns an int variable with nondetWithoutNull. Causes "
162+
// "unboxing.")
163+
// {
164+
// load_and_test_method("replaceNondetAssignmentUnbox:()V", functions, symbol_table);
165+
// }
99166

100-
remove_virtual_functions(model_function);
167+
WHEN(
168+
"A method assigns an Integer variable with nondetWithoutNull. Causes "
169+
"implicit cast.")
170+
{
171+
load_and_test_method(
172+
"replaceNondetAssignmentImplicitCast:()V", functions, symbol_table);
173+
}
101174

102-
WHEN("Remove returns is called before java nondet.")
175+
WHEN(
176+
"A method assigns an Integer variable with nondetWithoutNull. Uses "
177+
"explicit cast.")
103178
{
104-
remove_returns(model_function, [](const irep_idt &) { return false; });
179+
load_and_test_method(
180+
"replaceNondetAssignmentExplicitCast:()V", functions, symbol_table);
181+
}
105182

106-
replace_java_nondet(functions);
183+
// Currently causes an abort, needs to be re-enabled when logic is fixed.
107184

108-
THEN("The nondet method call should have been removed.")
109-
{
110-
validate_method_removal(goto_function.body.instructions);
111-
}
112-
}
185+
// WHEN(
186+
// "A method assigns an int variable with nondetWithoutNull +3 with explicit cast.")
187+
// {
188+
// load_and_test_method("replaceNondetAssignmentAddition:()V", functions, symbol_table);
189+
// }
113190

114-
WHEN("Remove returns is called after java nondet.")
191+
// WHEN(
192+
// "A method that calls nondetWithoutNull() without assigning the return value.")
193+
// {
194+
// load_and_test_method("replaceNondetUnused:()V", functions, symbol_table);
195+
// }
196+
197+
// WHEN(
198+
// "A method directly returns a nonDetWithoutNull of type int. Causes "
199+
// "unboxing.")
200+
// {
201+
// load_and_test_method("replaceNondetReturnUnboxed:()I", functions, symbol_table);
202+
// }
203+
204+
WHEN("A method directly returns a nonDetWithoutNull of type Object.")
115205
{
116-
replace_java_nondet(functions);
206+
load_and_test_method(
207+
"replaceNondetReturn:()Ljava/lang/Object;", functions, symbol_table);
208+
}
117209

118-
remove_returns(model_function, [](const irep_idt &) { return false; });
210+
WHEN(
211+
"A method directly returns a nonDetWithoutNull of type Integer. Causes "
212+
"implicit cast.")
213+
{
214+
load_and_test_method(
215+
"replaceNondetReturnWithImplicitCast:()Ljava/lang/Integer;",
216+
functions,
217+
symbol_table);
218+
}
119219

120-
THEN("The nondet method call should have been removed.")
121-
{
122-
validate_method_removal(goto_function.body.instructions);
123-
}
220+
WHEN(
221+
"A method directly returns a nonDetWithoutNull of type Integer. Uses "
222+
"explicit cast.")
223+
{
224+
load_and_test_method(
225+
"replaceNondetReturnWithExplicitCast:()Ljava/lang/Integer;",
226+
functions,
227+
symbol_table);
124228
}
229+
230+
// Currently causes an abort, needs to be re-enabled when logic is fixed.
231+
232+
// WHEN(
233+
// "A method directly returns a nonDetWithoutNull +3 with explicit int cast.")
234+
// {
235+
// load_and_test_method("replaceNondetReturnAddition:()Ljava/lang/Integer;", functions, symbol_table);
236+
// }
125237
}
126-
}
238+
}

cbmc/unit/java_bytecode/java_replace_nondet/test.java

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)