Skip to content

Commit d821caa

Browse files
committed
Quantifier instantiation via simplistic E-matching
Use E-matching on index expressions to instantiate quantifiers when eager instantiation (aka enumerative instantiation) cannot be applied.
1 parent 5624d64 commit d821caa

File tree

9 files changed

+305
-19
lines changed

9 files changed

+305
-19
lines changed

Diff for: doc/cprover-manual/cbmc-assertions.md

+1-6
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,7 @@ int foo(int a, int b) {
8282
}
8383
```
8484
85-
A future release of CPROVER will support using these pre and
86-
postconditions to create a function contract, which can be used for
87-
modular verification.
88-
89-
90-
Future CPROVER releases will support explicit quantifiers with a syntax
85+
CPROVER supports explicit quantifiers with a syntax
9186
that resembles Spec\#:
9287
9388
```C
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
int main()
2+
{
3+
int a[2][2];
4+
5+
__CPROVER_assume(__CPROVER_forall {
6+
unsigned i;
7+
__CPROVER_forall
8+
{
9+
unsigned j;
10+
a[i % 2][j % 2] == (i % 2) + (j % 2)
11+
}
12+
});
13+
14+
assert(a[0][0] == 0);
15+
assert(a[0][1] == 1);
16+
assert(a[1][0] == 1);
17+
assert(a[1][1] == 2);
18+
19+
return 0;
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE broken-cprover-smt-backend no-new-smt
2+
no-upper-bound.c
3+
--max-field-sensitivity-array-size 0
4+
^VERIFICATION SUCCESSFUL$
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
^warning: ignoring
9+
--
10+
E-matching does not yet handle array expressions (which field sensitivity
11+
produces for small fixed-size arrays), so we disable field sensitivity.

Diff for: regression/cbmc/Quantifiers-type/test.desc

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
KNOWNBUG broken-smt-backend
1+
CORE broken-smt-backend no-new-smt
22
main.c
33

44
^\*\* Results:$
5-
^\[main.assertion.1\] line 12 assertion tmp_if_expr(\$\d+)?: FAILURE$
6-
^\*\* 1 of 1 failed
5+
^\[main.assertion.1\] line 10 assertion a\[(\(signed (long )*int\))?0\] == 10 && a\[(\(signed (long )*int\))?1\] == 10: FAILURE$
6+
^\*\* 1 of \d+ failed
77
^VERIFICATION FAILED$
88
^EXIT=10$
99
^SIGNAL=0$
1010
--
1111
^warning: ignoring
12-
--
13-
This produces the expected verification result, but actually ignores some
14-
quantifiers.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#include <assert.h>
2+
#include <limits.h>
3+
#include <stdlib.h>
4+
#include <string.h>
5+
6+
size_t nondet_size_t();
7+
8+
// The model has an overflow, falsified instantly with SAT,
9+
// takes forever with z3 because of the quantifiers
10+
int main()
11+
{
12+
size_t size = nondet_size_t();
13+
__CPROVER_assume(0 < size);
14+
__CPROVER_assume(size <= __CPROVER_max_malloc_size / sizeof(int));
15+
// we remove this assumption, which in turn allows to cause an integer
16+
// overflow in the loop body when computing i*2
17+
// __CPROVER_assume(size < INT_MAX / 2);
18+
size_t nof_bytes = size * sizeof(int);
19+
int *arr = malloc(nof_bytes);
20+
__CPROVER_assume(arr);
21+
__CPROVER_array_set(arr, 0);
22+
23+
// None of this should overflow because initially arr[j] == 0 for all j
24+
25+
// the original loop
26+
// size_t i = 0;
27+
// while (i < size)
28+
// __CPROVER_loop_invariant(i <= size)
29+
// __CPROVER_loop_invariant(__CPROVER_forall {size_t j; !(j < i) || (arr[j] == j * 2) })
30+
// __CPROVER_loop_invariant(__CPROVER_forall {size_t j; !(i <= j && j < size) || (arr[j] == 0) })
31+
// {
32+
// arr[i] = arr[i] + 2 * i;
33+
// i += 1;
34+
// }
35+
36+
size_t i = 0;
37+
38+
// check base case
39+
assert(i <= size);
40+
assert(__CPROVER_forall {
41+
size_t j;
42+
!(j < i) || (arr[j] == j * 2)
43+
});
44+
assert(__CPROVER_forall {
45+
size_t j;
46+
!(i <= j && j < size) || (arr[j] == 0)
47+
});
48+
49+
// jump to a nondet state
50+
i = nondet_size_t();
51+
__CPROVER_havoc_object(arr);
52+
53+
// assume loop invariant
54+
__CPROVER_assume(i <= size);
55+
__CPROVER_assume(i <= size);
56+
__CPROVER_assume(__CPROVER_forall {
57+
size_t j;
58+
!(j < i) || (arr[j] == j * 2)
59+
});
60+
__CPROVER_assume(__CPROVER_forall {
61+
size_t j;
62+
!(i <= j && j < size) || (arr[j] == 0)
63+
});
64+
65+
size_t old_i = i;
66+
if(i < size)
67+
{
68+
arr[i] = arr[i] + i * 2;
69+
i += 1;
70+
71+
// check loop invariant post loop step
72+
assert(i <= size);
73+
assert(__CPROVER_forall {
74+
size_t j;
75+
!(j < i) || (arr[j] == j * 2)
76+
});
77+
assert(__CPROVER_forall {
78+
size_t j;
79+
!(i <= j && j < size) || (arr[j] == 0)
80+
});
81+
__CPROVER_assume(0); // stop progress
82+
}
83+
84+
// check that the loop invariant holds post loop
85+
assert(__CPROVER_forall {
86+
size_t j;
87+
!(j < i) || (arr[j] == j * 2)
88+
});
89+
90+
assert(__CPROVER_forall {
91+
size_t j;
92+
!(i <= j && j < size) || (arr[j] == 0)
93+
});
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE thorough-z3-smt-backend no-new-smt
2+
main.c
3+
--no-standard-checks
4+
^VERIFICATION FAILED$
5+
^EXIT=10$
6+
^SIGNAL=0$
7+
--
8+
^warning: ignoring
9+
--
10+
Uses --no-standard-checks to contain verification time with MiniSat; with
11+
CaDiCaL this completes in under 5 seconds either way. Takes an unknown amount of
12+
time (has never been run to completition) when using Z3.

Diff for: regression/cbmc/Quantifiers-unbounded-array/main.c

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include <assert.h>
2+
#include <limits.h>
3+
#include <stdlib.h>
4+
5+
size_t nondet_size_t();
6+
7+
int main()
8+
{
9+
size_t size = nondet_size_t();
10+
__CPROVER_assume(0 < size);
11+
12+
// avoids overflows in the loop body on i * 2
13+
__CPROVER_assume(size < INT_MAX / 2);
14+
size_t nof_bytes = size * sizeof(int);
15+
int *arr = malloc(nof_bytes);
16+
__CPROVER_assume(arr);
17+
__CPROVER_array_set(arr, 0);
18+
19+
// jump to a nondet state
20+
size_t i = nondet_size_t();
21+
__CPROVER_assume(i < size);
22+
__CPROVER_havoc_object(arr);
23+
24+
// assume loop invariant
25+
__CPROVER_assume(__CPROVER_forall {
26+
size_t j;
27+
!(j < i) || (arr[j] == j * 2)
28+
});
29+
__CPROVER_assume(__CPROVER_forall {
30+
size_t j;
31+
!(i <= j && j < size) || (arr[j] == 0)
32+
});
33+
34+
arr[i] = arr[i] + i * 2;
35+
i += 1;
36+
37+
assert(__CPROVER_forall {
38+
size_t j;
39+
!(i <= j && j < size) || (arr[j] == 0)
40+
});
41+
}
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CORE broken-cprover-smt-backend no-new-smt
2+
main.c
3+
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
^warning: ignoring

Diff for: src/solvers/flattening/boolbv_quantifier.cpp

+115-7
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ Author: Daniel Kroening, [email protected]
66
77
\*******************************************************************/
88

9-
#include "boolbv.h"
10-
119
#include <util/arith_tools.h>
1210
#include <util/expr_util.h>
1311
#include <util/invariant.h>
1412
#include <util/simplify_expr.h>
13+
#include <util/ssa_expr.h>
14+
15+
#include "boolbv.h"
1516

1617
/// A method to detect equivalence between experts that can contain typecast
1718
static bool expr_eq(const exprt &expr1, const exprt &expr2)
@@ -280,12 +281,119 @@ literalt boolbvt::convert_quantifier(const quantifier_exprt &src)
280281
return quantifier_list.back().l;
281282
}
282283

283-
void boolbvt::finish_eager_conversion_quantifiers()
284+
/// Eliminate the quantifier in \p q_expr via E-matching in \p context_map.
285+
/// \return Quantifier-free expression, if quantifier elimination was
286+
/// successful, else nullopt.
287+
static std::optional<exprt> finish_one_quantifier(
288+
const quantifier_exprt &q_expr,
289+
const std::unordered_map<const exprt, bvt, irep_hash> &context_map)
284290
{
285-
if(quantifier_list.empty())
286-
return;
291+
if(q_expr.variables().size() > 1)
292+
{
293+
// Rewrite Qx,y.P(x,y) as Qy.Qx.P(x,y), just like
294+
// eager_quantifier_instantiation does.
295+
auto new_variables = q_expr.variables();
296+
new_variables.pop_back();
297+
quantifier_exprt new_expression{
298+
q_expr.id(),
299+
q_expr.variables().back(),
300+
quantifier_exprt{q_expr.id(), new_variables, q_expr.where()}};
301+
return finish_one_quantifier(new_expression, context_map);
302+
}
303+
304+
// find the contexts in which the bound variable is used
305+
const irep_idt &bound_variable_id = q_expr.symbol().get_identifier();
306+
bool required_context = false;
307+
std::unordered_set<index_exprt, irep_hash> index_contexts;
308+
auto context_finder =
309+
[&bound_variable_id, &required_context, &index_contexts](const exprt &e) {
310+
if(auto symbol_expr = expr_try_dynamic_cast<symbol_exprt>(e))
311+
{
312+
required_context |= bound_variable_id == symbol_expr->get_identifier();
313+
}
314+
else if(required_context)
315+
{
316+
if(auto index_expr = expr_try_dynamic_cast<index_exprt>(e))
317+
{
318+
index_contexts.insert(*index_expr);
319+
required_context = false;
320+
}
321+
}
322+
};
323+
q_expr.where().visit_post(context_finder);
324+
// make sure we found some context for instantiation
325+
if(index_contexts.empty())
326+
return {};
327+
328+
// match the contexts against expressions that we have cached
329+
std::unordered_set<exprt, irep_hash> instantiation_candidates;
330+
for(const auto &cache_entry : context_map)
331+
{
332+
// consider re-organizing the cache to use expression ids at the top level
333+
if(auto index_expr = expr_try_dynamic_cast<index_exprt>(cache_entry.first))
334+
{
335+
for(const auto &index_context : index_contexts)
336+
{
337+
if(
338+
auto ssa_context =
339+
expr_try_dynamic_cast<ssa_exprt>(index_context.array()))
340+
{
341+
if(
342+
auto ssa_array =
343+
expr_try_dynamic_cast<ssa_exprt>(index_expr->array()))
344+
{
345+
if(
346+
ssa_context->get_l1_object_identifier() ==
347+
ssa_array->get_l1_object_identifier())
348+
{
349+
instantiation_candidates.insert(index_expr->index());
350+
break;
351+
}
352+
}
353+
}
354+
else if(index_expr->array() == index_context.array())
355+
{
356+
instantiation_candidates.insert(index_expr->index());
357+
break;
358+
}
359+
}
360+
}
361+
}
362+
363+
if(instantiation_candidates.empty())
364+
return {};
365+
366+
exprt::operandst instantiations;
367+
instantiations.reserve(instantiation_candidates.size());
368+
for(const auto &e : instantiation_candidates)
369+
{
370+
exprt::operandst values{
371+
{typecast_exprt::conditional_cast(e, q_expr.symbol().type())}};
372+
instantiations.push_back(q_expr.instantiate(values));
373+
}
287374

288-
// we do not yet have any elaborate post-processing
375+
if(q_expr.id() == ID_exists)
376+
return disjunction(instantiations);
377+
else
378+
return conjunction(instantiations);
379+
}
380+
381+
void boolbvt::finish_eager_conversion_quantifiers()
382+
{
289383
for(const auto &q : quantifier_list)
290-
conversion_failed(q.expr);
384+
{
385+
auto result_opt =
386+
finish_one_quantifier(to_quantifier_expr(q.expr), bv_cache);
387+
if(!result_opt.has_value())
388+
{
389+
conversion_failed(q.expr);
390+
continue;
391+
}
392+
393+
// Nested quantifiers may yield additional entries in quantifier_list via
394+
// convert; the range-for remains safe to use as long as quantifier_list is
395+
// a std::list.
396+
literalt result_lit = convert(*result_opt);
397+
prop.l_set_to_true(prop.lequal(q.l, result_lit));
398+
}
291399
}

0 commit comments

Comments
 (0)