Skip to content

Commit 3ddd4b6

Browse files
author
Remi Delmas
committedJan 24, 2025·
CONTRATCS: force success for some pointer predicates
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.
1 parent d4757e2 commit 3ddd4b6

File tree

9 files changed

+171
-20
lines changed

9 files changed

+171
-20
lines changed
 

‎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,

‎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

‎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+
}

‎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

‎src/goto-instrument/contracts/dynamic-frames/dfcc_is_fresh.cpp

+9-2
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"
@@ -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
}

‎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

‎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
{

‎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
}

‎src/goto-instrument/contracts/dynamic-frames/dfcc_wrapper_program.cpp

+104
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
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,103 @@
535536
postamble.add(goto_programt::make_dead(is_fresh_set, wrapper_sl));
536537
}
537538

539+
/// Recursively traverses expression, adding "no_fail" attributes to pointer
540+
/// predicates that we know cannot fail when invoked in an assume context:
541+
/// Starting from the root with "no_fail" true, we recurse over the boolean
542+
/// structure, setting the "no_fail" false for all but the last operand
543+
/// disjunctive operators like `||` and `==>`, and distributing the current
544+
/// "no_fail" status over disjunctions.
545+
///
546+
/// For instance:
547+
/// ```
548+
/// (len > 0):no_fail=false ==> is_fresh(p, len):no_fail=true
549+
/// ```
550+
///
551+
/// ```
552+
/// is_fresh(p, len):no_fail=true && is_fresh(q, len):no_fail=true
553+
/// ```
554+
///
555+
/// ```
556+
/// is_fresh(p, len):no_fail=false ||
557+
/// is_fresh(q, len):no_fail=false ||
558+
/// is_fresh(r, len):no_fail=true
559+
/// ```
560+
///
561+
/// ```
562+
/// is_fresh(p, len):no_fail=false ||
563+
/// (
564+
/// is_fresh(q, len):no_fail=true &&
565+
/// (
566+
/// (len>0):no_fail=true ==> is_fresh(r, len):no_fail=true
567+
/// )
568+
/// )
569+
/// ```
570+
/// \param expr The expression to traverse
571+
/// \param no_fail The current no_fail value based on logical context
572+
void disable_may_fail_rec(exprt &expr, bool no_fail)
573+
{
574+
if(expr.id() == ID_side_effect)
575+
{
576+
// Base case: pointer predicate function call
577+
side_effect_exprt &side_effect = to_side_effect_expr(expr);
578+
if(side_effect.get_statement() == ID_function_call)
579+
{
580+
exprt &function = side_effect.operands()[0];
581+
if(function.id() == ID_symbol)
582+
{
583+
const irep_idt &func_name = to_symbol_expr(function).get_identifier();
584+
if(dfcc_is_cprover_pointer_predicate(func_name))
585+
{
586+
function.add_source_location().set("no_fail", no_fail);
587+
}
588+
}
589+
}
590+
return;
591+
}
592+
else if(expr.id() == ID_and)
593+
{
594+
// Shortcutting AND: propagate current no_fail value to all operands
595+
for(auto &op : expr.operands())
596+
{
597+
disable_may_fail_rec(op, no_fail);
598+
}
599+
}
600+
else if(expr.id() == ID_or)
601+
{
602+
// Shortcutting OR: set no_fail=false for all but last operand
603+
auto &ops = expr.operands();
604+
// Process all operands except the last one with no_fail=false
605+
for(std::size_t i = 0; i < ops.size() - 1; ++i)
606+
{
607+
disable_may_fail_rec(ops[i], false);
608+
}
609+
// Process last operand with current no_fail value
610+
if(!ops.empty())
611+
{
612+
disable_may_fail_rec(ops.back(), no_fail);
613+
}
614+
}
615+
else if(expr.id() == ID_implies)
616+
{
617+
// Shortcutting implies: false for antecedent, current value for consequent
618+
INVARIANT(
619+
expr.operands().size() == 2,
620+
"Implication expression must have two operands");
621+
disable_may_fail_rec(expr.operands()[0], false);
622+
disable_may_fail_rec(expr.operands()[1], no_fail);
623+
}
624+
else
625+
{
626+
// bail on other types of expressions
627+
return;
628+
}
629+
}
630+
631+
void disable_may_fail(exprt &expr)
632+
{
633+
disable_may_fail_rec(expr, true);
634+
}
635+
538636
void dfcc_wrapper_programt::encode_requires_clauses()
539637
{
540638
// we use this empty requires write set to check for the absence of side
@@ -556,6 +654,9 @@
556654
{
557655
exprt requires_lmbd =
558656
to_lambda_expr(r).application(contract_lambda_parameters);
657+
658+
// add "no_fail" suffix to predicates required as units
659+
disable_may_fail(requires_lmbd);
559660
source_locationt sl(r.source_location());
560661
if(statement_type == ID_assert)
561662
{
@@ -605,6 +706,9 @@
605706
.application(contract_lambda_parameters)
606707
.with_source_location(e);
607708

709+
// add "no_fail" suffix to unit pointer predicates
710+
disable_may_fail(ensures);
711+
608712
// this also rewrites ID_old expressions to fresh variables
609713
generate_history_variables_initialization(
610714
goto_model.symbol_table, ensures, language_mode, history);

0 commit comments

Comments
 (0)
Please sign in to comment.