Skip to content

Commit a2a5662

Browse files
authored
Merge pull request #2263 from JohnDumbell/bugfix/nondet_direct_return
Fix for nondet replacement on a direct return pre-remove returns
2 parents bbf0d02 + 91e8b89 commit a2a5662

File tree

5 files changed

+253
-65
lines changed

5 files changed

+253
-65
lines changed

jbmc/src/java_bytecode/replace_java_nondet.cpp

+65-26
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,25 @@ static bool is_assignment_from(
152152
is_typecast_with_id(rhs, identifier);
153153
}
154154

155+
/// Return whether the instruction is a return, and the rhs is a symbol or
156+
/// typecast expression with the specified identifier.
157+
/// \param instr: A goto program instruction.
158+
/// \param identifier: Some identifier.
159+
/// \return True if the expression is a typecast with one operand, and the
160+
/// typecast's identifier matches the specified identifier.
161+
static bool is_return_with_variable(
162+
const goto_programt::instructiont &instr,
163+
const irep_idt &identifier)
164+
{
165+
if(!instr.is_return())
166+
{
167+
return false;
168+
}
169+
const auto &rhs = to_code_return(instr.code).return_value();
170+
return is_symbol_with_id(rhs, identifier) ||
171+
is_typecast_with_id(rhs, identifier);
172+
}
173+
155174
/// Given an iterator into a list of instructions, modify the list to replace
156175
/// 'nondet' library functions with CBMC-native nondet expressions, and return
157176
/// an iterator to the next instruction to check. It's important to note that
@@ -166,7 +185,10 @@ static bool is_assignment_from(
166185
/// obj = (<type-of-obj>)return_tmp0;
167186
///
168187
/// We're going to replace all of these lines with
169-
/// return_tmp0 = NONDET(<type-of-obj>)
188+
/// obj = NONDET(<type-of-obj>)
189+
///
190+
/// In the situation of a direct return, the end result should be:
191+
/// return NONDET(<type-of-obj>)
170192
/// \param goto_program: The goto program to modify.
171193
/// \param target: A single step of the goto program which may be erased and
172194
/// replaced.
@@ -225,47 +247,64 @@ static goto_programt::targett check_and_replace_target(
225247
// Look for the assignment of the temporary return variable into our target
226248
// variable.
227249
const auto end = goto_program.instructions.end();
228-
auto assignment_instruction = std::find_if(
250+
auto target_instruction = std::find_if(
229251
next_instr,
230252
end,
231253
[&return_identifier](const goto_programt::instructiont &instr) {
232254
return is_assignment_from(instr, return_identifier);
233255
});
234256

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

245-
// Erase from the nondet function call to the assignment
246-
const auto after_matching_assignment = std::next(assignment_instruction);
247268
INVARIANT(
248-
after_matching_assignment != end,
249-
"goto_program missing END_FUNCTION instruction");
269+
target_instruction != end,
270+
"failed to find return of the temporary return variable or assignment of "
271+
"the temporary return variable into a target variable");
250272

251273
std::for_each(
252-
target, after_matching_assignment, [](goto_programt::instructiont &instr) {
274+
target, target_instruction, [](goto_programt::instructiont &instr) {
253275
instr.make_skip();
254276
});
255277

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

266305
goto_program.update();
267306

268-
return after_matching_assignment;
307+
return std::next(target_instruction);
269308
}
270309

271310
/// Checks each instruction in the goto program to see whether it is a method
Binary file not shown.
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+
}

jbmc/unit/java_bytecode/java_replace_nondet/replace_nondet.cpp

+143-34
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,19 @@
2424
#include <goto-instrument/cover.h>
2525

2626
#include <iostream>
27+
#include <java-testing-utils/load_java_class.h>
2728

2829
void validate_method_removal(
2930
std::list<goto_programt::instructiont> instructions)
3031
{
3132
bool method_removed = true, replacement_nondet_exists = false;
3233

33-
// Quick loop to check that our method call has been replaced.
34+
// Loop over our instructions to make sure the nondet java method call has
35+
// been removed and that we can find an assignment/return with a nondet
36+
// as it's right-hand side.
3437
for(const auto &inst : instructions)
3538
{
39+
// Check that our NONDET(<type>) exists on a rhs somewhere.
3640
if(inst.is_assign())
3741
{
3842
const code_assignt &assignment = to_code_assign(inst.code);
@@ -46,6 +50,21 @@ void validate_method_removal(
4650
}
4751
}
4852

53+
if(inst.is_return())
54+
{
55+
const code_returnt &ret_expr = to_code_return(inst.code);
56+
if(ret_expr.return_value().id() == ID_side_effect)
57+
{
58+
const side_effect_exprt &see =
59+
to_side_effect_expr(ret_expr.return_value());
60+
if(see.get_statement() == ID_nondet)
61+
{
62+
replacement_nondet_exists = true;
63+
}
64+
}
65+
}
66+
67+
// And check to see that our nondet method call has been removed.
4968
if(inst.is_function_call())
5069
{
5170
const code_function_callt &function_call =
@@ -71,56 +90,146 @@ void validate_method_removal(
7190
REQUIRE(replacement_nondet_exists);
7291
}
7392

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

85146
journalling_symbol_tablet symbol_table =
86147
journalling_symbol_tablet::wrap(raw_symbol_table);
87148

149+
// Convert bytecode into goto.
88150
goto_functionst functions;
89151
goto_convert(symbol_table, functions, null_message_handler);
90152

91-
const std::string function_name = "java::Main.replaceNondet:()V";
92-
goto_functionst::goto_functiont &goto_function =
93-
functions.function_map.at(function_name);
94-
95-
goto_model_functiont model_function(
96-
symbol_table, functions, function_name, goto_function);
97-
98-
remove_instanceof(goto_function, symbol_table, null_message_handler);
99-
100-
remove_virtual_functions(model_function);
101-
102-
WHEN("Remove returns is called before java nondet.")
153+
WHEN("A method assigns a local Object variable with nondetWithoutNull.")
103154
{
104-
remove_returns(model_function, [](const irep_idt &) { return false; });
155+
load_and_test_method(
156+
"replaceNondetAssignment:()V", functions, symbol_table);
157+
}
105158

106-
replace_java_nondet(functions);
159+
WHEN(
160+
"A method assigns an Integer variable with nondetWithoutNull. Causes "
161+
"implicit cast.")
162+
{
163+
load_and_test_method(
164+
"replaceNondetAssignmentImplicitCast:()V", functions, symbol_table);
165+
}
107166

108-
THEN("The nondet method call should have been removed.")
109-
{
110-
validate_method_removal(goto_function.body.instructions);
111-
}
167+
WHEN(
168+
"A method assigns an Integer variable with nondetWithoutNull. Uses "
169+
"explicit cast.")
170+
{
171+
load_and_test_method(
172+
"replaceNondetAssignmentExplicitCast:()V", functions, symbol_table);
112173
}
113174

114-
WHEN("Remove returns is called after java nondet.")
175+
WHEN("A method directly returns a nonDetWithoutNull of type Object.")
115176
{
116-
replace_java_nondet(functions);
177+
load_and_test_method(
178+
"replaceNondetReturn:()Ljava/lang/Object;", functions, symbol_table);
179+
}
117180

118-
remove_returns(model_function, [](const irep_idt &) { return false; });
181+
WHEN(
182+
"A method directly returns a nonDetWithoutNull of type Integer. Causes "
183+
"implicit cast.")
184+
{
185+
load_and_test_method(
186+
"replaceNondetReturnWithImplicitCast:()Ljava/lang/Integer;",
187+
functions,
188+
symbol_table);
189+
}
119190

120-
THEN("The nondet method call should have been removed.")
121-
{
122-
validate_method_removal(goto_function.body.instructions);
123-
}
191+
WHEN(
192+
"A method directly returns a nonDetWithoutNull of type Integer. Uses "
193+
"explicit cast.")
194+
{
195+
load_and_test_method(
196+
"replaceNondetReturnWithExplicitCast:()Ljava/lang/Integer;",
197+
functions,
198+
symbol_table);
124199
}
200+
201+
// These currently cause an abort, issue detailed in https://github.com/diffblue/cbmc/issues/2281.
202+
203+
// WHEN(
204+
// "A method directly returns a nonDetWithoutNull +3 with explicit int cast.")
205+
// {
206+
// load_and_test_method("replaceNondetReturnAddition:()Ljava/lang/Integer;", functions, symbol_table);
207+
// }
208+
209+
// WHEN(
210+
// "A method assigns an int variable with nondetWithoutNull. Causes "
211+
// "unboxing.")
212+
// {
213+
// load_and_test_method("replaceNondetAssignmentUnbox:()V", functions, symbol_table);
214+
// }
215+
216+
// WHEN(
217+
// "A method assigns an int variable with nondetWithoutNull +3 with explicit cast.")
218+
// {
219+
// load_and_test_method("replaceNondetAssignmentAddition:()V", functions, symbol_table);
220+
// }
221+
222+
// WHEN(
223+
// "A method that calls nondetWithoutNull() without assigning the return value.")
224+
// {
225+
// load_and_test_method("replaceNondetUnused:()V", functions, symbol_table);
226+
// }
227+
228+
// WHEN(
229+
// "A method directly returns a nonDetWithoutNull of type int. Causes "
230+
// "unboxing.")
231+
// {
232+
// load_and_test_method("replaceNondetReturnUnboxed:()I", functions, symbol_table);
233+
// }
125234
}
126-
}
235+
}

0 commit comments

Comments
 (0)