Skip to content

Commit

Permalink
fix(bool-domain): propagation in assume(not b)
Browse files Browse the repository at this point in the history
The kind of facts that we keep track in m_bool_to_lincsts (resp
m_bool_to_bools) is of the form:

  if b becomes true then constraint C (resp. boolean b') MUST be true

Thus, if b is false then nothing can really be said about C (resp b')
but we were propagating incorrectly that C (resp b') was false.

We added a test that passed incorrectly before (assertion was proven)
and now it doesn't pass anymore (assertion cannot be proven).
  • Loading branch information
caballa committed Sep 15, 2023
1 parent e401a1e commit e43a7a1
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 81 deletions.
199 changes: 118 additions & 81 deletions include/crab/domains/flat_boolean_domain.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,29 +672,6 @@ class flat_boolean_numerical_domain final
}
}
}

template<class BoolToCstEnv>
void propagate_select_bool(BoolToCstEnv &env,
const boolean_value &b1_val, const boolean_value &b2_val,
const variable_t &lhs, const variable_t &cond,
const variable_t &b1, const variable_t &b2) {
// lhs := true false true
if (b2_val.is_false()) {
// if lhs becomes true later then cond and b1 must be true
env.set(lhs, env.at(cond) & env.at(b1));
} else if (b1_val.is_false()) {
// if lhs becomes true later then !cond and b2 must be true
auto cond_csts = env.at(cond);
if (cond_csts.size() == 1) {
auto cst = *(cond_csts.begin());
env.set(lhs, typename BoolToCstEnv::mapped_type(cst.negate()) & env.at(b2));
} else {
// we lost the condition because we cannot negate without
// introducing disjunctions.
env.set(lhs, env.at(b2));
}
}
}

// return true if cst's variables haven't been modified. As
// side-effect, it adds cst into the non-boolean domain if the
Expand Down Expand Up @@ -722,10 +699,20 @@ class flat_boolean_numerical_domain final
}
return unchanged;
}

// Return true if cond evaluates definitely to false
bool eval_false(const variable_t &cond) const{
return (m_product.first().get_bool(cond) == boolean_value::get_false());
}

// Return true if cond evaluates definitely to true
bool eval_true(const variable_t &cond) const {
return (m_product.first().get_bool(cond) == boolean_value::get_true());
}

template<class BoolToCstEnv>
void bwd_reduction_assume_bool(BoolToCstEnv &env,
const variable_t &x, bool is_negated) {
const variable_t &x, bool is_negated) {

if (env.at(x).is_top() || env.at(x).is_bottom()) {
return;
Expand Down Expand Up @@ -753,38 +740,29 @@ class flat_boolean_numerical_domain final
auto cst = *(csts.begin());
if (add_if_unchanged(cst.negate())) {
CRAB_LOG("flat-boolean",
crab::outs() << "\t" << "Applied " << cst << "\n";);
crab::outs() << "\t" << "Applied " << cst.negate() << "\n";);
} else {
CRAB_LOG("flat-boolean",
crab::outs() << "\t" << "Cannot apply " << cst << "\n";);
crab::outs() << "\t" << "Cannot apply " << cst.negate() << "\n";);
}
}
}
}

// Return true if cond evaluates definitely to false
bool eval_false(const variable_t &cond) const{
bool_domain_t inv(m_product.first());
inv.assume_bool(cond, false /*not negated*/);
return inv.is_bottom();
}

// Return true if cond evaluates definitely to true
bool eval_true(const variable_t &cond) const {
bool_domain_t inv(m_product.first());
inv.assume_bool(cond, true /*negated*/);
return inv.is_bottom();
}

