Skip to content

Commit b66c75b

Browse files
committed
Remove virtual functions: fix no-fallback-function mode
Changes since the assume-false-if-no-match mode was introduced had broken it, so this also adds unit tests to ensure that the assume-false mode continues to work in future.
1 parent d83fa17 commit b66c75b

10 files changed

+415
-45
lines changed

src/goto-programs/goto_program.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,15 @@ class goto_programt
593593
return end_function;
594594
}
595595

596+
const_targett get_end_function() const
597+
{
598+
PRECONDITION(!instructions.empty());
599+
const auto end_function=std::prev(instructions.end());
600+
DATA_INVARIANT(end_function->is_end_function(),
601+
"goto program ends on END_FUNCTION");
602+
return end_function;
603+
}
604+
596605
/// Copy a full goto program, preserving targets
597606
void copy_from(const goto_programt &src);
598607

src/goto-programs/remove_virtual_functions.cpp

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,24 @@ static void create_static_function_call(
120120
call.arguments()[0].make_typecast(need_type);
121121
}
122122

123+
/// Create a disjunction, or add to an existing one. For example,
124+
/// `add_to_disjunction(x || y, z)` = `x || y || z` (a ternary operation, not
125+
/// nested binary operations), but `add_to_disjunction(w, z)` where `w` is not
126+
/// an or_exprt already = `w || z`.
127+
/// \param maybe_or_expr: expression to add to (`x || y` or `w` in the examples)
128+
/// \param new_disjunct: new disjunct, written `z` in the examples.
129+
static void add_to_disjunction(exprt &maybe_or_expr, const exprt &new_disjunct)
130+
{
131+
if(maybe_or_expr.id() != ID_or)
132+
{
133+
or_exprt or_expr;
134+
or_expr.move_to_operands(maybe_or_expr);
135+
maybe_or_expr.swap(or_expr);
136+
}
137+
138+
maybe_or_expr.copy_to_operands(new_disjunct);
139+
}
140+
123141
void remove_virtual_functionst::remove_virtual_function(
124142
goto_programt &goto_program,
125143
goto_programt::targett target,
@@ -178,7 +196,8 @@ void remove_virtual_functionst::remove_virtual_function(
178196

179197
// So long as `this` is already not `void*` typed, the second parameter
180198
// is ignored:
181-
exprt c_id2=get_class_identifier_field(this_expr, symbol_typet(), ns);
199+
exprt this_class_identifier =
200+
get_class_identifier_field(this_expr, symbol_typet(), ns);
182201

183202
// If instructed, add an ASSUME(FALSE) to handle the case where we don't
184203
// match any expected type:
@@ -191,16 +210,12 @@ void remove_virtual_functionst::remove_virtual_function(
191210

192211
// get initial identifier for grouping
193212
INVARIANT(!functions.empty(), "Function dispatch table cannot be empty.");
194-
auto last_id = functions.back().symbol_expr.get_identifier();
195-
// record class_ids for disjunction
196-
std::set<irep_idt> class_ids;
197213

198214
std::map<irep_idt, goto_programt::targett> calls;
199215
// Note backwards iteration, to get the fallback candidate first.
200216
for(auto it=functions.crbegin(), itend=functions.crend(); it!=itend; ++it)
201217
{
202218
const auto &fun=*it;
203-
class_ids.insert(fun.class_id);
204219
auto insertit=calls.insert(
205220
{fun.symbol_expr.get_identifier(), goto_programt::targett()});
206221

@@ -232,49 +247,36 @@ void remove_virtual_functionst::remove_virtual_function(
232247
t3->make_goto(t_final, true_exprt());
233248
}
234249

235-
// Emit target if end of dispatch table is reached or if the next element is
236-
// dispatched to another function call. Assumes entries in the functions
237-
// variable to be sorted for the identifier of the function to be called.
238-
auto l_it = std::next(it);
239-
bool next_emit_target =
240-
(l_it == functions.crend()) ||
241-
l_it->symbol_expr.get_identifier() != fun.symbol_expr.get_identifier();
242-
243-
// The root function call is done via fall-through, so nothing to emit
244-
// explicitly for this.
245-
if(next_emit_target && fun.symbol_expr == last_function_symbol)
250+
// Fall through to the default callee if possible:
251+
if(fallback_action ==
252+
virtual_dispatch_fallback_actiont::CALL_LAST_FUNCTION &&
253+
fun.symbol_expr == last_function_symbol)
246254
{
247-
class_ids.clear();
255+
// Nothing to do
248256
}
249-
250-
// If this calls the fallback function we just fall through.
251-
// Otherwise branch to the right call:
252-
if(fallback_action!=virtual_dispatch_fallback_actiont::CALL_LAST_FUNCTION ||
253-
fun.symbol_expr!=last_function_symbol)
257+
else
254258
{
255-
// create a disjunction of class_ids to test
256-
if(next_emit_target && fun.symbol_expr != last_function_symbol)
259+
const constant_exprt fun_class_identifier(fun.class_id, string_typet());
260+
const equal_exprt class_id_test(
261+
fun_class_identifier, this_class_identifier);
262+
263+
// If the previous GOTO goes to the same callee, join it
264+
// (e.g. turning IF x GOTO y into IF x || z GOTO y)
265+
if(it != functions.crbegin() &&
266+
std::prev(it)->symbol_expr == fun.symbol_expr)
257267
{
258-
exprt::operandst or_ops;
259-
for(const auto &id : class_ids)
260-
{
261-
const constant_exprt c_id1(id, string_typet());
262-
const equal_exprt class_id_test(c_id1, c_id2);
263-
or_ops.push_back(class_id_test);
264-
}
265-
266-
goto_programt::targett t4 = new_code_gotos.add_instruction();
267-
t4->source_location = vcall_source_loc;
268-
t4->make_goto(insertit.first->second, disjunction(or_ops));
269-
270-
last_id = fun.symbol_expr.get_identifier();
271-
class_ids.clear();
268+
INVARIANT(
269+
!new_code_gotos.empty(),
270+
"a dispatch table entry has been processed already, "
271+
"which should have created a GOTO");
272+
add_to_disjunction(
273+
new_code_gotos.instructions.back().guard, class_id_test);
272274
}
273-
// record class_id
274-
else if(next_emit_target)
275+
else
275276
{
276-
last_id = fun.symbol_expr.get_identifier();
277-
class_ids.clear();
277+
goto_programt::targett new_goto = new_code_gotos.add_instruction();
278+
new_goto->source_location = vcall_source_loc;
279+
new_goto->make_goto(insertit.first->second, class_id_test);
278280
}
279281
}
280282
}
@@ -555,20 +557,35 @@ void remove_virtual_functions(goto_model_functiont &function)
555557
}
556558

557559
void remove_virtual_function(
558-
goto_modelt &goto_model,
560+
symbol_tablet &symbol_table,
559561
goto_programt &goto_program,
560562
goto_programt::targett instruction,
561563
const dispatch_table_entriest &dispatch_table,
562564
virtual_dispatch_fallback_actiont fallback_action)
563565
{
564566
class_hierarchyt class_hierarchy;
565-
class_hierarchy(goto_model.symbol_table);
566-
remove_virtual_functionst rvf(goto_model.symbol_table, class_hierarchy);
567+
class_hierarchy(symbol_table);
568+
remove_virtual_functionst rvf(symbol_table, class_hierarchy);
567569

568570
rvf.remove_virtual_function(
569571
goto_program, instruction, dispatch_table, fallback_action);
570572
}
571573

574+
void remove_virtual_function(
575+
goto_modelt &goto_model,
576+
goto_programt &goto_program,
577+
goto_programt::targett instruction,
578+
const dispatch_table_entriest &dispatch_table,
579+
virtual_dispatch_fallback_actiont fallback_action)
580+
{
581+
remove_virtual_function(
582+
goto_model.symbol_table,
583+
goto_program,
584+
instruction,
585+
dispatch_table,
586+
fallback_action);
587+
}
588+
572589
void collect_virtual_function_callees(
573590
const exprt &function,
574591
const symbol_table_baset &symbol_table,

src/goto-programs/remove_virtual_functions.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ void remove_virtual_function(
6666
const dispatch_table_entriest &dispatch_table,
6767
virtual_dispatch_fallback_actiont fallback_action);
6868

69+
void remove_virtual_function(
70+
symbol_tablet &symbol_table,
71+
goto_programt &goto_program,
72+
goto_programt::targett instruction,
73+
const dispatch_table_entriest &dispatch_table,
74+
virtual_dispatch_fallback_actiont fallback_action);
75+
6976
/// Given a function expression representing a virtual method of a class,
7077
/// the function computes all overridden methods of that virtual method.
7178
/// \param function: The virtual function expression for which the overridden

unit/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ SRC += unit_tests.cpp \
1919
goto-programs/goto_trace_output.cpp \
2020
goto-programs/class_hierarchy_output.cpp \
2121
goto-programs/class_hierarchy_graph.cpp \
22+
goto-programs/remove_virtual_functions_without_fallback.cpp \
2223
java_bytecode/java_bytecode_convert_class/convert_abstract_class.cpp \
2324
java_bytecode/java_bytecode_parse_generics/parse_generic_class.cpp \
2425
java_bytecode/java_object_factory/gen_nondet_string_init.cpp \
279 Bytes
Binary file not shown.
279 Bytes
Binary file not shown.
Binary file not shown.
404 Bytes
Binary file not shown.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
class VirtualFunctionsTestParent {
3+
4+
// These fields only exist so the classloader will load the children when
5+
// asked to load the parent:
6+
VirtualFunctionsTestChild1 c1;
7+
VirtualFunctionsTestChild2 c2;
8+
VirtualFunctionsTestGrandchild g;
9+
10+
public void f() { }
11+
12+
}
13+
14+
class VirtualFunctionsTestChild1 extends VirtualFunctionsTestParent {
15+
16+
public void f() { }
17+
18+
}
19+
20+
class VirtualFunctionsTestChild2 extends VirtualFunctionsTestParent {
21+
22+
public void f() { }
23+
24+
}
25+
26+
class VirtualFunctionsTestGrandchild extends VirtualFunctionsTestChild1 {
27+
28+
}

0 commit comments

Comments
 (0)