Skip to content

Commit

Permalink
Quantifier instantiation via simplistic E-matching
Browse files Browse the repository at this point in the history
Use E-matching on index expressions to instantiate quantifiers when
eager instantiation (aka enumerative instantiation) cannot be applied.
  • Loading branch information
tautschnig committed Mar 5, 2024
1 parent 6cad8ca commit 69f8720
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 19 deletions.
7 changes: 1 addition & 6 deletions doc/cprover-manual/cbmc-assertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,7 @@ int foo(int a, int b) {
}
```
A future release of CPROVER will support using these pre and
postconditions to create a function contract, which can be used for
modular verification.
Future CPROVER releases will support explicit quantifiers with a syntax
CPROVER supports explicit quantifiers with a syntax
that resembles Spec\#:
```C
Expand Down
9 changes: 3 additions & 6 deletions regression/cbmc/Quantifiers-type/test.desc
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
KNOWNBUG broken-smt-backend
CORE broken-smt-backend no-new-smt
main.c

^\*\* Results:$
^\[main.assertion.1\] line 12 assertion tmp_if_expr(\$\d+)?: FAILURE$
^\*\* 1 of 1 failed
^\[main.assertion.1\] line 10 assertion a\[(\(signed (long )*int\))?0\] == 10 && a\[(\(signed (long )*int\))?1\] == 10: FAILURE$
^\*\* 1 of \d+ failed
^VERIFICATION FAILED$
^EXIT=10$
^SIGNAL=0$
--
^warning: ignoring
--
This produces the expected verification result, but actually ignores some
quantifiers.
96 changes: 96 additions & 0 deletions regression/cbmc/Quantifiers-unbounded-array-overflow/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#include <assert.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>

size_t nondet_size_t();

// The model has an overflow, falsified instantly with SAT,
// takes forever with z3 because of the quantifiers
int main()
{
size_t size = nondet_size_t();
__CPROVER_assume(0 < size);
// we remove this assumption, which in turn allows to cause an integer
// overflow in the loop body when computing i*2
// __CPROVER_assume(size < INT32_MAX / 2);
size_t nof_bytes = size * sizeof(int);
int *arr = malloc(nof_bytes);
__CPROVER_assume(arr);
__CPROVER_array_set(arr, 0);

// None of this should overflow because initially arr[j] == 0 for all j

// the original loop
// size_t i = 0;
// while (i < size)
// __CPROVER_loop_invariant(i <= size)
// __CPROVER_loop_invariant(__CPROVER_forall {size_t j; !(j < i) || (arr[j] == j * 2) })
// __CPROVER_loop_invariant(__CPROVER_forall {size_t j; !(i <= j && j < size) || (arr[j] == 0) })
// {
// arr[i] = arr[i] + 2 * i;
// i += 1;
// }

size_t i = 0;

// check base case
assert(i <= size);
assert(__CPROVER_forall {
size_t j;
!(j < i) || (arr[j] == j * 2)
});
assert(__CPROVER_forall {
size_t j;
!(i <= j && j < size) || (arr[j] == 0)
});

// jump to a nondet state
i = nondet_size_t();
__CPROVER_havoc_object(arr);

// assume loop invariant
__CPROVER_assume(i <= size);
__CPROVER_assume(i <= size);
__CPROVER_assume(__CPROVER_forall {
size_t j;
!(j < i) || (arr[j] == j * 2)
});
__CPROVER_assume(__CPROVER_forall {
size_t j;
!(i <= j && j < size) || (arr[j] == 0)
});

size_t old_i = i;
if(i < size)
{
arr[i] = arr[i] + i * 2;
i += 1;

// check loop invariant post loop step
assert(i <= size);
// fails with SAT backend but passes with --smt2
assert(__CPROVER_forall {
size_t j;
!(j < i) || (arr[j] == j * 2)
});
// fails with SAT backend but passes with --smt2
assert(__CPROVER_forall {
size_t j;
!(i <= j && j < size) || (arr[j] == 0)
});
__CPROVER_assume(0); // stop progress
}

// check that the loop invariant holds post loop
// fails with SAT backend but passes with --smt2
assert(__CPROVER_forall {
size_t j;
!(j < i) || (arr[j] == j * 2)
});

assert(__CPROVER_forall {
size_t j;
!(i <= j && j < size) || (arr[j] == 0)
});
}
12 changes: 12 additions & 0 deletions regression/cbmc/Quantifiers-unbounded-array-overflow/test.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CORE thorough-z3-smt-backend no-new-smt
main.c
--no-standard-checks
^VERIFICATION FAILED$
^EXIT=10$
^SIGNAL=0$
--
^warning: ignoring
--
Uses --no-standard-checks to contain verification time with MiniSat; with
CaDiCaL this completes in under 5 seconds either way. Takes an unknown amount of
time (has never been run to completition) when using Z3.
41 changes: 41 additions & 0 deletions regression/cbmc/Quantifiers-unbounded-array/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <assert.h>
#include <limits.h>
#include <stdlib.h>

size_t nondet_size_t();