/**
* Given lhs:= select(cond, b1, b2)
* if cond is true then
* lhs := b1
* if cond is false then
* lhs := b2
**/
void fwd_reduction_select_bool(const variable_t &lhs, const variable_t &cond,
const variable_t &b1, const variable_t &b2) {
if (eval_true(cond)) {
// lhs:= select(cond, b1, b2) --> lhs := b1
if (eval_true(cond)) {
m_bool_to_lincsts.set(lhs, m_bool_to_lincsts.at(b1));
m_bool_to_refcsts.set(lhs, m_bool_to_refcsts.at(b1));
m_bool_to_bools.set(lhs, m_bool_to_bools.at(b1) & bool_set_t(b1));
} else if (eval_false(cond)) {
// lhs:= select(cond, b1, b2) --> lhs := b2
m_bool_to_lincsts.set(lhs, m_bool_to_lincsts.at(b2));
m_bool_to_refcsts.set(lhs, m_bool_to_refcsts.at(b2));
m_bool_to_bools.set(lhs, m_bool_to_bools.at(b2) & bool_set_t(b2));
Expand All @@ -794,39 +772,78 @@ class flat_boolean_numerical_domain final
m_bool_to_bools -= lhs;
}
}


/**
* Given lhs := ite(cond, b1, b2)
*
* if b1 is false then
* if lhs becomes true then not(cond) and b2 must be true
*
* if b2 is false then
* if lhs becomes true then cond and b1 must be true
**/
template<class BoolToCstEnv>
void propagate_select_bool(BoolToCstEnv &env,
const boolean_value &b1_val, const boolean_value &b2_val,
const variable_t &lhs, const variable_t &cond,
const variable_t &b1, const variable_t &b2) {
// lhs := true false true
if (b2_val.is_false()) {
// if lhs becomes true later then cond and b1 must be true
env.set(lhs, env.at(cond) & env.at(b1));
} else if (b1_val.is_false()) {
// if lhs becomes true later then !cond and b2 must be true
auto cond_csts = env.at(cond);
if (cond_csts.size() == 1) {
auto cst = *(cond_csts.begin());
env.set(lhs, typename BoolToCstEnv::mapped_type(cst.negate()) & env.at(b2));
} else {
// we lost the condition because we cannot negate without
// introducing disjunctions.
env.set(lhs, env.at(b2));
}
}
}

/** End helpers to update subdomains **/

/**
* Reduce from the boolean domain to the non-boolean domain.
**/
void reduce_bool_to_csts(const variable_t &x, bool is_negated) {
// backward reduction for m_bool_to_bools
// if x is true then all Boolean variables in bool_vars must be true.
auto bool_vars = m_bool_to_bools.at(x);
if (!is_negated) {
// x is true
// We know that x is definitely true

// backward reduction for m_bool_to_bools
// if x is true then all Boolean variables in bool_vars must be true.
auto bool_vars = m_bool_to_bools.at(x);
for (auto const& v: bool_vars) {
m_product.first().assume_bool(v, is_negated);
m_bool_to_lincsts.set(x, m_bool_to_lincsts.at(x) & m_bool_to_lincsts.at(v));
m_bool_to_refcsts.set(x, m_bool_to_refcsts.at(x) & m_bool_to_refcsts.at(v));
}

bwd_reduction_assume_bool(m_bool_to_lincsts, x, is_negated);
bwd_reduction_assume_bool(m_bool_to_refcsts, x, is_negated);

} else {
// x is false
if (bool_vars.size() == 1) {
auto v = *(bool_vars.begin());
m_product.first().assume_bool(v, is_negated);
m_bool_to_lincsts.set(x, m_bool_to_lincsts.at(x) & m_bool_to_lincsts.at(v));
m_bool_to_refcsts.set(x, m_bool_to_refcsts.at(x) & m_bool_to_refcsts.at(v));
} else {
// the falsity of x depends on the falsity of any of the
// variables in bool_vars.
}
/**
* The kind of facts that we keep track in m_bool_to_lincsts
* (resp m_bool_to_bools) is of the form "if b becomes true then
* constraint C (resp. boolean b') MUST be true"
*
* Thus, if b is false then nothing can really be said
* about C (resp b') because if b is false then it does not imply
* that C or b' are false.
*
* TODO: Missing propagation.
*
* However, if we know that b' is false and we have inferred the
* fact "if b is true then b' is true" then we can conclude that
* b must be false.
**/
}

bwd_reduction_assume_bool(m_bool_to_lincsts, x, is_negated);
bwd_reduction_assume_bool(m_bool_to_refcsts, x, is_negated);

}


Expand Down Expand Up @@ -1009,15 +1026,32 @@ class flat_boolean_numerical_domain final
m_bool_to_lincsts = m_bool_to_lincsts | other.m_bool_to_lincsts;
m_bool_to_refcsts = m_bool_to_refcsts | other.m_bool_to_refcsts;
m_bool_to_bools = m_bool_to_bools | other.m_bool_to_bools;

m_unchanged_vars = m_unchanged_vars | other.m_unchanged_vars;
CRAB_LOG("flat-boolean",
crab::outs() << "*** After join ***\n"
<< "\tINV=" << m_product << "\n"
<< "\tunchanged vars=" << m_unchanged_vars << "\n"
<< "\tlinear constraints for reduction=" << m_bool_to_lincsts << "\n"
<< "\tref constraints for reduction=" << m_bool_to_refcsts << "\n"
<< "\tBooleans for reduction=" << m_bool_to_bools << "\n";);

}

