Skip to content

Commit 8c62e85

Browse files
committed
Fix for nondet replacement on a direct return (pre-remove returns)
If remove returns hasn't been run and the nondet method call was a direct return - return nondetWithoutNull() - an assertion was hit because it couldn't find the destination assignment to add the nondet value too. This change just adds that particular situation in, saying if we can't find an assignment it's likely to be a return and then attempts to look for that. The logic is also very slightly modified to replace the code of the target instruction instead of destroying it, creating a new one and inserting that directly afterwards.
1 parent 5e5e264 commit 8c62e85

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)