Skip to content

Commit e979772

Browse files
author
Remi Delmas
committed
Soudness fix: pointer predicates can fail in assume contexts.
Make failed predicates yield invalid pointers (instead of nondet). Mark peformance regressing test as FUTURE.
1 parent 4161fd1 commit e979772

File tree

2 files changed

+44
-5
lines changed

2 files changed

+44
-5
lines changed

Diff for: regression/contracts-dfcc/quantifiers-loops-fresh-bound-vars-smt/test.desc

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE dfcc-only smt-backend broken-cprover-smt-backend
1+
FUTURE dfcc-only smt-backend broken-cprover-smt-backend
22
main.c
33
--dfcc main --apply-loop-contracts --enforce-contract foo --malloc-may-fail --malloc-fail-null _ --z3 --slice-formula --no-standard-checks
44
^EXIT=0$
@@ -7,6 +7,12 @@ main.c
77
--
88
^warning: ignoring
99
--
10+
11+
Marked as FUTURE because:
12+
- z3 >v4.12 and up can solve the problem with `--dfcc-simple-invalid-pointer-model`,
13+
but the CI uses older versions;
14+
- bitwuzla >v0.6 can solve the problem but we don't run bitwuzla in CI.
15+
1016
Tests support for quantifiers in loop contracts with the SMT backend.
1117
When quantified loop invariants are used, they are inserted three times
1218
in the transformed program (base case assertion, step case assumption,

Diff for: src/ansi-c/library/cprover_contracts.c

+37-4
Original file line numberDiff line numberDiff line change
@@ -1165,7 +1165,7 @@ void __CPROVER_contracts_make_invalid_pointer(void **ptr)
11651165

11661166
/// \brief Implementation of the `is_fresh` front-end predicate.
11671167
///
1168-
/// The behaviour depends on the boolean flags carried by \p set
1168+
/// The behaviour depends on the boolean flags carried by \p write_set
11691169
/// which reflect the invocation context: checking vs. replacing a contract,
11701170
/// in a requires or an ensures clause context.
11711171
/// \param elem First argument of the `is_fresh` predicate
@@ -1232,6 +1232,13 @@ __CPROVER_HIDE:;
12321232
__CPROVER_assume(size <= __CPROVER_max_malloc_size);
12331233
}
12341234

1235+
// SOUNDNESS: allow predicate to fail
1236+
if(__VERIFIER_nondet___CPROVER_bool())
1237+
{
1238+
__CPROVER_contracts_make_invalid_pointer(elem);
1239+
return 0;
1240+
}
1241+
12351242
void *ptr = __CPROVER_allocate(size, 0);
12361243
*elem = ptr;
12371244

@@ -1245,7 +1252,6 @@ __CPROVER_HIDE:;
12451252
// __CPROVER_bool record_may_leak = __VERIFIER_nondet___CPROVER_bool();
12461253
// __CPROVER_memory_leak = record_may_leak ? ptr : __CPROVER_memory_leak;
12471254

1248-
// record fresh object in the object set
12491255
#ifdef __CPROVER_DFCC_DEBUG_LIB
12501256
// manually inlined below
12511257
__CPROVER_contracts_obj_set_add(write_set->linked_is_fresh, ptr);
@@ -1286,6 +1292,13 @@ __CPROVER_HIDE:;
12861292
__CPROVER_assume(size <= __CPROVER_max_malloc_size);
12871293
}
12881294

1295+
// SOUNDNESS: allow predicate to fail
1296+
if(__VERIFIER_nondet___CPROVER_bool())
1297+
{
1298+
__CPROVER_contracts_make_invalid_pointer(elem);
1299+
return 0;
1300+
}
1301+
12891302
void *ptr = __CPROVER_allocate(size, 0);
12901303
*elem = ptr;
12911304

@@ -1300,7 +1313,6 @@ __CPROVER_HIDE:;
13001313
__CPROVER_bool record_may_leak = __VERIFIER_nondet___CPROVER_bool();
13011314
__CPROVER_memory_leak = record_may_leak ? ptr : __CPROVER_memory_leak;
13021315

1303-
// record fresh object in the caller's write set
13041316
#ifdef __CPROVER_DFCC_DEBUG_LIB
13051317
__CPROVER_contracts_obj_set_add(write_set->linked_allocated, ptr);
13061318
#else
@@ -1338,7 +1350,7 @@ __CPROVER_HIDE:;
13381350
if(seen->elems[object_id] != 0)
13391351
return 0;
13401352
#endif
1341-
// record fresh object in the object set
1353+
13421354
#ifdef __CPROVER_DFCC_DEBUG_LIB
13431355
// manually inlined below
13441356
__CPROVER_contracts_obj_set_add(seen, ptr);
@@ -1360,6 +1372,23 @@ __CPROVER_HIDE:;
13601372
}
13611373
}
13621374

1375+
/// \brief Implementation of the `pointer_in_range_dfcc` front-end predicate.
1376+
///
1377+
/// The behaviour depends on the boolean flags carried by \p write_set
1378+
/// which reflect the invocation context: checking vs. replacing a contract,
1379+
/// in a requires or an ensures clause context.
1380+
/// \param lb Lower bound pointer
1381+
/// \param ptr Target pointer of the predicate
1382+
/// \param ub Upper bound pointer
1383+
/// \param write_set Write set in which seen/allocated objects are recorded;
1384+
///
1385+
/// \details The behaviour is as follows:
1386+
/// - When \p set->assume_requires_ctx or \p set->assume_ensures_ctx is `true`,
1387+
/// the predicate checks that \p lb and \p ub are valid, into the same object,
1388+
/// ordered, and checks that \p ptr is between \p lb and \p ub.
1389+
/// - When \p set->assert_requires_ctx or \p set->assert_ensures_ctx is `true`,
1390+
/// the predicate checks that \p lb and \p ub are valid, into the same object,
1391+
/// ordered, and assigns \p ptr to some nondet offset between \p lb and \p ub.
13631392
__CPROVER_bool __CPROVER_contracts_pointer_in_range_dfcc(
13641393
void *lb,
13651394
void **ptr,
@@ -1386,7 +1415,11 @@ __CPROVER_HIDE:;
13861415
if(write_set->assume_requires_ctx | write_set->assume_ensures_ctx)
13871416
{
13881417
if(__VERIFIER_nondet___CPROVER_bool())
1418+
{
1419+
// SOUNDNESS: allow predicate to fail
1420+
__CPROVER_contracts_make_invalid_pointer(ptr);
13891421
return 0;
1422+
}
13901423

13911424
// add nondet offset
13921425
__CPROVER_size_t offset = __VERIFIER_nondet_size();

0 commit comments

Comments
 (0)