Skip to content

Assert type consistency in the simplifier #2064

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 3 commits into from
Mar 7, 2019
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
2 changes: 2 additions & 0 deletions src/solvers/strings/string_constraint_generator_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ array_string_exprt array_poolt::make_char_array_for_char_pointer(
if_expr.cond(),
to_array_type(t.type()).size(),
to_array_type(f.type()).size()));
// BEWARE: this expression is possibly type-inconsistent and must not be
// used for any purposes other than passing it to concretisation
return to_array_string_expr(if_exprt(if_expr.cond(), t, f, array_type));
}
const bool is_constant_array =
Expand Down
48 changes: 47 additions & 1 deletion src/solvers/strings/string_refinement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,49 @@ decision_proceduret::resultt string_refinementt::dec_solve()
return resultt::D_ERROR;
}

/// In a best-effort manner, try to clean up the type inconsistencies introduced
/// by \ref array_poolt::make_char_array_for_char_pointer, which creates
/// conditional expressions for the size of arrays. The cleanup is achieved by
/// removing branches that are found to be infeasible, and by simplifying the
/// conditional size expressions previously generated.
/// \param expr: Expression to be cleaned
/// \param ns: Namespace
/// \return Cleaned expression
static exprt adjust_if_recursive(exprt expr, const namespacet &ns)
{
for(auto it = expr.depth_begin(); it != expr.depth_end();)
{
if(it->id() == ID_if)
{
if_exprt if_expr = to_if_expr(*it);
const exprt simp_cond = simplify_expr(if_expr.cond(), ns);
if(simp_cond.is_true())
{
it.mutate() = adjust_if_recursive(if_expr.true_case(), ns);
it.next_sibling_or_parent();
}
else if(simp_cond.is_false())
{
it.mutate() = adjust_if_recursive(if_expr.false_case(), ns);
it.next_sibling_or_parent();
}
else if(
it->type().id() == ID_array &&
to_array_type(it->type()).size().id() == ID_if)
{
simplify(to_array_type(it.mutate().type()).size(), ns);
++it;
}
else
++it;
}
else
++it;
}

return expr;
}

/// Add the given lemma to the solver.
/// \param lemma: a Boolean expression
/// \param simplify_lemma: whether the lemma should be simplified before being
Expand All @@ -869,7 +912,10 @@ void string_refinementt::add_lemma(

exprt simple_lemma = lemma;
if(simplify_lemma)
{
simple_lemma = adjust_if_recursive(std::move(simple_lemma), ns);
Copy link
Contributor

Choose a reason for hiding this comment

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

This will probably not get rid of all the inconsistencies, so I'm not sure about how useful it is. Ideally we should get rid of the introduction of type inconsistencies, but I'm not sure about the best way to do that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I recall having tried removing them completely (which would be done by using type casts), but that did not seem to work very well. It's the long-term solution, and maybe that's what we should actually invest in instead of this hack. Not sure though, I'm certainly curious about the results with TG!

Copy link
Contributor

Choose a reason for hiding this comment

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

Everything is green on our side, so nothing blocking merging it.

simplify(simple_lemma, ns);
}

if(simple_lemma.is_true())
{
Expand Down Expand Up @@ -917,7 +963,7 @@ static optionalt<exprt> get_array(
{
const auto eom = messaget::eom;
const exprt &size = arr.length();
exprt arr_val = simplify_expr(super_get(arr), ns);
exprt arr_val = simplify_expr(adjust_if_recursive(super_get(arr), ns), ns);
exprt size_val = super_get(size);
size_val = simplify_expr(size_val, ns);
const typet char_type = arr.type().subtype();
Expand Down
1 change: 1 addition & 0 deletions src/util/simplify_expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2548,6 +2548,7 @@ bool simplify_exprt::simplify_rec(exprt &expr)

if(!result)
{
POSTCONDITION(tmp.type() == expr.type());
Copy link
Contributor

Choose a reason for hiding this comment

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

We are hitting this invariant - will try to produce a jbmc version of the crash at some point today.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok, that's another bug in the simplifier then or some weird expression being passed in. Some of the bug fixes already in this PR are a consequence of adding this invariant.

Copy link
Contributor

Choose a reason for hiding this comment

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

The problem is we've simplified something that has type pointer_typet(symbol_typet(java.lang.Object)) and end up with pointer_typet(struct_typet(.tag = java.lang.Object).

I've never really be clear whether this transformation (symbol_typet -> ns.follow(symbol_type)) should always be ignored? The bug relating to multi-dimensional arrays seems to arise from the fact the element type of the array is stored on the symbol_typet and is lost if it is ns.follow'd. Will see if happens on JBMC

Copy link
Collaborator Author

@tautschnig tautschnig Apr 25, 2018

Choose a reason for hiding this comment

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

In this PR I've made a few changes to the simplifier to restore the original type when type_before != type_after despite base_type_eq(type_before, type_after, ns) (which is exactly the case for symbol_typet expansion). In my view, the simplifier should not change the type at all and therefore should be careful to restore the symbol type where needed. We could of course, instead, relax the POSTCONDITION to check for semantic equivalence (via base_type_eq) rather than syntactic equivalence, but then this would negatively affect examples like the multi-dimensional array that you mentioned.

So what it would take is tracking down what simplification is being performed and then fix this in the same way it's already being done several times in this PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

I can't reproduce this yet on JBMC as failing example seems to depend on a model for java.lang.Class. It seems like util/type_eq should ignore symbolically equivalent types and could be used here, but it doesn't recurse unfortunately. Not got more time to investigate today but can have a bit more of a prod tomorrow.

Copy link
Contributor

Choose a reason for hiding this comment

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

Found it - tautschnig#7 - will verify fixes other issues. You might have some insight on how to create a regression that demonstrates this on the CBMC side?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

A test should look like this, except it doesn't really do the job right now, because the update to x is not constant-propagated.

#include <assert.h>

struct A
{
  struct A *next;
};

int main()
{
  struct A x, y;
  x.next = &y;
  assert(x.next == &y);
  return 0;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

There appears to be more - will investigate these as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ignore previous, appears to be PEBKAC. TG pointer updated - will post if passed.

Re regression - in our previously broken example, it is simplified to a null pointer, so perhaps assigning x.next = NULL will work? Will quickly give it a go

Copy link
Contributor

Choose a reason for hiding this comment

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

Nope - works before and after using cbmc test.c

expr.swap(tmp);

#ifdef USE_CACHE
Expand Down
3 changes: 1 addition & 2 deletions src/util/simplify_expr_struct.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,7 @@ bool simplify_exprt::simplify_member(exprt &expr)
if(
equivalent_member.has_value() &&
equivalent_member.value().id() != ID_byte_extract_little_endian &&
equivalent_member.value().id() != ID_byte_extract_big_endian &&
equivalent_member.value().type() == expr.type())
equivalent_member.value().id() != ID_byte_extract_big_endian)
{
expr = equivalent_member.value();
simplify_rec(expr);
Expand Down