CBMC
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
Requires and Ensures Clauses

Back to Code Contracts User Documentation

Syntax

ait supplies three of the four components needed: an abstract interpreter (in this case handling func...
Definition ai.h:562

A requires clause specifies a precondition for a function, i.e., a property that must hold to properly execute the function. Developers may see the requires clauses of a function as obligations on the caller when invoking the function. The precondition of a function is the conjunction of the requires clauses, or true if none are specified.

An ensures clause specifies a postcondition for a function, i.e., a property over arguments or global variables that the function guarantees at the end of the operation. Developers may see the ensures clauses of a function as the obligations of that function to the caller. The postcondition of a function is the conjunction of the ensures clauses, or true if none are specified.

Both requires clauses and ensures clauses take a Boolean expression over the arguments of a function and/or global variables. The expression can include calls to CBMC built-in functions, to Memory Predicates or to function pointer predicates. User-defined functions can also be called inside requires clauses as long as they are deterministic and do not have any side-effects (the absence of side effects is checked by the tool). In addition, ensures clauses also accept History Variables and the special built-in symbol __CPROVER_return_value which represents the return value of the function.

Semantics

The semantics of ensures and requires clauses can be understood in two contexts: enforcement and replacement. To illustrate these two perspectives, consider the following implementation of the sum function.

int sum(const uint32_t a, const uint32_t b, uint32_t* out)
(__CPROVER_return_value == SUCCESS) ==> (*out == (a + b)))
{
const uint64_t result = ((uint64_t) a) + ((uint64_t) b);
if (result > UINT32_MAX) return FAILURE;
*out = (uint32_t) result;
return SUCCESS;
}
__CPROVER_bool __CPROVER_is_fresh(const void *mem, __CPROVER_size_t size)

Enforcement

In order to determine whether requires and ensures clauses are a sound abstraction of the behavior of a function f, CBMC will try to check them as follows:

  1. Considers all arguments and global variables as non-deterministic values;
  2. Assumes all preconditions specified in the __CPROVER_requires clauses;
  3. Calls the implementation of function f;
  4. Asserts all postconditions described in the __CPROVER_ensures clauses.

In our example, the sum function will be instrumented as follows:

// The original sum function with a mangled name
const uint32_t a, const uint32_t b, uint32_t* out)
{
const uint64_t result = ((uint64_t) a) + ((uint64_t) b);
if (result > UINT32_MAX) return FAILURE;
*out = (uint32_t) result;
return SUCCESS;
}
/* Function contract enforcement wrapper */
{
// assume the requires clause
__CPROVER_assume(__CPROVER_is_fresh(out, sizeof(*out)));
// declare local to represetn __CPROVER_return_value
// call function under verification
// check the first ensures clause
"Check ensures clause");
// check the second requires clause
(__return_value == SUCCESS) ==> (*out == (a + b)),
"Check ensures clause");
}
void __CPROVER_assert(__CPROVER_bool assertion, const char *description)
void __CPROVER_assume(__CPROVER_bool assumption)

Replacement

Assuming requires and ensures clauses are a sound abstraction of the behavior of the function f, CBMC will use the function contract in place of the function implementation as follows:

  1. Adds assertions for all preconditions specified in the __CPROVER_requires clauses;
  2. Adds non-deterministic assignments for each symbol listed in the __CPROVER_assigns clause (see Assigns Clauses for details);
  1. Adds non-deterministic free statements for each symbol listed in the __CPROVER_frees clause (see Frees Clauses for details);
  2. Assumes all postconditions described in the __CPROVER_ensures clauses;

In our example, consider that a function foo may call sum.

int foo()
{
uint32_t a = ... ;
uint32_t b = ... ;
uint32_t out = 0;
int rval = sum(a, b, &out);
if (SUCCESS != rval)
return FAILURE;
// else, use out
utin32_t result = out + ...;
}

CBMC will use the function contract in place of the function implementation wherever the function is called.

int foo()
{
uint32_t a = ...;
uint32_t b = ...;
uint32_t out = 0;
// start of call-by-contract replacement
// check if preconditions hold
__CPROVER_is_fresh(out, sizeof(*out)), "Check requires clause");
// nondet return value
// assume postconditions
__CPROVER_assume((__return_value == SUCCESS) ==> (*out == (*a + *b)));
// end of call-by-contract replacement
if (SUCCESS != rval)
return FAILURE;
// else, use out
utin32_t result = out + ...;
}

Additional Resources