Skip to content

Commit

Permalink
CONTRATCS: force success for some pointer predicates
Browse files Browse the repository at this point in the history
Force success for pointer predicates that must
necessarily hold if they ever get invoked in
an assumption context. This ensures that the value sets
of pointers as as small as possible after assuming
a requires or an ensures clause and solves a performance
issue with z3. All predicates in the `cprover_contracts.c`
library now have an extra input `may_fail` controlling the
failure behaviour, DFCC instrumentation sets the `may_fail`
parameter to true or false using recursive algorithm.
  • Loading branch information
Remi Delmas committed Jan 24, 2025
1 parent d4757e2 commit 3ddd4b6
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FUTURE dfcc-only smt-backend broken-cprover-smt-backend
CORE dfcc-only smt-backend broken-cprover-smt-backend
main.c
--dfcc main --apply-loop-contracts --enforce-contract foo --malloc-may-fail --malloc-fail-null _ --z3 --slice-formula --no-standard-checks
^EXIT=0$
Expand All @@ -8,11 +8,6 @@ main.c
^warning: ignoring
--

Marked as FUTURE because:
- z3 >v4.12 and up can solve the problem with `--dfcc-simple-invalid-pointer-model`,
but the CI uses older versions;
- bitwuzla >v0.6 can solve the problem but we don't run bitwuzla in CI.

