Skip to content

Commit 05b6978

Browse files
Define a Boolean variable per quantifier when using Z3
The previous commit makes Z3 return Boolean values for variables that are defined via a quantified expression. Quantifiers may also be part of other definitions, such as an assignment to a non-Boolean symbol (in `int x = __CPROVER_forall { ... };`, x is defined via an if-then-else over the quantifier), or of Boolean definitions that use define-fun. Z3's solve_eqs preprocessor eliminates these symbols, too, and then returns quantified expressions from get-value. With Z3, now replace each quantifier that is not nested within another quantifier by a fresh Boolean variable, defined using the encoding of the previous commit, so that no other definition contains a quantifier. Quantifiers-assignment, Quantifiers-if, and Quantifiers-not now pass with Z3 and are no longer tagged broken-z3-smt-backend. Their runtime with Z3 4.8.12 is 11.5s, 0.14s, and 0.14s, respectively (previously, these stopped with an error after 5.3s, 0.09s, and 0.09s). Quantifiers-not-exists/fixed.desc remains broken with Z3: it now reports an error after 83s, previously after 0.09s. Fixes: #8679 Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
1 parent 2fccdf3 commit 05b6978

5 files changed

Lines changed: 51 additions & 3 deletions

File tree

‎regression/cbmc/Quantifiers-assignment/test.desc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE broken-z3-smt-backend no-new-smt
1+
CORE no-new-smt
22
main.c
33

44
^\*\* Results:$

‎regression/cbmc/Quantifiers-if/test.desc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE broken-z3-smt-backend no-new-smt
1+
CORE no-new-smt
22
main.c
33

44
^\*\* Results:$

‎regression/cbmc/Quantifiers-not/test.desc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE broken-z3-smt-backend no-new-smt
1+
CORE no-new-smt
22
main.c
33

44
^\*\* Results:$

‎src/solvers/smt2/smt2_conv.cpp‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5725,10 +5725,29 @@ exprt smt2_convt::prepare_for_convert_expr(const exprt &expr)
57255725
!has_byte_operator(lowered_expr),
57265726
"lower_byte_operators should remove all byte operators");
57275727

5728+
// With Z3, quantifiers that are not nested within another quantifier are
5729+
// replaced by a Boolean variable (see below), unless the expression itself
5730+
// is a quantifier, which is the case when we are asked to define such a
5731+
// variable.
5732+
const bool lift_quantifiers = solver == solvert::Z3 &&
5733+
lowered_expr.id() != ID_forall &&
5734+
lowered_expr.id() != ID_exists;
5735+
57285736
// Perform rewrites that may introduce new symbols
57295737
for(auto it = lowered_expr.depth_begin(), itend = lowered_expr.depth_end();
57305738
it != itend;) // no ++it
57315739
{
5740+
if(lift_quantifiers && (it->id() == ID_forall || it->id() == ID_exists))
5741+
{
5742+
// Z3 may report a quantified formula rather than a value for symbols
5743+
// whose definition contains a quantifier, see convert(const exprt &).
5744+
// Defining a Boolean variable per quantifier, using the encoding that
5745+
// works around this, keeps all other definitions quantifier-free.
5746+
literalt l = convert(*it);
5747+
it.mutate() = literal_exprt{l};
5748+
it.next_sibling_or_parent();
5749+
continue;
5750+
}
57325751
if(
57335752
auto prophecy_r_or_w_ok =
57345753
expr_try_dynamic_cast<prophecy_r_or_w_ok_exprt>(*it))

‎unit/solvers/smt2/smt2_conv.cpp‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,3 +934,32 @@ TEST_CASE("smt2_convt quantifier definition encoding", "[core][solvers][smt2]")
934934
}
935935
}
936936
}
937+
938+
TEST_CASE(
939+
"smt2_convt quantifier nested in another expression",
940+
"[core][solvers][smt2]")
941+
{
942+
const unsignedbv_typet bv8{8};
943+
const symbol_exprt i{"i", bv8};
944+
const exists_exprt quantified{i, equal_exprt{i, from_integer(0, bv8)}};
945+
const std::string quant = "(exists ((i (_ BitVec 8))) (= i (_ bv0 8)))";
946+
947+
GIVEN("a Boolean expression containing a quantifier and the Z3 solver")
948+
{
949+
const std::string out =
950+
convert_handle(not_exprt{quantified}, smt2_convt::solvert::Z3);
951+
INFO("SMT2 output:\n" << out);
952+
953+
THEN("the quantifier is defined by a Boolean variable of its own")
954+
{
955+
// Only the definition of B0 contains the quantifier, which avoids Z3
956+
// returning quantified expressions as values of other symbols.
957+
const std::string assertion =
958+
"(assert (let ((?def " + quant + ")) (and (=> B0 ?def) (=> ?def B0))))";
959+
REQUIRE(out.find(assertion) != std::string::npos);
960+
REQUIRE(out.find(quant) == out.rfind(quant));
961+
REQUIRE(
962+
out.find("(define-fun B1 () Bool (not B0))") != std::string::npos);
963+
}
964+
}
965+
}

0 commit comments

Comments
 (0)