Skip to content

Commit 9e8e5c2

Browse files
author
Remi Delmas
committed
CONTRATCS: force success for unit pointer predicates
Force success for pointer predicates that appear as top level units in requires or ensures clauses. For example `__CPROVER_ensures(__CPROVER_is_fresh(p,size))` is a unit. This ensures that the value set of `p` after assuming the clause contains a unique valid target. It 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, instrumentation detects units in contracts and sets the `may_fail` parameter accordingly.
1 parent d4757e2 commit 9e8e5c2

File tree

9 files changed

+106
-21
lines changed

9 files changed

+106
-21
lines changed

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

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FUTURE dfcc-only smt-backend broken-cprover-smt-backend
1+
CORE 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$
@@ -8,11 +8,6 @@ main.c
88
^warning: ignoring
99
--
1010

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-
1611
Tests support for quantifiers in loop contracts with the SMT backend.
1712
When quantified loop invariants are used, they are inserted three times
1813
in the transformed program (base case assertion, step case assumption,

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

+17-9
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,7 @@ void __CPROVER_contracts_make_invalid_pointer(void **ptr)
11661166
///
11671167
/// \param ptr1 First argument of the `pointer_equals` predicate
11681168
/// \param ptr2 Second argument of the `pointer_equals` predicate
1169+
/// \param may_fail Allow predicate to fail in assume mode
11691170
/// \param write_set Write set which conveys the invocation context
11701171
/// (requires/ensures clause, assert/assume context);
11711172
///
@@ -1179,6 +1180,7 @@ void __CPROVER_contracts_make_invalid_pointer(void **ptr)
11791180
__CPROVER_bool __CPROVER_contracts_pointer_equals(
11801181
void **ptr1,
11811182
void *ptr2,
1183+
__CPROVER_bool may_fail,
11821184
__CPROVER_contracts_write_set_ptr_t write_set)
11831185
{
11841186
__CPROVER_HIDE:;
@@ -1195,7 +1197,8 @@ __CPROVER_HIDE:;
11951197
#endif
11961198
if(write_set->assume_requires_ctx | write_set->assume_ensures_ctx)
11971199
{
1198-
if(__VERIFIER_nondet___CPROVER_bool())
1200+
// SOUNDNESS: allow predicate to fail
1201+
if(may_fail && __VERIFIER_nondet___CPROVER_bool())
11991202
{
12001203
__CPROVER_contracts_make_invalid_pointer(ptr1);
12011204
return 0;
@@ -1224,8 +1227,9 @@ __CPROVER_HIDE:;
12241227
/// The behaviour depends on the boolean flags carried by \p write_set
12251228
/// which reflect the invocation context: checking vs. replacing a contract,
12261229
/// in a requires or an ensures clause context.
1227-
/// \param elem First argument of the `is_fresh` predicate
1228-
/// \param size Second argument of the `is_fresh` predicate
1230+
/// \param elem Pointer to the target pointer of the check
1231+
/// \param size Size to check for
1232+
/// \param may_fail Allow predicate to fail in assume mode
12291233
/// \param write_set Write set in which seen/allocated objects are recorded;
12301234
///
12311235
/// \details The behaviour is as follows:
@@ -1242,6 +1246,7 @@ __CPROVER_HIDE:;
12421246
__CPROVER_bool __CPROVER_contracts_is_fresh(
12431247
void **elem,
12441248
__CPROVER_size_t size,
1249+
__CPROVER_bool may_fail,
12451250
__CPROVER_contracts_write_set_ptr_t write_set)
12461251
{
12471252
__CPROVER_HIDE:;
@@ -1289,7 +1294,7 @@ __CPROVER_HIDE:;
12891294
}
12901295

12911296
// SOUNDNESS: allow predicate to fail
1292-
if(__VERIFIER_nondet___CPROVER_bool())
1297+
if(may_fail && __VERIFIER_nondet___CPROVER_bool())
12931298
{
12941299
__CPROVER_contracts_make_invalid_pointer(elem);
12951300
return 0;
@@ -1349,7 +1354,7 @@ __CPROVER_HIDE:;
13491354
}
13501355

13511356
// SOUNDNESS: allow predicate to fail
1352-
if(__VERIFIER_nondet___CPROVER_bool())
1357+
if(may_fail && __VERIFIER_nondet___CPROVER_bool())
13531358
{
13541359
__CPROVER_contracts_make_invalid_pointer(elem);
13551360
return 0;
@@ -1436,6 +1441,7 @@ __CPROVER_HIDE:;
14361441
/// \param lb Lower bound pointer
14371442
/// \param ptr Target pointer of the predicate
14381443
/// \param ub Upper bound pointer
1444+
/// \param may_fail Allow predicate to fail in assume mode
14391445
/// \param write_set Write set in which seen/allocated objects are recorded;
14401446
///
14411447
/// \details The behaviour is as follows:
@@ -1449,6 +1455,7 @@ __CPROVER_bool __CPROVER_contracts_pointer_in_range_dfcc(
14491455
void *lb,
14501456
void **ptr,
14511457
void *ub,
1458+
__CPROVER_bool may_fail,
14521459
__CPROVER_contracts_write_set_ptr_t write_set)
14531460
{
14541461
__CPROVER_HIDE:;
@@ -1470,9 +1477,9 @@ __CPROVER_HIDE:;
14701477
lb_offset <= ub_offset, "lb and ub pointers must be ordered");
14711478
if(write_set->assume_requires_ctx | write_set->assume_ensures_ctx)
14721479
{
1473-
if(__VERIFIER_nondet___CPROVER_bool())
1480+
// SOUNDNESS: allow predicate to fail
1481+
if(may_fail && __VERIFIER_nondet___CPROVER_bool())
14741482
{
1475-
// SOUNDNESS: allow predicate to fail
14761483
__CPROVER_contracts_make_invalid_pointer(ptr);
14771484
return 0;
14781485
}
@@ -1647,6 +1654,7 @@ __CPROVER_HIDE:;
16471654
__CPROVER_bool __CPROVER_contracts_obeys_contract(
16481655
void (**function_pointer)(void),
16491656
void (*contract)(void),
1657+
__CPROVER_bool may_fail,
16501658
__CPROVER_contracts_write_set_ptr_t set)
16511659
{
16521660
__CPROVER_HIDE:;
@@ -1657,8 +1665,8 @@ __CPROVER_HIDE:;
16571665
"__CPROVER_obeys_contract is used only in requires or ensures clauses");
16581666
if((set->assume_requires_ctx == 1) | (set->assume_ensures_ctx == 1))
16591667
{
1660-
// decide if predicate must hold
1661-
if(__VERIFIER_nondet___CPROVER_bool())
1668+
// SOUDNESS: allow predicate to fail
1669+
if(may_fail && __VERIFIER_nondet___CPROVER_bool())
16621670
return 0;
16631671

16641672
// must hold, assign the function pointer to the contract function

Diff for: src/goto-instrument/contracts/dynamic-frames/dfcc_is_cprover_symbol.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,11 @@ bool dfcc_is_cprover_static_symbol(const irep_idt &id)
156156
// going_to variables converted from goto statements
157157
has_prefix(id2string(id), CPROVER_PREFIX "going_to");
158158
}
159+
160+
bool dfcc_is_cprover_pointer_predicate(const irep_idt &id)
161+
{
162+
return id == CPROVER_PREFIX "pointer_equals" ||
163+
id == CPROVER_PREFIX "is_fresh" ||
164+
id == CPROVER_PREFIX "pointer_in_range_dfcc" ||
165+
id == CPROVER_PREFIX "obeys_contract";
166+
}

Diff for: src/goto-instrument/contracts/dynamic-frames/dfcc_is_cprover_symbol.h

+3
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,7 @@ bool dfcc_is_cprover_function_symbol(const irep_idt &id);
2424
/// auto-generated object following a pointer dereference.
2525
bool dfcc_is_cprover_static_symbol(const irep_idt &id);
2626

27+
/// Returns `true` iff the symbol is one of the CPROVER pointer predicates
28+
bool dfcc_is_cprover_pointer_predicate(const irep_idt &id);
29+
2730
#endif

Diff for: src/goto-instrument/contracts/dynamic-frames/dfcc_is_fresh.cpp

+10-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Date: August 2022
1111

1212
#include <util/cprover_prefix.h>
1313
#include <util/pointer_expr.h>
14+
#include <util/prefix.h>
15+
#include <util/suffix.h>
1416
#include <util/symbol.h>
1517

1618
#include "dfcc_cfg_info.h"
@@ -33,7 +35,7 @@ void dfcc_is_fresht::rewrite_calls(
3335
program.instructions.end(),
3436
cfg_info);
3537
}
36-
38+
#include <iostream>
3739
void dfcc_is_fresht::rewrite_calls(
3840
goto_programt &program,
3941
goto_programt::targett first_instruction,
@@ -50,8 +52,7 @@ void dfcc_is_fresht::rewrite_calls(
5052
if(function.id() == ID_symbol)
5153
{
5254
const irep_idt &fun_name = to_symbol_expr(function).get_identifier();
53-
54-
if(fun_name == CPROVER_PREFIX "is_fresh")
55+
if(has_prefix(id2string(fun_name), CPROVER_PREFIX "is_fresh"))
5556
{
5657
// add address on first operand
5758
target->call_arguments()[0] =
@@ -61,6 +62,12 @@ void dfcc_is_fresht::rewrite_calls(
6162
to_symbol_expr(target->call_function())
6263
.set_identifier(library.dfcc_fun_symbol[dfcc_funt::IS_FRESH].name);
6364

65+
// pass the may_fail flag
66+
if(function.source_location().get_bool("no_fail"))
67+
target->call_arguments().push_back(false_exprt());
68+
else
69+
target->call_arguments().push_back(true_exprt());
70+
6471
// pass the write_set
6572
target->call_arguments().push_back(cfg_info.get_write_set(target));
6673
}

Diff for: src/goto-instrument/contracts/dynamic-frames/dfcc_obeys_contract.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Date: August 2022
1212
#include <util/cprover_prefix.h>
1313
#include <util/pointer_expr.h>
1414
#include <util/prefix.h>
15+
#include <util/suffix.h>
1516
#include <util/symbol.h>
1617

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

91+
// pass the may_fail flag
92+
if(function.source_location().get_bool("no_fail"))
93+
target->call_arguments().push_back(false_exprt());
94+
else
95+
target->call_arguments().push_back(true_exprt());
96+
9097
// pass the write_set
9198
target->call_arguments().push_back(cfg_info.get_write_set(target));
9299

Diff for: src/goto-instrument/contracts/dynamic-frames/dfcc_pointer_equals.cpp

+11-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ Date: Jan 2025
1313
#include <util/cprover_prefix.h>
1414
#include <util/expr_iterator.h>
1515
#include <util/pointer_expr.h>
16+
#include <util/prefix.h>
1617
#include <util/replace_expr.h>
1718
#include <util/std_code.h>
1819
#include <util/std_expr.h>
20+
#include <util/suffix.h>
1921
#include <util/symbol.h>
2022

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

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

72+
// pass the may_fail flag
73+
if(function.source_location().get_bool("no_fail"))
74+
target->call_arguments().push_back(false_exprt());
75+
else
76+
target->call_arguments().push_back(true_exprt());
77+
7078
// pass the write_set
7179
target->call_arguments().push_back(cfg_info.get_write_set(target));
7280
}
@@ -106,7 +114,8 @@ class pointer_equality_visitort : public expr_visitort
106114
code_typet::parametert(pointer_type(void_type()))},
107115
bool_typet());
108116

109-
symbol_exprt pointer_equals("ID_pointer_equals", pointer_equals_type);
117+
symbol_exprt pointer_equals(
118+
CPROVER_PREFIX "pointer_equals", pointer_equals_type);
110119

111120
for(exprt *expr_ptr : equality_exprs_to_transform)
112121
{

Diff for: src/goto-instrument/contracts/dynamic-frames/dfcc_pointer_in_range.cpp

+11-1
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ Date: August 2022
1111

1212
#include <util/cprover_prefix.h>
1313
#include <util/pointer_expr.h>
14+
#include <util/prefix.h>
1415
#include <util/replace_expr.h>
1516
#include <util/std_code.h>
17+
#include <util/suffix.h>
1618
#include <util/symbol.h>
1719

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

56-
if(fun_name == CPROVER_PREFIX "pointer_in_range_dfcc")
58+
if(has_prefix(
59+
id2string(fun_name), CPROVER_PREFIX "pointer_in_range_dfcc"))
5760
{
5861
// add address on second operand
5962
target->call_arguments()[1] =
@@ -64,6 +67,13 @@ void dfcc_pointer_in_ranget::rewrite_calls(
6467
.set_identifier(
6568
library.dfcc_fun_symbol[dfcc_funt::POINTER_IN_RANGE_DFCC].name);
6669

70+
// pass the may_fail flag
71+
// pass the may_fail flag
72+
if(function.source_location().get_bool("no_fail"))
73+
target->call_arguments().push_back(false_exprt());
74+
else
75+
target->call_arguments().push_back(true_exprt());
76+
6777
// pass the write_set
6878
target->call_arguments().push_back(cfg_info.get_write_set(target));
6979
}

Diff for: src/goto-instrument/contracts/dynamic-frames/dfcc_wrapper_program.cpp

+38
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Author: Remi Delmas, [email protected]
2525

2626
#include "dfcc_contract_functions.h"
2727
#include "dfcc_instrument.h"
28+
#include "dfcc_is_cprover_symbol.h"
2829
#include "dfcc_library.h"
2930
#include "dfcc_lift_memory_predicates.h"
3031
#include "dfcc_pointer_equals.h"
@@ -535,6 +536,37 @@ void dfcc_wrapper_programt::encode_is_fresh_set()
535536
postamble.add(goto_programt::make_dead(is_fresh_set, wrapper_sl));
536537
}
537538

539+
#include <iostream>
540+
541+
/// If the expression is a function call to one of the pointer predicates,
542+
/// adds a "_no_fail" prefix to the function name. This is later picked up
543+
/// by fix_calls to synthesize the value of the `may_fail` parameter for
544+
/// pointer predicates.
545+
bool disable_may_fail(exprt &expr)
546+
{
547+
if(expr.id() != ID_side_effect)
548+
return false;
549+
550+
side_effect_exprt &side_effect = to_side_effect_expr(expr);
551+
if(side_effect.get_statement() != ID_function_call)
552+
return false;
553+
554+
exprt &function = side_effect.operands()[0];
555+
if(function.id() != ID_symbol)
556+
return false;
557+
558+
const irep_idt &func_name = to_symbol_expr(function).get_identifier();
559+
560+
if(dfcc_is_cprover_pointer_predicate(func_name))
561+
{
562+
// tag call as no_fail
563+
function.add_source_location().set("no_fail", true);
564+
return true;
565+
}
566+
567+
return false;
568+
}
569+
538570
void dfcc_wrapper_programt::encode_requires_clauses()
539571
{
540572
// we use this empty requires write set to check for the absence of side
@@ -556,6 +588,9 @@ void dfcc_wrapper_programt::encode_requires_clauses()
556588
{
557589
exprt requires_lmbd =
558590
to_lambda_expr(r).application(contract_lambda_parameters);
591+
592+
// add "no_fail" suffix to predicates required as units
593+
disable_may_fail(requires_lmbd);
559594
source_locationt sl(r.source_location());
560595
if(statement_type == ID_assert)
561596
{
@@ -605,6 +640,9 @@ void dfcc_wrapper_programt::encode_ensures_clauses()
605640
.application(contract_lambda_parameters)
606641
.with_source_location(e);
607642

643+
// add "no_fail" suffix to unit pointer predicates
644+
disable_may_fail(ensures);
645+
608646
// this also rewrites ID_old expressions to fresh variables
609647
generate_history_variables_initialization(
610648
goto_model.symbol_table, ensures, language_mode, history);

0 commit comments

Comments
 (0)