Tests support for quantifiers in loop contracts with the SMT backend.
When quantified loop invariants are used, they are inserted three times
in the transformed program (base case assertion, step case assumption,
Expand Down
26 changes: 17 additions & 9 deletions src/ansi-c/library/cprover_contracts.c
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,7 @@ void __CPROVER_contracts_make_invalid_pointer(void **ptr)
///
/// \param ptr1 First argument of the `pointer_equals` predicate
/// \param ptr2 Second argument of the `pointer_equals` predicate
/// \param may_fail Allow predicate to fail in assume mode
/// \param write_set Write set which conveys the invocation context
/// (requires/ensures clause, assert/assume context);
///
Expand All @@ -1179,6 +1180,7 @@ void __CPROVER_contracts_make_invalid_pointer(void **ptr)
__CPROVER_bool __CPROVER_contracts_pointer_equals(
void **ptr1,
void *ptr2,
__CPROVER_bool may_fail,
__CPROVER_contracts_write_set_ptr_t write_set)
{
__CPROVER_HIDE:;
Expand All @@ -1195,7 +1197,8 @@ __CPROVER_HIDE:;
#endif
if(write_set->assume_requires_ctx | write_set->assume_ensures_ctx)
{
if(__VERIFIER_nondet___CPROVER_bool())
// SOUNDNESS: allow predicate to fail
if(may_fail && __VERIFIER_nondet___CPROVER_bool())
{
__CPROVER_contracts_make_invalid_pointer(ptr1);
return 0;
Expand Down Expand Up @@ -1224,8 +1227,9 @@ __CPROVER_HIDE:;
/// The behaviour depends on the boolean flags carried by \p write_set
/// which reflect the invocation context: checking vs. replacing a contract,
/// in a requires or an ensures clause context.
/// \param elem First argument of the `is_fresh` predicate
/// \param size Second argument of the `is_fresh` predicate
/// \param elem Pointer to the target pointer of the check
/// \param size Size to check for
/// \param may_fail Allow predicate to fail in assume mode
/// \param write_set Write set in which seen/allocated objects are recorded;
///
/// \details The behaviour is as follows:
Expand All @@ -1242,6 +1246,7 @@ __CPROVER_HIDE:;
__CPROVER_bool __CPROVER_contracts_is_fresh(
void **elem,
__CPROVER_size_t size,
__CPROVER_bool may_fail,
__CPROVER_contracts_write_set_ptr_t write_set)
{
__CPROVER_HIDE:;
Expand Down Expand Up @@ -1289,7 +1294,7 @@ __CPROVER_HIDE:;
}

// SOUNDNESS: allow predicate to fail
if(__VERIFIER_nondet___CPROVER_bool())
if(may_fail && __VERIFIER_nondet___CPROVER_bool())
{
__CPROVER_contracts_make_invalid_pointer(elem);
return 0;
Expand Down Expand Up @@ -1349,7 +1354,7 @@ __CPROVER_HIDE:;
}

// SOUNDNESS: allow predicate to fail
if(__VERIFIER_nondet___CPROVER_bool())
if(may_fail && __VERIFIER_nondet___CPROVER_bool())
{
__CPROVER_contracts_make_invalid_pointer(elem);
return 0;
Expand Down Expand Up @@ -1436,6 +1441,7 @@ __CPROVER_HIDE:;
/// \param lb Lower bound pointer
/// \param ptr Target pointer of the predicate
/// \param ub Upper bound pointer
/// \param may_fail Allow predicate to fail in assume mode
/// \param write_set Write set in which seen/allocated objects are recorded;
///
/// \details The behaviour is as follows:
Expand All @@ -1449,6 +1455,7 @@ __CPROVER_bool __CPROVER_contracts_pointer_in_range_dfcc(
void *lb,
void **ptr,
void *ub,
__CPROVER_bool may_fail,
__CPROVER_contracts_write_set_ptr_t write_set)
{
__CPROVER_HIDE:;
Expand All @@ -1470,9 +1477,9 @@ __CPROVER_HIDE:;
lb_offset <= ub_offset, "lb and ub pointers must be ordered");
if(write_set->assume_requires_ctx | write_set->assume_ensures_ctx)
{
if(__VERIFIER_nondet___CPROVER_bool())
// SOUNDNESS: allow predicate to fail
if(may_fail && __VERIFIER_nondet___CPROVER_bool())
{
// SOUNDNESS: allow predicate to fail
__CPROVER_contracts_make_invalid_pointer(ptr);
return 0;
}
Expand Down Expand Up @@ -1647,6 +1654,7 @@ __CPROVER_HIDE:;
__CPROVER_bool __CPROVER_contracts_obeys_contract(
void (**function_pointer)(void),
void (*contract)(void),
__CPROVER_bool may_fail,
__CPROVER_contracts_write_set_ptr_t set)
{
__CPROVER_HIDE:;
Expand All @@ -1657,8 +1665,8 @@ __CPROVER_HIDE:;
"__CPROVER_obeys_contract is used only in requires or ensures clauses");
if((set->assume_requires_ctx == 1) | (set->assume_ensures_ctx == 1))
{
// decide if predicate must hold
if(__VERIFIER_nondet___CPROVER_bool())
// SOUDNESS: allow predicate to fail
if(may_fail && __VERIFIER_nondet___CPROVER_bool())
return 0;

// must hold, assign the function pointer to the contract function
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,11 @@ bool dfcc_is_cprover_static_symbol(const irep_idt &id)
// going_to variables converted from goto statements
has_prefix(id2string(id), CPROVER_PREFIX "going_to");
}

bool dfcc_is_cprover_pointer_predicate(const irep_idt &id)
{
return id == CPROVER_PREFIX "pointer_equals" ||
id == CPROVER_PREFIX "is_fresh" ||
id == CPROVER_PREFIX "pointer_in_range_dfcc" ||
id == CPROVER_PREFIX "obeys_contract";
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ bool dfcc_is_cprover_function_symbol(const irep_idt &id);
/// auto-generated object following a pointer dereference.
bool dfcc_is_cprover_static_symbol(const irep_idt &id);

/// Returns `true` iff the symbol is one of the CPROVER pointer predicates
bool dfcc_is_cprover_pointer_predicate(const irep_idt &id);

#endif
11 changes: 9 additions & 2 deletions src/goto-instrument/contracts/dynamic-frames/dfcc_is_fresh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Date: August 2022

#include <util/cprover_prefix.h>
#include <util/pointer_expr.h>
#include <util/prefix.h>
#include <util/suffix.h>
#include <util/symbol.h>

#include "dfcc_cfg_info.h"
Expand Down Expand Up @@ -50,8 +52,7 @@ void dfcc_is_fresht::rewrite_calls(
if(function.id() == ID_symbol)
{
const irep_idt &fun_name = to_symbol_expr(function).get_identifier();

if(fun_name == CPROVER_PREFIX "is_fresh")
if(has_prefix(id2string(fun_name), CPROVER_PREFIX "is_fresh"))
{
// add address on first operand
target->call_arguments()[0] =
Expand All @@ -61,6 +62,12 @@ void dfcc_is_fresht::rewrite_calls(
to_symbol_expr(target->call_function())
.set_identifier(library.dfcc_fun_symbol[dfcc_funt::IS_FRESH].name);

// pass the may_fail flag

Check warning on line 65 in src/goto-instrument/contracts/dynamic-frames/dfcc_is_fresh.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/dynamic-frames/dfcc_is_fresh.cpp#L65

Added line #L65 was not covered by tests
if(function.source_location().get_bool("no_fail"))
target->call_arguments().push_back(false_exprt());
else
target->call_arguments().push_back(true_exprt());

// pass the write_set
target->call_arguments().push_back(cfg_info.get_write_set(target));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Date: August 2022
#include <util/cprover_prefix.h>
#include <util/pointer_expr.h>
#include <util/prefix.h>
#include <util/suffix.h>
#include <util/symbol.h>

#include <langapi/language_util.h>
Expand Down Expand Up @@ -87,6 +88,12 @@ void dfcc_obeys_contractt::rewrite_calls(
.set_identifier(
library.dfcc_fun_symbol[dfcc_funt::OBEYS_CONTRACT].name);

// pass the may_fail flag

Check warning on line 91 in src/goto-instrument/contracts/dynamic-frames/dfcc_obeys_contract.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/dynamic-frames/dfcc_obeys_contract.cpp#L91

Added line #L91 was not covered by tests
if(function.source_location().get_bool("no_fail"))
target->call_arguments().push_back(false_exprt());
else
target->call_arguments().push_back(true_exprt());

Check warning on line 95 in src/goto-instrument/contracts/dynamic-frames/dfcc_obeys_contract.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/dynamic-frames/dfcc_obeys_contract.cpp#L94-L95

Added lines #L94 - L95 were not covered by tests

// pass the write_set
target->call_arguments().push_back(cfg_info.get_write_set(target));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ Date: Jan 2025
#include <util/cprover_prefix.h>
#include <util/expr_iterator.h>
#include <util/pointer_expr.h>
#include <util/prefix.h>
#include <util/replace_expr.h>
#include <util/std_code.h>
#include <util/std_expr.h>
#include <util/suffix.h>
#include <util/symbol.h>

#include "dfcc_cfg_info.h"
Expand Down Expand Up @@ -56,7 +58,7 @@ void dfcc_pointer_equalst::rewrite_calls(
{
const irep_idt &fun_name = to_symbol_expr(function).get_identifier();

if(fun_name == CPROVER_PREFIX "pointer_equals")
if(has_prefix(id2string(fun_name), CPROVER_PREFIX "pointer_equals"))
{
// add address on first operand
target->call_arguments()[0] =
Expand All @@ -67,6 +69,12 @@ void dfcc_pointer_equalst::rewrite_calls(
.set_identifier(
library.dfcc_fun_symbol[dfcc_funt::POINTER_EQUALS].name);

// pass the may_fail flag
if(function.source_location().get_bool("no_fail"))
target->call_arguments().push_back(false_exprt());
else

Check warning on line 75 in src/goto-instrument/contracts/dynamic-frames/dfcc_pointer_equals.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/dynamic-frames/dfcc_pointer_equals.cpp#L75

Added line #L75 was not covered by tests
target->call_arguments().push_back(true_exprt());

Check warning on line 77 in src/goto-instrument/contracts/dynamic-frames/dfcc_pointer_equals.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/dynamic-frames/dfcc_pointer_equals.cpp#L77

Added line #L77 was not covered by tests
// pass the write_set
target->call_arguments().push_back(cfg_info.get_write_set(target));
}
Expand Down Expand Up @@ -106,7 +114,8 @@ class pointer_equality_visitort : public expr_visitort
code_typet::parametert(pointer_type(void_type()))},
bool_typet());

symbol_exprt pointer_equals("ID_pointer_equals", pointer_equals_type);
symbol_exprt pointer_equals(
CPROVER_PREFIX "pointer_equals", pointer_equals_type);

Check warning on line 118 in src/goto-instrument/contracts/dynamic-frames/dfcc_pointer_equals.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/dynamic-frames/dfcc_pointer_equals.cpp#L117-L118

Added lines #L117 - L118 were not covered by tests

for(exprt *expr_ptr : equality_exprs_to_transform)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ Date: August 2022

#include <util/cprover_prefix.h>
#include <util/pointer_expr.h>
#include <util/prefix.h>
#include <util/replace_expr.h>
#include <util/std_code.h>
#include <util/suffix.h>
#include <util/symbol.h>

#include "dfcc_cfg_info.h"
Expand Down Expand Up @@ -53,7 +55,8 @@ void dfcc_pointer_in_ranget::rewrite_calls(
{
const irep_idt &fun_name = to_symbol_expr(function).get_identifier();

if(fun_name == CPROVER_PREFIX "pointer_in_range_dfcc")
if(has_prefix(
id2string(fun_name), CPROVER_PREFIX "pointer_in_range_dfcc"))

Check warning on line 59 in src/goto-instrument/contracts/dynamic-frames/dfcc_pointer_in_range.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/dynamic-frames/dfcc_pointer_in_range.cpp#L59

Added line #L59 was not covered by tests
{
// add address on second operand
target->call_arguments()[1] =
Expand All @@ -64,6 +67,13 @@ void dfcc_pointer_in_ranget::rewrite_calls(
.set_identifier(
library.dfcc_fun_symbol[dfcc_funt::POINTER_IN_RANGE_DFCC].name);

// pass the may_fail flag
// pass the may_fail flag
if(function.source_location().get_bool("no_fail"))
target->call_arguments().push_back(false_exprt());
else

Check warning on line 74 in src/goto-instrument/contracts/dynamic-frames/dfcc_pointer_in_range.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/dynamic-frames/dfcc_pointer_in_range.cpp#L74

Added line #L74 was not covered by tests
target->call_arguments().push_back(true_exprt());

// pass the write_set
target->call_arguments().push_back(cfg_info.get_write_set(target));
}
Expand Down
Loading

0 comments on commit 3ddd4b6

Please sign in to comment.