Skip to content

Fixed: Bug in String.indexOf() #612 #1071

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
7 changes: 7 additions & 0 deletions regression/strings-smoke-tests/java_index_of2/test.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CORE
test_index_of2.class
--refine-strings
^EXIT=10$
^SIGNAL=0$
^VERIFICATION FAILED$
--
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public class test_index_of2
{
public static void main(String param)
{
String s = "abcdefg";
int i = s.indexOf(param);
assert(i != 1);
}
}
217 changes: 166 additions & 51 deletions src/solvers/refinement/string_refinement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -923,13 +923,73 @@ void string_refinementt::substitute_array_access(exprt &expr) const
}
}

/// negates the constraint and add it to the solver. the intended usage is to
/// find an assignement of the universal variable that would violate the axiom,
/// to avoid false positives the symbols other than the universal variable
/// should have been replaced by there valuation in the current model
/// \par parameters: a string constraint and a solver for non string expressions
void string_refinementt::add_negation_of_constraint_to_solver(
const string_constraintt &axiom, supert &solver)
/// Negates the constraint to be fed to a solver. The intended usage is to find
/// an assignement of the universal variable that would violate the axiom. To
/// avoid false positives, the symbols other than the universal variable should
/// have been replaced by their valuation in the current model.
/// \pre Symbols other than the universal variable should have been replaced by
/// their valuation in the current model.
/// \param axiom: the not_contains constraint to add the negation of
/// \param val: the existential witness for the axiom
/// \param univ_var: the universal variable for the negation of the axiom
/// \return: the negation of the axiom under the current evaluation
exprt string_refinementt::negation_of_not_contains_constraint(
const string_not_contains_constraintt &axiom,
const exprt &val,
const symbol_exprt &univ_var)
{
exprt lbu=axiom.univ_lower_bound();
exprt ubu=axiom.univ_upper_bound();
if(lbu.id()==ID_constant && ubu.id()==ID_constant)
{
mp_integer lb_int, ub_int;
to_integer(to_constant_expr(lbu), lb_int);
to_integer(to_constant_expr(ubu), ub_int);
if(ub_int<=lb_int)
Copy link
Member

Choose a reason for hiding this comment

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

Not sure whether it shouldn't be ub_int<lb_int?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The constraints here are of the form forall x in [lb, ub[ (open on upper), so <= is fine I think.

Copy link
Member

Choose a reason for hiding this comment

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

Alright. I assume this is documented somewhere ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep :)

Copy link
Contributor

Choose a reason for hiding this comment

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

There are some comments hidden in string_constraint.h but there are not yet in doxygen style. I'll sort this out later on.

{
debug() << "empty constraint with current model" << eom;
return false_exprt();
}
}

exprt lbe=axiom.exists_lower_bound();
exprt ube=axiom.exists_upper_bound();

if(axiom.premise()==false_exprt())
{
debug() << "(string_refinement::check_axioms) adding false" << eom;
return false_exprt();
}

// Witness is the Skolem function for the existential, which we evaluate at
// univ_var.
and_exprt univ_bounds(
binary_relation_exprt(lbu, ID_le, univ_var),
binary_relation_exprt(ubu, ID_gt, univ_var));
and_exprt exists_bounds(
binary_relation_exprt(lbe, ID_le, val),
binary_relation_exprt(ube, ID_gt, val));
equal_exprt equal_chars(
axiom.s0()[plus_exprt(univ_var, val)],
axiom.s1()[val]);
and_exprt negaxiom(univ_bounds, axiom.premise(), exists_bounds, equal_chars);

debug() << "(sr::check_axioms) negated not_contains axiom: "
<< from_expr(ns, "", negaxiom) << eom;
substitute_array_access(negaxiom);
return negaxiom;
}

