CBMC
|
Back to top Code Contracts Transformation Specification
The C extensions for contract specification provide three pointer-related memory predicates:
Users are free to call these predicates from requires and ensures clauses. Users can also define their own functions in terms of these predicates, and call them from requires and ensures clauses, but not from the program under analysis.
For instance, one could write a predicate defining linked lists of at most len
elements as follows:
One can also simply describe finite nested structures:
By rewriting such user-defined predicate we achieve two things:
To achieve point 1., we apply an instrumentation pass to transform the user-defined functions into functions that take their pointer arguments by reference instead of by value, so that enforcing the assumption described by the predicate can be done by updating the pointer in place using a side effect.
We first run a pass that collects all user-defined functions that are defined in terms of one of the three core memory predicates using this fixpoint algorithm:
We only support sets of predicates that are non-recursive or self-recursive (i.e. predicates that call themselves directly). We build a graph representing the P-calls-Q relation, omitting edges for self-recursion, and try to sort it topologically. If the sort succeeds we rewrite the predicates in topological order, so that when instrumenting P any other predicate Q it calls is already instrumented and we know which parameters of Q have been lifted. If the topological sort fails, then it means the predicate are mutually recursive and we abort instrumentation.
Rewriting a user-defined memory predicate P consists in:
p
into *p
(this brings back type coherence in the body of P);address_of
operator to arguments passed on a lifted parameter of Q;Once these transformations are applied, the body of the predicate is well typed and the predicate now takes the pointers on which it operates by reference instead of by value. By doing so, the P gains the capability to update in place the memory location hold the pointer value subject to the predicate definition.
The last step of the rewriting is to apply the normal DFCC instrumentation which adds a write set parameter to the function, instruments it for side effect checking, and maps core memory predicates to their implementations.
On our previous list example, this yields the following result:
On the nested structs example, it gives the following result:
The write_set parameter carries assumption/assertion context flags, so that the implementation of the core-predicates know when to update the pointers in place using malloc and assignments to make the predicates hold.
Prev | Next |
---|---|
Rewriting Declarative Assign and Frees Specification Functions | Dynamic Frame Condition Checking |