bool_num_domain_t operator|(const bool_num_domain_t &other) const override {
return bool_num_domain_t(m_product | other.m_product,
m_bool_to_lincsts | other.m_bool_to_lincsts,
m_bool_to_refcsts | other.m_bool_to_refcsts,
bool_num_domain_t res(m_product | other.m_product,
m_bool_to_lincsts | other.m_bool_to_lincsts,
m_bool_to_refcsts | other.m_bool_to_refcsts,
m_bool_to_bools | other.m_bool_to_bools,
m_unchanged_vars | other.m_unchanged_vars);
m_unchanged_vars | other.m_unchanged_vars);
CRAB_LOG("flat-boolean",
crab::outs() << "*** After join ***\n"
<< "\tINV=" << res.m_product << "\n"
<< "\tunchanged vars=" << res.m_unchanged_vars << "\n"
<< "\tlinear constraints for reduction=" << res.m_bool_to_lincsts << "\n"
<< "\tref constraints for reduction=" << res.m_bool_to_refcsts << "\n"
<< "\tBooleans for reduction=" << res.m_bool_to_bools << "\n";);
return res;
}

bool_num_domain_t operator&(const bool_num_domain_t &other) const override {
Expand Down Expand Up @@ -1310,7 +1344,7 @@ class flat_boolean_numerical_domain final
CRAB_LOG("flat-boolean",
crab::outs() << "\tunchanged vars=" << m_unchanged_vars << "\n"
<< "\tlinear constraints for reduction=" << m_bool_to_lincsts << "\n"
<< "\treference constraints for reduction=" << m_bool_to_refcsts
<< "\treference constraints for reduction=" << m_bool_to_refcsts << "\n"
<< "\tbooleans for reduction=" << m_bool_to_bools
<< "\n";);
}
Expand Down Expand Up @@ -1378,13 +1412,12 @@ class flat_boolean_numerical_domain final
<< "\tunchanged vars=" << m_unchanged_vars << "\n"
<< "\tlinear constraints for reduction=" << m_bool_to_lincsts << "\n"
<< "\tref constraints for reduction=" << m_bool_to_refcsts << "\n"
<< "\tBooleans for reduction=" << m_bool_to_bools
<< "\n";);

<< "\tBooleans for reduction=" << m_bool_to_bools << "\n";);

if (is_bottom()) {
return;
}

reduce_bool_to_csts(x, is_negated);

