Skip to content

Remove virtual functions: simplify and fix no-fallback-function mode #1940

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/goto-programs/goto_program.h
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,15 @@ class goto_programt
return end_function;
}

const_targett get_end_function() const
{
PRECONDITION(!instructions.empty());
const auto end_function=std::prev(instructions.end());
DATA_INVARIANT(end_function->is_end_function(),
"goto program ends on END_FUNCTION");
return end_function;
}

/// Copy a full goto program, preserving targets
void copy_from(const goto_programt &src);

Expand Down
89 changes: 44 additions & 45 deletions src/goto-programs/remove_virtual_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ void remove_virtual_functionst::remove_virtual_function(

// So long as `this` is already not `void*` typed, the second parameter
// is ignored:
exprt c_id2=get_class_identifier_field(this_expr, symbol_typet(), ns);
exprt this_class_identifier =
get_class_identifier_field(this_expr, symbol_typet(), ns);

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

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

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

Expand Down Expand Up @@ -232,49 +229,36 @@ void remove_virtual_functionst::remove_virtual_function(
t3->make_goto(t_final, true_exprt());
}

// Emit target if end of dispatch table is reached or if the next element is
// dispatched to another function call. Assumes entries in the functions
// variable to be sorted for the identifier of the function to be called.
auto l_it = std::next(it);
bool next_emit_target =
(l_it == functions.crend()) ||
l_it->symbol_expr.get_identifier() != fun.symbol_expr.get_identifier();

// The root function call is done via fall-through, so nothing to emit
// explicitly for this.
if(next_emit_target && fun.symbol_expr == last_function_symbol)
// Fall through to the default callee if possible:
if(fallback_action ==
virtual_dispatch_fallback_actiont::CALL_LAST_FUNCTION &&
fun.symbol_expr == last_function_symbol)
{
class_ids.clear();
// Nothing to do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about negating the condition to get rid of this branch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll keep this as I wanted to be explicit that I'm making a positive "I can skip a branch in exactly this situation" test, rather than a negative test for when I must do work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could do

if(condition)
  continue;

// do the work

To make it clear you are skipping over an instruction, removing the unnecessary indentation and make it clear there really is nothing to do in this case

}

// If this calls the fallback function we just fall through.
// Otherwise branch to the right call:
if(fallback_action!=virtual_dispatch_fallback_actiont::CALL_LAST_FUNCTION ||
fun.symbol_expr!=last_function_symbol)
else
{
// create a disjunction of class_ids to test
if(next_emit_target && fun.symbol_expr != last_function_symbol)
const constant_exprt fun_class_identifier(fun.class_id, string_typet());
const equal_exprt class_id_test(
fun_class_identifier, this_class_identifier);

// If the previous GOTO goes to the same callee, join it
// (e.g. turning IF x GOTO y into IF x || z GOTO y)
if(it != functions.crbegin() &&
std::prev(it)->symbol_expr == fun.symbol_expr)
{
exprt::operandst or_ops;
for(const auto &id : class_ids)
{
const constant_exprt c_id1(id, string_typet());
const equal_exprt class_id_test(c_id1, c_id2);
or_ops.push_back(class_id_test);
}

goto_programt::targett t4 = new_code_gotos.add_instruction();
t4->source_location = vcall_source_loc;
t4->make_goto(insertit.first->second, disjunction(or_ops));

last_id = fun.symbol_expr.get_identifier();
class_ids.clear();
INVARIANT(
!new_code_gotos.empty(),
"a dispatch table entry has been processed already, "
"which should have created a GOTO");
new_code_gotos.instructions.back().guard =
or_exprt(new_code_gotos.instructions.back().guard, class_id_test);
}
// record class_id
else if(next_emit_target)
else
{
last_id = fun.symbol_expr.get_identifier();
class_ids.clear();
goto_programt::targett new_goto = new_code_gotos.add_instruction();
new_goto->source_location = vcall_source_loc;
new_goto->make_goto(insertit.first->second, class_id_test);
}
}
}
Expand Down Expand Up @@ -555,20 +539,35 @@ void remove_virtual_functions(goto_model_functiont &function)
}

void remove_virtual_function(
goto_modelt &goto_model,
symbol_tablet &symbol_table,
goto_programt &goto_program,
goto_programt::targett instruction,
const dispatch_table_entriest &dispatch_table,
virtual_dispatch_fallback_actiont fallback_action)
{
class_hierarchyt class_hierarchy;
class_hierarchy(goto_model.symbol_table);
remove_virtual_functionst rvf(goto_model.symbol_table, class_hierarchy);
class_hierarchy(symbol_table);
remove_virtual_functionst rvf(symbol_table, class_hierarchy);

rvf.remove_virtual_function(
goto_program, instruction, dispatch_table, fallback_action);
}

void remove_virtual_function(
goto_modelt &goto_model,
goto_programt &goto_program,
goto_programt::targett instruction,
const dispatch_table_entriest &dispatch_table,
virtual_dispatch_fallback_actiont fallback_action)
{
remove_virtual_function(
goto_model.symbol_table,
goto_program,
instruction,
dispatch_table,
fallback_action);
}

void collect_virtual_function_callees(
const exprt &function,
const symbol_table_baset &symbol_table,
Expand Down
7 changes: 7 additions & 0 deletions src/goto-programs/remove_virtual_functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ void remove_virtual_function(
const dispatch_table_entriest &dispatch_table,
virtual_dispatch_fallback_actiont fallback_action);

void remove_virtual_function(
symbol_tablet &symbol_table,
goto_programt &goto_program,
goto_programt::targett instruction,
const dispatch_table_entriest &dispatch_table,
virtual_dispatch_fallback_actiont fallback_action);

/// Given a function expression representing a virtual method of a class,
/// the function computes all overridden methods of that virtual method.
/// \param function: The virtual function expression for which the overridden
Expand Down
1 change: 1 addition & 0 deletions unit/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ SRC += unit_tests.cpp \
goto-programs/goto_trace_output.cpp \
goto-programs/class_hierarchy_output.cpp \
goto-programs/class_hierarchy_graph.cpp \
goto-programs/remove_virtual_functions_without_fallback.cpp \
java_bytecode/java_bytecode_convert_class/convert_abstract_class.cpp \
java_bytecode/java_bytecode_parse_generics/parse_generic_class.cpp \
java_bytecode/java_object_factory/gen_nondet_string_init.cpp \
Expand Down
Binary file added unit/goto-programs/VirtualFunctionsTestChild1.class
Binary file not shown.
Binary file added unit/goto-programs/VirtualFunctionsTestChild2.class
Binary file not shown.
Binary file not shown.
Binary file added unit/goto-programs/VirtualFunctionsTestParent.class
Binary file not shown.
28 changes: 28 additions & 0 deletions unit/goto-programs/VirtualFunctionsTestParent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

class VirtualFunctionsTestParent {

// These fields only exist so the classloader will load the children when
// asked to load the parent:
VirtualFunctionsTestChild1 c1;
VirtualFunctionsTestChild2 c2;
VirtualFunctionsTestGrandchild g;

public void f() { }

}

class VirtualFunctionsTestChild1 extends VirtualFunctionsTestParent {

public void f() { }

}

class VirtualFunctionsTestChild2 extends VirtualFunctionsTestParent {

public void f() { }

}

class VirtualFunctionsTestGrandchild extends VirtualFunctionsTestChild1 {

}
Loading