Skip to content

Commit 8f8d169

Browse files
Merge pull request #7401 from remi-delmas-3000/contracts-lift-memory-predicates
CONTRACTS: Support bounded user defined memory-predicates
2 parents 9a84d62 + c144993 commit 8f8d169

File tree

64 files changed

+2036
-333
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+2036
-333
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,32 @@
1+
#include <stdbool.h>
12
#include <stdlib.h>
23

3-
// returns the index at which the write was performed if any
4-
// -1 otherwise
5-
int foo(char *a, int size)
4+
bool foo(char *a, size_t size, size_t *out)
65
// clang-format off
7-
__CPROVER_requires(0 <= size && size <= __CPROVER_max_malloc_size)
86
__CPROVER_requires(a == NULL || __CPROVER_is_fresh(a, size))
9-
__CPROVER_assigns(a: __CPROVER_object_whole(a))
7+
__CPROVER_requires(__CPROVER_is_fresh(out, sizeof(*out)))
8+
__CPROVER_assigns(a: __CPROVER_object_from(a), *out)
109
__CPROVER_ensures(
11-
a && __CPROVER_return_value >= 0 ==> a[__CPROVER_return_value] == 0)
10+
a && __CPROVER_return_value ==>
11+
(0 <= *out && *out < size &&
12+
a[*out] == 0)
13+
)
1214
// clang-format on
1315
{
14-
if(!a)
15-
return -1;
16-
int i;
17-
if(0 <= i && i < size)
16+
size_t i;
17+
if(a && 0 <= i && i < size)
1818
{
1919
a[i] = 0;
20-
return i;
20+
*out = i;
21+
return true;
2122
}
22-
return -1;
23+
return false;
2324
}
2425

