Skip to content

Commit 7edced4

Browse files
author
Remi Delmas
committed
CONTRACTS: Front-end: frees clause improvements
Add the following to the front-end: - add `__CPROVER_freeable_t` built-in type - add `__CPROVER_freeable` built-in function - allow calls to `__CPROVER_freeable_t` functions in frees clauses - add `__CPROVER_is_freeable` built-in predicate - add `__CPROVER_is_freed` built-in predicate The predicates are not yet supported in the back-end.
1 parent 0eec25f commit 7edced4

File tree

17 files changed

+327
-4
lines changed

17 files changed

+327
-4
lines changed

doc/cprover-manual/contracts-frees.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,59 @@ When replacing a function call or a loop by a contract, each pointer of the
7878
_frees_ clause is non-deterministically freed after the function call
7979
or after the loop.
8080

81+
# Using functions to specify parametric sets of freeable pointers
82+
83+
Users can define parametric sets of freeable pointers by writing functions that
84+
return the built-in type `__CPROVER_freeable_t` and call the built-in function
85+
`__CPROVER_freeable` or any user-defined function returning
86+
`__CPROVER_freeable_t`:
87+
88+
```c
89+
__CPROVER_freeable_t my_freeable_set(char **arr, size_t size)
90+
{
91+
if (arr && size > 3) {
92+
__CPROVER_freeable(arr[0]);
93+
__CPROVER_freeable(arr[1]);
94+
__CPROVER_freeable(arr[2]);
95+
}
96+
}
97+
```
98+
99+
The built-in function:
100+
101+
```c
102+
__CPROVER_freeable_t __CPROVER_freeable(void *ptr);
103+
```
104+
adds the given pointer to the freeable set described by the enclosing function.
105+
106+
Calls to functions returning `__CPROVER_freeable_t` can then be used as targets
107+
in `__CPROVER_frees` clauses:
108+
109+
```c
110+
void my_function(char **arr, size_t size)
111+
__CPROVER_frees(my_freeable_set(arr, size))
112+
{
113+
...
114+
}
115+
```
116+
117+
# Frees clause related predicates
118+
119+
The predicate:
120+
121+
```c
122+
__CPROVER_bool __CPROVER_is_freeable(void *ptr);
123+
```
124+
can only be used in pre and post conditions, in contract checking or replacement
125+
modes. It returns `true` if and only if the pointer satisfies the preconditions
126+
of the `free` function from `stdlib.h`, that is if and only if the pointer
127+
points to a valid dynamically allocated object and has offset zero.
128+
129+
The predicate:
130+
131+
```c
132+
__CPROVER_bool __CPROVER_is_freed(void *ptr);
133+
```
134+
can only be used in post conditions and returns `true` if and only if the
135+
pointer was freed during the execution of the function or the loop under
136+
analysis.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include <stdlib.h>
2+
3+
// A function defining a conditionally freeable target
4+
__CPROVER_freeable_t
5+
foo_frees(char *arr, const size_t size, const size_t new_size)
6+
{
7+
__CPROVER_freeable(arr);
8+
}
9+
10+
char *foo(char *arr, const size_t size, const size_t new_size)
11+
// clang-format off
12+
// error is_freed cannot be used in preconditions
13+
__CPROVER_requires(!__CPROVER_is_freed(arr))
14+
__CPROVER_requires(__CPROVER_is_freeable(arr))
15+
__CPROVER_assigns(__CPROVER_whole_object(arr))
16+
__CPROVER_frees(foo_frees(arr, size, new_size))
17+
__CPROVER_ensures(
18+
(arr && new_size > size) ==>
19+
__CPROVER_is_fresh(__CPROVER_return_value, new_size))
20+
__CPROVER_ensures(
21+
(arr && new_size > size) ==>
22+
__CPROVER_is_freed(__CPROVER_old(arr)))
23+
__CPROVER_ensures(
24+
!(arr && new_size > size) ==>
25+
__CPROVER_return_value == __CPROVER_old(arr))
26+
// clang-format on
27+
{
28+
if(arr && new_size > size)
29+
{
30+
free(arr);
31+
return malloc(new_size);
32+
}
33+
else
34+
{
35+
return arr;
36+
}
37+
}
38+
39+
int main()
40+
{
41+
size_t size;
42+
size_t new_size;
43+
char *arr = malloc(size);
44+
arr = foo(arr, size, new_size);
45+
return 0;
46+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main.c
3+
--enforce-contract foo
4+
^main.c.* error: __CPROVER_is_freed is not allowed in preconditions.$
5+
^CONVERSION ERROR$
6+
^EXIT=(1|64)$
7+
^SIGNAL=0$
8+
--
9+
--
10+
This test checks that the front end rejects __CPROVER_is_freed in preconditions.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include <stdlib.h>
2+
3+
// A function defining a conditionally freeable target
4+
void
5+
foo_frees(char *arr, const size_t size, const size_t new_size)
6+
{
7+
__CPROVER_freeable(arr);
8+
}
9+
10+
char *foo(char *arr, const size_t size, const size_t new_size)
11+
// clang-format off
12+
__CPROVER_requires(__CPROVER_is_freeable(arr))
13+
__CPROVER_assigns(__CPROVER_whole_object(arr))
14+
__CPROVER_frees(foo_frees(arr, size, new_size))
15+
__CPROVER_ensures(
16+
(arr && new_size > size) ==>
17+
__CPROVER_is_fresh(__CPROVER_return_value, new_size))
18+
__CPROVER_ensures(
19+
(arr && new_size > size) ==>
20+
__CPROVER_is_freed(__CPROVER_old(arr)))
21+
__CPROVER_ensures(
22+
!(arr && new_size > size) ==>
23+
__CPROVER_return_value == __CPROVER_old(arr))
24+
// clang-format on
25+
{
26+
if(arr && new_size > size)
27+
{
28+
free(arr);
29+
return malloc(new_size);
30+
}
31+
else
32+
{
33+
return arr;
34+
}
35+
}
36+
37+
int main()
38+
{
39+
size_t size;
40+
size_t new_size;
41+
char *arr = malloc(size);
42+
arr = foo(arr, size, new_size);
43+
return 0;
44+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
main.c
3+
--enforce-contract foo
4+
^main.c.* error: expecting __CPROVER_freeable_t return type for function foo_frees called in frees clause$
5+
^CONVERSION ERROR$
6+
^EXIT=(1|64)$
7+
^SIGNAL=0$
8+
--
9+
--
10+
This test checks that the front-end rejects non-__CPROVER_freeable_t-typed
11+
function calls in frees clauses.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include <stdlib.h>
2+
3+
// A function defining a conditionally freeable target
4+
__CPROVER_freeable_t
5+
foo_frees(char *arr, const size_t size, const size_t new_size)
6+
{
7+
__CPROVER_freeable(arr);
8+
}
9+
10+
char *foo(char *arr, const size_t size, const size_t new_size)
11+
// clang-format off
12+
__CPROVER_requires(__CPROVER_is_freeable(arr))
13+
__CPROVER_assigns(__CPROVER_whole_object(arr))
14+
__CPROVER_frees(foo_frees(arr, size, new_size))
15+
__CPROVER_ensures(
16+
(arr && new_size > size) ==>
17+
__CPROVER_is_fresh(__CPROVER_return_value, new_size))
18+
__CPROVER_ensures(
19+
(arr && new_size > size) ==>
20+
__CPROVER_is_freed(__CPROVER_old(arr)))
21+
__CPROVER_ensures(
22+
!(arr && new_size > size) ==>
23+
__CPROVER_return_value == __CPROVER_old(arr))
24+
// clang-format on
25+
{
26+
if(arr && new_size > size)
27+
{
28+
free(arr);
29+
return malloc(new_size);
30+
}
31+
else
32+
{
33+
return arr;
34+
}
35+
}
36+
37+
int main()
38+
{
39+
size_t size;
40+
size_t new_size;
41+
char *arr = malloc(size);
42+
arr = foo(arr, size, new_size);
43+
return 0;
44+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
CORE
2+
main.c
3+
--enforce-contract foo
4+
^\[foo.postcondition.\d+\] line \d+ Check ensures clause: FAILURE$
5+
^VERIFICATION FAILED$
6+
^EXIT=10$
7+
^SIGNAL=0$
8+
--
9+
--
10+
This test checks that the front end parses and typchecks correct uses of:
11+
- __CPROVER_freeable_t function calls as frees clause targets
12+
- the predicate __CPROVER_freeable
13+
- the predicate __CPROVER_is_freeable
14+
- the predicate __CPROVER_is_freed
15+
16+
The post condition of the contract is expected to fail because the predicates
17+
have no interpretation in the back-end yet.

regression/contracts/frees-clause-for-loop-fail/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
CORE
22
main.c
33
--apply-loop-contracts
4-
^main.c.*: error: frees clause target must be a pointer-typed expression$
4+
^main.c.* error: frees clause target must be a pointer-typed expression or a call to a function returning __CPROVER_freeable_t$
55
^CONVERSION ERROR$
66
^EXIT=(1|64)$
77
^SIGNAL=0$

regression/contracts/frees-clause-function-fail/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
CORE
22
main.c
33
--enforce-contract foo
4-
^main.c.*: error: frees clause target must be a pointer-typed expression$
4+
^main.c.* error: frees clause target must be a pointer-typed expression or a call to a function returning __CPROVER_freeable_t$
55
^CONVERSION ERROR$
66
^EXIT=(1|64)$
77
^SIGNAL=0$

regression/contracts/frees-clause-while-loop-fail/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
CORE
22
main.c
33
--apply-loop-contracts
4-
^main.c.*: error: frees clause target must be a pointer-typed expression$
4+
^main.c.* error: frees clause target must be a pointer-typed expression or a call to a function returning __CPROVER_freeable_t$
55
^CONVERSION ERROR$
66
^EXIT=(1|64)$
77
^SIGNAL=0$

src/ansi-c/ansi_c_internal_additions.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,14 @@ void ansi_c_internal_additions(std::string &code)
231231
CPROVER_PREFIX "assignable_t " CPROVER_PREFIX "object_from(void *ptr);\n"
232232
// Declares the whole object pointer to by ptr
233233
CPROVER_PREFIX "assignable_t " CPROVER_PREFIX "whole_object(void *ptr);\n"
234+
// Type that describes sets of freeable pointers
235+
"typedef void " CPROVER_PREFIX "freeable_t;\n"
236+
// Declares a pointer as freeable
237+
CPROVER_PREFIX "freeable_t " CPROVER_PREFIX "freeable(void *ptr);\n"
238+
// True iff ptr satisfies the preconditions of the free stdlib function
239+
CPROVER_PREFIX "bool " CPROVER_PREFIX "is_freeable(void *ptr);\n"
240+
// True iff ptr was freed during function execution or loop execution
241+
CPROVER_PREFIX "bool " CPROVER_PREFIX "is_freed(void *ptr);\n"
234242
"\n";
235243
// clang-format on
236244

src/ansi-c/c_typecheck_base.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,27 @@ void c_typecheck_baset::typecheck_declaration(
777777
CPROVER_PREFIX "loop_entry is not allowed in preconditions.");
778778
};
779779

780+
auto check_freed = [&](const exprt &expr) {
781+
const irep_idt id = CPROVER_PREFIX "is_freed";
782+
783+
auto pred = [&](const exprt &expr) {
784+
if(!can_cast_expr<side_effect_expr_function_callt>(expr))
785+
return false;
786+
787+
auto &called_function =
788+
to_side_effect_expr_function_call(expr).function();
789+
return can_cast_expr<symbol_exprt>(called_function) &&
790+
(to_symbol_expr(called_function).get_identifier() == id);
791+
};
792+
793+
if(!has_subexpr(expr, pred))
794+
return;
795+
796+
error().source_location = expr.source_location();
797+
error() << id2string(id) + " is not allowed in preconditions." << eom;
798+
throw 0;
799+
};
800+
780801
auto check_return_value = [&](const exprt &expr) {
781802
const irep_idt id = CPROVER_PREFIX "return_value";
782803

@@ -851,6 +872,7 @@ void c_typecheck_baset::typecheck_declaration(
851872
typecheck_expr(requires);
852873
implicit_typecast_bool(requires);
853874
check_history_expr(requires);
875+
check_freed(requires);
854876
check_return_value(requires);
855877
check_return_value(requires);
856878
lambda_exprt lambda{temporary_parameter_symbols, requires};

src/ansi-c/c_typecheck_code.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1032,11 +1032,43 @@ void c_typecheck_baset::typecheck_spec_frees_target(exprt &target)
10321032
// an expression with pointer-type without side effects
10331033
throw_on_side_effects(target);
10341034
}
1035+
else if(can_cast_expr<side_effect_expr_function_callt>(target))
1036+
{
1037+
// A call to a freeable_t function symbol without other side effects
1038+
const auto &funcall = to_side_effect_expr_function_call(target);
1039+
1040+
if(!can_cast_expr<symbol_exprt>(funcall.function()))
1041+
{
1042+
error().source_location = target.source_location();
1043+
error() << "function pointer calls not allowed in frees clauses" << eom;
1044+
throw 0;
1045+
}
1046+
1047+
bool has_freeable_type =
1048+
(type.id() == ID_empty) &&
1049+
(type.get(ID_C_typedef) == CPROVER_PREFIX "freeable_t");
1050+
if(!has_freeable_type)
1051+
{
1052+
error().source_location = target.source_location();
1053+
error() << "expecting " CPROVER_PREFIX
1054+
"freeable_t return type for function " +
1055+
id2string(
1056+
to_symbol_expr(funcall.function()).get_identifier()) +
1057+
" called in frees clause"
1058+
<< eom;
1059+
throw 0;
1060+
}
1061+
1062+
for(const auto &argument : funcall.arguments())
1063+
throw_on_side_effects(argument);
1064+
}
10351065
else
10361066
{
10371067
// anything else is rejected
10381068
error().source_location = target.source_location();
1039-
error() << "frees clause target must be a pointer-typed expression" << eom;
1069+
error() << "frees clause target must be a pointer-typed expression or a "
1070+
"call to a function returning " CPROVER_PREFIX "freeable_t"
1071+
<< eom;
10401072
throw 0;
10411073
}
10421074
}

src/ansi-c/c_typecheck_expr.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2248,6 +2248,30 @@ exprt c_typecheck_baset::do_special_functions(
22482248
typecheck_function_call_arguments(expr);
22492249
return nil_exprt();
22502250
}
2251+
else if(identifier == CPROVER_PREFIX "is_freeable")
2252+
{
2253+
if(expr.arguments().size() != 1)
2254+
{
2255+
error().source_location = f_op.source_location();
2256+
error() << CPROVER_PREFIX "is_freeable expects one operand; "
2257+
<< expr.arguments().size() << "provided." << eom;
2258+
throw 0;
2259+
}
2260+
typecheck_function_call_arguments(expr);
2261+
return nil_exprt();
2262+
}
2263+
else if(identifier == CPROVER_PREFIX "is_freed")
2264+
{
2265+
if(expr.arguments().size() != 1)
2266+
{
2267+
error().source_location = f_op.source_location();
2268+
error() << CPROVER_PREFIX "is_freed expects one operand; "
2269+
<< expr.arguments().size() << "provided." << eom;
2270+
throw 0;
2271+
}
2272+
typecheck_function_call_arguments(expr);
2273+
return nil_exprt();
2274+
}
22512275
else if(identifier == CPROVER_PREFIX "same_object")
22522276
{
22532277
if(expr.arguments().size()!=2)

0 commit comments

Comments
 (0)