Skip to content

Commit 69f8720

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 6cad8ca commit 69f8720

File tree

7 files changed

+269
-19
lines changed

7 files changed

+269
-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

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,96 @@
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+
// we remove this assumption, which in turn allows to cause an integer
15+
// overflow in the loop body when computing i*2
16+
// __CPROVER_assume(size < INT32_MAX / 2);
17+
size_t nof_bytes = size * sizeof(int);
18+
int *arr = malloc(nof_bytes);
19+
__CPROVER_assume(arr);
20+
__CPROVER_array_set(arr, 0);
21+
22+
// None of this should overflow because initially arr[j] == 0 for all j
23+
24+
// the original loop
25+
// size_t i = 0;
26+
// while (i < size)
27+
// __CPROVER_loop_invariant(i <= size)
28+
// __CPROVER_loop_invariant(__CPROVER_forall {size_t j; !(j < i) || (arr[j] == j * 2) })
29+
// __CPROVER_loop_invariant(__CPROVER_forall {size_t j; !(i <= j && j < size) || (arr[j] == 0) })
30+
// {
31+
// arr[i] = arr[i] + 2 * i;
32+
// i += 1;
33+
// }
34+
35+
size_t i = 0;
36+
37+
// check base case
38+
assert(i <= size);
39+
assert(__CPROVER_forall {
40+
size_t j;
41+
!(j < i) || (arr[j] == j * 2)
42+
});
43+
assert(__CPROVER_forall {
44+
size_t j;
45+
!(i <= j && j < size) || (arr[j] == 0)
46+
});
47+
48+
// jump to a nondet state
49+
i = nondet_size_t();
50+
__CPROVER_havoc_object(arr);
51+
52+
// assume loop invariant
53+
__CPROVER_assume(i <= size);
54+
__CPROVER_assume(i <= size);
55+
__CPROVER_assume(__CPROVER_forall {
56+
size_t j;
57+
!(j < i) || (arr[j] == j * 2)
58+
});
59+
__CPROVER_assume(__CPROVER_forall {
60+
size_t j;
61+
!(i <= j && j < size) || (arr[j] == 0)
62+
});
63+
64+
size_t old_i = i;
65+
if(i < size)
66+
{
67+
arr[i] = arr[i] + i * 2;
68+
i += 1;
69+
70+
// check loop invariant post loop step
71+
assert(i <= size);
72+
// fails with SAT backend but passes with --smt2
73+
assert(__CPROVER_forall {
74+
size_t j;
75+
!(j < i) || (arr[j] == j * 2)
76+
});
77+
// fails with SAT backend but passes with --smt2
78+
assert(__CPROVER_forall {
79+
size_t j;
80+
!(i <= j && j < size) || (arr[j] == 0)
81+
});
82+
__CPROVER_assume(0); // stop progress
83+
}
84+
85+
// check that the loop invariant holds post loop
86+
// fails with SAT backend but passes with --smt2
87+
assert(__CPROVER_forall {
88+
size_t j;
89+
!(j < i) || (arr[j] == j * 2)
90+
});
91+
92+
assert(__CPROVER_forall {
93+
size_t j;
94+
!(i <= j && j < size) || (arr[j] == 0)
95+
});
96+
}
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

+108-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)
@@ -285,10 +286,110 @@ literalt boolbvt::convert_quantifier(const quantifier_exprt &src)
285286

286287
void boolbvt::finish_eager_conversion_quantifiers()
287288
{
288-
if(quantifier_list.empty())
289-
return;
290-
291-
// we do not yet have any elaborate post-processing
292289
for(const auto &q : quantifier_list)
293-
conversion_failed(q.expr);
290+
{
291+
const auto &q_expr = to_quantifier_expr(q.expr);
292+
293+
// we don't yet do nested quantifiers
294+
if(can_cast_expr<quantifier_exprt>(q_expr.where()))
295+
{
296+
conversion_failed(q.expr);
297+
continue;
298+
}
299+
300+
// we don't yet handle multiple bound variables
301+
if(q_expr.variables().size() > 1)
302+
{
303+
conversion_failed(q.expr);
304+
continue;
305+
}
306+
307+
// find the context in which any of the bound variables are used
308+
std::unordered_set<irep_idt, irep_id_hash> bound_variables;
309+
for(const auto &v : q_expr.variables())
310+
bound_variables.insert(v.get_identifier());
311+
312+
bool required_context = false;
313+
std::unordered_set<index_exprt, irep_hash> index_contexts;
314+
auto context_finder =
315+
[&bound_variables, &required_context, &index_contexts](const exprt &e) {
316+
if(auto symbol_expr = expr_try_dynamic_cast<symbol_exprt>(e))
317+
{
318+
required_context |=
319+
bound_variables.find(symbol_expr->get_identifier()) !=
320+
bound_variables.end();
321+
}
322+
else if(required_context)
323+
{
324+
if(auto index_expr = expr_try_dynamic_cast<index_exprt>(e))
325+
{
326+
index_contexts.insert(*index_expr);
327+
required_context = false;
328+
}
329+
}
330+
};
331+
q_expr.where().visit_post(context_finder);
332+
// make sure we found some context for instantiation
333+
if(index_contexts.empty())
334+
{
335+
conversion_failed(q.expr);
336+
continue;
337+
}
338+
339+
// match the contexts against expressions that we have cached
340+
std::unordered_set<exprt, irep_hash> instantiation_candidates;
341+
for(const auto &cache_entry : bv_cache)
342+
{
343+
// consider re-organizing the cache to use expression ids at the top level
344+
if(
345+
auto index_expr = expr_try_dynamic_cast<index_exprt>(cache_entry.first))
346+
{
347+
for(const auto &index_context : index_contexts)
348+
{
349+
if(
350+
auto ssa_context =
351+
expr_try_dynamic_cast<ssa_exprt>(index_context.array()))
352+
{
353+
if(
354+
auto ssa_array =
355+
expr_try_dynamic_cast<ssa_exprt>(index_expr->array()))
356+
{
357+
if(
358+
ssa_context->get_l1_object_identifier() ==
359+
ssa_array->get_l1_object_identifier())
360+
{
361+
instantiation_candidates.insert(index_expr->index());
362+
break;
363+
}
364+
}
365+
}
366+
else if(index_expr->array() == index_context.array())
367+
{
368+
instantiation_candidates.insert(index_expr->index());
369+
break;
370+
}
371+
}
372+
}
373+
}
374+
375+
if(instantiation_candidates.empty())
376+
{
377+
conversion_failed(q.expr);
378+
continue;
379+
}
380+
381+
exprt::operandst instantiations;
382+
instantiations.reserve(instantiation_candidates.size());
383+
for(const auto &e : instantiation_candidates)
384+
{
385+
exprt::operandst values{
386+
{typecast_exprt::conditional_cast(e, q_expr.symbol().type())}};
387+
instantiations.push_back(q_expr.instantiate(values));
388+
}
389+
exprt result_expr = q_expr.id() == ID_exists ? disjunction(instantiations)
390+
: conjunction(instantiations);
391+
literalt result_lit = convert(result_expr);
392+
393+
prop.l_set_to_true(prop.lequal(q.l, result_lit));
394+
}
294395
}

0 commit comments

Comments
 (0)