25-
int main()
26+
void main()
2627
{
27-
size_t size;
2828
char *a;
29-
foo(a, size);
30-
return 0;
29+
size_t size;
30+
size_t *out;
31+
foo(a, size, out);
3132
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
CORE
22
main.c
3-
--dfcc main --enforce-contract foo _ --malloc-may-fail --malloc-fail-null
4-
^\[foo.assigns.\d+\] line 19 Check that a\[\(signed long (long )?int\)i\] is assignable: SUCCESS$
3+
--malloc-may-fail --malloc-fail-null --dfcc main --enforce-contract foo
4+
^\[foo.assigns.\d+\] line \d+ Check that a\[\(signed long (long )?int\)i\] is assignable: SUCCESS$
5+
^\[foo.assigns.\d+\] line \d+ Check that \*out is assignable: SUCCESS$
56
^EXIT=0$
67
^SIGNAL=0$
78
^VERIFICATION SUCCESSFUL$
89
--
9-
This test checks that objects of size zero or __CPROVER_max_malloc_size
10-
do not cause spurious falsifications in assigns clause instrumentation
11-
in contract enforcement mode.
10+
This test checks assuming is_fresh(ptr, size) with a non-deterministic size
11+
does not result in spurious falsifications when checking assigns clauses.
12+
An implicit strict upper bound of __CPROVER_max_malloc_size is imposed on the size by
13+
__CPROVER_is_fresh which guarantees the absence of pointer overflow when computing
14+
the address `ptr + size`.
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,41 @@
1+
#include <stdbool.h>
12
#include <stdlib.h>
23

3-
// returns the index at which the write was performed if any
4-
// -1 otherwise
5-
int foo(char *a, int size)
4+
bool foo(char *a, size_t size, size_t *out)
65
// clang-format off
7-
__CPROVER_requires(0 <= size && size <= __CPROVER_max_malloc_size)
8-
__CPROVER_requires(a == NULL || __CPROVER_rw_ok(a, size))
9-
__CPROVER_assigns(__CPROVER_object_whole(a))
10-
__CPROVER_ensures(
11-
a && __CPROVER_return_value >= 0 ==> a[__CPROVER_return_value] == 0)
6+
__CPROVER_requires(a == NULL || __CPROVER_is_fresh(a, size))
7+
__CPROVER_requires(__CPROVER_is_fresh(out, sizeof(*out)))
8+
__CPROVER_assigns(a: __CPROVER_object_from(a), *out)
9+
__CPROVER_ensures(
10+
a && __CPROVER_return_value ==>
11+
(0 <= *out && *out < size &&
12+
a[*out] == 0)
13+
)
1214
// clang-format on
1315
{
14-
if(!a)
15-
return -1;
16-
int i;
17-
if(0 <= i && i < size)
16+
size_t i;
17+
if(a && 0 <= i && i < size)
1818
{
1919
a[i] = 0;
20-
return i;
20+
*out = i;
21+
return true;
2122
}
22-
return -1;
23+
return false;
2324
}
2425

25-
int main()
26+
int nondet_int();
27+
28+
void bar(int size)
29+
{
30+
size_t out;
31+
char *a = malloc(size);
32+
bool res = foo(a, size, &out);
33+
if(a && res)
34+
__CPROVER_assert(a[out] == 0, "expecting SUCCESS");
35+
}
36+
37+
void main()
2638
{
27-
int size;
28-
if(size < 0)
29-
size = 0;
30-
if(size > __CPROVER_max_malloc_size)
31-
size = __CPROVER_max_malloc_size;
32-
char *a = malloc(size * sizeof(*a));
33-
int res = foo(a, size);
34-
if(a && res >= 0)
35-
__CPROVER_assert(a[res] == 0, "expecting SUCCESS");
36-
return 0;
39+
size_t size;
40+
bar(size);
3741
}

regression/contracts-dfcc/assigns-replace-malloc-zero/test.desc

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
CORE
22
main.c
3-
--dfcc main --replace-call-with-contract foo _ --malloc-may-fail --malloc-fail-null
3+
--dfcc main --replace-call-with-contract foo --malloc-may-fail --malloc-fail-null
44
^EXIT=0$
55
^SIGNAL=0$
6-
\[main\.assertion\.1\] line 35 expecting SUCCESS: SUCCESS$
6+
\[bar\.assertion\.\d+\] line \d+ expecting SUCCESS: SUCCESS$
77
^VERIFICATION SUCCESSFUL$
88
--
99
This test checks that objects of size zero or of __CPROVER_max_malloc_size
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,38 @@
11
#include <assert.h>
22
#include <stdbool.h>
3-
#include <stddef.h>
4-
#include <stdint.h>
53
#include <stdlib.h>
64

7-
bool dummy_for_definitions(int *n)
8-
{
9-
assert(__CPROVER_is_fresh(&n, sizeof(int)));
10-
int *x = malloc(sizeof(int));
11-
}
12-
135
bool ptr_ok(int *x)
146
{
15-
return *x < 5;
16-
}
17-
18-
/*
19-
Here are the meanings of the predicates:
20-
21-
static _Bool __foo_memory_map[__CPROVER_constant_infinity_uint];
22-
23-
bool __foo_requires_is_fresh(void **elem, size_t size) {
24-
*elem = malloc(size);
25-
if (!*elem || (__foo_memory_map[__CPROVER_POINTER_OBJECT(*elem)] != 0)) return false;
26-
27-
__foo_memory_map[__CPROVER_POINTER_OBJECT(*elem)] = 1;
28-
return true;
7+
return 0 < *x && *x < 5;
298
}
309

31-
bool __foo_ensures_is_fresh(void *elem, size_t size) {
32-
bool ok = (__foo_memory_map[__CPROVER_POINTER_OBJECT(elem)] == 0 &&
33-
__CPROVER_r_ok(elem, size));
34-
__foo_memory_map[__CPROVER_POINTER_OBJECT(elem)] = 1;
35-
return ok;
36-
}
37-
38-
39-
_Bool __call_foo_requires_is_fresh(void *elem, size_t size) {
40-
_Bool r_ok = __CPROVER_r_ok(elem, size);
41-
if (!__CPROVER_r_ok(elem, size) ||
42-
__foo_memory_map[__CPROVER_POINTER_OBJECT(elem)]) return 0;
43-
__foo_memory_map[__CPROVER_POINTER_OBJECT(elem)] = 1;
44-
return 1;
45-
}
46-
47-
// In the calling context, we assume freshness means new
48-
// allocation from within the function.
49-
bool __call_foo_ensures_is_fresh(void **elem, size_t size) {
50-
*elem = malloc(size);
51-
return (*elem != NULL);
52-
}
53-
*/
54-
5510
bool return_ok(int ret_value, int *x)
5611
{
57-
int a;
58-
a = *x;
59-
return ret_value == *x + 5;
12+
return ret_value == (*x + 5);
6013
}
6114

62-
// The 'ensures' __CPROVER_is_fresh test is unnecessary, but left in just to test the function is working correctly.
63-
// If you remove the negation, the program will fail, because 'x' is not fresh.
64-
65-
int foo(int *x, int y) __CPROVER_assigns(*x)
66-
__CPROVER_requires(__CPROVER_is_fresh(x, sizeof(int)) && *x > 0 && ptr_ok(x))
67-
__CPROVER_ensures(
68-
!ptr_ok(x) && !__CPROVER_is_fresh(x, sizeof(int)) &&
69-
return_ok(__CPROVER_return_value, x))
15+
int foo(int *x)
16+
// clang-format off
17+
__CPROVER_requires(__CPROVER_is_fresh(x, sizeof(int)))
18+
__CPROVER_requires(ptr_ok(x))
19+
__CPROVER_assigns(*x)
20+
__CPROVER_ensures(!ptr_ok(x))
21+
__CPROVER_ensures(return_ok(__CPROVER_return_value, x))
22+
// clang-format on
7023
{
7124
*x = *x + 4;
72-
int y = *x + 5;
7325
return *x + 5;
7426
}
7527

76-
int main()
28+
void main()
7729
{
78-
int *n = malloc(sizeof(int));
79-
assert(__CPROVER_r_ok(n, sizeof(int)));
80-
*n = 3;
81-
int o = foo(n, 10);
82-
assert(o >= 10 && o == *n + 5);
83-
return 0;
30+
int x;
31+
int *_x = &x;
32+
if(ptr_ok(_x))
33+
{
34+
int ret_val = foo(_x);
35+
assert(!ptr_ok(_x));
36+
assert(return_ok(ret_val, _x));
37+
}
8438
}

regression/contracts-dfcc/is_unique_01_replace/test.desc

+2-5
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,5 @@ main.c
66
^VERIFICATION SUCCESSFUL$
77
--
88
--
9-
This test checks that verification succeeds if only expressions inside the assigns clause are assigned within the function.
10-
11-
Note: For all 'enforce' tests, nothing can be assumed about the return value of the function (as the function call is not replaced at this point).
12-
13-
To make such assumptions would cause verification to fail.
9+
This test shows that replacing a call by a contract establishes the
10+
postconditions of the contracts.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#include <stdlib.h>
2+
3+
void foo(char *arr, size_t size)
4+
// clang-format off
5+
__CPROVER_requires(__CPROVER_is_fresh(arr, size))
6+
__CPROVER_assigns(__CPROVER_object_from(arr))
7+
// clang-format on
8+
{
9+
assert(__CPROVER_same_object(arr, arr + size));
10+
if(size > 0)
11+
{
12+
arr[0] = 0;
13+
arr[size - 1] = 0;
14+
}
15+
}
16+
17+
int main()
18+
{
19+
char *arr;
20+
size_t size;
21+
foo(arr, size);
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
main.c
3+
--dfcc main --enforce-contract foo _ --pointer-check --pointer-primitive-check --pointer-overflow-check
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
This tests shows how assuming that a pointer is fresh with a nondeterministic
10+
size implicitly assumes that the size is strictly less than the max malloc
11+
size. This is in order to ensure that the internal representation used for
12+
assignable address ranges cannot suffer pointer overflows.
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.

0 commit comments

Comments
 (0)