CRAB_LOG("flat-boolean",
Expand All @@ -1393,31 +1426,35 @@ class flat_boolean_numerical_domain final

void select_bool(const variable_t &lhs, const variable_t &cond,
const variable_t &b1, const variable_t &b2) override {



if (!is_bottom()) {
if (b1 == b2) {
assign_bool_var(lhs, b1, false);
} else {
m_product.select_bool(lhs, cond, b1, b2);

fwd_reduction_select_bool(lhs, cond, b1, b2);
auto b1_val = m_product.first().get_bool(b1);
auto b2_val = m_product.first().get_bool(b2);
propagate_select_bool(m_bool_to_lincsts, b1_val, b2_val, lhs, cond, b1, b2);
propagate_select_bool(m_bool_to_refcsts, b1_val, b2_val, lhs, cond, b1, b2);
if (b2_val.is_false()) {
auto val1 = m_product.first().get_bool(b1);
auto val2 = m_product.first().get_bool(b2);
propagate_select_bool(m_bool_to_lincsts, val1, val2, lhs, cond, b1, b2);
propagate_select_bool(m_bool_to_refcsts, val1, val2, lhs, cond, b1, b2);
if (val2.is_false()) {
m_bool_to_bools.set(lhs,
m_bool_to_bools.at(b1) & m_bool_to_bools.at(cond) &
bool_set_t(b1) & bool_set_t(cond));
} else if (b1_val.is_false()) {
} else if (val1.is_false()) {
m_bool_to_bools.set(lhs, m_bool_to_bools.at(b2) & bool_set_t(b2));
// TODO: we don't handle negative booleans in
// m_bool_to_bools so we don't add not(cond).
// m_bool_to_bools so we don't add not(cond)
}
}
}

CRAB_LOG("flat-boolean",
crab::outs() << lhs << ":= select(" << cond << "," << b1 << "," << b2 << ")\n"
<< "\tINV=" << m_product << "\n"
<< "\tunchanged vars=" << m_unchanged_vars << "\n"
<< "\tlinear constraints for reduction=" << m_bool_to_lincsts << "\n"
<< "\tref constraints for reduction=" << m_bool_to_refcsts << "\n"
<< "\tBooleans for reduction=" << m_bool_to_bools << "\n";);
}

void backward_assign_bool_cst(const variable_t &lhs,
Expand Down
88 changes: 88 additions & 0 deletions tests/domains/flat_bool_domain8.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include "../common.hpp"
#include "../program_options.hpp"

using namespace std;
using namespace crab::cfg;
using namespace crab::cfg_impl;
using namespace crab::domain_impl;

z_cfg_t *prog1(variable_factory_t &vfac) {
/*
entry:
havoc(x)
havoc(y)
b1 = x <= y
assume(b1)
assume(y >= 0)
goto bb1, bb2
bb1:
assume x >= 0
b2 = item(y <= -1, true, b1)
goto exit
bb2
assume x <= -1
b2 = item(y <= -1, b1, false)
goto exit
exit:
assert(b2) // EXPECTED FALSE (e.g., x=-1, y=1)
*/

// entry and exit block
auto cfg = new z_cfg_t("entry", "exit");
// adding blocks
z_basic_block_t &entry = cfg->insert("entry");
z_basic_block_t &exit = cfg->insert("exit");
z_basic_block_t &bb1 = cfg->insert("bb1");
z_basic_block_t &bb2 = cfg->insert("bb2");
// adding control flow
entry >> bb1;
entry >> bb2;
bb1 >> exit;
bb2 >> exit;

z_var b1(vfac["b1"], crab::BOOL_TYPE, 1);
z_var b2(vfac["b2"], crab::BOOL_TYPE, 1);
z_var b3(vfac["b3"], crab::BOOL_TYPE, 1);
z_var bTrue(vfac["bTrue"], crab::BOOL_TYPE, 1);
z_var bFalse(vfac["bFalse"], crab::BOOL_TYPE, 1);
z_var x(vfac["x"], crab::INT_TYPE, 32);
z_var y(vfac["y"], crab::INT_TYPE, 32);

// adding statements
entry.havoc(x);
entry.havoc(y);
entry.bool_assign(b1, z_lin_exp_t(x) <= z_lin_exp_t(y));
entry.bool_assume(b1);
entry.assume(z_lin_exp_t(y) >= 0);
entry.bool_assign(b3, z_lin_exp_t(y) <= -1);
entry.bool_assign(bTrue, z_lin_cst_t::get_true());
entry.bool_assign(bFalse, z_lin_cst_t::get_false());
bb1.assume(z_lin_exp_t(x) >= 0);
bb1.bool_select(b2, b3, bTrue, b1);
bb2.assume(z_lin_exp_t(x) <= -1);
bb2.bool_select(b2, b3, b1, bFalse);
// The assertion is NOT provable
exit.bool_assert(b2);

return cfg;
}


int main(int argc, char **argv) {
bool stats_enabled = false;
if (!crab_tests::parse_user_options(argc, argv, stats_enabled)) {
return 0;
}

variable_factory_t vfac;
{
z_cfg_t *cfg = prog1(vfac);
//crab::outs() << *cfg << "\n";
z_bool_num_domain_t init;
run_and_check(cfg, cfg->entry(), init, false, 1, 2, 20, stats_enabled);
delete cfg;
}

return 0;

}
Loading

0 comments on commit e43a7a1

Please sign in to comment.