Skip to content

Commit af50e42

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 f9058e7 commit af50e42

File tree

5 files changed

+250
-63
lines changed

5 files changed

+250
-63
lines changed

jbmc/src/java_bytecode/replace_java_nondet.cpp

Lines changed: 61 additions & 26 deletions
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,60 @@ 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+
}
289+
else if(target_instruction->is_assign())
290+
{
291+
// Assume that the LHS of *this* assignment is the actual nondet variable
292+
const auto &nondet_var = to_code_assign(target_instruction->code).lhs();
293+
294+
side_effect_expr_nondett inserted_expr(nondet_var.type());
295+
inserted_expr.set_nullable(
296+
instr_info.get_nullable_type() ==
297+
nondet_instruction_infot::is_nullablet::TRUE);
298+
target_instruction->code = code_assignt(nondet_var, inserted_expr);
299+
}
265300

266301
goto_program.update();
267302

268-
return after_matching_assignment;
303+
return std::next(target_instruction);
269304
}
270305

271306
/// 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+
}

jbmc/unit/java_bytecode/java_replace_nondet/replace_nondet.cpp

Lines changed: 144 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
\*******************************************************************/
99

1010
#include <testing-utils/catch.hpp>
11-
#include <java-testing-utils/load_java_class.h>
11+
#include <testing-utils/load_java_class.h>
1212

1313
#include <goto-programs/goto_convert_functions.h>
1414
#include <goto-programs/remove_virtual_functions.h>
@@ -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+
}

0 commit comments

Comments
 (0)