/// Negates the constraint to be fed to a solver. The intended usage is to find
/// an assignement of the universal variable that would violate the axiom. To
/// avoid false positives, the symbols other than the universal variable should
/// have been replaced by their valuation in the current model.
/// \pre Symbols other than the universal variable should have been replaced by
/// their valuation in the current model.
/// \param axiom: the not_contains constraint to add the negation of
/// \return: the negation of the axiom under the current evaluation
exprt string_refinementt::negation_of_constraint(
const string_constraintt &axiom)
{
exprt lb=axiom.lower_bound();
exprt ub=axiom.upper_bound();
Expand All @@ -941,24 +1001,23 @@ void string_refinementt::add_negation_of_constraint_to_solver(
if(ub_int<=lb_int)
{
debug() << "empty constraint with current model" << eom;
solver << false_exprt();
return;
return false_exprt();
}
}

if(axiom.premise()==false_exprt())
{
debug() << "(string_refinement::check_axioms) adding false" << eom;
solver << false_exprt();
return;
debug() << "(string_refinement::check_axioms) adding false" << eom;
return false_exprt();
}

and_exprt premise(axiom.premise(), axiom.univ_within_bounds());
and_exprt negaxiom(premise, not_exprt(axiom.body()));

debug() << "(sr::check_axioms) negated axiom: " << from_expr(ns, "", negaxiom) << eom;
debug() << "(sr::check_axioms) negated axiom: "
<< from_expr(ns, "", negaxiom) << eom;
substitute_array_access(negaxiom);
solver << negaxiom;
return negaxiom;
}

