Skip to content

Commit 8653f0b

Browse files
author
Remi Delmas
committed
CONTRACTS: replace calls to pure contracts globally
Adds a CLI switch `--replace-pure-contracts` to goto-instrument to replace calls to pure contracts globally when.
1 parent d103094 commit 8653f0b

File tree

9 files changed

+169
-1
lines changed

9 files changed

+169
-1
lines changed

doc/cprover-manual/contracts-functions.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,27 @@ instruments the code to use the function contract in place of the function
143143
implementation wherever is invoked, and the third one runs CBMC to check the
144144
program using contracts.
145145

146+
## Pure contracts
147+
148+
A pure contract is a function definition equipped with a contract and
149+
for which the body consists of a single call to the function
150+
`__CPROVER_pure_contract`:
151+
152+
```C
153+
ret_t foo(param_t params)
154+
__CPROVER_requires(R)
155+
__CPROVER_assigns(A)
156+
__CPROVER_ensures(E)
157+
{
158+
__CPROVER_pure_contract();
159+
}
160+
```
161+
Using `__CPROVER_pure_contract();` in any other way results in a failed
162+
assertion.
163+
164+
Calls to pure contracts are globally replaced by goto-instrument when the
165+
flag `--replace-pure-contracts` is specified on the command line.
166+
146167
## Additional Resources
147168
148169
- [Requires \& Ensures Clauses](contracts-requires-and-ensures.md)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include <stdlib.h>
2+
3+
char nondet_char();
4+
5+
char foo(char *arr, size_t size)
6+
// clang-format off
7+
__CPROVER_requires(
8+
0 < size && size < __CPROVER_max_malloc_size &&
9+
__CPROVER_is_fresh(arr, size))
10+
__CPROVER_assigns(arr[0], arr[size-1])
11+
__CPROVER_ensures(
12+
arr[0] == __CPROVER_return_value && arr[size-1] == __CPROVER_return_value)
13+
// clang-format on
14+
{
15+
char retval = nondet_char();
16+
arr[0] = retval;
17+
arr[size - 1] = retval;
18+
__CPROVER_pure_contract();
19+
return retval;
20+
}
21+
22+
char bar(char *arr, size_t size)
23+
// clang-format off
24+
__CPROVER_requires(
25+
0 < size && size < __CPROVER_max_malloc_size &&
26+
__CPROVER_is_fresh(arr, size))
27+
__CPROVER_assigns(arr[0], arr[size-1])
28+
__CPROVER_ensures(
29+
arr[0] == __CPROVER_return_value && arr[size-1] == __CPROVER_return_value)
30+
// clang-format on
31+
{
32+
return foo(arr, size);
33+
}
34+
35+
int main()
36+
{
37+
size_t size;
38+
char *arr;
39+
bar(arr, size);
40+
return 0;
41+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#include <stdlib.h>
2+
3+
char foo(char *arr, size_t size)
4+
// clang-format off
5+
__CPROVER_requires(
6+
0 < size && size < __CPROVER_max_malloc_size &&
7+
__CPROVER_is_fresh(arr, size))
8+
__CPROVER_assigns(arr[0], arr[size - 1])
9+
__CPROVER_ensures(
10+
arr[0] == __CPROVER_return_value && arr[size - 1] == __CPROVER_return_value)
11+
// clang-format on
12+
{
13+
__CPROVER_pure_contract();
14+
}
15+
16+
char bar(char *arr, size_t size)
17+
// clang-format off
18+
__CPROVER_requires(
19+
0 < size && size < __CPROVER_max_malloc_size &&
20+
__CPROVER_is_fresh(arr, size))
21+
__CPROVER_assigns(arr[0], arr[size - 1])
22+
__CPROVER_ensures(
23+
arr[0] == __CPROVER_return_value && arr[size - 1] == __CPROVER_return_value)
24+
// clang-format on
25+
26+
{
27+
return foo(arr, size);
28+
}
29+
30+
int main()
31+
{
32+
size_t size;
33+
char *arr;
34+
bar(arr, size);
35+
return 0;
36+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
main-fail.c
3+
--replace-pure-contracts --enforce-contract bar
4+
^main-fail.c.* error: incorrect use of __CPROVER_pure_contract, function contains other instructions$
5+
^CONVERSION ERROR$
6+
^EXIT=(1|64)$
7+
^SIGNAL=0$
8+
--
9+
--
10+
This test demonstrates that an improper use of __CPROVER_pure_contract
11+
is detected and causes a conversion error.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
CORE
2+
main.c
3+
--replace-pure-contracts --enforce-contract bar
4+
^\[foo.pure_contract.\d+\] line \d+ all calls replaced: (UNREACHABLE|SUCCESS)$
5+
^VERIFICATION SUCCESSFUL$
6+
^EXIT=0$
7+
^SIGNAL=0$
8+
--
9+
--
10+
This test demonstrates that calls to functions tagged as pure contracts
11+
replaced by the contract when the command line switch --replace-pure-contracts
12+
is used. foo is a pure contract, bar has the same contract and calls foo.
13+
foo gets replaced in bar, and bar satisfies its contract.
14+
The assertion `foo.pure_contract` is UNREACHABLE or SUCCESS when
15+
all calls to foo have actually been replaced by foo's contract.

src/goto-instrument/contracts/contracts.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,18 @@ class inlining_decoratort : public message_handlert
133133
}
134134
};
135135

