From ad38602c95d40253914075e31819d413521fa013 Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Mon, 22 Aug 2022 14:40:47 -0400 Subject: [PATCH 01/20] CONTRACTS: Front-end extensions for assigns clauses and frees clauses. Add new functions to specify assignable targets in assigns clauses: - add type __CPROVER_assignable_t - add builtin __CPROVER_assignable - add builtin __CPROVER_whole_object - add builtin __CPROVER_object_upto - add builtin __CPROVER_typed_target - allow function call expressions as assigns clause targets as long as they have the return type __CPROVER_assignable_t and are one of the built-ins. Add support for `__CPROVER_frees()` clauses to the contract language to let users specify the pointers a function (or loop) is allowed to free. --- doc/cprover-manual/contracts-assigns.md | 46 ++++-- doc/cprover-manual/contracts-frees.md | 80 ++++++++++ .../assigns-slice-targets/main-enforce.c | 22 ++- .../assigns-slice-targets/main-replace.c | 62 +++++++- .../assigns-slice-targets/test-enforce.desc | 10 +- .../assigns-slice-targets/test-replace.desc | 23 +++ .../assigns_enforce_address_of/test.desc | 2 +- .../test.desc | 2 +- .../test.desc | 2 +- .../assigns_enforce_function_calls/test.desc | 2 +- .../assigns_enforce_literal/test.desc | 2 +- .../assigns_enforce_side_effects_2/test.desc | 2 +- .../assigns_enforce_side_effects_3/test.desc | 2 +- .../test.desc | 2 +- .../contracts/cprover-assignable-fail/main.c | 29 ++++ .../cprover-assignable-fail/test.desc | 20 +++ .../contracts/cprover-assignable-pass/main.c | 25 +++ .../cprover-assignable-pass/test.desc | 20 +++ .../frees-clause-for-loop-fail/main.c | 19 +++ .../frees-clause-for-loop-fail/test.desc | 10 ++ .../frees-clause-for-loop-pass/main.c | 19 +++ .../frees-clause-for-loop-pass/test.desc | 9 ++ .../frees-clause-function-fail/main.c | 19 +++ .../frees-clause-function-fail/test.desc | 10 ++ .../frees-clause-function-pass/main.c | 20 +++ .../frees-clause-function-pass/test.desc | 9 ++ .../frees-clause-while-loop-fail/main.c | 20 +++ .../frees-clause-while-loop-fail/test.desc | 10 ++ .../frees-clause-while-loop-pass/main.c | 20 +++ .../frees-clause-while-loop-pass/test.desc | 9 ++ src/ansi-c/ansi_c_convert_type.cpp | 11 ++ src/ansi-c/ansi_c_convert_type.h | 2 +- src/ansi-c/ansi_c_internal_additions.cpp | 15 ++ src/ansi-c/c_typecheck_base.cpp | 30 ++++ src/ansi-c/c_typecheck_base.h | 9 +- src/ansi-c/c_typecheck_code.cpp | 143 ++++++++++++------ src/ansi-c/c_typecheck_expr.cpp | 52 ++++++- src/ansi-c/cprover_builtin_headers.h | 7 +- src/ansi-c/library/cprover.h | 2 + src/ansi-c/parser.y | 59 ++++++-- src/ansi-c/scanner.l | 1 + .../contracts/instrument_spec_assigns.cpp | 40 ++++- src/goto-programs/goto_convert.cpp | 7 + src/linking/remove_internal_symbols.cpp | 5 + src/util/c_types.h | 12 +- src/util/irep_ids.def | 1 + src/util/rename_symbol.cpp | 8 + src/util/replace_symbol.cpp | 5 + 48 files changed, 841 insertions(+), 95 deletions(-) create mode 100644 doc/cprover-manual/contracts-frees.md create mode 100644 regression/contracts/cprover-assignable-fail/main.c create mode 100644 regression/contracts/cprover-assignable-fail/test.desc create mode 100644 regression/contracts/cprover-assignable-pass/main.c create mode 100644 regression/contracts/cprover-assignable-pass/test.desc create mode 100644 regression/contracts/frees-clause-for-loop-fail/main.c create mode 100644 regression/contracts/frees-clause-for-loop-fail/test.desc create mode 100644 regression/contracts/frees-clause-for-loop-pass/main.c create mode 100644 regression/contracts/frees-clause-for-loop-pass/test.desc create mode 100644 regression/contracts/frees-clause-function-fail/main.c create mode 100644 regression/contracts/frees-clause-function-fail/test.desc create mode 100644 regression/contracts/frees-clause-function-pass/main.c create mode 100644 regression/contracts/frees-clause-function-pass/test.desc create mode 100644 regression/contracts/frees-clause-while-loop-fail/main.c create mode 100644 regression/contracts/frees-clause-while-loop-fail/test.desc create mode 100644 regression/contracts/frees-clause-while-loop-pass/main.c create mode 100644 regression/contracts/frees-clause-while-loop-pass/test.desc diff --git a/doc/cprover-manual/contracts-assigns.md b/doc/cprover-manual/contracts-assigns.md index 3c9a3048cc8..bbafa46e1c0 100644 --- a/doc/cprover-manual/contracts-assigns.md +++ b/doc/cprover-manual/contracts-assigns.md @@ -17,28 +17,46 @@ value(s) therein are not modified. ### Object slice expressions -The following functions can be used in assigns clause to specify ranges of -assignable addresses. +The following functions can be used in assigns clauses to specify ranges of assignable bytes. -Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_from(ptr)` -specifies that all bytes starting from the given pointer and until the end of -the object are assignable: +Given an lvalue expression `expr` with a complete type `expr_t`, + `__CPROVER_typed_target(expr)` specifies that the range + of `sizeof(expr_t)` bytes starting at `&expr` is assignable: ```c -__CPROVER_size_t __CPROVER_object_from(void *ptr); +__CPROVER_assignable_t __CPROVER_typed_target(expr_t expr); ``` -Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_from(ptr, size)` -specifies that `size` bytes starting from the given pointer and until the end of the object are assignable. -The `size` value must such that `size <= __CPROVER_object_size(ptr) - __CPROVER_pointer_offset(ptr)` holds: +Given a pointer `ptr` pointing into some object `o`, +`__CPROVER_whole_object(ptr)` specifies that all bytes of the object `o` +are assignable: +```c +__CPROVER_assignable_t __CPROVER_whole_object(void *ptr); +``` +Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_from(ptr)` +specifies that the range of bytes starting from the pointer and until the end of +the object `o` are assignable: ```c -__CPROVER_size_t __CPROVER_object_slice(void *ptr, __CPROVER_size_t size); +__CPROVER_assignable_t __CPROVER_object_from(void *ptr); ``` -Caveats and limitations: The slices in question must *not* -be interpreted as pointers by the program. During call-by-contract replacement, -`__CPROVER_havoc_slice(ptr, size)` is used to havoc these targets, -and `__CPROVER_havoc_slice` does not support havocing pointers. +Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_upto(ptr, size)` +specifies that the range of `size` bytes of `o` starting at `ptr` are assignable: +The `size` value must such that the range does not exceed the object boundary, +that is, `__CPROVER_object_size(ptr) - __CPROVER_pointer_offset(ptr) >= size` must hold: + +```c +__CPROVER_assignable_t __CPROVER_object_upto(void *ptr, __CPROVER_size_t size); +``` + +CAVEAT: The ranges specified by `__CPROVER_whole_object`, +`__CPROVER_object_from` and `__CPROVER_object_upto` must *not* +be interpreted as pointers by the program. This is because during +call-by-contract replacement, `__CPROVER_havoc_slice(ptr, size)` is used to +havoc these byte ranges, and `__CPROVER_havoc_slice` does not support +havocing pointers. `__CPROVER_typed_target` must be used to specify targets +that are pointers. + ### Parameters An _assigns_ clause currently supports simple variable types and their pointers, diff --git a/doc/cprover-manual/contracts-frees.md b/doc/cprover-manual/contracts-frees.md new file mode 100644 index 00000000000..7b8134ce5c8 --- /dev/null +++ b/doc/cprover-manual/contracts-frees.md @@ -0,0 +1,80 @@ +[CPROVER Manual TOC](../../) + +# Frees Clause + +### Syntax + +```c +__CPROVER_frees(*pointer-typed-expression*, ...) +``` + +A _frees_ clause allows the user to specify a set of pointers that may be freed +by a function or the body of a loop. +A function or loop contract contract may have zero or more _frees_ clauses. + +_Frees_ clause targets must be pointer-typed expressions. + +_Frees_ clause targets can also be _conditional_ and written as follows: + +``` +condition: list-of-pointer-typed-expressions; +``` + +### Examples + +In a function contract +```c +int foo(char *arr1, char *arr2, size_t size) +__CPROVER_frees( + // arr1 freeable only if the condition holds + size > 0 && arr1: arr1; + // arr2 always freeable + arr2 +) +{ + if(size > 0 && arr1) + free(arr1); + free(arr2); + return 0; +} +``` + +In a loop contract: + +```c +int main() +{ + size_t size = 10; + char *arr = malloc(size); + + for(size_t i = 0; i <= size; i++) + // clang-format off + __CPROVER_assigns(i, __CPROVER_POINTER_OBJECT(arr)) + __CPROVER_frees(arr) + // clang-format on2 + { + if(i < size) + arr[i] = 0; + else + free(arr); + } + return 0; +} +``` + +### Semantics + +The set of pointers specified by the frees clause of the contract is interpreted +at the function call-site for function contracts, and right before entering the +loop for loop contracts. + +#### For contract checking +When checking a contract against a function or a loop, each pointer that the +function or loop body attempts to free gets checked for membership in the set of +pointers specified by the contract. + +#### For replacement of function calls or loops by contracts +When replacing a function call or a loop by a contract, each pointer of the +_frees_ clause is non-deterministically freed after the function call +or after the loop. + diff --git a/regression/contracts/assigns-slice-targets/main-enforce.c b/regression/contracts/assigns-slice-targets/main-enforce.c index 7f39d6f06cd..aa1c5a4070d 100644 --- a/regression/contracts/assigns-slice-targets/main-enforce.c +++ b/regression/contracts/assigns-slice-targets/main-enforce.c @@ -7,11 +7,14 @@ struct st int c; }; -void foo(struct st *s) +void foo(struct st *s, struct st *ss) // clang-format off __CPROVER_requires(__CPROVER_is_fresh(s, sizeof(*s))) - __CPROVER_assigns(__CPROVER_object_slice(s->arr1, 5)) - __CPROVER_assigns(__CPROVER_object_from(s->arr2 + 5)) + __CPROVER_assigns( + __CPROVER_object_upto(s->arr1, 5); + __CPROVER_object_from(s->arr2 + 5); + __CPROVER_whole_object(ss); +) // clang-format on { // PASS @@ -41,13 +44,24 @@ void foo(struct st *s) s->arr2[7] = 0; s->arr2[8] = 0; s->arr2[9] = 0; + + // PASS + ss->a = 0; + ss->arr1[0] = 0; + ss->arr1[7] = 0; + ss->arr1[9] = 0; + ss->b = 0; + ss->arr2[6] = 0; + ss->arr2[8] = 0; + ss->c = 0; } int main() { struct st s; + struct st ss; - foo(&s); + foo(&s, &ss); return 0; } diff --git a/regression/contracts/assigns-slice-targets/main-replace.c b/regression/contracts/assigns-slice-targets/main-replace.c index 5f21100ee7c..975d43b8606 100644 --- a/regression/contracts/assigns-slice-targets/main-replace.c +++ b/regression/contracts/assigns-slice-targets/main-replace.c @@ -7,11 +7,14 @@ struct st int c; }; -void foo(struct st *s) +void foo(struct st *s, struct st *ss) // clang-format off __CPROVER_requires(__CPROVER_is_fresh(s, sizeof(*s))) - __CPROVER_assigns(__CPROVER_object_slice(s->arr1, 5)) - __CPROVER_assigns(__CPROVER_object_from(s->arr2 + 5)) + __CPROVER_assigns( + __CPROVER_object_upto(s->arr1, 5); + __CPROVER_object_from(s->arr2 + 5); + __CPROVER_whole_object(ss); + ) // clang-format on { s->arr1[0] = 0; @@ -54,7 +57,32 @@ int main() s.arr2[9] = 0; s.c = 0; - foo(&s); + struct st ss; + ss.a = 0; + ss.arr1[0] = 0; + ss.arr1[1] = 0; + ss.arr1[2] = 0; + ss.arr1[3] = 0; + ss.arr1[4] = 0; + ss.arr1[5] = 0; + ss.arr1[6] = 0; + ss.arr1[7] = 0; + ss.arr1[8] = 0; + ss.arr1[9] = 0; + + ss.arr2[0] = 0; + ss.arr2[1] = 0; + ss.arr2[2] = 0; + ss.arr2[3] = 0; + ss.arr2[4] = 0; + ss.arr2[5] = 0; + ss.arr2[6] = 0; + ss.arr2[7] = 0; + ss.arr2[8] = 0; + ss.arr2[9] = 0; + ss.c = 0; + + foo(&s, &ss); // PASS assert(s.a == 0); @@ -92,5 +120,31 @@ int main() // PASS assert(s.c == 0); + + // FAIL + assert(ss.a == 0); + assert(ss.arr1[0] == 0); + assert(ss.arr1[1] == 0); + assert(ss.arr1[2] == 0); + assert(ss.arr1[3] == 0); + assert(ss.arr1[4] == 0); + assert(ss.arr1[5] == 0); + assert(ss.arr1[6] == 0); + assert(ss.arr1[7] == 0); + assert(ss.arr1[8] == 0); + assert(ss.arr1[9] == 0); + assert(ss.b == 0); + assert(ss.arr2[0] == 0); + assert(ss.arr2[1] == 0); + assert(ss.arr2[2] == 0); + assert(ss.arr2[3] == 0); + assert(ss.arr2[4] == 0); + assert(ss.arr2[5] == 0); + assert(ss.arr2[6] == 0); + assert(ss.arr2[7] == 0); + assert(ss.arr2[8] == 0); + assert(ss.arr2[9] == 0); + assert(ss.c == 0); + return 0; } diff --git a/regression/contracts/assigns-slice-targets/test-enforce.desc b/regression/contracts/assigns-slice-targets/test-enforce.desc index 5b8f4fe5f58..d7c221d1a86 100644 --- a/regression/contracts/assigns-slice-targets/test-enforce.desc +++ b/regression/contracts/assigns-slice-targets/test-enforce.desc @@ -1,8 +1,6 @@ CORE main-enforce.c --enforce-contract foo -^\[foo.assigns.\d+\].* Check that __CPROVER_object_slice\(\(void \*\)s->arr1, \(.*\)5\) is valid: SUCCESS$ -^\[foo.assigns.\d+\].* Check that __CPROVER_object_from\(\(void \*\)\(s->arr2 \+ \(.*\)5\)\) is valid: SUCCESS$ ^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)0\] is assignable: SUCCESS$ ^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)1\] is assignable: SUCCESS$ ^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)2\] is assignable: SUCCESS$ @@ -23,6 +21,14 @@ main-enforce.c ^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)7\] is assignable: SUCCESS$ ^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)8\] is assignable: SUCCESS$ ^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)9\] is assignable: SUCCESS$ +^\[foo.assigns.\d+\].* Check that ss->a is assignable: SUCCESS$ +^\[foo.assigns.\d+\].* Check that ss->arr1\[\(.*\)0\] is assignable: SUCCESS$ +^\[foo.assigns.\d+\].* Check that ss->arr1\[\(.*\)7\] is assignable: SUCCESS$ +^\[foo.assigns.\d+\].* Check that ss->arr1\[\(.*\)9\] is assignable: SUCCESS$ +^\[foo.assigns.\d+\].* Check that ss->b is assignable: SUCCESS$ +^\[foo.assigns.\d+\].* Check that ss->arr2\[\(.*\)6\] is assignable: SUCCESS$ +^\[foo.assigns.\d+\].* Check that ss->arr2\[\(.*\)8\] is assignable: SUCCESS$ +^\[foo.assigns.\d+\].* Check that ss->c is assignable: SUCCESS$ ^VERIFICATION FAILED$ ^EXIT=10$ ^SIGNAL=0$ diff --git a/regression/contracts/assigns-slice-targets/test-replace.desc b/regression/contracts/assigns-slice-targets/test-replace.desc index 1f23fb0ae7e..1c7532841a1 100644 --- a/regression/contracts/assigns-slice-targets/test-replace.desc +++ b/regression/contracts/assigns-slice-targets/test-replace.desc @@ -24,6 +24,29 @@ main-replace.c ^\[main.assertion.\d+\].*assertion \(.*\)s.arr2\[\(.*\)8\] == 0: FAILURE$ ^\[main.assertion.\d+\].*assertion \(.*\)s.arr2\[\(.*\)9\] == 0: FAILURE$ ^\[main.assertion.\d+\].*assertion s.c == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion ss.a == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)0\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)1\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)2\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)3\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)4\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)5\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)6\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)7\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)8\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)9\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion ss.b == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)0\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)1\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)2\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)3\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)4\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)5\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)6\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)7\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)8\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)9\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion ss.c == 0: FAILURE$ ^VERIFICATION FAILED$ ^EXIT=10$ ^SIGNAL=0$ diff --git a/regression/contracts/assigns_enforce_address_of/test.desc b/regression/contracts/assigns_enforce_address_of/test.desc index 7500be4b1e4..9b717247265 100644 --- a/regression/contracts/assigns_enforce_address_of/test.desc +++ b/regression/contracts/assigns_enforce_address_of/test.desc @@ -3,7 +3,7 @@ main.c --enforce-contract foo ^EXIT=(1|64)$ ^SIGNAL=0$ -^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$ +^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$ ^CONVERSION ERROR$ -- -- diff --git a/regression/contracts/assigns_enforce_conditional_non_lvalue_target/test.desc b/regression/contracts/assigns_enforce_conditional_non_lvalue_target/test.desc index 2a9141844c9..dd48913cefd 100644 --- a/regression/contracts/assigns_enforce_conditional_non_lvalue_target/test.desc +++ b/regression/contracts/assigns_enforce_conditional_non_lvalue_target/test.desc @@ -1,7 +1,7 @@ CORE main.c --enforce-contract foo -^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$ +^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$ ^CONVERSION ERROR ^EXIT=(1|64)$ ^SIGNAL=0$ diff --git a/regression/contracts/assigns_enforce_conditional_non_lvalue_target_list/test.desc b/regression/contracts/assigns_enforce_conditional_non_lvalue_target_list/test.desc index 2a9141844c9..dd48913cefd 100644 --- a/regression/contracts/assigns_enforce_conditional_non_lvalue_target_list/test.desc +++ b/regression/contracts/assigns_enforce_conditional_non_lvalue_target_list/test.desc @@ -1,7 +1,7 @@ CORE main.c --enforce-contract foo -^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$ +^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$ ^CONVERSION ERROR ^EXIT=(1|64)$ ^SIGNAL=0$ diff --git a/regression/contracts/assigns_enforce_function_calls/test.desc b/regression/contracts/assigns_enforce_function_calls/test.desc index 5f482125c08..a8e3b382646 100644 --- a/regression/contracts/assigns_enforce_function_calls/test.desc +++ b/regression/contracts/assigns_enforce_function_calls/test.desc @@ -3,7 +3,7 @@ main.c --enforce-contract foo ^EXIT=(1|64)$ ^SIGNAL=0$ -^.*error: function calls in assigns clause targets must be to __CPROVER_object_from, __CPROVER_object_slice$ +^.*error: expecting __CPROVER_assignable_t return type for function bar called in assigns clause$ ^CONVERSION ERROR$ -- -- diff --git a/regression/contracts/assigns_enforce_literal/test.desc b/regression/contracts/assigns_enforce_literal/test.desc index 74d1d576235..d1952cdfe3c 100644 --- a/regression/contracts/assigns_enforce_literal/test.desc +++ b/regression/contracts/assigns_enforce_literal/test.desc @@ -3,7 +3,7 @@ main.c --enforce-contract foo ^EXIT=(1|64)$ ^SIGNAL=0$ -^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$ +^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$ ^CONVERSION ERROR$ -- -- diff --git a/regression/contracts/assigns_enforce_side_effects_2/test.desc b/regression/contracts/assigns_enforce_side_effects_2/test.desc index 8cab6884f92..368481cb54d 100644 --- a/regression/contracts/assigns_enforce_side_effects_2/test.desc +++ b/regression/contracts/assigns_enforce_side_effects_2/test.desc @@ -3,7 +3,7 @@ main.c --enforce-contract foo ^EXIT=(1|64)$ ^SIGNAL=0$ -^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$ +^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$ ^CONVERSION ERROR$ -- -- diff --git a/regression/contracts/assigns_enforce_side_effects_3/test.desc b/regression/contracts/assigns_enforce_side_effects_3/test.desc index 8cab6884f92..368481cb54d 100644 --- a/regression/contracts/assigns_enforce_side_effects_3/test.desc +++ b/regression/contracts/assigns_enforce_side_effects_3/test.desc @@ -3,7 +3,7 @@ main.c --enforce-contract foo ^EXIT=(1|64)$ ^SIGNAL=0$ -^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$ +^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$ ^CONVERSION ERROR$ -- -- diff --git a/regression/contracts/assigns_type_checking_invalid_case_01/test.desc b/regression/contracts/assigns_type_checking_invalid_case_01/test.desc index 51c84807dcb..232c8b3f575 100644 --- a/regression/contracts/assigns_type_checking_invalid_case_01/test.desc +++ b/regression/contracts/assigns_type_checking_invalid_case_01/test.desc @@ -4,6 +4,6 @@ main.c ^EXIT=(1|64)$ ^SIGNAL=0$ ^CONVERSION ERROR$ -^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$ +^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$ -- Checks whether type checking rejects literal constants in assigns clause. diff --git a/regression/contracts/cprover-assignable-fail/main.c b/regression/contracts/cprover-assignable-fail/main.c new file mode 100644 index 00000000000..9db72b0af93 --- /dev/null +++ b/regression/contracts/cprover-assignable-fail/main.c @@ -0,0 +1,29 @@ +#include + +__CPROVER_assignable_t my_write_set(char *arr, size_t size) +{ + __CPROVER_assert( + !arr || __CPROVER_rw_ok(arr, size), "target null or writable"); + + if(arr && size > 0) + { + __CPROVER_whole_object(arr); + __CPROVER_object_upto(arr, size); + __CPROVER_object_from(arr); + __CPROVER_typed_target(arr[0]); + } +} + +void main() +{ + size_t size; + char *arr; + int do_init; + if(do_init) + { + int nondet; + arr = nondet ? malloc(size) : NULL; + } + // pointer can be invalid expecting failed checks + my_write_set(arr, size); +} diff --git a/regression/contracts/cprover-assignable-fail/test.desc b/regression/contracts/cprover-assignable-fail/test.desc new file mode 100644 index 00000000000..0550c09fb40 --- /dev/null +++ b/regression/contracts/cprover-assignable-fail/test.desc @@ -0,0 +1,20 @@ +CORE +main.c + +CALL __CPROVER_whole_object +CALL __CPROVER_object_upto +CALL __CPROVER_object_from +CALL __CPROVER_assignable +^\[my_write_set.assertion.\d+\] .* target null or writable: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +CALL __CPROVER_typed_target +-- +This test checks that: +- built-in __CPROVER_assignable_t functions are supported; +- GOTO conversion preserves calls to __CPROVER_whole_object, + __CPROVER_object_upto, __CPROVER_object_from; +- GOTO conversion translates __CPROVER_typed_target to __CPROVER_assignable; +- user-defined checks embedded in `my_write_set` persist after conversion. diff --git a/regression/contracts/cprover-assignable-pass/main.c b/regression/contracts/cprover-assignable-pass/main.c new file mode 100644 index 00000000000..bbeb08acc03 --- /dev/null +++ b/regression/contracts/cprover-assignable-pass/main.c @@ -0,0 +1,25 @@ +#include + +__CPROVER_assignable_t my_write_set(char *arr, size_t size) +{ + __CPROVER_assert( + !arr || __CPROVER_rw_ok(arr, size), "target null or writable"); + + if(arr && size > 0) + { + __CPROVER_whole_object(arr); + __CPROVER_object_upto(arr, size); + __CPROVER_object_from(arr); + __CPROVER_typed_target(arr[0]); + } +} + +void main() +{ + int nondet; + size_t size; + char *arr; + arr = nondet ? malloc(size) : NULL; + // pointer is not invalid + my_write_set(arr, size); +} diff --git a/regression/contracts/cprover-assignable-pass/test.desc b/regression/contracts/cprover-assignable-pass/test.desc new file mode 100644 index 00000000000..ee96e43352f --- /dev/null +++ b/regression/contracts/cprover-assignable-pass/test.desc @@ -0,0 +1,20 @@ +CORE +main.c + +CALL __CPROVER_whole_object +CALL __CPROVER_object_upto +CALL __CPROVER_object_from +CALL __CPROVER_assignable +^\[my_write_set.assertion.\d+\] .* target null or writable: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +CALL __CPROVER_typed_target +-- +This test checks that: +- built-in __CPROVER_assignable_t functions are supported; +- GOTO conversion preserves calls to __CPROVER_whole_object, + __CPROVER_object_upto, __CPROVER_object_from; +- GOTO conversion translates __CPROVER_typed_target to __CPROVER_assignable; +- user-defined checks embedded in `my_write_set` persist after conversion. diff --git a/regression/contracts/frees-clause-for-loop-fail/main.c b/regression/contracts/frees-clause-for-loop-fail/main.c new file mode 100644 index 00000000000..715b93998dc --- /dev/null +++ b/regression/contracts/frees-clause-for-loop-fail/main.c @@ -0,0 +1,19 @@ +#include +int main() +{ + size_t size = 10; + char *arr = malloc(size); + + for(size_t i = 0; i <= size; i++) + // clang-format off + __CPROVER_assigns(i, arr[2], __CPROVER_POINTER_OBJECT(arr)) + __CPROVER_frees(arr[2]) + // clang-format on2 + { + if(i == 2) + arr[i] = 0; + if(i == size) + free(arr); + } + return 0; +} diff --git a/regression/contracts/frees-clause-for-loop-fail/test.desc b/regression/contracts/frees-clause-for-loop-fail/test.desc new file mode 100644 index 00000000000..e39b9ce3349 --- /dev/null +++ b/regression/contracts/frees-clause-for-loop-fail/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--apply-loop-contracts +^main.c.*: error: frees clause target must be a pointer-typed expression$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test is expected trigger a typchecking error on a frees clause target. diff --git a/regression/contracts/frees-clause-for-loop-pass/main.c b/regression/contracts/frees-clause-for-loop-pass/main.c new file mode 100644 index 00000000000..5bd912d69e9 --- /dev/null +++ b/regression/contracts/frees-clause-for-loop-pass/main.c @@ -0,0 +1,19 @@ +#include +int main() +{ + size_t size = 10; + char *arr = malloc(size); + + for(size_t i = 0; i <= size; i++) + // clang-format off + __CPROVER_assigns(i, arr[2], __CPROVER_POINTER_OBJECT(arr)) + __CPROVER_frees(arr) + // clang-format on2 + { + if(i == 2) + arr[i] = 0; + if(i == size) + free(arr); + } + return 0; +} diff --git a/regression/contracts/frees-clause-for-loop-pass/test.desc b/regression/contracts/frees-clause-for-loop-pass/test.desc new file mode 100644 index 00000000000..85d74473748 --- /dev/null +++ b/regression/contracts/frees-clause-for-loop-pass/test.desc @@ -0,0 +1,9 @@ +CORE +main.c +--apply-loop-contracts +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +This test checks that frees clauses are parsed and typechecked for for loops. diff --git a/regression/contracts/frees-clause-function-fail/main.c b/regression/contracts/frees-clause-function-fail/main.c new file mode 100644 index 00000000000..f25c8e0be67 --- /dev/null +++ b/regression/contracts/frees-clause-function-fail/main.c @@ -0,0 +1,19 @@ +#include + +int foo(char *arr, int size) + // clang-format off +__CPROVER_frees(size) +// clang-format on +{ + // the body does not actually free the array + // since we are only testing parsing and typechecking + return 0; +} + +int main() +{ + size_t size; + char *arr = malloc(size); + foo(arr, size); + return 0; +} diff --git a/regression/contracts/frees-clause-function-fail/test.desc b/regression/contracts/frees-clause-function-fail/test.desc new file mode 100644 index 00000000000..6936f96e1ba --- /dev/null +++ b/regression/contracts/frees-clause-function-fail/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--enforce-contract foo +^main.c.*: error: frees clause target must be a pointer-typed expression$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test is expected trigger a typchecking error on a frees clause target. diff --git a/regression/contracts/frees-clause-function-pass/main.c b/regression/contracts/frees-clause-function-pass/main.c new file mode 100644 index 00000000000..99c5ee2b0e0 --- /dev/null +++ b/regression/contracts/frees-clause-function-pass/main.c @@ -0,0 +1,20 @@ +#include + +int foo(char *arr1, char *arr2, size_t size) + // clang-format off +__CPROVER_frees(size > 0 && arr1: arr1; arr2) +// clang-format on +{ + // the body does not actually free the function + // since we are only testing parsing and typechecking + return 0; +} + +int main() +{ + size_t size; + char *arr1 = malloc(size); + char *arr2 = malloc(size); + foo(arr1, arr2, size); + return 0; +} diff --git a/regression/contracts/frees-clause-function-pass/test.desc b/regression/contracts/frees-clause-function-pass/test.desc new file mode 100644 index 00000000000..88f63207658 --- /dev/null +++ b/regression/contracts/frees-clause-function-pass/test.desc @@ -0,0 +1,9 @@ +CORE +main.c +--enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that frees clauses are parsed and typechecked. diff --git a/regression/contracts/frees-clause-while-loop-fail/main.c b/regression/contracts/frees-clause-while-loop-fail/main.c new file mode 100644 index 00000000000..e49afea1bb3 --- /dev/null +++ b/regression/contracts/frees-clause-while-loop-fail/main.c @@ -0,0 +1,20 @@ +#include +int main() +{ + size_t size = 10; + char *arr = malloc(size); + size_t i = 0; + while(i <= size) + // clang-format off + __CPROVER_assigns(i, arr[2], __CPROVER_POINTER_OBJECT(arr)) + __CPROVER_frees(arr[2]) + // clang-format on2 + { + if(i == 2) + arr[i] = 0; + if(i == size) + free(arr); + i++; + } + return 0; +} diff --git a/regression/contracts/frees-clause-while-loop-fail/test.desc b/regression/contracts/frees-clause-while-loop-fail/test.desc new file mode 100644 index 00000000000..e39b9ce3349 --- /dev/null +++ b/regression/contracts/frees-clause-while-loop-fail/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--apply-loop-contracts +^main.c.*: error: frees clause target must be a pointer-typed expression$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test is expected trigger a typchecking error on a frees clause target. diff --git a/regression/contracts/frees-clause-while-loop-pass/main.c b/regression/contracts/frees-clause-while-loop-pass/main.c new file mode 100644 index 00000000000..d8a1edbfae9 --- /dev/null +++ b/regression/contracts/frees-clause-while-loop-pass/main.c @@ -0,0 +1,20 @@ +#include +int main() +{ + size_t size = 10; + char *arr = malloc(size); + size_t i = 0; + while(i <= size) + // clang-format off + __CPROVER_assigns(i, arr[2], __CPROVER_POINTER_OBJECT(arr)) + __CPROVER_frees(arr) + // clang-format on2 + { + if(i == 2) + arr[i] = 0; + if(i == size) + free(arr); + i++; + } + return 0; +} diff --git a/regression/contracts/frees-clause-while-loop-pass/test.desc b/regression/contracts/frees-clause-while-loop-pass/test.desc new file mode 100644 index 00000000000..44420a4a998 --- /dev/null +++ b/regression/contracts/frees-clause-while-loop-pass/test.desc @@ -0,0 +1,9 @@ +CORE +main.c +--apply-loop-contracts +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +This test checks that frees clauses are parsed and typechecked for while loops. diff --git a/src/ansi-c/ansi_c_convert_type.cpp b/src/ansi-c/ansi_c_convert_type.cpp index cb5beb98a2c..270daf1da2a 100644 --- a/src/ansi-c/ansi_c_convert_type.cpp +++ b/src/ansi-c/ansi_c_convert_type.cpp @@ -273,6 +273,14 @@ void ansi_c_convert_typet::read_rec(const typet &type) for(const exprt &target : to_unary_expr(as_expr).op().operands()) assigns.push_back(target); } + else if(type.id() == ID_C_spec_frees) + { + const exprt &as_expr = + static_cast(static_cast(type)); + + for(const exprt &target : to_unary_expr(as_expr).op().operands()) + frees.push_back(target); + } else if(type.id() == ID_C_spec_ensures) { const exprt &as_expr = @@ -341,6 +349,9 @@ void ansi_c_convert_typet::write(typet &type) if(!assigns.empty()) to_code_with_contract_type(type).assigns() = std::move(assigns); + if(!frees.empty()) + to_code_with_contract_type(type).frees() = std::move(frees); + if(!ensures.empty()) to_code_with_contract_type(type).ensures() = std::move(ensures); diff --git a/src/ansi-c/ansi_c_convert_type.h b/src/ansi-c/ansi_c_convert_type.h index 83f65ffe3ff..1eabc279006 100644 --- a/src/ansi-c/ansi_c_convert_type.h +++ b/src/ansi-c/ansi_c_convert_type.h @@ -47,7 +47,7 @@ class ansi_c_convert_typet:public messaget bool constructor, destructor; // contracts - exprt::operandst assigns, ensures, requires, ensures_contract, + exprt::operandst assigns, frees, ensures, requires, ensures_contract, requires_contract; // storage spec diff --git a/src/ansi-c/ansi_c_internal_additions.cpp b/src/ansi-c/ansi_c_internal_additions.cpp index 6ea08fb5b44..b9952f9a4d2 100644 --- a/src/ansi-c/ansi_c_internal_additions.cpp +++ b/src/ansi-c/ansi_c_internal_additions.cpp @@ -216,6 +216,21 @@ void ansi_c_internal_additions(std::string &code) // This function needs to be declared, or otherwise can't be called // by the entry-point construction. "void " INITIALIZE_FUNCTION "(void);\n" + "\n" + // frame specifications for contracts + // Type that describes assignable memory locations + "typedef void " CPROVER_PREFIX "assignable_t;\n" + // Declares a range of bytes as assignable (internal representation) + CPROVER_PREFIX "assignable_t " CPROVER_PREFIX "assignable(void *ptr, " + CPROVER_PREFIX "size_t size," + CPROVER_PREFIX "bool is_ptr_to_ptr);\n" + // Declares a range of bytes as assignable + CPROVER_PREFIX "assignable_t " CPROVER_PREFIX "object_upto(void *ptr, " + CPROVER_PREFIX "size_t size);\n" + // Declares bytes from ptr to the end of the object as assignable + CPROVER_PREFIX "assignable_t " CPROVER_PREFIX "object_from(void *ptr);\n" + // Declares the whole object pointer to by ptr + CPROVER_PREFIX "assignable_t " CPROVER_PREFIX "whole_object(void *ptr);\n" "\n"; // clang-format on diff --git a/src/ansi-c/c_typecheck_base.cpp b/src/ansi-c/c_typecheck_base.cpp index a10ae5847e2..c772d78eca8 100644 --- a/src/ansi-c/c_typecheck_base.cpp +++ b/src/ansi-c/c_typecheck_base.cpp @@ -777,6 +777,24 @@ void c_typecheck_baset::typecheck_declaration( CPROVER_PREFIX "loop_entry is not allowed in preconditions."); }; + auto check_return_value = [&](const exprt &expr) { + const irep_idt id = CPROVER_PREFIX "return_value"; + + auto pred = [&](const exprt &expr) { + if(!can_cast_expr(expr)) + return false; + + return to_symbol_expr(expr).get_identifier() == id; + }; + + if(!has_subexpr(expr, pred)) + return; + + error().source_location = expr.source_location(); + error() << id2string(id) + "is not allowed in preconditions." << eom; + throw 0; + }; + // check the contract, if any symbolt &new_symbol = symbol_table.get_writeable_ref(identifier); if( @@ -833,6 +851,8 @@ void c_typecheck_baset::typecheck_declaration( typecheck_expr(requires); implicit_typecast_bool(requires); check_history_expr(requires); + check_return_value(requires); + check_return_value(requires); lambda_exprt lambda{temporary_parameter_symbols, requires}; lambda.add_source_location() = requires.source_location(); requires.swap(lambda); @@ -846,6 +866,14 @@ void c_typecheck_baset::typecheck_declaration( assigns.swap(lambda); } + typecheck_spec_frees(code_type.frees()); + for(auto &frees : code_type.frees()) + { + lambda_exprt lambda{temporary_parameter_symbols, frees}; + lambda.add_source_location() = frees.source_location(); + frees.swap(lambda); + } + for(auto &expr : code_type.ensures_contract()) { typecheck_spec_function_pointer_obeys_contract(expr); @@ -898,6 +926,8 @@ void c_typecheck_baset::typecheck_declaration( // Remove the contract from the original symbol as we have created a // dedicated contract symbol. new_symbol.type.remove(ID_C_spec_assigns); + new_symbol.type.remove(ID_C_spec_frees); + new_symbol.type.remove(ID_C_spec_frees); new_symbol.type.remove(ID_C_spec_ensures); new_symbol.type.remove(ID_C_spec_ensures_contract); new_symbol.type.remove(ID_C_spec_requires); diff --git a/src/ansi-c/c_typecheck_base.h b/src/ansi-c/c_typecheck_base.h index 10dbb1f8196..f9080af5eb8 100644 --- a/src/ansi-c/c_typecheck_base.h +++ b/src/ansi-c/c_typecheck_base.h @@ -148,10 +148,17 @@ class c_typecheck_baset: // contracts virtual void typecheck_spec_function_pointer_obeys_contract(exprt &expr); virtual void typecheck_spec_assigns(exprt::operandst &targets); - virtual void typecheck_spec_assigns_condition(exprt &condition); + virtual void typecheck_spec_frees(exprt::operandst &targets); + virtual void typecheck_conditional_targets( + exprt::operandst &targets, + const std::function typecheck_target, + const std::string &clause_type); + virtual void typecheck_spec_condition(exprt &condition); virtual void typecheck_spec_assigns_target(exprt &target); + virtual void typecheck_spec_frees_target(exprt &target); virtual void typecheck_spec_loop_invariant(codet &code); virtual void typecheck_spec_decreases(codet &code); + virtual void throw_on_side_effects(const exprt &expr); bool break_is_allowed; bool continue_is_allowed; diff --git a/src/ansi-c/c_typecheck_code.cpp b/src/ansi-c/c_typecheck_code.cpp index a27585d8022..94f1bde6272 100644 --- a/src/ansi-c/c_typecheck_code.cpp +++ b/src/ansi-c/c_typecheck_code.cpp @@ -505,6 +505,12 @@ void c_typecheck_baset::typecheck_for(codet &code) typecheck_spec_assigns( static_cast(code.add(ID_C_spec_assigns)).op().operands()); } + + if(code.find(ID_C_spec_frees).is_not_nil()) + { + typecheck_spec_frees( + static_cast(code.add(ID_C_spec_frees)).op().operands()); + } } void c_typecheck_baset::typecheck_label(code_labelt &code) @@ -805,6 +811,12 @@ void c_typecheck_baset::typecheck_while(code_whilet &code) typecheck_spec_assigns( static_cast(code.add(ID_C_spec_assigns)).op().operands()); } + + if(code.find(ID_C_spec_frees).is_not_nil()) + { + typecheck_spec_frees( + static_cast(code.add(ID_C_spec_frees)).op().operands()); + } } void c_typecheck_baset::typecheck_dowhile(code_dowhilet &code) @@ -845,9 +857,15 @@ void c_typecheck_baset::typecheck_dowhile(code_dowhilet &code) typecheck_spec_assigns( static_cast(code.add(ID_C_spec_assigns)).op().operands()); } + + if(code.find(ID_C_spec_frees).is_not_nil()) + { + typecheck_spec_frees( + static_cast(code.add(ID_C_spec_frees)).op().operands()); + } } -void c_typecheck_baset::typecheck_spec_assigns_condition(exprt &condition) +void c_typecheck_baset::typecheck_spec_condition(exprt &condition) { // compute type typecheck_expr(condition); @@ -903,13 +921,36 @@ void c_typecheck_baset::typecheck_spec_assigns_condition(exprt &condition) throw 0; } +void c_typecheck_baset::throw_on_side_effects(const exprt &expr) +{ + if(has_subexpr(expr, ID_side_effect)) + { + error().source_location = expr.source_location(); + error() << "side-effects not allowed in assigns clause targets" + << messaget::eom; + throw 0; + } + if(has_subexpr(expr, ID_if)) + { + error().source_location = expr.source_location(); + error() << "ternary expressions not allowed in assigns " + "clause targets" + << messaget::eom; + throw 0; + } +} + void c_typecheck_baset::typecheck_spec_assigns_target(exprt &target) { // compute type typecheck_expr(target); // fatal errors - if(target.type().id() == ID_empty) + bool is_empty_type = target.type().id() == ID_empty; + bool is_assignable_typedef = + target.type().get(ID_C_typedef) == CPROVER_PREFIX "assignable_t"; + // only allow void type if it is the typedef CPROVER_PREFIX "assignable_t" + if(target.type().id() == ID_empty && !is_assignable_typedef) { error().source_location = target.source_location(); error() << "void-typed expressions not allowed as assigns clause targets" @@ -917,25 +958,6 @@ void c_typecheck_baset::typecheck_spec_assigns_target(exprt &target) throw 0; } - // throws exception if expr contains side effect or ternary expr - auto throw_on_side_effects = [&](const exprt &expr) { - if(has_subexpr(expr, ID_side_effect)) - { - error().source_location = expr.source_location(); - error() << "side-effects not allowed in assigns clause targets" - << messaget::eom; - throw 0; - } - if(has_subexpr(expr, ID_if)) - { - error().source_location = expr.source_location(); - error() << "ternary expressions not allowed in assigns " - "clause targets" - << messaget::eom; - throw 0; - } - }; - if(target.get_bool(ID_C_lvalue)) { throw_on_side_effects(target); @@ -950,37 +972,53 @@ void c_typecheck_baset::typecheck_spec_assigns_target(exprt &target) if(can_cast_expr(funcall.function())) { const auto &ident = to_symbol_expr(funcall.function()).get_identifier(); - if( - ident == CPROVER_PREFIX "object_from" || ident == CPROVER_PREFIX - "object_slice") - { - for(const auto &argument : funcall.arguments()) - throw_on_side_effects(argument); - } - else + if(!(is_empty_type && is_assignable_typedef)) { error().source_location = target.source_location(); - error() << "function calls in assigns clause targets must be " - "to " CPROVER_PREFIX "object_from, " CPROVER_PREFIX - "object_slice" + error() << "expecting " CPROVER_PREFIX + "assignable_t return type for function " + + id2string(ident) + " called in assigns clause" << eom; throw 0; } + for(const auto &argument : funcall.arguments()) + throw_on_side_effects(argument); } else { error().source_location = target.source_location(); - error() << "function pointer calls not allowed in assigns targets" << eom; + error() << "function pointer calls not allowed in assigns clauses" << eom; throw 0; } } else { error().source_location = target.source_location(); - error() << "assigns clause target must be an lvalue or a " CPROVER_PREFIX - "POINTER_OBJECT, " CPROVER_PREFIX "object_from, " CPROVER_PREFIX - "object_slice expression" - << eom; + error() + << "assigns clause target must be an lvalue or a call to " CPROVER_PREFIX + "POINTER_OBJECT or to a function returning " CPROVER_PREFIX + "assignable_t" + << eom; + throw 0; + } +} + +void c_typecheck_baset::typecheck_spec_frees_target(exprt &target) +{ + // compute type + typecheck_expr(target); + const auto &type = target.type(); + + if(can_cast_type(type)) + { + // an expression with pointer-type without side effects + throw_on_side_effects(target); + } + else + { + // anything else is rejected + error().source_location = target.source_location(); + error() << "frees clause target must be a pointer-typed expression" << eom; throw 0; } } @@ -1067,7 +1105,10 @@ void c_typecheck_baset::typecheck_spec_function_pointer_obeys_contract( } } -void c_typecheck_baset::typecheck_spec_assigns(exprt::operandst &targets) +void c_typecheck_baset::typecheck_conditional_targets( + exprt::operandst &targets, + const std::function typecheck_target, + const std::string &clause_type) { exprt::operandst tmp; bool must_throw = false; @@ -1078,8 +1119,8 @@ void c_typecheck_baset::typecheck_spec_assigns(exprt::operandst &targets) { must_throw = true; error().source_location = target.source_location(); - error() << "expected ID_conditional_target_group expression in assigns " - "clause, found " + error() << "expected ID_conditional_target_group expression in " + + clause_type + "clause, found " << id2string(target.id()) << eom; } @@ -1088,7 +1129,7 @@ void c_typecheck_baset::typecheck_spec_assigns(exprt::operandst &targets) // typecheck condition auto &condition = conditional_target_group.condition(); - typecheck_spec_assigns_condition(condition); + typecheck_spec_condition(condition); if(condition.is_true()) { @@ -1096,7 +1137,7 @@ void c_typecheck_baset::typecheck_spec_assigns(exprt::operandst &targets) // simplify expr and expose the bare expressions for(auto &actual_target : conditional_target_group.targets()) { - typecheck_spec_assigns_target(actual_target); + typecheck_target(actual_target); tmp.push_back(actual_target); } } @@ -1105,7 +1146,7 @@ void c_typecheck_baset::typecheck_spec_assigns(exprt::operandst &targets) // if the condition is not trivially true, typecheck in place for(auto &actual_target : conditional_target_group.targets()) { - typecheck_spec_assigns_target(actual_target); + typecheck_target(actual_target); } tmp.push_back(std::move(target)); } @@ -1123,6 +1164,22 @@ void c_typecheck_baset::typecheck_spec_assigns(exprt::operandst &targets) std::swap(targets, tmp); } +void c_typecheck_baset::typecheck_spec_frees(exprt::operandst &targets) +{ + const std::function typecheck_target = [&](exprt &target) { + typecheck_spec_frees_target(target); + }; + typecheck_conditional_targets(targets, typecheck_target, "frees"); +} + +void c_typecheck_baset::typecheck_spec_assigns(exprt::operandst &targets) +{ + const std::function typecheck_target = [&](exprt &target) { + typecheck_spec_assigns_target(target); + }; + typecheck_conditional_targets(targets, typecheck_target, "assigns"); +} + void c_typecheck_baset::typecheck_spec_loop_invariant(codet &code) { if(code.find(ID_C_spec_loop_invariant).is_not_nil()) diff --git a/src/ansi-c/c_typecheck_expr.cpp b/src/ansi-c/c_typecheck_expr.cpp index 7e560ec7c47..d1642497a60 100644 --- a/src/ansi-c/c_typecheck_expr.cpp +++ b/src/ansi-c/c_typecheck_expr.cpp @@ -492,7 +492,9 @@ void c_typecheck_baset::typecheck_expr_main(exprt &expr) { // already type checked } - else if(expr.id() == ID_C_spec_assigns || expr.id() == ID_target_list) + else if( + expr.id() == ID_C_spec_assigns || expr.id() == ID_C_spec_frees || + expr.id() == ID_target_list) { // already type checked } @@ -1949,8 +1951,54 @@ void c_typecheck_baset::typecheck_side_effect_function_call( if(symbol_table.symbols.find(identifier)==symbol_table.symbols.end()) { // This is an undeclared function. + + // Is it the polymorphic typed_target function ? + if(identifier == CPROVER_PREFIX "typed_target") + { + if(expr.arguments().size() != 1) + { + error().source_location = f_op.source_location(); + error() << "expected 1 argument for " << CPROVER_PREFIX "typed_target" + << " found " << expr.arguments().size() << eom; + throw 0; + } + + auto arg0 = expr.arguments().at(0); + typecheck_expr(arg0); + if(!is_assignable(arg0) || !arg0.get_bool(ID_C_lvalue)) + { + error().source_location = arg0.source_location(); + error() << "argument of " << CPROVER_PREFIX "typed_target" + << "must be assignable" << eom; + throw 0; + } + const auto &size = size_of_expr(arg0.type(), *this); + if(!size.has_value()) + { + error().source_location = arg0.source_location(); + error() << "sizeof not defined for argument of " + << CPROVER_PREFIX "typed_target" + << " of type " << to_string(arg0.type()) << eom; + throw 0; + } + // rewrite call to "assignable" + to_symbol_expr(f_op).set_identifier(CPROVER_PREFIX "assignable"); + exprt::operandst arguments; + // pointer + arguments.push_back(address_of_exprt(arg0)); + // size + arguments.push_back(size.value()); + // is_pointer + if(arg0.type().id() == ID_pointer) + arguments.push_back(true_exprt()); + else + arguments.push_back(false_exprt()); + + expr.arguments().swap(arguments); + typecheck_side_effect_function_call(expr); + } // Is this a builtin? - if(!builtin_factory(identifier, symbol_table, get_message_handler())) + else if(!builtin_factory(identifier, symbol_table, get_message_handler())) { // yes, it's a builtin } diff --git a/src/ansi-c/cprover_builtin_headers.h b/src/ansi-c/cprover_builtin_headers.h index 17876ea8981..43d4e53eee2 100644 --- a/src/ansi-c/cprover_builtin_headers.h +++ b/src/ansi-c/cprover_builtin_headers.h @@ -134,6 +134,9 @@ __CPROVER_bool __CPROVER_overflow_unary_minus(); __CPROVER_bool __CPROVER_enum_is_in_range(); // contracts -__CPROVER_size_t __CPROVER_object_from(void *); -__CPROVER_size_t __CPROVER_object_slice(void *, __CPROVER_size_t); +__CPROVER_assignable_t __CPROVER_assignable(void *ptr, __CPROVER_size_t size, + __CPROVER_bool is_ptr_to_ptr); +__CPROVER_assignable_t __CPROVER_whole_object(void *ptr); +__CPROVER_assignable_t __CPROVER_object_from(void *ptr); +__CPROVER_assignable_t __CPROVER_object_upto(void *ptr, __CPROVER_size_t size); // clang-format on diff --git a/src/ansi-c/library/cprover.h b/src/ansi-c/library/cprover.h index 16b17990ea8..61bf2687c68 100644 --- a/src/ansi-c/library/cprover.h +++ b/src/ansi-c/library/cprover.h @@ -17,6 +17,8 @@ Author: Daniel Kroening, kroening@kroening.com typedef __typeof__(sizeof(int)) __CPROVER_size_t; // NOLINTNEXTLINE(readability/identifiers) typedef signed long long __CPROVER_ssize_t; +// NOLINTNEXTLINE(readability/identifiers) +typedef void __CPROVER_assignable_t; void *__CPROVER_allocate(__CPROVER_size_t size, __CPROVER_bool zero); void __CPROVER_deallocate(void *); diff --git a/src/ansi-c/parser.y b/src/ansi-c/parser.y index b8f187e3050..63090755cbc 100644 --- a/src/ansi-c/parser.y +++ b/src/ansi-c/parser.y @@ -209,6 +209,7 @@ extern char *yyansi_ctext; %token TOK_CPROVER_ENSURES_CONTRACT "__CPROVER_ensures_contract" %token TOK_CPROVER_ENSURES "__CPROVER_ensures" %token TOK_CPROVER_ASSIGNS "__CPROVER_assigns" +%token TOK_CPROVER_FREES "__CPROVER_frees" %token TOK_IMPLIES "==>" %token TOK_EQUIVALENT "<==>" %token TOK_XORXOR "^^" @@ -2449,26 +2450,31 @@ declaration_or_expression_statement: iteration_statement: TOK_WHILE '(' comma_expression_opt ')' cprover_contract_assigns_opt - cprover_contract_loop_invariant_list_opt + cprover_contract_frees_opt + cprover_contract_loop_invariant_list_opt cprover_contract_decreases_opt statement { $$=$1; statement($$, ID_while); - parser_stack($$).add_to_operands(std::move(parser_stack($3)), std::move(parser_stack($8))); + parser_stack($$).add_to_operands(std::move(parser_stack($3)), std::move(parser_stack($9))); if(!parser_stack($5).operands().empty()) static_cast(parser_stack($$).add(ID_C_spec_assigns)).operands().swap(parser_stack($5).operands()); if(!parser_stack($6).operands().empty()) - static_cast(parser_stack($$).add(ID_C_spec_loop_invariant)).operands().swap(parser_stack($6).operands()); + static_cast(parser_stack($$).add(ID_C_spec_frees)).operands().swap(parser_stack($6).operands()); if(!parser_stack($7).operands().empty()) - static_cast(parser_stack($$).add(ID_C_spec_decreases)).operands().swap(parser_stack($7).operands()); + static_cast(parser_stack($$).add(ID_C_spec_loop_invariant)).operands().swap(parser_stack($7).operands()); + + if(!parser_stack($8).operands().empty()) + static_cast(parser_stack($$).add(ID_C_spec_decreases)).operands().swap(parser_stack($8).operands()); } | TOK_DO statement TOK_WHILE '(' comma_expression ')' cprover_contract_assigns_opt - cprover_contract_loop_invariant_list_opt + cprover_contract_frees_opt + cprover_contract_loop_invariant_list_opt cprover_contract_decreases_opt ';' { $$=$1; @@ -2479,10 +2485,13 @@ iteration_statement: static_cast(parser_stack($$).add(ID_C_spec_assigns)).operands().swap(parser_stack($7).operands()); if(!parser_stack($8).operands().empty()) - static_cast(parser_stack($$).add(ID_C_spec_loop_invariant)).operands().swap(parser_stack($8).operands()); + static_cast(parser_stack($$).add(ID_C_spec_frees)).operands().swap(parser_stack($8).operands()); if(!parser_stack($9).operands().empty()) - static_cast(parser_stack($$).add(ID_C_spec_decreases)).operands().swap(parser_stack($9).operands()); + static_cast(parser_stack($$).add(ID_C_spec_loop_invariant)).operands().swap(parser_stack($9).operands()); + + if(!parser_stack($10).operands().empty()) + static_cast(parser_stack($$).add(ID_C_spec_decreases)).operands().swap(parser_stack($10).operands()); } | TOK_FOR { @@ -2497,7 +2506,8 @@ iteration_statement: comma_expression_opt ';' comma_expression_opt ')' cprover_contract_assigns_opt - cprover_contract_loop_invariant_list_opt + cprover_contract_frees_opt + cprover_contract_loop_invariant_list_opt cprover_contract_decreases_opt statement { @@ -2507,16 +2517,19 @@ iteration_statement: mto($$, $4); mto($$, $5); mto($$, $7); - mto($$, $12); + mto($$, $13); if(!parser_stack($9).operands().empty()) static_cast(parser_stack($$).add(ID_C_spec_assigns)).operands().swap(parser_stack($9).operands()); if(!parser_stack($10).operands().empty()) - static_cast(parser_stack($$).add(ID_C_spec_loop_invariant)).operands().swap(parser_stack($10).operands()); + static_cast(parser_stack($$).add(ID_C_spec_frees)).operands().swap(parser_stack($10).operands()); if(!parser_stack($11).operands().empty()) - static_cast(parser_stack($$).add(ID_C_spec_decreases)).operands().swap(parser_stack($11).operands()); + static_cast(parser_stack($$).add(ID_C_spec_loop_invariant)).operands().swap(parser_stack($11).operands()); + + if(!parser_stack($12).operands().empty()) + static_cast(parser_stack($$).add(ID_C_spec_decreases)).operands().swap(parser_stack($12).operands()); if(PARSER.for_has_scope) PARSER.pop_scope(); // remove the C99 for-scope @@ -3319,6 +3332,7 @@ cprover_function_contract: parser_stack($$).add_to_operands(std::move(tmp)); } | cprover_contract_assigns + | cprover_contract_frees ; unary_expression_list: @@ -3372,7 +3386,7 @@ conditional_target_list_opt_semicol: } | conditional_target_list { - $$ = $1; + $$ = $1; } cprover_contract_assigns: @@ -3396,6 +3410,27 @@ cprover_contract_assigns_opt: | cprover_contract_assigns ; +cprover_contract_frees: + TOK_CPROVER_FREES '(' conditional_target_list_opt_semicol ')' + { + $$=$1; + set($$, ID_C_spec_frees); + mto($$, $3); + } + | TOK_CPROVER_FREES '(' ')' + { + $$=$1; + set($$, ID_C_spec_frees); + parser_stack($$).add_to_operands(exprt(ID_target_list)); + } + ; + +cprover_contract_frees_opt: + /* nothing */ + { init($$); parser_stack($$).make_nil(); } + | cprover_contract_frees + ; + cprover_function_contract_sequence: cprover_function_contract | cprover_function_contract_sequence cprover_function_contract diff --git a/src/ansi-c/scanner.l b/src/ansi-c/scanner.l index 210e6866dac..05faae82d62 100644 --- a/src/ansi-c/scanner.l +++ b/src/ansi-c/scanner.l @@ -1292,6 +1292,7 @@ __decltype { if(PARSER.cpp98 && {CPROVER_PREFIX}"requires" { loc(); return TOK_CPROVER_REQUIRES; } {CPROVER_PREFIX}"ensures" { loc(); return TOK_CPROVER_ENSURES; } {CPROVER_PREFIX}"assigns" { loc(); return TOK_CPROVER_ASSIGNS; } +{CPROVER_PREFIX}"frees" { loc(); return TOK_CPROVER_FREES; } {CPROVER_PREFIX}"requires_contract" { loc(); return TOK_CPROVER_REQUIRES_CONTRACT; } {CPROVER_PREFIX}"ensures_contract" { loc(); return TOK_CPROVER_ENSURES_CONTRACT; } diff --git a/src/goto-instrument/contracts/instrument_spec_assigns.cpp b/src/goto-instrument/contracts/instrument_spec_assigns.cpp index f56f4a47d27..0f1956e99d3 100644 --- a/src/goto-instrument/contracts/instrument_spec_assigns.cpp +++ b/src/goto-instrument/contracts/instrument_spec_assigns.cpp @@ -505,7 +505,7 @@ car_exprt instrument_spec_assignst::create_car_expr( upper_bound_var, car_havoc_methodt::HAVOC_SLICE}; } - if(ident == CPROVER_PREFIX "object_slice") + else if(ident == CPROVER_PREFIX "object_upto") { const auto &ptr = funcall.arguments().at(0); const auto &size = funcall.arguments().at(1); @@ -519,6 +519,44 @@ car_exprt instrument_spec_assignst::create_car_expr( upper_bound_var, car_havoc_methodt::HAVOC_SLICE}; } + else if(ident == CPROVER_PREFIX "whole_object") + { + const auto &ptr = funcall.arguments().at(0); + return { + condition, + target, + minus_exprt( + typecast_exprt::conditional_cast(ptr, pointer_type(char_type())), + pointer_offset(ptr)), + typecast_exprt::conditional_cast(object_size(ptr), size_type()), + valid_var, + lower_bound_var, + upper_bound_var, + car_havoc_methodt::HAVOC_OBJECT}; + } + else if(ident == CPROVER_PREFIX "assignable") + { + const auto &ptr = funcall.arguments().at(0); + const auto &size = funcall.arguments().at(1); + const auto &is_ptr_to_ptr = funcall.arguments().at(2); + return { + condition, + target, + typecast_exprt::conditional_cast(ptr, pointer_type(char_type())), + typecast_exprt::conditional_cast(size, size_type()), + valid_var, + lower_bound_var, + upper_bound_var, + is_ptr_to_ptr.is_true() ? car_havoc_methodt::NONDET_ASSIGN + : car_havoc_methodt::HAVOC_SLICE}; + } + else + { + log.error().source_location = target.source_location(); + log.error() << "call to " + id2string(ident) + + " in assigns clauses not supported in " + "this version"; + } } } else if(is_assignable(target)) diff --git a/src/goto-programs/goto_convert.cpp b/src/goto-programs/goto_convert.cpp index f2ffb1df759..460e1bac1b2 100644 --- a/src/goto-programs/goto_convert.cpp +++ b/src/goto-programs/goto_convert.cpp @@ -870,6 +870,13 @@ void goto_convertt::convert_loop_contracts( loop->condition_nonconst().add(ID_C_spec_assigns).swap(assigns.op()); } + auto frees = static_cast(code.find(ID_C_spec_frees)); + if(frees.is_not_nil()) + { + PRECONDITION(loop->is_goto()); + loop->condition_nonconst().add(ID_C_spec_frees).swap(frees.op()); + } + auto invariant = static_cast(code.find(ID_C_spec_loop_invariant)); if(!invariant.is_nil()) diff --git a/src/linking/remove_internal_symbols.cpp b/src/linking/remove_internal_symbols.cpp index 5563b1b835c..09c3ab4f73c 100644 --- a/src/linking/remove_internal_symbols.cpp +++ b/src/linking/remove_internal_symbols.cpp @@ -151,6 +151,11 @@ void remove_internal_symbols( special.insert(INITIALIZE_FUNCTION); special.insert(CPROVER_PREFIX "deallocated"); special.insert(CPROVER_PREFIX "dead_object"); + special.insert(CPROVER_PREFIX "assignable_t"); + special.insert(CPROVER_PREFIX "assignable"); + special.insert(CPROVER_PREFIX "object_upto"); + special.insert(CPROVER_PREFIX "object_from"); + special.insert(CPROVER_PREFIX "whole_object"); special.insert(rounding_mode_identifier()); special.insert("__new"); special.insert("__new_array"); diff --git a/src/util/c_types.h b/src/util/c_types.h index e9b922a709f..59afc5885a3 100644 --- a/src/util/c_types.h +++ b/src/util/c_types.h @@ -370,7 +370,7 @@ class code_with_contract_typet : public code_typet { return !ensures().empty() || !ensures_contract().empty() || !requires().empty() || !requires_contract().empty() || - !assigns().empty(); + !assigns().empty() || !frees().empty(); } const exprt::operandst &assigns() const @@ -383,6 +383,16 @@ class code_with_contract_typet : public code_typet return static_cast(add(ID_C_spec_assigns)).operands(); } + const exprt::operandst &frees() const + { + return static_cast(find(ID_C_spec_frees)).operands(); + } + + exprt::operandst &frees() + { + return static_cast(add(ID_C_spec_frees)).operands(); + } + const exprt::operandst &requires_contract() const { return static_cast(find(ID_C_spec_requires_contract)) diff --git a/src/util/irep_ids.def b/src/util/irep_ids.def index 2704262ec24..90a931665a5 100644 --- a/src/util/irep_ids.def +++ b/src/util/irep_ids.def @@ -582,6 +582,7 @@ IREP_ID_TWO(C_spec_requires, #spec_requires) IREP_ID_TWO(C_spec_ensures_contract, #spec_ensures_contract) IREP_ID_TWO(C_spec_ensures, #spec_ensures) IREP_ID_TWO(C_spec_assigns, #spec_assigns) +IREP_ID_TWO(C_spec_frees, #spec_frees) IREP_ID_ONE(target_list) IREP_ID_ONE(conditional_target_group) IREP_ID_ONE(function_pointer_obeys_contract) diff --git a/src/util/rename_symbol.cpp b/src/util/rename_symbol.cpp index 41755fdb049..9b950c0d013 100644 --- a/src/util/rename_symbol.cpp +++ b/src/util/rename_symbol.cpp @@ -171,6 +171,14 @@ bool rename_symbolt::rename(typet &dest) const result = false; } + const exprt &spec_frees = + static_cast(dest.find(ID_C_spec_frees)); + if(spec_frees.is_not_nil() && have_to_rename(spec_frees)) + { + rename(static_cast(dest.add(ID_C_spec_frees))); + result = false; + } + const exprt &spec_ensures = static_cast(dest.find(ID_C_spec_ensures)); if(spec_ensures.is_not_nil() && have_to_rename(spec_ensures)) diff --git a/src/util/replace_symbol.cpp b/src/util/replace_symbol.cpp index 2d2cf061482..dd53c6ad3e4 100644 --- a/src/util/replace_symbol.cpp +++ b/src/util/replace_symbol.cpp @@ -237,6 +237,11 @@ bool replace_symbolt::replace(typet &dest) const if(spec_assigns.is_not_nil() && have_to_replace(spec_assigns)) result &= replace(static_cast(dest.add(ID_C_spec_assigns))); + const exprt &spec_frees = + static_cast(dest.find(ID_C_spec_frees)); + if(spec_frees.is_not_nil() && have_to_replace(spec_frees)) + result &= replace(static_cast(dest.add(ID_C_spec_frees))); + const exprt &spec_ensures = static_cast(dest.find(ID_C_spec_ensures)); if(spec_ensures.is_not_nil() && have_to_replace(spec_ensures)) From 3b159aeef35b3e461ffcb3bb7e88a81895a1dc49 Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Tue, 23 Aug 2022 13:00:58 -0400 Subject: [PATCH 02/20] 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. --- doc/cprover-manual/contracts-frees.md | 56 +++++++++++++++++++ .../frees-clause-and-predicates-fail/main.c | 46 +++++++++++++++ .../test.desc | 10 ++++ .../frees-clause-and-predicates-fail2/main.c | 43 ++++++++++++++ .../test.desc | 11 ++++ .../frees-clause-and-predicates/main.c | 44 +++++++++++++++ .../frees-clause-and-predicates/test.desc | 17 ++++++ .../frees-clause-for-loop-fail/test.desc | 2 +- .../frees-clause-function-fail/test.desc | 2 +- .../frees-clause-while-loop-fail/test.desc | 2 +- src/ansi-c/ansi_c_internal_additions.cpp | 8 +++ src/ansi-c/c_typecheck_base.cpp | 23 +++++++- src/ansi-c/c_typecheck_code.cpp | 34 ++++++++++- src/ansi-c/c_typecheck_expr.cpp | 24 ++++++++ src/ansi-c/cprover_builtin_headers.h | 3 + src/ansi-c/library/cprover.h | 2 + src/linking/remove_internal_symbols.cpp | 4 ++ 17 files changed, 324 insertions(+), 7 deletions(-) create mode 100644 regression/contracts/frees-clause-and-predicates-fail/main.c create mode 100644 regression/contracts/frees-clause-and-predicates-fail/test.desc create mode 100644 regression/contracts/frees-clause-and-predicates-fail2/main.c create mode 100644 regression/contracts/frees-clause-and-predicates-fail2/test.desc create mode 100644 regression/contracts/frees-clause-and-predicates/main.c create mode 100644 regression/contracts/frees-clause-and-predicates/test.desc diff --git a/doc/cprover-manual/contracts-frees.md b/doc/cprover-manual/contracts-frees.md index 7b8134ce5c8..f9d00c2b502 100644 --- a/doc/cprover-manual/contracts-frees.md +++ b/doc/cprover-manual/contracts-frees.md @@ -78,3 +78,59 @@ When replacing a function call or a loop by a contract, each pointer of the _frees_ clause is non-deterministically freed after the function call or after the loop. +# Using functions to specify parametric sets of freeable pointers + +Users can define parametric sets of freeable pointers by writing functions that +return the built-in type `__CPROVER_freeable_t` and call the built-in function +`__CPROVER_freeable` or any user-defined function returning +`__CPROVER_freeable_t`: + +```c +__CPROVER_freeable_t my_freeable_set(char **arr, size_t size) +{ + if (arr && size > 3) { + __CPROVER_freeable(arr[0]); + __CPROVER_freeable(arr[1]); + __CPROVER_freeable(arr[2]); + } +} +``` + +The built-in function: + +```c +__CPROVER_freeable_t __CPROVER_freeable(void *ptr); +``` +adds the given pointer to the freeable set described by the enclosing function. + +Calls to functions returning `__CPROVER_freeable_t` can then be used as targets +in `__CPROVER_frees` clauses: + +```c +void my_function(char **arr, size_t size) +__CPROVER_frees(my_freeable_set(arr, size)) +{ + ... +} +``` + +# Frees clause related predicates + +The predicate: + +```c +__CPROVER_bool __CPROVER_is_freeable(void *ptr); +``` +can only be used in pre and post conditions, in contract checking or replacement +modes. It returns `true` if and only if the pointer satisfies the preconditions +of the `free` function from `stdlib.h`, that is if and only if the pointer +points to a valid dynamically allocated object and has offset zero. + +The predicate: + +```c +__CPROVER_bool __CPROVER_is_freed(void *ptr); +``` +can only be used in post conditions and returns `true` if and only if the +pointer was freed during the execution of the function or the loop under +analysis. diff --git a/regression/contracts/frees-clause-and-predicates-fail/main.c b/regression/contracts/frees-clause-and-predicates-fail/main.c new file mode 100644 index 00000000000..7285a551e6d --- /dev/null +++ b/regression/contracts/frees-clause-and-predicates-fail/main.c @@ -0,0 +1,46 @@ +#include + +// A function defining a conditionally freeable target +__CPROVER_freeable_t +foo_frees(char *arr, const size_t size, const size_t new_size) +{ + __CPROVER_freeable(arr); +} + +char *foo(char *arr, const size_t size, const size_t new_size) + // clang-format off + // error is_freed cannot be used in preconditions +__CPROVER_requires(!__CPROVER_is_freed(arr)) +__CPROVER_requires(__CPROVER_is_freeable(arr)) +__CPROVER_assigns(__CPROVER_whole_object(arr)) +__CPROVER_frees(foo_frees(arr, size, new_size)) +__CPROVER_ensures( + (arr && new_size > size) ==> + __CPROVER_is_fresh(__CPROVER_return_value, new_size)) +__CPROVER_ensures( + (arr && new_size > size) ==> + __CPROVER_is_freed(__CPROVER_old(arr))) +__CPROVER_ensures( + !(arr && new_size > size) ==> + __CPROVER_return_value == __CPROVER_old(arr)) +// clang-format on +{ + if(arr && new_size > size) + { + free(arr); + return malloc(new_size); + } + else + { + return arr; + } +} + +int main() +{ + size_t size; + size_t new_size; + char *arr = malloc(size); + arr = foo(arr, size, new_size); + return 0; +} diff --git a/regression/contracts/frees-clause-and-predicates-fail/test.desc b/regression/contracts/frees-clause-and-predicates-fail/test.desc new file mode 100644 index 00000000000..7c01104d8f6 --- /dev/null +++ b/regression/contracts/frees-clause-and-predicates-fail/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--enforce-contract foo +^main.c.* error: __CPROVER_is_freed is not allowed in preconditions.$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test checks that the front end rejects __CPROVER_is_freed in preconditions. diff --git a/regression/contracts/frees-clause-and-predicates-fail2/main.c b/regression/contracts/frees-clause-and-predicates-fail2/main.c new file mode 100644 index 00000000000..bf23353d665 --- /dev/null +++ b/regression/contracts/frees-clause-and-predicates-fail2/main.c @@ -0,0 +1,43 @@ +#include + +// A function defining a conditionally freeable target +void foo_frees(char *arr, const size_t size, const size_t new_size) +{ + __CPROVER_freeable(arr); +} + +char *foo(char *arr, const size_t size, const size_t new_size) + // clang-format off +__CPROVER_requires(__CPROVER_is_freeable(arr)) +__CPROVER_assigns(__CPROVER_whole_object(arr)) +__CPROVER_frees(foo_frees(arr, size, new_size)) +__CPROVER_ensures( + (arr && new_size > size) ==> + __CPROVER_is_fresh(__CPROVER_return_value, new_size)) +__CPROVER_ensures( + (arr && new_size > size) ==> + __CPROVER_is_freed(__CPROVER_old(arr))) +__CPROVER_ensures( + !(arr && new_size > size) ==> + __CPROVER_return_value == __CPROVER_old(arr)) +// clang-format on +{ + if(arr && new_size > size) + { + free(arr); + return malloc(new_size); + } + else + { + return arr; + } +} + +int main() +{ + size_t size; + size_t new_size; + char *arr = malloc(size); + arr = foo(arr, size, new_size); + return 0; +} diff --git a/regression/contracts/frees-clause-and-predicates-fail2/test.desc b/regression/contracts/frees-clause-and-predicates-fail2/test.desc new file mode 100644 index 00000000000..57230e0a51c --- /dev/null +++ b/regression/contracts/frees-clause-and-predicates-fail2/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--enforce-contract foo +^main.c.* error: expecting __CPROVER_freeable_t return type for function foo_frees called in frees clause$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test checks that the front-end rejects non-__CPROVER_freeable_t-typed +function calls in frees clauses. diff --git a/regression/contracts/frees-clause-and-predicates/main.c b/regression/contracts/frees-clause-and-predicates/main.c new file mode 100644 index 00000000000..71a1b84771d --- /dev/null +++ b/regression/contracts/frees-clause-and-predicates/main.c @@ -0,0 +1,44 @@ +#include + +// A function defining a conditionally freeable target +__CPROVER_freeable_t +foo_frees(char *arr, const size_t size, const size_t new_size) +{ + __CPROVER_freeable(arr); +} + +char *foo(char *arr, const size_t size, const size_t new_size) + // clang-format off +__CPROVER_requires(__CPROVER_is_freeable(arr)) +__CPROVER_assigns(__CPROVER_whole_object(arr)) +__CPROVER_frees(foo_frees(arr, size, new_size)) +__CPROVER_ensures( + (arr && new_size > size) ==> + __CPROVER_is_fresh(__CPROVER_return_value, new_size)) +__CPROVER_ensures( + (arr && new_size > size) ==> + __CPROVER_is_freed(__CPROVER_old(arr))) +__CPROVER_ensures( + !(arr && new_size > size) ==> + __CPROVER_return_value == __CPROVER_old(arr)) +// clang-format on +{ + if(arr && new_size > size) + { + free(arr); + return malloc(new_size); + } + else + { + return arr; + } +} + +int main() +{ + size_t size; + size_t new_size; + char *arr = malloc(size); + arr = foo(arr, size, new_size); + return 0; +} diff --git a/regression/contracts/frees-clause-and-predicates/test.desc b/regression/contracts/frees-clause-and-predicates/test.desc new file mode 100644 index 00000000000..770c3f319e6 --- /dev/null +++ b/regression/contracts/frees-clause-and-predicates/test.desc @@ -0,0 +1,17 @@ +CORE +main.c +--enforce-contract foo +^\[foo.postcondition.\d+\] line \d+ Check ensures clause: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +This test checks that the front end parses and typchecks correct uses of: +- __CPROVER_freeable_t function calls as frees clause targets +- the predicate __CPROVER_freeable +- the predicate __CPROVER_is_freeable +- the predicate __CPROVER_is_freed + +The post condition of the contract is expected to fail because the predicates +have no interpretation in the back-end yet. diff --git a/regression/contracts/frees-clause-for-loop-fail/test.desc b/regression/contracts/frees-clause-for-loop-fail/test.desc index e39b9ce3349..84b08d72c84 100644 --- a/regression/contracts/frees-clause-for-loop-fail/test.desc +++ b/regression/contracts/frees-clause-for-loop-fail/test.desc @@ -1,7 +1,7 @@ CORE main.c --apply-loop-contracts -^main.c.*: error: frees clause target must be a pointer-typed expression$ +^main.c.*: error: frees clause target must be a pointer-typed expression or a call to a function returning __CPROVER_freeable_t$ ^CONVERSION ERROR$ ^EXIT=(1|64)$ ^SIGNAL=0$ diff --git a/regression/contracts/frees-clause-function-fail/test.desc b/regression/contracts/frees-clause-function-fail/test.desc index 6936f96e1ba..8c217f0a311 100644 --- a/regression/contracts/frees-clause-function-fail/test.desc +++ b/regression/contracts/frees-clause-function-fail/test.desc @@ -1,7 +1,7 @@ CORE main.c --enforce-contract foo -^main.c.*: error: frees clause target must be a pointer-typed expression$ +^main.c.* error: frees clause target must be a pointer-typed expression or a call to a function returning __CPROVER_freeable_t$ ^CONVERSION ERROR$ ^EXIT=(1|64)$ ^SIGNAL=0$ diff --git a/regression/contracts/frees-clause-while-loop-fail/test.desc b/regression/contracts/frees-clause-while-loop-fail/test.desc index e39b9ce3349..e2d39e5a965 100644 --- a/regression/contracts/frees-clause-while-loop-fail/test.desc +++ b/regression/contracts/frees-clause-while-loop-fail/test.desc @@ -1,7 +1,7 @@ CORE main.c --apply-loop-contracts -^main.c.*: error: frees clause target must be a pointer-typed expression$ +^main.c.* error: frees clause target must be a pointer-typed expression or a call to a function returning __CPROVER_freeable_t$ ^CONVERSION ERROR$ ^EXIT=(1|64)$ ^SIGNAL=0$ diff --git a/src/ansi-c/ansi_c_internal_additions.cpp b/src/ansi-c/ansi_c_internal_additions.cpp index b9952f9a4d2..fabb546b528 100644 --- a/src/ansi-c/ansi_c_internal_additions.cpp +++ b/src/ansi-c/ansi_c_internal_additions.cpp @@ -231,6 +231,14 @@ void ansi_c_internal_additions(std::string &code) CPROVER_PREFIX "assignable_t " CPROVER_PREFIX "object_from(void *ptr);\n" // Declares the whole object pointer to by ptr CPROVER_PREFIX "assignable_t " CPROVER_PREFIX "whole_object(void *ptr);\n" + // Type that describes sets of freeable pointers + "typedef void " CPROVER_PREFIX "freeable_t;\n" + // Declares a pointer as freeable + CPROVER_PREFIX "freeable_t " CPROVER_PREFIX "freeable(void *ptr);\n" + // True iff ptr satisfies the preconditions of the free stdlib function + CPROVER_PREFIX "bool " CPROVER_PREFIX "is_freeable(void *ptr);\n" + // True iff ptr was freed during function execution or loop execution + CPROVER_PREFIX "bool " CPROVER_PREFIX "is_freed(void *ptr);\n" "\n"; // clang-format on diff --git a/src/ansi-c/c_typecheck_base.cpp b/src/ansi-c/c_typecheck_base.cpp index c772d78eca8..ef07dde57f8 100644 --- a/src/ansi-c/c_typecheck_base.cpp +++ b/src/ansi-c/c_typecheck_base.cpp @@ -791,7 +791,25 @@ void c_typecheck_baset::typecheck_declaration( return; error().source_location = expr.source_location(); - error() << id2string(id) + "is not allowed in preconditions." << eom; + error() << id2string(id) + " is not allowed in preconditions." << eom; + throw 0; + }; + + auto check_is_freed = [&](const exprt &expr) { + const irep_idt id = CPROVER_PREFIX "is_freed"; + + auto pred = [&](const exprt &expr) { + if(!can_cast_expr(expr)) + return false; + + return to_symbol_expr(expr).get_identifier() == id; + }; + + if(!has_subexpr(expr, pred)) + return; + + error().source_location = expr.source_location(); + error() << id2string(id) + " is not allowed in preconditions." << eom; throw 0; }; @@ -852,7 +870,7 @@ void c_typecheck_baset::typecheck_declaration( implicit_typecast_bool(requires); check_history_expr(requires); check_return_value(requires); - check_return_value(requires); + check_is_freed(requires); lambda_exprt lambda{temporary_parameter_symbols, requires}; lambda.add_source_location() = requires.source_location(); requires.swap(lambda); @@ -927,7 +945,6 @@ void c_typecheck_baset::typecheck_declaration( // dedicated contract symbol. new_symbol.type.remove(ID_C_spec_assigns); new_symbol.type.remove(ID_C_spec_frees); - new_symbol.type.remove(ID_C_spec_frees); new_symbol.type.remove(ID_C_spec_ensures); new_symbol.type.remove(ID_C_spec_ensures_contract); new_symbol.type.remove(ID_C_spec_requires); diff --git a/src/ansi-c/c_typecheck_code.cpp b/src/ansi-c/c_typecheck_code.cpp index 94f1bde6272..613cc308602 100644 --- a/src/ansi-c/c_typecheck_code.cpp +++ b/src/ansi-c/c_typecheck_code.cpp @@ -1014,11 +1014,43 @@ void c_typecheck_baset::typecheck_spec_frees_target(exprt &target) // an expression with pointer-type without side effects throw_on_side_effects(target); } + else if(can_cast_expr(target)) + { + // A call to a freeable_t function symbol without other side effects + const auto &funcall = to_side_effect_expr_function_call(target); + + if(!can_cast_expr(funcall.function())) + { + error().source_location = target.source_location(); + error() << "function pointer calls not allowed in frees clauses" << eom; + throw 0; + } + + bool has_freeable_type = + (type.id() == ID_empty) && + (type.get(ID_C_typedef) == CPROVER_PREFIX "freeable_t"); + if(!has_freeable_type) + { + error().source_location = target.source_location(); + error() << "expecting " CPROVER_PREFIX + "freeable_t return type for function " + + id2string( + to_symbol_expr(funcall.function()).get_identifier()) + + " called in frees clause" + << eom; + throw 0; + } + + for(const auto &argument : funcall.arguments()) + throw_on_side_effects(argument); + } else { // anything else is rejected error().source_location = target.source_location(); - error() << "frees clause target must be a pointer-typed expression" << eom; + error() << "frees clause target must be a pointer-typed expression or a " + "call to a function returning " CPROVER_PREFIX "freeable_t" + << eom; throw 0; } } diff --git a/src/ansi-c/c_typecheck_expr.cpp b/src/ansi-c/c_typecheck_expr.cpp index d1642497a60..9a5aca962d2 100644 --- a/src/ansi-c/c_typecheck_expr.cpp +++ b/src/ansi-c/c_typecheck_expr.cpp @@ -2255,6 +2255,30 @@ exprt c_typecheck_baset::do_special_functions( typecheck_function_call_arguments(expr); return nil_exprt(); } + else if(identifier == CPROVER_PREFIX "is_freeable") + { + if(expr.arguments().size() != 1) + { + error().source_location = f_op.source_location(); + error() << CPROVER_PREFIX "is_freeable expects one operand; " + << expr.arguments().size() << "provided." << eom; + throw 0; + } + typecheck_function_call_arguments(expr); + return nil_exprt(); + } + else if(identifier == CPROVER_PREFIX "is_freed") + { + if(expr.arguments().size() != 1) + { + error().source_location = f_op.source_location(); + error() << CPROVER_PREFIX "is_freed expects one operand; " + << expr.arguments().size() << "provided." << eom; + throw 0; + } + typecheck_function_call_arguments(expr); + return nil_exprt(); + } else if(identifier == CPROVER_PREFIX "same_object") { if(expr.arguments().size()!=2) diff --git a/src/ansi-c/cprover_builtin_headers.h b/src/ansi-c/cprover_builtin_headers.h index 43d4e53eee2..0623f8149d4 100644 --- a/src/ansi-c/cprover_builtin_headers.h +++ b/src/ansi-c/cprover_builtin_headers.h @@ -45,6 +45,8 @@ void __CPROVER_atomic_end(); void __CPROVER_fence(const char *kind, ...); // contract-related functions +__CPROVER_bool __CPROVER_is_freeable(const void *mem); +__CPROVER_bool __CPROVER_is_freed(const void *mem); __CPROVER_bool __CPROVER_is_fresh(const void *mem, __CPROVER_size_t size); void __CPROVER_old(const void *); void __CPROVER_loop_entry(const void *); @@ -139,4 +141,5 @@ __CPROVER_assignable_t __CPROVER_assignable(void *ptr, __CPROVER_size_t size, __CPROVER_assignable_t __CPROVER_whole_object(void *ptr); __CPROVER_assignable_t __CPROVER_object_from(void *ptr); __CPROVER_assignable_t __CPROVER_object_upto(void *ptr, __CPROVER_size_t size); +__CPROVER_assignable_t __CPROVER_freeable(void *ptr); // clang-format on diff --git a/src/ansi-c/library/cprover.h b/src/ansi-c/library/cprover.h index 61bf2687c68..e62818879f8 100644 --- a/src/ansi-c/library/cprover.h +++ b/src/ansi-c/library/cprover.h @@ -19,6 +19,8 @@ typedef __typeof__(sizeof(int)) __CPROVER_size_t; typedef signed long long __CPROVER_ssize_t; // NOLINTNEXTLINE(readability/identifiers) typedef void __CPROVER_assignable_t; +// NOLINTNEXTLINE(readability/identifiers) +typedef void __CPROVER_freeable_t; void *__CPROVER_allocate(__CPROVER_size_t size, __CPROVER_bool zero); void __CPROVER_deallocate(void *); diff --git a/src/linking/remove_internal_symbols.cpp b/src/linking/remove_internal_symbols.cpp index 09c3ab4f73c..7f634ec1f83 100644 --- a/src/linking/remove_internal_symbols.cpp +++ b/src/linking/remove_internal_symbols.cpp @@ -156,6 +156,10 @@ void remove_internal_symbols( special.insert(CPROVER_PREFIX "object_upto"); special.insert(CPROVER_PREFIX "object_from"); special.insert(CPROVER_PREFIX "whole_object"); + special.insert(CPROVER_PREFIX "freeable_t"); + special.insert(CPROVER_PREFIX "freeable"); + special.insert(CPROVER_PREFIX "is_freeable"); + special.insert(CPROVER_PREFIX "is_freed"); special.insert(rounding_mode_identifier()); special.insert("__new"); special.insert("__new_array"); From 8895dbe9e5dc4787be3e290cdfa4263d5db80c2c Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Tue, 23 Aug 2022 15:27:21 -0400 Subject: [PATCH 03/20] CONTRACTS: Front-end extension: add `__CPROVER_is_freshr` `__CPROVER_is_freshr` works like `__CPROVER_is_fresh` but takes pointers by reference instead of by value. This willallow using the predicate from within user-defined functions. Only type-checking in the front-end is implemented, support in the back-end will come with dynamic frames. --- .../contracts-memory-predicates.md | 51 +++++++++++++++++-- src/ansi-c/c_typecheck_expr.cpp | 12 +++++ src/ansi-c/cprover_builtin_headers.h | 2 + .../contracts/module_dependencies.txt | 1 + 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/doc/cprover-manual/contracts-memory-predicates.md b/doc/cprover-manual/contracts-memory-predicates.md index 318f70d2efb..9eb76814253 100644 --- a/doc/cprover-manual/contracts-memory-predicates.md +++ b/doc/cprover-manual/contracts-memory-predicates.md @@ -4,19 +4,62 @@ ### Syntax +To specify memory footprint we provide the built-in function `__CPROVER_is_fresh`. + +```c +bool __CPROVER_is_fresh(const void *p, size_t size); +``` + +When used in a precondition, `__CPROVER_is_fresh(p, s)` expresses that the contract +is expecting `p` to be a pointer to a fresh object of size at least `s` provided +by the calling context. In a postcondition, `__CPROVER_is_fresh(p, s)` expresses +that the function returns a pointer to a fresh object to the calling context. +From the perspective of the contract, and object is _fresh_ if and only if it is +distinct from any other object seen by any other occurrences of +`__CPROVER_is_fresh` in the same contract. + +When asserting a precondition (in contract replacement mode) or asserting a +postcondition (in contract checking mode), a call to `__CPROVER_is_fresh(p, s)` +returns true if and only if `p` points to and object distinct from any other +object seen by another occurrence of `__CPROVER_is_fresh(p', s')` (in either +preconditions or post-conditions). + +When assuming a postcondition (in contract replacement mode) or assuming a +precondition (in contact enforcement mode), a call to `__CPROVER_is_fresh(p, s)` +gets translated to a statement `p = malloc(s)` which ensures that `p` points to +an object distinct from any other object. + +The predicate `__CPROVER_is_freshr ` has the same meaning as +`__CPROVER_is_fresh` but takes its pointer parameter by reference: + ```c -bool __CPROVER_is_fresh(void *p, size_t size); +bool __CPROVER_is_freshr(const void **p, size_t size); ``` -To specify memory footprint we use a special function called `__CPROVER_is_fresh `. The meaning of `__CPROVER_is_fresh` is that we are borrowing a pointer from the -external environment (in a precondition), or returning it to the calling context (in a postcondition). +This allows `__CPROVER_is_freshr` to be called from within functions and still +check or update the desired pointer: + +```c +// a function specifying that `arr` must be fresh +bool foo_preconditions(const char **arr, const size_t size) { + return __CPROVER_is_freshr(arr, size); +} +int foo(char *arr, size_t size) +// call if from the requires clause, checks or updates arr as seen +// by foo +__CPROVER_requires(foo_preconditions(&arr, size)) +{ + for(size_t i = 0; i < size ; i ++) + arr[i] = i; +} +``` ### Parameters `__CPROVER_is_fresh` takes two arguments: a pointer and an allocation size. The first argument is the pointer to be checked for "freshness" (i.e., not previously allocated), and the second is the expected size in bytes for the memory -available at the pointer. +available at the pointer. ### Return Value diff --git a/src/ansi-c/c_typecheck_expr.cpp b/src/ansi-c/c_typecheck_expr.cpp index 9a5aca962d2..f4876f53d81 100644 --- a/src/ansi-c/c_typecheck_expr.cpp +++ b/src/ansi-c/c_typecheck_expr.cpp @@ -2255,6 +2255,18 @@ exprt c_typecheck_baset::do_special_functions( typecheck_function_call_arguments(expr); return nil_exprt(); } + if(identifier == CPROVER_PREFIX "is_freshr") + { + if(expr.arguments().size() != 2) + { + error().source_location = f_op.source_location(); + error() << CPROVER_PREFIX "is_freshr expects two operands; " + << expr.arguments().size() << "provided." << eom; + throw 0; + } + typecheck_function_call_arguments(expr); + return nil_exprt(); + } else if(identifier == CPROVER_PREFIX "is_freeable") { if(expr.arguments().size() != 1) diff --git a/src/ansi-c/cprover_builtin_headers.h b/src/ansi-c/cprover_builtin_headers.h index 0623f8149d4..e00f0d3c0e3 100644 --- a/src/ansi-c/cprover_builtin_headers.h +++ b/src/ansi-c/cprover_builtin_headers.h @@ -48,6 +48,8 @@ void __CPROVER_fence(const char *kind, ...); __CPROVER_bool __CPROVER_is_freeable(const void *mem); __CPROVER_bool __CPROVER_is_freed(const void *mem); __CPROVER_bool __CPROVER_is_fresh(const void *mem, __CPROVER_size_t size); +// Is fresh, reference version (pronounced "fresher") +__CPROVER_bool __CPROVER_is_freshr(const void **mem, __CPROVER_size_t size); void __CPROVER_old(const void *); void __CPROVER_loop_entry(const void *); diff --git a/src/goto-instrument/contracts/module_dependencies.txt b/src/goto-instrument/contracts/module_dependencies.txt index a797c099df6..b70757e12c7 100644 --- a/src/goto-instrument/contracts/module_dependencies.txt +++ b/src/goto-instrument/contracts/module_dependencies.txt @@ -1,6 +1,7 @@ analyses ansi-c goto-instrument/contracts +goto-instrument/contracts/dynamic-frames goto-instrument goto-programs langapi From 3aec0ee83b84dd3ef96c2588e8df92a810fb91a1 Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Tue, 23 Aug 2022 15:30:32 -0400 Subject: [PATCH 04/20] CONTRACTS: Add class `dfcc_utilst` for DFCC utility methods. The `dfcc_utilst` class provides methods to manipulate function symbols in the symbol table: - create fresh symbols - clone and rename function parameter lists, - clone and rename function symbols, - add parameters to functions symbols and goto functions These transformations will help us implement dynamic frames. --- src/goto-instrument/Makefile | 1 + .../contracts/dynamic-frames/dfcc_utils.cpp | 570 ++++++++++++++++++ .../contracts/dynamic-frames/dfcc_utils.h | 215 +++++++ .../dynamic-frames/module_dependencies.txt | 8 + 4 files changed, 794 insertions(+) create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_utils.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_utils.h create mode 100644 src/goto-instrument/contracts/dynamic-frames/module_dependencies.txt diff --git a/src/goto-instrument/Makefile b/src/goto-instrument/Makefile index 1e73214a8b2..dd186497a2c 100644 --- a/src/goto-instrument/Makefile +++ b/src/goto-instrument/Makefile @@ -17,6 +17,7 @@ SRC = accelerate/accelerate.cpp \ branch.cpp \ call_sequences.cpp \ contracts/contracts.cpp \ + contracts/dynamic-frames/dfcc_utils.cpp \ contracts/havoc_assigns_clause_targets.cpp \ contracts/instrument_spec_assigns.cpp \ contracts/memory_predicates.cpp \ diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_utils.cpp b/src/goto-instrument/contracts/dynamic-frames/dfcc_utils.cpp new file mode 100644 index 00000000000..5504c1e260b --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_utils.cpp @@ -0,0 +1,570 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking + +Author: Remi Delmas, delmarsd@amazon.com +Date: August 2022 + +\*******************************************************************/ + +#include "dfcc_utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +dfcc_utilst::dfcc_utilst(goto_modelt &goto_model, messaget &log) + : goto_model(goto_model), + log(log), + message_handler(log.get_message_handler()), + ns(goto_model.symbol_table) +{ +} + +const bool dfcc_utilst::symbol_exists( + const std::string &name, + const bool require_has_code_type, + const bool require_body_available) +{ + const symbolt *sym; + if(ns.lookup(name, sym)) + return false; + + if(require_has_code_type && sym->type.id() != ID_code) + return false; + + if(require_body_available) + { + const auto found = goto_model.goto_functions.function_map.find(name); + + return found != goto_model.goto_functions.function_map.end() && + found->second.body_available(); + } + return true; +} + +const bool dfcc_utilst::function_symbol_exists(const std::string &function_id) +{ + return symbol_exists(function_id, true, false); +} + +const bool +dfcc_utilst::function_symbol_with_body_exists(const std::string &function_id) +{ + return symbol_exists(function_id, true, true); +} + +symbolt &dfcc_utilst::get_function_symbol(const irep_idt &function_id) +{ + auto &symbol_table = goto_model.symbol_table; + PRECONDITION(symbol_table.has_symbol(function_id)); + symbolt &function_symbol = symbol_table.get_writeable_ref(function_id); + PRECONDITION(function_symbol.type.id() == ID_code); + return function_symbol; +} + +const symbolt &dfcc_utilst::create_symbol( + const typet &type, + const std::string &prefix, + const std::string &base_name, + const source_locationt &source_location, + const irep_idt &mode, + const irep_idt &module, + bool is_parameter) +{ + log.debug() << "dfcc_utilst::create_symbol(" << prefix << "::" << base_name + << ")" << messaget::eom; + + symbolt &symbol = get_fresh_aux_symbol( + type, prefix, base_name, source_location, mode, goto_model.symbol_table); + symbol.is_lvalue = true; + symbol.is_state_var = true; + symbol.is_thread_local = true; + symbol.is_file_local = true; + symbol.is_parameter = is_parameter; + return symbol; +} + +const symbolt &dfcc_utilst::create_static_symbol( + const typet &type, + const std::string &prefix, + const std::string &base_name, + const source_locationt &source_location, + const irep_idt &mode, + const irep_idt &module, + const exprt &initial_value) +{ + log.debug() << "dfcc_utilst::create_static_symbol(" << prefix + << "::" << base_name << ")" << messaget::eom; + + symbolt &symbol = get_fresh_aux_symbol( + type, prefix, base_name, source_location, mode, goto_model.symbol_table); + symbol.is_static_lifetime = true; + symbol.value = initial_value; + symbol.is_lvalue = true; + symbol.is_state_var = true; + symbol.is_thread_local = true; + symbol.is_file_local = true; + symbol.is_parameter = false; + return symbol; +} + +void dfcc_utilst::create_initialize_function() +{ + if(goto_model.goto_functions.function_map.erase(INITIALIZE_FUNCTION) != 0) + { + static_lifetime_init( + goto_model.symbol_table, + goto_model.symbol_table.lookup_ref(INITIALIZE_FUNCTION).location); + goto_convert( + INITIALIZE_FUNCTION, + goto_model.symbol_table, + goto_model.goto_functions, + message_handler); + goto_model.goto_functions.update(); + } +} + +void dfcc_utilst::fix_parameters_symbols(const irep_idt &function_id) +{ + auto &function_symbol = get_function_symbol(function_id); + auto &code_type = to_code_type(function_symbol.type); + // create parameter symbols + std::size_t anon_counter = 0; + for(auto &p : code_type.parameters()) + { + // may be anonymous + if(p.get_base_name().empty()) + { + p.set_base_name("#anon" + std::to_string(anon_counter++)); + } + + // produce identifier + const irep_idt &base_name = p.get_base_name(); + irep_idt parameter_identifier = + id2string(function_id) + "::" + id2string(base_name); + + p.set_identifier(parameter_identifier); + + if(!goto_model.symbol_table.has_symbol(p.get_identifier())) + { + create_new_parameter_symbol( + function_id, id2string(p.get_base_name()), p.type()); + } + } +} + +const symbolt &dfcc_utilst::create_new_parameter_symbol( + const irep_idt &function_id, + const std::string &base_name, + const typet &type) +{ + symbolt &function_symbol = get_function_symbol(function_id); + + // insert new parameter in the symbol table + const symbolt &symbol = create_symbol( + type, + id2string(function_id), + base_name, + function_symbol.location, + function_symbol.mode, + function_symbol.module, + true); + return symbol; +} + +void dfcc_utilst::add_parameter(const symbolt &symbol, code_typet &code_type) +{ + PRECONDITION(symbol.is_parameter); + code_typet::parametert parameter(symbol.type); + parameter.set_base_name(symbol.base_name); + parameter.set_identifier(symbol.name); + auto ¶meters = code_type.parameters(); + parameters.insert(parameters.end(), parameter); +} + +void dfcc_utilst::add_parameter( + const symbolt &symbol, + const irep_idt &function_id) +{ + PRECONDITION(symbol.is_parameter); + auto &function_symbol = get_function_symbol(function_id); + code_typet &code_type = to_code_type(function_symbol.type); + add_parameter(symbol, code_type); + auto found = goto_model.goto_functions.function_map.find(function_id); + if(found != goto_model.goto_functions.function_map.end()) + found->second.set_parameter_identifiers(code_type); +} + +const symbolt &dfcc_utilst::add_parameter( + const irep_idt &function_id, + const std::string &base_name, + const typet &type) +{ + log.debug() << "dfcc_utilst::add_parameter(" << function_id << ", " + << base_name << ")" << messaget::eom; + const symbolt &symbol = + create_new_parameter_symbol(function_id, base_name, type); + add_parameter(symbol, function_id); + return symbol; +} + +const symbolt &dfcc_utilst::clone_and_rename_function( + const irep_idt &function_id, + std::function &trans_fun, + std::function &trans_param, + std::function &trans_ret_type, + std::function &trans_loc) +{ + log.debug() << "->dfcc_utilst::clone_and_rename_function(" << function_id + << ")" << messaget::eom; + const symbolt &old_function_symbol = get_function_symbol(function_id); + code_typet old_code_type = to_code_type(old_function_symbol.type); + + irep_idt new_function_id = trans_fun(function_id); + + log.debug() << "dfcc_utilst::clone_and_rename_function: function " + << function_id << "->" << new_function_id << messaget::eom; + + code_typet::parameterst new_params; + source_locationt new_location = trans_loc(old_function_symbol.location); + clone_parameters( + old_code_type.parameters(), + old_function_symbol.mode, + old_function_symbol.mode, + new_location, + trans_param, + new_function_id, + new_params); + + // build new function symbol + code_typet new_code_type( + new_params, trans_ret_type(old_code_type.return_type())); + symbolt new_function_symbol; + new_function_symbol.base_name = new_function_id; + new_function_symbol.name = new_function_id; + new_function_symbol.pretty_name = new_function_id; + new_function_symbol.type = new_code_type; + new_function_symbol.mode = old_function_symbol.mode; + new_function_symbol.module = old_function_symbol.module; + new_function_symbol.location = trans_loc(old_function_symbol.location); + new_function_symbol.set_compiled(); + + if(goto_model.symbol_table.add(new_function_symbol)) + { + log.error() << "dfcc_utilst::clone_and_rename_function: renamed function " + << function_id << " -> " << new_function_id << " already exists" + << messaget::eom; + exit(0); + } + + // create new goto_function + goto_functiont new_goto_function; + new_goto_function.set_parameter_identifiers(new_code_type); + goto_model.goto_functions.function_map[new_function_id].copy_from( + new_goto_function); + return goto_model.symbol_table.lookup_ref(new_function_id); + log.debug() << "<-dfcc_utilst::clone_and_rename_function(" << function_id + << ")" << messaget::eom; +} + +void dfcc_utilst::clone_parameters( + const code_typet::parameterst &old_params, + const irep_idt &mode, + const irep_idt &module, + const source_locationt &location, + std::function &trans_param, + const irep_idt &new_function_id, + code_typet::parameterst &new_params) +{ + // rename function parameters in the wrapper function's code_type + std::size_t anon_counter = 0; + + // build parameters and symbols + for(auto &old_param : old_params) + { + // new identifier for new_code_type + const irep_idt &old_base_name = old_param.get_base_name().empty() + ? "#anon" + std::to_string(anon_counter++) + : old_param.get_base_name(); + const irep_idt &new_base_name = trans_param(old_base_name); + + irep_idt new_param_id = + id2string(new_function_id) + "::" + id2string(new_base_name); + + // build parameter + code_typet::parametert new_param(old_param.type()); + new_param.set_base_name(new_base_name); + new_param.set_identifier(new_param_id); + new_params.push_back(new_param); + + // build symbol + log.debug() << "dfcc_utilst::clone_parameters: param " << old_base_name + << "->" << new_param_id << messaget::eom; + parameter_symbolt new_param_symbol; + new_param_symbol.base_name = new_base_name; + new_param_symbol.name = new_param_id; + new_param_symbol.pretty_name = new_param_id; + new_param_symbol.type = old_param.type(); + new_param_symbol.mode = mode; + new_param_symbol.module = module; + new_param_symbol.location = location; + INVARIANT( + goto_model.symbol_table.lookup(new_param_id) == NULL, "name clash"); + if(goto_model.symbol_table.add(new_param_symbol)) + { + log.error() << "dfcc_utilst::clone_parameters: renamed parameter " + << old_param.get_identifier() << " -> " + << new_param.get_identifier() << " already exists" + << messaget::eom; + exit(0); + } + } +} + +const symbolt &dfcc_utilst::clone_and_rename_function( + const irep_idt &function_id, + const irep_idt &new_function_id, + optionalt new_return_type = {}) +{ + std::function trans_fun = + [&](const irep_idt &old_name) { return new_function_id; }; + + std::function trans_param = + [&](const irep_idt &old_name) { return old_name; }; + + std::function trans_ret_type = + [&](const typet &old_type) { + return new_return_type.has_value() ? new_return_type.value() : old_type; + }; + + std::function trans_loc = + [&](const source_locationt &old_location) { return old_location; }; + + return clone_and_rename_function( + function_id, trans_fun, trans_param, trans_ret_type, trans_loc); +} + +void dfcc_utilst::wrap_function( + const irep_idt &function_id, + const irep_idt &wrapped_function_id) +{ + auto &goto_functions = goto_model.goto_functions; + auto &symbol_table = goto_model.symbol_table; + + auto old_function = goto_functions.function_map.find(function_id); + INVARIANT( + old_function != goto_functions.function_map.end(), + "dfcc_utilst::wrap_function: function to wrap " + id2string(function_id) + + " must exist in the program"); + + // Register the wrapped function under the new name + std::swap( + goto_functions.function_map[wrapped_function_id], old_function->second); + + // Check that new name really exists + INVARIANT( + goto_functions.function_map.find(wrapped_function_id) != + goto_functions.function_map.end(), + "dfcc_utilst::wrap_function: wrapped function " + + id2string(wrapped_function_id) + + " shall exist in function_map at this point"); + + // Remove previous entry + goto_functions.function_map.erase(old_function); + + // Check that the slot is empty in function_map + INVARIANT( + goto_functions.function_map.find(function_id) == + goto_functions.function_map.end(), + "dfcc_utilst::wrap_function: the original function " + + id2string(function_id) + + " shall not exist in function_map anymore at this point"); + + // Add new symbol for the wrapped function in the symbol table + source_locationt sl; + sl.set_file("wrapped functions for code contracts"); + sl.set_line("0"); + const symbolt *old_sym = symbol_table.lookup(function_id); + symbolt wrapped_sym; + wrapped_sym = *old_sym; + wrapped_sym.name = wrapped_function_id; + wrapped_sym.base_name = wrapped_function_id; + wrapped_sym.location = sl; + const auto wrapped_found = symbol_table.insert(std::move(wrapped_sym)); + INVARIANT( + wrapped_found.second, + "dfcc_utilst::wrap_function: wrapped function symbol " + + id2string(wrapped_function_id) + " already exists in the symbol table"); + + // Re-insert a symbol for `function_id` which is now the wrapper function + symbolt wrapper_sym; + wrapper_sym = *old_sym; + + std::function trans_param = + [&](const irep_idt &old_param) { + return id2string(old_param) + "_wrapper"; + }; + + // create new code_type with renamed parameters for the wrapper + const auto &old_code_type = to_code_type(old_sym->type); + code_typet::parameterst new_params; + clone_parameters( + old_code_type.parameters(), + wrapper_sym.mode, + wrapper_sym.module, + wrapper_sym.location, + // the naming scheme is `function_id::param` + `param_suffix` + trans_param, + function_id, + new_params); + + code_typet new_code_type(new_params, old_code_type.return_type()); + + wrapper_sym.type = new_code_type; + + // Remove original symbol from the symbol_table + if(goto_model.symbol_table.remove(function_id)) + { + log.error() << "dfcc_utilst::wrap_function: could not remove " + << function_id << " from the symbol table" << messaget::eom; + exit(0); + } + + // Insert update symbol in the symbol_table + const auto wrapper_sym_found = symbol_table.insert(std::move(wrapper_sym)); + INVARIANT( + wrapper_sym_found.second, + "dfcc_utilst::wrap_function: could not insert symbol " + + id2string(function_id) + " in the symbol table"); + + // Insert wrapper function in the function_map + goto_functiont &wrapper_fun = goto_functions.function_map[function_id]; + wrapper_fun.set_parameter_identifiers(new_code_type); + wrapper_fun.body.add(goto_programt::make_end_function(sl)); +} + +const exprt dfcc_utilst::make_null_check_expr(const exprt &ptr) +{ + return equal_exprt(ptr, null_pointer_exprt(to_pointer_type(ptr.type()))); +} + +exprt dfcc_utilst::make_sizeof_expr(const exprt &expr) +{ + log.debug() << "dfcc_utilst::make_sizeof_expr(" << format(expr) << ")" + << messaget::eom; + const auto &size = + size_of_expr(expr.type(), namespacet(goto_model.symbol_table)); + + PRECONDITION_WITH_DIAGNOSTICS( + size.has_value(), + "dfcc_utilst::make_sizeof_expr: no definite size for lvalue target:\n" + + expr.pretty()); + + return typecast_exprt::conditional_cast(size.value(), size_type()); +} + +exprt dfcc_utilst::make_map_start_address(const exprt &expr) +{ + return typecast_exprt::conditional_cast( + address_of_exprt(index_exprt(expr, from_integer(0, c_index_type()))), + pointer_type(bool_typet())); +} + +void dfcc_utilst::inline_function(const irep_idt &function_id) +{ + auto &goto_function = goto_model.goto_functions.function_map.at(function_id); + + PRECONDITION_WITH_DIAGNOSTICS( + goto_function.body_available(), + "dfcc_utilst::inline_function: '" + id2string(function_id) + + "' must have a body"); + + inlining_decoratort decorated(log.get_message_handler()); + namespacet ns{goto_model.symbol_table}; + goto_function_inline( + goto_model.goto_functions, function_id, ns, log.get_message_handler()); + + decorated.throw_on_recursive_calls(log, 0); + decorated.throw_on_no_body(log, 0); + decorated.throw_on_missing_function(log, 0); + decorated.throw_on_not_enough_arguments(log, 0); + + goto_model.goto_functions.update(); +} + +void dfcc_utilst::inline_function( + const irep_idt &function_id, + std::set &no_body, + std::set &recursive_call, + std::set &missing_function, + std::set ¬_enough_arguments) +{ + auto &goto_function = goto_model.goto_functions.function_map.at(function_id); + + PRECONDITION_WITH_DIAGNOSTICS( + goto_function.body_available(), + "dfcc_utilst::inline_function: '" + id2string(function_id) + + "' must have a body"); + + inlining_decoratort decorated(log.get_message_handler()); + namespacet ns{goto_model.symbol_table}; + goto_function_inline( + goto_model.goto_functions, function_id, ns, log.get_message_handler()); + no_body.insert( + decorated.get_no_body_set().begin(), decorated.get_no_body_set().end()); + recursive_call.insert( + decorated.get_recursive_call_set().begin(), + decorated.get_recursive_call_set().end()); + missing_function.insert( + decorated.get_missing_function_set().begin(), + decorated.get_missing_function_set().end()); + not_enough_arguments.insert( + decorated.get_not_enough_arguments_set().begin(), + decorated.get_not_enough_arguments_set().end()); + goto_model.goto_functions.update(); +} + +void dfcc_utilst::check_loop_freeness( + const irep_idt &function_id, + std::string msg, + int err) +{ + auto &goto_function = goto_model.goto_functions.function_map.at(function_id); + if(!is_loop_free(goto_function.body, ns, log)) + { + log.error() << "loop-freeness check failed for function '" + << id2string(function_id) << "': " << msg << messaget::eom; + throw err; + } +} + +void dfcc_utilst::set_hide(const irep_idt &function_id, bool hide) +{ + auto &goto_function = goto_model.goto_functions.function_map.at(function_id); + if(goto_function.body_available()) + { + Forall_goto_program_instructions(inst, goto_function.body) + { + inst->source_location_nonconst().set(ID_hide, hide); + } + } +} diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_utils.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_utils.h new file mode 100644 index 00000000000..a2efd5603ca --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_utils.h @@ -0,0 +1,215 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com +Date: August 2022 + +\*******************************************************************/ + +/// \file +/// Dynamic frame condition checking utility functions + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_UTILS_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_UTILS_H + +#include +#include + +#include + +class goto_modelt; +class messaget; +class message_handlert; +class symbolt; + +class dfcc_utilst +{ +public: + dfcc_utilst(goto_modelt &goto_model, messaget &log); + +protected: + goto_modelt &goto_model; + messaget &log; + message_handlert &message_handler; + namespacet ns; + +public: + /// Returns true iff the given symbol exists and satisfies requirements. + const bool symbol_exists( + const std::string &function_id, + const bool require_has_code_type = false, + const bool require_body_available = false); + + const bool function_symbol_exists(const std::string &function_id); + const bool function_symbol_with_body_exists(const std::string &function_id); + + /// Returns the `symbolt` for `function_id`. + symbolt &get_function_symbol(const irep_idt &function_id); + + /// Adds a new symbol named `prefix::base_name` of type `type` + /// with given attributes in the symbol table, and returns the created symbol. + /// If a symbol of the same name already exists the name will be decorated + /// with a unique suffix. + const symbolt &create_symbol( + const typet &type, + const std::string &prefix, + const std::string &base_name, + const source_locationt &source_location, + const irep_idt &mode, + const irep_idt &module, + bool is_parameter); + + /// Adds a new static symbol named `prefix::base_name` of type `type` with + /// value `initial_value` in the symbol table, returns the created symbol. + /// If a symbol of the same name already exists the name will be decorated + /// with a unique suffix. + const symbolt &create_static_symbol( + const typet &type, + const std::string &prefix, + const std::string &base_name, + const source_locationt &source_location, + const irep_idt &mode, + const irep_idt &module, + const exprt &initial_value); + + /// Regenerates the CPROVER_INITIALIZE function which defines all global + /// statics of the goto model. + void create_initialize_function(); + + /// Creates a new parameter symbol for the given function_id + const symbolt &create_new_parameter_symbol( + const irep_idt &function_id, + const std::string &base_name, + const typet &type); + + /// \brief Adds the given symbol as parameter to the function symbol's + /// code_type. Also adds the corresponding parameter to its goto_function if + /// it exists in the function map of the goto model. + void add_parameter(const symbolt &symbol, const irep_idt &function_id); + + /// \brief Adds the given symbol as parameter to the given code_typet. + void add_parameter(const symbolt &symbol, code_typet &code_type); + + /// \brief Adds a parameter with given `base_name` and `type` to the given + /// `function_id`. Both the symbol and the goto_function are updated. + const symbolt &add_parameter( + const irep_idt &function_id, + const std::string &base_name, + const typet &type); + + /// \brief Creates a new symbol in the `symbol_table` by cloning + /// the given `function_id` function and transforming the existing's + /// function's name, parameter names, return type and source location + /// using the given `trans_fun`, `trans_param` and `trans_ret_type` and + /// `trans_loc` functions. + /// + /// Also creates a new `goto_function` under the transformed name in + /// the `function_map` with new parameters and an empty body. + /// Returns the new symbol. + /// + /// \param function_id function to clone + /// \param trans_fun transformation function for the function name + /// \param trans_param transformation function for the parameter names + /// \param trans_ret_type transformation function for the return type + /// \param trans_loc transformation function for the source location + /// \return the new function symbol + const symbolt &clone_and_rename_function( + const irep_idt &function_id, + std::function &trans_fun, + std::function &trans_param, + std::function &trans_ret_type, + std::function &trans_loc); + + /// \brief Clones the old_params into the new_params list, applying the + /// trans_param function to generate the names of the cloned params. + void clone_parameters( + const code_typet::parameterst &old_params, + const irep_idt &mode, + const irep_idt &module, + const source_locationt &location, + std::function &trans_param, + const irep_idt &new_function_id, + code_typet::parameterst &new_params); + + /// \brief Creates names for anonymous parameters and declares them + /// in the symbol table if needed (using goto_convert requires all function + /// parameters to have names). + void fix_parameters_symbols(const irep_idt &function_id); + + /// \brief Creates a new function symbol and and goto_function + /// entry in the `goto_functions_map` by cloning the given `function_id`. + /// + /// The cloned function symbol has `new_function_id` as name + /// The cloned parameters symbols have `new_function_id::name` as name + /// Returns the new function symbol + const symbolt &clone_and_rename_function( + const irep_idt &function_id, + const irep_idt &new_function_id, + optionalt new_return_type); + + /// Given a function to wrap `foo` and a new name `wrapped_foo` + /// + /// ``` + /// ret_t foo(x_t foo::x, y_t foo::y) { foo_body; } + /// ``` + /// + /// This method creates a new entry in the symbol_table for + /// `wrapped_foo` and moves the body of the original function, `foo_body`, + /// under `wrapped_foo` in `function_map`. + /// + /// The parameter symbols of `wrapped_foo` remain the same as in `foo`: + /// ``` + /// ret_t wrapped_foo(x_t foo::x, y_t foo::y) { foo_body; } + /// ``` + /// + /// The parameters of the original `foo` get renamed to + /// make sure they are distinct from that of `wrapped_foo`, and a new + /// empty body is generated for `foo`: + /// + /// ``` + /// ret_t foo(x_t foo::x_wrapped, y_t foo::y_wrapped) { }; + /// ``` + /// + /// Any other goto_function calling `foo` still calls `foo` which has become + /// a wrapper for `wrapped_foo`. + /// + /// By generating a new body for `foo`, one can implement contract + /// checking logic, contract replacement logic, etc. + void wrap_function( + const irep_idt &function_id, + const irep_idt &wrapped_function_id); + + /// \brief Returns the expression `expr == NULL`. + const exprt make_null_check_expr(const exprt &ptr); + + /// \brief Returns the expression `sizeof(expr)`. + exprt make_sizeof_expr(const exprt &expr); + + /// \brief Returns the expression `&expr[0]` + exprt make_map_start_address(const exprt &expr); + + /// \brief Inlines the given function, aborts on recursive calls during + /// inlining. + void inline_function(const irep_idt &function_id); + + /// \brief Inlines the given function, and returns function symbols that + /// caused warnings. + void inline_function( + const irep_idt &function_id, + std::set &no_body, + std::set &recursive_call, + std::set &missing_function, + std::set ¬_enough_arguments); + + /// \brief Checks if the function is loop free. The function must exist + /// and have a body. Prints `msg` and throws `err` on failure. + void + check_loop_freeness(const irep_idt &function_id, std::string msg, int err); + + /// \brief Sets the given hide flag on all instructions of the function if it + /// exists. + void set_hide(const irep_idt &function_id, bool hide); +}; + +#endif diff --git a/src/goto-instrument/contracts/dynamic-frames/module_dependencies.txt b/src/goto-instrument/contracts/dynamic-frames/module_dependencies.txt new file mode 100644 index 00000000000..7bd3dc617e5 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/module_dependencies.txt @@ -0,0 +1,8 @@ +ansi-c +goto-instrument/contracts +goto-instrument/contracts/dynamic-frames +goto-instrument +goto-programs +langapi +linking +util From c2b085df386d22232cba1e47866ef8c617438d8c Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Wed, 24 Aug 2022 09:14:16 -0400 Subject: [PATCH 05/20] CONTRACTS: Add library functions for dynamic frames `cprover_contracts.c` defines C types representing dynamic frames: - `__CPROVER_contract_car_t` - `__CPROVER_contract_car_set_t` - `__CPROVER_contract_obj_set_t` - `__CPROVER_contract_write_set_t` It also defines functions that build and check frame conditions in various situations: - allocations/deallocations, - assignments, - array operations, - havoc operations, - assigns clause inclusion checks for contract replacement, - frees clause inclusion checks for contract replacement, - etc. It also provides C implementations for built-in predicates: - `__CPROVER_contracts_is_freeable` for `__CPROVER_is_freeable` - `__CPROVER_contracts_is_freed` for `__CPROVER_is_freed` - `__CPROVER_contracts_is_freshr` for `__CPROVER_is_fresh` and `__CPROVER_is_freshr` The class `dfcc_libraryt` provides a programmatic interface to load and use the library types and functions and perform some optimisations like inlining or unwinding loops in library functions. dfcc_libraryt fixes --- src/ansi-c/library/cprover_contracts.c | 3518 +++++++++++++++++ src/goto-instrument/Makefile | 1 + .../contracts/dynamic-frames/dfcc_library.cpp | 571 +++ .../contracts/dynamic-frames/dfcc_library.h | 258 ++ 4 files changed, 4348 insertions(+) create mode 100644 src/ansi-c/library/cprover_contracts.c create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_library.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_library.h diff --git a/src/ansi-c/library/cprover_contracts.c b/src/ansi-c/library/cprover_contracts.c new file mode 100644 index 00000000000..714c6287476 --- /dev/null +++ b/src/ansi-c/library/cprover_contracts.c @@ -0,0 +1,3518 @@ +/// \file cprover_contracts.c +/// This file defines types functions supporting dynamic frames in contracts. + +/* FUNCTION: __CPROVER_contracts_car_create */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +/// \brief A conditionally writable range of bytes. +typedef struct __CPROVER_contracts_car_t +{ + /// \brief True iff __CPROVER_w_ok(lb, size) holds at creation + __CPROVER_bool is_writable; + /// \brief Size of the range in bytes + __CPROVER_size_t size; + /// \brief Lower bound address of the range + void *lb; + /// \brief Upper bound address of the range + void *ub; +} __CPROVER_contracts_car_t; + +/// \brief A set of \ref __CPROVER_contracts_car_t. +typedef struct __CPROVER_contracts_car_set_t +{ + /// \brief Maximum number of elements that can be stored in the set + __CPROVER_size_t max_elems; + /// \brief An array of car_t of size max_elems + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +/// \brief Type of pointers to \ref __CPROVER_contracts_car_set_t. +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +/// \brief A set of pointers. +typedef struct __CPROVER_contracts_obj_set_t +{ + /// \brief Maximum number of elements that can be stored in the set + __CPROVER_size_t max_elems; + /// \brief next usable index in elems if less than max_elems + /// (1 + greatest used index in elems) + __CPROVER_size_t watermark; + /// \brief Number of elements currently in the elems array + __CPROVER_size_t nof_elems; + /// \brief True iff nof_elems is 0 + __CPROVER_bool is_empty; + /// \brief True iff elems is indexed by the object id of the pointers + __CPROVER_bool indexed_by_object_id; + /// \brief Array of void *pointers, indexed by their object ID + /// or some other order + void **elems; +} __CPROVER_contracts_obj_set_t; + +/// \brief Type of pointers to \ref __CPROVER_contracts_car_set_t. +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +/// \brief Runtime representation of a write set. +typedef struct __CPROVER_contracts_write_set_t +{ + /// \brief Set of car derived from the contract + __CPROVER_contracts_car_set_t contract_assigns; + /// \brief Set of freeable pointers derived from the contract (indexed mode) + __CPROVER_contracts_obj_set_t contract_frees; + /// \brief Set of freeable pointers derived from the contract (append mode) + __CPROVER_contracts_obj_set_t contract_frees_replacement; + /// \brief Set of objects allocated by the function under analysis + /// (indexed mode) + __CPROVER_contracts_obj_set_t allocated; + /// \brief Set of objects deallocated by the function under analysis + /// (indexed mode) + __CPROVER_contracts_obj_set_t deallocated; + /// \brief Object set supporting the is_freshr predicate checks + /// (indexed mode) + __CPROVER_contracts_obj_set_t is_freshr_seen; + /// \brief Object set recording the is_freshr allocations in post conditions + /// (replacement mode only) + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + /// \brief Object set recording the deallocations (used by predicate is_freed) + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + /// \brief True iff this write set is used for contract replacement + __CPROVER_bool replacement; + /// \brief True iff the write set checks requires clauses in an assumption ctx + __CPROVER_bool assume_requires_ctx; + /// \brief True iff the write set checks requires clauses in an assertion ctx + __CPROVER_bool assert_requires_ctx; + /// \brief True iff the write set checks ensures clauses in an assumption ctx + __CPROVER_bool assume_ensures_ctx; + /// \brief True iff this write set checks ensures clauses in an assertion ctx + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; +/// \brief Type of pointers to \ref __CPROVER_contracts_write_set_t. +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Creates a __CPROVER_car_t struct from \p ptr and \p size +/// \param[in] ptr Start address of the range +/// \param[in] size Size in bytes +/// \return A \ref __CPROVER_contracts_car_t value +inline __CPROVER_contracts_car_t +__CPROVER_contracts_car_create(void *ptr, __CPROVER_size_t size) +{ +__CPROVER_HIDE:; +#pragma CPROVER check push +#pragma CPROVER check disable "pointer" +#pragma CPROVER check disable "pointer-primitive" +#pragma CPROVER check disable "unsigned-overflow" +#pragma CPROVER check disable "signed-overflow" +#pragma CPROVER check disable "undefined-shift" +#pragma CPROVER check disable "conversion" + __CPROVER_assert( + ((ptr == 0) | __CPROVER_rw_ok(ptr, size)), + "ptr NULL or writable up to size"); + __CPROVER_contracts_car_t car = {0}; + car.is_writable = ptr != 0; + car.size = size; + car.lb = ptr; + __CPROVER_assert( + size < __CPROVER_max_malloc_size, + "CAR size is less than __CPROVER_max_malloc_size"); + __CPROVER_ssize_t offset = __CPROVER_POINTER_OFFSET(ptr); + __CPROVER_assert( + !(offset > 0) | + ((__CPROVER_size_t)offset + size < __CPROVER_max_malloc_size), + "no offset bits overflow on CAR upper bound computation"); + car.ub = (void *)((char *)ptr + size); +#pragma CPROVER check pop + return car; +} + +/* FUNCTION: __CPROVER_contracts_car_set_create */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Initialises a __CPROVER_contracts_car_set_ptr_t object +/// \param[inout] set Pointer to the object to initialise +/// \param[in] max_elems Max number of elements to store in the set +inline void __CPROVER_contracts_car_set_create( + __CPROVER_contracts_car_set_ptr_t set, + // maximum number of elements that can be stored in the set + __CPROVER_size_t max_elems) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert( + __CPROVER_rw_ok(set, sizeof(__CPROVER_contracts_car_set_t)), + "set writable"); +#endif + set->max_elems = max_elems; + set->elems = + __CPROVER_allocate(max_elems * sizeof(__CPROVER_contracts_car_t), 1); +} + +/* FUNCTION: __CPROVER_contracts_car_set_insert */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +extern __CPROVER_size_t __CPROVER_max_malloc_size; + +/// \brief Inserts a \ref __CPROVER_contracts_car_t snapshotted from \p ptr +/// and \p size into \p set at index \p idx. +/// \param[inout] set Set to insert into +/// \param[in] idx Insertion index +/// \param[in] ptr Pointer to the range of bytes +/// \param[in] size Size of the range in number of bytes +inline void __CPROVER_contracts_car_set_insert( + __CPROVER_contracts_car_set_ptr_t set, + __CPROVER_size_t idx, + void *ptr, + __CPROVER_size_t size) +{ +__CPROVER_HIDE:; +#pragma CPROVER check push +#pragma CPROVER check disable "pointer" +#pragma CPROVER check disable "pointer-overflow" +#pragma CPROVER check disable "pointer-primitive" +#pragma CPROVER check disable "unsigned-overflow" +#pragma CPROVER check disable "signed-overflow" +#pragma CPROVER check disable "undefined-shift" +#pragma CPROVER check disable "conversion" +#ifdef DFCC_SELF_CHECK + __CPROVER_assert((set != 0) & (idx < set->max_elems), "no OOB access"); +#endif + __CPROVER_assert( + ((ptr == 0) | __CPROVER_rw_ok(ptr, size)), + "ptr NULL or writable up to size"); + __CPROVER_contracts_car_t *elem = set->elems + idx; + elem->is_writable = ptr != 0; + elem->size = size; + elem->lb = ptr; + __CPROVER_assert( + size < __CPROVER_max_malloc_size, + "CAR size is less than __CPROVER_max_malloc_size"); + __CPROVER_ssize_t offset = __CPROVER_POINTER_OFFSET(ptr); + __CPROVER_assert( + !(offset > 0) | + ((__CPROVER_size_t)offset + size < __CPROVER_max_malloc_size), + "no offset bits overflow on CAR upper bound computation"); + elem->ub = (void *)((char *)ptr + size); +#pragma CPROVER check pop +} + +/* FUNCTION: __CPROVER_contracts_car_set_remove */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Invalidates all cars in the \p set that point into the same object +/// as the given \p ptr. +/// \param[inout] set Set to update +/// \param[in] ptr Pointer to the object to invalidate +void __CPROVER_contracts_car_set_remove( + __CPROVER_contracts_car_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); + __CPROVER_size_t idx = set->max_elems; + __CPROVER_contracts_car_t *elem = set->elems; +CAR_SET_REMOVE_LOOP: + while(idx != 0) + { + if(object_id == __CPROVER_POINTER_OBJECT(elem->lb)) + elem->is_writable = 0; + ++elem; + --idx; + } +} + +/* FUNCTION: __CPROVER_contracts_car_set_contains */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Checks if \p candidate is included in one of \p set 's elements. +/// \param[in] set Set to check inclusion in +/// \param[in] candidate A candidate \ref __CPROVER_contracts_car_t +/// \return True iff an element of \p set contains \p candidate +inline __CPROVER_bool __CPROVER_contracts_car_set_contains( + __CPROVER_contracts_car_set_ptr_t set, + __CPROVER_contracts_car_t candidate) +{ +__CPROVER_HIDE:; + __CPROVER_bool incl = 0; + __CPROVER_size_t idx = set->max_elems; + __CPROVER_contracts_car_t *elem = set->elems; +CAR_SET_CONTAINS_LOOP: + while(idx != 0) + { + incl |= candidate.is_writable & elem->is_writable & + __CPROVER_same_object(elem->lb, candidate.lb) & + (__CPROVER_POINTER_OFFSET(elem->lb) <= + __CPROVER_POINTER_OFFSET(candidate.lb)) & + (__CPROVER_POINTER_OFFSET(candidate.ub) <= + __CPROVER_POINTER_OFFSET(elem->ub)); + ++elem; + --idx; + } + + return incl; +} + +/* FUNCTION: __CPROVER_contracts_obj_set_create_indexed_by_object_id */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +extern __CPROVER_size_t __CPROVER_max_malloc_size; + +/// \brief Count the number of leading zeroes in an unsigned long long +int __builtin_clzll(unsigned long long); + +/// \brief Initialises a \ref __CPROVER_contracts_obj_set_t object to use it +/// in "indexed by object id" mode. +/// +/// The elements array is allocated to `2^OBJECT_BITS`, where object bits is +/// calculated as the number of leading zeroes in the `__CPROVER_max_alloc_size` +/// constant. +/// +/// \param[inout] set Pointer to the object to initialise +inline void __CPROVER_contracts_obj_set_create_indexed_by_object_id( + __CPROVER_contracts_obj_set_ptr_t set) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert( + __CPROVER_rw_ok(set, sizeof(__CPROVER_contracts_obj_set_t)), + "set writable"); +#endif + // compute the maximum number of objects that can exist in the + // symex state from the number of object_bits/offset_bits + // the number of object bits is determined by counting the number of leading + // zeroes of the built-in constant __CPROVER_max_malloc_size; + __CPROVER_size_t object_bits = __builtin_clzll(__CPROVER_max_malloc_size); + __CPROVER_size_t nof_objects = 1UL << object_bits; + set->max_elems = nof_objects; + set->watermark = 0; + set->nof_elems = 0; + set->is_empty = 1; + set->indexed_by_object_id = 1; + set->elems = __CPROVER_allocate(nof_objects * sizeof(*(set->elems)), 1); +} + +/* FUNCTION: __CPROVER_contracts_obj_set_create_append */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Initialises a \ref __CPROVER_contracts_obj_set_t object to use it +/// in "append" mode for at most \p max_elems elements. +/// +/// \param[inout] set Pointer to the object to initialise +/// \param[inout] max_elems Maximum number of objects in the set. +inline void __CPROVER_contracts_obj_set_create_append( + __CPROVER_contracts_obj_set_ptr_t set, + __CPROVER_size_t max_elems) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert( + __CPROVER_rw_ok(set, sizeof(__CPROVER_contracts_obj_set_t)), + "set writable"); +#endif + set->max_elems = max_elems; + set->watermark = 0; + set->nof_elems = 0; + set->is_empty = 1; + set->indexed_by_object_id = 0; + set->elems = __CPROVER_allocate(set->max_elems * sizeof(*(set->elems)), 1); +} + +/* FUNCTION: __CPROVER_contracts_obj_set_add */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Adds the \p ptr to \p set. +/// \pre \p set->indexed_by_object_id must be true. +/// \param[inout] set Set to add the pointer to +/// \param[in] ptr The pointer to add +inline void __CPROVER_contracts_obj_set_add( + __CPROVER_contracts_obj_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert(set->indexed_by_object_id, "indexed by object id"); + __CPROVER_assert( + __CPROVER_POINTER_OBJECT(ptr) < set->max_elems, "no OOB access"); +#endif + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); + set->nof_elems = + (set->elems[object_id] != 0) ? set->nof_elems : set->nof_elems + 1; + set->elems[object_id] = ptr; + set->is_empty = 0; +} + +/* FUNCTION: __CPROVER_contracts_obj_set_append */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Appends \p ptr to \p set. +/// \pre \p set->indexed_by_object_id must be false. +/// \param[inout] set The set to append to +/// \param[in] ptr The pointer to append +inline void __CPROVER_contracts_obj_set_append( + __CPROVER_contracts_obj_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert(!(set->indexed_by_object_id), "not indexed by object id"); + __CPROVER_assert(set->watermark < set->max_elems, "no OOB access"); +#endif + set->nof_elems = set->watermark; + set->elems[set->watermark] = ptr; + set->watermark += 1; + set->is_empty = 0; +} + +/* FUNCTION: __CPROVER_contracts_obj_set_remove */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Removes \p ptr form \p set if \p ptr exists in \p set, +/// no-op otherwise. +/// \param[inout] set Set to update +/// \param[in] ptr Pointer to remove +inline void __CPROVER_contracts_obj_set_remove( + __CPROVER_contracts_obj_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert(set->indexed_by_object_id, "indexed by object id"); + __CPROVER_assert( + __CPROVER_POINTER_OBJECT(ptr) < set->max_elems, "no OOB access"); +#endif + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); + set->nof_elems = set->elems[object_id] ? set->nof_elems - 1 : set->nof_elems; + set->is_empty = set->nof_elems == 0; + set->elems[object_id] = 0; +} + +/* FUNCTION: __CPROVER_contracts_obj_set_contains */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Checks if a pointer with the same object id as \p ptr is contained in +/// \p set. +/// \param[inout] set The set to check membership in +/// \param ptr The pointer to check +/// \return True iff a pointer with the same object id exists in \p set +inline __CPROVER_bool __CPROVER_contracts_obj_set_contains( + __CPROVER_contracts_obj_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert(set->indexed_by_object_id, "indexed by object id"); + __CPROVER_assert( + __CPROVER_POINTER_OBJECT(ptr) < set->max_elems, "no OOB access"); +#endif + return set->elems[__CPROVER_POINTER_OBJECT(ptr)] != 0; +} + +/* FUNCTION: __CPROVER_contracts_obj_set_contains_exact */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Checks if \p ptr is contained in \p set. +/// \param[inout] set The set to check membership in +/// \param ptr The pointer to check +/// \return True iff \p ptr exists in \p set +inline __CPROVER_bool __CPROVER_contracts_obj_set_contains_exact( + __CPROVER_contracts_obj_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert(set->indexed_by_object_id, "indexed by object id"); + __CPROVER_assert( + __CPROVER_POINTER_OBJECT(ptr) < set->max_elems, "no OOB access"); +#endif + return set->elems[__CPROVER_POINTER_OBJECT(ptr)] == ptr; +} + +/* FUNCTION: __CPROVER_contracts_write_set_create */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +inline void __CPROVER_contracts_car_set_create( + __CPROVER_contracts_car_set_ptr_t, + __CPROVER_size_t); + +inline void __CPROVER_contracts_obj_set_create_indexed_by_object_id( + __CPROVER_contracts_obj_set_ptr_t); + +inline void __CPROVER_contracts_obj_set_create_append( + __CPROVER_contracts_obj_set_ptr_t, + __CPROVER_size_t); + +/// \brief Initialises a \ref __CPROVER_contracts_write_set_t object. +/// \param[inout] set Pointer to the object to initialise +/// \param[in] contract_assigns_size Max size of the assigns clause +/// \param[in] contract_frees_size Max size of the frees clause +/// \param[in] replacement True iff this write set is used to replace a contract +/// \param[in] assume_requires_ctx True iff this write set is used to check side +/// effects in a requires clause in contract checking mode +/// \param[in] assert_requires_ctx True iff this write set is used to check side +/// effects in a requires clause in contract replacement mode +/// \param[in] assume_ensures_ctx True iff this write set is used to check for +/// side effects in an ensures clause in contract replacement mode +/// \param[in] assert_ensures_ctx True iff this write set is used to check for +/// side effects in an ensures clause in contract checking mode +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 replacement, + __CPROVER_bool assume_requires_ctx, + __CPROVER_bool assert_requires_ctx, + __CPROVER_bool assume_ensures_ctx, + __CPROVER_bool assert_ensures_ctx) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert( + __CPROVER_w_ok(set, sizeof(__CPROVER_contracts_write_set_t)), + "set writable"); +#endif + __CPROVER_contracts_car_set_create( + &(set->contract_assigns), contract_assigns_size); + __CPROVER_contracts_obj_set_create_indexed_by_object_id( + &(set->contract_frees)); + set->replacement = replacement; + if(replacement) + { + __CPROVER_contracts_obj_set_create_append( + &(set->contract_frees_replacement), contract_frees_size); + } + else + { + set->contract_frees_replacement.elems = 0; + } + __CPROVER_contracts_obj_set_create_indexed_by_object_id(&(set->allocated)); + __CPROVER_contracts_obj_set_create_indexed_by_object_id(&(set->deallocated)); + __CPROVER_contracts_obj_set_create_indexed_by_object_id( + &(set->is_freshr_seen)); + set->linked_allocated = 0; + set->assume_requires_ctx = assume_requires_ctx; + set->assert_requires_ctx = assert_requires_ctx; + set->assume_ensures_ctx = assume_ensures_ctx; + set->assert_ensures_ctx = assert_ensures_ctx; +} + +/* FUNCTION: __CPROVER_contracts_write_set_release */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// @brief Releases resources used by \p set. +void __CPROVER_contracts_write_set_release( + __CPROVER_contracts_write_set_ptr_t set) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert( + __CPROVER_rw_ok(set, sizeof(__CPROVER_contracts_write_set_t)), + "set readable"); + __CPROVER_assert( + __CPROVER_rw_ok(&(set->contract_assigns.elems), 0), + "contract_assigns writable"); + __CPROVER_assert( + __CPROVER_rw_ok(&(set->contract_frees.elems), 0), + "contract_frees writable"); + __CPROVER_assert( + (set->replacement == 0) || + __CPROVER_rw_ok(&(set->contract_frees_replacement.elems), 0), + "contract_frees_replacement writable"); + __CPROVER_assert( + __CPROVER_rw_ok(&(set->allocated.elems), 0), "allocated writable"); + __CPROVER_assert( + __CPROVER_rw_ok(&(set->deallocated.elems), 0), "deallocated writable"); + __CPROVER_assert( + __CPROVER_rw_ok(&(set->deallocated.elems), 0), "is_freshr_seen writable"); +#endif + __CPROVER_deallocate(set->contract_assigns.elems); + __CPROVER_deallocate(set->contract_frees.elems); + if(set->replacement != 0) + { + __CPROVER_deallocate(set->contract_frees_replacement.elems); + } + __CPROVER_deallocate(set->allocated.elems); + __CPROVER_deallocate(set->deallocated.elems); + __CPROVER_deallocate(set->is_freshr_seen.elems); + // do not free set->deallocated_linked->elems + // since it is owned by another write_set instance +} + +/* FUNCTION: __CPROVER_contracts_write_set_insert_assignable */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +inline void __CPROVER_contracts_car_set_insert( + __CPROVER_contracts_car_set_ptr_t set, + __CPROVER_size_t idx, + void *ptr, + __CPROVER_size_t size); + +/// \brief Inserts a snapshot of the range starting at \p ptr of size \p size +/// at index \p idx in \p set->contract_assigns. +/// \param[inout] set The set to update +/// \param[in] idx Insertion index +/// \param[in] ptr Start of the range of bytes +/// \param[in] size Size of the range in bytes +void __CPROVER_contracts_write_set_insert_assignable( + __CPROVER_contracts_write_set_ptr_t set, + __CPROVER_size_t idx, + void *ptr, + __CPROVER_size_t size) +{ +__CPROVER_HIDE:; + __CPROVER_contracts_car_set_insert(&(set->contract_assigns), idx, ptr, size); +} + +/* FUNCTION: __CPROVER_contracts_write_set_insert_whole_object */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +inline void __CPROVER_contracts_car_set_insert( + __CPROVER_contracts_car_set_ptr_t set, + __CPROVER_size_t idx, + void *ptr, + __CPROVER_size_t size); + +/// \brief Inserts a snapshot of the range of bytes covering the whole object +/// pointed to by \p ptr in \p set->contact_assigns at index \p idx. +/// +/// - The start address is `ptr - __CPROVER_POINTER_OFFSET(ptr)` +/// - The size in bytes is `__CPROVER_OBJECT_SIZE(ptr)` +/// +/// at index \p idx in \p set. +/// \param[inout] set The set to update +/// \param[in] idx Insertion index +/// \param[in] ptr Pointer to the object +void __CPROVER_contracts_write_set_insert_whole_object( + __CPROVER_contracts_write_set_ptr_t set, + __CPROVER_size_t idx, + void *ptr) +{ +__CPROVER_HIDE:; + __CPROVER_contracts_car_set_insert( + &(set->contract_assigns), + idx, + ((char *)ptr) - __CPROVER_POINTER_OFFSET(ptr), + __CPROVER_OBJECT_SIZE(ptr)); +} + +/* FUNCTION: __CPROVER_contracts_write_set_insert_object_from */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +inline void __CPROVER_contracts_car_set_insert( + __CPROVER_contracts_car_set_ptr_t set, + __CPROVER_size_t idx, + void *ptr, + __CPROVER_size_t size); + +/// \brief Inserts a snapshot of the range of bytes starting at \p ptr and +/// extending to the end of the object in \p set->contact_assigns at index +/// \p idx. +/// +/// - The start address is `ptr` +/// - The size in bytes is +/// `__CPROVER_OBJECT_SIZE(ptr) - __CPROVER_POINTER_OFFSET(ptr)` +/// +/// \param[inout] set The set to update +/// \param[in] idx Insertion index +/// \param[in] ptr Pointer to the start of the range +void __CPROVER_contracts_write_set_insert_object_from( + __CPROVER_contracts_write_set_ptr_t set, + __CPROVER_size_t idx, + void *ptr) +{ + __CPROVER_contracts_car_set_insert( + &(set->contract_assigns), + idx, + ptr, + __CPROVER_OBJECT_SIZE(ptr) - __CPROVER_POINTER_OFFSET(ptr)); +} + +/* FUNCTION: __CPROVER_contracts_write_set_insert_object_upto */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +inline void __CPROVER_contracts_car_set_insert( + __CPROVER_contracts_car_set_ptr_t set, + __CPROVER_size_t idx, + void *ptr, + __CPROVER_size_t size); + +/// \brief Inserts a snapshot of the range of bytes starting at \p ptr of +/// \p size bytes in \p set->contact_assigns at index \p idx. +/// +/// - The start address is `ptr` +/// - The size in bytes is `size` +/// +/// \param[inout] set The set to update +/// \param[in] idx Insertion index +/// \param[in] ptr Pointer to the start of the range +/// \param[in] size Size of the range in bytes +void __CPROVER_contracts_write_set_insert_object_upto( + __CPROVER_contracts_write_set_ptr_t set, + __CPROVER_size_t idx, + void *ptr, + __CPROVER_size_t size) +{ +__CPROVER_HIDE:; + __CPROVER_contracts_car_set_insert(&(set->contract_assigns), idx, ptr, size); +} + +/* FUNCTION: __CPROVER_contracts_write_set_add_freeable */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +void __CPROVER_contracts_obj_set_add( + __CPROVER_contracts_obj_set_ptr_t set, + void *ptr); + +void __CPROVER_contracts_obj_set_append( + __CPROVER_contracts_obj_set_ptr_t set, + void *ptr); + +/// \brief Adds the freeable pointer \p ptr to \p set->contract_frees. +/// \param[inout] set The set to update +/// \param[in] ptr The pointer to add +void __CPROVER_contracts_write_set_add_freeable( + __CPROVER_contracts_write_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; + // we don't check yet that the pointer satisfies + // the __CPROVER_contracts_is_freeable as precondition. + // preconditions will be checked if there is an actual attempt + // to free the pointer. + + // store pointer + __CPROVER_contracts_obj_set_add(&(set->contract_frees), ptr); + + // append pointer if available + if(set->replacement) + __CPROVER_contracts_obj_set_append(&(set->contract_frees_replacement), ptr); +} + +/* FUNCTION: __CPROVER_contracts_write_set_add_allocated */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +void __CPROVER_contracts_obj_set_add(__CPROVER_contracts_obj_set_ptr_t, void *); + +/// \brief Adds the pointer \p ptr to \p set->allocated. +/// \param[inout] set The set to update +/// \param[in] ptr Pointer to an object declared using a `DECL x` or +/// `x = __CPROVER_allocate(...)` GOTO instruction. +void __CPROVER_contracts_write_set_add_allocated( + __CPROVER_contracts_write_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; + __CPROVER_contracts_obj_set_add(&(set->allocated), ptr); +} + +/* FUNCTION: __CPROVER_contracts_write_set_record_dead */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +void __CPROVER_contracts_obj_set_remove( + __CPROVER_contracts_obj_set_ptr_t, + void *); + +/// \brief Records that an object is dead by removing the pointer \p ptr from +/// \p set->allocated. +/// +/// \pre \p ptr is the start address `&x` of an object declared as 'DEAD x'. +/// +/// \param[inout] set The set to update +/// \param[in] ptr Pointer to the dead object +void __CPROVER_contracts_write_set_record_dead( + __CPROVER_contracts_write_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; + __CPROVER_contracts_obj_set_remove(&(set->allocated), ptr); +} + +/* FUNCTION: __CPROVER_contracts_write_set_record_deallocated */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Records that an object is deallocated by adding the pointer \p ptr to +/// \p set->deallocated. +/// +/// \pre \p ptr was deallocated with a call to `__CPROVER_deallocate(ptr)`. +/// +/// \param[inout] set The set to update +/// \param[in] ptr Pointer to the deallocated object +void __CPROVER_contracts_write_set_record_deallocated( + __CPROVER_contracts_write_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert(set->replacement == 0, "!replacement"); +#endif + // we need to record the deallocation to evaluate post conditions + __CPROVER_contracts_obj_set_add(&(set->deallocated), ptr); +} + +/* FUNCTION: __CPROVER_contracts_write_set_check_allocated_deallocated_is_empty */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Returns true iff \p set->deallocated is empty. +/// +/// \param[in] set The set to be checked for emptiness +/// \returns True iff \p set->deallocated is empty +inline __CPROVER_bool +__CPROVER_contracts_write_set_check_allocated_deallocated_is_empty( + __CPROVER_contracts_write_set_ptr_t set) +{ +__CPROVER_HIDE:; + return set->allocated.is_empty & set->deallocated.is_empty; +} + +/* FUNCTION: __CPROVER_contracts_write_set_check_assignment */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +inline __CPROVER_contracts_car_t +__CPROVER_contracts_car_create(void *, __CPROVER_size_t); + +inline __CPROVER_bool __CPROVER_contracts_car_set_contains( + __CPROVER_contracts_car_set_ptr_t, + __CPROVER_contracts_car_t); + +#if 0 +/// \brief Checks if an assignment to the range of bytes starting at \p ptr of +/// size \p size is allowed by \p set. +/// +/// \param[in] set Write set to check the assignment against +/// \param[in] ptr Start address of the assigned range +/// \param[in] size Size of the assigned range in bytes +/// \return True iff the range of bytes starting at \p ptr of size \p size is +/// contained \p set->allocated or in \p set->contract_assigns. +inline __CPROVER_bool __CPROVER_contracts_write_set_check_assignment( + __CPROVER_contracts_write_set_ptr_t set, + void *ptr, + __CPROVER_size_t size) +{ +__CPROVER_HIDE:; +# ifdef DFCC_SELF_CHECK + __CPROVER_assert(set->replacement == 0, "!replacement"); +# endif + __CPROVER_assert( + ((ptr == 0) | __CPROVER_rw_ok(ptr, size)), + "ptr NULL or writable up to size"); + + __CPROVER_assert( + (ptr == 0) | (__CPROVER_POINTER_OBJECT(ptr) < set->allocated.max_elems), + "no OOB access"); + + __CPROVER_contracts_car_t car = __CPROVER_contracts_car_create(ptr, size); + if(!car.is_writable) + return 0; + + if(set->allocated.elems[__CPROVER_POINTER_OBJECT(ptr)] != 0) + return 1; + + return __CPROVER_contracts_car_set_contains(&(set->contract_assigns), car); +} +#else + +/// \brief Checks if an assignment to the range of bytes starting at \p ptr and +/// of \p size bytes is allowed according to \p set. +/// +/// \param[in] set Write set to check the assignment against +/// \param[in] ptr Start address of the assigned range +/// \param[in] size Size of the assigned range in bytes +/// \return True iff the range of bytes starting at \p ptr of \p size bytes is +/// contained in \p set->allocated or \p set->contract_assigns. +inline __CPROVER_bool __CPROVER_contracts_write_set_check_assignment( + __CPROVER_contracts_write_set_ptr_t set, + void *ptr, + __CPROVER_size_t size) +{ +__CPROVER_HIDE:; +# ifdef DFCC_SELF_CHECK + __CPROVER_assert(set->replacement == 0, "!replacement"); +# endif + +# pragma CPROVER check push +# pragma CPROVER check disable "pointer" +# pragma CPROVER check disable "pointer-primitive" +# pragma CPROVER check disable "unsigned-overflow" +# pragma CPROVER check disable "signed-overflow" +# pragma CPROVER check disable "undefined-shift" +# pragma CPROVER check disable "conversion" + + __CPROVER_assert( + (ptr == 0) | (__CPROVER_POINTER_OBJECT(ptr) < set->allocated.max_elems), + "no OOB access"); + + __CPROVER_assert( + ((ptr == 0) | __CPROVER_rw_ok(ptr, size)), + "ptr NULL or writable up to size"); + + // the range is not writable + if(ptr == 0) + return 0; + + // is ptr pointing to a locally allocated object ? + if(set->allocated.elems[__CPROVER_POINTER_OBJECT(ptr)] != 0) + return 1; + + // don't even drive symex into the rest of the function if the set is emtpy + if(set->contract_assigns.max_elems == 0) + return 0; + + // Compute the upper bound, perform inclusion check against contract-assigns + __CPROVER_assert( + size < __CPROVER_max_malloc_size, + "CAR size is less than __CPROVER_max_malloc_size"); + __CPROVER_ssize_t offset = __CPROVER_POINTER_OFFSET(ptr); + __CPROVER_assert( + !(offset > 0) | + ((__CPROVER_size_t)offset + size < __CPROVER_max_malloc_size), + "no offset bits overflow on CAR upper bound computation"); + void *ub = (void *)((char *)ptr + size); + __CPROVER_contracts_car_t *elem = set->contract_assigns.elems; + __CPROVER_size_t idx = set->contract_assigns.max_elems; + __CPROVER_bool incl = 0; + +SET_CHECK_ASSIGNMENT_LOOP: + while(idx != 0) + { + incl |= + elem->is_writable & __CPROVER_same_object(elem->lb, ptr) & + (__CPROVER_POINTER_OFFSET(elem->lb) <= __CPROVER_POINTER_OFFSET(ptr)) & + (__CPROVER_POINTER_OFFSET(ub) <= __CPROVER_POINTER_OFFSET(elem->ub)); + ++elem; + --idx; + } + return incl; +# pragma CPROVER check pop +} +#endif + +/* FUNCTION: __CPROVER_contracts_write_set_check_array_set */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +inline __CPROVER_bool __CPROVER_contracts_write_set_check_assignment( + __CPROVER_contracts_write_set_ptr_t, + void *, + __CPROVER_size_t); + +/// \brief Checks if the operation `array_set(dest, ...)` is allowed according +/// to \p set. +/// +/// \remark The `array_set` operation updates all bytes of the object starting +/// from \p dest. +/// +/// \param[in] set Write set to check the array_set operation against +/// \param[in] dest The destination pointer +/// \return True iff the range of bytes starting at \p dest and of +/// `__CPROVER_OBJECT_SIZE(dest) - __CPROVER_POINTER_OFFSET(dest)` bytes is +/// contained in \p set->allocated or \p set->contract_assigns. +__CPROVER_bool __CPROVER_contracts_write_set_check_array_set( + __CPROVER_contracts_write_set_ptr_t set, + void *dest) +{ +__CPROVER_HIDE:; + return __CPROVER_contracts_write_set_check_assignment( + set, dest, __CPROVER_OBJECT_SIZE(dest) - __CPROVER_POINTER_OFFSET(dest)); +} + +/* FUNCTION: __CPROVER_contracts_write_set_check_array_copy */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +inline __CPROVER_bool __CPROVER_contracts_write_set_check_assignment( + __CPROVER_contracts_write_set_ptr_t, + void *, + __CPROVER_size_t); + +/// \brief Checks if the operation `array_copy(dest, ...)` is allowed according +/// to \p set. +/// +/// \remark The `array_copy` operation updates all of `*dest` (possibly using +/// nondet values), even when `*src` is smaller. +/// +/// \param[in] set Write set to check the `array_copy` operation against +/// \param[in] dest The destination pointer +/// \return True iff the range of bytes starting at \p dest and of +/// `__CPROVER_OBJECT_SIZE(dest) - __CPROVER_POINTER_OFFSET(dest)` bytes is +/// contained in \p set->allocated or \p set->contract_assigns. +__CPROVER_bool __CPROVER_contracts_write_set_check_array_copy( + __CPROVER_contracts_write_set_ptr_t set, + void *dest) +{ +__CPROVER_HIDE:; + return __CPROVER_contracts_write_set_check_assignment( + set, dest, __CPROVER_OBJECT_SIZE(dest) - __CPROVER_POINTER_OFFSET(dest)); +} + +/* FUNCTION: __CPROVER_contracts_write_set_check_array_replace */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +inline __CPROVER_bool __CPROVER_contracts_write_set_check_assignment( + __CPROVER_contracts_write_set_ptr_t, + void *, + __CPROVER_size_t); + +/// \brief Checks if the operation `array_replace(dest, src)` is allowed +/// according to \p set. +/// +/// \remark The `array_replace` operation updates at most `size-of-*src` bytes +/// in \p *dest, i.e. it replaces `MIN(size-of-*dest, size-of-*src)` bytes in +/// \p *dest. +/// +/// \param[in] set Write set to check the `array_replace` operation against +/// \param[in] dest The destination pointer +/// \param[in] src The source pointer +/// \return True iff the range of bytes starting at \p dest and extending for +/// `MIN(__CPROVER_OBJECT_SIZE(dest) - __CPROVER_POINTER_OFFSET(dest), +/// __CPROVER_OBJECT_SIZE(src) - __CPROVER_POINTER_OFFSET(src))` bytes is +/// contained in \p set->allocated or \p set->contract_assigns. +__CPROVER_bool __CPROVER_contracts_write_set_check_array_replace( + __CPROVER_contracts_write_set_ptr_t set, + void *dest, + void *src) +{ +__CPROVER_HIDE:; + __CPROVER_size_t src_size = + __CPROVER_OBJECT_SIZE(src) - __CPROVER_POINTER_OFFSET(src); + __CPROVER_size_t dest_size = + __CPROVER_OBJECT_SIZE(dest) - __CPROVER_POINTER_OFFSET(dest); + __CPROVER_size_t size = dest_size < src_size ? dest_size : src_size; + return __CPROVER_contracts_write_set_check_assignment(set, dest, size); +} + +/* FUNCTION: __CPROVER_contracts_write_set_check_havoc_object */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +inline __CPROVER_bool __CPROVER_contracts_write_set_check_assignment( + __CPROVER_contracts_write_set_ptr_t, + void *, + __CPROVER_size_t); + +/// \brief Checks if a `havoc_object(ptr)` is allowed according to \p set. +/// +/// \param[in] set The write set to check the operation against +/// \param[in] ptr Pointer to the havoced object +/// \return True iff the range of bytes starting at +/// `(char *)ptr - __CPROVER_POINTER_OFFSET(ptr)` and of size +/// `__CPROVER_OBJECT_SIZE(ptr)` is contained in `set->contract_assigns` or +/// `set->allocated`. +__CPROVER_bool __CPROVER_contracts_write_set_check_havoc_object( + __CPROVER_contracts_write_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; + return __CPROVER_contracts_write_set_check_assignment( + set, + (char *)ptr - __CPROVER_POINTER_OFFSET(ptr), + __CPROVER_OBJECT_SIZE(ptr)); +} + +/* FUNCTION: __CPROVER_contracts_write_set_check_deallocate */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +__CPROVER_bool +__CPROVER_contracts_obj_set_contains(__CPROVER_contracts_obj_set_ptr_t, void *); + +/// \brief Checks if the deallocation of \p ptr is allowed according to \p set. +/// +/// \pre The pointer \p ptr is involved in the GOTO instruction +/// `CALL __CPROVER_deallocate(ptr);` +/// +/// \param[in] set Write set to check the deallocation against +/// \param[in] ptr Deallocated pointer to check set to check the deallocation +/// against +/// \return True iff \p ptr is contained in \p set->contract_frees or +/// \p set->allocated. +__CPROVER_bool __CPROVER_contracts_write_set_check_deallocate( + __CPROVER_contracts_write_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert(set->replacement == 0, "!replacement"); +#endif + // NULL pointers can always be passed to free + if(!ptr) + return 1; + + // is this one of the recorded pointers ? + return __CPROVER_contracts_obj_set_contains_exact( + &(set->contract_frees), ptr) || + __CPROVER_contracts_obj_set_contains_exact(&(set->allocated), ptr); +} + +/* FUNCTION: __CPROVER_contracts_write_set_check_assigns_clause_inclusion */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Checks the inclusion of the \p candidate->contract_assigns elements +/// in \p reference->contract_assigns or \p reference->allocated. +/// +/// \pre \p reference must not be in replacement mode. +/// \pre \p candidate must be in replacement mode and \p candidate->allocated +/// must be empty. +/// +/// \param[in] reference Reference write set from a caller +/// \param[in] candidate Candidate write set from a contract being replaced +/// \return True iff all elements of \p candidate->contract_assigns are included +/// in some element of \p reference->contract_assigns or \p reference->allocated +inline __CPROVER_bool +__CPROVER_contracts_write_set_check_assigns_clause_inclusion( + __CPROVER_contracts_write_set_ptr_t reference, + __CPROVER_contracts_write_set_ptr_t candidate) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert( + reference->replacement == 0, "reference set in !replacement"); + __CPROVER_assert(candidate->replacement != 0, "candidate set in replacement"); +#endif + __CPROVER_bool incl = 1; + __CPROVER_contracts_car_t *current = candidate->contract_assigns.elems; + __CPROVER_size_t idx = candidate->contract_assigns.max_elems; +SET_CHECK_ASSIGNS_CLAUSE_INCLUSION_LOOP: + while(idx != 0) + { + if(current->is_writable) + { + incl &= __CPROVER_contracts_write_set_check_assignment( + reference, current->lb, current->size); + } + --idx; + ++current; + } + return incl; +} + +/* FUNCTION: __CPROVER_contracts_write_set_check_frees_clause_inclusion */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Checks the inclusion of the \p candidate->contract_frees elements +/// in \p reference->contract_frees or \p reference->allocated. +/// +/// \pre \p reference must not be in replacement mode. +/// \pre \p candidate must be in replacement mode and \p candidate->allocated +/// must be empty. +/// +/// \param[in] reference Reference write set from a caller +/// \param[in] candidate Candidate write set from a contract being replaced +/// \return True iff all elements of \p candidate->contract_frees are included +/// in some element of \p reference->contract_frees or \p reference->allocated +inline __CPROVER_bool +__CPROVER_contracts_write_set_check_frees_clause_inclusion( + __CPROVER_contracts_write_set_ptr_t reference, + __CPROVER_contracts_write_set_ptr_t candidate) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert( + reference->replacement == 0, "reference set in !replacement"); + __CPROVER_assert(candidate->replacement != 0, "candidate set in replacement"); +#endif + __CPROVER_bool all_incl = 1; + void **current = candidate->contract_frees_replacement.elems; + __CPROVER_size_t idx = candidate->contract_frees_replacement.max_elems; +SET_CHECK_FREES_CLAUSE_INCLUSION_LOOP: + while(idx != 0) + { + void *ptr = *current; + all_incl &= + __CPROVER_contracts_obj_set_contains(&(reference->contract_frees), ptr) || + __CPROVER_contracts_obj_set_contains(&(reference->allocated), ptr); + --idx; + ++current; + } + + return all_incl; +} + +/* FUNCTION: __CPROVER_contracts_write_set_deallocate_freeable */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +__CPROVER_bool nondet_CPROVER_bool(); + +/// \brief Models the instrumented version of the free function. +/// +/// \remark Uses of this function will be remapped to the instrumented version +/// of the `free` found in the goto model. +__CPROVER_bool +__CPROVER_contracts_free(void *, __CPROVER_contracts_write_set_ptr_t); + +void __CPROVER_contracts_write_set_record_deallocated( + __CPROVER_contracts_write_set_ptr_t, + void *); + +/// \brief Non-deterministically call \ref __CPROVER_contracts_free on all +/// elements of \p set->contract_frees, and records the freed pointers in +/// \p target->deallocated. +/// +/// \param[in] set Write set to free +/// \param[out] target Write set to record deallocations in +void __CPROVER_contracts_write_set_deallocate_freeable( + __CPROVER_contracts_write_set_ptr_t set, + __CPROVER_contracts_write_set_ptr_t target) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert(set->replacement == 1, "set is in replacement"); + __CPROVER_assert( + (target == 0) | (target->replacement == 0), "target is in !replacement"); +#endif + void **current = set->contract_frees_replacement.elems; + __CPROVER_size_t idx = set->contract_frees_replacement.max_elems; +SET_DEALLOCATE_FREEABLE_LOOP: + while(idx != 0) + { + void *ptr = *current; + + // call free only iff the pointer is valid preconditions are met +#pragma CPROVER check push +#pragma CPROVER check disable "pointer" +#pragma CPROVER check disable "pointer-primitive" +#pragma CPROVER check disable "unsigned-overflow" +#pragma CPROVER check disable "signed-overflow" +#pragma CPROVER check disable "undefined-shift" +#pragma CPROVER check disable "conversion" + // avoid pointer-primitive checks on r_ok, dynobject and offset + __CPROVER_bool preconditions = + (ptr == 0) | (__CPROVER_r_ok(ptr, 0) & __CPROVER_DYNAMIC_OBJECT(ptr) & + (__CPROVER_POINTER_OFFSET(ptr) == 0)); +#pragma CPROVER check pop + // TODO make sure not to deallocate the same pointer twice + if((ptr != 0) & preconditions & nondet_CPROVER_bool()) + { + __CPROVER_contracts_free(ptr, 0); + __CPROVER_contracts_write_set_record_deallocated(set, ptr); + // also record effects in the caller write set + if(target != 0) + __CPROVER_contracts_write_set_record_deallocated(target, ptr); + } + --idx; + ++current; + } +} + +/* FUNCTION: __CPROVER_contracts_link_is_freshr_allocated */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Links \p write_set_to_link->allocated to +/// \p write_set_postconditions->linked_allocated so that allocations performed +/// by \ref __CPROVER_contracts_is_freshr when evaluating ensures clauses are +/// recorded in \p write_set_to_link. +void __CPROVER_contracts_link_is_freshr_allocated( + __CPROVER_contracts_write_set_ptr_t write_set_postconditions, + __CPROVER_contracts_write_set_ptr_t write_set_to_link) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert( + write_set_postconditions != 0, "write_set_postconditions not NULL"); +#endif + if((write_set_to_link != 0)) + { + write_set_postconditions->linked_allocated = + &(write_set_to_link->allocated); + } + else + { + write_set_postconditions->linked_allocated = 0; + } +} + +/* FUNCTION: __CPROVER_contracts_link_deallocated */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Links \p write_set_to_link->deallocated to +/// \p write_set_postconditions->linked_deallocated so that deallocations +/// performed by the function get recorded in \p write_set_to_link->deallocated +/// and are later available to \ref __CPROVER_contracts_is_freed predicate +/// when evaluating ensures clauses. +void __CPROVER_contracts_link_deallocated( + __CPROVER_contracts_write_set_ptr_t write_set_postconditions, + __CPROVER_contracts_write_set_ptr_t write_set_to_link) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert( + write_set_postconditions != 0, "write_set_postconditions not NULL"); +#endif + if((write_set_to_link != 0)) + { + write_set_postconditions->linked_deallocated = + &(write_set_to_link->deallocated); + } + else + { + write_set_postconditions->linked_deallocated = 0; + } +} + +/* FUNCTION: __CPROVER_contracts_is_freshr */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Models the instrumented interface of the `malloc` function +/// \remark Calls to this function will be remapped to the actual instrumented +/// version of malloc found in the goto model. +void *__CPROVER_contracts_malloc( + __CPROVER_size_t, + __CPROVER_contracts_write_set_ptr_t); + +void __CPROVER_contracts_obj_set_add(__CPROVER_contracts_obj_set_ptr_t, void *); + +__CPROVER_bool +__CPROVER_contracts_obj_set_contains(__CPROVER_contracts_obj_set_ptr_t, void *); + +/// \brief Implementation of the `is_fresh`/`is_freshr` front-end predicates. +/// +/// The behaviour depends on the boolean flags carried by \p set +/// which reflect the invocation context: checking vs. replacing a contract, +/// in a requires or an ensures clause context. +/// \param elem First argument of the `is_freshr`/`is_fresh` predicate +/// \param size Second argument of the `is_freshr`/`is_fresh` predicate +/// \param set Write set to record seen and allocated objects in; +/// +/// \details The behaviour is as follows: +/// - When \p set->assume_requires_ctx is `true`, the predicate allocates a new +/// object, records the object in \p set->is_freshr_seen, updates \p *elem to +/// point to the fresh object and returns `true`; +/// - When \p set->assume_ensures_ctx is `true`, the predicate allocates a new +/// object, records the object in \p set->linked_allocated, updates \p *elem +/// to point to the fresh object and returns `true`; +/// - When \p set->assert_requires_ctx or \p set->assert_ensures_ctx is `true`, +/// the predicate computes if \p *elem is readable to the desired \p size and is +/// is not found in \p set->is_freshr_seen, records the object in +/// \p set->is_freshr_seen and returns the computed condition. +__CPROVER_bool __CPROVER_contracts_is_freshr( + void **elem, + __CPROVER_size_t size, + __CPROVER_contracts_write_set_ptr_t set) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert( + __CPROVER_rw_ok(set, sizeof(__CPROVER_contracts_write_set_t)), + "set readable"); +#endif +#pragma CPROVER check push +#pragma CPROVER check disable "pointer" +#pragma CPROVER check disable "pointer-primitive" +#pragma CPROVER check disable "pointer-overflow" +#pragma CPROVER check disable "signed-overflow" +#pragma CPROVER check disable "unsigned-overflow" +#pragma CPROVER check disable "conversion" + if(set->assume_requires_ctx) + { + __CPROVER_assert( + (set->assert_requires_ctx == 0) & (set->assume_ensures_ctx == 0) & + (set->assert_ensures_ctx == 0), + "only one context flag at a time"); + // pass a null pointer to malloc si that the object does not get tracked + // as assignable in the requires clause scope + void *ptr = __CPROVER_contracts_malloc(size, 0); + *elem = ptr; + if(!ptr) + return 0; + // record fresh object in the map + __CPROVER_contracts_obj_set_add(&(set->is_freshr_seen), ptr); + return 1; + } + else if(set->assume_ensures_ctx) + { + __CPROVER_assert( + (set->assume_requires_ctx == 0) & (set->assert_requires_ctx == 0) & + (set->assert_ensures_ctx == 0), + "only one context flag at a time"); + void *ptr = __CPROVER_contracts_malloc(size, 0); + // record new object in linked allocated set + __CPROVER_contracts_obj_set_add(set->linked_allocated, ptr); + *elem = ptr; + return (ptr != 0); + } + else if(set->assert_requires_ctx | set->assert_ensures_ctx) + { + __CPROVER_assert( + (set->assume_requires_ctx == 0) & (set->assume_ensures_ctx == 0), + "only one context flag at a time"); + // check separation + __CPROVER_contracts_obj_set_ptr_t seen = &(set->is_freshr_seen); + __CPROVER_bool not_contains = + !__CPROVER_contracts_obj_set_contains(seen, *elem); + __CPROVER_bool r_ok = __CPROVER_r_ok(*elem, size); + __CPROVER_bool ok = not_contains & r_ok; + // record object + __CPROVER_contracts_obj_set_add(seen, *elem); + return ok; + } + else + { + __CPROVER_assert( + 0, "__CPROVER_is_freshr is only called in requires or ensures clauses"); + __CPROVER_assume(0); + return 0; // just to silence libcheck + } +#pragma CPROVER check pop +} + +/* FUNCTION: __CPROVER_contracts_write_set_havoc_get_assignable_target */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Returns the start address of the conditional address range found at +/// index \p idx in \p set->contract_assigns. +void *__CPROVER_contracts_write_set_havoc_get_assignable_target( + __CPROVER_contracts_write_set_ptr_t set, + __CPROVER_size_t idx) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert(idx < set->contract_assigns.max_elems, "no OOB access"); +#endif + __CPROVER_contracts_car_t car = set->contract_assigns.elems[idx]; + if(car.is_writable) + return car.lb; + else + return (void *)0; +} + +/* FUNCTION: __CPROVER_contracts_write_set_havoc_whole_object */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +/// \brief Havocs the whole object pointed to by the lower bound pointer of the +/// element stored at index \p idx in \p set->contract_assigns, if it is +/// writable. +void __CPROVER_contracts_write_set_havoc_whole_object( + __CPROVER_contracts_write_set_ptr_t set, + __CPROVER_size_t idx) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert(idx < set->contract_assigns.max_elems, "no OOB access"); +#endif + __CPROVER_contracts_car_t car = set->contract_assigns.elems[idx]; + if(car.is_writable) + __CPROVER_havoc_object(car.lb); +} + +/* FUNCTION: __CPROVER_contracts_write_set_havoc_slice */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +char __VERIFIER_nondet_char(); + +/// \brief Havocs the range of bytes represented byt the element stored at index +/// \p idx in \p set->contract_assigns, if it is writable. +void __CPROVER_contracts_write_set_havoc_slice( + __CPROVER_contracts_write_set_ptr_t set, + __CPROVER_size_t idx) +{ +__CPROVER_HIDE:; +#ifdef DFCC_SELF_CHECK + __CPROVER_assert(idx < set->contract_assigns.max_elems, "no OOB access"); +#endif + __CPROVER_contracts_car_t car = set->contract_assigns.elems[idx]; + if(car.is_writable) + { + // TODO use __CPROVER_havoc_slice(car.lb, car.size); + // when array_replace gets fixed + char *target = car.lb; + __CPROVER_size_t i = car.size; + while(i != 0) + { + *(target) = __VERIFIER_nondet_char(); + target++; + i--; + } + } +} + +/* FUNCTION: __CPROVER_contracts_is_freeable */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +extern void *__CPROVER_alloca_object; +extern const void *__CPROVER_deallocated; +extern const void *__CPROVER_new_object; +extern __CPROVER_bool __CPROVER_malloc_is_new_array; + +/// \brief Implementation of the `is_freeable` front-end predicate. +/// \return True iff a pointer satisfies the preconditions for the `free` +/// function and can hence be safely deallocated using `free`. +/// +/// \details If called in an assumption context, +/// only basic conditions are checked: the pointer has offset 0 and points to a +/// dynamic object. If called in an assertion context, extra conditions +/// depending on nondeterministic CPROVER instrumentation variables are checked, +/// yielding the full set of conditions checked by the CPROVER library +/// implementation of free. +__CPROVER_bool __CPROVER_contracts_is_freeable( + void *ptr, + __CPROVER_contracts_write_set_ptr_t set) +{ +__CPROVER_HIDE:; + __CPROVER_assert( + (set != 0) & + ((set->assume_requires_ctx == 1) | (set->assert_requires_ctx == 1) | + (set->assume_ensures_ctx == 1) | (set->assert_ensures_ctx == 1)), + "__CPROVER_is_freeable is used only in requires or ensures clauses"); + + // These are all the preconditions checked by `free` of the CPROVER library + __CPROVER_bool is_dynamic_object = (ptr == 0) | __CPROVER_DYNAMIC_OBJECT(ptr); + __CPROVER_bool has_offset_zero = + (ptr == 0) | (__CPROVER_POINTER_OFFSET(ptr) == 0); + + if((set->assume_requires_ctx == 1) || (set->assume_ensures_ctx == 1)) + return is_dynamic_object & has_offset_zero; + + // these conditions cannot be used in assumptions since they involve + // demonic non-determinism + __CPROVER_bool is_null_or_valid_pointer = (ptr == 0) | __CPROVER_r_ok(ptr, 0); + __CPROVER_bool is_not_deallocated = + (ptr == 0) | (__CPROVER_deallocated != ptr); + __CPROVER_bool is_not_alloca = (ptr == 0) | (__CPROVER_alloca_object != ptr); + __CPROVER_bool is_not_array = (ptr == 0) | (__CPROVER_new_object != ptr) | + (!__CPROVER_malloc_is_new_array); + return is_null_or_valid_pointer & is_dynamic_object & has_offset_zero & + is_not_deallocated & is_not_alloca & is_not_array; +} + +/* FUNCTION: __CPROVER_contracts_is_freed */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +__CPROVER_bool +__CPROVER_contracts_obj_set_contains(__CPROVER_contracts_obj_set_ptr_t, void *); + +/// \brief Returns true iff the pointer \p ptr is found in \p set->deallocated. +__CPROVER_bool +__CPROVER_contracts_is_freed(void *ptr, __CPROVER_contracts_write_set_ptr_t set) +{ +__CPROVER_HIDE:; + __CPROVER_assert( + (set != 0) & + ((set->assume_ensures_ctx == 1) | (set->assert_ensures_ctx == 1)), + "__CPROVER_is_freed is used only in ensures clauses"); + __CPROVER_assert( + (set->linked_deallocated != 0), "linked_deallocated is not null"); + return __CPROVER_contracts_obj_set_contains(set->linked_deallocated, ptr); +} + +/* FUNCTION: __CPROVER_contracts_check_replace_ensures_is_freed_preconditions */ + +#ifndef __CPROVER_contracts_write_set_t_defined +# define __CPROVER_contracts_write_set_t_defined + +typedef struct __CPROVER_contracts_car_t +{ + __CPROVER_bool is_writable; + __CPROVER_size_t size; + void *lb; + void *ub; +} __CPROVER_contracts_car_t; + +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; + +typedef __CPROVER_contracts_car_set_t *__CPROVER_contracts_car_set_ptr_t; + +typedef struct __CPROVER_contracts_obj_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_size_t watermark; + __CPROVER_size_t nof_elems; + __CPROVER_bool is_empty; + __CPROVER_bool indexed_by_object_id; + void **elems; +} __CPROVER_contracts_obj_set_t; + +typedef __CPROVER_contracts_obj_set_t *__CPROVER_contracts_obj_set_ptr_t; + +typedef struct __CPROVER_contracts_write_set_t +{ + __CPROVER_contracts_car_set_t contract_assigns; + __CPROVER_contracts_obj_set_t contract_frees; + __CPROVER_contracts_obj_set_t contract_frees_replacement; + __CPROVER_contracts_obj_set_t allocated; + __CPROVER_contracts_obj_set_t deallocated; + __CPROVER_contracts_obj_set_t is_freshr_seen; + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + __CPROVER_bool replacement; + __CPROVER_bool assume_requires_ctx; + __CPROVER_bool assert_requires_ctx; + __CPROVER_bool assume_ensures_ctx; + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; + +typedef __CPROVER_contracts_write_set_t *__CPROVER_contracts_write_set_ptr_t; +#endif + +__CPROVER_bool +__CPROVER_contracts_obj_set_contains(__CPROVER_contracts_obj_set_ptr_t, void *); + +/// \brief Asserts that \p ptr is found in \p set->contract_frees. +/// +/// \details If proved, the assertion demonstrates that it is possible to assume +/// that `is_freed(ptr)` holds as a post condition without causing a +/// contradiction when assuming that the ensures clause of a contract holds. +void __CPROVER_contracts_check_replace_ensures_is_freed_preconditions( + void *ptr, + __CPROVER_contracts_write_set_ptr_t set) +{ +__CPROVER_HIDE:; + __CPROVER_assert( + set && ((set->assume_ensures_ctx == 1) | (set->assert_ensures_ctx == 1)), + "__CPROVER_is_freed is used only in ensures clauses"); + + if(set->assume_ensures_ctx) + { + __CPROVER_assert( + __CPROVER_contracts_obj_set_contains(&(set->contract_frees), ptr), + "assuming __CPROVER_is_freed(ptr) requires ptr to always exist in the " + "contract's frees clause"); + } +} diff --git a/src/goto-instrument/Makefile b/src/goto-instrument/Makefile index dd186497a2c..65b40462517 100644 --- a/src/goto-instrument/Makefile +++ b/src/goto-instrument/Makefile @@ -18,6 +18,7 @@ SRC = accelerate/accelerate.cpp \ call_sequences.cpp \ contracts/contracts.cpp \ contracts/dynamic-frames/dfcc_utils.cpp \ + contracts/dynamic-frames/dfcc_library.cpp \ contracts/havoc_assigns_clause_targets.cpp \ contracts/instrument_spec_assigns.cpp \ contracts/memory_predicates.cpp \ diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_library.cpp b/src/goto-instrument/contracts/dynamic-frames/dfcc_library.cpp new file mode 100644 index 00000000000..f68b17756e4 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_library.cpp @@ -0,0 +1,571 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking + +Author: Remi Delmas, delmarsd@amazon.com + +\*******************************************************************/ + +#include "dfcc_library.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "dfcc_utils.h" + +// NOLINTNEXTLINE(build/deprecated) +#define CONTRACTS_PREFIX CPROVER_PREFIX "contracts_" + +/// Class constructor +dfcc_libraryt::dfcc_libraryt( + goto_modelt &goto_model, + dfcc_utilst &utils, + messaget &log) + : goto_model(goto_model), + utils(utils), + log(log), + message_handler(log.get_message_handler()) +{ +} + +/// Swaps keys and values in a map +template +std::map swap_map(std::map const &map) +{ + std::map result; + for(auto const &pair : map) + result.insert({pair.second, pair.first}); + return result; +} + +/// enum to type name mapping +static const std::map dfcc_type_to_name = { + {dfcc_typet::FREEABLE, CPROVER_PREFIX "freeable_t"}, + {dfcc_typet::ASSIGNABLE, CPROVER_PREFIX "assignable_t"}, + {dfcc_typet::CAR, CONTRACTS_PREFIX "car_t"}, + {dfcc_typet::CAR_SET, CONTRACTS_PREFIX "car_set_t"}, + {dfcc_typet::CAR_SET_PTR, CONTRACTS_PREFIX "car_set_ptr_t"}, + {dfcc_typet::OBJ_SET, CONTRACTS_PREFIX "obj_set_t"}, + {dfcc_typet::OBJ_SET_PTR, CONTRACTS_PREFIX "obj_set_ptr_t"}, + {dfcc_typet::WRITE_SET, CONTRACTS_PREFIX "write_set_t"}, + {dfcc_typet::WRITE_SET_PTR, CONTRACTS_PREFIX "write_set_ptr_t"}}; + +/// Swapped dfcc_type_to_name +static const std::map + dfcc_name_to_type(swap_map(dfcc_type_to_name)); + +/// enum to function name mapping +static const std::map dfcc_fun_to_name = { + // clang-format off + {dfcc_funt::CAR_CREATE, + CONTRACTS_PREFIX "car_create"}, + {dfcc_funt::CAR_SET_CREATE, + CONTRACTS_PREFIX "car_set_create"}, + {dfcc_funt::CAR_SET_INSERT, + CONTRACTS_PREFIX "car_set_insert"}, + {dfcc_funt::CAR_SET_REMOVE, + CONTRACTS_PREFIX "car_set_remove"}, + {dfcc_funt::CAR_SET_CONTAINS, + CONTRACTS_PREFIX "car_set_contains"}, + {dfcc_funt::OBJ_SET_CREATE_INDEXED_BY_OBJECT_ID, + CONTRACTS_PREFIX "obj_set_create_indexed_by_object_id"}, + {dfcc_funt::OBJ_SET_CREATE_APPEND, + CONTRACTS_PREFIX "obj_set_create_append"}, + {dfcc_funt::OBJ_SET_ADD, + CONTRACTS_PREFIX "obj_set_add"}, + {dfcc_funt::OBJ_SET_APPEND, + CONTRACTS_PREFIX "obj_set_append"}, + {dfcc_funt::OBJ_SET_REMOVE, + CONTRACTS_PREFIX "obj_set_remove"}, + {dfcc_funt::OBJ_SET_CONTAINS, + CONTRACTS_PREFIX "obj_set_contains"}, + {dfcc_funt::OBJ_SET_CONTAINS_EXACT, + CONTRACTS_PREFIX "obj_set_contains_exact"}, + {dfcc_funt::WRITE_SET_CREATE, + CONTRACTS_PREFIX "write_set_create"}, + {dfcc_funt::WRITE_SET_RELEASE, + CONTRACTS_PREFIX "write_set_release"}, + {dfcc_funt::WRITE_SET_INSERT_ASSIGNABLE, + CONTRACTS_PREFIX "write_set_insert_assignable"}, + {dfcc_funt::WRITE_SET_INSERT_WHOLE_OBJECT, + CONTRACTS_PREFIX "write_set_insert_whole_object"}, + {dfcc_funt::WRITE_SET_INSERT_OBJECT_FROM, + CONTRACTS_PREFIX "write_set_insert_object_from"}, + {dfcc_funt::WRITE_SET_INSERT_OBJECT_UPTO, + CONTRACTS_PREFIX "write_set_insert_object_upto"}, + {dfcc_funt::WRITE_SET_ADD_FREEABLE, + CONTRACTS_PREFIX "write_set_add_freeable"}, + {dfcc_funt::WRITE_SET_ADD_ALLOCATED, + CONTRACTS_PREFIX "write_set_add_allocated"}, + {dfcc_funt::WRITE_SET_RECORD_DEAD, + CONTRACTS_PREFIX "write_set_record_dead"}, + {dfcc_funt::WRITE_SET_RECORD_DEALLOCATED, + CONTRACTS_PREFIX "write_set_record_deallocated"}, + {dfcc_funt::WRITE_SET_CHECK_ALLOCATED_DEALLOCATED_IS_EMPTY, + CONTRACTS_PREFIX "write_set_check_allocated_deallocated_is_empty"}, + {dfcc_funt::WRITE_SET_CHECK_ASSIGNMENT, + CONTRACTS_PREFIX "write_set_check_assignment"}, + {dfcc_funt::WRITE_SET_CHECK_ARRAY_SET, + CONTRACTS_PREFIX "write_set_check_array_set"}, + {dfcc_funt::WRITE_SET_CHECK_ARRAY_COPY, + CONTRACTS_PREFIX "write_set_check_array_copy"}, + {dfcc_funt::WRITE_SET_CHECK_ARRAY_REPLACE, + CONTRACTS_PREFIX "write_set_check_array_replace"}, + {dfcc_funt::WRITE_SET_CHECK_HAVOC_OBJECT, + CONTRACTS_PREFIX "write_set_check_havoc_object"}, + {dfcc_funt::WRITE_SET_CHECK_DEALLOCATE, + CONTRACTS_PREFIX "write_set_check_deallocate"}, + {dfcc_funt::WRITE_SET_CHECK_ASSIGNS_CLAUSE_INCLUSION, + CONTRACTS_PREFIX "write_set_check_assigns_clause_inclusion"}, + {dfcc_funt::WRITE_SET_CHECK_FREES_CLAUSE_INCLUSION, + CONTRACTS_PREFIX "write_set_check_frees_clause_inclusion"}, + {dfcc_funt::WRITE_SET_DEALLOCATE_FREEABLE, + CONTRACTS_PREFIX "write_set_deallocate_freeable"}, + {dfcc_funt::WRITE_SET_HAVOC_GET_ASSIGNABLE_TARGET, + CONTRACTS_PREFIX "write_set_havoc_get_assignable_target"}, + {dfcc_funt::WRITE_SET_HAVOC_WHOLE_OBJECT, + CONTRACTS_PREFIX "write_set_havoc_whole_object"}, + {dfcc_funt::WRITE_SET_HAVOC_SLICE, + CONTRACTS_PREFIX "write_set_havoc_slice"}, + {dfcc_funt::LINK_DEALLOCATED, + CONTRACTS_PREFIX "link_deallocated"}, + {dfcc_funt::LINK_IS_FRESHR_ALLOCATED, + CONTRACTS_PREFIX "link_is_freshr_allocated"}, + {dfcc_funt::IS_FRESHR, + CONTRACTS_PREFIX "is_freshr"}, + {dfcc_funt::IS_FREEABLE, + CONTRACTS_PREFIX "is_freeable"}, + {dfcc_funt::IS_FREED, + CONTRACTS_PREFIX "is_freed"}, + {dfcc_funt::REPLACE_ENSURES_IS_FREED_PRECONDITIONS, + CONTRACTS_PREFIX "check_replace_ensures_is_freed_preconditions"} + // clang-format on +}; + +// Swapped dfcc_fun_to_name +static const std::map + dfcc_name_to_fun(swap_map(dfcc_fun_to_name)); + +/// Maps built-in function names to enums to use for instrumentation +static const std::map dfcc_hook = { + {CPROVER_PREFIX "assignable", dfcc_funt::WRITE_SET_INSERT_ASSIGNABLE}, + {CPROVER_PREFIX "whole_object", dfcc_funt::WRITE_SET_INSERT_WHOLE_OBJECT}, + {CPROVER_PREFIX "object_from", dfcc_funt::WRITE_SET_INSERT_OBJECT_FROM}, + {CPROVER_PREFIX "object_upto", dfcc_funt::WRITE_SET_INSERT_OBJECT_UPTO}, + {CPROVER_PREFIX "freeable", dfcc_funt::WRITE_SET_ADD_FREEABLE}}; + +/// Returns the instrumentation function to use for a given front-end function +optionalt dfcc_libraryt::get_hook(const irep_idt &function_id) const +{ + auto found = dfcc_hook.find(function_id); + if(found != dfcc_hook.end()) + return {found->second}; + else + return {}; +} + +/// Maps front-end functions to library functions giving their havoc semantics +static const std::map havoc_hook = { + {CPROVER_PREFIX "assignable", + dfcc_funt::WRITE_SET_HAVOC_GET_ASSIGNABLE_TARGET}, + {CPROVER_PREFIX "whole_object", dfcc_funt::WRITE_SET_HAVOC_WHOLE_OBJECT}, + {CPROVER_PREFIX "object_from", dfcc_funt::WRITE_SET_HAVOC_SLICE}, + {CPROVER_PREFIX "object_upto", dfcc_funt::WRITE_SET_HAVOC_SLICE}}; + +/// Returns the havoc function to use for a given front-end function +optionalt +dfcc_libraryt::get_havoc_hook(const irep_idt &function_id) const +{ + auto found = havoc_hook.find(function_id); + if(found != havoc_hook.end()) + return {found->second}; + else + return {}; +} + +/// All built-in function names (front-end and instrumentation hooks) +static const std::set assignable_builtin_names = { + CPROVER_PREFIX "assignable", + CPROVER_PREFIX "assignable_set_insert_assignable", + CPROVER_PREFIX "whole_object", + CPROVER_PREFIX "assignable_set_insert_whole_object", + CPROVER_PREFIX "object_from", + CPROVER_PREFIX "assignable_set_insert_object_from", + CPROVER_PREFIX "object_upto", + CPROVER_PREFIX "assignable_set_insert_object_upto", + CPROVER_PREFIX "freeable", + CPROVER_PREFIX "assignable_set_add_freeable"}; + +void dfcc_libraryt::get_missing_funs( + std::set &missing, + std::set &to_instrument) +{ + missing.clear(); + for(const auto &pair : dfcc_fun_to_name) + { + symbol_tablet::symbolst::const_iterator found = + goto_model.symbol_table.symbols.find(pair.second); + + if( + found == goto_model.symbol_table.symbols.end() || + found->second.value.is_nil()) + { + missing.insert(pair.second); + } + } + + // add `malloc` since it is needed used by the `is_freshr` function + missing.insert("malloc"); + to_instrument.insert("malloc"); + + // add `free` and `__CPROVER_deallocate` since they are used by the + // `write_set_deallocate_freeable` + missing.insert("free"); + to_instrument.insert("free"); + + // used by `write_set_release` + missing.insert(CPROVER_PREFIX "deallocate"); + to_instrument.insert(CPROVER_PREFIX "deallocate"); + + // Make sure all front end functions are loaded + missing.insert(CPROVER_PREFIX "assignable"); + missing.insert(CPROVER_PREFIX "object_from"); + missing.insert(CPROVER_PREFIX "object_upto"); + missing.insert(CPROVER_PREFIX "whole_object"); + missing.insert(CPROVER_PREFIX "freeable"); +} + +bool dfcc_libraryt::loaded = false; + +void dfcc_libraryt::load(std::set &to_instrument) +{ + PRECONDITION_WITH_DIAGNOSTICS( + !loaded, "the cprover_contracts library can only be loaded once"); + loaded = true; + + log.status() << "Adding the cprover_contracts library (" << config.ansi_c.arch + << ")" << messaget::eom; + + std::set missing; + get_missing_funs(missing, to_instrument); + + symbol_tablet tmp_symbol_table; + cprover_c_library_factory_force_load( + missing, tmp_symbol_table, message_handler); + + // copy all missing symbols to the main symbol table + for(const auto &symbol_pair : tmp_symbol_table.symbols) + { + const auto &sym = symbol_pair.first; + if(!goto_model.symbol_table.has_symbol(sym)) + { + log.debug() << "dfcc_libraryt: inserting " << sym << " in symbol table" + << messaget::eom; + goto_model.symbol_table.insert(symbol_pair.second); + } + } + + // compile all missing symbols to goto + for(const auto &id : missing) + { + log.debug() << "dfcc_libraryt: goto_convert(" << id << ")" << messaget::eom; + goto_convert( + id, goto_model.symbol_table, goto_model.goto_functions, message_handler); + } + + // check that all symbols have a goto_implementation + // and populate symbol maps + namespacet ns(goto_model.symbol_table); + for(const auto &pair : dfcc_fun_to_name) + { + const auto &found = + goto_model.goto_functions.function_map.find(pair.second); + if(!(found != goto_model.goto_functions.function_map.end() && + found->second.body_available())) + { + log.error() << "dfcc_libraryt: GOTO body of dfcc function " << pair.second + << " not found" << messaget::eom; + throw 0; + } + dfcc_fun_symbol[pair.first] = ns.lookup(pair.second); + } + + // populate symbol maps for easy access to symbols during translation + for(const auto &pair : dfcc_type_to_name) + { + dfcc_type[pair.first] = ns.lookup(pair.second).type; + } + + // fix malloc and free calls + fix_malloc_free_calls(); + + // inline the functions that need to be inlined for perf reasons + inline_functions(); + + // hide all instructions in counter example traces + set_hide(true); +} + +optionalt dfcc_libraryt::get_dfcc_fun(const irep_idt &id) const +{ + auto found = dfcc_name_to_fun.find(id); + if(found != dfcc_name_to_fun.end()) + return {found->second}; + else + return {}; +} + +bool dfcc_libraryt::is_dfcc_library_symbol(const irep_idt &id) const +{ + return get_dfcc_fun(id).has_value(); +} + +const irep_idt &dfcc_libraryt::get_dfcc_fun_name(dfcc_funt fun) const +{ + return dfcc_fun_to_name.at(fun); +} + +/// set of functions that need to be inlined +static const std::set to_inline = { + dfcc_funt::WRITE_SET_CREATE, + dfcc_funt::WRITE_SET_RELEASE, + dfcc_funt::WRITE_SET_INSERT_ASSIGNABLE, + dfcc_funt::WRITE_SET_INSERT_WHOLE_OBJECT, + dfcc_funt::WRITE_SET_INSERT_OBJECT_FROM, + dfcc_funt::WRITE_SET_INSERT_OBJECT_UPTO, + dfcc_funt::WRITE_SET_ADD_FREEABLE, + dfcc_funt::WRITE_SET_ADD_ALLOCATED, + dfcc_funt::WRITE_SET_RECORD_DEAD, + dfcc_funt::WRITE_SET_RECORD_DEALLOCATED, + dfcc_funt::WRITE_SET_CHECK_ASSIGNMENT, + dfcc_funt::WRITE_SET_CHECK_ARRAY_SET, + dfcc_funt::WRITE_SET_CHECK_ARRAY_COPY, + dfcc_funt::WRITE_SET_CHECK_ARRAY_REPLACE, + dfcc_funt::WRITE_SET_CHECK_HAVOC_OBJECT, + dfcc_funt::WRITE_SET_CHECK_DEALLOCATE, + dfcc_funt::WRITE_SET_DEALLOCATE_FREEABLE}; + +bool dfcc_libraryt::inlined = false; + +void dfcc_libraryt::inline_functions() +{ + log.debug() << "->dfcc_libraryt::inline_functions() " << messaget::eom; + INVARIANT(!inlined, "inline_functions can only be called once"); + inlined = true; + for(const auto &function_id : to_inline) + { + log.debug() << "dfcc_libraryt::inline_functions: inlining " + << dfcc_fun_to_name.at(function_id) << messaget::eom; + utils.inline_function(dfcc_fun_to_name.at(function_id)); + } + log.debug() << "<-dfcc_libraryt::inline_functions() " << messaget::eom; +} + +/// set of functions that need to be unwound to assigns clause size with +/// corresponding loop identifiers. +static const std::map to_unwind = { + {dfcc_funt::WRITE_SET_CHECK_ASSIGNMENT, 0}, + {dfcc_funt::WRITE_SET_CHECK_ARRAY_SET, 0}, + {dfcc_funt::WRITE_SET_CHECK_ARRAY_COPY, 0}, + {dfcc_funt::WRITE_SET_CHECK_ARRAY_REPLACE, 0}, + {dfcc_funt::WRITE_SET_CHECK_HAVOC_OBJECT, 0}}; + +bool dfcc_libraryt::specialized = false; + +void dfcc_libraryt::specialize(const int contract_assigns_size) +{ + log.debug() << "->dfcc_libraryt::specialize(" << contract_assigns_size << ") " + << messaget::eom; + INVARIANT( + !specialized, + "dfcc_libraryt::specialize_functions can only be called once"); + specialized = true; + unwindsett unwindset{goto_model}; + std::list set; + for(const auto &entry : to_unwind) + { + const auto &function = entry.first; + const auto &loop_id = entry.second; + std::stringstream stream; + stream << id2string(dfcc_fun_to_name.at(function)) << "." << loop_id << ":" + << contract_assigns_size + 1; + const auto &str = stream.str(); + log.debug() << "->dfcc_libraryt::specialize(" << str << ") " + << messaget::eom; + set.push_back(str); + } + unwindset.parse_unwindset(set, message_handler); + goto_unwindt goto_unwind; + goto_unwind( + goto_model, unwindset, goto_unwindt::unwind_strategyt::ASSERT_ASSUME); + log.debug() << "<-dfcc_libraryt::specialize(" << contract_assigns_size << ") " + << messaget::eom; +} + +/// Set of functions that contain calls to assignable_malloc or assignable_free +static const std::set fix_malloc_free_set = { + dfcc_funt::WRITE_SET_DEALLOCATE_FREEABLE, + dfcc_funt::IS_FRESHR}; + +/// True iff the library functions have already been fixed +bool dfcc_libraryt::malloc_free_fixed = false; + +void dfcc_libraryt::fix_malloc_free_calls() +{ + INVARIANT( + !malloc_free_fixed, + "dfcc_libraryt::fix_malloc_free_calls can only be called once"); + malloc_free_fixed = true; + for(const auto fun : fix_malloc_free_set) + { + goto_programt &prog = + goto_model.goto_functions.function_map.at(dfcc_fun_to_name.at(fun)).body; + + Forall_goto_program_instructions(ins, prog) + { + if(ins->is_function_call()) + { + const auto &function = ins->call_function(); + + if(function.id() == ID_symbol) + { + const irep_idt &fun_name = to_symbol_expr(function).get_identifier(); + + if(fun_name == (CONTRACTS_PREFIX "malloc")) + to_symbol_expr(ins->call_function()).set_identifier("malloc"); + + if(fun_name == (CONTRACTS_PREFIX "free")) + to_symbol_expr(ins->call_function()).set_identifier("free"); + } + } + } + } +} + +void dfcc_libraryt::inhibit_front_end_builtins() +{ + for(const auto &it : dfcc_hook) + { + const auto &fid = it.first; + if(goto_model.symbol_table.has_symbol(fid)) + { + log.debug() << "inhibiting built-in function " << fid << messaget::eom; + + // make sure parameter symbols exist + utils.fix_parameters_symbols(fid); + + // create fatal assertion code block as body + source_locationt sl; + sl.set_function(fid); + sl.set_file(""); + sl.set_property_class("sanity_check"); + sl.set_comment( + "Built-in " + id2string(fid) + + " should not be called after contracts transformation"); + auto block = create_fatal_assertion(false_exprt(), sl); + auto &symbol = goto_model.symbol_table.get_writeable_ref(fid); + symbol.value = block; + + // convert the function + goto_convert( + fid, + goto_model.symbol_table, + goto_model.goto_functions, + message_handler); + } + } +} + +/// Sets the given hide flag on all instructions of all library functions +void dfcc_libraryt::set_hide(bool hide) +{ + PRECONDITION(dfcc_libraryt::loaded); + for(auto it : dfcc_fun_symbol) + utils.set_hide(it.second.name, hide); +} + +/// Returns the "__dfcc_instrumented_functions" symbol or creates it if it does +/// not exist already. +/// This symbol is an unbounded map of booleans indexed +/// by function pointer ID, meant to have value true for instrumented functions +/// and false for non-instrumented functions. +/// This variable is meant to be defined by adding instrunctions to the +/// CPROVER_INITIALIZE function. +const symbolt &dfcc_libraryt::get_instrumented_functions_map_symbol() +{ + const irep_idt map_name = "__dfcc_instrumented_functions"; + + if(goto_model.symbol_table.has_symbol(map_name)) + return goto_model.symbol_table.lookup_ref(map_name); + + auto map_type = array_typet(c_bool_typet(8), infinity_exprt(size_type())); + + return utils.create_static_symbol( + map_type, + "", + "__dfcc_instrumented_functions", + source_locationt{}, + ID_C, + "", + array_of_exprt(from_integer(0, c_bool_typet(8)), map_type)); +} + +void dfcc_libraryt::add_instrumented_functions_map_init_instructions( + const std::set &instrumented_functions, + goto_programt &dest) +{ + auto instrumented_functions_map = + get_instrumented_functions_map_symbol().symbol_expr(); + + for(auto &function_id : instrumented_functions) + { + auto object_id = pointer_object( + address_of_exprt(utils.get_function_symbol(function_id).symbol_expr())); + auto index_expr = index_exprt(instrumented_functions_map, object_id); + dest.add(goto_programt::make_assignment( + index_expr, from_integer(1, c_bool_typet(8)))); + } + goto_model.goto_functions.update(); +} + +/// Re-generates the INITIALIZE_FUNCTION, embedding the information that +/// functions in `instrumented_functions` have been instrumented into the +/// goto model. +void dfcc_libraryt::create_initialize_function( + const std::set &instrumented_functions) +{ + // creates the symbol if it does not exist already + get_instrumented_functions_map_symbol(); + + // initialises all statics + utils.create_initialize_function(); + + // add extra init instructions at the end + goto_programt payload; + add_instrumented_functions_map_init_instructions( + instrumented_functions, payload); + auto &init_function = + goto_model.goto_functions.function_map.at(INITIALIZE_FUNCTION); + auto &body = init_function.body; + auto end = body.instructions.end(); + end--; + body.destructive_insert(end, payload); + goto_model.goto_functions.update(); +} diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_library.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_library.h new file mode 100644 index 00000000000..d2b123e62ab --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_library.h @@ -0,0 +1,258 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com + +\*******************************************************************/ + +/// \file +/// Dynamic frame condition checking library loading + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_LIBRARY_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_LIBRARY_H + +#include +#include + +#include +#include + +/// One enum value per type defined by the `cprover_dfcc.c` library file. +/// These enums are used to perform lookups into maps that contain the actual +/// symbols and to avoid using strings/irep_ids everywhere. +enum class dfcc_typet +{ + /// return type of functions that specify freeable locations (declarative) + FREEABLE, + /// return type of functions that specify assignable locations (declarative) + ASSIGNABLE, + /// type of descriptors of conditionally assignable ranges of bytes + CAR, + /// type of sets of CAR + CAR_SET, + /// type of pointers to sets of CAR + CAR_SET_PTR, + /// type of sets of object identifiers + OBJ_SET, + /// type of pointers to sets of object identifiers + OBJ_SET_PTR, + /// type of descriptors of assignable/freeable sets of locations + WRITE_SET, + /// type of pointers to descriptors of assignable/freeable sets of locations + WRITE_SET_PTR +}; + +/// One enum value per function defined by the `cprover_dfcc.c` library file. +/// These enums are used to perform lookups into maps that contain the actual +/// symbols and to avoid using strings/irep_ids everywhere. +enum class dfcc_funt +{ + /// \see __CPROVER_contracts_car_create + CAR_CREATE, + /// \see __CPROVER_contracts_car_set_create + CAR_SET_CREATE, + /// \see __CPROVER_contracts_car_set_insert + CAR_SET_INSERT, + /// \see __CPROVER_contracts_car_set_remove + CAR_SET_REMOVE, + /// \see __CPROVER_contracts_car_set_contains + CAR_SET_CONTAINS, + /// \see __CPROVER_contracts_obj_set_create_indexed_by_object_id + OBJ_SET_CREATE_INDEXED_BY_OBJECT_ID, + /// \see __CPROVER_contracts_obj_set_create_append + OBJ_SET_CREATE_APPEND, + /// \see __CPROVER_contracts_obj_set_add + OBJ_SET_ADD, + /// \see __CPROVER_contracts_obj_set_append + OBJ_SET_APPEND, + /// \see __CPROVER_contracts_obj_set_remove + OBJ_SET_REMOVE, + /// \see __CPROVER_contracts_obj_set_contains + OBJ_SET_CONTAINS, + /// \see __CPROVER_contracts_obj_set_contains_exact + OBJ_SET_CONTAINS_EXACT, + /// \see __CPROVER_contracts_write_set_create + WRITE_SET_CREATE, + /// \see __CPROVER_contracts_write_set_release + WRITE_SET_RELEASE, + /// \see __CPROVER_contracts_write_set_insert_assignable + WRITE_SET_INSERT_ASSIGNABLE, + /// \see __CPROVER_contracts_write_set_insert_whole_object + WRITE_SET_INSERT_WHOLE_OBJECT, + /// \see __CPROVER_contracts_write_set_insert_object_from + WRITE_SET_INSERT_OBJECT_FROM, + /// \see __CPROVER_contracts_write_set_object_upto + WRITE_SET_INSERT_OBJECT_UPTO, + /// \see __CPROVER_contracts_write_set_add_freeable + WRITE_SET_ADD_FREEABLE, + /// \see __CPROVER_contracts_write_set_add_allocated + WRITE_SET_ADD_ALLOCATED, + /// \see __CPROVER_contracts_write_set_record_dead + WRITE_SET_RECORD_DEAD, + /// \see __CPROVER_contracts_write_set_record_deallocated + WRITE_SET_RECORD_DEALLOCATED, + /// \see __CPROVER_contracts_write_set_check_allocated_deallocated_is_empty + WRITE_SET_CHECK_ALLOCATED_DEALLOCATED_IS_EMPTY, + /// \see __CPROVER_contracts_write_set_check_assignment + WRITE_SET_CHECK_ASSIGNMENT, + /// \see __CPROVER_contracts_write_set_check_array_set + WRITE_SET_CHECK_ARRAY_SET, + /// \see __CPROVER_contracts_write_set_check_array_copy + WRITE_SET_CHECK_ARRAY_COPY, + /// \see __CPROVER_contracts_write_set_check_array_replace + WRITE_SET_CHECK_ARRAY_REPLACE, + /// \see __CPROVER_contracts_write_set_check_havoc_object + WRITE_SET_CHECK_HAVOC_OBJECT, + /// \see __CPROVER_contracts_write_set_check_deallocate + WRITE_SET_CHECK_DEALLOCATE, + /// \see __CPROVER_contracts_write_set_check_assigns_clause_inclusion + WRITE_SET_CHECK_ASSIGNS_CLAUSE_INCLUSION, + /// \see __CPROVER_contracts_write_set_check_frees_clause_inclusion + WRITE_SET_CHECK_FREES_CLAUSE_INCLUSION, + /// \see __CPROVER_contracts_write_set_deallocate_freeable + WRITE_SET_DEALLOCATE_FREEABLE, + /// \see __CPROVER_contracts_write_set_havoc_get_assignable_target + WRITE_SET_HAVOC_GET_ASSIGNABLE_TARGET, + /// \see __CPROVER_contracts_write_set_havoc_whole_object + WRITE_SET_HAVOC_WHOLE_OBJECT, + /// \see __CPROVER_contracts_write_set_havoc_slice + WRITE_SET_HAVOC_SLICE, + /// \see __CPROVER_contracts_link_is_freshr_allocated + LINK_IS_FRESHR_ALLOCATED, + /// \see __CPROVER_contracts_link_deallocated + LINK_DEALLOCATED, + /// \see __CPROVER_contracts_is_freshr + IS_FRESHR, + /// \see __CPROVER_contracts_is_freeable + IS_FREEABLE, + /// \see __CPROVER_contracts_is_freed + IS_FREED, + /// \see __CPROVER_contracts_check_replace_ensures_is_freed_preconditions + REPLACE_ENSURES_IS_FREED_PRECONDITIONS +}; + +class goto_modelt; +class goto_functiont; +class goto_programt; +class messaget; +class message_handlert; +class symbolt; +class symbol_exprt; +class typet; +class dfcc_utilst; + +/// Class interface to library types and functions defined in +/// `cprover_contracts.c`. +class dfcc_libraryt +{ +public: + dfcc_libraryt(goto_modelt &goto_model, dfcc_utilst &utils, messaget &log); + +protected: + /// True iff the contracts library symbols are loaded + static bool loaded; + + /// True iff the library functions are inlined + static bool inlined; + + /// True iff the library functions are specialized + /// to a particular contract + static bool specialized; + + /// True iff the library functions uses of malloc and free are fixed + static bool malloc_free_fixed; + + goto_modelt &goto_model; + dfcc_utilst &utils; + messaget &log; + message_handlert &message_handler; + + /// Collects the names of all library functions currently missing from the + /// goto_model into `missing`. The ones that also need to be instrumented + /// are added to `to_instrument`. + void get_missing_funs( + std::set &missing, + std::set &to_instrument); + + /// Inlines library functions that need to be inlined before use + void inline_functions(); + + /// Fixes function calls to malloc and free in library functions. + /// Change calls to `__CPROVER_contracts_malloc` into calls to `malloc` + /// Change calls to `__CPROVER_contracts_free` into calls to `free` + void fix_malloc_free_calls(); + +public: + /// Maps enum values to the actual types (dynamically loaded) + std::map dfcc_type; + + /// Maps enum values to the actual function symbols (dynamically loaded) + std::map dfcc_fun_symbol; + + /// After calling this function, all library types and functions are present + /// in the the goto_model. Any other functions that the DFCC functions rely on + /// and need to be instrumented will be added to `to_instrument` + void load(std::set &to_instrument); + + /// Returns the dfcc_funt that corresponds to the given id if any. + optionalt get_dfcc_fun(const irep_idt &id) const; + + /// Returns the name of the given dfcc_funt. + const irep_idt &get_dfcc_fun_name(dfcc_funt fun) const; + + /// True iff the given id is one of the library symbols. + bool is_dfcc_library_symbol(const irep_idt &id) const; + + /// Specializes the library by unwinding loops in library functions + /// to the given assigns clause size. + /// \param contract_assigns_size_hint size of the assigns clause being checked + void specialize(const int contract_assigns_size_hint); + + /// Adds an ASSERT(false) body to all front-end functions + /// __CPROVER_whole_object + /// __CPROVER_object_upto + /// __CPROVER_object_from + /// __CPROVER_assignable + /// __CPROVER_freeable + /// To make sure they cannot be used in a proof unexpectedly + /// without causing verification errors. + void inhibit_front_end_builtins(); + + /// Sets the given hide flag on all instructions of all library functions + void set_hide(bool hide); + + /// Returns the library instrumentation hook for the given built-in. + /// function_id must be one of `__CPROVER_assignable`, + /// `__CPROVER_whole_object`, `__CPROVER_object_from`, + /// `__CPROVER_object_upto`, `__CPROVER_freeable` + optionalt get_hook(const irep_idt &function_id) const; + + /// Returns the library instrumentation hook for the given built-in. + /// function_id must be one of `__CPROVER_assignable`, + /// `__CPROVER_whole_object`, `__CPROVER_object_from`, `__CPROVER_object_upto` + optionalt get_havoc_hook(const irep_idt &function_id) const; + + /// Returns the "__dfcc_instrumented_functions" symbol or creates it if it + /// does not exist already. + /// This symbol is an unbounded map of booleans indexed + /// by function pointer ID, that holds value true for functions + /// that accept an extra write set parameter and false ones that don't. + /// To have this map actually defined one must call + /// \ref create_initialize_function with the set of instrumented functions. + const symbolt &get_instrumented_functions_map_symbol(); + + /// Generates instructions to initialize the instrumented function map + /// symbol from the given set of instrumented function + void add_instrumented_functions_map_init_instructions( + const std::set &instrumented_functions, + goto_programt &dest); + + /// Re-generates the INITIALIZE_FUNCTION, embedding the information that + /// functions in `instrumented_functions` have been instrumented into the + /// goto model. + void + create_initialize_function(const std::set &instrumented_functions); +}; + +#endif From 9bd1bdd14e29b9d0fc4349729077465130ec3203 Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Wed, 24 Aug 2022 11:08:02 -0400 Subject: [PATCH 06/20] CONTRACTS: Add `dfcc_contract_modet` and `dfcc_contract_handlert`. - `dfcc_contract_modet` is an enum representing the CHECK and REPLACE modes for contracts, - `dfcc_contract_handlert` is an abstract interface for generating GOTO instructions from contracts for CHECK or REPLACE modes. --- .../dynamic-frames/dfcc_contract_handler.h | 77 +++++++++++++++++++ .../dynamic-frames/dfcc_contract_mode.h | 23 ++++++ 2 files changed, 100 insertions(+) create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_contract_handler.h create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_contract_mode.h diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_handler.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_handler.h new file mode 100644 index 00000000000..5f8edaecec9 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_handler.h @@ -0,0 +1,77 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com + +\*******************************************************************/ + +/// \file +/// Abstract interface to generate contract checking and contract +/// replacement instructions from contracts. + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_CONTRACT_HANDLER_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_CONTRACT_HANDLER_H + +#include "dfcc_contract_mode.h" + +#include + +class goto_programt; +class symbolt; + +/// An abstract interface offering functions to generate GOTO instructions +/// encoding a contract in CHECK or REPLACE mode. +class dfcc_contract_handlert +{ +public: + /// Adds instruction modeling the contract to the `dest` program. + /// + /// \param[in] contract_mode CHECK or REPLACE mode + /// \param[in] wrapped_id name of function that is being checked/replaced + /// \param[in] wrapper_id name of function receiving the instructions + /// \param[in] contract_id name of the contract to check + /// \param[in] caller_write_set_symbol extra parameter of the wrapper + /// function through which the caller's write set is passed + /// \param[out] dest program where instructions are added + /// (should eventually become the body of wrapper_id) + /// \param[out] function_pointer_contracts list of contracts found in either + /// function pointer requires or ensures clauses. These contracts need to + /// be swapped and wrapped with themselves in replacement mode + /// if they are not already. + /// + /// In CHECK mode, the caller_write_set is ignored and the generated + /// instructions are: + /// - assume requires clauses + /// - assume requires clauses about function pointers contracts + /// - capture history variables for the ensures clauses + /// - create and populate write set from the contract + /// - call the checked function with the contract's write set + /// - assert ensures clauses + /// - assert ensures clauses about function pointers contracts + /// + /// In REPLACE mode, the generated instructions are: + /// - assert requires clauses + /// - assert requires clauses about function pointers contracts + /// - capture history variables for ensures clauses + /// - create write set from the contract + /// - check write set inclusion in the caller's write set + /// - havoc assigns clause targets set and call return value to nondet + /// - nondeterminstically free freeable targets specified by the frees clause + /// - assume ensures clauses + /// - assume ensures clauses about function pointers contracts + virtual void add_contract_instructions( + const dfcc_contract_modet contract_mode, + const irep_idt &wrapper_id, + const irep_idt &wrapped_id, + const irep_idt &contract_id, + const symbolt &caller_write_set_symbol, + goto_programt &dest, + std::set &function_pointer_contracts) = 0; + + /// Returns the size of the assigns clause of the given contract_id + /// in number of targets. + virtual const int get_assigns_clause_size(const irep_idt &contract_id) = 0; +}; + +#endif diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_mode.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_mode.h new file mode 100644 index 00000000000..f41cfe0b9ab --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_mode.h @@ -0,0 +1,23 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com +Date: August 2022 + +\*******************************************************************/ + +/// \file +/// Enum type representing the contract checking and replacement modes. + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_CONTRACT_MODE_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_CONTRACT_MODE_H + +/// Enum type representing the contract checking and replacement modes. +enum class dfcc_contract_modet +{ + CHECK, + REPLACE +}; + +#endif From a3af206cf4e824afc1761cefb2961dadb4f3d34a Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Wed, 24 Aug 2022 11:28:24 -0400 Subject: [PATCH 07/20] CONTRACTS: Map built-in predicate calls to their contract implem. Rewrite calls to - `is_fresh` to `__CPROVER_contracts_is_freshr` - `is_freshr` to `__CPROVER_contracts_is_freshr` - `is_freeable` to `__CPROVER_contracts_is_freeable` - `is_freed` to `__CPROVER_contracts_is_freed` --- src/goto-instrument/Makefile | 2 + .../dynamic-frames/dfcc_is_freeable.cpp | 79 +++++++++++++++++++ .../dynamic-frames/dfcc_is_freeable.h | 58 ++++++++++++++ .../dynamic-frames/dfcc_is_fresh.cpp | 78 ++++++++++++++++++ .../contracts/dynamic-frames/dfcc_is_fresh.h | 56 +++++++++++++ 5 files changed, 273 insertions(+) create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_is_freeable.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_is_freeable.h create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_is_fresh.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_is_fresh.h diff --git a/src/goto-instrument/Makefile b/src/goto-instrument/Makefile index 65b40462517..c5df83dfc1e 100644 --- a/src/goto-instrument/Makefile +++ b/src/goto-instrument/Makefile @@ -19,6 +19,8 @@ SRC = accelerate/accelerate.cpp \ contracts/contracts.cpp \ contracts/dynamic-frames/dfcc_utils.cpp \ contracts/dynamic-frames/dfcc_library.cpp \ + contracts/dynamic-frames/dfcc_is_fresh.cpp \ + contracts/dynamic-frames/dfcc_is_freeable.cpp \ contracts/havoc_assigns_clause_targets.cpp \ contracts/instrument_spec_assigns.cpp \ contracts/memory_predicates.cpp \ diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_is_freeable.cpp b/src/goto-instrument/contracts/dynamic-frames/dfcc_is_freeable.cpp new file mode 100644 index 00000000000..cdfc8768fb9 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_is_freeable.cpp @@ -0,0 +1,79 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com +Date: August 2022 + +\*******************************************************************/ +#include "dfcc_is_freeable.h" + +#include + +#include "dfcc_library.h" + +dfcc_is_freeablet::dfcc_is_freeablet(dfcc_libraryt &library, messaget &log) + : library(library), log(log) +{ +} + +void dfcc_is_freeablet::rewrite_calls( + goto_programt &program, + const exprt &write_set) +{ + rewrite_calls( + program, + program.instructions.begin(), + program.instructions.end(), + write_set); +} + +void dfcc_is_freeablet::rewrite_calls( + goto_programt &program, + goto_programt::targett first_instruction, + const goto_programt::targett &last_instruction, + const exprt &write_set) +{ + auto target = first_instruction; + while(target != last_instruction) + { + if(target->is_function_call()) + { + const auto &function = target->call_function(); + + if(function.id() == ID_symbol) + { + const irep_idt &fun_name = to_symbol_expr(function).get_identifier(); + + if(fun_name == CPROVER_PREFIX "is_freeable") + { + // redirect call to library implementation + to_symbol_expr(target->call_function()) + .set_identifier(library.get_dfcc_fun_name(dfcc_funt::IS_FREEABLE)); + target->call_arguments().push_back(write_set); + } + + if(fun_name == CPROVER_PREFIX "is_freed") + { + // insert call to precondition for vacuity checking + auto inst = goto_programt::make_function_call( + code_function_callt{ + library + .dfcc_fun_symbol + [dfcc_funt::REPLACE_ENSURES_IS_FREED_PRECONDITIONS] + .symbol_expr(), + {target->call_arguments().at(0), write_set}}, + target->source_location()); + program.insert_before_swap(target, inst); + target++; + + // redirect call to library implementation + to_symbol_expr(target->call_function()) + .set_identifier(library.get_dfcc_fun_name(dfcc_funt::IS_FREED)); + target->call_arguments().push_back(write_set); + } + } + } + target++; + } +} diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_is_freeable.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_is_freeable.h new file mode 100644 index 00000000000..0685297b6d1 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_is_freeable.h @@ -0,0 +1,58 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com +Date: August 2022 + +\*******************************************************************/ + +/// \file +/// Instruments occurrences of is_freeable predicates in programs +/// encoding requires and ensures clauses of contracts + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_IS_FREEABLE_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_IS_FREEABLE_H + +#include + +#include +#include +#include + +#include + +class goto_modelt; +class messaget; +class dfcc_libraryt; + +/// Rewrites calls to is_freeable and is_freed predicates in goto programs +/// encoding pre and post conditions. +class dfcc_is_freeablet +{ +public: + /// \param library the contracts instrumentation library + /// \param log the log to use for messages + dfcc_is_freeablet(dfcc_libraryt &library, messaget &log); + + /// Rewrites calls to is_freeable and is_freed predicates into calls + /// to the library implementation in the given program, passing the + /// given write_set expression as parameter to the library function. + void rewrite_calls(goto_programt &program, const exprt &write_set); + + /// Rewrites calls to is_fresh and is_freshr predicates into calls + /// to the library implementation in the given program between + /// first_instruction (included) and last_instruction (excluded), passing the + /// given write_set expression as parameter to the library function. + void rewrite_calls( + goto_programt &program, + goto_programt::targett first_instruction, + const goto_programt::targett &last_instruction, // excluding the last + const exprt &write_set); + +protected: + dfcc_libraryt &library; + messaget &log; +}; + +#endif diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_is_fresh.cpp b/src/goto-instrument/contracts/dynamic-frames/dfcc_is_fresh.cpp new file mode 100644 index 00000000000..4f23dc3645e --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_is_fresh.cpp @@ -0,0 +1,78 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com +Date: August 2022 + +\*******************************************************************/ + +#include "dfcc_is_fresh.h" + +#include +#include + +#include "dfcc_library.h" + +dfcc_is_fresht::dfcc_is_fresht(dfcc_libraryt &library, messaget &log) + : library(library), log(log) +{ +} + +void dfcc_is_fresht::rewrite_calls( + goto_programt &program, + const exprt &write_set) +{ + rewrite_calls( + program, + program.instructions.begin(), + program.instructions.end(), + write_set); +} + +void dfcc_is_fresht::rewrite_calls( + goto_programt &program, + goto_programt::targett first_instruction, + const goto_programt::targett &last_instruction, // excluding the last + const exprt &write_set) +{ + auto &target = first_instruction; + while(target != last_instruction) + { + if(target->is_function_call()) + { + const auto &function = target->call_function(); + + if(function.id() == ID_symbol) + { + const irep_idt &fun_name = to_symbol_expr(function).get_identifier(); + + if(fun_name == (is_fresh_id)) + { + // add address on first operand + target->call_arguments()[0] = + address_of_exprt(target->call_arguments()[0]); + + // fix the function name. + to_symbol_expr(target->call_function()) + .set_identifier(id2string( + library.dfcc_fun_symbol.find(dfcc_funt::IS_FRESHR)->second.name)); + + // pass the write_set + target->call_arguments().push_back(write_set); + } + if(fun_name == (is_freshr_id)) + { + // fix the function name. + to_symbol_expr(target->call_function()) + .set_identifier(id2string( + library.dfcc_fun_symbol.find(dfcc_funt::IS_FRESHR)->second.name)); + + // pass the write_set + target->call_arguments().push_back(write_set); + } + } + } + target++; + } +} diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_is_fresh.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_is_fresh.h new file mode 100644 index 00000000000..3c1cc87d57a --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_is_fresh.h @@ -0,0 +1,56 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com +Date: August 2022 + +\*******************************************************************/ + +/// \file +/// Instruments occurrences of is_fresh/is_freshr predicates in programs +/// encoding requires and ensures clauses of contracts + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_IS_FRESH_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_IS_FRESH_H + +#include +#include + +#include + +class messaget; +class dfcc_libraryt; + +/// Rewrites calls to is_fresh and is_freshr predicates into calls +/// to the library implementation. +class dfcc_is_fresht +{ +public: + /// \param library The contracts instrumentation library + /// \param log The log to use for messages + dfcc_is_fresht(dfcc_libraryt &library, messaget &log); + + /// Rewrites calls to is_fresh and is_freshr predicates into calls + /// to the library implementation in the given program, passing the + /// given write_set expression as parameter to the library function. + void rewrite_calls(goto_programt &program, const exprt &write_set); + + /// Rewrites calls to is_fresh and is_freshr predicates into calls + /// to the library implementation in the given program between + /// first_instruction (included) and last_instruction (excluded), passing the + /// given write_set expression as parameter to the library function. + void rewrite_calls( + goto_programt &program, + goto_programt::targett first_instruction, + const goto_programt::targett &last_instruction, // excluding the last + const exprt &write_set); + +protected: + dfcc_libraryt &library; + messaget &log; + const irep_idt is_fresh_id = CPROVER_PREFIX + std::string("is_fresh"); + const irep_idt is_freshr_id = CPROVER_PREFIX + std::string("is_freshr"); +}; + +#endif From 1a354f714119307a83060ca21c080b7ca676f7ec Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Wed, 24 Aug 2022 11:30:47 -0400 Subject: [PATCH 08/20] CONTRACTS: Add class `goto_program_cfg_infot`. This class extends `cfg_infot` and computes locals and dirty locals directly from a goto program's instruction sequence. Contary to `function_cfg_infot` it does not consider function parameters as locals. --- src/goto-instrument/contracts/cfg_info.h | 45 ++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/goto-instrument/contracts/cfg_info.h b/src/goto-instrument/contracts/cfg_info.h index 1ed166c3f4a..a3cc8b44dbd 100644 --- a/src/goto-instrument/contracts/cfg_info.h +++ b/src/goto-instrument/contracts/cfg_info.h @@ -188,4 +188,49 @@ class loop_cfg_infot : public cfg_infot const dirtyt is_dirty; std::unordered_set locals; }; + +/// For a goto program. locals and dirty locals are inferred directly from +/// the instruction sequence. +class goto_program_cfg_infot : public cfg_infot +{ +public: + explicit goto_program_cfg_infot(const goto_programt &goto_program) + { + // collect symbols declared in the insruction sequence as locals + for(const auto &instruction : goto_program.instructions) + { + if(instruction.is_decl()) + locals.insert(instruction.decl_symbol().get_identifier()); + } + + // collect dirty locals + goto_functiont goto_function; + goto_function.body.copy_from(goto_program); + + dirtyt is_dirty(goto_function); + auto dirty_ids = is_dirty.get_dirty_ids(); + dirty.insert(dirty_ids.begin(), dirty_ids.end()); + } + + /// Returns true iff `ident` is a loop local. + bool is_local(const irep_idt &ident) const override + { + return locals.find(ident) != locals.end(); + } + + /// Returns true iff the given `ident` is either not a loop local + /// or is a loop local that is dirty. + bool is_not_local_or_dirty_local(const irep_idt &ident) const override + { + if(is_local(ident)) + return dirty.find(ident) != dirty.end(); + else + return true; + } + +protected: + std::unordered_set locals; + std::unordered_set dirty; +}; + #endif From b202f92e7a8da8c4ef9d3487756ca2bdfb91f14d Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Wed, 24 Aug 2022 11:32:07 -0400 Subject: [PATCH 09/20] CONTRACTS: Add class `dfcc_instrumentt` to instrument goto programs with checks against a dynamic frame. --- src/goto-instrument/Makefile | 1 + .../dynamic-frames/dfcc_instrument.cpp | 1299 +++++++++++++++++ .../dynamic-frames/dfcc_instrument.h | 227 +++ 3 files changed, 1527 insertions(+) create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_instrument.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_instrument.h diff --git a/src/goto-instrument/Makefile b/src/goto-instrument/Makefile index c5df83dfc1e..27f89a9cb72 100644 --- a/src/goto-instrument/Makefile +++ b/src/goto-instrument/Makefile @@ -21,6 +21,7 @@ SRC = accelerate/accelerate.cpp \ contracts/dynamic-frames/dfcc_library.cpp \ contracts/dynamic-frames/dfcc_is_fresh.cpp \ contracts/dynamic-frames/dfcc_is_freeable.cpp \ + contracts/dynamic-frames/dfcc_instrument.cpp \ contracts/havoc_assigns_clause_targets.cpp \ contracts/instrument_spec_assigns.cpp \ contracts/memory_predicates.cpp \ diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_instrument.cpp b/src/goto-instrument/contracts/dynamic-frames/dfcc_instrument.cpp new file mode 100644 index 00000000000..efb85867983 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_instrument.cpp @@ -0,0 +1,1299 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking + +Author: Remi Delmas, delmarsd@amazon.com + +\*******************************************************************/ + +// TODO apply loop contracts transformations as part of function instrumentation + +#include "dfcc_instrument.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "dfcc_is_freeable.h" +#include "dfcc_is_fresh.h" +#include "dfcc_library.h" +#include "dfcc_utils.h" + +std::set dfcc_instrumentt::function_cache; + +dfcc_instrumentt::dfcc_instrumentt( + goto_modelt &goto_model, + messaget &log, + dfcc_utilst &utils, + dfcc_libraryt &library) + : goto_model(goto_model), + log(log), + message_handler(log.get_message_handler()), + utils(utils), + library(library), + ns(goto_model.symbol_table) +{ +} + +void dfcc_instrumentt::get_instrumented_functions( + std::set &dest) const +{ + dest.insert( + dfcc_instrumentt::function_cache.begin(), + dfcc_instrumentt::function_cache.end()); +} + +/* + A word on built-ins: + + C compiler builtins are declared in + - src/ansi-c/clang_builtin_headers*.h + - src/ansi-c/gcc_builtin_headers*.h + - src/ansi-c/windows_builtin_headers.h + + Some gcc builtins are compiled down to goto instructions + and inlined in place during type-checking: + - src/ansi-c/c_typecheck_gcc_polymorphic_builtins.cpp + - src/ansi-c/c_typecheck_expr.cpp, method do_special_functions + so they essentially disappear from the model. + + Builtins are also handled in: + - src/goto-programs/builtin_functions.cpp + - src/goto-symex/symex_builtin_functions.cpp + + Some compiler builtins have implementations defined as C functions in the + cprover library, and these should be instrumented just like other functions. + + Last, some compiler built-ins might have just a declaration but + no conversion or library implementation. + They might then persist in the program as functions which return a nondet + value or transformed into side_effect_nondet_exprt during conversion + If they survive as functions we should be able to add an extra parameter + to these functions even if they have no body. + + The CPROVER built-ins are declared here: + - src/ansi-c/cprover_builtin_headers.h + - src/ansi-c/cprover_library.h + - src/ansi-c/library/cprover_contracts.c + and should not be instrumented. + + The case of __CPROVER_deallocate is special: it is a wrapper for an assignment + to the __CPROVER_deallocated_object global. We do not want to + instrument this function, but we still want to check that its parameters + are allowed for deallocation by the write set. + + There is also a number of CPROVER global static symbols that are used to + suport memory safety property instrumentation, and assignments to these + statics should always be allowed (i.e not instrumented): + - __CPROVER_alloca_object, + - __CPROVER_dead_object, + - __CPROVER_deallocated, + - __CPROVER_malloc_is_new_array, + - __CPROVER_max_malloc_size, + - __CPROVER_memory_leak, + - __CPROVER_new_object, + - __CPROVER_next_thread_id, + - __CPROVER_next_thread_key, + - __CPROVER_pipe_count, + - __CPROVER_rounding_mode, + - __CPROVER_thread_id, + - __CPROVER_thread_key_dtors, + - __CPROVER_thread_keys, + - __CPROVER_threads_exited, + - ... (and more of them) + + /// These have a library implementation and must be instrumented + static std::set alloca_builtins = {"alloca", "__builtin_alloca"}; + + /// These built-ins have CPROVER library implementation, can be instrumented + static std::set builtins_with_cprover_impl = { + "__builtin_ia32_sfence", + "__builtin_ia32_lfence", + "__builtin_ia32_mfence", + "__builtin_ffs", + "__builtin_ffsl", + "__builtin_ffsll", + "__builtin_ia32_vec_ext_v4hi", + "__builtin_ia32_vec_ext_v8hi", + "__builtin_ia32_vec_ext_v4si", + "__builtin_ia32_vec_ext_v2di", + "__builtin_ia32_vec_ext_v16qi", + "__builtin_ia32_vec_ext_v4sf", + "__builtin_ia32_psubsw128", + "__builtin_ia32_psubusw128", + "__builtin_ia32_paddsw", + "__builtin_ia32_psubsw", + "__builtin_ia32_vec_init_v4hi", + "__builtin_flt_rounds", + "__builtin_fabs", + "__builtin_fabsl", + "__builtin_fabsf", + "__builtin_inff", + "__builtin_inf", + "__builtin_infl", + "__builtin_isinf", + "__builtin_isinff", + "__builtin_isinf", + "__builtin_isnan", + "__builtin_isnanf", + "__builtin_huge_valf", + "__builtin_huge_val", + "__builtin_huge_vall", + "__builtin_nan", + "__builtin_nanf", + "__builtin_abs", + "__builtin_labs", + "__builtin_llabs", + "__builtin_alloca", + "__builtin___strcpy_chk", + "__builtin___strcat_chk", + "__builtin___strncat_chk", + "__builtin___strncpy_chk", + "__builtin___memcpy_chk", + "__builtin_memset", + "__builtin___memset_chk", + "__builtin___memmove_chk"}; +*/ + +// these are internal symbols which implementation is generated and inlined into +// the model at conversion or symex time and must not be instrumented +static std::set internal_symbol = { + // these are come from different assert.h implementation on different systems + // and eventually become ASSERT instructions and must not be instrumented + "__assert_fail", + "_assert", + "__assert_c99", + "_wassert", + "__assert_rtn", + "__assert", + "__assert_func", + + /// These builtins are translated to no-ops and must not be instrumented + "__builtin_prefetch", + "__builtin_unreachable", + + /// These builtins are valist management functions + /// and interpreted natively in src/goto-symex/symex_builtin_functions.cpp + /// and must not be instrumented + ID_gcc_builtin_va_arg, + "__builtin_va_copy", + "__builtin_va_start", + "__va_start", + "__builtin_va_end", + + /// These builtins get translated to CPROVER equivalents + /// and must not be instrumented + "__builtin_isgreater", + "__builtin_isgreaterequal", + "__builtin_isless", + "__builtin_islessequal", + "__builtin_islessgreater", + "__builtin_isunordered", +}; + +/// True iff the symbol must not be instrumented +bool dfcc_instrumentt::is_internal_symbol(const irep_idt &id) const +{ + // TODO could be used instead ? + return internal_symbol.find(id) != internal_symbol.end(); +} + +/// True iff `id` starts with `prefix` +inline bool has_prefix(const irep_idt &id, std::string prefix) +{ + return id2string(id).rfind(prefix, 0) == 0; +} + +/// True if id has `CPROVER_PREFIX` or `__VERIFIER` or `nondet` prefix, +/// or an `&object` suffix (cf system_library_symbols.cpp) +bool dfcc_instrumentt::is_cprover_symbol(const irep_idt &id) const +{ + return has_prefix(id, CPROVER_PREFIX) || has_prefix(id, "__VERIFIER") || + has_prefix(id, "nondet") || has_suffix(id2string(id), "$object"); +} + +bool dfcc_instrumentt::do_not_instrument(const irep_idt &id) const +{ + return !has_prefix(id, CPROVER_PREFIX "file_local") && + (is_cprover_symbol(id) || is_internal_symbol(id)); +} + +void dfcc_instrumentt::instrument_harness_function(const irep_idt &function_id) +{ + log.debug() << "->dfcc_instrumentt::instrument_harness_function(" + << function_id << ")" << messaget::eom; + if( + dfcc_instrumentt::function_cache.find(function_id) != + dfcc_instrumentt::function_cache.end()) + return; + + dfcc_instrumentt::function_cache.insert(function_id); + + const auto null_expr = null_pointer_exprt( + to_pointer_type(library.dfcc_type[dfcc_typet::WRITE_SET_PTR])); + + auto &goto_function = goto_model.goto_functions.function_map.at(function_id); + auto &body = goto_function.body; + + // rewrite is_fresh_calls + dfcc_is_fresht is_fresh(library, log); + is_fresh.rewrite_calls(body, null_expr); + + // rewrite is_freeable/is_freed calls + dfcc_is_freeablet is_freeable(library, log); + is_freeable.rewrite_calls(body, null_expr); + + // rewrite calls + Forall_goto_program_instructions(it, body) + { + if(it->is_function_call()) + instrument_call_instruction(null_expr, it, body); + } + + goto_model.goto_functions.update(); + + // debug print + forall_goto_program_instructions(it, body) + { + body.output_instruction(ns, function_id, log.debug(), *it); + } + log.debug() << "<-dfcc_instrumentt::instrument_harness_function(" + << function_id << ")" << messaget::eom; +} + +void dfcc_instrumentt::instrument_function(const irep_idt &function_id) +{ + log.debug() << "->dfcc_instrumentt::instrument_function(" << function_id + << ")" << messaget::eom; + + // never instrument a function twice + if( + dfcc_instrumentt::function_cache.find(function_id) != + dfcc_instrumentt::function_cache.end()) + return; + + dfcc_instrumentt::function_cache.insert(function_id); + + auto found = goto_model.goto_functions.function_map.find(function_id); + if(found == goto_model.goto_functions.function_map.end()) + { + // abort on missing functions since if they ever get called/used + // they would not be transformed/available and unsound + log.error() << "dfcct::transform_goto_model: function '" << function_id + << "' was not found in the function map." << messaget::eom; + throw 0; + // TODO create an entry in the function table + // if the function is not in the list of functions expected + // not to have a body then create body with assert(false)assume(false) + } + else + { + auto &goto_function = found->second; + + function_cfg_infot cfg_info(goto_function); + + const auto &write_set = utils.add_parameter( + function_id, + "__write_set_to_check", + library.dfcc_type[dfcc_typet::WRITE_SET_PTR]); + + instrument_function_body(function_id, write_set.symbol_expr(), cfg_info); + log.debug() << "->dfcc_instrumentt::instrument_function(" << function_id + << ")" << messaget::eom; + } +} + +void dfcc_instrumentt::instrument_function_body( + const irep_idt &function_id, + const exprt &write_set, + cfg_infot &cfg_info) +{ + log.debug() << "<-dfcc_instrumentt::instrument_function_body(" << function_id + << ")" << messaget::eom; + + auto &goto_function = goto_model.goto_functions.function_map.at(function_id); + + if(!goto_function.body_available()) + { + log.warning() << "dfcc_instrumentt::instrument_function: '" << function_id + << "' body is unavailable. Results may be unsound if the real" + "function happens to have side effects." + << messaget::eom; + return; + } + + auto &body = goto_function.body; + + // instrument the whole body + instrument_instructions( + function_id, + write_set, + body, + body.instructions.begin(), + body.instructions.end(), + cfg_info, + // don't skip any instructions + {}); + + // cleanup + remove_skip(body); + + // recalculate numbers, etc. + goto_model.goto_functions.update(); + + // add loop ids + goto_model.goto_functions.compute_loop_numbers(); + + // debug print instrumented body + forall_goto_program_instructions(ins_it, goto_function.body) + { + goto_function.body.output_instruction( + ns, function_id, log.debug(), *ins_it); + } + log.debug() << "<-dfcc_instrumentt::instrument_function_body(" << function_id + << ")" << messaget::eom; +} + +void dfcc_instrumentt::instrument_goto_program( + const irep_idt &function_id, + goto_programt &goto_program, + const exprt &write_set) +{ + log.debug() << "->dfcc_instrumentt::instrument_goto_program(" << function_id + << ")" << messaget::eom; + + goto_program_cfg_infot cfg_info(goto_program); + + instrument_instructions( + function_id, + write_set, + goto_program, + goto_program.instructions.begin(), + goto_program.instructions.end(), + cfg_info, + // no pred, don't skip any instructions + {}); + + // cleanup + remove_skip(goto_program); + + // debug print + forall_goto_program_instructions(ins_it, goto_program) + { + goto_program.output_instruction(ns, function_id, log.debug(), *ins_it); + } + log.debug() << "<-dfcc_instrumentt::instrument_goto_program(" << function_id + << ")" << messaget::eom; +} + +void dfcc_instrumentt::instrument_instructions( + const irep_idt &function_id, + const exprt &write_set, + goto_programt &goto_program, + goto_programt::targett first_instruction, + const goto_programt::targett &last_instruction, + cfg_infot &cfg_info, + const std::function &pred) +{ + log.debug() << "->dfcc_instrumentt::instrument_instructions(" << function_id + << ")" << messaget::eom; + + // rewrite is_fresh calls + dfcc_is_fresht is_fresh(library, log); + is_fresh.rewrite_calls( + goto_program, first_instruction, last_instruction, write_set); + + // rewrite is_freeable/is_freed calls + dfcc_is_freeablet is_freeable(library, log); + is_freeable.rewrite_calls( + goto_program, first_instruction, last_instruction, write_set); + + const auto ns = namespacet(goto_model.symbol_table); + auto &target = first_instruction; + + // excluding the last + while(target != last_instruction) + { + // Skip instructions marked as disabled for assigns clause checking + // or rejected by the predicate + if((pred && !pred(target))) + { + target++; + continue; + } + + if(target->is_decl()) + { + instrument_decl(function_id, write_set, target, goto_program, cfg_info); + } + if(target->is_dead()) + { +#if 0 + // Remark: we do not really need to record DEAD instructions since the + // default CBMC checks are already able to detect writes to DEAD objects + instrument_dead(function_id, write_set, target, goto_program, cfg_info); +#endif + } + else if(target->is_assign()) + { + instrument_assign(function_id, write_set, target, goto_program, cfg_info); + } + else if(target->is_function_call()) + { + instrument_function_call( + function_id, write_set, target, goto_program, cfg_info); + } + else if(target->is_other()) + { + instrument_other(function_id, write_set, target, goto_program, cfg_info); + } + // else do nothing + target++; + } + log.debug() << "<-dfcc_instrumentt::instrument_instructions(" << function_id + << ")" << messaget::eom; +} + +bool dfcc_instrumentt::must_track_decl_or_dead( + const goto_programt::targett &target, + const cfg_infot &cfg_info) const +{ + INVARIANT( + target->is_decl() || target->is_dead(), + "dfcc_instrumentt::must_track_decl_or_dead: target must be a DECL or DEAD " + "instruction"); + + const auto &ident = target->is_decl() + ? target->decl_symbol().get_identifier() + : target->dead_symbol().get_identifier(); + bool retval = cfg_info.is_not_local_or_dirty_local(ident); + + log.debug() << "dfcc_instrumentt::must_track_decl_or_dead(" << ident + << ") is_not_local_or_dirty_local = " << retval << messaget::eom; + return retval; +} + +void dfcc_instrumentt::instrument_decl( + const irep_idt &function_id, + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program, + cfg_infot &cfg_info) +{ + if(!must_track_decl_or_dead(target, cfg_info)) + return; + + // ``` + // DECL x; + // ---- + // IF !write_set GOTO skip_target; + // CALL add_allocated(write_set, &x); + // skip_target: SKIP; + // ``` + const auto &decl_symbol = target->decl_symbol(); + // step over instruction + target++; + goto_programt payload; + auto goto_instruction = payload.add(goto_programt::make_incomplete_goto( + utils.make_null_check_expr(write_set), target->source_location())); + + payload.add(goto_programt::make_function_call( + code_function_callt{ + library.dfcc_fun_symbol[dfcc_funt::WRITE_SET_ADD_ALLOCATED].symbol_expr(), + {write_set, address_of_exprt(decl_symbol)}}, + target->source_location())); + + auto label_instruction = + payload.add(goto_programt::make_skip(target->source_location())); + goto_instruction->complete_goto(label_instruction); + + insert_before_swap_and_advance(goto_program, target, payload); + // step back + target--; +} + +void dfcc_instrumentt::instrument_dead( + const irep_idt &function_id, + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program, + cfg_infot &cfg_info) +{ + if(!must_track_decl_or_dead(target, cfg_info)) + return; + + // ``` + // IF !write_set GOTO skip_target; + // CALL record_dead(write_set, &x); + // skip_target: SKIP; + // ---- + // DEAD x; + // ``` + const auto &decl_symbol = target->dead_symbol(); + + goto_programt payload; + + auto goto_instruction = payload.add(goto_programt::make_incomplete_goto( + utils.make_null_check_expr(write_set), target->source_location())); + + payload.add(goto_programt::make_function_call( + code_function_callt{ + library.dfcc_fun_symbol[dfcc_funt::WRITE_SET_RECORD_DEAD].symbol_expr(), + {write_set, address_of_exprt(decl_symbol)}}, + target->source_location())); + + auto label_instruction = + payload.add(goto_programt::make_skip(target->source_location())); + + goto_instruction->complete_goto(label_instruction); + + insert_before_swap_and_advance(goto_program, target, payload); +} + +/// Returns true iff the lhs of a `ASSIGN lhs := ...` instruction or +/// `CALL lhs := ...` must be checked against the write set. +bool dfcc_instrumentt::must_check_lhs( + const source_locationt &lhs_source_location, + source_locationt &check_source_location, + const irep_idt &language_mode, + const exprt &lhs, + const cfg_infot &cfg_info) +{ + log.debug().source_location = lhs_source_location; + + if(can_cast_expr(lhs)) + { + const auto &symbol_expr = to_symbol_expr(lhs); + const auto &id = symbol_expr.get_identifier(); + + check_source_location.set_comment( + "Check that " + from_expr_using_mode(ns, language_mode, lhs) + + " is assignable"); + + if(cfg_info.is_local(id)) + return false; + + // this is a global symbol. Ignore if it is one of the CPROVER globals + if(is_cprover_symbol(id)) + { + check_source_location.set_comment( + "Check that " + from_expr_using_mode(ns, language_mode, lhs) + + " is assignable"); + + return false; + } + + return true; + } + else + { + // This is a more complex expression. + // Since non-dirty locals are not tracked explicitly in the write set, + // we need to skip the check if we can verify that the expression describes + // an access to a non-dirty local symbol or an function parameter, + // otherwise the check will necessarily fail. + // If the expression contains address_of operator, + // the assignment gets checked. If the base object of the expression + // is a local or a function parameter, it will also be flagged as dirty and + // will be tracked explicitly, and the check will pass. + // If the expression contains a dereference operation, the assignment gets + // checked. If the dereferenced address was computed from a local object, + // from a function parameter or returned by a local malloc, + // then the object will also be tracked explicitly for being dirty, + // and the check will pass. + // In all other cases (address of a non-local object, or dereference of + // a non-locally computed address) the location must be given explicitly + // in the assigns clause to be allowed for assignment and we must check + // the assignment. + if(cfg_info.is_local_composite_access(lhs)) + { + check_source_location.set_comment( + "Check that " + from_expr_using_mode(ns, language_mode, lhs) + + " is assignable"); + + log.debug() + << "dfcc_instrumentt: Assignment to local symbol composite member " + << format(lhs) << messaget::eom; + return false; + } + + log.debug() << "dfcc_instrumentt: checking assignment to expression " + << format(lhs) << messaget::eom; + return true; + } +} + +void dfcc_instrumentt::instrument_lhs( + const irep_idt &function_id, + const exprt &write_set, + goto_programt::targett &target, + const exprt &lhs, + goto_programt &goto_program, + cfg_infot &cfg_info) +{ + const auto &mode = utils.get_function_symbol(function_id).mode; + + goto_programt payload; + + const auto &lhs_source_location = target->source_location(); + auto goto_instruction = payload.add(goto_programt::make_incomplete_goto( + utils.make_null_check_expr(write_set), lhs_source_location)); + + source_locationt check_source_location(target->source_location()); + check_source_location.set_property_class("assigns"); + + if(must_check_lhs( + target->source_location(), check_source_location, mode, lhs, cfg_info)) + { + // ``` + // IF !write_set GOTO skip_target; + // DECL check_assign: bool; + // CALL check_assign = check_assignment(write_set, &lhs, sizeof(lhs)); + // ASSERT(check_assign); + // DEAD check_assign; + // skip_target: SKIP; + // ---- + // ASSIGN lhs := rhs; + // ``` + + auto &check_sym = get_fresh_aux_symbol( // TODO use dfcc_utilst class + bool_typet(), + id2string(function_id), + "__check_lhs_assignment", + lhs_source_location, + mode, + goto_model.symbol_table); + + const auto &check_var = check_sym.symbol_expr(); + + payload.add(goto_programt::make_decl(check_var, lhs_source_location)); + + payload.add(goto_programt::make_function_call( + code_function_callt{ + check_var, + library.dfcc_fun_symbol[dfcc_funt::WRITE_SET_CHECK_ASSIGNMENT] + .symbol_expr(), + {write_set, + typecast_exprt::conditional_cast( + address_of_exprt(lhs), pointer_type(empty_typet{})), + utils.make_sizeof_expr(lhs)}}, + lhs_source_location)); + + // TODO add property class on assertion source_location + std::string comment = + "Check that " + from_expr_using_mode(ns, mode, lhs) + " is assignable"; + check_source_location.set_comment(comment); + payload.add( + goto_programt::make_assertion(check_var, check_source_location)); + payload.add(goto_programt::make_dead(check_var)); + } + else + { + // ``` + // IF !write_set GOTO skip_target; + // ASSERT(true); + // skip_target: SKIP; + // ---- + // ASSIGN lhs := rhs; + // ``` + payload.add( + goto_programt::make_assertion(true_exprt(), check_source_location)); + } + + auto label_instruction = + payload.add(goto_programt::make_skip(lhs_source_location)); + goto_instruction->complete_goto(label_instruction); + + insert_before_swap_and_advance(goto_program, target, payload); +} + +/// Checks if lhs is the `dead_object`, and if the rhs +/// is an `if_exprt(nondet, ptr, dead_object)` expression. +/// Returns `ptr` if the pattern was matched, nullptr otherwise. +const exprt * +dfcc_instrumentt::is_dead_object_update(const exprt &lhs, const exprt &rhs) +{ + if( + lhs.id() == ID_symbol && + to_symbol_expr(lhs).get_identifier() == CPROVER_PREFIX "dead_object") + { + // error out if rhs is different from `if_exprt(nondet, ptr, dead_object)` + PRECONDITION(rhs.id() == ID_if); + auto &if_expr = to_if_expr(rhs); + PRECONDITION(can_cast_expr(if_expr.cond())); + PRECONDITION(if_expr.false_case() == lhs); + return &if_expr.true_case(); + } + else + { + return nullptr; + } +} + +void dfcc_instrumentt::instrument_assign( + const irep_idt &function_id, + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program, + cfg_infot &cfg_info) +{ + const auto &lhs = target->assign_lhs(); + const auto &rhs = target->assign_rhs(); + const auto &target_location = target->source_location(); + + // check the lhs + instrument_lhs(function_id, write_set, target, lhs, goto_program, cfg_info); + + // handle dead_object updates (created by __builtin_alloca for instance) + // Remark: we do not really need to track this deallocation since the default + // CBMC checks are already able to detect writes to DEAD objects +#if 0 + const auto &dead_ptr = is_dead_object_update(lhs, rhs); + if(dead_ptr != nullptr) + { + // ``` + // ASSIGN dead_object := if_exprt(nondet, ptr, dead_object); + // ---- + // IF !write_set GOTO skip_target; + // CALL record_deallocated(write_set, ptr); + // skip_target: SKIP; + // ``` + + // step over the instruction + target++; + + goto_programt payload; + + auto goto_instruction = payload.add(goto_programt::make_incomplete_goto( + utils.make_null_check_expr(write_set), target_location)); + + payload.add(goto_programt::make_function_call( + code_function_callt{ + library.dfcc_fun_symbol[dfcc_funt::WRITE_SET_RECORD_DEALLOCATED] + .symbol_expr(), + {write_set, *dead_ptr}}, + target_location)); + + auto label_instruction = + payload.add(goto_programt::make_skip(target_location)); + goto_instruction->complete_goto(label_instruction); + + insert_before_swap_and_advance(goto_program, target, payload); + + // step back + target--; + } +#endif + + // is the rhs expression a side_effect("allocate") expression ? + if(rhs.id() == ID_side_effect && rhs.get(ID_statement) == ID_allocate) + { + // ``` + // CALL lhs := side_effect(statemet = ID_allocate, args = {size, clear}); + // ---- + // IF !write_set GOTO skip_target; + // CALL add_allocated(write_set, lhs); + // skip_target: SKIP; + // ``` + + // step over the instruction + target++; + + goto_programt payload; + + auto goto_instruction = payload.add(goto_programt::make_incomplete_goto( + utils.make_null_check_expr(write_set), target_location)); + + payload.add(goto_programt::make_function_call( + code_function_callt{ + library.dfcc_fun_symbol[dfcc_funt::WRITE_SET_ADD_ALLOCATED] + .symbol_expr(), + {write_set, lhs}}, + target_location)); + + auto label_instruction = + payload.add(goto_programt::make_skip(target_location)); + goto_instruction->complete_goto(label_instruction); + + insert_before_swap_and_advance(goto_program, target, payload); + + // step back + target--; + } +} + +void dfcc_instrumentt::instrument_fptr_call_instruction_dynamic_lookup( + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program) +{ + // Insert a dynamic lookup in __instrumented_functions_map + // and pass the write set only to functions that are known to be able + // to accept it. + // + // ``` + // IF __instrumented_functions_map[__CPROVER_POINTER_OBJECT(fptr)] != 1 + // GOTO no_inst; + // CALL [lhs] = fptr(params, write_set); + // GOTO end; + // no_inst: + // CALL [lhs] = fptr(params); + // end: + // SKIP; + // --- + // SKIP // [lhs] = fptr(params) turned into SKIP + // ``` + const auto &target_location = target->source_location(); + const auto &callf = target->call_function(); + auto object_id = pointer_object( + (callf.id() == ID_dereference) ? to_dereference_expr(callf).pointer() + : address_of_exprt(callf)); + auto index_expr = index_exprt( + library.get_instrumented_functions_map_symbol().symbol_expr(), object_id); + auto cond = notequal_exprt(index_expr, from_integer(1, c_bool_typet(8))); + goto_programt payload; + auto goto_no_inst = + payload.add(goto_programt::make_incomplete_goto(cond, target_location)); + code_function_callt call_inst( + target->call_lhs(), target->call_function(), target->call_arguments()); + call_inst.arguments().push_back(write_set); + payload.add(goto_programt::make_function_call(call_inst, target_location)); + auto goto_end_inst = payload.add( + goto_programt::make_incomplete_goto(true_exprt(), target_location)); + auto no_inst_label = payload.add(goto_programt::make_skip(target_location)); + goto_no_inst->complete_goto(no_inst_label); + code_function_callt call_no_inst( + target->call_lhs(), target->call_function(), target->call_arguments()); + payload.add(goto_programt::make_function_call(call_no_inst, target_location)); + auto end_label = payload.add(goto_programt::make_skip(target_location)); + goto_end_inst->complete_goto(end_label); + // erase the original call + target->turn_into_skip(); + insert_before_swap_and_advance(goto_program, target, payload); +} + +void dfcc_instrumentt::instrument_call_instruction( + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program) +{ + if(target->is_function_call()) + { + if(target->call_function().id() == ID_symbol) + { + // this is a function call + if(do_not_instrument( + to_symbol_expr(target->call_function()).get_identifier())) + { + // do not pass write set if we know the function is not instrumented + return; + } + // pass write set argument + target->call_arguments().push_back(write_set); + } + else + { + // this is a function pointer call. + // pass write set argument + target->call_arguments().push_back(write_set); + // TODO use instrument_fptr_call_instruction_dynamic_lookup once symex + // is able to dispatch function pointers internally + } + } +} + +void dfcc_instrumentt::instrument_deallocate_call( + const irep_idt &function_id, + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program) +{ + INVARIANT(target->is_function_call(), "target must be a function call"); + INVARIANT( + target->call_function().id() == ID_symbol && + (id2string(to_symbol_expr(target->call_function()).get_identifier()) == + CPROVER_PREFIX "deallocate"), + "target must be a call to" CPROVER_PREFIX "deallocate"); + + auto &target_location = target->source_location(); + // ``` + // IF !write_set GOTO skip_target; + // DECL check_deallocate: bool; + // CALL check_deallocate := check_deallocate(write_set, ptr); + // ASSERT(check_deallocate); + // DEAD check_deallocate; + // CALL record_deallocated(write_set, lhs); + // skip_target: SKIP; + // ---- + // CALL __CPROVER_deallocate(ptr); + // ``` + const auto &mode = utils.get_function_symbol(function_id).mode; + goto_programt payload; + + auto goto_instruction = payload.add(goto_programt::make_incomplete_goto( + utils.make_null_check_expr(write_set), target_location)); + + auto &check_sym = get_fresh_aux_symbol( + bool_typet(), + id2string(function_id), + "__check_deallocate", + target_location, + mode, + goto_model.symbol_table); + + const auto &check_var = check_sym.symbol_expr(); + + payload.add(goto_programt::make_decl(check_var, target_location)); + + const auto &ptr = target->call_arguments().at(0); + + payload.add(goto_programt::make_function_call( + code_function_callt{ + check_var, + library.dfcc_fun_symbol[dfcc_funt::WRITE_SET_CHECK_DEALLOCATE] + .symbol_expr(), + {write_set, ptr}}, + target_location)); + + // add property class on assertion source_location + source_locationt check_location(target_location); + check_location.set_property_class("frees"); + std::string comment = + "Check that " + from_expr_using_mode(ns, mode, ptr) + " is freeable"; + check_location.set_comment(comment); + + payload.add(goto_programt::make_assertion(check_var, check_location)); + payload.add(goto_programt::make_dead(check_var, target_location)); + + payload.add(goto_programt::make_function_call( + code_function_callt{ + library.dfcc_fun_symbol[dfcc_funt::WRITE_SET_RECORD_DEALLOCATED] + .symbol_expr(), + {write_set, ptr}}, + target_location)); + + auto label_instruction = + payload.add(goto_programt::make_skip(target_location)); + goto_instruction->complete_goto(label_instruction); + + insert_before_swap_and_advance(goto_program, target, payload); +} + +void dfcc_instrumentt::instrument_function_call( + const irep_idt &function_id, + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program, + cfg_infot &cfg_info) +{ + log.debug() << "instrument_function_call" << messaget::eom; + + INVARIANT( + target->is_function_call(), + "the target must be a function call instruction"); + + // Instrument the lhs if any. + if(target->call_lhs().is_not_nil()) + { + instrument_lhs( + function_id, + write_set, + target, + target->call_lhs(), + goto_program, + cfg_info); + } + + const auto &call_function = target->call_function(); + if( + call_function.id() == ID_symbol && + (id2string(to_symbol_expr(call_function).get_identifier()) == CPROVER_PREFIX + "deallocate")) + { + instrument_deallocate_call(function_id, write_set, target, goto_program); + } + else + { + // instrument as a normal function/function pointer call + instrument_call_instruction(write_set, target, goto_program); + } +} + +void dfcc_instrumentt::instrument_other( + const irep_idt &function_id, + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program, + cfg_infot &cfg_info) +{ + const auto &target_location = target->source_location(); + auto &statement = target->get_other().get_statement(); + if(statement == ID_array_set) + { + // ``` + // IF !write_set GOTO skip_target; + // DECL check_array_set: bool; + // CALL check_array_set = check_array_set(write_set, dest); + // ASSERT(check_array_set); + // DEAD check_array_set; + // skip_target: SKIP; + // ---- + // OTHER {statemet = array_set, args = {dest, value}}; + // ``` + const auto &mode = utils.get_function_symbol(function_id).mode; + goto_programt payload; + + auto goto_instruction = payload.add(goto_programt::make_incomplete_goto( + utils.make_null_check_expr(write_set), target_location)); + + auto &check_sym = get_fresh_aux_symbol( + bool_typet(), + id2string(function_id), + "__check_array_set", + target_location, + mode, + goto_model.symbol_table); + + const auto &check_var = check_sym.symbol_expr(); + + payload.add(goto_programt::make_decl(check_var, target_location)); + + const auto &dest = target->get_other().operands().at(0); + + payload.add(goto_programt::make_function_call( + code_function_callt{ + check_var, + library.dfcc_fun_symbol[dfcc_funt::WRITE_SET_CHECK_ARRAY_SET] + .symbol_expr(), + {write_set, dest}}, + target_location)); + + // add property class on assertion source_location + source_locationt check_location(target_location); + check_location.set_property_class("assigns"); + std::string comment = "Check that array_set(" + + from_expr_using_mode(ns, mode, dest) + + ", ...) is allowed by the assigns clause"; + check_location.set_comment(comment); + + payload.add(goto_programt::make_assertion(check_var, check_location)); + payload.add(goto_programt::make_dead(check_var)); + + auto label_instruction = + payload.add(goto_programt::make_skip(target_location)); + goto_instruction->complete_goto(label_instruction); + + insert_before_swap_and_advance(goto_program, target, payload); + } + else if(statement == ID_array_copy) + { + // ``` + // IF !write_set GOTO skip_target; + // DECL check_array_copy: bool; + // CALL check_array_copy = check_array_copy(write_set, dest); + // ASSERT(check_array_copy); + // DEAD check_array_copy; + // skip_target: SKIP; + // ---- + // OTHER {statemet = array_copy, args = {dest, src}}; + // ``` + const auto &mode = utils.get_function_symbol(function_id).mode; + goto_programt payload; + + auto goto_instruction = payload.add(goto_programt::make_incomplete_goto( + utils.make_null_check_expr(write_set))); + + auto &check_sym = get_fresh_aux_symbol( + bool_typet(), + id2string(function_id), + "__check_array_copy", + target_location, + mode, + goto_model.symbol_table); + + const auto &check_var = check_sym.symbol_expr(); + + payload.add(goto_programt::make_decl(check_var)); + + const auto &dest = target->get_other().operands().at(0); + + payload.add(goto_programt::make_function_call( + code_function_callt{ + check_var, + library.dfcc_fun_symbol[dfcc_funt::WRITE_SET_CHECK_ARRAY_COPY] + .symbol_expr(), + {write_set, dest}}, + target_location)); + + // add property class on assertion source_location + source_locationt check_location(target_location); + check_location.set_property_class("assigns"); + std::string comment = "Check that array_copy(" + + from_expr_using_mode(ns, mode, dest) + + ", ...) is allowed by the assigns clause"; + check_location.set_comment(comment); + + payload.add(goto_programt::make_assertion(check_var, check_location)); + payload.add(goto_programt::make_dead(check_var, target_location)); + + auto label_instruction = + payload.add(goto_programt::make_skip(target_location)); + goto_instruction->complete_goto(label_instruction); + + insert_before_swap_and_advance(goto_program, target, payload); + } + else if(statement == ID_array_replace) + { + // ``` + // IF !write_set GOTO skip_target; + // DECL check_array_replace: bool; + // CALL check_array_replace = check_array_replace(write_set, dest); + // ASSERT(check_array_replace); + // DEAD check_array_replace; + // skip_target: SKIP; + // ---- + // OTHER {statemet = array_replace, args = {dest, src}}; + // ``` + const auto &mode = utils.get_function_symbol(function_id).mode; + goto_programt payload; + + auto goto_instruction = payload.add(goto_programt::make_incomplete_goto( + utils.make_null_check_expr(write_set))); + + auto &check_sym = get_fresh_aux_symbol( + bool_typet(), + id2string(function_id), + "__check_array_replace", + target_location, + mode, + goto_model.symbol_table); + + const auto &check_var = check_sym.symbol_expr(); + + payload.add(goto_programt::make_decl(check_var)); + + const auto &dest = target->get_other().operands().at(0); + const auto &src = target->get_other().operands().at(1); + + payload.add(goto_programt::make_function_call( + code_function_callt{ + check_var, + library.dfcc_fun_symbol[dfcc_funt::WRITE_SET_CHECK_ARRAY_REPLACE] + .symbol_expr(), + {write_set, dest, src}}, + target_location)); + + // add property class on assertion source_location + source_locationt check_location(target_location); + check_location.set_property_class("assigns"); + std::string comment = "Check that array_replace(" + + from_expr_using_mode(ns, mode, dest) + + ", ...) is allowed by the assigns clause"; + check_location.set_comment(comment); + + payload.add(goto_programt::make_assertion(check_var, check_location)); + payload.add(goto_programt::make_dead(check_var, target_location)); + + auto label_instruction = + payload.add(goto_programt::make_skip(target_location)); + goto_instruction->complete_goto(label_instruction); + + insert_before_swap_and_advance(goto_program, target, payload); + } + else if(statement == ID_havoc_object) + { + // insert before instruction + // ``` + // IF !write_set GOTO skip_target; + // DECL check_havoc_object: bool; + // CALL check_havoc_object = check_havoc_object(write_set, ptr); + // ASSERT(check_havoc_object); + // DEAD check_havoc_object; + // ``` + const auto &mode = utils.get_function_symbol(function_id).mode; + goto_programt payload; + + auto goto_instruction = payload.add(goto_programt::make_incomplete_goto( + utils.make_null_check_expr(write_set), target_location)); + + auto &check_sym = get_fresh_aux_symbol( + bool_typet(), + id2string(function_id), + "__check_havoc_object", + target_location, + mode, + goto_model.symbol_table); + + const auto &check_var = check_sym.symbol_expr(); + + payload.add(goto_programt::make_decl(check_var)); + + const auto &ptr = target->get_other().operands().at(0); + + payload.add(goto_programt::make_function_call( + code_function_callt{ + check_var, + library.dfcc_fun_symbol[dfcc_funt::WRITE_SET_CHECK_HAVOC_OBJECT] + .symbol_expr(), + {write_set, ptr}}, + target_location)); + + // add property class on assertion source_location + source_locationt check_location(target_location); + check_location.set_property_class("assigns"); + std::string comment = "Check that havoc_object(" + + from_expr_using_mode(ns, mode, ptr) + + ") is allowed by the assigns clause"; + check_location.set_comment(comment); + + payload.add(goto_programt::make_assertion(check_var, check_location)); + payload.add(goto_programt::make_dead(check_var, target_location)); + + auto label_instruction = + payload.add(goto_programt::make_skip(target_location)); + goto_instruction->complete_goto(label_instruction); + + insert_before_swap_and_advance(goto_program, target, payload); + } + else if(statement == ID_expression) + { + // When in Rome do like the Romans (cf src/pointer_analysis/value_set.cpp) + // can be ignored, we don't expect side effects here + } + else + { + // Other cases not presently handled + // * ID_array_equal + // * ID_decl track new symbol ? + // * ID_cpp_delete + // * ID_printf track as side effect on stdout ? + // * code_inputt track as nondet ? + // * code_outputt track as side effect on stdout ? + // * ID_nondet track as nondet ? + // * ID_asm track as side effect depending on the instruction ? + // * ID_user_specified_predicate + // should be pure ? + // * ID_user_specified_parameter_predicates + // should be pure ? + // * ID_user_specified_return_predicates + // should be pure ? + // * ID_fence + // bail out ? + log.warning().source_location = target_location; + log.warning() << "dfcc_instrument::instrument_other: statement type '" + << statement << "' is not supported, analysis may be unsound" + << messaget::eom; + } +} diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_instrument.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_instrument.h new file mode 100644 index 00000000000..81f3bd62eaa --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_instrument.h @@ -0,0 +1,227 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com + +\*******************************************************************/ + +/// \file +/// Add instrumentation to a goto program to perform frame condition checks + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_INSTRUMENT_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_INSTRUMENT_H + +#include +#include +#include +#include +#include + +#include + +#include "dfcc_contract_mode.h" + +#include +#include + +class goto_modelt; +class messaget; +class message_handlert; +class symbolt; +class conditional_target_group_exprt; +class cfg_infot; +class dfcc_libraryt; +class dfcc_utilst; + +/// This class instruments GOTO functions or instruction sequences +/// for frame condition checking. +class dfcc_instrumentt +{ +public: + dfcc_instrumentt( + goto_modelt &goto_model, + messaget &log, + dfcc_utilst &utils, + dfcc_libraryt &library); + + /// True if id has `CPROVER_PREFIX` or `__VERIFIER` or `nondet` prefix, + /// or an `&object` + bool is_cprover_symbol(const irep_idt &function_id) const; + + /// True iff the symbol an internal symbol + bool is_internal_symbol(const irep_idt &id) const; + + /// True iff the symbol must not be instrumented because it is an internal + /// symbol or a CPROVER symbol + bool do_not_instrument(const irep_idt &id) const; + + /// Instruments a function as a proof harness. + /// + /// Instrumenting a harness function just consists in passing a NULL value + /// for the write_set parameter to all function and function pointer calls + /// it contains. + /// + /// This will result in no write_set updates or checks being performed in + /// the harness or in the functions called directly from the harness + /// (and transitively in functions they call). + /// + /// One of the functions called directly (or indirectly) by the harness + /// is eventually going to be a wrapper function that checks the contract + /// against the function of interest. This wrapper will ignore the NULL + /// write set it received from the harness and instantiate its own local + /// write set from the contract and pass it to the function under analysis. + /// This will trigger cascading checks in all functions called from the + /// checked function thanks to the propagation of the write set through + /// function calls and function pointer calls. + void instrument_harness_function(const irep_idt &function_id); + + /// Instruments a GOTO function by adding an extra write set parameter and + /// inserting frame condition checks in its goto program. + void instrument_function(const irep_idt &function_id); + + /// Instruments a GOTO program against a given write set pointer variable. + void instrument_goto_program( + const irep_idt &function_id, + goto_programt &goto_program, + const exprt &write_set); + + /// Adds the set of instrumented functions to dest + void get_instrumented_functions(std::set &dest) const; + +protected: + goto_modelt &goto_model; + messaget &log; + message_handlert &message_handler; + dfcc_utilst &utils; + dfcc_libraryt &library; + namespacet ns; + + static std::set function_cache; + + /// Instruments the body of a GOTO function against a given write set. + void instrument_function_body( + const irep_idt &function_id, + const exprt &write_set, + cfg_infot &cfg_info); + + /// Instruments a sequence of instructions. + /// + /// \param function_id name of the enclosing function + /// (used as prefix for new variables) + /// \param write_set write set variable to instrument against + /// \param goto_program goto program to instrument + /// \param first_instruction first instruction to instrument in the program + /// \param last_instruction last instruction to instrument (excluded !!!) + /// \param cfg_info computes local and dirty variables to discard some checks + /// \param pred filter predicate for instructions + /// If `pred` is not provided, all instructions are checked. + /// If `pred` is provided, only instructions satisfying pred are checked. + void instrument_instructions( + const irep_idt &function_id, + const exprt &write_set, + goto_programt &goto_program, + goto_programt::targett first_instruction, + const goto_programt::targett &last_instruction, // excluding the last + cfg_infot &cfg_info, + const std::function &pred); + + /// When true the symbol `x` in `DECL x` or `DEAD x` must be added explicitly + /// to the write set. When false, assignments to `x` are implicitly allowed. + bool must_track_decl_or_dead( + const goto_programt::targett &target, + const cfg_infot &cfg_info) const; + + /// Instruments a `DECL x` instruction. + void instrument_decl( + const irep_idt &function_id, + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program, + cfg_infot &cfg_info); + + /// Instruments a `DEAD x` instruction. + void instrument_dead( + const irep_idt &function_id, + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program, + cfg_infot &cfg_info); + + /// Returns true iff the lhs of a `ASSIGN lhs := ...` instruction or + /// `CALL lhs := ...` must be checked against the write set. + /// When false, the assignment is implicitly allowed. + bool must_check_lhs( + const source_locationt &lhs_source_location, + source_locationt &check_source_location, + const irep_idt &language_mode, + const exprt &lhs, + const cfg_infot &cfg_info); + + void instrument_lhs( + const irep_idt &function_id, + const exprt &write_set, + goto_programt::targett &target, + const exprt &lhs, + goto_programt &goto_program, + cfg_infot &cfg_info); + + /// Checks if lhs is the `dead_object`, and if the rhs + /// is an `if_exprt(nondet, ptr, dead_object)` expression. + /// Returns `ptr` if the pattern was matched, nullptr otherwise. + const exprt *is_dead_object_update(const exprt &lhs, const exprt &rhs); + + /// Instrument the `lhs` of an `ASSIGN lhs := rhs` instruction by + /// adding an inclusion check of `lhs` in `write_set`. + /// If the rhs is an side_effect_expr(ID_allocate) + void instrument_assign( + const irep_idt &function_id, + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program, + cfg_infot &cfg_info); + + /// Adjusts the arguments of function or function pointer call instruction + void instrument_call_instruction( + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program); + + /// Same as \ref instrument_call_instruction but inserts a dynamic check + /// do pass the extra write set parameter only to function pointers that point + /// to instrumented functions that can effectively accept it. + void instrument_fptr_call_instruction_dynamic_lookup( + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program); + + /// Inserts deallocation checks and a write set update before a call + /// to the __CPROVER_deallocate function. + void instrument_deallocate_call( + const irep_idt &function_id, + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program); + + /// Instruments a `CALL lhs := function(params)` instruction by + /// adding an inclusion check of `lhs` in `write_set`, + /// and passing `write_set` as an extra argument to the function call. + void instrument_function_call( + const irep_idt &function_id, + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program, + cfg_infot &cfg_info); + + /// Instruments a `OTHER statement;` instruction. + /// OTHER instructions can be an array_set, array_copy, array_replace or + /// a havoc_object instruction. + void instrument_other( + const irep_idt &function_id, + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program, + cfg_infot &cfg_info); +}; + +#endif From 7f9910c569908e360168511f5b652b344d45bf9e Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Wed, 24 Aug 2022 12:33:55 -0400 Subject: [PATCH 10/20] CONTRACTS: Adds the `dfcc_spec_functionst` class. This class rewrites `__CPROVER_assignable_t` and `__CPROVER_freeable_t` functions into functions that accept two extra write set parameters. Targets specified by the function are added to the first write set. The second write set is used to check that any other the side effect performed by the function is allowed. This second write set will usually be the empty write set, or a write set that allows only writing to history variables. --- doc/cprover-manual/contracts-assigns.md | 100 ++++- doc/cprover-manual/contracts-frees.md | 34 +- src/goto-instrument/Makefile | 1 + .../dynamic-frames/dfcc_spec_functions.cpp | 422 ++++++++++++++++++ .../dynamic-frames/dfcc_spec_functions.h | 156 +++++++ 5 files changed, 696 insertions(+), 17 deletions(-) create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_spec_functions.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_spec_functions.h diff --git a/doc/cprover-manual/contracts-assigns.md b/doc/cprover-manual/contracts-assigns.md index bbafa46e1c0..6039835ea73 100644 --- a/doc/cprover-manual/contracts-assigns.md +++ b/doc/cprover-manual/contracts-assigns.md @@ -6,14 +6,34 @@ ### Syntax +An _assigns_ clause allows the user to specify a set of locations that may be +assigned to by a function or the body of a loop: + ```c -__CPROVER_assigns(*identifier*, ...) +__CPROVER_assigns(*targets*) +``` + +A function or loop contract contract may have zero or more _assigns_ clauses. + +The clause accepts a semicolon-separated list of _conditional target groups_. +A _conditional target group+ consists of an optional _condition_ followed by a +coma-separated list of _targets_. +A _target_ is either an lvalue expression or a call to a function returning +the built-in type `__CPROVER_assignable_t`. + +``` +targets ::= conditional_target_group (';' conditional_target_group)* (';')? +conditional_target_group ::= (condition ':')? target (',' target)* +target ::= lvalue-expression | call-to-cprover-assignable-t-function ``` -An _assigns_ clause allows the user to specify that a memory location may be written by a function. The set of locations writable by a function is the union of the locations specified by the assigns clauses, or the empty set of no _assigns_ clause is specified. While, in general, an _assigns_ clause could be interpreted with either _writes_ or _modifies_ semantics, this -design is based on the former. This means that memory not captured by an -_assigns_ clause must not be written within the given function, even if the -value(s) therein are not modified. +The set of locations writable by a function is the union of the locations +specified by the assigns clauses, or the empty set of no _assigns_ clause is +specified. +While, in general, an _assigns_ clause could be interpreted with either +_writes_ or _modifies_ semantics, this design is based on the former. +This means that memory not captured by an _assigns_ clause must not be assigned +to by the given function, even if the value(s) therein are not modified. ### Object slice expressions @@ -57,6 +77,66 @@ havoc these byte ranges, and `__CPROVER_havoc_slice` does not support havocing pointers. `__CPROVER_typed_target` must be used to specify targets that are pointers. +### Specifying parameterized and reusable sets of assignable locations + +Users can specify sets of assignable locations by writing their own functions +returning the built-in type `__CPROVER_assignable_t`. + +Such functions may call other functions returning `__CPROVER_assignable_t` (built-in or user-defined). +These functions must ultimately be side-effect free, deterministic and loop-free, +or can contain loops that must be unwound to completion using a preliminary +`goto-instrument --unwindset` pass. + +For example, the following function declares even cells of `arr` up to index `10` +as assignable: + +```c +__CPROVER_assignable_t assign_even_upto_ten(int *arr, size_t size) +{ + assert(arr && size > 10); + __CPROVER_typed_target(arr[0]); + __CPROVER_typed_target(arr[2]); + __CPROVER_typed_target(arr[4]); + __CPROVER_typed_target(arr[6]); + __CPROVER_typed_target(arr[8]); + __CPROVER_typed_target(arr[10]); +} +``` + +This function other defines a conditional set of assignable locations, +and calls the function `assign_even_upto_ten`. + +```c +__CPROVER_assignable_t my_assignable_set(int *arr, size_t size) +{ + if (arr && 0 < size) + __CPROVER_typed_target(arr[0]); + + if (arr && 5 < size && size < 10) + __CPROVER_object_upto(arr, 5 * sizeof(int)); + + if (arr && 10 < size) + assign_even_upto_ten(arr, 5 * sizeof(int)); +} +``` + +An assigns clause target can consist of a call to a function returning +`__CPROVER_assignable_t`: + +```c +int foo(int *arr, size_t size) +__CPROVER_requires(!arr || __CPROVER_is_fresh(arr, size)) +__CPROVER_assigns(my_assignable_set(arr, size)) +__CPROVER_ensures(true) +{ ... } +``` + +When checking a function contract at some call-site or +(loop contract at some program location), these `__CPROVER_assignable_t` +functions are evaluated and any target they describe gets added to +the write set. If a function call is not reachable according to the control flow +the target does not get added to the write set. + ### Parameters An _assigns_ clause currently supports simple variable types and their pointers, @@ -186,7 +266,7 @@ int foo() uint32_t b; uint32_t out; int rval = sum(a, b, &out); - if (rval == SUCCESS) + if (rval == SUCCESS) return out; return rval; } @@ -201,21 +281,21 @@ int foo() uint32_t a; uint32_t b; uint32_t out; - + /* Function Contract Replacement */ /* Precondition */ __CPROVER_assert(__CPROVER_is_fresh(out, sizeof(*out)), "Check requires clause"); - + /* Writable Set */ *(&out) = nondet_uint32_t(); - + /* Postconditions */ int return_value_sum = nondet_int(); __CPROVER_assume(return_value_sum == SUCCESS || return_value_sum == FAILURE); __CPROVER_assume((return_value_sum == SUCCESS) ==> (*out == (a + b))); int rval = return_value_sum; - if (rval == SUCCESS) + if (rval == SUCCESS) return out; return rval; } diff --git a/doc/cprover-manual/contracts-frees.md b/doc/cprover-manual/contracts-frees.md index f9d00c2b502..d797175b3f0 100644 --- a/doc/cprover-manual/contracts-frees.md +++ b/doc/cprover-manual/contracts-frees.md @@ -4,22 +4,42 @@ ### Syntax +A _frees_ clause allows the user to specify a set of pointers that may be freed +by a function or the body of a loop: + ```c -__CPROVER_frees(*pointer-typed-expression*, ...) +__CPROVER_frees(*targets*) ``` -A _frees_ clause allows the user to specify a set of pointers that may be freed -by a function or the body of a loop. A function or loop contract contract may have zero or more _frees_ clauses. -_Frees_ clause targets must be pointer-typed expressions. - -_Frees_ clause targets can also be _conditional_ and written as follows: +The clause accepts a semicolon-separated list of _conditional target groups_. +A _conditional target group+ consists of an optional _condition_ followed by a +coma-separated list of _targets_. +A _target_ is either a pointer-typed expression or a call to a function returning +the built-in type `__CPROVER_freeable_t`. ``` -condition: list-of-pointer-typed-expressions; +targets ::= conditional_target_group (';' conditional_target_group)* (';')? +conditional_target_group ::= (condition ':')? target (',' target)* +target ::= pointer-typed-expression | call-to-cprover-freeable-t-function +``` + +The built-in type `__CPROVER_freeable_t` is the return type for functions that +describe sets of freeable pointers. + +We provide the following built-in function that declares a single pointer +as freeable. + +```c +__CPROVER_freeable_t __CRPROVER_freeable(const void *ptr); ``` +Users can write their own functions returning `__CPROVER_freeable_t`, which +can call other `__CPROVER_freeable_t` functions and must be side-effect free, +deterministic and loop-free, or contain loops that must be unwound to completion +using a preliminary `goto-instrument --unwindset` pass. + ### Examples In a function contract diff --git a/src/goto-instrument/Makefile b/src/goto-instrument/Makefile index 27f89a9cb72..e36af0005c0 100644 --- a/src/goto-instrument/Makefile +++ b/src/goto-instrument/Makefile @@ -22,6 +22,7 @@ SRC = accelerate/accelerate.cpp \ contracts/dynamic-frames/dfcc_is_fresh.cpp \ contracts/dynamic-frames/dfcc_is_freeable.cpp \ contracts/dynamic-frames/dfcc_instrument.cpp \ + contracts/dynamic-frames/dfcc_spec_functions.cpp \ contracts/havoc_assigns_clause_targets.cpp \ contracts/instrument_spec_assigns.cpp \ contracts/memory_predicates.cpp \ diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_spec_functions.cpp b/src/goto-instrument/contracts/dynamic-frames/dfcc_spec_functions.cpp new file mode 100644 index 00000000000..f1a3dde57ec --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_spec_functions.cpp @@ -0,0 +1,422 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking + +Author: Remi Delmas, delmarsd@amazon.com + +\*******************************************************************/ + +#include "dfcc_spec_functions.h" +#include + +#include +#include + +#include + +#include "dfcc_library.h" +#include "dfcc_utils.h" + +dfcc_spec_functionst::dfcc_spec_functionst( + goto_modelt &goto_model, + messaget &log, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_instrumentt &instrument) + : goto_model(goto_model), + log(log), + message_handler(log.get_message_handler()), + utils(utils), + library(library), + instrument(instrument), + ns(goto_model.symbol_table) +{ +} + +bool dfcc_spec_functionst::is_assignable_t_function(const irep_idt &function_id) +{ + auto &symbol = utils.get_function_symbol(function_id); + auto &return_type = to_code_type(symbol.type).return_type(); + return return_type.id() == ID_empty && + return_type.get(ID_C_typedef) == CPROVER_PREFIX "assignable_t"; +} + +bool dfcc_spec_functionst::is_freeable_t_function(const irep_idt &function_id) +{ + auto &symbol = utils.get_function_symbol(function_id); + auto &return_type = to_code_type(symbol.type).return_type(); + return return_type.id() == ID_empty && + return_type.get(ID_C_typedef) == CPROVER_PREFIX "freeable_t"; +} + +const typet &dfcc_spec_functionst::get_target_type(const exprt &expr) +{ + if(expr.id() != ID_typecast || expr.type().id() != ID_pointer) + { + goto ERROR; + } + + if(expr.operands().at(0).id() != ID_address_of) + { + goto ERROR; + } + + return expr.operands().at(0).operands().at(0).type(); + +ERROR: + log.error() << "dfcc_spec_functionst::get_target_type: expression " + << format(expr) + << " is not of the form `cast(address_of(target), empty*)`" + << messaget::eom; + exit(0); +} + +void dfcc_spec_functionst::generate_havoc_function( + const irep_idt &function_id, + const irep_idt &havoc_function_id, + int &nof_targets) +{ + if(goto_model.symbol_table.has_symbol(havoc_function_id)) + { + log.error() << "dfcc_spec_functionst::generate_havoc_function: '" + << havoc_function_id << "' already exists" << messaget::eom; + exit(0); + } + + const auto &function_symbol = utils.get_function_symbol(function_id); + + // create the write_set symbol used as input by the havoc function + const auto &write_set_symbol = utils.create_symbol( + library.dfcc_type.at(dfcc_typet::CAR_SET_PTR), + id2string(havoc_function_id), + "__write_set_to_havoc", + function_symbol.location, + function_symbol.mode, + function_symbol.module, + true); + + // create the code type that goes on the function symbol + code_typet::parametert write_set_param(write_set_symbol.type); + write_set_param.set_base_name(write_set_symbol.base_name); + write_set_param.set_identifier(write_set_symbol.name); + code_typet havoc_code_type({write_set_param}, empty_typet()); + + // create the havoc function symbol + symbolt havoc_function_symbol; + havoc_function_symbol.base_name = havoc_function_id; + havoc_function_symbol.name = havoc_function_id; + havoc_function_symbol.pretty_name = havoc_function_id; + havoc_function_symbol.type = havoc_code_type; + havoc_function_symbol.mode = function_symbol.mode; + havoc_function_symbol.module = function_symbol.module; + havoc_function_symbol.location = function_symbol.location; + havoc_function_symbol.set_compiled(); + + if(goto_model.symbol_table.add(havoc_function_symbol)) + { + log.error() << "dfcc_spec_functionst::generate_havoc_function: could not " + "insert symbol '" + << havoc_function_id << "' in symbol table" << messaget::eom; + exit(0); + } + + // create new goto_function + goto_functiont dummy_havoc_function; + dummy_havoc_function.set_parameter_identifiers(havoc_code_type); + goto_model.goto_functions.function_map[havoc_function_id].copy_from( + dummy_havoc_function); + + // body will be filled with instructions + auto &body = + goto_model.goto_functions.function_map.at(havoc_function_id).body; + + // index of the CAR to havoc in the write set + int next_idx = 0; + + // iterate on the body of the original function and emit one havoc instruction + // per target + Forall_goto_program_instructions( + ins_it, goto_model.goto_functions.function_map.at(function_id).body) + { + if(ins_it->is_function_call()) + { + if(ins_it->call_function().id() != ID_symbol) + { + log.error().source_location = ins_it->source_location(); + log.error() << "dfcc_spec_functionst::generate_havoc_function: " + "function pointer calls not supported" + << messaget::eom; + throw 0; + } + + const irep_idt &callee_id = + to_symbol_expr(ins_it->call_function()).get_identifier(); + + // only process built-in functions that return assignable_t, + // error out on any other function call + // find the corresponding instrumentation hook + auto hook_opt = library.get_havoc_hook(callee_id); + if(hook_opt.has_value()) + { + // TODO craft location for each instruction + source_locationt location; + auto hook = hook_opt.value(); + auto write_set_var = write_set_symbol.symbol_expr(); + code_function_callt call( + library.dfcc_fun_symbol.at(hook).symbol_expr(), + {write_set_var, from_integer(next_idx, size_type())}); + + if(hook == dfcc_funt::WRITE_SET_HAVOC_GET_ASSIGNABLE_TARGET) + { + // ``` + // CALL __havoc_target = havoc_hook(set, next_idx); + // IF !__havoc_target GOTO skip_label; + // ASSIGN *__havoc_target = nondet(target_type); + // skip_label: SKIP; + // ``` + // declare a local var to store targets havoced via nondet assignment + auto &target_type = get_target_type(ins_it->call_arguments().at(0)); + + const auto &target_symbol = utils.create_symbol( + pointer_type(target_type), + id2string(havoc_function_id), + "__havoc_target", + function_symbol.location, + function_symbol.mode, + function_symbol.module, + false); + + auto target_expr = target_symbol.symbol_expr(); + body.add(goto_programt::make_decl(target_expr)); + + call.lhs() = target_expr; + body.add(goto_programt::make_function_call(call, location)); + + auto goto_instruction = body.add(goto_programt::make_incomplete_goto( + utils.make_null_check_expr(write_set_var))); + // create nondet assignment to the target + side_effect_expr_nondett nondet(target_type, location); + body.add(goto_programt::make_assignment( + dereference_exprt{typecast_exprt::conditional_cast( + target_expr, pointer_type(target_type))}, + nondet, + location)); + auto label_instruction = body.add(goto_programt::make_skip()); + body.add(goto_programt::make_dead(target_expr)); + goto_instruction->complete_goto(label_instruction); + } + else if( + hook == dfcc_funt::WRITE_SET_HAVOC_WHOLE_OBJECT || + hook == dfcc_funt::WRITE_SET_HAVOC_SLICE) + { + // ``` + // CALL havoc_hook(set, next_idx); + // ``` + body.add(goto_programt::make_function_call(call, location)); + } + else + { + UNREACHABLE; + } + ++next_idx; + } + else + { + INVARIANT( + false, + "dfcc_spec_functionst::generate_havoc_function: function calls must " + "be inlined before calling this function"); + } + } + nof_targets = next_idx; + } + + body.add(goto_programt::make_end_function()); + + goto_model.goto_functions.update(); + + std::set no_body; + std::set missing_function; + std::set recursive_call; + std::set not_enough_arguments; + utils.inline_function( + havoc_function_id, + no_body, + recursive_call, + missing_function, + not_enough_arguments); + INVARIANT( + no_body.size() == 0, + "no body warnings when inlining " + id2string(havoc_function_id)); + INVARIANT( + missing_function.size() == 0, + "missing function warnings when inlining " + id2string(havoc_function_id)); + INVARIANT( + recursive_call.size() == 0, + "recursive calls when inlining " + id2string(havoc_function_id)); + INVARIANT( + not_enough_arguments.size() == 0, + "not enough arguments when inlining " + id2string(havoc_function_id)); + + utils.set_hide(havoc_function_id, true); + + goto_model.goto_functions.update(); +} + +void dfcc_spec_functionst::to_spec_assigns_function( + const irep_idt &function_id, + int &nof_targets) +{ + PRECONDITION(is_assignable_t_function(function_id)); + log.debug() << "->dfcc_spec_functionst::to_spec_assigns_function(" + << function_id << ")" << messaget::eom; + + // counts the number of calls to built-ins to get an over approximation + // of the size of the set + int next_idx = 0; + + auto &goto_function = goto_model.goto_functions.function_map.at(function_id); + + // add write_set parameter + auto &set_symbol = utils.add_parameter( + function_id, + "__write_set_to_fill", + library.dfcc_type[dfcc_typet::WRITE_SET_PTR]); + + // rewrite calls + Forall_goto_program_instructions(ins_it, goto_function.body) + { + if(ins_it->is_function_call()) + { + if(ins_it->call_function().id() != ID_symbol) + { + log.error().source_location = ins_it->source_location(); + log.error() + << "dfcc_spec_functionst::function pointer calls not supported" + << messaget::eom; + throw 0; + } + + const irep_idt &callee_id = + to_symbol_expr(ins_it->call_function()).get_identifier(); + + // only process built-in functions that return assignable_t, + // error out on any other function call + // find the corresponding instrumentation hook + auto hook_opt = library.get_hook(callee_id); + if(hook_opt.has_value()) + { + auto hook = hook_opt.value(); + // redirect the call to the hook + ins_it->call_function() = library.dfcc_fun_symbol[hook].symbol_expr(); + // insert insertion index argument + ins_it->call_arguments().insert( + ins_it->call_arguments().begin(), + from_integer(next_idx, size_type())); + // insert write set argument + ins_it->call_arguments().insert( + ins_it->call_arguments().begin(), set_symbol.symbol_expr()); + + // remove the is_pointer_to_pointer argument which is not used in the + // hook + if(hook == dfcc_funt::WRITE_SET_INSERT_ASSIGNABLE) + ins_it->call_arguments().pop_back(); + + ++next_idx; + } + else + { + INVARIANT( + false, + "dfcc_spec_functionst::to_spec_assigns_function: function calls must " + "be inlined before calling this function"); + } + } + } + + nof_targets = next_idx; + goto_model.goto_functions.update(); + + // instrument for side-effects checking + instrument.instrument_function(function_id); + utils.set_hide(function_id, true); + + // print result + auto ns = namespacet(goto_model.symbol_table); + forall_goto_program_instructions(ins_it, goto_function.body) + { + goto_function.body.output_instruction( + ns, function_id, log.debug(), *ins_it); + } + log.debug() << "<-dfcc_spec_functionst::to_spec_assigns_function(" + << function_id << ")" << messaget::eom; +} + +void dfcc_spec_functionst::to_spec_frees_function( + const irep_idt &function_id, + int &nof_targets) +{ + PRECONDITION(is_freeable_t_function(function_id)); + log.debug() << "dfcc_spec_functionst::to_spec_frees_function(" << function_id + << ")" << messaget::eom; + + // counts the number of calls to the `freeable` builtin + + int next_idx = 0; + auto &goto_function = goto_model.goto_functions.function_map.at(function_id); + + // add __dfcc_set parameter + auto &set_symbol = utils.add_parameter( + function_id, + "__write_set_to_fill", + library.dfcc_type[dfcc_typet::WRITE_SET_PTR]); + + Forall_goto_program_instructions(ins_it, goto_function.body) + { + if(ins_it->is_function_call()) + { + if(ins_it->call_function().id() != ID_symbol) + { + log.error().source_location = ins_it->source_location(); + log.error() << "function pointer calls not supported" << messaget::eom; + throw 0; + } + + const irep_idt &callee_id = + to_symbol_expr(ins_it->call_function()).get_identifier(); + + // only process the built-in `freeable` function + // error out on any other function call + if(callee_id == CPROVER_PREFIX "freeable") + { + ins_it->call_function() = + library.dfcc_fun_symbol[dfcc_funt::WRITE_SET_ADD_FREEABLE] + .symbol_expr(); + ins_it->call_arguments().insert( + ins_it->call_arguments().begin(), set_symbol.symbol_expr()); + ++next_idx; + } + else + { + INVARIANT( + false, + "dfcc_spec_functionst::to_spec_frees_function: function calls must " + "be inlined before calling this function"); + } + } + } + + nof_targets = next_idx; + goto_model.goto_functions.update(); + + // instrument for side-effects checking + instrument.instrument_function(function_id); + + auto ns = namespacet(goto_model.symbol_table); + forall_goto_program_instructions(ins_it, goto_function.body) + { + goto_function.body.output_instruction( + ns, function_id, log.debug(), *ins_it); + } + utils.set_hide(function_id, true); +} diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_spec_functions.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_spec_functions.h new file mode 100644 index 00000000000..e275465974c --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_spec_functions.h @@ -0,0 +1,156 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com + +\*******************************************************************/ + +/// \file +/// Translate functions that specify assignable and freeable targets +/// declaratively into active functions that build write sets dynamically +/// by rewriting calls to front-end functions into calls to library functions +/// defining their dynamic semantics. + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_SPEC_FUNCTIONS_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_SPEC_FUNCTIONS_H + +#include +#include +#include +#include + +#include + +#include "dfcc_instrument.h" +#include "dfcc_library.h" +#include "dfcc_utils.h" + +#include +#include + +class goto_modelt; +class messaget; +class message_handlert; +class symbolt; +class conditional_target_group_exprt; +class cfg_infot; + +/// This class transforms (in place) declarative assigns clause and frees clause +/// specification functions expressed in terms of the builtins: +/// - `__CPROVER_assignable`, +/// - `__CPROVER_whole_object`, +/// - `__CRPOVER_object_from`, +/// - `__CPROVER_object_upto`, +/// - `__CPROVER_freeable` +/// into active functions by transforming the builtin calls into calls to +/// dfcc library functions that actually built frame descriptions. +/// The resulting function is then itself instrumented for frame condition +/// checking to be able to prove the absence of side effects. +class dfcc_spec_functionst +{ +public: + dfcc_spec_functionst( + goto_modelt &goto_model, + messaget &log, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_instrumentt &instrument); + + /// Returns true iff the return type of the function is __CPROVER_assignable_t + bool is_assignable_t_function(const irep_idt &function_id); + + /// Returns true iff the return type of the function is __CPROVER_freeable_t + bool is_freeable_t_function(const irep_idt &function_id); + + /// From a function: + /// + /// ``` + /// __CPROVER_assignable_t function_id(params); + /// ``` + /// + /// generates a new function: + /// + /// ``` + /// void havoc_function_id(__CPROVER_assignable_set_ptr_t write_set_to_havoc); + /// ``` + /// + /// Which havocs the targets specified by `function_id`, passed + /// + /// \param function_id function to generate instructions from + /// \param havoc_function_id write set variable to havoc + /// \param nof_targets maximum number of targets to havoc + /// + void generate_havoc_function( + const irep_idt &function_id, + const irep_idt &havoc_function_id, + int &nof_targets); + + /// Transforms (in place) a function + /// + /// ``` + /// __CPROVER_assignable_t function_id(params); + /// ``` + /// + /// into a function + /// + /// ``` + /// __CPROVER_assignable_t function_id( + /// params, + /// __CPROVER_assignable_set_t write_set_to_fill, + /// __CPROVER_assignable_set_t write_set_to_check + /// ) + /// ``` + /// + /// Where: + /// - `write_set_to_fill` is the write set to populate. + /// - `write_set_to_check` is the write set to use for checking side effects. + /// + /// \param function_id function to transform in place + /// \param nof_targets receives the estimated size of the write set + /// + void to_spec_assigns_function(const irep_idt &function_id, int &nof_targets); + + /// Transforms (in place) a function + /// + /// ``` + /// __CPROVER_freeable_t function_id(params); + /// ``` + /// + /// into a function + /// + /// ``` + /// __CPROVER_freeable_t function_id( + /// params, + /// __CPROVER_assignable_set_t write_set_to_fill, + /// __CPROVER_assignable_set_t write_set_to_check + /// ) + /// ``` + /// + /// Where: + /// - `write_set_to_fill` is the write set to populate. + /// - `write_set_to_check` is the write set to use for checking side effects. + /// + /// The function must be fully inlined and loop free. + /// + /// \param function_id function to transform in place + /// \param nof_targets receives the estimated size of the write set + /// + void to_spec_frees_function(const irep_idt &function_id, int &nof_targets); + +protected: + goto_modelt &goto_model; + messaget &log; + message_handlert &message_handler; + dfcc_utilst &utils; + dfcc_libraryt &library; + dfcc_instrumentt &instrument; + namespacet ns; + + /// Extracts the type of an assigns clause target exrpession + /// The expression must be of the form: + /// `expr = cast(address_of(target), empty*)` + const typet &get_target_type(const exprt &expr); +}; + +#endif From 242fc9bfd4be728d722d85d215b4b1d8e78772ce Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Wed, 24 Aug 2022 12:59:39 -0400 Subject: [PATCH 11/20] CONTRACTS: Add the `dfcc_dsl_contract_functionst` class. The class translates the assigns and frees clauses of a contract expressed in DSL style into goto functions returning `__CPROVER_assignable_t` and `__CPROVER_freeable_t` types and describing the same set of targets as the clause. Internally it uses the `dfcc_spec_functiontst` class to translate the functions generated from the clauses into functions that dynamically build write sets at runtime. A havoc function is also generated for the assigns clause, that havocs the locations described by the clause in a type-directed way, taking snapshots of start addresses from a write set instance. --- src/goto-instrument/Makefile | 1 + .../dfcc_dsl_contract_functions.cpp | 579 ++++++++++++++++++ .../dfcc_dsl_contract_functions.h | 161 +++++ 3 files changed, 741 insertions(+) create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_functions.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_functions.h diff --git a/src/goto-instrument/Makefile b/src/goto-instrument/Makefile index e36af0005c0..6a7b4872fd7 100644 --- a/src/goto-instrument/Makefile +++ b/src/goto-instrument/Makefile @@ -23,6 +23,7 @@ SRC = accelerate/accelerate.cpp \ contracts/dynamic-frames/dfcc_is_freeable.cpp \ contracts/dynamic-frames/dfcc_instrument.cpp \ contracts/dynamic-frames/dfcc_spec_functions.cpp \ + contracts/dynamic-frames/dfcc_dsl_contract_functions.cpp \ contracts/havoc_assigns_clause_targets.cpp \ contracts/instrument_spec_assigns.cpp \ contracts/memory_predicates.cpp \ diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_functions.cpp b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_functions.cpp new file mode 100644 index 00000000000..b1b702eb850 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_functions.cpp @@ -0,0 +1,579 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com +Date: August 2022 + +\*******************************************************************/ +#include "dfcc_dsl_contract_functions.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "dfcc_library.h" +#include "dfcc_spec_functions.h" +#include "dfcc_utils.h" + +dfcc_dsl_contract_functionst::dfcc_dsl_contract_functionst( + const symbolt &pure_contract_symbol, + goto_modelt &goto_model, + messaget &log, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_spec_functionst &spec_functions) + : pure_contract_symbol(pure_contract_symbol), + code_with_contract(to_code_with_contract_type(pure_contract_symbol.type)), + spec_assigns_function_id( + id2string(pure_contract_symbol.name) + "::assigns"), + spec_assigns_havoc_function_id( + id2string(pure_contract_symbol.name) + "::assigns::havoc"), + spec_frees_function_id(id2string(pure_contract_symbol.name) + "::frees"), + language_mode(pure_contract_symbol.mode), + goto_model(goto_model), + log(log), + utils(utils), + library(library), + spec_functions(spec_functions), + ns(goto_model.symbol_table) +{ + gen_spec_assigns_function(); + + spec_functions.generate_havoc_function( + spec_assigns_function_id, + spec_assigns_havoc_function_id, + nof_assigns_targets); + + spec_functions.to_spec_assigns_function( + spec_assigns_function_id, nof_assigns_targets); + + gen_spec_frees_function(); + + spec_functions.to_spec_frees_function( + spec_frees_function_id, nof_frees_targets); +} + +const symbolt & +dfcc_dsl_contract_functionst::get_spec_assigns_function_symbol() const +{ + return ns.lookup(spec_assigns_function_id); +} + +const symbolt & +dfcc_dsl_contract_functionst::get_spec_assigns_havoc_function_symbol() const +{ + return ns.lookup(spec_assigns_havoc_function_id); +} + +const symbolt & +dfcc_dsl_contract_functionst::get_spec_frees_function_symbol() const +{ + return ns.lookup(spec_frees_function_id); +} + +const int dfcc_dsl_contract_functionst::get_nof_assigns_targets() const +{ + return nof_assigns_targets; +} + +const int dfcc_dsl_contract_functionst::get_nof_frees_targets() const +{ + return nof_frees_targets; +} + +void dfcc_dsl_contract_functionst::gen_spec_assigns_function() +{ + log.debug() << "->dfcc_dsl_contract_functionst::gen_spec_assigns_functions(" + << pure_contract_symbol.name << ")" << messaget::eom; + + // return type for the cloned function + optionalt new_return_type(library.dfcc_type[dfcc_typet::ASSIGNABLE]); + + const auto &spec_function_symbol = utils.clone_and_rename_function( + pure_contract_symbol.name, spec_assigns_function_id, new_return_type); + + const auto &spec_function_id = spec_function_symbol.name; + + auto &spec_code_type = to_code_type(spec_function_symbol.type); + + for(auto ¶m : spec_code_type.parameters()) + { + log.debug() + << "dfcc_dsl_contract_functionst::gen_spec_assigns_functions: new param " + << param.get_identifier() << messaget::eom; + } + + exprt::operandst lambda_parameters; + + if(code_with_contract.return_type().id() != ID_empty) + { + // use a dummy symbol for __CPROVER_return_value + // which does occur in the assigns clause anyway + symbolt dummy; + dummy.name = "dummy_return_value"; + dummy.type = code_with_contract.return_type(); + lambda_parameters.push_back(dummy.symbol_expr()); + } + + for(const auto ¶m_id : spec_code_type.parameter_identifiers()) + { + lambda_parameters.push_back(ns.lookup(param_id).symbol_expr()); + } + + // fetch the goto_function to add instructions to + goto_functiont &goto_function = + goto_model.goto_functions.function_map.at(spec_function_id); + goto_programt &body = goto_function.body; + + for(const auto &assigns_expr : code_with_contract.assigns()) + { + const auto &expr = + to_lambda_expr(assigns_expr).application(lambda_parameters); + if(can_cast_expr(expr)) + { + encode_assignable_target_group( + to_conditional_target_group_expr(expr), body); + } + else + { + encode_assignable_target(expr, body); + } + } + + body.add(goto_programt::make_end_function(spec_function_symbol.location)); + + goto_model.goto_functions.update(); + + // debug print the generated body + log.debug() << "->dfcc_dsl_contract_functionst::gen_spec_assigns_functions(" + << pure_contract_symbol.name + << ") before inlining :" << messaget::eom; + + forall_goto_program_instructions(iter, body) + { + body.output_instruction(ns, spec_function_id, log.debug(), *iter); + } + + inline_and_check_warnings(spec_function_id); + + utils.check_loop_freeness( + spec_function_id, + "loops in " CPROVER_PREFIX + "assignable_t functions must be unwound before model instrumentation", + 0); + + goto_model.goto_functions.update(); + + // debug print the generated body + log.debug() << "->dfcc_dsl_contract_functionst::gen_spec_assigns_functions(" + << pure_contract_symbol.name + << ") after inlining :" << messaget::eom; + + forall_goto_program_instructions(iter, body) + { + body.output_instruction(ns, spec_function_id, log.debug(), *iter); + } +} + +void dfcc_dsl_contract_functionst::encode_assignable_target_group( + const conditional_target_group_exprt &group, + goto_programt &dest) +{ + const source_locationt &source_location = group.source_location(); + + // clean up side effects from the condition expression if needed + cleanert cleaner(goto_model.symbol_table, log.get_message_handler()); + exprt condition(group.condition()); + if(has_subexpr(condition, ID_side_effect)) + cleaner.clean(condition, dest, language_mode); + + // Jump target if condition is false + auto goto_instruction = dest.add( + goto_programt::make_incomplete_goto(not_exprt{condition}, source_location)); + + for(const auto &target : group.targets()) + encode_assignable_target(target, dest); + + auto label_instruction = dest.add(goto_programt::make_skip(source_location)); + goto_instruction->complete_goto(label_instruction); +} + +void dfcc_dsl_contract_functionst::encode_assignable_target( + const exprt &target, + goto_programt &dest) +{ + log.debug() << "->dfcc_dsl_contract_functionst::encode_assignable_target(" + << format(target) << ")" << messaget::eom; + + const source_locationt &source_location = target.source_location(); + + if(can_cast_expr(target)) + { + const auto &funcall = to_side_effect_expr_function_call(target); + if(can_cast_expr(funcall.function())) + { + const auto &sym_expr = to_symbol_expr(funcall.function()); + const auto &ident = sym_expr.get_identifier(); + auto hook = library.get_hook(ident); + if(hook.has_value()) + { + // if `ident` is one of the built-ins + // + // `__CPROVER_object_from` + // `__CPROVER_object_upto` + // `__CPROVER_whole_object` + // `__CPROVER_assignable` + // + // generate a call to the built-in + // + // ``` + // CALL ident(list, args); + // ``` + code_function_callt code_function_call(sym_expr); + auto &arguments = code_function_call.arguments(); + for(auto &arg : funcall.arguments()) + arguments.emplace_back(arg); + + dest.add(goto_programt::make_function_call( + code_function_call, source_location)); + } + else + { + // Calls to user-defined __CPROVER_assignable_t functions + // + // `cond: foo(params)` + // + // Become + // + // ``` + // CALL foo(params); + // ``` + code_function_callt code_function_call( + to_symbol_expr(funcall.function())); + auto &arguments = code_function_call.arguments(); + for(auto &arg : funcall.arguments()) + arguments.emplace_back(arg); + dest.add(goto_programt::make_function_call( + code_function_call, source_location)); + } + } + } + else if(is_assignable(target)) + { + // An lvalue `target` becomes + // + // ``` + // CALL __CPROVER_assignable(&target, sizeof(target), is_ptr_to_ptr); + // ``` + const auto &size = + size_of_expr(target.type(), namespacet(goto_model.symbol_table)); + + if(!size.has_value()) + { + log.error().source_location = target.source_location(); + log.error() + << "dfcc_dsl_contract_functionst::encode_assignable_target: no " + "definite size for lvalue assigns clause target " + << format(target) << messaget::eom; + throw 0; + } + // we have to build the symbol manually because it might not + // be present in the symbol table if the user program does not already + // use it. + code_function_callt code_function_call(symbol_exprt( + CPROVER_PREFIX "assignable", library.dfcc_type[dfcc_typet::ASSIGNABLE])); + auto &arguments = code_function_call.arguments(); + + // ptr + arguments.emplace_back(typecast_exprt::conditional_cast( + address_of_exprt{target}, pointer_type(empty_typet()))); + + // size + arguments.emplace_back( + typecast_exprt::conditional_cast(size.value(), size_type())); + + // is_ptr_to_ptr + exprt is_ptr_to_ptr = false_exprt(); + if(target.type().id() == ID_pointer) + is_ptr_to_ptr = true_exprt(); + + arguments.emplace_back(is_ptr_to_ptr); + + dest.add( + goto_programt::make_function_call(code_function_call, source_location)); + } + else + { + log.error().source_location = target.source_location(); + if(target.id() == ID_pointer_object) + { + log.error() + << "dfcc_dsl_contract_functionst::encode_assignable_" + "target " CPROVER_PREFIX + "POINTER_OBJECT is not supported, please use " CPROVER_PREFIX + "whole_object instead" + << messaget::eom; + } + else + { + // any other type of target is unsupported + log.error() << "dfcc_dsl_contract_functionst::encode_assignable_target: " + "unsupported assigns clause target " + << format(target) << messaget::eom; + } + throw 0; + } + log.debug() << "<-dfcc_dsl_contract_functionst::encode_assignable_target(" + << format(target) << ")" << messaget::eom; +} + +void dfcc_dsl_contract_functionst::gen_spec_frees_function() +{ + log.debug() + << "dfcc_dsl_contract_functionst::create_spec_frees_function_from_contract(" + << pure_contract_symbol.name << ")" << messaget::eom; + + // fetch pure contract symbol + const auto &code_with_contract = + to_code_with_contract_type(pure_contract_symbol.type); + + // return type for the cloned function + optionalt new_return_type(library.dfcc_type[dfcc_typet::FREEABLE]); + + auto &spec_function_symbol = utils.clone_and_rename_function( + pure_contract_symbol.name, spec_frees_function_id, new_return_type); + + const auto &spec_function_id = spec_function_symbol.name; + + auto &spec_code_type = to_code_type(spec_function_symbol.type); + + for(auto ¶m : spec_code_type.parameters()) + { + log.debug() + << "dfcc_dsl_contract_functionst::gen_spec_frees_function: param " + << param.get_identifier() << messaget::eom; + } + + exprt::operandst lambda_parameters; + + if(code_with_contract.return_type().id() != ID_empty) + { + // use a dummy symbol for __CPROVER_return_value + // which does occur in the assigns clause anyway + symbolt dummy; + dummy.name = "dummy_return_value"; + dummy.type = code_with_contract.return_type(); + lambda_parameters.push_back(dummy.symbol_expr()); + } + + for(const auto ¶m_id : spec_code_type.parameter_identifiers()) + { + lambda_parameters.push_back(ns.lookup(param_id).symbol_expr()); + } + + // fetch the goto_function to add instructions to + goto_functiont &goto_function = + goto_model.goto_functions.function_map.at(spec_function_id); + goto_programt &body = goto_function.body; + + for(const auto &frees_expr : code_with_contract.frees()) + { + const auto &expr = + to_lambda_expr(frees_expr).application(lambda_parameters); + if(can_cast_expr(expr)) + { + encode_freeable_target_group( + to_conditional_target_group_expr(expr), body); + } + else + { + encode_freeable_target(expr, body); + } + } + + body.add(goto_programt::make_end_function(spec_function_symbol.location)); + + goto_model.goto_functions.update(); + + inline_and_check_warnings(spec_function_id); + + utils.check_loop_freeness( + spec_function_id, + "loops in " CPROVER_PREFIX + "freeable_t functions must be unwound before model instrumentation", + 0); + + goto_model.goto_functions.update(); + + // debug print instructions + forall_goto_program_instructions(iter, body) + { + body.output_instruction(ns, spec_function_id, log.debug(), *iter); + } +} + +void dfcc_dsl_contract_functionst::inline_and_check_warnings( + const irep_idt &function_id) +{ + std::set no_body; + std::set missing_function; + std::set recursive_call; + std::set not_enough_arguments; + + utils.inline_function( + function_id, + no_body, + recursive_call, + missing_function, + not_enough_arguments); + + // check that the only no body / missing functions are the cprover builtins + for(auto it : no_body) + { + INVARIANT( + library.get_hook(it).has_value(), + "no body for '" + id2string(it) + "' when inlining '" + + id2string(function_id) + "'"); + } + + for(auto it : missing_function) + { + INVARIANT( + library.get_hook(it).has_value(), + "missing function '" + id2string(it) + "' when inlining '" + + id2string(function_id) + "'"); + } + + INVARIANT( + recursive_call.size() == 0, + "recursive calls when inlining '" + id2string(function_id) + "'"); + + INVARIANT( + not_enough_arguments.size() == 0, + "not enough arguments when inlining '" + id2string(function_id) + "'"); +} + +void dfcc_dsl_contract_functionst::encode_freeable_target_group( + const conditional_target_group_exprt &group, + goto_programt &dest) +{ + const source_locationt &source_location = group.source_location(); + + // clean up side effects from the condition expression if needed + cleanert cleaner(goto_model.symbol_table, log.get_message_handler()); + exprt condition(group.condition()); + if(has_subexpr(condition, ID_side_effect)) + cleaner.clean(condition, dest, language_mode); + + // Jump target if condition is false + auto goto_instruction = dest.add( + goto_programt::make_incomplete_goto(not_exprt{condition}, source_location)); + + for(const auto &target : group.targets()) + encode_freeable_target(target, dest); + + auto label_instruction = dest.add(goto_programt::make_skip(source_location)); + goto_instruction->complete_goto(label_instruction); +} + +void dfcc_dsl_contract_functionst::encode_freeable_target( + const exprt &target, + goto_programt &dest) +{ + const source_locationt &source_location = target.source_location(); + + if(can_cast_expr(target)) + { + const auto &funcall = to_side_effect_expr_function_call(target); + if(can_cast_expr(funcall.function())) + { + const auto &ident = to_symbol_expr(funcall.function()).get_identifier(); + if(ident == CPROVER_PREFIX "freeable") + { + // for calls to the builtin + // `__CPROVER_freeable(args)` + // generate + // ``` + // CALL __CPROVER_freeable(args); + // ``` + + // we have to build the symbol manually because it might not + // be present in the symbol table if the user program does not already + // use it. + code_function_callt code_function_call( + symbol_exprt(ident, library.dfcc_type[dfcc_typet::FREEABLE])); + auto &arguments = code_function_call.arguments(); + + for(auto &arg : funcall.arguments()) + arguments.emplace_back(arg); + + dest.add(goto_programt::make_function_call( + code_function_call, source_location)); + } + else + { + // for calls to user-defined __CPROVER_freeable_t functions + /// `foo(params)` + // + // ``` + // CALL = foo(params); + // ``` + code_function_callt code_function_call( + to_symbol_expr(funcall.function())); + auto &arguments = code_function_call.arguments(); + for(auto &arg : funcall.arguments()) + arguments.emplace_back(arg); + dest.add(goto_programt::make_function_call( + code_function_call, source_location)); + } + } + } + else if(can_cast_type(target.type())) + { + // A plain `target` becomes + // + // ``` + // CALL __CPROVER_freeable(target); + // ``` + code_function_callt code_function_call( + utils.get_function_symbol(CPROVER_PREFIX "freeable").symbol_expr()); + auto &arguments = code_function_call.arguments(); + arguments.emplace_back(target); + + dest.add( + goto_programt::make_function_call(code_function_call, source_location)); + } + else + { + log.error().source_location = target.source_location(); + if(target.id() == ID_pointer_object) + { + log.error() + << "dfcc_dsl_contract_functionst:" CPROVER_PREFIX + "POINTER_OBJECT is not supported, please use " CPROVER_PREFIX + "freeable instead" + << messaget::eom; + } + else + { + // any other type of target is unsupported + log.error() + << "dfcc_dsl_contract_functionst: unsupported frees clause target " + << format(target) << "\n" + << target.type().pretty() << messaget::eom; + } + throw 0; + } +} diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_functions.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_functions.h new file mode 100644 index 00000000000..41eaec63f35 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_functions.h @@ -0,0 +1,161 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com +Date: August 2022 + +\*******************************************************************/ + +/// \file +/// Translates assigns and frees clauses of a contract expressed in DSL style +/// into goto functions that allow to build and havoc write sets dynamically. + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_DSL_CONTRACT_FUNCTIONS_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_DSL_CONTRACT_FUNCTIONS_H + +#include + +#include +#include +#include + +#include "dfcc_contract_mode.h" + +#include + +class goto_modelt; +class messaget; +class message_handlert; +class dfcc_libraryt; +class dfcc_utilst; +class dfcc_spec_functionst; +class code_with_contract_typet; +class conditional_target_group_exprt; +class function_pointer_obeys_contract_exprt; + +/// Generates GOTO functions modelling a contract assigns and frees clauses. +/// +/// The generated functions are the following. +/// +/// Populates write_set_to_fill with targets of the assigns clause +/// checks its own body against write_set_to_check: +/// ``` +/// __CPROVER_assignable_t contract_id::assigns( +/// function-params, +/// write_set_to_fill, +/// write_set_to_check); +/// +/// Havocs the targets specified in the assigns clause, assuming +/// write_set_to_havoc is a snapshot created using contract_id::assigns: +/// ``` +/// void contract_id::assigns::havoc(write_set_to_havoc); +/// ``` +/// Populates write_set_to_fill with targets of the frees clause +/// checks its own body against write_set_to_check: +/// ``` +/// __CPROVER_assignable_t contract_id::frees( +/// function-params, +/// write_set_to_fill, +/// write_set_to_check); +/// ``` +class dfcc_dsl_contract_functionst +{ +public: + /// \param pure_contract_symbol the contract to generate code from + /// \param goto_model goto model being transformed + /// \param log logger for debug/warning/error messages + /// \param utils utility class for dynamic frames + /// \param library the contracts instrumentation library + /// \param spec_functions used to translate a declarative + /// __CPROVER_assignable_t or __CPROVER_freeable_t GOTO function + /// into an active function that builds an actual write set. + dfcc_dsl_contract_functionst( + const symbolt &pure_contract_symbol, + goto_modelt &goto_model, + messaget &log, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_spec_functionst &spec_functions); + + /// Returns the contract::assigns function symbol + const symbolt &get_spec_assigns_function_symbol() const; + + /// Returns the contract::assigns::havoc function symbol + const symbolt &get_spec_assigns_havoc_function_symbol() const; + + /// Returns the contract::frees function symbol + const symbolt &get_spec_frees_function_symbol() const; + + /// Returns the maximum number of targets in the assigns clause + const int get_nof_assigns_targets() const; + + /// Returns the maximum number of targets in the frees clause + const int get_nof_frees_targets() const; + + /// The function symbol carrying the contract + const symbolt &pure_contract_symbol; + + /// The code_with_contract_type carrying the contract clauses + const code_with_contract_typet &code_with_contract; + + /// Identifier of the contract::assigns function + const irep_idt spec_assigns_function_id; + + /// Identifier of the contract::assigns::havoc function + const irep_idt spec_assigns_havoc_function_id; + + /// Identifier of the contract::frees function + const irep_idt spec_frees_function_id; + + /// Language mode of the contract symbol + const irep_idt &language_mode; + +protected: + goto_modelt &goto_model; + messaget &log; + dfcc_utilst &utils; + dfcc_libraryt &library; + dfcc_spec_functionst &spec_functions; + namespacet ns; + int nof_assigns_targets; + int nof_frees_targets; + + /// Translates the contract's assigns clause to a GOTO function + /// that uses the `__CPROVER_assignable_t` built-in functions to express + /// targets declaratively. + void gen_spec_assigns_function(); + + /// Translates the contract's frees clause to a GOTO function + /// that uses the `__CPROVER_freeable_t` built-in functions to express + /// targets declaratively. + void gen_spec_frees_function(); + + /// Generates GOTO instructions to build the representation of the given + /// conditional target group. + void encode_assignable_target_group( + const conditional_target_group_exprt &group, + goto_programt &dest); + + /// Generates GOTO instructions to build the representation of the given + /// assignable target. + void encode_assignable_target(const exprt &target, goto_programt &dest); + + /// Generates GOTO instructions to build the representation of the given + /// conditional target group. + void encode_freeable_target_group( + const conditional_target_group_exprt &group, + goto_programt &dest); + + /// Generates GOTO instructions to build the representation of the given + /// freeable target. + void encode_freeable_target(const exprt &target, goto_programt &dest); + + /// Inlines the given function and checks that the only missign functions + /// or no body functions are front-end + // __CPROVER_assignable_t or __CPROVER_freeable_t functions, + /// and that no other warnings happened. + void inline_and_check_warnings(const irep_idt &function_id); +}; + +#endif From 3ec82b75a5338d0cdee807c855a73f95c9a9a89e Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Thu, 22 Sep 2022 11:34:45 -0400 Subject: [PATCH 12/20] dfcc_dsl_contract_functionst fixes --- .../dynamic-frames/dfcc_dsl_contract_functions.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_functions.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_functions.h index 41eaec63f35..e7dea7c396b 100644 --- a/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_functions.h +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_functions.h @@ -40,20 +40,20 @@ class function_pointer_obeys_contract_exprt; /// /// Populates write_set_to_fill with targets of the assigns clause /// checks its own body against write_set_to_check: -/// ``` +/// ```c /// __CPROVER_assignable_t contract_id::assigns( /// function-params, /// write_set_to_fill, /// write_set_to_check); -/// +/// ``` /// Havocs the targets specified in the assigns clause, assuming /// write_set_to_havoc is a snapshot created using contract_id::assigns: -/// ``` +/// ```c /// void contract_id::assigns::havoc(write_set_to_havoc); /// ``` /// Populates write_set_to_fill with targets of the frees clause /// checks its own body against write_set_to_check: -/// ``` +/// ```c /// __CPROVER_assignable_t contract_id::frees( /// function-params, /// write_set_to_fill, From 408e529422f5b893aa2c0c199318b276366ae22b Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Wed, 24 Aug 2022 23:39:46 -0400 Subject: [PATCH 13/20] CONTRACTS: Add the `dfcc_dsl_wrapper_programt` class. The class generates a sequence of GOTO instruction from a contract expressed in the DSL syntax. These instructions are meant to be added to the GOTO function that serves as a wrapper for the function under verification or being replaced by the contract. It first uses `dfcc_dsl_contract_functionst` to generate goto functions from the contract's assigns and frees clauses. In contract checking mode, it generates GOTO instructions that assume preconditions, snapshots history variables, creates a write set instance, call the function under verification asserts the postconditions, and returns the call's return value. In contract replacement mode, it generates GOTO instructions encoding the nondeterministic abstraction defined by the contract: assert preconditions, create and havoc assigns clause targets, create a nondet return value for the call, nondeterministically free frees clause targets, assume postconditions, return a value that satisfies the post conditions. --- src/goto-instrument/Makefile | 1 + .../dfcc_dsl_wrapper_program.cpp | 1068 +++++++++++++++++ .../dynamic-frames/dfcc_dsl_wrapper_program.h | 196 +++ 3 files changed, 1265 insertions(+) create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_wrapper_program.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_wrapper_program.h diff --git a/src/goto-instrument/Makefile b/src/goto-instrument/Makefile index 6a7b4872fd7..e9c8bf068ca 100644 --- a/src/goto-instrument/Makefile +++ b/src/goto-instrument/Makefile @@ -24,6 +24,7 @@ SRC = accelerate/accelerate.cpp \ contracts/dynamic-frames/dfcc_instrument.cpp \ contracts/dynamic-frames/dfcc_spec_functions.cpp \ contracts/dynamic-frames/dfcc_dsl_contract_functions.cpp \ + contracts/dynamic-frames/dfcc_dsl_wrapper_program.cpp \ contracts/havoc_assigns_clause_targets.cpp \ contracts/instrument_spec_assigns.cpp \ contracts/memory_predicates.cpp \ diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_wrapper_program.cpp b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_wrapper_program.cpp new file mode 100644 index 00000000000..d65531f3750 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_wrapper_program.cpp @@ -0,0 +1,1068 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com + +\*******************************************************************/ +#include "dfcc_dsl_wrapper_program.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "dfcc_dsl_contract_functions.h" +#include "dfcc_instrument.h" +#include "dfcc_is_freeable.h" +#include "dfcc_is_fresh.h" +#include "dfcc_library.h" +#include "dfcc_utils.h" + +dfcc_dsl_wrapper_programt::dfcc_dsl_wrapper_programt( + const dfcc_contract_modet contract_mode, + const symbolt &wrapper_symbol, + const symbolt &wrapped_symbol, + const dfcc_dsl_contract_functionst &contract_functions, + const symbolt &caller_write_set_symbol, + goto_modelt &goto_model, + messaget &log, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_instrumentt &instrument) + : contract_mode(contract_mode), + wrapper_symbol(wrapper_symbol), + wrapped_symbol(wrapped_symbol), + contract_functions(contract_functions), + contract_symbol(contract_functions.pure_contract_symbol), + contract_code_type(to_code_with_contract_type(contract_symbol.type)), + caller_write_set_symbol(caller_write_set_symbol), + return_value_symbol_opt(nullptr), + contract_write_set_symbol_opt(nullptr), + addr_of_contract_write_set_symbol_opt(nullptr), + requires_write_set_symbol_opt(nullptr), + addr_of_requires_write_set_symbol_opt(nullptr), + ensures_write_set_symbol_opt(nullptr), + addr_of_ensures_write_set_symbol_opt(nullptr), + function_pointer_contracts(), + goto_model(goto_model), + log(log), + utils(utils), + library(library), + instrument(instrument), + ns(goto_model.symbol_table), + converter(goto_model.symbol_table, log.get_message_handler()) +{ + // generate a return value symbol (needed to instanciate all contract lambdas) + if(contract_code_type.return_type().id() != ID_empty) + { + return_value_symbol_opt = &utils.create_symbol( + contract_code_type.return_type(), + id2string(wrapper_symbol.name), + "__contract_return_value", + wrapper_symbol.location, + wrapper_symbol.module, + wrapper_symbol.mode, + false); + } + + // generate a contract write set + contract_write_set_symbol_opt = &utils.create_symbol( + library.dfcc_type[dfcc_typet::WRITE_SET], + id2string(wrapper_symbol.name), + "__contract_write_set", + wrapper_symbol.location, + wrapper_symbol.mode, + wrapper_symbol.module, + false); + + // generate a contract write set pointer + addr_of_contract_write_set_symbol_opt = &utils.create_symbol( + library.dfcc_type[dfcc_typet::WRITE_SET_PTR], + id2string(wrapper_symbol.name), + "__address_of_contract_write_set", + wrapper_symbol.location, + wrapper_symbol.mode, + wrapper_symbol.module, + false); + + // generate local write set symbol to check for side effects in pre and post + // conditions + requires_write_set_symbol_opt = &utils.create_symbol( + library.dfcc_type[dfcc_typet::WRITE_SET], + id2string(wrapper_symbol.name), + "__requires_write_set", + wrapper_symbol.location, + wrapper_symbol.mode, + wrapper_symbol.module, + false); + + // generate local write set symbol to check for side effects in pre and post + // conditions + addr_of_requires_write_set_symbol_opt = &utils.create_symbol( + library.dfcc_type[dfcc_typet::WRITE_SET_PTR], + id2string(wrapper_symbol.name), + "__address_of_requires_write_set", + wrapper_symbol.location, + wrapper_symbol.mode, + wrapper_symbol.module, + false); + + // generate local write set symbol to check for side effects in pre and post + // conditions + ensures_write_set_symbol_opt = &utils.create_symbol( + library.dfcc_type[dfcc_typet::WRITE_SET], + id2string(wrapper_symbol.name), + "__ensures_write_set", + wrapper_symbol.location, + wrapper_symbol.mode, + wrapper_symbol.module, + false); + + // generate local write set symbol to check for side effects in pre and post + // conditions + addr_of_ensures_write_set_symbol_opt = &utils.create_symbol( + library.dfcc_type[dfcc_typet::WRITE_SET_PTR], + id2string(wrapper_symbol.name), + "__address_of_ensures_write_set", + wrapper_symbol.location, + wrapper_symbol.mode, + wrapper_symbol.module, + false); + + // build contract_lambda_parameters + if(contract_code_type.return_type().id() != ID_empty) + { + contract_lambda_parameters.push_back( + return_value_symbol_opt->symbol_expr()); + } + + for(const auto ¶m_id : + to_code_type(wrapper_symbol.type).parameter_identifiers()) + { + contract_lambda_parameters.push_back(ns.lookup(param_id).symbol_expr()); + } + + // encode all contract clauses + encode_requires_write_set(); + encode_requires_clauses(); + encode_requires_contract_clauses(); + encode_contract_write_set(); + encode_function_call(); + encode_ensures_write_set(); + encode_ensures_clauses(); + encode_ensures_contract_clauses(); +} + +void dfcc_dsl_wrapper_programt::add_to_dest( + goto_programt &dest, + std::set &dest_fp_contracts) +{ + add_to_dest(dest); + dest_fp_contracts.insert( + function_pointer_contracts.begin(), function_pointer_contracts.end()); +} + +void dfcc_dsl_wrapper_programt::add_to_dest(goto_programt &dest) +{ + // add code to dest in the right order + dest.destructive_append(preamble); + dest.destructive_append(preconditions); + dest.destructive_append(history); + dest.destructive_append(write_set_checks); + dest.destructive_append(function_call); + dest.destructive_append(link_caller_write_set); + dest.destructive_append(link_deallocated); + dest.destructive_append(postconditions); + dest.destructive_append(postamble); +} + +void dfcc_dsl_wrapper_programt::encode_requires_write_set() +{ + log.debug() << "->dfcc_dsl_wrapper_programt::encode_requires_write_set()" + << messaget::eom; + + PRECONDITION(requires_write_set_symbol_opt); + PRECONDITION(addr_of_requires_write_set_symbol_opt); + + // call set_create( + // requires_write_set, + // assigns_size = 0, + // frees_size = 0, + // replacement_mode = false, + // assume_requires_ctx = contract_mode == check, + // assert_requires_ctx = contract_mode != check, + // assume_ensures_ctx = false, + // assert_ensures_ctx = false, + // ) + const auto write_set = requires_write_set_symbol_opt->symbol_expr(); + preamble.add(goto_programt::make_decl(write_set)); + + const auto address_of_write_set = + addr_of_requires_write_set_symbol_opt->symbol_expr(); + preamble.add(goto_programt::make_decl(address_of_write_set)); + preamble.add(goto_programt::make_assignment( + address_of_write_set, address_of_exprt(write_set))); + + auto function_symbol = + library.dfcc_fun_symbol.at(dfcc_funt::WRITE_SET_CREATE).symbol_expr(); + code_function_callt call(function_symbol); + auto &arguments = call.arguments(); + + // write set + arguments.emplace_back(address_of_write_set); + + // max assigns clause size + arguments.emplace_back(from_integer(0, size_type())); + + // max frees clause size + arguments.emplace_back(from_integer(0, size_type())); + + // replacement mode + arguments.emplace_back((exprt)false_exprt()); + + if(contract_mode == dfcc_contract_modet::CHECK) + { + // assume_requires_ctx + arguments.emplace_back((exprt)true_exprt()); + + // assert_requires_ctx + arguments.emplace_back((exprt)false_exprt()); + } + else + { + // assume_requires_ctx + arguments.emplace_back((exprt)false_exprt()); + + // assert_requires_ctx mode + arguments.emplace_back((exprt)true_exprt()); + } + + // assume_ensures_ctx mode + arguments.emplace_back((exprt)false_exprt()); + + // assert_ensures_ctx mode + arguments.emplace_back((exprt)false_exprt()); + + preamble.add(goto_programt::make_function_call(call)); + + // check for absence of allocation/deallocation in requires clauses + // ``` + // DECL __check_no_alloc: bool; + // CALL __check_no_alloc = + // check_empty_alloc_dealloct(write_set, lhs, sizeof(lhs)); + // ASSERT(__check_no_alloc); + // DEAD __check_no_alloc: bool; + // ``` + auto check_var = get_fresh_aux_symbol( + bool_typet(), + id2string(wrapper_symbol.name), + "__no_alloc_dealloc_in_requires", + source_locationt{}, // TODO add property class and comment + wrapper_symbol.mode, + goto_model.symbol_table) + .symbol_expr(); + + postamble.add(goto_programt::make_decl(check_var)); + + postamble.add(goto_programt::make_function_call(code_function_callt{ + check_var, + library + .dfcc_fun_symbol + [dfcc_funt::WRITE_SET_CHECK_ALLOCATED_DEALLOCATED_IS_EMPTY] + .symbol_expr(), + {address_of_write_set}})); + + // TODO add property class on assertion source_location + source_locationt sl; + sl.set_function(wrapper_symbol.name); + sl.set_comment("Check that requires do not allocate or deallocate memory"); + postamble.add(goto_programt::make_assertion(check_var, sl)); + postamble.add(goto_programt::make_dead(check_var)); + + // generate write set release and DEAD instructions + { + code_function_callt call( + library.dfcc_fun_symbol.at(dfcc_funt::WRITE_SET_RELEASE).symbol_expr(), + {address_of_write_set}); + postamble.add(goto_programt::make_function_call(call)); + postamble.add(goto_programt::make_dead(write_set)); + } + log.debug() << "<-dfcc_dsl_wrapper_programt::encode_requires_write_set()" + << messaget::eom; +} + +void dfcc_dsl_wrapper_programt::encode_ensures_write_set() +{ + log.debug() << "->dfcc_dsl_wrapper_programt::encode_ensures_write_set()" + << messaget::eom; + + PRECONDITION(ensures_write_set_symbol_opt); + PRECONDITION(addr_of_ensures_write_set_symbol_opt); + + // call set_create( + // ensures_write_set, + // assigns_size = 0, + // frees_size = 0, + // replacement_mode = false, + // assume_requires_ctx = false, + // assert_requires_ctx = false, + // assume_ensures_ctx = contract_mode != check, + // assert_ensures_ctx = contract_mode == check, + // ) + const auto write_set = ensures_write_set_symbol_opt->symbol_expr(); + preamble.add(goto_programt::make_decl(write_set)); + + const auto address_of_write_set = + addr_of_ensures_write_set_symbol_opt->symbol_expr(); + preamble.add(goto_programt::make_decl(address_of_write_set)); + preamble.add(goto_programt::make_assignment( + address_of_write_set, address_of_exprt(write_set))); + + auto function_symbol = + library.dfcc_fun_symbol.at(dfcc_funt::WRITE_SET_CREATE).symbol_expr(); + code_function_callt call(function_symbol); + auto &arguments = call.arguments(); + + // write set + arguments.emplace_back(address_of_write_set); + + // max assigns clause size + arguments.emplace_back(from_integer(0, size_type())); + + // max frees clause size + arguments.emplace_back(from_integer(0, size_type())); + + // replacement mode + arguments.emplace_back((exprt)false_exprt()); + + // assume_requires_ctx + arguments.emplace_back((exprt)false_exprt()); + + // assume_requires_ctx + arguments.emplace_back((exprt)false_exprt()); + + if(contract_mode == dfcc_contract_modet::CHECK) + { + // assume_ensures_ctx + arguments.emplace_back((exprt)false_exprt()); + + // assert_ensures_ctx + arguments.emplace_back((exprt)true_exprt()); + } + else + { + // assume_ensures_ctx + arguments.emplace_back((exprt)true_exprt()); + + // assert_ensures_ctx + arguments.emplace_back((exprt)false_exprt()); + } + + preamble.add(goto_programt::make_function_call(call)); + + // call link_is_freshr_allocated(pre_post, caller) if we are doing replacement + if(contract_mode == dfcc_contract_modet::REPLACE) + { + auto function_symbol = + library.dfcc_fun_symbol.at(dfcc_funt::LINK_IS_FRESHR_ALLOCATED) + .symbol_expr(); + code_function_callt call(function_symbol); + auto &arguments = call.arguments(); + arguments.emplace_back(address_of_write_set); + arguments.emplace_back(caller_write_set_symbol.symbol_expr()); + link_caller_write_set.add(goto_programt::make_function_call(call)); + } + + // check for absence of allocation/deallocation in contract clauses + // ``` + // DECL __check_no_alloc: bool; + // CALL __check_no_alloc = + /// check_empty_alloc_dealloct(write_set, lhs, sizeof(lhs)); + // ASSERT(__check_no_alloc); + // DEAD __check_no_alloc: bool; + // ``` + auto check_var = get_fresh_aux_symbol( + bool_typet(), + id2string(wrapper_symbol.name), + "__no_alloc_dealloc_in_ensures_clauses", + source_locationt{}, // TODO add property class and comment + wrapper_symbol.mode, + goto_model.symbol_table) + .symbol_expr(); + + postamble.add(goto_programt::make_decl(check_var)); + + postamble.add(goto_programt::make_function_call(code_function_callt{ + check_var, + library + .dfcc_fun_symbol + [dfcc_funt::WRITE_SET_CHECK_ALLOCATED_DEALLOCATED_IS_EMPTY] + .symbol_expr(), + {address_of_write_set}})); + + // TODO add property class on assertion source_location + source_locationt sl; + sl.set_function(wrapper_symbol.name); + sl.set_comment("Check that ensures do not allocate or deallocate memory"); + postamble.add(goto_programt::make_assertion(check_var, sl)); + postamble.add(goto_programt::make_dead(check_var)); + + // generate write set release and DEAD instructions + { + code_function_callt call( + library.dfcc_fun_symbol.at(dfcc_funt::WRITE_SET_RELEASE).symbol_expr(), + {address_of_write_set}); + postamble.add(goto_programt::make_function_call(call)); + postamble.add(goto_programt::make_dead(write_set)); + } + log.debug() << "<-dfcc_dsl_wrapper_programt::encode_ensures_write_set()" + << messaget::eom; +} + +void dfcc_dsl_wrapper_programt::encode_contract_write_set() +{ + log.debug() << "->dfcc_dsl_wrapper_programt::encode_contract_write_set()" + << messaget::eom; + + PRECONDITION(contract_write_set_symbol_opt); + + const auto write_set = contract_write_set_symbol_opt->symbol_expr(); + preamble.add(goto_programt::make_decl(write_set)); + + PRECONDITION(addr_of_contract_write_set_symbol_opt); + const auto address_of_contract_write_set = + addr_of_contract_write_set_symbol_opt->symbol_expr(); + preamble.add(goto_programt::make_decl(address_of_contract_write_set)); + preamble.add(goto_programt::make_assignment( + address_of_contract_write_set, address_of_exprt(write_set))); + + // We use the empty write set used to check ensures for side effects + // to check for side effects in the assigns and frees functions as well + PRECONDITION(addr_of_requires_write_set_symbol_opt); + const auto address_of_requires_write_set = + addr_of_requires_write_set_symbol_opt->symbol_expr(); + + // call set_create + { + auto function_symbol = + library.dfcc_fun_symbol.at(dfcc_funt::WRITE_SET_CREATE).symbol_expr(); + + code_function_callt call(function_symbol); + auto &arguments = call.arguments(); + + // write set + arguments.emplace_back(address_of_contract_write_set); + + // max assigns clause size + arguments.emplace_back( + from_integer(contract_functions.get_nof_assigns_targets(), size_type())); + + // max frees clause size + arguments.emplace_back( + from_integer(contract_functions.get_nof_frees_targets(), size_type())); + + // activate replace mode + const bool replace = contract_mode == dfcc_contract_modet::REPLACE; + arguments.emplace_back( + (replace ? (exprt)true_exprt() : (exprt)false_exprt())); + + // assume_requires_ctx + arguments.emplace_back((exprt)false_exprt()); + + // assert_requires_ctx + arguments.emplace_back((exprt)false_exprt()); + + // assume_ensures_ctx + arguments.emplace_back((exprt)false_exprt()); + + // assert_ensures_ctx + arguments.emplace_back((exprt)false_exprt()); + + write_set_checks.add(goto_programt::make_function_call(call)); + } + + // build arguments for assigns and frees clauses functions + exprt::operandst wrapper_arguments; + const auto &wrapper_code_type = to_code_type(wrapper_symbol.type); + for(const auto ¶meter : wrapper_code_type.parameter_identifiers()) + { + PRECONDITION(!parameter.empty()); + const symbolt ¶meter_symbol = ns.lookup(parameter); + wrapper_arguments.push_back(parameter_symbol.symbol_expr()); + } + + // call spec_assigns to build the contract write set + { + code_function_callt call( + contract_functions.get_spec_assigns_function_symbol().symbol_expr()); + + auto &arguments = call.arguments(); + + // forward wrapper arguments + for(const auto &arg : wrapper_arguments) + arguments.push_back(arg); + + // pass write set to populate + arguments.emplace_back(address_of_contract_write_set); + + // pass the empty write set to check side effects against + arguments.emplace_back(address_of_requires_write_set); + + write_set_checks.add(goto_programt::make_function_call(call)); + } + + // call spec_frees to build the contract write set + { + code_function_callt call( + contract_functions.get_spec_frees_function_symbol().symbol_expr()); + auto &arguments = call.arguments(); + + // forward wrapper arguments + for(const auto &arg : wrapper_arguments) + arguments.push_back(arg); + + // pass write set to populate + arguments.emplace_back(address_of_contract_write_set); + + // pass the empty write set to check side effects against + arguments.emplace_back(address_of_requires_write_set); + + write_set_checks.add(goto_programt::make_function_call(call)); + } + + // generate write set release and DEAD instructions + { + code_function_callt call( + library.dfcc_fun_symbol.at(dfcc_funt::WRITE_SET_RELEASE).symbol_expr(), + {address_of_contract_write_set}); + postamble.add(goto_programt::make_function_call(call)); + postamble.add(goto_programt::make_dead(write_set)); + } + log.debug() << "<-dfcc_dsl_wrapper_programt::encode_contract_write_set()" + << messaget::eom; +} + +void dfcc_dsl_wrapper_programt::encode_requires_clauses() +{ + log.debug() << "->dfcc_dsl_wrapper_programt::encode_requires_clauses()" + << messaget::eom; + // we use this empty requires write set to check for the absence of side + // effects in the requires clauses + PRECONDITION(addr_of_requires_write_set_symbol_opt); + const auto &wrapper_id = wrapped_symbol.name; + const auto &language_mode = utils.get_function_symbol(wrapper_id).mode; + + code_contractst code_contracts(goto_model, log); + + // translate requires clauses + exprt::operandst requires_conjuncts; + for(const auto &r : contract_code_type.requires()) + { + requires_conjuncts.push_back( + to_lambda_expr(r).application(contract_lambda_parameters)); + } + + // propagate source location to the conjunction + source_locationt sl = + requires_conjuncts.empty() + ? contract_code_type.source_location() + : contract_code_type.requires().front().source_location(); + + sl.set_function(wrapper_symbol.name); + + if(contract_mode == dfcc_contract_modet::REPLACE) + { + // if in replacement mode, check that assertions hold in the current context + sl.set_property_class(ID_precondition); + sl.set_comment( + "Check requires clause of contract " + id2string(contract_symbol.name) + + " for function " + id2string(wrapper_id)); + } + + auto requires = conjunction(requires_conjuncts); + requires.add_source_location() = sl; + + if(has_subexpr(requires, ID_exists) || has_subexpr(requires, ID_forall)) + code_contracts.add_quantified_variable(requires, language_mode); + + goto_programt requires_program; + + // if in replacement mode, check that assertions hold in the current context, + // otherwise assume + const auto &statement_type = + (contract_mode == dfcc_contract_modet::REPLACE) ? ID_assert : ID_assume; + + codet requires_statement(statement_type, {std::move(requires)}); + + requires_statement.add_source_location() = requires.source_location(); + + converter.goto_convert(requires_statement, requires_program, language_mode); + + requires_program.instructions.back().source_location_nonconst() = sl; + + const auto address_of_requires_write_set = + addr_of_requires_write_set_symbol_opt->symbol_expr(); + + // rewrite is_fresh predicates + dfcc_is_fresht is_fresh(library, log); + is_fresh.rewrite_calls(requires_program, address_of_requires_write_set); + + // rewrite is_freeable predicates + dfcc_is_freeablet is_freeable(library, log); + is_freeable.rewrite_calls(requires_program, address_of_requires_write_set); + + // instrument for side effects + instrument.instrument_goto_program( + wrapper_id, requires_program, address_of_requires_write_set); + + // append resulting program to preconditions section + preconditions.destructive_append(requires_program); + log.debug() << "<-dfcc_dsl_wrapper_programt::encode_requires_clauses()" + << messaget::eom; +} + +void dfcc_dsl_wrapper_programt::encode_ensures_clauses() +{ + log.debug() << "->dfcc_dsl_wrapper_programt::encode_ensures_clauses()" + << messaget::eom; + // we need the contract write set for the freed predicates + PRECONDITION(addr_of_contract_write_set_symbol_opt); + // we need the ensures write set to check for side effects + PRECONDITION(addr_of_ensures_write_set_symbol_opt); + const auto &language_mode = wrapper_symbol.mode; + const auto &wrapper_id = wrapper_symbol.name; + + code_contractst code_contracts(goto_model, log); + + // Build the ensures clause program and snapshot program + exprt::operandst ensures_conjuncts; + for(const auto &r : contract_code_type.ensures()) + { + ensures_conjuncts.push_back( + to_lambda_expr(r).application(contract_lambda_parameters)); + } + + source_locationt sl = + ensures_conjuncts.empty() + ? contract_code_type.source_location() + : contract_code_type.ensures().front().source_location(); + + sl.set_function(wrapper_symbol.name); + + if(contract_mode == dfcc_contract_modet::CHECK) + { + sl.set_property_class(ID_postcondition); + sl.set_comment( + "Check ensures clause of contract " + id2string(contract_symbol.name) + + " for function " + id2string(wrapper_id)); + } + + auto ensures = conjunction(ensures_conjuncts); + + if(has_subexpr(ensures, ID_exists) || has_subexpr(ensures, ID_forall)) + code_contracts.add_quantified_variable(ensures, language_mode); + + const auto &statement_type = + (contract_mode == dfcc_contract_modet::CHECK) ? ID_assert : ID_assume; + + codet ensures_statement(statement_type, {std::move(ensures)}); + + // Generate pair of programs from the ensures statement where: + // - the first program is the ensures clause with history variables + // replaced by snapshot variables + // - second program takes snapshots of the history variables + std::pair program_pair = + code_contracts.create_ensures_instruction( + ensures_statement, ensures.source_location(), language_mode); + goto_programt &ensures_program = program_pair.first; + goto_programt &history_snapshot_program = program_pair.second; + + // inline all calls in the preconditions + + ensures_program.instructions.back().source_location_nonconst() = sl; + + const auto address_of_ensures_write_set = + addr_of_ensures_write_set_symbol_opt->symbol_expr(); + + // rewrite is_freshr predicates + dfcc_is_fresht is_fresh(library, log); + is_fresh.rewrite_calls(ensures_program, address_of_ensures_write_set); + + // rewrite is_freed predicates + // When checking an ensures clause we link the contract write set to the + // ensures write set to know what was deallocated by the function and pass + // it to the is_freed predicate and perform the checks + { + PRECONDITION(addr_of_contract_write_set_symbol_opt); + auto function_symbol = + library.dfcc_fun_symbol.at(dfcc_funt::LINK_DEALLOCATED).symbol_expr(); + code_function_callt call(function_symbol); + auto &arguments = call.arguments(); + arguments.emplace_back(address_of_ensures_write_set); + arguments.emplace_back( + addr_of_contract_write_set_symbol_opt->symbol_expr()); + link_deallocated.add(goto_programt::make_function_call(call)); + } + + dfcc_is_freeablet is_freeable(library, log); + is_freeable.rewrite_calls(ensures_program, address_of_ensures_write_set); + + // instrument for side effects + instrument.instrument_goto_program( + wrapper_id, ensures_program, address_of_ensures_write_set); + + // add the snapshot program in the history section + history.destructive_append(history_snapshot_program); + + // add the ensures program to the postconditions section + postconditions.destructive_append(ensures_program); + log.debug() << "<-dfcc_dsl_wrapper_programt::encode_ensures_clauses()" + << messaget::eom; +} + +void dfcc_dsl_wrapper_programt::encode_requires_contract_clauses() +{ + log.debug() + << "->dfcc_dsl_wrapper_programt::encode_requires_contract_clauses()" + << messaget::eom; + + const auto &requires_contract = contract_code_type.requires_contract(); + + if(contract_mode == dfcc_contract_modet::CHECK) + { + for(auto &expr : requires_contract) + { + auto instance = + to_lambda_expr(expr).application(contract_lambda_parameters); + INVARIANT( + can_cast_expr(instance), + "instance ok"); + + assume_function_pointer_obeys_contract( + to_function_pointer_obeys_contract_expr(instance), preconditions); + } + } + else + { + for(auto &expr : requires_contract) + { + auto instance = + to_lambda_expr(expr).application(contract_lambda_parameters); + INVARIANT( + can_cast_expr(instance), + "instance ok"); + + assert_function_pointer_obeys_contract( + to_function_pointer_obeys_contract_expr(instance), + ID_precondition, + preconditions); + } + } + log.debug() + << "<-dfcc_dsl_wrapper_programt::encode_requires_contract_clauses()" + << messaget::eom; +} + +void dfcc_dsl_wrapper_programt::encode_ensures_contract_clauses() +{ + log.debug() + << "->dfcc_dsl_wrapper_programt::encode_ensures_contract_clauses()" + << messaget::eom; + + const auto &ensures_contract = contract_code_type.ensures_contract(); + + if(contract_mode == dfcc_contract_modet::CHECK) + { + for(auto &expr : ensures_contract) + { + assert_function_pointer_obeys_contract( + to_function_pointer_obeys_contract_expr( + to_lambda_expr(expr).application(contract_lambda_parameters)), + ID_postcondition, + postconditions); + } + } + else + { + for(auto &expr : ensures_contract) + { + assume_function_pointer_obeys_contract( + to_function_pointer_obeys_contract_expr( + to_lambda_expr(expr).application(contract_lambda_parameters)), + postconditions); + } + } + log.debug() + << "<-dfcc_dsl_wrapper_programt::encode_ensures_contract_clauses()" + << messaget::eom; +} + +void dfcc_dsl_wrapper_programt::assert_function_pointer_obeys_contract( + const function_pointer_obeys_contract_exprt &expr, + const irep_idt &property_class, + goto_programt &dest) +{ + function_pointer_contracts.insert( + expr.contract_symbol_expr().get_identifier()); + source_locationt loc(expr.source_location()); + loc.set_property_class(property_class); + std::stringstream comment; + comment << "Assert function pointer '" + << from_expr_using_mode( + ns, contract_symbol.mode, expr.function_pointer()) + << "' obeys contract '" + << from_expr_using_mode( + ns, contract_symbol.mode, expr.address_of_contract()) + << "'"; + loc.set_comment(comment.str()); + code_assertt assert_expr( + equal_exprt{expr.function_pointer(), expr.address_of_contract()}); + assert_expr.add_source_location() = loc; + goto_programt instructions; + converter.goto_convert(assert_expr, instructions, contract_symbol.mode); + dest.destructive_append(instructions); +} + +void dfcc_dsl_wrapper_programt::assume_function_pointer_obeys_contract( + const function_pointer_obeys_contract_exprt &expr, + goto_programt &dest) +{ + log.debug() + << "->dfcc_dsl_wrapper_programt::assume_function_pointer_obeys_contract(" + << format(expr) << ")" << messaget::eom; + + function_pointer_contracts.insert( + expr.contract_symbol_expr().get_identifier()); + + source_locationt loc(expr.source_location()); + std::stringstream comment; + comment << "Assume function pointer '" + << from_expr_using_mode( + ns, contract_symbol.mode, expr.function_pointer()) + << "' obeys contract '" + << from_expr_using_mode( + ns, contract_symbol.mode, expr.contract_symbol_expr()) + << "'"; + loc.set_comment(comment.str()); + dest.add(goto_programt::make_assignment( + expr.function_pointer(), expr.address_of_contract(), loc)); + + log.debug() + << "<-dfcc_dsl_wrapper_programt::assume_function_pointer_obeys_contract(" + << format(expr) << ")" << messaget::eom; +} + +void dfcc_dsl_wrapper_programt::encode_function_call() +{ + if(contract_mode == dfcc_contract_modet::CHECK) + encode_checked_function_call(); + else + encode_havoced_function_call(); +} + +void dfcc_dsl_wrapper_programt::encode_checked_function_call() +{ + log.debug() << "->dfcc_dsl_wrapper_programt::encode_checked_function_call(" + << wrapped_symbol.name << ")" << messaget::eom; + + PRECONDITION(contract_write_set_symbol_opt); + + // build call to the wrapped function + code_function_callt call(wrapped_symbol.symbol_expr()); + + if(return_value_symbol_opt) + { + // DECL + preamble.add( + goto_programt::make_decl(return_value_symbol_opt->symbol_expr())); + call.lhs() = return_value_symbol_opt->symbol_expr(); + + // SET_RETURN_VALUE + postamble.add(goto_programt::make_set_return_value( + return_value_symbol_opt->symbol_expr())); + + // DEAD + postamble.add( + goto_programt::make_dead(return_value_symbol_opt->symbol_expr())); + } + + // forward wrapper arguments + const auto &wrapper_code_type = to_code_type(wrapper_symbol.type); + + for(const auto ¶meter : wrapper_code_type.parameter_identifiers()) + { + PRECONDITION(!parameter.empty()); + const symbolt ¶meter_symbol = ns.lookup(parameter); + call.arguments().push_back(parameter_symbol.symbol_expr()); + } + + // pass write set to check against + call.arguments().push_back( + addr_of_contract_write_set_symbol_opt->symbol_expr()); + + function_call.add(goto_programt::make_function_call(call)); + + log.debug() << "<-dfcc_dsl_wrapper_programt::encode_checked_function_call()" + << messaget::eom; +} + +void dfcc_dsl_wrapper_programt::encode_havoced_function_call() +{ + log.debug() << "->dfcc_dsl_wrapper_programt::encode_havoced_function_call()" + << messaget::eom; + + PRECONDITION(contract_write_set_symbol_opt); + + // generate local write set and add as parameter to the call + exprt::operandst write_set_arguments; + for(const auto ¶meter : + to_code_type(wrapper_symbol.type).parameter_identifiers()) + { + PRECONDITION(!parameter.empty()); + const symbolt ¶meter_symbol = ns.lookup(parameter); + write_set_arguments.push_back(parameter_symbol.symbol_expr()); + } + + auto address_of_contract_write_set = + addr_of_contract_write_set_symbol_opt->symbol_expr(); + + // check assigns clause inclusion + // IF __caller_write_set==NULL GOTO skip_target; + // DECL check: bool; + // CALL check = __CPROVER_contracts_write_set_check_assigns_clause_inclusion( + // __caller_write_set, &__local_write_set); + // ASSERT check; + // CALL check = __CPROVER_contracts_write_set_check_frees_clause_inclusion( + // __caller_write_set, &__local_write_set); + // ASSERT check; + // DEAD check; + // skip_target: skip; + + auto caller_write_set = caller_write_set_symbol.symbol_expr(); + + auto goto_instruction = + write_set_checks.add(goto_programt::make_incomplete_goto( + utils.make_null_check_expr(caller_write_set))); + + { // assigns clause inclusion + auto check_var = get_fresh_aux_symbol( + bool_typet(), + id2string(wrapper_symbol.name), + "__check_assigns_clause_incl", + source_locationt{}, + wrapper_symbol.mode, + goto_model.symbol_table) + .symbol_expr(); + + write_set_checks.add(goto_programt::make_decl(check_var)); + + code_function_callt check_incl_call( + check_var, + library.dfcc_fun_symbol + .at(dfcc_funt::WRITE_SET_CHECK_ASSIGNS_CLAUSE_INCLUSION) + .symbol_expr(), + {caller_write_set, address_of_contract_write_set}); + + write_set_checks.add(goto_programt::make_function_call(check_incl_call)); + + // TODO use source location of the call site + source_locationt sl; + sl.set_function(wrapper_symbol.name); + sl.set_property_class("assigns"); + sl.set_comment( + "Check that the assigns clause of " + id2string(contract_symbol.name) + + " is included in the caller's assigns clause"); + write_set_checks.add(goto_programt::make_assertion(check_var, sl)); + write_set_checks.add(goto_programt::make_dead(check_var)); + } + + { // frees clause inclusion + auto check_var = get_fresh_aux_symbol( + bool_typet(), + id2string(wrapper_symbol.name), + "__check_frees_clause_incl", + source_locationt{}, + wrapper_symbol.mode, + goto_model.symbol_table) + .symbol_expr(); + + write_set_checks.add(goto_programt::make_decl(check_var)); + + code_function_callt check_incl_call( + check_var, + library.dfcc_fun_symbol + .at(dfcc_funt::WRITE_SET_CHECK_FREES_CLAUSE_INCLUSION) + .symbol_expr(), + {caller_write_set, address_of_contract_write_set}); + + write_set_checks.add(goto_programt::make_function_call(check_incl_call)); + + // TODO use source location of the call site + source_locationt sl; + sl.set_function(wrapper_symbol.name); + sl.set_property_class("frees"); + sl.set_comment( + "Check that the frees clause of " + id2string(contract_symbol.name) + + " is included in the caller's frees clause"); + write_set_checks.add(goto_programt::make_assertion(check_var, sl)); + write_set_checks.add(goto_programt::make_dead(check_var)); + } + + auto label_instruction = write_set_checks.add(goto_programt::make_skip()); + goto_instruction->complete_goto(label_instruction); + + code_function_callt havoc_call( + contract_functions.get_spec_assigns_havoc_function_symbol().symbol_expr(), + {address_of_contract_write_set}); + + function_call.add(goto_programt::make_function_call(havoc_call)); + + // assign nondet to the return value + if(return_value_symbol_opt) + { + source_locationt location; // TODO create location + // DECL + preamble.add( + goto_programt::make_decl(return_value_symbol_opt->symbol_expr())); + + // ASSIGN NONDET + auto target = function_call.add(goto_programt::make_assignment( + return_value_symbol_opt->symbol_expr(), + side_effect_expr_nondett(return_value_symbol_opt->type, location), + location)); + + // SET RETURN VALUE + postamble.add(goto_programt::make_set_return_value( + return_value_symbol_opt->symbol_expr())); + + // DEAD + postamble.add( + goto_programt::make_dead(return_value_symbol_opt->symbol_expr())); + + target->code_nonconst().add_source_location() = location; + } + + // nondet free the freeable set, record effects in both the contract write + // set and the caller write set + code_function_callt deallocate_call( + library.dfcc_fun_symbol.at(dfcc_funt::WRITE_SET_DEALLOCATE_FREEABLE) + .symbol_expr(), + {address_of_contract_write_set, caller_write_set}); + function_call.add(goto_programt::make_function_call(deallocate_call)); + log.debug() << "<-dfcc_dsl_wrapper_programt::encode_havoced_function_call()" + << messaget::eom; +} diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_wrapper_program.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_wrapper_program.h new file mode 100644 index 00000000000..a6690894ca2 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_wrapper_program.h @@ -0,0 +1,196 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com + +\*******************************************************************/ + +/// \file +/// Generates the body of a wrapper function from a DSL-style contract +/// for contract checking or contract replacement + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DFCC_DSL_WRAPPER_PROGRAM_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DFCC_DSL_WRAPPER_PROGRAM_H + +#include + +#include +#include +#include + +#include "dfcc_contract_mode.h" +#include "dfcc_dsl_contract_functions.h" + +#include + +class goto_modelt; +class messaget; +class message_handlert; +class dfcc_instrumentt; +class dfcc_libraryt; +class dfcc_utilst; +class code_with_contract_typet; +class conditional_target_group_exprt; +class function_pointer_obeys_contract_exprt; + +/// Generates the body of a wrapper function from a DSL-style contract +class dfcc_dsl_wrapper_programt +{ +public: + /// \param contract_mode checking or replacement mode for the contract + /// \param wrapper_symbol function symbol receiving the generated instructions + /// \param wrapped_symbol function symbol being checked or replaced + /// \param contract_functions contains the DSL contract expressions and the + /// assigns/frees/havoc functions symbols derived from the contract + /// \param caller_write_set_symbol symbol representing the write set passed + /// to the wrapper function by its caller. + /// \param goto_model the goto model being transformed + /// \param log logger for debug/warning/error messages + /// \param utils utility functions for contracts transformation + /// \param library the contracts instrumentation library + /// \param instrument the instrumenter class for goto functions/goto programs + dfcc_dsl_wrapper_programt( + const dfcc_contract_modet contract_mode, + const symbolt &wrapper_symbol, + const symbolt &wrapped_symbol, + const dfcc_dsl_contract_functionst &contract_functions, + const symbolt &caller_write_set_symbol, + goto_modelt &goto_model, + messaget &log, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_instrumentt &instrument); + + /// Adds the whole generated program to `dest`, which is meant to be a part of + /// the body of the `wrapper_symbol`. + void add_to_dest(goto_programt &dest); + + /// Adds the whole program to `dest` and the discovered function pointer + /// contracts `dest_fp_contracts`. + void add_to_dest(goto_programt &dest, std::set &dest_fp_contracts); + +protected: + const dfcc_contract_modet contract_mode; + const symbolt &wrapper_symbol; + const symbolt &wrapped_symbol; + const dfcc_dsl_contract_functionst &contract_functions; + const symbolt &contract_symbol; + const code_with_contract_typet &contract_code_type; + const symbolt &caller_write_set_symbol; + + // We are using pointer to const here because optional + // and optional> + // are both rejected by the compiler + + /// Symbol for the return value of the wrapped function + const symbolt *return_value_symbol_opt; + + /// Symbol for the write set object derived from the contract + const symbolt *contract_write_set_symbol_opt; + + /// Symbol for the pointer the write set object derived from the contract + const symbolt *addr_of_contract_write_set_symbol_opt; + + /// Symbol for the write set used to check requires clauses for side effects + const symbolt *requires_write_set_symbol_opt; + + /// Symbol for the pointer to the write set used to check requires clauses + const symbolt *addr_of_requires_write_set_symbol_opt; + + /// Symbol for the write set used to check ensures clauses for side effects + const symbolt *ensures_write_set_symbol_opt; + + /// Symbol for the pointer to the write set used to check ensures clauses + const symbolt *addr_of_ensures_write_set_symbol_opt; + + /// Set of required or ensured contracts for function pointers discovered + /// when processing the contract of interest. + std::set function_pointer_contracts; + + goto_modelt &goto_model; + messaget &log; + dfcc_utilst &utils; + dfcc_libraryt &library; + dfcc_instrumentt &instrument; + namespacet ns; + goto_convertt converter; + + /// Vector of arguments to use to instanciate the lambdas wrapping the + /// contract clauses + exprt::operandst contract_lambda_parameters; + + // The body of a wrapper function is decomposed in different sections. + // Each type of contract clause may add instructions to multiple sections. + // The sections then get added to the target program by \ref add_to_dest + // in the declaration order below. + + goto_programt preamble; + goto_programt preconditions; + goto_programt history; + goto_programt write_set_checks; + goto_programt function_call; + goto_programt link_caller_write_set; + goto_programt link_deallocated; + goto_programt postconditions; + goto_programt postamble; + + /// Encodes the empty write set used to check for side effects in requires + void encode_requires_write_set(); + + /// Encodes preconditions, instruments them to check for side effects + void encode_requires_clauses(); + + /// Encodes function pointer preconditions + void encode_requires_contract_clauses(); + + /// Encodes the empty write set used to check for side effects in ensures + void encode_ensures_write_set(); + + /// Encodes postconditions, instruments them to check for side effects + void encode_ensures_clauses(); + + /// Encodes function pointer postconditions + void encode_ensures_contract_clauses(); + + /// Encodes the write set of the contract being checked/replaced + /// (gets populated by calling functions provided in contract_functions) + void encode_contract_write_set(); + + /// Translates a function_pointer_obeys_contract_exprt into an assertion + /// ``` + /// ASSERT function_pointer == contract; + /// ``` + /// + /// \param expr expression to translate + /// \param property_class property class to use for the generated assertions + /// \param dest goto_program where generated instructions are appended + void assert_function_pointer_obeys_contract( + const function_pointer_obeys_contract_exprt &expr, + const irep_idt &property_class, + goto_programt &dest); + + /// Translates a function_pointer_obeys_contract_exprt into an assignment + /// ``` + /// ASSIGN function_pointer = contract; + /// ``` + /// \param expr expression to translate + /// \param dest goto_program where generated instructions are appended + void assume_function_pointer_obeys_contract( + const function_pointer_obeys_contract_exprt &expr, + goto_programt &dest); + + /// Encodes the function call section of the wrapper program. + void encode_function_call(); + + /// Creates a checked function call to the wrapped function, passing it the + /// contract-derived write set as parameter. + void encode_checked_function_call(); + + /// Creates instructions that havoc the contract write set, + /// create a nondet return value, nondeterministically free the freeable + /// part of the write set. + void encode_havoced_function_call(); +}; + +#endif From b6c770a9f07278c576b87882bffecdb03b50c978 Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Wed, 24 Aug 2022 23:50:38 -0400 Subject: [PATCH 14/20] CONTRACTS: Add the `dfcc_dsl_contract_handlert` class. This class implements the abstract `dfcc_contract_handlert` interfance for DSL-style contracts using the class `dfcc_dsl_wrapper_programt`. --- src/goto-instrument/Makefile | 1 + .../dfcc_dsl_contract_handler.cpp | 202 ++++++++++++++++++ .../dfcc_dsl_contract_handler.h | 166 ++++++++++++++ .../dynamic-frames/dfcc_dsl_wrapper_program.h | 4 +- 4 files changed, 371 insertions(+), 2 deletions(-) create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_handler.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_handler.h diff --git a/src/goto-instrument/Makefile b/src/goto-instrument/Makefile index e9c8bf068ca..293bc68cc36 100644 --- a/src/goto-instrument/Makefile +++ b/src/goto-instrument/Makefile @@ -25,6 +25,7 @@ SRC = accelerate/accelerate.cpp \ contracts/dynamic-frames/dfcc_spec_functions.cpp \ contracts/dynamic-frames/dfcc_dsl_contract_functions.cpp \ contracts/dynamic-frames/dfcc_dsl_wrapper_program.cpp \ + contracts/dynamic-frames/dfcc_dsl_contract_handler.cpp \ contracts/havoc_assigns_clause_targets.cpp \ contracts/instrument_spec_assigns.cpp \ contracts/memory_predicates.cpp \ diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_handler.cpp b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_handler.cpp new file mode 100644 index 00000000000..31a32867944 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_handler.cpp @@ -0,0 +1,202 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com +Date: August 2022 + +\*******************************************************************/ + +#include "dfcc_dsl_contract_handler.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "dfcc_dsl_wrapper_program.h" +#include "dfcc_instrument.h" +#include "dfcc_library.h" +#include "dfcc_spec_functions.h" +#include "dfcc_utils.h" + +std::map + dfcc_dsl_contract_handlert::contract_cache; + +dfcc_dsl_contract_handlert::dfcc_dsl_contract_handlert( + goto_modelt &goto_model, + messaget &log, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_instrumentt &instrument, + dfcc_spec_functionst &spec_functions) + : goto_model(goto_model), + log(log), + message_handler(log.get_message_handler()), + utils(utils), + library(library), + instrument(instrument), + spec_functions(spec_functions), + ns(goto_model.symbol_table) +{ +} + +const dfcc_dsl_contract_functionst & +dfcc_dsl_contract_handlert::get_contract_functions(const irep_idt &contract_id) +{ + auto found = dfcc_dsl_contract_handlert::contract_cache.find(contract_id); + if(found == dfcc_dsl_contract_handlert::contract_cache.end()) + { + dfcc_dsl_contract_handlert::contract_cache.insert( + {contract_id, + dfcc_dsl_contract_functionst( + get_pure_contract_symbol(contract_id), + goto_model, + log, + utils, + library, + spec_functions)}); + } + return dfcc_dsl_contract_handlert::contract_cache.at(contract_id); +} + +const int +dfcc_dsl_contract_handlert::get_assigns_clause_size(const irep_idt &contract_id) +{ + return get_contract_functions(contract_id).get_nof_assigns_targets(); +} + +void dfcc_dsl_contract_handlert::add_contract_instructions( + const dfcc_contract_modet contract_mode, + const irep_idt &wrapper_id, + const irep_idt &wrapped_id, + const irep_idt &contract_id, + const symbolt &wrapper_write_set_symbol, + goto_programt &dest, + std::set &function_pointer_contracts) +{ + dfcc_dsl_wrapper_programt( + contract_mode, + utils.get_function_symbol(wrapper_id), + utils.get_function_symbol(wrapped_id), + get_contract_functions(contract_id), + wrapper_write_set_symbol, + goto_model, + log, + utils, + library, + instrument) + .add_to_dest(dest, function_pointer_contracts); +} + +const symbolt *dfcc_dsl_contract_handlert::get_pure_contract_symbol_ptr( + const irep_idt &contract_id) +{ + const auto &contract_symbol = utils.get_function_symbol(contract_id); + const symbolt *pure_contract_symbol = nullptr; + auto pure_contract_id = "contract::" + id2string(contract_id); + if(!ns.lookup(pure_contract_id, pure_contract_symbol)) + { + log.debug() << "contract_symbol: " << contract_symbol.name << messaget::eom; + log.debug() << contract_symbol.type.pretty() << messaget::eom; + log.debug() << "pure_contract_symbol: " << pure_contract_id + << messaget::eom; + log.debug() << pure_contract_symbol->type.pretty() << messaget::eom; + + check_signature_compat( + contract_id, + to_code_type(contract_symbol.type), + pure_contract_id, + to_code_type(pure_contract_symbol->type)); + } + else + { + // The contract symbol might not have been created if the function had + // no contract or a contract with all empty clauses (which is equivalent). + // in that case we create a fresh symbol again with empty clauses + symbolt new_symbol; + new_symbol.name = pure_contract_id; + new_symbol.base_name = pure_contract_id; + new_symbol.pretty_name = pure_contract_id; + new_symbol.is_property = true; + new_symbol.type = contract_symbol.type; + new_symbol.mode = contract_symbol.mode; + new_symbol.module = contract_symbol.module; + new_symbol.location = contract_symbol.location; + auto entry = goto_model.symbol_table.insert(std::move(new_symbol)); + if(!entry.second) + { + log.error().source_location = contract_symbol.location; + log.error() << "contract '" << contract_symbol.display_name() + << "' already set at " << entry.first.location.as_string() + << messaget::eom; + throw 0; + } + // this lookup must work, and no need to check for signature compatibility + ns.lookup(pure_contract_id, pure_contract_symbol); + } + return pure_contract_symbol; +} + +const symbolt &dfcc_dsl_contract_handlert::get_pure_contract_symbol( + const irep_idt &contract_id) +{ + auto result = get_pure_contract_symbol_ptr(contract_id); + if(result != nullptr) + return *result; + + log.error() << "dfcc_dsl_contract_handlert: pure contract symbol for " + << contract_id << " was not found, aborting" << messaget::eom; + throw 0; +} + +const bool dfcc_dsl_contract_handlert::pure_contract_symbol_exists( + const irep_idt &contract_id) +{ + auto result = get_pure_contract_symbol_ptr(contract_id); + return result != nullptr; +} + +void dfcc_dsl_contract_handlert::check_signature_compat( + const irep_idt &contract_id, + const code_typet &contract_type, + const irep_idt &pure_contract_id, + const code_typet &pure_contract_type) +{ + // we consider that the return value is used if the contract has a return type + bool compatible = function_is_type_compatible( + pure_contract_type.return_type().is_not_nil() || + contract_type.return_type().is_not_nil(), + pure_contract_type, + contract_type, + ns); + + if(!compatible) + { + log.error() << "dfcc_dsl_contract_handlert: function '" << contract_id + << "' and the corresponding pure contract symbol '" + << pure_contract_id + << "' have incompatible type signatures:" << messaget::eom; + log.error() << "- '" << contract_id << "': " << format(contract_type) + << messaget::eom; + log.error() << "- '" << pure_contract_id + << "': " << format(pure_contract_type) << messaget::eom; + log.error() << "aborting." << messaget::eom; + throw 0; + } +} diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_handler.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_handler.h new file mode 100644 index 00000000000..9adb7e9f417 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_contract_handler.h @@ -0,0 +1,166 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com +Date: August 2022 + +\*******************************************************************/ + +/// \file +/// Specialisation of \ref dfcc_contract_handlert for DSL-style contracts + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_DSL_CONTRACT_HANDLER_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_DSL_CONTRACT_HANDLER_H + +#include + +#include +#include +#include + +#include "dfcc_contract_handler.h" +#include "dfcc_dsl_contract_functions.h" + +#include + +class goto_modelt; +class messaget; +class message_handlert; +class dfcc_libraryt; +class dfcc_utilst; +class dfcc_instrumentt; +class dfcc_spec_functionst; +class code_with_contract_typet; +class conditional_target_group_exprt; +class function_pointer_obeys_contract_exprt; + +/// A DSL-style contract is represented by a function declaration or definition +/// with contract clauses attached to its signature: +/// +/// ``` +/// ret_t foo(foo_params) +/// __CPROVER_requires(R) +/// __CPROVER_requires_contract(ptr, contract) +/// __CPROVER_assigns(A) +/// __CPROVER_frees(F) +/// __CPROVER_ensures(E) +/// __CPROVER_ensures_contract(ptr, contract) +/// { foo_body; } [optional] +/// ``` +/// +/// In the symbol table, this declaration creates two symbols: +/// - `ret_t foo(foo_params)` which represents the function (with optional body) +/// - `ret_t contract::foo(foo_params)` which represents the pure contract part +/// and carries the contract clauses in its `.type` attribute. +/// +/// This class allows, given a contract name `foo`, to lookup the symbol +/// `contract::foo` and translate its assigns and frees clauses into GOTO +/// functions that build dynamic frames at runtime (stored in an object of +/// type \ref dfcc_dsl_contract_functionst). +/// +/// Translation results are cached so it is safe to call +/// `get_contract_functions` several times. +class dfcc_dsl_contract_handlert : public dfcc_contract_handlert +{ +public: + dfcc_dsl_contract_handlert( + goto_modelt &goto_model, + messaget &log, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_instrumentt &instrument, + dfcc_spec_functionst &spec_functions); + + /// Adds instructions in `dest` modeling contract checking, assuming + /// that `ret_t wrapper_id(params)` is the function receiving + /// the instructions. + /// + /// \param[in] contract_mode checking or replacement mode + /// \param[in] wrapper_id name of function receiving the instructions + /// \param[in] wrapped_id name of function that is being checked/replaced + /// \param[in] contract_id name of the contract to check + /// \param[in] wrapper_write_set_symbol write_set parameter of the wrapper + /// \param[out] dest destination program where instructions are added + /// (should eventually become the body of wrapper_id) + /// \param[out] function_pointer_contracts list of contracts found in either + /// pre or post conditions of the processed contract. These contracts need to + /// be swapped_and_wrapped in replacement mode if they are not already. + void add_contract_instructions( + const dfcc_contract_modet contract_mode, + const irep_idt &wrapper_id, + const irep_idt &wrapped_id, + const irep_idt &contract_id, + const symbolt &wrapper_write_set_symbol, + goto_programt &dest, + std::set &function_pointer_contracts) override; + + /// Returns the size assigns clause of the given contract in number of targets + const int get_assigns_clause_size(const irep_idt &contract_id) override; + + /// Searches for a symbol named "contract::contract_id" that has a type + /// signature compatible with that of "contract_id", or creates an empty one + /// if it does not exist. + /// Returns true if found or created, throws an exception if + /// "contract::contract_id" already exists but has signature incompatible + /// with that of "contract_id". + const bool pure_contract_symbol_exists(const irep_idt &contract_id); + +protected: + goto_modelt &goto_model; + messaget &log; + message_handlert &message_handler; + dfcc_utilst &utils; + dfcc_libraryt &library; + dfcc_instrumentt &instrument; + dfcc_spec_functionst &spec_functions; + namespacet ns; + + // Caches the functions generated from DSL contracts + static std::map contract_cache; + + /// Searches for a symbol named "contract::contract_id" in the symbol table. + /// + /// If a symbol "contract::contract_id" was found and its type signature is + /// compatible with that of "contract_id" a pointer to the symbol is returned. + /// + /// If a symbol "contract::contract_id" was found but its type signature is + /// not compatible with that of "contract_id" an exception is thrown. + /// + /// If a symbol "contract::contract_id" was found, a fresh symbol representing + /// a contract with empty clauses is inserted in the symbol table and a + /// pointer to that symbol is returned. + const symbolt *get_pure_contract_symbol_ptr(const irep_idt &contract_id); + + /// Searches for a symbol named "contract::contract_id" in the symbol table. + /// + /// If a symbol "contract::contract_id" was found and its type signature is + /// compatible with that of "contract_id" a reference to the symbol is + /// returned. + /// + /// If a symbol "contract::contract_id" was found but its type signature is + /// not compatible with that of "contract_id" an exception is thrown. + /// + /// If a symbol "contract::contract_id" was found, a fresh symbol representing + /// a contract with empty clauses is inserted in the symbol table and a + /// reference to that symbol is returned. + const symbolt &get_pure_contract_symbol(const irep_idt &contract_id); + + /// Returns the `dfcc_dsl_contract_functionst` object for the given contract + /// from the cache, creates it if it does not exists. + const dfcc_dsl_contract_functionst & + get_contract_functions(const irep_idt &contract_id); + + /// \brief Throws an error if the type signatures are not compatible + /// \param contract_id name of the function that carries the contract + /// \param contract_type code_type of contract_id + /// \param pure_contract_id name of the pure contract symbol for contract_id + /// \param pure_contract_type code_type of pure_contract_id + void check_signature_compat( + const irep_idt &contract_id, + const code_typet &contract_type, + const irep_idt &pure_contract_id, + const code_typet &pure_contract_type); +}; + +#endif diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_wrapper_program.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_wrapper_program.h index a6690894ca2..016901f779e 100644 --- a/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_wrapper_program.h +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_dsl_wrapper_program.h @@ -10,8 +10,8 @@ Author: Remi Delmas, delmasrd@amazon.com /// Generates the body of a wrapper function from a DSL-style contract /// for contract checking or contract replacement -#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DFCC_DSL_WRAPPER_PROGRAM_H -#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DFCC_DSL_WRAPPER_PROGRAM_H +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_DSL_WRAPPER_PROGRAM_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_DSL_WRAPPER_PROGRAM_H #include From f99526ad8300c833c24dbf874e77a0eb9552dffb Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Wed, 24 Aug 2022 23:55:21 -0400 Subject: [PATCH 15/20] CONTRACTS: Add the `dfcc_swap_and_wrapt class`. Given a `function_id`, a `contract_id`, a contract `mode`, and a `dfcc_contract_handlert` for the contract, this class swaps the body of the function to a function with a mangled name, instruments the mangled function for dynamic frame condition checking using `dfcc_instrument`. The body of the original function is then replaced with instructions generated by the contract handler for the required contract mode. To handle recursive functions in contract checking mode, the body of the wrapper function is generated in such a way the first invocation results in checking the contract against the mangled function and recursive invocations of the wrapper behave like the contract abstraction (modelling that the contract holds). --- src/goto-instrument/Makefile | 1 + .../dynamic-frames/dfcc_swap_and_wrap.cpp | 309 ++++++++++++++++++ .../dynamic-frames/dfcc_swap_and_wrap.h | 93 ++++++ 3 files changed, 403 insertions(+) create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_swap_and_wrap.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_swap_and_wrap.h diff --git a/src/goto-instrument/Makefile b/src/goto-instrument/Makefile index 293bc68cc36..1df302d0a0a 100644 --- a/src/goto-instrument/Makefile +++ b/src/goto-instrument/Makefile @@ -26,6 +26,7 @@ SRC = accelerate/accelerate.cpp \ contracts/dynamic-frames/dfcc_dsl_contract_functions.cpp \ contracts/dynamic-frames/dfcc_dsl_wrapper_program.cpp \ contracts/dynamic-frames/dfcc_dsl_contract_handler.cpp \ + contracts/dynamic-frames/dfcc_swap_and_wrap.cpp \ contracts/havoc_assigns_clause_targets.cpp \ contracts/instrument_spec_assigns.cpp \ contracts/memory_predicates.cpp \ diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_swap_and_wrap.cpp b/src/goto-instrument/contracts/dynamic-frames/dfcc_swap_and_wrap.cpp new file mode 100644 index 00000000000..f82eaf4781f --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_swap_and_wrap.cpp @@ -0,0 +1,309 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking + +Author: Remi Delmas, delmarsd@amazon.com + +\*******************************************************************/ + +// TODO prune includes +#include "dfcc_swap_and_wrap.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +dfcc_swap_and_wrapt::dfcc_swap_and_wrapt( + goto_modelt &goto_model, + messaget &log, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_instrumentt &instrument, + dfcc_spec_functionst &spec_functions, + dfcc_contract_handlert &contract_handler) + : goto_model(goto_model), + log(log), + message_handler(log.get_message_handler()), + utils(utils), + library(library), + instrument(instrument), + spec_functions(spec_functions), + contract_handler(contract_handler), + ns(goto_model.symbol_table) +{ +} + +// static map +std::map> + dfcc_swap_and_wrapt::cache; + +void dfcc_swap_and_wrapt::swap_and_wrap( + const dfcc_contract_modet contract_mode, + const irep_idt &function_id, + const irep_idt &contract_id, + std::set &function_pointer_contracts, + bool allow_recursive_calls) +{ + log.debug() << "->swap_and_wrap(" << function_id << "," << contract_id << ")" + << messaget::eom; + auto found = cache.find(function_id); + if(found != cache.end()) + { + auto &pair = found->second; + + // same swap already performed + if(pair.first == contract_id && pair.second == contract_mode) + return; + + // already swapped with something else, abort + log.error() << " multiple attempts to swap and wrap function '" + << function_id << "':" << messaget::eom; + auto mode1 = + (pair.second == dfcc_contract_modet::REPLACE) ? "REPLACE" : "CHECK"; + log.error() << "with '" << pair.first << "' in mode " << mode1 + << messaget::eom; + auto mode2 = + (contract_mode == dfcc_contract_modet::REPLACE) ? "REPLACE" : "CHECK)"; + log.error() << "with '" << contract_id << "' in mode " << mode2 + << messaget::eom; + throw 0; + } + + cache.insert({function_id, {contract_id, contract_mode}}); + + switch(contract_mode) + { + case dfcc_contract_modet::CHECK: + { + check_contract( + function_id, + contract_id, + function_pointer_contracts, + allow_recursive_calls); + break; + } + case dfcc_contract_modet::REPLACE: + { + replace_with_contract(function_id, contract_id, function_pointer_contracts); + break; + } + } + log.debug() << "<-swap_and_wrap(" << function_id << "," << contract_id << ")" + << messaget::eom; +} + +void dfcc_swap_and_wrapt::get_swapped_functions(std::set &dest) const +{ + for(const auto &it : dfcc_swap_and_wrapt::cache) + { + dest.insert(it.first); + } +} + +/// Generates statics: +/// ``` +/// static bool on_stack = false; +/// static bool checked = false; +/// ``` +/// +/// Generates instructions: +/// ``` +/// IF on_stack GOTO replace; +/// ASSERT !checked "only a single top-level called allowed"; +/// on_stack = true; +/// ; +/// checked = true; +/// on_stack = false; +/// GOTO end; +/// replace: +/// // if allow_recursive_calls +/// ASSERT false, "recursive calls not allowed"; // if !allow_recursive_calls +/// end: +/// END_FUNCTION; +/// ``` +void dfcc_swap_and_wrapt::check_contract( + const irep_idt &function_id, + const irep_idt &contract_id, + std::set &function_pointer_contracts, + bool allow_recursive_calls) +{ + log.debug() << "->check_contract(" << function_id << "," << contract_id << ")" + << messaget::eom; + + // all code generation needs to run on functions with unmodified signatures + const irep_idt &wrapper_id = function_id; + const irep_idt wrapped_id = + id2string(wrapper_id) + "_wrapped_for_contract_checking"; + utils.wrap_function(wrapper_id, wrapped_id); + + // wrapper body + goto_programt body; + + const auto &wrapper_symbol = utils.get_function_symbol(wrapper_id); + + auto check_started = utils + .create_static_symbol( + bool_typet(), + id2string(function_id), + "__contract_check_started", + wrapper_symbol.location, + wrapper_symbol.mode, + wrapper_symbol.module, + false_exprt()) + .symbol_expr(); + + auto check_completed = utils + .create_static_symbol( + bool_typet(), + id2string(function_id), + "__contract_check_completed", + wrapper_symbol.location, + wrapper_symbol.mode, + wrapper_symbol.module, + false_exprt()) + .symbol_expr(); + + auto check_started_goto = + body.add(goto_programt::make_incomplete_goto(check_started)); + + // At most a single top level call to the checked function in any execution + + // Recursive calls within a contract check correspond to + // `check_started && !check_completed` and are allowed. + + // Any other call occuring with `check_completed` true is forbidden. + source_locationt sl; + sl.set_comment( + "only a single top-level call to '" + id2string(function_id) + + "' is allowed when checking contract '" + id2string(contract_id) + "'"); + body.add(goto_programt::make_assertion(not_exprt(check_completed), sl)); + body.add(goto_programt::make_assignment(check_started, true_exprt())); + + const auto write_set_symbol = utils.create_new_parameter_symbol( + function_id, + "__write_set_to_check", + library.dfcc_type[dfcc_typet::CAR_SET_PTR]); + + contract_handler.add_contract_instructions( + dfcc_contract_modet::CHECK, + wrapper_id, + wrapped_id, + contract_id, + write_set_symbol, + body, + function_pointer_contracts); + + body.add(goto_programt::make_assignment(check_completed, true_exprt())); + + // unconditionally Jump to the end after the check + auto goto_end_function = body.add(goto_programt::make_incomplete_goto()); + + // Jump to the replacement section if check already in progress + auto contract_replacement_label = body.add(goto_programt::make_skip()); + check_started_goto->complete_goto(contract_replacement_label); + + // if(allow_recursive_calls) + // { + // contract_handler.add_contract_instructions( + // dfcc_contract_modet::REPLACE, + // wrapper_id, + // wrapped_id, + // contract_id, + // write_set_symbol, + // body, + // function_pointer_contracts); + // } + // else + // { + // source_locationt sl; + // sl.set_comment( + // "unexpected recursive call for '" + id2string(function_id) + + // "' when checking contract '" + id2string(contract_id) + "'"); + // body.add(goto_programt::make_assertion(false_exprt(), sl)); + // } + + auto end_function_label = body.add(goto_programt::make_end_function()); + goto_end_function->complete_goto(end_function_label); + + // write the body to the GOTO function + goto_model.goto_functions.function_map.at(function_id).body.swap(body); + + // extend the signature of the wrapper function with the write set parameter + utils.add_parameter(write_set_symbol, function_id); + + utils.set_hide(wrapper_id, true); + + // instrument the wrapped function + instrument.instrument_function(wrapped_id); + + goto_model.goto_functions.update(); + + log.debug() << "<-check_contract(" << function_id << "," << contract_id << ")" + << messaget::eom; +} + +void dfcc_swap_and_wrapt::replace_with_contract( + const irep_idt &function_id, + const irep_idt &contract_id, + std::set &function_pointer_contracts) +{ + log.debug() << "->replace_with_contract(" << function_id << "," << contract_id + << ")" << messaget::eom; + + const irep_idt &wrapper_id = function_id; + const irep_idt wrapped_id = + id2string(function_id) + "_wrapped_for_replacement_with_contract"; + utils.wrap_function(function_id, wrapped_id); + + const auto write_set_symbol = utils.create_new_parameter_symbol( + function_id, + "__write_set_to_check", + library.dfcc_type[dfcc_typet::CAR_SET_PTR]); + + goto_programt body; + + contract_handler.add_contract_instructions( + dfcc_contract_modet::REPLACE, + wrapper_id, + wrapped_id, + contract_id, + write_set_symbol, + body, + function_pointer_contracts); + + body.add(goto_programt::make_end_function()); + + utils.set_hide(wrapper_id, true); + + // write the body to the GOTO function + goto_model.goto_functions.function_map.at(function_id).body.swap(body); + + // extend the signature with the new write set parameter + utils.add_parameter(write_set_symbol, function_id); + log.debug() << "<-replace_with_contract(" << function_id << "," << contract_id + << ")" << messaget::eom; +} diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_swap_and_wrap.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_swap_and_wrap.h new file mode 100644 index 00000000000..b9da8a0082b --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_swap_and_wrap.h @@ -0,0 +1,93 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com + +\*******************************************************************/ + +/// \file +/// Given a function_id and contract_id, swaps its body to a function +/// with a fresh mangled name, instruments it for dynamic frame condition +/// checking, and replaces the original function's body with instructions +/// encoding contract checking against the mangled function, +/// or contract replacement. + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_SWAP_AND_WRAP_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_SWAP_AND_WRAP_H +#include +#include +#include +#include + +#include + +#include "dfcc_contract_handler.h" +#include "dfcc_dsl_contract_handler.h" +#include "dfcc_instrument.h" +#include "dfcc_library.h" +#include "dfcc_spec_functions.h" +#include "dfcc_utils.h" + +#include +#include + +class goto_modelt; +class messaget; +class message_handlert; +class symbolt; +class conditional_target_group_exprt; +class cfg_infot; + +class dfcc_swap_and_wrapt +{ +public: + dfcc_swap_and_wrapt( + goto_modelt &goto_model, + messaget &log, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_instrumentt &instrument, + dfcc_spec_functionst &spec_functions, + dfcc_contract_handlert &contract_handler); + + void swap_and_wrap( + const dfcc_contract_modet contract_mode, + const irep_idt &function_id, + const irep_idt &contract_id, + std::set &function_pointer_contracts, + bool allow_recursive_calls = false); + + /// Adds the set of swapped functions to dest + void get_swapped_functions(std::set &dest) const; + +protected: + goto_modelt &goto_model; + messaget &log; + message_handlert &message_handler; + dfcc_utilst &utils; + dfcc_libraryt &library; + dfcc_instrumentt &instrument; + dfcc_spec_functionst &spec_functions; + dfcc_contract_handlert &contract_handler; + namespacet ns; + + /// remember all functions that were swapped/wrapped and in which mode + static std::map> cache; + + /// Swaps-and-wraps the given `function_id` in a wrapper function that + /// checks the given `contract_id`. + void check_contract( + const irep_idt &function_id, + const irep_idt &contract_id, + std::set &function_pointer_contracts, + bool allow_recursive_calls); + + /// Swaps-and-wraps the given `function_id` in a wrapper function that + /// models the abstract behaviour of contract `contract_id`. + void replace_with_contract( + const irep_idt &function_id, + const irep_idt &contract_id, + std::set &function_pointer_contracts); +}; +#endif From cd537efd2587cac423db7b960457bd6a377ee410 Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Thu, 22 Sep 2022 11:34:01 -0400 Subject: [PATCH 16/20] dfcc_swap_and_wrap fixes --- .../dynamic-frames/dfcc_swap_and_wrap.cpp | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_swap_and_wrap.cpp b/src/goto-instrument/contracts/dynamic-frames/dfcc_swap_and_wrap.cpp index f82eaf4781f..83dab8f3e68 100644 --- a/src/goto-instrument/contracts/dynamic-frames/dfcc_swap_and_wrap.cpp +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_swap_and_wrap.cpp @@ -124,24 +124,26 @@ void dfcc_swap_and_wrapt::get_swapped_functions(std::set &dest) const } } -/// Generates statics: -/// ``` -/// static bool on_stack = false; -/// static bool checked = false; +/// \details Generates globals statics: +/// ```c +/// static bool __contract_check_started = false; +/// static bool __contract_check_completed = false; /// ``` /// -/// Generates instructions: -/// ``` -/// IF on_stack GOTO replace; +/// Adds the following instructions in the wrapper function body: +/// ```c +/// IF __contract_check_started GOTO replace; /// ASSERT !checked "only a single top-level called allowed"; -/// on_stack = true; -/// ; -/// checked = true; -/// on_stack = false; +/// __contract_check_started = true; +/// ; +/// __contract_check_completed = true; +/// __contract_check_started = false; /// GOTO end; /// replace: -/// // if allow_recursive_calls -/// ASSERT false, "recursive calls not allowed"; // if !allow_recursive_calls +/// // if allow_recursive_calls +/// ; +/// // if !allow_recursive_calls +/// ASSERT false, "recursive calls not allowed"; /// end: /// END_FUNCTION; /// ``` From a624d48df15d55e4968607dd8feb2ee97c29d72e Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Thu, 25 Aug 2022 00:00:07 -0400 Subject: [PATCH 17/20] CONTRACTS: Add class `dfcct`, the main class for function contracts with dynamic frames. The class receives the proof harness function, the map of functions and contracts to check and the map of functions to replace with contracts. It instruments the proof harness, performs swap-and-wrap operations for each of the checked and replaced functions. It then instruments all other functions for DFCC. --- src/goto-instrument/Makefile | 1 + .../contracts/dynamic-frames/dfcc.cpp | 479 ++++++++++++++++++ .../contracts/dynamic-frames/dfcc.h | 220 ++++++++ 3 files changed, 700 insertions(+) create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc.h diff --git a/src/goto-instrument/Makefile b/src/goto-instrument/Makefile index 1df302d0a0a..c56191418ad 100644 --- a/src/goto-instrument/Makefile +++ b/src/goto-instrument/Makefile @@ -27,6 +27,7 @@ SRC = accelerate/accelerate.cpp \ contracts/dynamic-frames/dfcc_dsl_wrapper_program.cpp \ contracts/dynamic-frames/dfcc_dsl_contract_handler.cpp \ contracts/dynamic-frames/dfcc_swap_and_wrap.cpp \ + contracts/dynamic-frames/dfcc.cpp \ contracts/havoc_assigns_clause_targets.cpp \ contracts/instrument_spec_assigns.cpp \ contracts/memory_predicates.cpp \ diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc.cpp b/src/goto-instrument/contracts/dynamic-frames/dfcc.cpp new file mode 100644 index 00000000000..e71fbdc6f16 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc.cpp @@ -0,0 +1,479 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking + +Author: Remi Delmas, delmarsd@amazon.com + +\*******************************************************************/ + +// TODO havoc global statics +// TODO move disable assigns clause pragma into utils +// TODO prune includes +#include "dfcc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +void dfcc( + goto_modelt &goto_model, + const std::string &harness_id, + const std::set &to_check, + const std::set &to_replace, + const bool apply_loop_contracts, + const std::set &to_exclude_from_nondet_static, + messaget &log) +{ + std::map to_check_map; + for(const auto &function_id : to_check) + to_check_map.insert({function_id, function_id}); + + std::map to_replace_map; + for(const auto &function_id : to_replace) + to_replace_map.insert({function_id, function_id}); + + dfcct{goto_model, log}.transform_goto_model( + harness_id, + to_check_map, + to_replace_map, + apply_loop_contracts, + to_exclude_from_nondet_static); +} + +void dfcc( + goto_modelt &goto_model, + const std::string &harness_id, + const std::map &to_check, + const std::map &to_replace, + const bool apply_loop_contracts, + const std::set &to_exclude_from_nondet_static, + messaget &log) +{ + dfcct{goto_model, log}.transform_goto_model( + harness_id, + to_check, + to_replace, + apply_loop_contracts, + to_exclude_from_nondet_static); +} + +std::map dfcct::wrapped_functions_cache; + +dfcct::dfcct(goto_modelt &goto_model, messaget &log) + : goto_model(goto_model), + log(log), + message_handler(log.get_message_handler()), + utils(goto_model, log), + library(goto_model, utils, log), + ns(goto_model.symbol_table), + instrument(goto_model, log, utils, library), + spec_functions(goto_model, log, utils, library, instrument), + dsl_contract_handler( + goto_model, + log, + utils, + library, + instrument, + spec_functions), + swap_and_wrap( + goto_model, + log, + utils, + library, + instrument, + spec_functions, + dsl_contract_handler), + max_assigns_clause_size(-1) +{ +} + +void dfcct::check_transform_goto_model_preconditions( + const std::string &harness_id, + const std::map &to_check, + const std::map &to_replace, + const bool apply_loop_contracts, + const std::set &to_exclude_from_nondet_static) +{ + log.debug() << "harness_id " << harness_id << messaget::eom; + + for(const auto &pair : to_check) + { + log.debug() << "to check " << pair.first << "->" << pair.second + << messaget::eom; + } + + for(const auto &pair : to_replace) + { + log.debug() << "to replace " << pair.first << "->" << pair.second + << messaget::eom; + } + // TODO reactivate this once I understand how entry points work + // Check that the goto_model entry point matches the given harness_id + // if(!config.main.has_value()) + // { + // log.error() << "dfcct::transform_goto_model: no entry point found in the" + // "goto model, expected '" + // << harness_id << "', aborting contracts transformations." + // << messaget::eom; + // throw 0; + // } + + // if(config.main.value() != harness_id) + // { + // log.error() << "dfcct::transform_goto_model: entry point '" + // << config.main.value() + // << "' differs from given harness function '" << harness_id + // << "', aborting contracts transformations." << messaget::eom; + // throw 0; + // } + + // check there is at most one function to check + if(to_check.size() > 1) + { + log.error() + << "dfcct::transform_goto_model_preconditions: only a single " + "(function,contract) pair can be checked at a time, aborting." + << messaget::eom; + throw 0; + } + + // check that harness function exists + PRECONDITION_WITH_DIAGNOSTICS( + utils.function_symbol_with_body_exists(harness_id), + "dfcct::transform_goto_model_preconditions: harness function '" + + harness_id + "' either not found or has no body."); + + for(const auto &pair : to_check) + { + PRECONDITION_WITH_DIAGNOSTICS( + utils.function_symbol_with_body_exists(pair.first), + "dfcct::transform_goto_model_preconditions: function to check '" + + pair.first + "' either not found or has no body."); + + PRECONDITION_WITH_DIAGNOSTICS( + dsl_contract_handler.pure_contract_symbol_exists(pair.second), + "dfcct::transform_goto_model_preconditions: contract to check '" + + pair.second + "' not found."); + + PRECONDITION_WITH_DIAGNOSTICS( + pair.first != harness_id, + "dfcct::transform_goto_model_preconditions: function '" + pair.first + + "' cannot be both be checked against a contract and be the harness"); + + PRECONDITION_WITH_DIAGNOSTICS( + pair.second != harness_id, + "dfcct::transform_goto_model_preconditions: function '" + pair.first + + "' cannot be both the contract to check and be the harness"); + + PRECONDITION_WITH_DIAGNOSTICS( + to_replace.find(pair.first) == to_replace.end(), + "dfcct::transform_goto_model_preconditions: function '" + pair.first + + "' cannot be both checked against contract and replaced by a contract"); + + PRECONDITION_WITH_DIAGNOSTICS( + !instrument.do_not_instrument(pair.first), + "dfcct::transform_goto_model_preconditions: CPROVER function or builtin " + "'" + + pair.first + "' cannot be checked against a contract"); + } + + for(const auto &pair : to_replace) + { + // for functions to replace with contracts we don't require the replaced + // function to have a body because only the contract is actually used + PRECONDITION_WITH_DIAGNOSTICS( + utils.function_symbol_exists(pair.first), + "dfcct::transform_goto_model_preconditions: function to replace '" + + pair.first + "' not found."); + + PRECONDITION_WITH_DIAGNOSTICS( + dsl_contract_handler.pure_contract_symbol_exists(pair.second), + "dfcct::transform_goto_model_preconditions: contract to replace '" + + pair.second + "' not found."); + + PRECONDITION_WITH_DIAGNOSTICS( + pair.first != harness_id, + "dfcct::transform_goto_model_preconditions: function '" + pair.first + + "' cannot both be replaced with a contract and be the harness"); + + PRECONDITION_WITH_DIAGNOSTICS( + pair.second != harness_id, + "dfcct::transform_goto_model_preconditions: function '" + pair.first + + "' cannot both be the contract to use for replacement and be the " + "harness"); + } + + for(const auto &id : to_exclude_from_nondet_static) + { + // for functions to replace with contracts we don't require the replaced + // function to have a body because only the contract is actually used + PRECONDITION_WITH_DIAGNOSTICS( + utils.symbol_exists(id), + "dfcct::transform_goto_model_preconditions: symbol '" + id + + "' to exclude from nondet-static not found."); + } +} + +void dfcct::partition_function_symbols( + const std::vector &symbols, + std::set &contract_symbols, + std::set &assignable_t_symbols, + std::set &freeable_t_symbols, + std::set &other_symbols) +{ + auto &symbol_table = goto_model.symbol_table; + + for(auto &sym_name : symbols) + { + const auto &symbol = symbol_table.lookup_ref(sym_name); + + if(symbol.type.id() != ID_code) + continue; + + // is it a pure contract ? + if(symbol.is_property && (id2string(sym_name).rfind("contract::", 0) == 0)) + { + contract_symbols.insert(sym_name); + } + else if(spec_functions.is_assignable_t_function(sym_name)) + { + assignable_t_symbols.insert(sym_name); + } + else if(spec_functions.is_freeable_t_function(sym_name)) + { + freeable_t_symbols.insert(sym_name); + } + else + { + other_symbols.insert(sym_name); + } + } +} + +void dfcct::transform_goto_model( + const std::string &harness_id, + const std::map &to_check, + const std::map &to_replace, + const bool apply_loop_contracts, + const std::set &to_exclude_from_nondet_static) +{ + check_transform_goto_model_preconditions( + harness_id, + to_check, + to_replace, + apply_loop_contracts, + to_exclude_from_nondet_static); + + // load the cprover library to make sure the model is complete + log.status() << "Loading CPROVER C library (" << config.ansi_c.arch << ")" + << messaget::eom; + link_to_library( + goto_model, log.get_message_handler(), cprover_c_library_factory); + + // shortcuts + symbol_tablet &symbol_table = goto_model.symbol_table; + + // partition the set of functions of the goto_model + std::set pure_contract_symbols; + std::set assignable_t_symbols; + std::set freeable_t_symbols; + std::set other_symbols; + std::set function_pointer_contracts; + + // this vector must be computed before loading the dfcc lib + // so it does not contain dfcc library symbols + const auto preexisting_symbols = symbol_table.sorted_symbol_names(); + + partition_function_symbols( + preexisting_symbols, + pure_contract_symbols, + assignable_t_symbols, + freeable_t_symbols, + other_symbols); + + // load the dfcc library before instrumentation starts + library.load(other_symbols); + + // add C prover lib again to fetch any dependencies of the dfcc functions + link_to_library( + goto_model, log.get_message_handler(), cprover_c_library_factory); + + // instrument the harness function + // load the cprover library to make sure the model is complete + log.status() << "Instrumenting harness function '" << harness_id << "'" + << messaget::eom; + instrument.instrument_harness_function(harness_id); + + other_symbols.erase(harness_id); + + // swap-and-wrap checked functions with contracts + for(const auto &pair : to_check) + { + const auto &wrapper_id = pair.first; + const auto &contract_id = pair.second; + log.status() << "Wrapping '" << wrapper_id << "' with contract '" + << contract_id << "' in CHECK mode" << messaget::eom; + + swap_and_wrap.swap_and_wrap( + dfcc_contract_modet::CHECK, + wrapper_id, + contract_id, + function_pointer_contracts, + false /* do not allow recursive calls for now */); + + if(other_symbols.find(wrapper_id) != other_symbols.end()) + other_symbols.erase(wrapper_id); + + // upate max contract size + const int assigns_clause_size = + dsl_contract_handler.get_assigns_clause_size(contract_id); + if(assigns_clause_size > max_assigns_clause_size) + max_assigns_clause_size = assigns_clause_size; + } + + // swap-and-wrap replaced functions with contracts + for(const auto &pair : to_replace) + { + const auto &wrapper_id = pair.first; + const auto &contract_id = pair.second; + log.status() << "Wrapping '" << wrapper_id << "' with contract '" + << contract_id << "' in REPLACE mode" << messaget::eom; + swap_and_wrap.swap_and_wrap( + dfcc_contract_modet::REPLACE, + wrapper_id, + contract_id, + function_pointer_contracts); + if(other_symbols.find(wrapper_id) != other_symbols.end()) + { + other_symbols.erase(wrapper_id); + } + } + + // swap-and-wrap function pointer contracts with themselves + for(const auto &fp_contract : function_pointer_contracts) + { + // function contracts are necessarily replaced with themselves + // so we need to check that: + // - the corresponding function symbol exists + // - the corresponding pure contract symbol exists + // - the function symbol is not itself already swapped with something + // else than itself for replacement + // - the function symbol is not itself already swapped with any other + // contract for contract checking + + const auto str = id2string(fp_contract); + + PRECONDITION_WITH_DIAGNOSTICS( + utils.function_symbol_exists(str), + "dfcct::transform_goto_model: function pointer contract '" + str + + "' not found."); + + PRECONDITION_WITH_DIAGNOSTICS( + dsl_contract_handler.pure_contract_symbol_exists(str), + "dfcct::transform_goto_model_preconditions: contract to replace " + "'contract::" + + str + "' not found."); + + PRECONDITION_WITH_DIAGNOSTICS( + to_check.find(str) == to_check.end(), + "dfcct::transform_goto_model: function '" + str + + "' used as contract for function pointer cannot be " + "itself the object of a contract check."); + + // check if not already swapped + auto found = to_replace.find(str); + + if(found == to_replace.end()) + { + log.status() << "wrapping function pointer contract '" << fp_contract + << "' with itself in REPLACE mode" << messaget::eom; + + swap_and_wrap.swap_and_wrap( + dfcc_contract_modet::REPLACE, + fp_contract, + fp_contract, + function_pointer_contracts); + if(other_symbols.find(fp_contract) != other_symbols.end()) + other_symbols.erase(fp_contract); + } + else + { + PRECONDITION_WITH_DIAGNOSTICS( + found->first == found->second, + "dfcct::transform_goto_model: function '" + str + + "' used as contract for function pointer cannot be " + "itself the object of a contract replacement with '" + + found->second + "'"); + } + } + + // instrument all other remaining functions + for(const auto &function_id : other_symbols) + { + // Don't instrument CPROVER and internal functions + if(instrument.do_not_instrument(function_id)) + { + continue; + } + + log.status() << "Instrumenting '" << function_id << "'" << messaget::eom; + + instrument.instrument_function(function_id); + } + + log.status() << "Updating goto functions" << messaget::eom; + goto_model.goto_functions.update(); + + if(!to_check.empty()) + { + log.status() << "Specializing cprover_contracts functions for assigns " + "clauses of at most " + << max_assigns_clause_size << " targets" << messaget::eom; + library.specialize(max_assigns_clause_size); + } + + log.status() << "Inhibiting front-end functions" << messaget::eom; + library.inhibit_front_end_builtins(); + + // collect set of functions which are now write set checkable + std::set instrumented_functions; + instrument.get_instrumented_functions(instrumented_functions); + swap_and_wrap.get_swapped_functions(instrumented_functions); + + // update statics and static function maps + log.status() << "Updating CPROVER init function" << messaget::eom; + library.create_initialize_function(instrumented_functions); + + log.status() << "Removing SKIP instructions" << messaget::eom; + remove_skip(goto_model); + + log.status() << "Removing unused functions" << messaget::eom; + // unsound if function pointers have not been yet been removed + // Another solution would be to discard the bodies of removed functions and + // add an assert(false) instead + remove_unused_functions(goto_model, message_handler); +} diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc.h b/src/goto-instrument/contracts/dynamic-frames/dfcc.h new file mode 100644 index 00000000000..feafcfca514 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc.h @@ -0,0 +1,220 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com + +\*******************************************************************/ + +/// \defgroup contracts-module Code Contracts + +/// \defgroup dfcc-module Dynamic Frame Condition Checking (DFCC) +/// \ingroup contracts-module + +/// \file +/// \ingroup dfcc-module +/// +/// \brief Main class orchestrating the the whole program transformation +/// for function contracts with Dynamic Frame Condition Checking (DFCC) +/// +/// Contracts must be expressed in DSL-style, i.e. as contract clauses attached +/// to the function declaration at the C level. +/// +/// All functions of the model are extended with a ghost parameter representing +/// a dynamic frame and all their assignments are instrumented and checked +/// against the dynamic frame parameter. +/// +/// Targets specified by assigns clauses and frees clauses of the contracts +/// are reified into dynamic data structures built by ghost code embedded in the +/// program and are propagated through function calls and function pointer calls +/// as ghost call arguments. + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_H + +#include +#include +#include +#include + +#include + +#include "dfcc_contract_handler.h" +#include "dfcc_dsl_contract_handler.h" +#include "dfcc_instrument.h" +#include "dfcc_library.h" +#include "dfcc_spec_functions.h" +#include "dfcc_swap_and_wrap.h" +#include "dfcc_utils.h" + +#include +#include + +class goto_modelt; +class messaget; +class message_handlert; +class symbolt; +class conditional_target_group_exprt; +class cfg_infot; + +#define FLAG_DFCC "dfcc" +#define OPT_DFCC "(" FLAG_DFCC "):" +// clang-format off +#define HELP_DFCC \ + "--dfcc activate dynamic frame condition checking for function"\ + " contracts using given function as entry point" +// clang-format on + +/// \ingroup dfcc-module +/// \brief Applies function contracts transformation to GOTO model, +/// using the dynamic frame condition checking approach. +/// +/// \pre This function requires that the contract associated to function `foo` +/// exists in the symbol table as symbol `contract::foo`. +/// +/// \param goto_model GOTO model to transform +/// \param harness_id proof harness name, must be the entry point of the model +/// \param to_check set of functions to check against their contract +/// (must contain at most one element) +/// \param to_replace set of functions to replace with their contract +/// \param apply_loop_contracts apply loop contract transformations iff true +/// \param exclude_from_nondet_static set of symbols to exclude when havocing +/// static program symbols. +/// \param log logger to use for debug/warning/error messages +void dfcc( + goto_modelt &goto_model, + const std::string &harness_id, + const std::set &to_check, + const std::set &to_replace, + const bool apply_loop_contracts, + const std::set &exclude_from_nondet_static, + messaget &log); + +/// \ingroup dfcc-module +/// \brief Applies function contracts and loop contracts transformation to GOTO +/// model, using the dynamic frame condition checking approach. +/// +/// Functions to check/replace are explicitly mapped to contracts. +/// When checking function `foo` against contract `bar`, we require the +/// actual contract symbol to exist as `contract::bar` in the symbol table. +/// +/// \param goto_model GOTO model to transform +/// \param harness_id proof harness name, must be the entry point of the model +/// \param to_check functions-to-contract mapping for contract checking +/// (must contain at most one element) +/// \param to_replace functions-to-contract mapping for replacement +/// \param apply_loop_contracts apply loop contract transformations iff true +/// \param exclude_from_nondet_static set of symbols to exclude when havocing +/// static program symbols. +/// \param log logger to use for debug/warning/error messages +void dfcc( + goto_modelt &goto_model, + const std::string &harness_id, + const std::map &to_check, + const std::map &to_replace, + const bool apply_loop_contracts, + const std::set &exclude_from_nondet_static, + messaget &log); + +/// \ingroup dfcc-module +/// \brief Entry point into the contracts transformation +class dfcct +{ +public: + /// Class constructor + /// \param goto_model GOTO model to transform + /// \param log logger to use for debug/warning/error messages + dfcct(goto_modelt &goto_model, messaget &log); + + /// Applies function contracts and loop contracts transformation to GOTO model + /// using the dynamic frame condition checking approach. + /// + /// Functions to check/replace are explicitly mapped to contracts. + /// When checking function `foo` against contract `bar`, we require the + /// actual contract symbol to exist as `contract::bar` in the symbol table. + /// + /// \param harness_id proof harness name, must be the entry point of the model + /// \param to_check functions-to-contract mapping for contract checking + /// (must contain at most one element) + /// \param to_replace functions-to-contract mapping for replacement + /// \param apply_loop_contracts apply loop contract transformations iff true + /// \param to_exclude_from_nondet_static set of symbols to exclude when havocing + /// static program symbols. + /// + /// Transformation steps: + /// - check preconditions on existence and absence of clash between harness, + /// functions and contract symbols parameters. + /// - link the goto model to the standard library + /// - instrument the harness function. + /// - partition function symbols of the model into + /// - `contract::` symbols + /// - assigns/frees clause specification functions (not instrumented) + /// - all other functions (instrumented) + /// - swap-and-wrap/instrument functions to check with their contract + /// - swap-and-wrap/instrument functions to replace with their contract + /// - swap-and-wrap all discovered function pointer contracts with themselves + /// (replacement mode) + /// - instrument all remaining functions GOTO model + /// - (this includes standard library functions). + /// - specialise the instrumentation functions by unwiding loops they contain + /// to the maximum size of any assigns clause involved in the model. + void transform_goto_model( + const std::string &harness_id, + const std::map &to_check, + const std::map &to_replace, + const bool apply_loop_contracts, + const std::set &to_exclude_from_nondet_static); + +protected: + goto_modelt &goto_model; + messaget &log; + message_handlert &message_handler; + + // hold the global state of the transformation (caches etc.) + dfcc_utilst utils; + dfcc_libraryt library; + namespacet ns; + dfcc_instrumentt instrument; + dfcc_spec_functionst spec_functions; + dfcc_dsl_contract_handlert dsl_contract_handler; + dfcc_swap_and_wrapt swap_and_wrap; + + /// Maps wrapper function to the wrapped function. + /// Caching prevents wrapping more than once. + static std::map wrapped_functions_cache; + + /// Tracks the maximum number of targets in any assigns clause handled in the + /// transformation (used to specialize/unwind loops in DFCC library functions) + int max_assigns_clause_size; + + /// Checks preconditions on arguments of \ref transform_goto_model. + /// + /// The preconditions are: + /// - The harness function exists in the model and has a body, + /// - The model's entry point is the harness function, + /// - The harness function is distinct from any checked or replaced function, + /// - The harness function is distinct from any contract, + /// - All function to check exist and have a body available, + /// - All function to replace exist (body is not necessary), + /// - All contracts to check or replace exist as pure contract symbols, + /// - The checked function cannot be also replaced, + /// - compiler built-ins or `__CPROVER_*` functions cannot be checked against + /// a contract (because they cannot be instrumented), + /// - all symbols of `exclude_from_nondet_static` exist in the model. + void check_transform_goto_model_preconditions( + const std::string &harness_id, + const std::map &to_check, + const std::map &to_replace, + const bool apply_loop_contracts, + const std::set &exclude_from_nondet_static); + + /// Partitions the given vector `symbols` into 4 sets of function symbols. + void partition_function_symbols( + const std::vector &symbols, + std::set &pure_contract_symbols, + std::set &assignable_t_symbols, + std::set &freeable_t_symbols, + std::set &other_symbols); +}; + +#endif From f59b93c7170a6b5b82ef6b862d84090f06820899 Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Thu, 25 Aug 2022 00:04:16 -0400 Subject: [PATCH 18/20] CONTRACTS: add the `--dfcc` switch to the goto-instrument CLI interface The switch expects the harness function name as parameter, and accepts any number of `--enforce-contract` and `--replace-call-with-contract` switches. --- .../goto_instrument_parse_options.cpp | 52 ++++++++++++++++++- .../goto_instrument_parse_options.h | 2 + 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/goto-instrument/goto_instrument_parse_options.cpp b/src/goto-instrument/goto_instrument_parse_options.cpp index cc286c59512..2af36f6d966 100644 --- a/src/goto-instrument/goto_instrument_parse_options.cpp +++ b/src/goto-instrument/goto_instrument_parse_options.cpp @@ -1157,9 +1157,56 @@ void goto_instrument_parse_optionst::instrument_goto_program() annotate_invariants(synthesizer.synthesize_all(), goto_model, log); } + bool use_dfcc = false; + if(cmdline.isset(FLAG_DFCC)) + { + use_dfcc = true; + + if(cmdline.get_values(FLAG_DFCC).size() != 1) + { + log.error() << "Only a single --" << FLAG_DFCC << " is allowed" + << messaget::eom; + throw 0; + } + + if(cmdline.isset(FLAG_LOOP_CONTRACTS)) + { + log.error() << " The combination of " << FLAG_DFCC << " and " + << FLAG_LOOP_CONTRACTS << " is not yet supported" + << messaget::eom; + throw 0; + } + + do_indirect_call_and_rtti_removal(); + + const std::string &harness_id = *cmdline.get_values(FLAG_DFCC).begin(); + + std::set to_replace( + cmdline.get_values(FLAG_REPLACE_CALL).begin(), + cmdline.get_values(FLAG_REPLACE_CALL).end()); + + std::set to_check( + cmdline.get_values(FLAG_ENFORCE_CONTRACT).begin(), + cmdline.get_values(FLAG_ENFORCE_CONTRACT).end()); + + std::set to_exclude_from_nondet_static( + cmdline.get_values("nondet-static-exclude").begin(), + cmdline.get_values("nondet-static-exclude").end()); + + dfcc( + goto_model, + harness_id, + to_check, + to_replace, + false, + to_exclude_from_nondet_static, + log); + } + if( - cmdline.isset(FLAG_LOOP_CONTRACTS) || cmdline.isset(FLAG_REPLACE_CALL) || - cmdline.isset(FLAG_ENFORCE_CONTRACT)) + !use_dfcc && + (cmdline.isset(FLAG_LOOP_CONTRACTS) || cmdline.isset(FLAG_REPLACE_CALL) || + cmdline.isset(FLAG_ENFORCE_CONTRACT))) { do_indirect_call_and_rtti_removal(); code_contractst contracts(goto_model, log); @@ -1911,6 +1958,7 @@ void goto_instrument_parse_optionst::help() " force aggressive slicer to preserve all direct paths\n" // NOLINT(*) "\n" "Code contracts:\n" + HELP_DFCC HELP_LOOP_CONTRACTS HELP_LOOP_INVARIANT_SYNTHESIZER HELP_REPLACE_CALL diff --git a/src/goto-instrument/goto_instrument_parse_options.h b/src/goto-instrument/goto_instrument_parse_options.h index 905e7d191f1..479f8b2cd56 100644 --- a/src/goto-instrument/goto_instrument_parse_options.h +++ b/src/goto-instrument/goto_instrument_parse_options.h @@ -41,6 +41,7 @@ Author: Daniel Kroening, kroening@kroening.com #include "unwindset.h" #include "contracts/contracts.h" +#include "contracts/dynamic-frames/dfcc.h" #include "synthesizer/enumerative_loop_invariant_synthesizer.h" #include "wmm/weak_memory.h" @@ -95,6 +96,7 @@ Author: Daniel Kroening, kroening@kroening.com "(list-symbols)(list-undefined-functions)" \ "(z3)(add-library)(show-dependence-graph)" \ "(horn)(skip-loops):(model-argc-argv):" \ + OPT_DFCC \ "(" FLAG_LOOP_CONTRACTS ")" \ "(" FLAG_REPLACE_CALL "):" \ "(" FLAG_ENFORCE_CONTRACT "):" \ From 24ba3bdd69ae68a746c5c65808d658d60f6c2648 Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Thu, 25 Aug 2022 00:04:55 -0400 Subject: [PATCH 19/20] CONTACTS: update regression test suite for DFCC Updated tests - update assigns-enforce-malloc-zero - update assigns-local-composite - update assigns-replace-ignored-return-value - update assigns-replace-malloc-zero - update assigns-slice-targets - update assigns-slice-targets - update assigns_enforce_01 - update assigns_enforce_02 - update assigns_enforce_03 - update assigns_enforce_04 - update assigns_enforce_05 - update assigns_enforce_06 - update assigns_enforce_07 --- .../assigns-enforce-malloc-zero/main.c | 2 +- .../assigns-enforce-malloc-zero/test.desc | 3 +- .../assigns-local-composite/test.desc | 6 +- .../test.desc | 2 +- .../assigns-replace-malloc-zero/main.c | 2 +- .../assigns-replace-malloc-zero/test.desc | 2 +- .../assigns-slice-targets/test-enforce.desc | 2 +- .../assigns-slice-targets/test-replace.desc | 2 +- .../contracts/assigns_enforce_01/test.desc | 2 +- .../contracts/assigns_enforce_02/test.desc | 3 +- .../contracts/assigns_enforce_03/test.desc | 14 +--- .../contracts/assigns_enforce_04/test.desc | 5 +- .../contracts/assigns_enforce_05/test.desc | 2 +- .../contracts/assigns_enforce_06/main.c | 35 +++++----- .../contracts/assigns_enforce_06/test.desc | 5 +- .../contracts/assigns_enforce_07/main.c | 37 ++++++----- .../contracts/assigns_enforce_07/test.desc | 2 +- .../frees-clause-and-predicates/main.c | 64 ++++++++++++++----- .../frees-clause-and-predicates/test.desc | 18 ++---- 19 files changed, 116 insertions(+), 92 deletions(-) diff --git a/regression/contracts/assigns-enforce-malloc-zero/main.c b/regression/contracts/assigns-enforce-malloc-zero/main.c index 9592997ddd5..eeb6a4d06ae 100644 --- a/regression/contracts/assigns-enforce-malloc-zero/main.c +++ b/regression/contracts/assigns-enforce-malloc-zero/main.c @@ -6,7 +6,7 @@ int foo(char *a, int size) // clang-format off __CPROVER_requires(0 <= size && size <= __CPROVER_max_malloc_size) __CPROVER_requires(a == NULL || __CPROVER_is_fresh(a, size)) - __CPROVER_assigns(a: __CPROVER_POINTER_OBJECT(a)) + __CPROVER_assigns(a: __CPROVER_whole_object(a)) __CPROVER_ensures( a && __CPROVER_return_value >= 0 ==> a[__CPROVER_return_value] == 0) // clang-format on diff --git a/regression/contracts/assigns-enforce-malloc-zero/test.desc b/regression/contracts/assigns-enforce-malloc-zero/test.desc index eefba72ce5b..7d82f4cf938 100644 --- a/regression/contracts/assigns-enforce-malloc-zero/test.desc +++ b/regression/contracts/assigns-enforce-malloc-zero/test.desc @@ -1,7 +1,6 @@ CORE main.c ---enforce-contract foo _ --malloc-may-fail --malloc-fail-null -^\[foo.assigns.\d+\] line 9 Check that POINTER_OBJECT\(\(const void \*\)a\) is valid when a != \(\(char \*\)NULL\): SUCCESS$ +--dfcc main --enforce-contract foo _ --malloc-may-fail --malloc-fail-null ^\[foo.assigns.\d+\] line 19 Check that a\[\(signed long (long )?int\)i\] is assignable: SUCCESS$ ^EXIT=0$ ^SIGNAL=0$ diff --git a/regression/contracts/assigns-local-composite/test.desc b/regression/contracts/assigns-local-composite/test.desc index 1e4b211e77d..261546f1591 100644 --- a/regression/contracts/assigns-local-composite/test.desc +++ b/regression/contracts/assigns-local-composite/test.desc @@ -1,12 +1,12 @@ CORE main.c ---enforce-contract foo +--dfcc main --enforce-contract foo ^EXIT=0$ ^SIGNAL=0$ ^VERIFICATION SUCCESSFUL$ -- -- -Checks that assigns clause checking explicitly checks assignments to locally +Checks that assigns clause checking explicitly checks assignments to locally declared symbols with composite types, when they are dirty. -Out of bounds accesses to locally declared arrays, structs, etc. +Out of bounds accesses to locally declared arrays, structs, etc. will be detected by assigns clause checking. diff --git a/regression/contracts/assigns-replace-ignored-return-value/test.desc b/regression/contracts/assigns-replace-ignored-return-value/test.desc index c362aba98fd..92d57115adf 100644 --- a/regression/contracts/assigns-replace-ignored-return-value/test.desc +++ b/regression/contracts/assigns-replace-ignored-return-value/test.desc @@ -1,6 +1,6 @@ CORE main.c ---replace-call-with-contract bar --replace-call-with-contract baz --enforce-contract foo +--dfcc main --replace-call-with-contract bar --replace-call-with-contract baz --enforce-contract foo ^EXIT=0$ ^SIGNAL=0$ ^VERIFICATION SUCCESSFUL$ diff --git a/regression/contracts/assigns-replace-malloc-zero/main.c b/regression/contracts/assigns-replace-malloc-zero/main.c index 14c34366a18..1877cf0fa48 100644 --- a/regression/contracts/assigns-replace-malloc-zero/main.c +++ b/regression/contracts/assigns-replace-malloc-zero/main.c @@ -6,7 +6,7 @@ int foo(char *a, int size) // clang-format off __CPROVER_requires(0 <= size && size <= __CPROVER_max_malloc_size) __CPROVER_requires(a == NULL || __CPROVER_rw_ok(a, size)) -__CPROVER_assigns(__CPROVER_POINTER_OBJECT(a)) +__CPROVER_assigns(__CPROVER_whole_object(a)) __CPROVER_ensures( a && __CPROVER_return_value >= 0 ==> a[__CPROVER_return_value] == 0) // clang-format on diff --git a/regression/contracts/assigns-replace-malloc-zero/test.desc b/regression/contracts/assigns-replace-malloc-zero/test.desc index 6b265418f9b..2e410cec503 100644 --- a/regression/contracts/assigns-replace-malloc-zero/test.desc +++ b/regression/contracts/assigns-replace-malloc-zero/test.desc @@ -1,6 +1,6 @@ CORE main.c ---replace-call-with-contract foo _ --malloc-may-fail --malloc-fail-null +--dfcc main --replace-call-with-contract foo _ --malloc-may-fail --malloc-fail-null ^EXIT=0$ ^SIGNAL=0$ \[main\.assertion\.1\] line 35 expecting SUCCESS: SUCCESS$ diff --git a/regression/contracts/assigns-slice-targets/test-enforce.desc b/regression/contracts/assigns-slice-targets/test-enforce.desc index d7c221d1a86..e1df873e83e 100644 --- a/regression/contracts/assigns-slice-targets/test-enforce.desc +++ b/regression/contracts/assigns-slice-targets/test-enforce.desc @@ -1,6 +1,6 @@ CORE main-enforce.c ---enforce-contract foo +--dfcc main --enforce-contract foo ^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)0\] is assignable: SUCCESS$ ^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)1\] is assignable: SUCCESS$ ^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)2\] is assignable: SUCCESS$ diff --git a/regression/contracts/assigns-slice-targets/test-replace.desc b/regression/contracts/assigns-slice-targets/test-replace.desc index 1c7532841a1..b1eeafd252c 100644 --- a/regression/contracts/assigns-slice-targets/test-replace.desc +++ b/regression/contracts/assigns-slice-targets/test-replace.desc @@ -1,6 +1,6 @@ CORE main-replace.c ---replace-call-with-contract foo +--dfcc main --replace-call-with-contract foo ^\[main.assertion.\d+\].*assertion s.a == 0: SUCCESS$ ^\[main.assertion.\d+\].*assertion \(.*\)s.arr1\[\(.*\)0\] == 0: FAILURE$ ^\[main.assertion.\d+\].*assertion \(.*\)s.arr1\[\(.*\)1\] == 0: FAILURE$ diff --git a/regression/contracts/assigns_enforce_01/test.desc b/regression/contracts/assigns_enforce_01/test.desc index d3774ec5dfa..1dd64604f87 100644 --- a/regression/contracts/assigns_enforce_01/test.desc +++ b/regression/contracts/assigns_enforce_01/test.desc @@ -1,6 +1,6 @@ CORE main.c ---enforce-contract foo +--dfcc main --enforce-contract foo ^EXIT=0$ ^SIGNAL=0$ ^VERIFICATION SUCCESSFUL$ diff --git a/regression/contracts/assigns_enforce_02/test.desc b/regression/contracts/assigns_enforce_02/test.desc index 2abca0b8bd1..9b05008a350 100644 --- a/regression/contracts/assigns_enforce_02/test.desc +++ b/regression/contracts/assigns_enforce_02/test.desc @@ -1,7 +1,6 @@ CORE main.c ---enforce-contract foo -^\[foo.assigns.\d+\] line 3 Check that z is valid: SUCCESS$ +--dfcc main --enforce-contract foo ^\[foo.assigns.\d+\] line 6 Check that \*x is assignable: FAILURE$ ^EXIT=10$ ^SIGNAL=0$ diff --git a/regression/contracts/assigns_enforce_03/test.desc b/regression/contracts/assigns_enforce_03/test.desc index 4b342024cc9..39067aa46f6 100644 --- a/regression/contracts/assigns_enforce_03/test.desc +++ b/regression/contracts/assigns_enforce_03/test.desc @@ -1,15 +1,6 @@ CORE main.c ---enforce-contract f1 --enforce-contract f2 --enforce-contract f3 -^\[f1.assigns.\d+\] line 1 Check that \*x1 is valid: SUCCESS$ -^\[f1.assigns.\d+\] line 1 Check that \*y1 is valid: SUCCESS$ -^\[f1.assigns.\d+\] line 1 Check that \*z1 is valid: SUCCESS$ -^\[f2.assigns.\d+\] line 6 Check that \*x2 is valid: SUCCESS$ -^\[f2.assigns.\d+\] line 6 Check that \*y2 is valid: SUCCESS$ -^\[f2.assigns.\d+\] line 6 Check that \*z2 is valid: SUCCESS$ -^\[f3.assigns.\d+\] line 11 Check that \*x3 is valid: SUCCESS$ -^\[f3.assigns.\d+\] line 11 Check that \*y3 is valid: SUCCESS$ -^\[f3.assigns.\d+\] line 12 Check that \*z3 is valid: SUCCESS$ +--dfcc main --enforce-contract f1 ^\[f3.assigns.\d+\] line 14 Check that \*x3 is assignable: SUCCESS$ ^\[f3.assigns.\d+\] line 15 Check that \*y3 is assignable: SUCCESS$ ^\[f3.assigns.\d+\] line 16 Check that \*z3 is assignable: SUCCESS$ @@ -18,4 +9,5 @@ main.c ^SIGNAL=0$ -- -- -This test checks that verification succeeds when assigns clauses are respected through multiple function calls. +This test checks that verification succeeds when assigns clauses are respected +through multiple function calls. diff --git a/regression/contracts/assigns_enforce_04/test.desc b/regression/contracts/assigns_enforce_04/test.desc index 35a898dd382..dd4edf44d7c 100644 --- a/regression/contracts/assigns_enforce_04/test.desc +++ b/regression/contracts/assigns_enforce_04/test.desc @@ -1,9 +1,6 @@ CORE main.c ---enforce-contract f1 -^\[f1.assigns.\d+\] line 1 Check that \*x1 is valid: SUCCESS$ -^\[f1.assigns.\d+\] line 1 Check that \*y1 is valid: SUCCESS$ -^\[f1.assigns.\d+\] line 1 Check that \*z1 is valid: SUCCESS$ +--dfcc main --enforce-contract f1 ^\[f3.assigns.\d+\] line 13 Check that \*x3 is assignable: SUCCESS$ ^\[f3.assigns.\d+\] line 14 Check that \*y3 is assignable: SUCCESS$ ^\[f3.assigns.\d+\] line 15 Check that \*z3 is assignable: SUCCESS$ diff --git a/regression/contracts/assigns_enforce_05/test.desc b/regression/contracts/assigns_enforce_05/test.desc index 97352a948f2..4432da69532 100644 --- a/regression/contracts/assigns_enforce_05/test.desc +++ b/regression/contracts/assigns_enforce_05/test.desc @@ -1,6 +1,6 @@ CORE main.c ---enforce-contract f1 --enforce-contract f2 --enforce-contract f3 +--dfcc main --enforce-contract f1 ^EXIT=0$ ^SIGNAL=0$ ^VERIFICATION SUCCESSFUL$ diff --git a/regression/contracts/assigns_enforce_06/main.c b/regression/contracts/assigns_enforce_06/main.c index e4d12d140f6..5987494a45e 100644 --- a/regression/contracts/assigns_enforce_06/main.c +++ b/regression/contracts/assigns_enforce_06/main.c @@ -1,41 +1,44 @@ -void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1) +void f1(int *x, int *y, int *z) __CPROVER_assigns(*x, *y, *z) { - f2(x1, y1, z1); + f2(x, y, z); } -void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2) +void f2(int *x, int *y, int *z) __CPROVER_assigns(*x, *y, *z) { - f3(x2, y2, z2); + f3(x, y, z); } -void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*x3, *y3, *z3) +void f3(int *x, int *y, int *z) __CPROVER_assigns(*x, *y, *z) { - *x3 = *x3 + 1; - *y3 = *y3 + 1; - *z3 = *z3 + 1; + *x = *x + 1; + *y = *y + 1; + *z = *z + 1; } -int main() +void f(int *x, int *y, int *z) __CPROVER_assigns(*x, *y, *z) { - int p = 1; - int q = 2; - int r = 3; - for(int i = 0; i < 3; ++i) { if(i == 0) { - f1(&p, &q, &r); + f1(x, y, z); } if(i == 1) { - f2(&p, &q, &r); + f2(x, y, z); } if(i == 2) { - f3(&p, &q, &r); + f3(x, y, z); } } +} +int main() +{ + int p = 1; + int q = 2; + int r = 3; + f(&p, &q, &r); return 0; } diff --git a/regression/contracts/assigns_enforce_06/test.desc b/regression/contracts/assigns_enforce_06/test.desc index 255dd3a361c..db14528b092 100644 --- a/regression/contracts/assigns_enforce_06/test.desc +++ b/regression/contracts/assigns_enforce_06/test.desc @@ -1,9 +1,10 @@ CORE main.c ---enforce-contract f1 --enforce-contract f2 --enforce-contract f3 +--dfcc main --enforce-contract f ^EXIT=0$ ^SIGNAL=0$ ^VERIFICATION SUCCESSFUL$ -- -- -This test checks that verification succeeds when functions with assigns clauses are called from within a loop. +This test checks that verification succeeds when functions +are called from within a loop. diff --git a/regression/contracts/assigns_enforce_07/main.c b/regression/contracts/assigns_enforce_07/main.c index 384807e6218..4d513625106 100644 --- a/regression/contracts/assigns_enforce_07/main.c +++ b/regression/contracts/assigns_enforce_07/main.c @@ -1,41 +1,46 @@ -void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1) +void f1(int *x, int *y, int *z) __CPROVER_assigns(*x, *y) { - f2(x1, y1, z1); + *x = *x + 1; + *y = *y + 1; } -void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2) +void f2(int *x, int *y, int *z) __CPROVER_assigns(*x, *y) { - f3(x2, y2, z2); + *x = *x + 1; + *y = *y + 1; } -void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*y3, *z3) +void f3(int *x, int *y, int *z) __CPROVER_assigns(*x, *y, *z) { - *x3 = *x3 + 1; - *y3 = *y3 + 1; - *z3 = *z3 + 1; + *x = *x + 1; + *y = *y + 1; + *z = *z + 1; } -int main() +void f(int *x, int *y, int *z) __CPROVER_assigns(*x, *y) { - int p = 1; - int q = 2; - int r = 3; - for(int i = 0; i < 3; ++i) { if(i == 0) { - f1(&p, &q, &r); + f1(x, y, z); } if(i == 1) { - f2(&p, &q, &r); + f2(x, y, z); } if(i == 2) { - f3(&p, &q, &r); + f3(x, y, z); } } +} +int main() +{ + int p = 1; + int q = 2; + int r = 3; + f(&p, &q, &r); return 0; } diff --git a/regression/contracts/assigns_enforce_07/test.desc b/regression/contracts/assigns_enforce_07/test.desc index cdf7220a8a1..e9f2d4f52df 100644 --- a/regression/contracts/assigns_enforce_07/test.desc +++ b/regression/contracts/assigns_enforce_07/test.desc @@ -1,6 +1,6 @@ CORE main.c ---enforce-contract f1 --enforce-contract f2 --enforce-contract f3 +--dfcc main --enforce-contract f ^EXIT=10$ ^SIGNAL=0$ ^VERIFICATION FAILED$ diff --git a/regression/contracts/frees-clause-and-predicates/main.c b/regression/contracts/frees-clause-and-predicates/main.c index 71a1b84771d..74da4a8d4a7 100644 --- a/regression/contracts/frees-clause-and-predicates/main.c +++ b/regression/contracts/frees-clause-and-predicates/main.c @@ -1,36 +1,66 @@ +#include #include -// A function defining a conditionally freeable target -__CPROVER_freeable_t -foo_frees(char *arr, const size_t size, const size_t new_size) +// A function defining an assignable target +__CPROVER_assignable_t foo_assigns(char *arr, const size_t size) +{ + __CPROVER_object_upto(arr, size); +} + +// A function defining an freeable target +__CPROVER_freeable_t foo_frees(char *arr, const size_t size) { __CPROVER_freeable(arr); } -char *foo(char *arr, const size_t size, const size_t new_size) +bool is_freeable(void *ptr) +{ + bool is_dynamic_object = (ptr == 0) | __CPROVER_DYNAMIC_OBJECT(ptr); + bool has_offset_zero = (ptr == 0) | (__CPROVER_POINTER_OFFSET(ptr) == 0); + return is_dynamic_object & has_offset_zero; +} + +char *foo(char *ptr, const size_t size, const size_t new_size) // clang-format off -__CPROVER_requires(__CPROVER_is_freeable(arr)) -__CPROVER_assigns(__CPROVER_whole_object(arr)) -__CPROVER_frees(foo_frees(arr, size, new_size)) +__CPROVER_requires(__CPROVER_is_freeable(ptr)) +__CPROVER_assigns(foo_assigns(ptr, size)) +__CPROVER_frees(foo_frees(ptr, size)) __CPROVER_ensures( - (arr && new_size > size) ==> + (ptr && new_size > size) ==> __CPROVER_is_fresh(__CPROVER_return_value, new_size)) __CPROVER_ensures( - (arr && new_size > size) ==> - __CPROVER_is_freed(__CPROVER_old(arr))) + (ptr && new_size > size) ==> + __CPROVER_is_freed(ptr)) __CPROVER_ensures( - !(arr && new_size > size) ==> - __CPROVER_return_value == __CPROVER_old(arr)) + !(ptr && new_size > size) ==> + __CPROVER_return_value == __CPROVER_old(ptr)) // clang-format on { - if(arr && new_size > size) + // The harness allows to add a nondet offset to the pointer passed to foo. + // Proving this assertion shows that the __CPROVER_is_freeable(ptr) assumption + // is in effect as expected for the verification + __CPROVER_assert(is_freeable(ptr), "ptr is freeable"); + + if(ptr && new_size > size) { - free(arr); - return malloc(new_size); + free(ptr); + ptr = malloc(new_size); + + // write at some nondet i (should be always allowed since ptr is fresh) + size_t i; + if(i < new_size) + ptr[i] = 0; + + return ptr; } else { - return arr; + // write at some nondet i + size_t i; + if(i < size) + ptr[i] = 0; + + return ptr; } } @@ -38,6 +68,8 @@ int main() { size_t size; size_t new_size; + __CPROVER_assume(size < __CPROVER_max_malloc_size); + __CPROVER_assume(new_size < __CPROVER_max_malloc_size); char *arr = malloc(size); arr = foo(arr, size, new_size); return 0; diff --git a/regression/contracts/frees-clause-and-predicates/test.desc b/regression/contracts/frees-clause-and-predicates/test.desc index 770c3f319e6..13027907495 100644 --- a/regression/contracts/frees-clause-and-predicates/test.desc +++ b/regression/contracts/frees-clause-and-predicates/test.desc @@ -1,17 +1,13 @@ CORE main.c ---enforce-contract foo -^\[foo.postcondition.\d+\] line \d+ Check ensures clause: FAILURE$ -^VERIFICATION FAILED$ -^EXIT=10$ +-dfcc main --enforce-contract foo +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ ^SIGNAL=0$ -- -- -This test checks that the front end parses and typchecks correct uses of: +This test checks that using the -dffdfcc flag we can check contracts that use: - __CPROVER_freeable_t function calls as frees clause targets -- the predicate __CPROVER_freeable -- the predicate __CPROVER_is_freeable -- the predicate __CPROVER_is_freed - -The post condition of the contract is expected to fail because the predicates -have no interpretation in the back-end yet. +- the __CPROVER_freeable built-in +- the __CPROVER_is_freeable built-in predicate +- the __CPROVER_is_freed built-in predicate From 29ef0b09d57db934a1b8e820705cfbd125205e46 Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Fri, 2 Sep 2022 11:13:00 -0400 Subject: [PATCH 20/20] developer doc for contracts with dynamic frames --- .../contracts-requires-and-ensures.md | 136 - src/goto-instrument/contracts/doc/Doxyfile | 2683 +++++++++++++++++ .../contracts/doc}/contracts.md | 13 +- .../doc/developer/contracts-dev-arch.md | 48 + .../developer/contracts-dev-spec-codegen.md | 143 + ...ontracts-dev-spec-contract-checking-rec.md | 38 + .../contracts-dev-spec-contract-checking.md | 174 ++ ...contracts-dev-spec-contract-replacement.md | 101 + .../contracts-dev-spec-dfcc-instrument.md | 255 ++ .../contracts-dev-spec-dfcc-runtime.md | 179 ++ .../doc/developer/contracts-dev-spec-dfcc.md | 103 + .../developer/contracts-dev-spec-harness.md | 12 + .../developer/contracts-dev-spec-is-freed.md | 26 + .../developer/contracts-dev-spec-is-fresh.md | 18 + .../developer/contracts-dev-spec-reminder.md | 63 + .../contracts-dev-spec-spec-rewriting.md | 218 ++ .../contracts-dev-spec-transfo-params.md | 37 + .../doc/developer/contracts-dev-spec.md | 21 + .../contracts/doc/developer/contracts-dev.md | 9 + .../contracts/doc/user}/contracts-assigns.md | 41 +- .../contracts/doc/user/contracts-cli.md | 20 + .../doc/user}/contracts-decreases.md | 33 +- .../contracts/doc/user}/contracts-frees.md | 29 +- .../doc/user}/contracts-functions.md | 78 +- .../doc/user}/contracts-history-variables.md | 21 +- .../doc/user/contracts-loop-invariants.md | 29 +- .../contracts/doc/user}/contracts-loops.md | 41 +- .../doc/user}/contracts-memory-predicates.md | 37 +- .../doc/user}/contracts-quantifiers.md | 25 +- .../doc/user/contracts-requires-ensures.md | 191 ++ .../contracts/doc/user/contracts-user.md | 16 + 31 files changed, 4588 insertions(+), 250 deletions(-) delete mode 100644 doc/cprover-manual/contracts-requires-and-ensures.md create mode 100644 src/goto-instrument/contracts/doc/Doxyfile rename {doc/cprover-manual => src/goto-instrument/contracts/doc}/contracts.md (77%) create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev-arch.md create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev-spec-codegen.md create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev-spec-contract-checking-rec.md create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev-spec-contract-checking.md create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev-spec-contract-replacement.md create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev-spec-dfcc-instrument.md create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev-spec-dfcc-runtime.md create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev-spec-dfcc.md create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev-spec-harness.md create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev-spec-is-freed.md create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev-spec-is-fresh.md create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev-spec-reminder.md create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev-spec-spec-rewriting.md create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev-spec-transfo-params.md create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev-spec.md create mode 100644 src/goto-instrument/contracts/doc/developer/contracts-dev.md rename {doc/cprover-manual => src/goto-instrument/contracts/doc/user}/contracts-assigns.md (92%) create mode 100644 src/goto-instrument/contracts/doc/user/contracts-cli.md rename {doc/cprover-manual => src/goto-instrument/contracts/doc/user}/contracts-decreases.md (88%) rename {doc/cprover-manual => src/goto-instrument/contracts/doc/user}/contracts-frees.md (87%) rename {doc/cprover-manual => src/goto-instrument/contracts/doc/user}/contracts-functions.md (66%) rename {doc/cprover-manual => src/goto-instrument/contracts/doc/user}/contracts-history-variables.md (67%) rename doc/cprover-manual/contracts-invariants.md => src/goto-instrument/contracts/doc/user/contracts-loop-invariants.md (89%) rename {doc/cprover-manual => src/goto-instrument/contracts/doc/user}/contracts-loops.md (82%) rename {doc/cprover-manual => src/goto-instrument/contracts/doc/user}/contracts-memory-predicates.md (90%) rename {doc/cprover-manual => src/goto-instrument/contracts/doc/user}/contracts-quantifiers.md (82%) create mode 100644 src/goto-instrument/contracts/doc/user/contracts-requires-ensures.md create mode 100644 src/goto-instrument/contracts/doc/user/contracts-user.md diff --git a/doc/cprover-manual/contracts-requires-and-ensures.md b/doc/cprover-manual/contracts-requires-and-ensures.md deleted file mode 100644 index 1fc15dd9f5c..00000000000 --- a/doc/cprover-manual/contracts-requires-and-ensures.md +++ /dev/null @@ -1,136 +0,0 @@ -[CPROVER Manual TOC](../../) - -# Requires \& Ensures Clauses - - -### Syntax - -```c -__CPROVER_requires(bool cond) -``` - -A _requires_ clause specifies a precondition for a function, i.e., a property that must hold to properly execute the operation. 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. - -```c -__CPROVER_ensures(bool cond) -``` - -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. - - -### Parameters - -A _requires_ clause takes a Boolean expression over the arguments of -a function and/or global variables, including CBMC primitive functions (e.g., -[Memory Predicates](../../contracts/memory-predicates/)). Similarly, _ensures_ clauses also accept Boolean -expressions and CBMC primitives, but also [History Variables](../../contracts/history-variables/) and `__CPROVER_return_value`. - -**Important.** Developers may call functions inside _requires_ and _ensures_ -clauses to better write larger specifications (e.g., predicates). However, at -this point CBMC does not enforce such functions to be without side effects -(i.e., do not modify any global state). This will be checked in future -versions. - - -### 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. - -```c -int sum(const uint32_t a, const uint32_t b, uint32_t* out) -/* Precondition */ -__CPROVER_requires(__CPROVER_is_fresh(out, sizeof(*out))) -/* Postconditions */ -__CPROVER_ensures(__CPROVER_return_value == SUCCESS || __CPROVER_return_value == FAILURE) -__CPROVER_ensures((__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; -} -``` - -#### 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; -4. Calls the implementation of function *f*; -5. Asserts all postconditions described in the `__CPROVER_ensures` clauses. - -In our example, the `sum` function will be instrumented as follows: - -```c -/* Function Contract Enforcement */ -int sum(uint32_t a, uint32_t b, uint32_t* out) -{ - __CPROVER_assume(__CPROVER_is_fresh(out, sizeof(*out))); - - int return_value_sum = __CPROVER_contracts_original_sum(a, b, out); - - __CPROVER_assert(return_value_sum == SUCCESS || return_value_sum == FAILURE, "Check ensures clause"); - __CPROVER_assert((return_value_sum == SUCCESS) ==> (*out == (a + b)), "Check ensures clause"); - - return return_value_sum; -} -``` - -#### 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 Clause](../../contracts/assigns/) -for details); -3. Assumes all postconditions described in the `__CPROVER_ensures` clauses; - -In our example, consider that a function `foo` may call `sum`. - -```c -int foo() -{ - uint32_t a; - uint32_t b; - uint32_t out; - int rval = sum(a, b, &out); - if (rval == SUCCESS) - return out; - return rval; -} -``` - -CBMC will use the function contract in place of the function implementation -wherever the function is called. - -```c -int foo() -{ - uint32_t a; - uint32_t b; - uint32_t out; - - /* Function Contract Replacement */ - /* Precondition */ - __CPROVER_assert(__CPROVER_is_fresh(out, sizeof(*out)), "Check requires clause"); - - /* Postconditions */ - int return_value_sum = nondet_int(); - __CPROVER_assume(return_value_sum == SUCCESS || return_value_sum == FAILURE); - __CPROVER_assume((return_value_sum == SUCCESS) ==> (*out == (*a + *b))); - - int rval = return_value_sum; - if (rval == SUCCESS) - return out; - return rval; -} -``` diff --git a/src/goto-instrument/contracts/doc/Doxyfile b/src/goto-instrument/contracts/doc/Doxyfile new file mode 100644 index 00000000000..02b78eadb6a --- /dev/null +++ b/src/goto-instrument/contracts/doc/Doxyfile @@ -0,0 +1,2683 @@ +# Doxyfile 1.9.5 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "contracts" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = . + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# numer of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = SYSTEM + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = ../ ../../../ansi-c/library/cprover_contracts.c + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.cpp \ + *.h \ + *.md + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = */.svn/* \ + */.git/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = doc/assets + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = doc/assets + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = doc/contracts-mainpage.md + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. Default setting AUTO_LIGHT +# enables light output unless the user preference is dark output. Other options +# are DARK to always use dark mode, LIGHT to always use light mode, AUTO_DARK to +# default to dark mode unless the user prefers light mode, and TOGGLE to let the +# user toggle between dark and light mode via a button. +# Possible values are: LIGHT Always generate light output., DARK Always generate +# dark output., AUTO_LIGHT Automatically set the mode according to the user +# preference, use light mode if no preference is set (the default)., AUTO_DARK +# Automatically set the mode according to the user preference, use dark mode if +# no preference is set. and TOGGLE Allow to user to switch between light and +# dark mode via a button.. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = YES + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = YES + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /