Skip to content

Do not generate redundant if statements in assert expansions #974

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 4 commits into from
Apr 16, 2018
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 regression/cbmc/simple_assert/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <assert.h>

int main(int argc, char *argv[])
{
int x = 5;
assert(x == 5);

return 0;
}
13 changes: 13 additions & 0 deletions regression/cbmc/simple_assert/test.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CORE
main.c
--cover location
^EXIT=0$
^SIGNAL=0$
^\[main\.coverage\.1\] .* function main block 1: SATISFIED$
(1 of 1|3 of 3) covered \(100\.0%\)$
--
^warning: ignoring
^CONVERSION ERROR$
^\[main\.coverage\..\] .* function main block .: FAILED$
--
On Windows/Visual Studio, "assert" does not introduce any branching.
7 changes: 7 additions & 0 deletions regression/goto-instrument/assert1/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include <assert.h>

int main(int argc, char *argv[])
{
assert(0);
return 0;
}
8 changes: 8 additions & 0 deletions regression/goto-instrument/assert1/test.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CORE
main.c
--dump-c
^EXIT=10$
^SIGNAL=0$
--
^warning: ignoring
^[[:space:]]*IF
10 changes: 8 additions & 2 deletions src/goto-programs/goto_clean_expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,13 +260,19 @@ void goto_convertt::clean_expr(
// preserve the expressions for possible later checks
if(if_expr.true_case().is_not_nil())
{
code_expressiont code_expression(if_expr.true_case());
// add a (void) type cast so that is_skip catches it in case the
// expression is just a constant
code_expressiont code_expression(
typecast_exprt(if_expr.true_case(), empty_typet()));
convert(code_expression, tmp_true);
}

if(if_expr.false_case().is_not_nil())
{
code_expressiont code_expression(if_expr.false_case());
// add a (void) type cast so that is_skip catches it in case the
// expression is just a constant
code_expressiont code_expression(
typecast_exprt(if_expr.false_case(), empty_typet()));
convert(code_expression, tmp_false);
}

Expand Down
32 changes: 29 additions & 3 deletions src/goto-programs/goto_convert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@ Author: Daniel Kroening, [email protected]

#include "goto_convert_class.h"
#include "destructor.h"
#include "remove_skip.h"

static bool is_empty(const goto_programt &goto_program)
{
forall_goto_program_instructions(it, goto_program)
if(!it->is_skip() ||
!it->labels.empty() ||
!it->code.is_nil())
if(!is_skip(goto_program, it))
return false;

return true;
Expand Down Expand Up @@ -1723,6 +1722,11 @@ void goto_convertt::generate_ifthenelse(
true_case.instructions.back().guard=boolean_negate(guard);
dest.destructive_append(true_case);
true_case.instructions.clear();
if(
is_empty(false_case) ||
(is_size_one(false_case) &&
is_skip(false_case, false_case.instructions.begin())))
return;
}

// similarly, do guarded assertions directly
Expand All @@ -1736,6 +1740,28 @@ void goto_convertt::generate_ifthenelse(
false_case.instructions.back().guard=guard;
dest.destructive_append(false_case);
false_case.instructions.clear();
if(
is_empty(true_case) ||
(is_size_one(true_case) &&
is_skip(true_case, true_case.instructions.begin())))
return;
}

// a special case for C libraries that use
// (void)((cond) || (assert(0),0))
if(
is_empty(false_case) && true_case.instructions.size() == 2 &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any reason to restrict ourselves to two instructions here? Could we simply say "if(x) { assert(false); ... } --> assert(!x)", since the rest of the true-case is dead code? Also, why only consider the true case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's only dead code if none of the instruction has a label that could be jumped to.

true_case.instructions.front().is_assert() &&
true_case.instructions.front().guard.is_false() &&
true_case.instructions.front().labels.empty() &&
true_case.instructions.back().labels.empty())
{
true_case.instructions.front().guard = boolean_negate(guard);
true_case.instructions.erase(--true_case.instructions.end());
dest.destructive_append(true_case);
true_case.instructions.clear();

return;
}

// Flip around if no 'true' case code.
Expand Down
16 changes: 10 additions & 6 deletions src/goto-programs/remove_skip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Author: Daniel Kroening, [email protected]
#include "remove_skip.h"
#include "goto_model.h"

static bool is_skip(goto_programt::instructionst::iterator it)
bool is_skip(const goto_programt &body, goto_programt::const_targett it)
{
// we won't remove labelled statements
// (think about error labels or the like)
Expand All @@ -28,9 +28,12 @@ static bool is_skip(goto_programt::instructionst::iterator it)
if(it->guard.is_false())
return true;

goto_programt::instructionst::iterator next_it=it;
goto_programt::const_targett next_it = it;
next_it++;

if(next_it == body.instructions.end())
return false;

// A branch to the next instruction is a skip
// We also require the guard to be 'true'
return it->guard.is_true() &&
Expand Down Expand Up @@ -92,7 +95,7 @@ void remove_skip(goto_programt &goto_program)
// for collecting labels
std::list<irep_idt> labels;

while(is_skip(it))
while(is_skip(goto_program, it))
{
// don't remove the last skip statement,
// it could be a target
Expand Down Expand Up @@ -144,9 +147,10 @@ void remove_skip(goto_programt &goto_program)
// remove the last skip statement unless it's a target
goto_program.compute_incoming_edges();

if(!goto_program.instructions.empty() &&
is_skip(--goto_program.instructions.end()) &&
!goto_program.instructions.back().is_target())
if(
!goto_program.instructions.empty() &&
is_skip(goto_program, --goto_program.instructions.end()) &&
!goto_program.instructions.back().is_target())
goto_program.instructions.pop_back();
}
while(goto_program.instructions.size()<old_size);
Expand Down
1 change: 1 addition & 0 deletions src/goto-programs/remove_skip.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Author: Daniel Kroening, [email protected]

class goto_modelt;

bool is_skip(const goto_programt &, goto_programt::const_targett);
void remove_skip(goto_programt &);
void remove_skip(goto_functionst &);
void remove_skip(goto_modelt &);
Expand Down