136+
bool code_contractst::is_pure_contract(const irep_idt &function_id)
137+
{
138+
for(const auto &pragma : ns.lookup(function_id).location.get_pragmas())
139+
{
140+
const auto s = id2string(pragma.first);
141+
if(!s.compare(0, 13, "pure_contract"))
142+
return true;
143+
}
144+
145+
return false;
146+
}
147+
136148
void code_contractst::check_apply_loop_contracts(
137149
const irep_idt &function_name,
138150
goto_functionst::goto_functiont &goto_function,
@@ -1537,6 +1549,17 @@ void code_contractst::check_all_functions_found(
15371549
}
15381550
}
15391551

1552+
void code_contractst::replace_pure_contracts()
1553+
{
1554+
std::set<std::string> to_replace;
1555+
for(const auto &it : goto_functions.function_map)
1556+
{
1557+
if(is_pure_contract(it.first))
1558+
to_replace.insert(id2string(it.first));
1559+
}
1560+
replace_calls(to_replace);
1561+
}
1562+
15401563
void code_contractst::replace_calls(const std::set<std::string> &to_replace)
15411564
{
15421565
if(to_replace.empty())

src/goto-instrument/contracts/contracts.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ Date: February 2016
3636
" --apply-loop-contracts\n" \
3737
" check and use loop contracts when provided\n"
3838

39+
#define FLAG_REPLACE_PURE_CONTRACTS "replace-pure-contracts"
40+
#define HELP_REPLACE_PURE_CONTRACTS \
41+
" --replace-pure-contracts\n" \
42+
" replace calls to all functions defined as " \
43+
" pure contracts with their contracts\n"
44+
3945
#define FLAG_REPLACE_CALL "replace-call-with-contract"
4046
#define HELP_REPLACE_CALL \
4147
" --replace-call-with-contract <fun>\n" \
@@ -62,6 +68,10 @@ class code_contractst
6268
{
6369
}
6470

71+
/// \brief Replace all calls to functions defined pure contracts by their
72+
/// contracts
73+
void replace_pure_contracts();
74+
6575
/// Throws an exception if some function `functions` is found in the program.
6676
void check_all_functions_found(const std::set<std::string> &functions) const;
6777

@@ -128,6 +138,10 @@ class code_contractst
128138

129139
std::unordered_set<irep_idt> summarized;
130140

141+
/// \brief Returns true iff a function is a pure contract and must
142+
/// automatically be replaced by its contract
143+
bool is_pure_contract(const irep_idt &function_id);
144+
131145
/// \brief Enforce contract of a single function
132146
void enforce_contract(const irep_idt &function);
133147

src/goto-instrument/goto_instrument_parse_options.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,8 @@ void goto_instrument_parse_optionst::instrument_goto_program()
11331133

11341134
if(
11351135
cmdline.isset(FLAG_LOOP_CONTRACTS) || cmdline.isset(FLAG_REPLACE_CALL) ||
1136-
cmdline.isset(FLAG_ENFORCE_CONTRACT))
1136+
cmdline.isset(FLAG_ENFORCE_CONTRACT) ||
1137+
cmdline.isset(FLAG_REPLACE_PURE_CONTRACTS))
11371138
{
11381139
do_indirect_call_and_rtti_removal();
11391140
code_contractst contracts(goto_model, log);
@@ -1150,6 +1151,10 @@ void goto_instrument_parse_optionst::instrument_goto_program()
11501151
// first replacement then enforcement. We rely on contract replacement
11511152
// and inlining of sub-function calls to properly annotate all
11521153
// assignments.
1154+
1155+
if(cmdline.isset(FLAG_REPLACE_PURE_CONTRACTS))
1156+
contracts.replace_pure_contracts();
1157+
11531158
contracts.replace_calls(to_replace);
11541159
contracts.enforce_contracts(to_enforce);
11551160

@@ -1844,6 +1849,7 @@ void goto_instrument_parse_optionst::help()
18441849
"Code contracts:\n"
18451850
HELP_LOOP_CONTRACTS
18461851
HELP_REPLACE_CALL
1852+
HELP_REPLACE_PURE_CONTRACTS
18471853
HELP_ENFORCE_CONTRACT
18481854
"\n"
18491855
"Other options:\n"

src/goto-instrument/goto_instrument_parse_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ Author: Daniel Kroening, [email protected]
9696
"(horn)(skip-loops):(model-argc-argv):" \
9797
"(" FLAG_LOOP_CONTRACTS ")" \
9898
"(" FLAG_REPLACE_CALL "):" \
99+
"(" FLAG_REPLACE_PURE_CONTRACTS ")" \
99100
"(" FLAG_ENFORCE_CONTRACT "):" \
100101
"(show-threaded)(list-calls-args)" \
101102
"(undefined-function-is-assume-false)" \

0 commit comments

Comments
 (0)