Skip to content

Commit ca29e29

Browse files
author
Remi Delmas
committed
CONTRACTS: user doc for obeys_contact predicates
1 parent b57335a commit ca29e29

10 files changed

+265
-3
lines changed

src/goto-instrument/contracts/doc/developer/contracts-dev-spec-reminder.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ __CPROVER_frees(F)
2727
2828
- A `__CPROVER_requires` clause (@ref contracts-requires-ensures) specifies a
2929
precondition as boolean expression R that may only depend on program globals,
30-
function parameters, [memory predicates](@ref contracts-memory-predicates) and
30+
function parameters, [memory predicates](@ref contracts-memory-predicates),
31+
[function pointer predicates](@ref contracts-function-pointer-predicates) and
3132
deterministic, side effect-free function calls;
3233
- A `__CPROVER_ensures` clause (@ref contracts-requires-ensures) specifies a
3334
postcondition as boolean expression E that may only depend on program globals,
3435
function parameters, [memory predicates](@ref contracts-memory-predicates),
36+
[function pointer predicates](@ref contracts-function-pointer-predicates),
3537
deterministic, side effect-free function calls,
3638
[history variables](@ref contracts-history-variables), and the special
3739
variable `__CPROVER_return_value`;

src/goto-instrument/contracts/doc/user/contracts-assigns.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,5 +510,6 @@ int foo()
510510
- @ref contracts-assigns
511511
- @ref contracts-frees
512512
- @ref contracts-memory-predicates
513+
- @ref contracts-function-pointer-predicates
513514
- @ref contracts-history-variables
514515
- @ref contracts-quantifiers

