Skip to content

Commit 030f804

Browse files
author
Remi Delmas
committed
CONTRACTS: support bounded user defined memory-predicates
1 parent 2b40da3 commit 030f804

26 files changed

+1058
-100
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#include <stdbool.h>
2+
#include <stdlib.h>
3+
4+
typedef struct buffer_t
5+
{
6+
size_t size;
7+
char *arr;
8+
char *cursor;
9+
} buffer_t;
10+
11+
typedef struct double_buffer_t
12+
{
13+
buffer_t *first;
14+
buffer_t *second;
15+
} double_buffer_t;
16+
17+
bool is_sized_array(char *arr, size_t size)
18+
{
19+
return __CPROVER_is_fresh(arr, size);
20+
}
21+
22+
bool is_ranged_cursor(char *cursor, char *arr, size_t size)
23+
{
24+
void *lb = arr;
25+
void *ub = arr + size;
26+
__CPROVER_pointer_in_range_dfcc(lb, cursor, ub);
27+
}
28+
29+
bool is_buffer(buffer_t *b)
30+
{
31+
return __CPROVER_is_fresh(b, sizeof(*b)) && (0 < b->size && b->size <= 10) &&
32+
is_sized_array(b->arr, b->size) &&
33+
is_ranged_cursor(b->cursor, b->arr, b->size);
34+
}
35+
36+
bool is_double_buffer(double_buffer_t *b)
37+
{
38+
return __CPROVER_is_fresh(b, sizeof(*b)) && is_buffer(b->first) &&
39+
is_buffer(b->second);
40+
}
41+
42+
typedef struct list_t
43+
{
44+
int value;
45+
struct list_t *next;
46+
} list_t;
47+
48+
// true iff list of len max_len with values in [-10,10]
49+
bool is_list(size_t max_len, list_t *l)
50+
{
51+
if(max_len == 0)
52+
return l == NULL;
53+
else
54+
return __CPROVER_is_fresh(l, sizeof(*l)) && -10 <= l->value &&
55+
l->value <= 10 && is_list(max_len - 1, l->next);
56+
}
57+
58+
int foo(list_t *l, double_buffer_t *b)
59+
// clang-format off
60+
__CPROVER_requires(is_list(3, l))
61+
__CPROVER_requires(is_double_buffer(b))
62+
__CPROVER_ensures(-28 <= __CPROVER_return_value &&
63+
__CPROVER_return_value <= 50)
64+
// clang-format on
65+
{
66+
// access the assumed data structure
67+
return l->value + l->next->value + l->next->next->value + b->first->size +
68+
b->second->size;
69+
}
70+
71+
int main()
72+
{
73+
list_t *l;
74+
double_buffer_t *b;
75+
int return_value = foo(l, b);
76+
assert(-28 <= return_value && return_value <= 50);
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
main.c
3+
--dfcc main --enforce-contract foo _ --pointer-check
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
Checks if user-defined predicates work;

src/ansi-c/c_typecheck_expr.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -2342,6 +2342,17 @@ exprt c_typecheck_baset::do_special_functions(
23422342
// returning nil leaves the call expression in place
23432343
return nil_exprt();
23442344
}
2345+
else if(identifier == CPROVER_PREFIX "pointer_in_range_dfcc")
2346+
{
2347+
// experimental feature for DFCC contracts encodings -- do not use
2348+
if(expr.arguments().size() != 3)
2349+
{
2350+
error().source_location = f_op.source_location();
2351+
error() << "pointer_in_range expects three arguments" << eom;
2352+
throw 0;
2353+
}
2354+
return nil_exprt();
2355+
}
23452356
else if(identifier == CPROVER_PREFIX "same_object")
23462357
{
23472358
if(expr.arguments().size()!=2)

src/ansi-c/cprover_builtin_headers.h

+2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ __CPROVER_bool __CPROVER_is_freeable(const void *mem);
5050
__CPROVER_bool __CPROVER_was_freed(const void *mem);
5151
__CPROVER_bool __CPROVER_is_fresh(const void *mem, __CPROVER_size_t size);
5252
__CPROVER_bool __CPROVER_obeys_contract(void (*)(void), void (*)(void));
53+
// same as pointer_in_range with experimental support in contracts
54+
__CPROVER_bool __CPROVER_pointer_in_range_dfcc(void *lb, void *ptr, void *ub);
5355
void __CPROVER_old(const void *);
5456
void __CPROVER_loop_entry(const void *);
5557

src/ansi-c/library/cprover_contracts.c

+44
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,50 @@ __CPROVER_HIDE:;
12831283
}
12841284
}
12851285