int main()
{
size_t size = nondet_size_t();
__CPROVER_assume(0 < size);

// avoids overflows in the loop body on i * 2
__CPROVER_assume(size < INT_MAX / 2);
size_t nof_bytes = size * sizeof(int);
int *arr = malloc(nof_bytes);
__CPROVER_assume(arr);
__CPROVER_array_set(arr, 0);

// jump to a nondet state
size_t i = nondet_size_t();
__CPROVER_assume(i < size);
__CPROVER_havoc_object(arr);

// assume loop invariant
__CPROVER_assume(__CPROVER_forall {
size_t j;
!(j < i) || (arr[j] == j * 2)
});
__CPROVER_assume(__CPROVER_forall {
size_t j;
!(i <= j && j < size) || (arr[j] == 0)
});

arr[i] = arr[i] + i * 2;
i += 1;

assert(__CPROVER_forall {
size_t j;
!(i <= j && j < size) || (arr[j] == 0)
});
}
8 changes: 8 additions & 0 deletions regression/cbmc/Quantifiers-unbounded-array/test.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CORE broken-cprover-smt-backend no-new-smt
main.c

^EXIT=0$
^SIGNAL=0$
^VERIFICATION SUCCESSFUL$
--
^warning: ignoring
115 changes: 108 additions & 7 deletions src/solvers/flattening/boolbv_quantifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ Author: Daniel Kroening, [email protected]
\*******************************************************************/

#include "boolbv.h"

#include <util/arith_tools.h>
#include <util/expr_util.h>
#include <util/invariant.h>
#include <util/simplify_expr.h>
#include <util/ssa_expr.h>

#include "boolbv.h"

/// A method to detect equivalence between experts that can contain typecast
static bool expr_eq(const exprt &expr1, const exprt &expr2)
Expand Down Expand Up @@ -285,10 +286,110 @@ literalt boolbvt::convert_quantifier(const quantifier_exprt &src)

void boolbvt::finish_eager_conversion_quantifiers()
{
if(quantifier_list.empty())
return;

// we do not yet have any elaborate post-processing
for(const auto &q : quantifier_list)
conversion_failed(q.expr);
{
const auto &q_expr = to_quantifier_expr(q.expr);

// we don't yet do nested quantifiers
if(can_cast_expr<quantifier_exprt>(q_expr.where()))
{
conversion_failed(q.expr);
continue;

Check warning on line 297 in src/solvers/flattening/boolbv_quantifier.cpp

View check run for this annotation

Codecov / codecov/patch

src/solvers/flattening/boolbv_quantifier.cpp#L296-L297

Added lines #L296 - L297 were not covered by tests
}

// we don't yet handle multiple bound variables
if(q_expr.variables().size() > 1)
{
conversion_failed(q.expr);
continue;

Check warning on line 304 in src/solvers/flattening/boolbv_quantifier.cpp

View check run for this annotation

Codecov / codecov/patch

src/solvers/flattening/boolbv_quantifier.cpp#L303-L304

Added lines #L303 - L304 were not covered by tests
}

// find the context in which any of the bound variables are used
std::unordered_set<irep_idt, irep_id_hash> bound_variables;
for(const auto &v : q_expr.variables())
bound_variables.insert(v.get_identifier());

bool required_context = false;
std::unordered_set<index_exprt, irep_hash> index_contexts;
auto context_finder =
[&bound_variables, &required_context, &index_contexts](const exprt &e) {
if(auto symbol_expr = expr_try_dynamic_cast<symbol_exprt>(e))
{
required_context |=
bound_variables.find(symbol_expr->get_identifier()) !=
bound_variables.end();
}
else if(required_context)
{
if(auto index_expr = expr_try_dynamic_cast<index_exprt>(e))
{
index_contexts.insert(*index_expr);
required_context = false;
}
}
};
q_expr.where().visit_post(context_finder);
// make sure we found some context for instantiation
if(index_contexts.empty())
{
conversion_failed(q.expr);
continue;

Check warning on line 336 in src/solvers/flattening/boolbv_quantifier.cpp

View check run for this annotation

Codecov / codecov/patch

src/solvers/flattening/boolbv_quantifier.cpp#L335-L336

Added lines #L335 - L336 were not covered by tests
}

// match the contexts against expressions that we have cached
std::unordered_set<exprt, irep_hash> instantiation_candidates;
for(const auto &cache_entry : bv_cache)
{
// consider re-organizing the cache to use expression ids at the top level
if(
auto index_expr = expr_try_dynamic_cast<index_exprt>(cache_entry.first))
{
for(const auto &index_context : index_contexts)
{
if(
auto ssa_context =
expr_try_dynamic_cast<ssa_exprt>(index_context.array()))
{
if(
auto ssa_array =
expr_try_dynamic_cast<ssa_exprt>(index_expr->array()))
{
if(
ssa_context->get_l1_object_identifier() ==
ssa_array->get_l1_object_identifier())
{
instantiation_candidates.insert(index_expr->index());
break;
}
}
}
else if(index_expr->array() == index_context.array())
{
instantiation_candidates.insert(index_expr->index());
break;
}
}
}
}

if(instantiation_candidates.empty())
{
conversion_failed(q.expr);
continue;

Check warning on line 378 in src/solvers/flattening/boolbv_quantifier.cpp

View check run for this annotation

Codecov / codecov/patch

src/solvers/flattening/boolbv_quantifier.cpp#L377-L378

Added lines #L377 - L378 were not covered by tests
}

exprt::operandst instantiations;
instantiations.reserve(instantiation_candidates.size());
for(const auto &e : instantiation_candidates)
{
exprt::operandst values{
{typecast_exprt::conditional_cast(e, q_expr.symbol().type())}};
instantiations.push_back(q_expr.instantiate(values));
}
exprt result_expr = q_expr.id() == ID_exists ? disjunction(instantiations)
: conjunction(instantiations);
literalt result_lit = convert(result_expr);

prop.l_set_to_true(prop.lequal(q.l, result_lit));
}
}

0 comments on commit 69f8720

Please sign in to comment.