|
| 1 | +# Function Contracts |
| 2 | + |
| 3 | +CBMC offers support for function contracts, which includes three basic |
| 4 | +clauses: _requires_, _ensures_, and _assigns_. These clauses |
| 5 | +accept either symbols or Boolean predicates that formally describe the |
| 6 | +specification of a function, but they might need more expressive constructs to |
| 7 | +fully specify these operations. Therefore, CBMC also provides a series of |
| 8 | +built-in constructus to be used with functions contracts (e.g., _history |
| 9 | +variables_, _quantifiers_, and _memory predicates_). |
| 10 | + |
| 11 | +To learn more about contracts, take a look at the chapter [Design by Contract](http://se.inf.ethz.ch/~meyer/publications/old/dbc_chapter.pdf) from the book Object-Oriented Software Construction by Bertrand Meyer or check it out the notes [Contract-based Design](https://www.georgefairbanks.com/york-university-contract-based-design-2021) by George Fairbanks. |
| 12 | + |
| 13 | +## Overview |
| 14 | + |
| 15 | +Take a look at the example below. |
| 16 | + |
| 17 | +```c |
| 18 | +#include <stdlib.h> |
| 19 | +#include <stdint.h> |
| 20 | + |
| 21 | +#define SUCCESS 0 |
| 22 | +#define FAILURE -1 |
| 23 | + |
| 24 | +int sum(uint32_t* a, uint32_t* b, uint32_t* out) |
| 25 | +{ |
| 26 | + const uint64_t result = ((uint64_t) *a) + ((uint64_t) *b); |
| 27 | + if (result > UINT32_MAX) return FAILURE; |
| 28 | + *out = (uint32_t) result; |
| 29 | + return SUCCESS; |
| 30 | +} |
| 31 | + |
| 32 | +int foo() |
| 33 | +{ |
| 34 | + uint32_t* a = malloc(sizeof(*a)); |
| 35 | + uint32_t* b = malloc(sizeof(*a)); |
| 36 | + uint32_t out; |
| 37 | + int rval = sum(a, b, &out); |
| 38 | + if (rval == SUCCESS) |
| 39 | + return out; |
| 40 | + return rval; |
| 41 | +} |
| 42 | +``` |
| 43 | +
|
| 44 | +Function `sum` writes the sum of `a` and `b` to `out`, and returns `SUCCESS`; unless there is overflows, in which case it returns `FAILURE`. Let's write a function contract for this function. |
| 45 | +
|
| 46 | +A function contract has three parts: |
| 47 | +
|
| 48 | +- **Precondition** - describes what the function requires of the arguments supplied by the caller and global variables; |
| 49 | +- **Postcondition** - describes the effects of the function; |
| 50 | +- **Writable Set** - describes the set of locations outside the function that might be written to; |
| 51 | +
|
| 52 | +In our example, the developer may require from the caller to properly allocate all |
| 53 | +arguments, thus, pointers must be valid. We can specify the preconditions of a function using `__CPROVER_requires` (see [Requires \& Ensures Clauses](requires-and-ensures-clauses.md) Section for details) and we can specify an allocated object using a predicate called `__CPROVER_is_fresh` (see [Memory Predicate]() Section for details). Thus, for the `sum` function, the set of preconditions are |
| 54 | +
|
| 55 | +```c |
| 56 | +/* Precondition */ |
| 57 | +__CPROVER_requires(__CPROVER_is_fresh(a, sizeof(*a))) |
| 58 | +__CPROVER_requires(__CPROVER_is_fresh(b, sizeof(*b))) |
| 59 | +__CPROVER_requires(__CPROVER_is_fresh(out, sizeof(*out))) |
| 60 | +``` |
| 61 | + |
| 62 | +We can use `__CPROVER_ensures` to specify postconditions (see [Requires \& Ensures Clauses](requires-and-ensures-clauses.md) Section for details). |
| 63 | +In our example, developers can use the built-in construct |
| 64 | +`__CPROVER_return_value`, which represents the return value of a function. As |
| 65 | +postconditions, one may list possible return values (in this |
| 66 | +case, either `SUCCESS` or `FAILURE`) as well as describe the main property of |
| 67 | +this function: if the function returns `SUCCESS`, then `*out` stores the |
| 68 | +result of `*a + *b`. |
| 69 | +We can also check that the value in `out` will be preserved in case of failure by using `__CPROVER_old`, which refers to the value of a given object in the pre-state of a function (see [History Varaibles]() Section for details). Thus, for the `sum` function, the set of postconditions are |
| 70 | + |
| 71 | + |
| 72 | +```c |
| 73 | +/* Postconditions */ |
| 74 | +__CPROVER_ensures(__CPROVER_return_value == SUCCESS || __CPROVER_return_value == FAILURE) |
| 75 | +__CPROVER_ensures((__CPROVER_return_value == SUCCESS) ==> (*out == (*a + *b))) |
| 76 | +__CPROVER_ensures((__CPROVER_return_value == SUCCESS) ==> (*out == __CPROVER_old(*out))) |
| 77 | +``` |
| 78 | +
|
| 79 | +Finally, the _assigns_ clause allows developers to define a frame condition (see [Assigns Clauses](assigns-clause.md) Section for details). |
| 80 | +in general, be interpreted with either _writes_ or _modifies_ semantics, this |
| 81 | +design is based on the former. This means that memory not specified by the |
| 82 | +assigns clause must not be written within the given function scope, even if the |
| 83 | +value(s) therein are not modified. In our example, since we only expect |
| 84 | +that the value pointed by `out` may be modified, we annotate the function using |
| 85 | +`__CPROVER_assigns(*out)`. |
| 86 | +
|
| 87 | +```c |
| 88 | +/* Writable Set */ |
| 89 | +__CPROVER_assigns(*out) |
| 90 | +``` |
| 91 | + |
| 92 | +Here is the whole function with its contract. |
| 93 | + |
| 94 | +```c |
| 95 | +int sum(uint32_t* a, uint32_t* b, uint32_t* out) |
| 96 | +/* Precondition */ |
| 97 | +__CPROVER_requires(__CPROVER_is_fresh(a, sizeof(*a))) |
| 98 | +__CPROVER_requires(__CPROVER_is_fresh(b, sizeof(*b))) |
| 99 | +__CPROVER_requires(__CPROVER_is_fresh(out, sizeof(*out))) |
| 100 | +/* Postconditions */ |
| 101 | +__CPROVER_ensures(__CPROVER_return_value == SUCCESS || __CPROVER_return_value == FAILURE) |
| 102 | +__CPROVER_ensures((__CPROVER_return_value == SUCCESS) ==> (*out == (*a + *b))) |
| 103 | +__CPROVER_ensures((__CPROVER_return_value == SUCCESS) ==> (*out == __CPROVER_old(*out))) |
| 104 | +/* Writable Set */ |
| 105 | +__CPROVER_assigns(*out) |
| 106 | +{ |
| 107 | + const uint64_t result = ((uint64_t) *a) + ((uint64_t) *b); |
| 108 | + if (result > UINT32_MAX) return FAILURE; |
| 109 | + *out = (uint32_t) result; |
| 110 | + return SUCCESS; |
| 111 | +} |
| 112 | +``` |
| 113 | +
|
| 114 | +First, we have to prove the function contract is correct. |
| 115 | +
|
| 116 | +```shell |
| 117 | + goto-cc -o main.goto *.c |
| 118 | + goto-instrument --enforce-contract sum main.goto main-checking-contracts.goto |
| 119 | + cbmc main-checking-contracts.goto |
| 120 | +``` |
| 121 | + |
| 122 | +The first command just compiles the GOTO program as usual, the second command instruments the code to check the function contract, and the third one runs CBMC to do the checking. |
| 123 | + |
| 124 | +Now that we proved the function contract is correct, we can use the function contract in place of the function implementation wherever the function is called. |
| 125 | + |
| 126 | +```shell |
| 127 | + goto-cc -o main.goto *.c |
| 128 | + goto-instrument --replace-contract sum main.goto main-using-contracts.goto |
| 129 | + cbmc main-using-contracts.goto |
| 130 | +``` |
| 131 | + |
| 132 | +The first command just compiles the goto program as usual, the second command instruments the code to use the function contract in place of the function implementation wherever is invoked, and the third one runs CBMC to check the program using contracts. |
| 133 | + |
| 134 | +## Additional Resources |
| 135 | + |
| 136 | +- [Requires \& Ensures Clauses](requires-and-ensures-clauses.md) |
| 137 | +- [Assigns Clauses](assigns-clause.md) |
| 138 | +- [Memory Predicates](memory-predicates.md) |
| 139 | +- [History Variables](history-variables.md) |
| 140 | +- [Quantifiers](quantifiers.md) |
0 commit comments