src/goto-instrument/contracts/doc/user/contracts-decreases.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,5 +187,6 @@ then the weakest possible invariant (i.e, `true`) is used to model an arbitrary
187187
- @ref contracts-assigns
188188
- @ref contracts-frees
189189
- @ref contracts-memory-predicates
190+
- @ref contracts-function-pointer-predicates
190191
- @ref contracts-history-variables
191192
- @ref contracts-quantifiers
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
# Function Pointer Predicates {#contracts-function-pointer-predicates}
2+
3+
Back to @ref contracts-user
4+
5+
@tableofcontents
6+
7+
8+
## Syntax
9+
10+
The function contract language offers the following predicate to specify
11+
preconditions and postconditions about function pointers in _requires clauses_
12+
and _ensures clauses_.
13+
14+
```c
15+
bool __CPROVER_obeys_contract(void (*f)(void), void (*c)(void));
16+
```
17+
18+
Informally, this predicate holds iff function pointer `f` is known to satisfy
19+
contract `c`.
20+
21+
### Parameters
22+
23+
The predicate `__CPROVER_obeys_contract` takes two function pointers as arguments.
24+
The first function pointer must be an lvalue, the second parameter must be a pointer
25+
to a pure contract symbol.
26+
27+
### Return Value
28+
29+
It returns a `bool` value, indicating whether the function pointer is known to
30+
satisfy contract `c`.
31+
32+
## Semantics
33+
34+
To illustrate the semantics of the predicate, let's consider the example below.
35+
36+
The `arr_fun_contract` specifies the class of functions that take a non-`NULL`
37+
array `arr` of size `size` as input and initialise its first element to zero.
38+
39+
The function `foo` takes a possibly `NULL` array `arr` of given `size` and a
40+
possibly `NULL` opaque function pointer `arr_fun`, assumed to satisfy the
41+
contract `arr_fun_contract`.
42+
43+
The function `foo` checks some condition and then calls `arr_fun`.
44+
When `arr_fun` if called, the preconditions of its contract `arr_fun_contract`
45+
will be checked, write set inclusion with respect to the contract of `foo` will
46+
be checked, and its postconditions will be assumed, allowing to establish the
47+
postconditions of `foo`.
48+
49+
```c
50+
// \file fptr.c
51+
#include <stdlib.h>
52+
#include <stdbool.h>
53+
54+
// Type of functions that manipulate arrays of a given size
55+
typedef void (*arr_fun_t)(char *arr, size_t size);
56+
57+
// A contract for the arr_fun_t type
58+
void arr_fun_contract(char *arr, size_t size)
59+
__CPROVER_requires(size > 0 && __CPROVER_is_fresh(arr, size))
60+
__CPROVER_assigns(arr[0])
61+
__CPROVER_ensures(arr[0] == 0)
62+
;
63+
64+
// Uses a function pointer satisfying arr_fun_contract
65+
int foo(char *arr, size_t size, arr_fun_t arr_fun)
66+
__CPROVER_requires(arr ==> __CPROVER_is_fresh(arr, size))
67+
__CPROVER_requires(arr_fun ==> __CPROVER_obeys_contract(arr_fun, arr_fun_contract))
68+
__CPROVER_assigns(arr && size > 0 && arr_fun : arr[0])
69+
__CPROVER_ensures(__CPROVER_return_value ==> (arr[0] == 0))
70+
{
71+
if (arr && size > 0 && arr_fun) {
72+
CALL:
73+
arr_fun(arr, size);
74+
return true;
75+
}
76+
return false;
77+
}
78+
79+
void main()
80+
{
81+
size_t size;
82+
char *arr;
83+
arr_fun_t arr_fun;
84+
foo(arr, size, arr_fun);
85+
}
86+
```
87+
88+
### Enforcement
89+
90+
To check that `foo` satisfies its contract, we compile, instrument and analyse the program as follows:
91+
92+
```bash
93+
goto-cc fptr.c
94+
goto-instrument \
95+
--restrict-function-pointer foo.CALL/arr_fun_contract \
96+
--dfcc main \
97+
--enforce-contract foo \
98+
a.out b.out
99+
cbmc b.out
100+
```
101+
102+
The function pointer restriction directive is needed to inform CBMC that we only
103+
expect the contract function `arr_fun_contratt` to be called where `arr_fun`
104+
is called.
105+
106+
We get the following analysis result:
107+
108+
```bash
109+
...
110+
111+
fptr.c function arr_fun_contract
112+
[arr_fun_contract.precondition.1] line 8 Check requires clause of contract contract::arr_fun_contract for function arr_fun_contract: SUCCESS
113+
[arr_fun_contract.assigns.4] line 7 Check that the assigns clause of contract::arr_fun_contract is included in the caller's assigns clause: SUCCESS
114+
[arr_fun_contract.frees.1] line 7 Check that the frees clause of contract::arr_fun_contract is included in the caller's frees clause: SUCCESS
115+
116+
fptr.c function foo
117+
[foo.postcondition.1] line 20 Check ensures clause of contract contract::foo for function foo: SUCCESS
118+
[foo.pointer_dereference.1] line 24 dereferenced function pointer must be arr_fun_contract: SUCCESS
119+
120+
...
121+
122+
** 0 of 72 failed (1 iterations)
123+
VERIFICATION SUCCESSFUL
124+
```
125+
126+
### Replacement
127+
128+
Let's now consider an extended example with two new functions:
129+
- The function `get_arr_fun` returns a pointer to a function satisfying
130+
`arr_fun_contract`;
131+
- The function bar allocates an array and uses `get_arr_fun` and `foo`
132+
to initialise it;
133+
134+
Our goal is to check that `bar` satisfies its contract, assuming that `foo`
135+
satisfies its contract and that `get_arr_fun` satisfies its contract.
136+
137+
```c
138+
#include <stdlib.h>
139+
140+
// Type of functions that manipulate arrays of a given size
141+
typedef void (*arr_fun_t)(char *arr, size_t size);
142+
143+
// A contract for the arr_fun_t type
144+
void arr_fun_contract(char *arr, size_t size)
145+
__CPROVER_requires(size > 0 && __CPROVER_is_fresh(arr, size))
146+
__CPROVER_assigns(arr[0])
147+
__CPROVER_ensures(arr[0] == 0)
148+
;
149+
150+
// Uses a function pointer satisfying arr_fun_contract
151+
int foo(char *arr, size_t size, arr_fun_t arr_fun)
152+
__CPROVER_requires(
153+
arr ==> __CPROVER_is_fresh(arr, size))
154+
__CPROVER_requires(
155+
arr_fun ==> __CPROVER_obeys_contract(arr_fun, arr_fun_contract))
156+
__CPROVER_assigns(arr && size > 0 && arr_fun : arr[0])
157+
__CPROVER_ensures(__CPROVER_return_value ==> (arr[0] == 0))
158+
{
159+
if (arr && size > 0 && arr_fun) {
160+
CALL:
161+
arr_fun(arr, size);
162+
return 1;
163+
}
164+
return 0;
165+
}
166+
167+
// Returns a function that satisfies arr_fun_contract
168+
arr_fun_t get_arr_fun()
169+
__CPROVER_ensures(
170+
__CPROVER_obeys_contract(__CPROVER_return_value, arr_fun_contract))
171+
;
172+
173+
// Creates an array and uses get_arr_fun and foo to initialise it
174+
char *bar(size_t size)
175+
__CPROVER_ensures(
176+
__CPROVER_return_value ==> size > 0 &&
177+
__CPROVER_is_fresh(__CPROVER_return_value, size) &&
178+
__CPROVER_return_value[0] == 0)
179+
{
180+
if (size > 0) {
181+
char *arr;
182+
arr = malloc(size);
183+
if (arr && foo(arr, size, get_arr_fun()))
184+
return arr;
185+
186+
return NULL;
187+
}
188+
return NULL;
189+
}
190+
191+
void main()
192+
{
193+
size_t size;
194+
char *arr = bar(size);
195+
}
196+
```
197+
We compile, instrument and analyse the program as follows:
198+
199+
```bash
200+
goto-cc fptr.c
201+
goto-instrument \
202+
--dfcc main \
203+
--enforce-contract bar \
204+
--replace-call-with-contract foo \
205+
--replace-call-with-contract get_arr_fun \
206+
a.out b.out
207+
cbmc b.out
208+
```
209+
210+
And obtain the following results:
211+
212+
```bash
213+
...
214+
215+
fptr.c function get_arr_fun
216+
[get_arr_fun.assigns.1] line 13 Check that the assigns clause of contract::get_arr_fun is included in the caller's assigns clause: SUCCESS
217+
[get_arr_fun.frees.1] line 13 Check that the frees clause of contract::get_arr_fun is included in the caller's frees clause: SUCCESS
218+
219+
fptr.c function bar
220+
[bar.postcondition.1] line 36 Check ensures clause of contract contract::bar for function bar: SUCCESS
221+
222+
fptr.c function foo
223+
[foo.assigns.7] line 18 Check that the assigns clause of contract::foo is included in the caller's assigns clause: SUCCESS
224+
[foo.frees.1] line 18 Check that the frees clause of contract::foo is included in the caller's frees clause: SUCCESS
225+
[foo.precondition.1] line 20 Check requires clause of contract contract::foo for function foo: SUCCESS
226+
[foo.precondition.2] line 22 Check requires clause of contract contract::foo for function foo: SUCCESS
227+
...
228+
229+
** 0 of 80 failed (1 iterations)
230+
VERIFICATION SUCCESSFUL
231+
```
232+
233+
Proving that `bar` satisfies its contract.
234+
235+
## Additional Resources
236+
237+
- @ref contracts-functions
238+
- @ref contracts-requires-ensures
239+
- @ref contracts-assigns
240+
- @ref contracts-frees
241+
- @ref contracts-loops
242+
- @ref contracts-loop-invariants
243+
- @ref contracts-decreases
244+
- @ref contracts-assigns
245+
- @ref contracts-frees
246+
- @ref contracts-memory-predicates
247+
- @ref contracts-function-pointer-predicates
248+
- @ref contracts-history-variables
249+
- @ref contracts-quantifiers