/// return true if the current model satisfies all the axioms
Expand All @@ -979,63 +1038,89 @@ bool string_refinementt::check_axioms()
for(size_t i=0; i<universal_axioms.size(); i++)
{
const string_constraintt &axiom=universal_axioms[i];
symbol_exprt univ_var=axiom.univ_var();
exprt bound_inf=axiom.lower_bound();
exprt bound_sup=axiom.upper_bound();
exprt prem=axiom.premise();
exprt body=axiom.body();
const symbol_exprt univ_var=axiom.univ_var();
const exprt bound_inf=axiom.lower_bound();
const exprt bound_sup=axiom.upper_bound();
const exprt prem=axiom.premise();
const exprt body=axiom.body();

string_constraintt axiom_in_model(
const string_constraintt axiom_in_model(
univ_var, get(bound_inf), get(bound_sup), get(prem), get(body));

satcheck_no_simplifiert sat_check;
supert solver(ns, sat_check);
solver.set_ui(ui);
add_negation_of_constraint_to_solver(axiom_in_model, solver);
const exprt negaxiom=negation_of_constraint(axiom_in_model);
exprt witness;

switch(solver())
bool is_sat=is_axiom_sat(negaxiom, univ_var, witness);

if(is_sat)
{
case decision_proceduret::resultt::D_SATISFIABLE:
{
exprt val=solver.get(axiom_in_model.univ_var());
debug() << "string constraint can be violated for "
<< axiom_in_model.univ_var().get_identifier()
<< " = " << from_expr(ns, "", val) << eom;
violated[i]=val;
}
break;
case decision_proceduret::resultt::D_UNSATISFIABLE:
break;
default:
throw "failure in checking axiom";
debug() << "string constraint can be violated for "
<< univ_var.get_identifier()
<< " = " << from_expr(ns, "", witness) << eom;
violated[i]=witness;
}
}

// tells whether one of the not_contains constraint can be violated
bool violated_not_contains=false;
// Maps from indexes of violated not_contains axiom to a witness of violation
std::map<std::size_t, exprt> violated_not_contains;

debug() << "there are " << not_contains_axioms.size()
<< " not_contains axioms" << eom;

for(auto nc_axiom : not_contains_axioms)
for(size_t i=0; i<not_contains_axioms.size(); i++)
Copy link
Member

Choose a reason for hiding this comment

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

Where else is the index i used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's used slightly below to keep track of how the constraint is violated by storing it in violated_not_contains for debugging purposes and there is code that shows it will be used in the future for counter examples.

{
typet index_type=nc_axiom.s0().length().type();
exprt zero=from_integer(0, index_type);
exprt witness=generator.get_witness_of(nc_axiom, zero);
exprt val=get(witness);
violated_not_contains=true;
const string_not_contains_constraintt &nc_axiom=not_contains_axioms[i];
const exprt univ_bound_inf=nc_axiom.univ_lower_bound();
const exprt univ_bound_sup=nc_axiom.univ_upper_bound();
const exprt prem=nc_axiom.premise();
const exprt exists_bound_inf=nc_axiom.exists_lower_bound();
const exprt exists_bound_sup=nc_axiom.exists_upper_bound();
const string_exprt s0=nc_axiom.s0();
const string_exprt s1=nc_axiom.s1();

symbol_exprt univ_var=generator.fresh_univ_index(
"not_contains_univ_var", nc_axiom.s0().length().type());
exprt wit=generator.get_witness_of(nc_axiom, univ_var);
exprt val=get(wit);
const string_not_contains_constraintt nc_axiom_in_model(
get(univ_bound_inf),
get(univ_bound_sup),
get(prem),
get(exists_bound_inf),
get(exists_bound_sup),
to_string_expr(get(s0)),
to_string_expr(get(s1)));

const exprt negaxiom=negation_of_not_contains_constraint(
nc_axiom_in_model, val, univ_var);
exprt witness;

bool is_sat=is_axiom_sat(negaxiom, univ_var, witness);

if(is_sat)
{
debug() << "string constraint can be violated for "
<< univ_var.get_identifier()
<< " = " << from_expr(ns, "", witness) << eom;
violated_not_contains[i]=witness;
}
}

if(violated.empty() && !violated_not_contains)
if(violated.empty() && violated_not_contains.empty())
Copy link
Contributor

Choose a reason for hiding this comment

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

My understanding is that these maps are either both empty or both non-empty. If that is true, then one of the conditions should be written as an assertion. If not, disregard my comment.

Copy link
Contributor

Choose a reason for hiding this comment

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

no they are really independent, one could be empty and not the other (one talks about string_constraintts and the other about string_not_contains_constraintts)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They keep track of which axioms are violated, and because there is a difference between the setup and methods to do this, they are treated separately, so there is not connection.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh right, I see! All good!

{
debug() << "no violated property" << eom;
return true;
}
else
{
debug() << violated.size() << " string axioms can be violated" << eom;
debug() << violated.size()
<< " universal string axioms can be violated" << eom;
debug() << violated_not_contains.size()
<< " not_contains string axioms can be violated" << eom;

if(use_counter_example)
{
// TODO: add counter examples for not_contains?

// Checking if the current solution satisfies the constraints
for(const auto &v : violated)
{
Expand Down Expand Up @@ -1076,8 +1161,8 @@ std::map<exprt, int> string_refinementt::map_representation_of_sum(
to_process.pop_back();
if(cur.id()==ID_plus)
{
to_process.push_back(std::make_pair(cur.op1(), positive));
to_process.push_back(std::make_pair(cur.op0(), positive));
for(const auto &op : cur.operands())
to_process.push_back(std::make_pair(op, positive));
}
else if(cur.id()==ID_minus)
{
Expand Down Expand Up @@ -1533,3 +1618,33 @@ exprt string_refinementt::get(const exprt &expr) const

return substitute_array_lists(ecopy);
}

/// Creates a solver with `axiom` as the only formula added and runs it. If it
/// is SAT, then true is returned and the given evalutation of `var` is stored
/// in `witness`. If UNSAT, then what witness is is undefined.
/// \param [in] axiom: the axiom to be checked
/// \param [in] var: the variable whose evaluation will be stored in witness
/// \param [out] witness: the witness of the satisfying assignment if one
/// exists. If UNSAT, then behaviour is undefined.
/// \return: true if axiom is SAT, false if UNSAT
bool string_refinementt::is_axiom_sat(
Copy link
Contributor

Choose a reason for hiding this comment

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

Bad indent

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wait, what's bad about it? It's supposed to be 2 spaces from the line above right? That's how I've seen other comments (also others in this file).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh wait, you mean the argument list. Yep, I'll change that.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh yes sorry, I might have highlighted the wrong line.

const exprt &axiom, const symbol_exprt& var, exprt &witness)
{
satcheck_no_simplifiert sat_check;
supert solver(ns, sat_check);
solver.set_ui(ui);
solver << axiom;

switch(solver())
{
case decision_proceduret::resultt::D_SATISFIABLE:
{
witness=solver.get(var);
return true;
}
case decision_proceduret::resultt::D_UNSATISFIABLE:
return false;
default:
throw "failure in checking axiom";
Copy link
Contributor

Choose a reason for hiding this comment

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

I know you've just copied this code, but throwing a string isn't good, and if it's easy for you to fix it then you should

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there some place I can look for how to do this properly?

}
}
9 changes: 7 additions & 2 deletions src/solvers/refinement/string_refinement.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,15 @@ class string_refinementt: public bv_refinementt
void set_to(const exprt &expr, bool value) override;

void add_instantiations();
void add_negation_of_constraint_to_solver(
const string_constraintt &axiom, supert &solver);
exprt negation_of_not_contains_constraint(
const string_not_contains_constraintt &axiom,
const exprt &val,
const symbol_exprt &univ_var);
exprt negation_of_constraint(const string_constraintt &axiom);
void fill_model();
bool check_axioms();
bool is_axiom_sat(
const exprt &axiom, const symbol_exprt& var, exprt &witness);

void set_char_array_equality(const exprt &lhs, const exprt &rhs);
void update_index_set(const exprt &formula);
Expand Down