1286+
__CPROVER_size_t __VERIFIER_nondet_size();
1287+
1288+
__CPROVER_bool __CPROVER_contracts_pointer_in_range_dfcc(
1289+
void *lb,
1290+
void **ptr,
1291+
void *ub,
1292+
__CPROVER_contracts_write_set_ptr_t write_set)
1293+
{
1294+
__CPROVER_HIDE:;
1295+
__CPROVER_assert(
1296+
(write_set != 0) & ((write_set->assume_requires_ctx == 1) |
1297+
(write_set->assert_requires_ctx == 1) |
1298+
(write_set->assume_ensures_ctx == 1) |
1299+
(write_set->assert_ensures_ctx == 1)),
1300+
"__CPROVER_pointer_in_range_dfcc is used only in requires or ensures "
1301+
"clauses");
1302+
__CPROVER_assert(__CPROVER_r_ok(lb, 0), "lb pointer must be valid");
1303+
__CPROVER_assert(__CPROVER_r_ok(ub, 0), "ub pointer must be valid");
1304+
__CPROVER_assert(
1305+
__CPROVER_same_object(lb, ub),
1306+
"lb and ub pointers must have the same object");
1307+
__CPROVER_ssize_t lb_offset = __CPROVER_POINTER_OFFSET(lb);
1308+
__CPROVER_ssize_t ub_offset = __CPROVER_POINTER_OFFSET(ub);
1309+
__CPROVER_assert(
1310+
lb_offset <= ub_offset, "lb and ub pointers must be ordered");
1311+
if(write_set->assume_requires_ctx | write_set->assume_ensures_ctx)
1312+
{
1313+
// add nondet offset
1314+
__CPROVER_size_t offset = __VERIFIER_nondet_size();
1315+
1316+
// this cast is safe because we prove that ub and lb are ordered
1317+
__CPROVER_size_t max_offset = (__CPROVER_size_t)(ub_offset - lb_offset);
1318+
__CPROVER_assume(offset <= max_offset);
1319+
*ptr = (char *)lb + offset;
1320+
return 1;
1321+
}
1322+
else /* write_set->assert_requires_ctx | write_set->assert_ensures_ctx */
1323+
{
1324+
__CPROVER_ssize_t offset = __CPROVER_POINTER_OFFSET(ptr);
1325+
return __CPROVER_same_object(lb, ptr) && lb_offset <= offset &&
1326+
offset <= ub_offset;
1327+
}
1328+
}
1329+
12861330
/// \brief Returns the start address of the conditional address range found at
12871331
/// index \p idx in \p set->contract_assigns.
12881332
void *__CPROVER_contracts_write_set_havoc_get_assignable_target(

src/goto-instrument/Makefile

+2
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ SRC = accelerate/accelerate.cpp \
2222
contracts/dynamic-frames/dfcc_is_fresh.cpp \
2323
contracts/dynamic-frames/dfcc_is_freeable.cpp \
2424
contracts/dynamic-frames/dfcc_obeys_contract.cpp \
25+
contracts/dynamic-frames/dfcc_lift_memory_predicates.cpp \
2526
contracts/dynamic-frames/dfcc_instrument.cpp \
2627
contracts/dynamic-frames/dfcc_spec_functions.cpp \
2728
contracts/dynamic-frames/dfcc_contract_functions.cpp \
2829
contracts/dynamic-frames/dfcc_wrapper_program.cpp \
2930
contracts/dynamic-frames/dfcc_contract_handler.cpp \
3031
contracts/dynamic-frames/dfcc_swap_and_wrap.cpp \
32+
contracts/dynamic-frames/dfcc_pointer_in_range.cpp \
3133
contracts/dynamic-frames/dfcc.cpp \
3234
contracts/havoc_assigns_clause_targets.cpp \
3335
contracts/instrument_spec_assigns.cpp \

src/goto-instrument/contracts/doc/developer/contracts-dev-arch.md

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Each of these translation passes is implemented in a specific class:
2828
:-------------------------------|:---------------------------------------
2929
@ref dfcc_instrumentt | Implements @ref contracts-dev-spec-dfcc for @ref goto_functiont, @ref goto_programt, or subsequences of instructions of @ref goto_programt
3030
@ref dfcc_is_fresht | Implements @ref contracts-dev-spec-is-fresh
31+
@ref dfcc_pointer_in_ranget | Implements @ref contracts-dev-spec-pointer-in-range
3132
@ref dfcc_is_freeablet | Implements @ref contracts-dev-spec-is-freeable
3233
@ref dfcc_obeys_contractt | Implements @ref contracts-dev-spec-obeys-contract
3334
@ref dfcc_spec_functionst | Implements @ref contracts-dev-spec-spec-rewriting
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# Rewriting Declarative Assign and Frees Specification Functions {#contracts-dev-spec-memory-predicates-rewriting}
2+
3+
Back to top @ref contracts-dev-spec
4+
5+
@tableofcontents
6+
7+
The C extensions for contract specification provide three pointer-related memory
8+
predicates:
9+
10+
```c
11+
// Holds iff ptr is pointing to an object distinct to all objects pointed to by
12+
// other __CPROVER_is_fresh occurrences in the contract's pre and post conditions
13+
__CPROVER_bool __CPROVER_is_fresh(void *ptr, size_t size);
14+
15+
// Holds iff ptr is a valid pointer pointing between lb and ub pointers.
16+
// \pre lb and ub must be valid pointers pointing in the same object.
17+
__CPROVER_bool __CPROVER_in_range(void *ptr, void *lb, void *ub);
18+
19+
// Holds iff the function pointer \p fptr points to a function satisfying
20+
// \p contract.
21+
__CPROVER_bool __CPROVER_obeys_contract(void (*fptr)(void), void (*contract)(void));
22+
```
23+
24+
Users are free to express pre and post conditions using these predicates,
25+
and we let users define their own functions in terms of these predicates.
26+
27+
28+
For instance, one could write a predicate defining linked lists of at most `len`
29+
elements as follows:
30+
31+
```c
32+
struct list_t {
33+
int value;
34+
struct list_t *next;
35+
};
36+
37+
bool is_list(struct list_t *ptr, size_t len)
38+
{
39+
if(ptr)
40+
return len > 0 &&
41+
__CPROVER_is_fresh(ptr, sizeof(list_t)) && is_list(ptr->next, len-1));
42+
else
43+
return 0;
44+
}
45+
```
46+
47+
One can also simply describe finite structures:
48+
49+
```c
50+
struct buffer_t {
51+
size_t size;
52+
char *arr;
53+
char *cursor;
54+
};
55+
56+
struct two_buffers_t {
57+
struct buffer_t *left;
58+
struct buffer_t *right;
59+
};
60+
61+
bool is_buffer(struct buffer_t *ptr)
62+
{
63+
return
64+
__CPROVER_is_fresh(ptr, sizeof(struct buffer_t)) &&
65+
__CPROVER_is_fresh(ptr->arr, ptr->size) &&
66+
__CPROVER_in_range(ptr->cursor, ptr->arr, ptr->arr + size-1);
67+
}
68+
69+
bool is_two_buffers(struct two_buffers_t *ptr)
70+
{
71+
return
72+
__CPROVER_is_fresh(ptr, sizeof(struct two_buffers_t)) &&
73+
is_buffer(ptr->left) &&
74+
is_buffer(ptr->right);
75+
}
76+
```
77+
78+
By rewriting such user-defined predicate we achieve two things:
79+
1. in assumption contexts, evaluating the predicate allocates the data stucture
80+
specified by the predicate;
81+
1. in assertion contexts, evaluating the predicate checks that a given pointer
82+
satisfies the predicate definiton;
83+
84+
To be able to achieve point 1., the user-defined functions need to be
85+
transformed into functions that take their pointer arguments by reference
86+
instead of by value, so that enforcing the assumption described by the
87+
predicate can be done by updating the pointer in place using a side effect.
88+
89+
## Collecting user-defined memory predicates {#contracts-dev-spec-memory-predicate-collect}
90+
91+
We first run a pass that collects all user-defined functions that are defined
92+
in terms of one of the three memory predicates using this fixpoint algorithm:
93+
94+
```
95+
functions_to_lift =
96+
{`__CPROVER_is_fresh`, `__CPROVER_in_range`, `__CPROVER_obeys_contract`};
97+
98+
updated = true;
99+
while(updated)
100+
{
101+
updated = false;
102+
for(function : goto_model)
103+
{
104+
if(!contains(function_to_lift, function) &&
105+
calls_one_of(function, functions_to_lift) &&)
106+
{
107+
rewrite(function);
108+
functions_to_lift.insert(function);
109+
updated = true;
110+
}
111+
}
112+
}
113+
```
114+
115+
## Rewriting user-defined memory predicates {#contracts-dev-spec-memory-predicate-collect}
116+
117+
Rewriting a user-defined memory predicate consists in:
118+
- Adding an extra write set parameter to the predicate signature;
119+
- identifying the subset of its pointer-typed arguments which eventually gets
120+
passed to a built-in or user-defined memory predicate in a lifted parameter position; Let's call this subset `Lifted`;
121+
- in the signature of the function, lift the types of all parameters of `Lifted` into pointer-to-pointer types;
122+
- rewrite all occurences of a lifted parameter `p` into `*p`;
123+
- add an `address_of` operator to all arguments passed to a lifted parameter of a memory predicate;
124+
- pass the write set as ghost parameter to function calls;
125+
- rewrite calls to built-in predicates to call their library implementation;
126+
127+
128+
This yields the following result
129+
```c
130+
bool is_list(
131+
struct list_t **ptr,
132+
size_t len,
133+
__CPROVER_contracts_write_set_ptr_t __write_set)
134+
{
135+
if(*ptr)
136+
{
137+
return (len > 0 &&
138+
__CPROVER_contracts_is_fresh(ptr, sizeof(list_t), __write_set) &&
139+
is_list(&((*ptr)->next), len-1, __write_set));
140+
}
141+
else
142+
}
143+
return 0;
144+
}
145+
}
146+
```
147+
148+
```c
149+
bool is_buffer(struct buffer_t **ptr,
150+
__CPROVER_contracts_write_set_ptr_t __write_set)
151+
{
152+
return
153+
__CPROVER_contracts_is_fresh(
154+
ptr, sizeof(struct buffer_t), __write_set) &&
155+
__CPROVER_contracts_is_fresh(
156+
&((*ptr)->arr), (*ptr)->size, __write_set) &&
157+
__CPROVER_contracts_in_range(
158+
&((*ptr)->cursor), (*ptr)->arr, (*ptr)->arr + size-1, __write_set);
159+
}
160+
161+
bool is_two_buffers(struct two_buffers_t **ptr,
162+
__CPROVER_contracts_write_set_ptr_t __write_set)
163+
{
164+
return
165+
__CPROVER_contracts_is_fresh(
166+
ptr, sizeof(struct two_buffers_t), __write_set) &&
167+
is_buffer(&((*ptr)->left), __write_set) &&
168+
is_buffer(&((*ptr)->right), __write_set);
169+
}
170+
```
171+
172+
---
173+
Prev | Next
174+
:-----|:------
175+
@ref contracts-dev-spec-spec-rewriting | @ref contracts-dev-spec-dfcc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Rewriting Calls to the __CPROVER_pointer_in_range_dfcc Predicate {#contracts-dev-spec-pointer-in-range}
2+
3+
Back to top @ref contracts-dev-spec
4+
5+
@tableofcontents
6+
7+
In goto programs encoding pre or post conditions (generated from the contract
8+
clauses) and in all user-defined functions, we simply replace calls to
9+
`__CPROVER_pointer_in_range_dfcc` with calls to the library implementation
10+
11+
```c
12+
__CPROVER_contracts_pointer_in_range_dfcc(
13+
void *lb,
14+
void **ptr,
15+
void *ub,
16+
__CPROVER_contracts_write_set_ptr_t write_set);
17+
```
18+
19+
This function implements the `__CPROVER_pointer_in_range_dfcc` behaviour in all
20+
possible contexts (contract checking vs replacement, requires vs ensures clause
21+
context, as described by the flags carried by the write set parameter).
22+
23+
---
24+
Prev | Next
25+
:-----|:------
26+
@ref contracts-dev | @ref contracts-dev-spec-reminder

0 commit comments

Comments
 (0)