src/goto-instrument/contracts/doc/user/contracts-functions.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ The following built-in constructs can also be used with functions contracts:
4848
- @ref contracts-history-variables allow to refer to past versions of function parameters,
4949
- @ref contracts-quantifiers allow to express quantified formulas,
5050
- @ref contracts-memory-predicates allow to describe simple heap structures;
51+
- @ref contracts-function-pointer-predicates allow to describe function pointer properties;
52+
5153
5254
In our example, the developer may require from the caller to properly allocate
5355
all arguments, thus, pointers must be valid. We can specify the preconditions of
@@ -162,5 +164,6 @@ program using contracts.
162164
- @ref contracts-assigns
163165
- @ref contracts-frees
164166
- @ref contracts-memory-predicates
167+
- @ref contracts-function-pointer-predicates
165168
- @ref contracts-history-variables
166169
- @ref contracts-quantifiers

src/goto-instrument/contracts/doc/user/contracts-history-variables.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,6 @@ TODO: Document `__CPROVER_loop_entry` and `__CPROVER_loop_old`.
5656
- @ref contracts-assigns
5757
- @ref contracts-frees
5858
- @ref contracts-memory-predicates
59+
- @ref contracts-function-pointer-predicates
5960
- @ref contracts-history-variables
6061
- @ref contracts-quantifiers

