Skip to content

Commit c144993

Browse files
author
Remi Delmas
committed
CONTRACTS: support pointer_in_range_dfcc predicate
Adds support for pointer_in_range_dfcc in ensures and requires clauses and user-defined predicates. This is a temporary workaround to the fact that pointer_in_range is lowered by the front-end.
1 parent a465b98 commit c144993

File tree

23 files changed

+451
-11
lines changed

23 files changed

+451
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#include <stdbool.h>
2+
#include <stdlib.h>
3+
4+
// A vtable is a struct containing function pointers
5+
typedef struct vtable_s
6+
{
7+
int (*f)(void);
8+
} vtable_t;
9+
10+
int return_0()
11+
{
12+
return 0;
13+
}
14+
15+
int return_1()
16+
{
17+
return 1;
18+
}
19+
20+
// we have two possible vtables
21+
vtable_t vtable_0 = {.f = &return_0};
22+
vtable_t vtable_1 = {.f = &return_1};
23+
24+
// foo takes a vtable and calls f
25+
int foo(vtable_t *vtable)
26+
// clang-format off
27+
__CPROVER_requires(
28+
// vtable is nondeterministic pointer to one of two objects
29+
__CPROVER_pointer_in_range_dfcc(&vtable_0, vtable, &vtable_0) ||
30+
__CPROVER_pointer_in_range_dfcc(&vtable_1, vtable, &vtable_1))
31+
__CPROVER_ensures(__CPROVER_return_value == 0 || __CPROVER_return_value == 1)
32+
// clang-format on
33+
{
34+
CALL:
35+
return vtable->f();
36+
}
37+
38+
int main()
39+
{
40+
vtable_t *vtable;
41+
foo(vtable);
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main.c
3+
--restrict-function-pointer foo.CALL/return_0,return_1 --nondet-static-exclude vtable_0 --nondet-static-exclude vtable_1 --dfcc main --enforce-contract foo _ --pointer-check --pointer-primitive-check
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
Demonstrates the use of __CPROVER_pointer_in_range_dfcc to define nondeterministic
10+
pointers to one or more targets.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include <assert.h>
2+
#include <stdlib.h>
3+
4+
void foo(char *arr, size_t size, char *cur)
5+
// clang-format off
6+
__CPROVER_requires(
7+
(0 < size && size < __CPROVER_max_malloc_size) &&
8+
__CPROVER_is_fresh(arr, size) &&
9+
__CPROVER_pointer_in_range_dfcc(arr, cur, arr + size))
10+
__CPROVER_ensures(__CPROVER_pointer_in_range_dfcc(arr, cur, arr + size))
11+
// clang-format on
12+
{
13+
assert(__CPROVER_r_ok(arr, size));
14+
assert(__CPROVER_same_object(arr, cur));
15+
assert(arr <= cur && cur <= arr + size);
16+
}
17+
18+
int main()
19+
{
20+
char *arr;
21+
size_t size;
22+
char *cur;
23+
foo(arr, size, cur);
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main.c
3+
--dfcc main --enforce-contract foo _ --pointer-check --pointer-primitive-check
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
Checks that assuming pointer-in-range as preconditions establishes a state
10+
in which the definition of the predicate holds.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <assert.h>
2+
#include <stdlib.h>
3+
4+
void foo(char *arr, size_t size, char *cur)
5+
// clang-format off
6+
__CPROVER_requires(
7+
(0 < size && size < __CPROVER_max_malloc_size) &&
8+
__CPROVER_is_fresh(arr, size) &&
9+
__CPROVER_pointer_in_range_dfcc(arr, cur, arr + size))
10+
__CPROVER_ensures(__CPROVER_pointer_in_range_dfcc(
11+
arr, cur /*, arr + size missing arg */))
12+
// clang-format on
13+
{
14+
assert(__CPROVER_r_ok(arr, size));
15+
assert(__CPROVER_same_object(arr, cur));
16+
assert(arr <= cur && cur <= arr + size);
17+
}
18+
19+
int main()
20+
{
21+
char *arr;
22+
size_t size;
23+
char *cur;
24+
foo(arr, size, cur);
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main.c
3+
--dfcc main --enforce-contract foo _ --pointer-check --pointer-primitive-check
4+
^.*error: __CPROVER_pointer_in_range_dfcc expects three arguments$
5+
^EXIT=(1|64)$
6+
^SIGNAL=0$
7+
^CONVERSION ERROR$
8+
--
9+
--
10+
Checks that badly typed occurrences of __CPROVER_pointer_in_range_dfcc are detected.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <assert.h>
2+
#include <stdlib.h>
3+
4+
void foo(char *arr, size_t size, char *cur)
5+
// clang-format off
6+
__CPROVER_requires(
7+
(0 < size && size < __CPROVER_max_malloc_size) &&
8+
__CPROVER_is_fresh(arr, size) &&
9+
__CPROVER_pointer_in_range_dfcc(arr, cur, arr + size))
10+
__CPROVER_ensures(__CPROVER_pointer_in_range_dfcc(
11+
arr, cur, 2 /* wrong type arg */))
12+
// clang-format on
13+
{
14+
assert(__CPROVER_r_ok(arr, size));
15+
assert(__CPROVER_same_object(arr, cur));
16+
assert(arr <= cur && cur <= arr + size);
17+
}
18+
19+
int main()
20+
{
21+
char *arr;
22+
size_t size;
23+
char *cur;
24+
foo(arr, size, cur);
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main.c
3+
--dfcc main --enforce-contract foo _ --pointer-check --pointer-primitive-check
4+
^.*error: __CPROVER_pointer_in_range_dfcc expects pointer-typed arguments$
5+
^EXIT=(1|64)$
6+
^SIGNAL=0$
7+
^CONVERSION ERROR$
8+
--
9+
--
10+
Checks that badly typed occurrences of __CPROVER_pointer_in_range_dfcc are detected.

Diff for: src/ansi-c/c_typecheck_expr.cpp

+23
Original file line numberDiff line numberDiff line change
@@ -2334,6 +2334,29 @@ exprt c_typecheck_baset::do_special_functions(
23342334
// returning nil leaves the call expression in place
23352335
return nil_exprt();
23362336
}
2337+
else if(identifier == CPROVER_PREFIX "pointer_in_range_dfcc")
2338+
{
2339+
// same as pointer_in_range with experimental support for DFCC contracts
2340+
// -- do not use
2341+
if(expr.arguments().size() != 3)
2342+
{
2343+
throw invalid_source_file_exceptiont{
2344+
CPROVER_PREFIX "pointer_in_range_dfcc expects three arguments",
2345+
expr.source_location()};
2346+
}
2347+
2348+
for(const auto &arg : expr.arguments())
2349+
{
2350+
if(arg.type().id() != ID_pointer)
2351+
{
2352+
throw invalid_source_file_exceptiont{
2353+
CPROVER_PREFIX
2354+
"pointer_in_range_dfcc expects pointer-typed arguments",
2355+
arg.source_location()};
2356+
}
2357+
}
2358+
return nil_exprt();
2359+
}
23372360
else if(identifier == CPROVER_PREFIX "same_object")
23382361
{
23392362
if(expr.arguments().size()!=2)

Diff for: 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

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

+45
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,51 @@ __CPROVER_HIDE:;
12921292
}
12931293
}
12941294

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

Diff for: src/goto-instrument/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ SRC = accelerate/accelerate.cpp \
2020
contracts/dynamic-frames/dfcc_utils.cpp \
2121
contracts/dynamic-frames/dfcc_library.cpp \
2222
contracts/dynamic-frames/dfcc_is_fresh.cpp \
23+
contracts/dynamic-frames/dfcc_pointer_in_range.cpp \
2324
contracts/dynamic-frames/dfcc_is_freeable.cpp \
2425
contracts/dynamic-frames/dfcc_obeys_contract.cpp \
2526
contracts/dynamic-frames/dfcc_lift_memory_predicates.cpp \

Diff for: 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_lift_memory_predicatest | Implements @ref contracts-dev-spec-memory-predicates-rewriting
3233
@ref dfcc_is_freeablet | Implements @ref contracts-dev-spec-is-freeable
3334
@ref dfcc_obeys_contractt | Implements @ref contracts-dev-spec-obeys-contract

Diff for: src/goto-instrument/contracts/doc/developer/contracts-dev-spec-dfcc-instrument.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ Calls to `__CPROVER_is_fresh` are rewritten as described in
171171
Calls to `__CPROVER_obeys_contract` are rewritten as described in
172172
@subpage contracts-dev-spec-obeys-contract
173173
174-
Calls to `__CPROVER_obeys_contract` are rewritten as described in @subpage contracts-dev-spec-obeys-contract
174+
Calls to `__CPROVER_pointer_in_range_dfcc` are rewritten as described in
175+
@subpage contracts-dev-spec-pointer-in-range
175176
176177
For all other function or function pointer calls, we proceed as follows.
177178

Diff for: src/goto-instrument/contracts/doc/developer/contracts-dev-spec-memory-predicates-rewriting.md

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ predicates:
1212
// other __CPROVER_is_fresh occurrences in the contract's pre and post conditions
1313
__CPROVER_bool __CPROVER_is_fresh(void *ptr, size_t size);
1414

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_pointer_in_range_dfcc(void *lb, void *ptr, void *ub);
18+
1519
// Holds iff the function pointer \p fptr points to a function satisfying
1620
// \p contract.
1721
__CPROVER_bool __CPROVER_obeys_contract(void (*fptr)(void), void (*contract)(void));
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_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)