Back to top Code Contracts Transformation Specification
Swapping-and-Wrapping Functions
The first operation one can do with contracts is to check if a function satisfies a contract.
Let us consider a contract c
, a function foo
and a proof harness main
.
int c(int *a, int *b)
__CPROVER_requires(R)
__CPROVER_ensures(E)
__CPROVER_assigns(A)
__CPROVER_frees(F)
;
int foo(int *a, int *b)
{
}
{
int *a = ...;
int *b = ...;
foo(a, b);
}
int main(int argc, char *argv[])
In order to check the contract c
against foo
in the context set up by the harness main
, we apply a swap-and-wrap transformation to the function foo
:
The swapping step consists in creating a fresh function foo_swapped
with the same interface as foo
and swapping the body of foo
into foo_swapped
, while instrumenting it for frame condition checking against A and F.
int foo_swapped(int *a, int *b)
{
}
The wrapping step consists in creating a new body for foo
where the swapped function gets called between precondition and postcondition checking instructions generated from the contract:
int foo(int *a, int *b)
{
int reval = foo_swapped(a, b);
return retval;
}
After the swap-and-wrap transformation is applied, the main
function calls the contract-checking wrapper. Analysing this model with CBMC will effectively check that the initial foo
(now foo_swapped
), under the assumption that the contract preconditions hold, satisfies the postconditions and the frame conditions of the contract.
static bool foo_check_started = false;
static bool foo_check_completed = false;
ret_t foo(foo-parameters, write_set_t caller_write_set) {
assert(!foo_check_started, "recursive calls to foo not allowed");
assert(!foo_check_completed, "foo can only be called once from the harness");
foo_check_started = true;
assume(contract::requires(foo_params, requires_write_set));
assert(__CPROVER_contracts_write_set_allocated_deallocated_is_empty(requires_write_set));
hist1_t hist1 = ...;
hist2_t hist2 = ...;
contract::assigns(contract_write_set, empty_write_set);
contract::frees(contract_write_set, empty_write_set);
ret_t retval;
retval = foo_swapped(foo_params, contact_write_set);
assert(contract::ensures(foo_params, ensures_write_set));
assert(__CPROVER_contracts_write_set_allocated_deallocated_is_empty(ensures_write_set));
foo_check_completed = true;
return retval;
}
void __CPROVER_contracts_write_set_release(__CPROVER_contracts_write_set_ptr_t set)
Releases resources used by set.
void __CPROVER_contracts_write_set_create(__CPROVER_contracts_write_set_ptr_t set, __CPROVER_size_t contract_assigns_size, __CPROVER_size_t contract_frees_size, __CPROVER_bool assume_requires_ctx, __CPROVER_bool assert_requires_ctx, __CPROVER_bool assume_ensures_ctx, __CPROVER_bool assert_ensures_ctx, __CPROVER_bool allow_allocate, __CPROVER_bool allow_deallocate)
Initialises a __CPROVER_contracts_write_set_t object.
Runtime representation of a write set.
Wrapping Recursive Functions
If the function to be checked is potentially recursive, we generate the wrapper function body such that the first call trigger the contract checking, and any subsequent call triggers the contract replacement logic as described in Replacing a Function by a Contract :
static bool foo_check_started = false;
static bool foo_check_completed = false;
ret_t foo(foo_params, write_set_t caller_write_set) {
assert(!foo_check_completed, "foo can only be called once from the harness");
if(!foo_check_started) {
return retval;
} else {
return retval;
}
}