src/goto-instrument/contracts/doc/user/contracts-loop-invariants.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,5 +165,6 @@ A few things to note here:
165165
- @ref contracts-assigns
166166
- @ref contracts-frees
167167
- @ref contracts-memory-predicates
168+
- @ref contracts-function-pointer-predicates
168169
- @ref contracts-history-variables
169170
- @ref contracts-quantifiers

src/goto-instrument/contracts/doc/user/contracts-memory-predicates.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,5 +132,6 @@ int foo()
132132
- @ref contracts-assigns
133133
- @ref contracts-frees
134134
- @ref contracts-memory-predicates
135+
- @ref contracts-function-pointer-predicates
135136
- @ref contracts-history-variables
136137
- @ref contracts-quantifiers

src/goto-instrument/contracts/doc/user/contracts-quantifiers.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,5 +104,6 @@ int bar_sat(int *arr, int len)
104104
- @ref contracts-assigns
105105
- @ref contracts-frees
106106
- @ref contracts-memory-predicates
107+
- @ref contracts-function-pointer-predicates
107108
- @ref contracts-history-variables
108109
- @ref contracts-quantifiers

src/goto-instrument/contracts/doc/user/contracts-requires-ensures.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ the conjunction of the _ensures_ clauses, or `true` if none are specified.
2828

2929
Both _requires_ clauses and _ensures_ clauses take a Boolean expression over the
3030
arguments of a function and/or global variables. The expression can include
31-
calls to CBMC built-in functions or to
32-
[Memory Predicates](@ref contracts-memory-predicates)).
31+
calls to CBMC built-in functions, to
32+
[Memory Predicates](@ref contracts-memory-predicates)) or to
33+
[function pointer predicates](@ref contracts-function-pointer-predicates).
3334
User-defined functions can also be called inside _requires_ clauses as long as
3435
they are deterministic and do not have any side-effects
3536
(the absence of side effects is checked by the tool). In addition, _ensures_
@@ -187,5 +188,6 @@ int foo()
187188
- @ref contracts-assigns
188189
- @ref contracts-frees
189190
- @ref contracts-memory-predicates
191+
- @ref contracts-function-pointer-predicates
190192
- @ref contracts-history-variables
191193
- @ref contracts-quantifiers

0 commit comments

Comments
 (0)