From e22a788588569b7a52f5a12c6f11662c498614c5 Mon Sep 17 00:00:00 2001 From: Remi Delmas Date: Tue, 23 Aug 2022 15:30:32 -0400 Subject: [PATCH] CONTRACTS: dynamic frame condition checking for function contracts. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixes #7197 - Fixes #7198 - Fixes #7206 Overview --- The new features brought by the system are: - Allow to use C functions to specify reusable/parametric assigns clauses and frees clauses in the contracts language; - Allow function calls in pre- and post-conditions and check the absence of side-effects; - Checking contracts on recursive functions (by assuming preconditions and asserting post conditions on the top level call and asserting pre conditions and assuming post conditions on the recursive call) - The frame checks are fully dynamic and does not require inlining the program or unwinding loops a priori; - The method is compatible with deferred resolution of function pointers calls during symex; - All CPROVER library functions are supported; In addition: - The checks are implemented as C library `src/ansi-c/library/cprover_contracts.c`, the instrumentation insert calls to the library in the program; - Contract checking and contract replacement mostly share the same code; - The whole system's user documentation and developer documentation is written in the doxygen format and found in `src/goto-instrument/contracts/doc/`. Limitations: - loop contracts are not yet ported to the new framework; - deferred function pointer resolution is not yet implemented, user-specified function pointer restriction is still required; - havocing during contract replacement still relies on snapshots known to affect performance; - more work is performed by symbolic execution which can be felt on runtime; Method --- Let’s call `f_top`​ the top-level function against which the contract is being checked. We assume `f_top`​ is itself called from a `harness` function, which may call other functions besides `f_top`​. Let’s consider a function `f` that is either `f_top​` or one of the functions called (directly or indirectly) by `f_top`​. The goal is to verify that `f` writes only to memory locations that are part of the frame of the call to `f_top` but allowed by the contract or that were allocated after entering `f_top`​ and are hence not part of the frame of the call. To do that, we add ghost code to the program to build representations the following sets of memory locations as the program runs: (`contract_assigns`, `contract_frees`, `allocated`, `deallocated`) where: - `contract_assigns` is the set of memory locations specified in the *assigns clause* of the contract being checked - `contract_frees` is the set of pointers specified in the *frees clause* of the contract being checked - `allocated` is the set objects identifiers allocated on the stack or the heap since entering `f_top`​ - `deallocated` is the set of heap objects identifiers deallocated since entering `f_top`​ (allows verification that an object was freed as a post-condition of the contract) The instrumentation transforms all user-defined goto functions and library functions: ```c ret_t f(); ``` Into functions accepting an extra parameter that is a pointer to a dynamic representation of the sets introduced above: ```c ret_t f(, __CPROVER_contracts_write_set_ptr_t write_set); ``` Function bodies is instrumented so that when the write_set pointer is NULL no checks are performed, when it is not NULL the following checks are performed: - Assignments LHS are checked for inclusion in `contract_assigns` or `allocated`, - DECL objects are recorded in `allocated` - Dynamically allocated objects are recorded in `allocated` - DEAD objects or objects assigned to `__CPROVER_dead_object` are removed from `allocated` - dynamic objects deallocated with `__CPROVER_deallocate` are checked for inclusion in `contract_frees` or `allocated`, and recorded in `deallocated` (so that if needed we can check in post conditions that the function indeed deallocated the expected memory). - The current write set is propagated to all functions calls or function pointer calls. Since the write set is dynamically updated, a same function called at different call sites can see a different write set. Write sets are initialised from contracts descriptions and injected in function calls that are checked against contracts, or used to perform inclusion checks against write sets obtained from callers for functions that are replaced by a contract. Implementation --- The class `dfcc_utilst` provides methods to manipulate function symbols in the symbol table: - create fresh local symbols - create parameter symbols - create static symbols - clone and rename function symbols - add function parameters - rename function parameters The file `cprover_contracts.c` defines: - C types representing write sets - C functions to create/populate/release write sets - C functions to do write sets inclusion checks - C functions implementing built-in predicates `__CPROVER_is_freeable`, `__CPROVER_was_freed`, `__CPROVER_is_fresh` The class `dfcc_libraryt` provides a programmatic interface to load and use the library-defined types and functions and perform some optimisations like inlining or unwinding loops in library functions. The class `dfcc_contract_modet` is an enum representing the CHECK and REPLACE modes for contracts. The class `dfcc_is_fresht` Rewrites calls to `__CPROVER_is_fresh` into calls to the library implementation `__CPROVER_contracts_is_fresh`. The class `dfcc_is_freeablet` rewrites calls to `__CPROVER_is_freeable` into calls to the library implementation `__CPROVER_contracts_is_freeable` and rewrites calls to `__CPROVER_was_freed` into calls to the library implementation `__CPROVER_contracts_was_freed`. The class `goto_program_cfg_infot` extends `cfg_infot` and computes the set of program-locals from DECLs found in the instruction sequence. Contrary to the existing `function_cfg_infot` this new class does consider parameters symbols as locals. The class `dfcc_instrumentt` instruments GOTO functions with write-set checks. It also instruments proof harness functions and sub-sequences of GOTO instructions extracted from larger GOTO programs. The class `dfcc_spec_functionst` rewrites assigns clause and frees clause specification functions into functions that populate a given write-set, and can be checked for side-effects against a second write-set. The class `dfcc_contract_functionst` allows to translate the assigns clause (or frees clause) of a contract expressed in DSL style into GOTO a function describing the same set of targets as the clause. It also allows to generate a havoc function from an assigns clause, that havocs the targets described by the clause in a type-directed way. The class `dfcc_dsl_wrapper_programt` generates a sequence of GOTO instruction from a contract expressed in the DSL syntax. The instructions either encode a checked call to the function under verification (CHECK mode) or an abstraction derived from the contract (REPLACE mode). In CHECK 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 REPLACE 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. 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. The class `dfcc_contract_handlert` is an abstract interface for generating GOTO instructions from contracts for CHECK or REPLACE modes. It delegates the actual generation to `dfcc_dsl_wrapper_programt`. The class `dfcc_swap_and_wrapt`, given a `function_id`, a `contract_id`, a contract `mode`, and a `dfcc_contract_handlert` for the contract, swaps the body of the function to a function with a mangled name and 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 (CHECK or REPLACE). 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 (induction hypothesis for the recursive call). The `dfcct` 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. Regression Tests --- Regression tests are found in `regression/contracts-dfcc`. These tests are ported from `regression/contracts` and only contain tests for function contracts. --- .../contracts-requires-and-ensures.md | 136 -- doc/man/goto-instrument.1 | 6 + regression/CMakeLists.txt | 1 + regression/Makefile | 1 + regression/contracts-dfcc/CMakeLists.txt | 43 + regression/contracts-dfcc/Makefile | 42 + .../assigns-enforce-malloc-zero/main.c | 31 + .../assigns-enforce-malloc-zero/test.desc | 11 + .../assigns-local-composite/main.c | 104 ++ .../assigns-local-composite/test.desc | 12 + .../main.c | 37 + .../test.desc | 13 + .../assigns-replace-malloc-zero/main.c | 37 + .../assigns-replace-malloc-zero/test.desc | 11 + .../assigns-slice-targets/main-enforce.c | 67 + .../assigns-slice-targets/main-replace.c | 150 ++ .../assigns-slice-targets/test-enforce.desc | 38 + .../assigns-slice-targets/test-replace.desc | 57 + .../contracts-dfcc/assigns_enforce_01/main.c | 14 + .../assigns_enforce_01/test.desc | 13 + .../contracts-dfcc/assigns_enforce_02/main.c | 16 + .../assigns_enforce_02/test.desc | 10 + .../contracts-dfcc/assigns_enforce_03/main.c | 27 + .../assigns_enforce_03/test.desc | 13 + .../contracts-dfcc/assigns_enforce_04/main.c | 26 + .../assigns_enforce_04/test.desc | 12 + .../contracts-dfcc/assigns_enforce_05/main.c | 23 + .../assigns_enforce_05/test.desc | 10 + .../contracts-dfcc/assigns_enforce_06/main.c | 44 + .../assigns_enforce_06/test.desc | 10 + .../contracts-dfcc/assigns_enforce_07/main.c | 46 + .../assigns_enforce_07/test.desc | 9 + .../contracts-dfcc/assigns_enforce_08/main.c | 17 + .../assigns_enforce_08/test.desc | 11 + .../contracts-dfcc/assigns_enforce_09/main.c | 17 + .../assigns_enforce_09/test.desc | 9 + .../contracts-dfcc/assigns_enforce_10/main.c | 20 + .../assigns_enforce_10/test.desc | 9 + .../contracts-dfcc/assigns_enforce_11/main.c | 20 + .../assigns_enforce_11/test.desc | 9 + .../contracts-dfcc/assigns_enforce_12/main.c | 13 + .../assigns_enforce_12/test.desc | 9 + .../contracts-dfcc/assigns_enforce_13/main.c | 14 + .../assigns_enforce_13/test.desc | 9 + .../contracts-dfcc/assigns_enforce_14/main.c | 19 + .../assigns_enforce_14/test.desc | 13 + .../contracts-dfcc/assigns_enforce_15/main.c | 35 + .../assigns_enforce_15/test-baz.desc | 11 + .../assigns_enforce_15/test-foo.desc | 10 + .../assigns_enforce_15/test-qux.desc | 11 + .../contracts-dfcc/assigns_enforce_16/main.c | 16 + .../assigns_enforce_16/test.desc | 10 + .../contracts-dfcc/assigns_enforce_17/main.c | 17 + .../assigns_enforce_17/test.desc | 11 + .../contracts-dfcc/assigns_enforce_18/main.c | 43 + .../assigns_enforce_18/test-bar.desc | 13 + .../assigns_enforce_18/test-baz.desc | 14 + .../assigns_enforce_18/test-foo.desc | 14 + .../assigns_enforce_19_a/main.c | 43 + .../assigns_enforce_19_a/test.desc | 14 + .../assigns_enforce_19_b/main.c | 20 + .../assigns_enforce_19_b/test.desc | 17 + .../contracts-dfcc/assigns_enforce_20/main.c | 19 + .../assigns_enforce_20/test.desc | 10 + .../contracts-dfcc/assigns_enforce_21/main.c | 42 + .../assigns_enforce_21/test.desc | 10 + .../contracts-dfcc/assigns_enforce_23/main.c | 41 + .../assigns_enforce_23/test.desc | 9 + .../assigns_enforce_address_of/main.c | 16 + .../assigns_enforce_address_of/test.desc | 10 + .../assigns_enforce_arrays_01/main.c | 16 + .../assigns_enforce_arrays_01/test.desc | 10 + .../assigns_enforce_arrays_02/main.c | 25 + .../assigns_enforce_arrays_02/test-f1.desc | 12 + .../assigns_enforce_arrays_02/test-f2.desc | 12 + .../assigns_enforce_arrays_03/main.c | 12 + .../assigns_enforce_arrays_03/test.desc | 11 + .../assigns_enforce_arrays_04/main.c | 19 + .../assigns_enforce_arrays_04/test.desc | 11 + .../assigns_enforce_arrays_05/main.c | 23 + .../assigns_enforce_arrays_05/test.desc | 10 + .../assigns_enforce_arrays_10/main.c | 24 + .../assigns_enforce_arrays_10/test.desc | 11 + .../main.c | 33 + .../test.desc | 17 + .../assigns_enforce_conditional_lvalue/main.c | 29 + .../test.desc | 17 + .../main.c | 29 + .../test.desc | 17 + .../main.c | 21 + .../test.desc | 10 + .../main.c | 21 + .../test.desc | 10 + .../main.c | 35 + .../test.desc | 17 + .../main.c | 34 + .../test.desc | 16 + .../main.c | 20 + .../test.desc | 10 + .../main.c | 16 + .../test.desc | 10 + .../main.c | 16 + .../test.desc | 10 + .../main.c | 16 + .../test.desc | 10 + .../main.c | 16 + .../test.desc | 10 + .../assigns_enforce_conditional_unions/main.c | 118 ++ .../test.desc | 17 + .../main.c | 15 + .../test.desc | 10 + .../main.c | 16 + .../test.desc | 10 + .../main.c | 31 + .../test.desc | 12 + .../main.c | 33 + .../test.desc | 21 + .../assigns_enforce_free_dead/main.c | 44 + .../assigns_enforce_free_dead/test.desc | 14 + .../assigns_enforce_function_calls/main.c | 16 + .../assigns_enforce_function_calls/test.desc | 10 + .../main.c | 17 + .../test.desc | 10 + .../header.h | 12 + .../main.c | 9 + .../test.desc | 13 + .../utility.h | 40 + .../assigns_enforce_havoc_object/main.c | 59 + .../assigns_enforce_havoc_object/test.desc | 10 + .../assigns_enforce_literal/main.c | 16 + .../assigns_enforce_literal/test.desc | 10 + .../assigns_enforce_malloc_01/main.c | 15 + .../assigns_enforce_malloc_01/test.desc | 12 + .../assigns_enforce_malloc_02/main.c | 23 + .../assigns_enforce_malloc_02/test.desc | 14 + .../assigns_enforce_malloc_03/main.c | 18 + .../assigns_enforce_malloc_03/test.desc | 11 + .../assigns_enforce_multi_file_01/header.h | 22 + .../assigns_enforce_multi_file_01/main.c | 11 + .../assigns_enforce_multi_file_01/test.desc | 10 + .../assigns_enforce_multi_file_02/header.h | 23 + .../assigns_enforce_multi_file_02/main.c | 10 + .../assigns_enforce_multi_file_02/test.desc | 10 + .../assigns_enforce_object_wrong_args/main.c | 15 + .../test.desc | 10 + .../assigns_enforce_offsets_1/main.c | 17 + .../assigns_enforce_offsets_1/test.desc | 10 + .../assigns_enforce_offsets_2/main.c | 17 + .../assigns_enforce_offsets_2/test.desc | 14 + .../assigns_enforce_offsets_3/main.c | 20 + .../assigns_enforce_offsets_3/test.desc | 9 + .../assigns_enforce_offsets_4/main.c | 20 + .../assigns_enforce_offsets_4/test.desc | 12 + .../assigns_enforce_scoping_01/main.c | 18 + .../assigns_enforce_scoping_01/test.desc | 10 + .../assigns_enforce_scoping_02/main.c | 20 + .../assigns_enforce_scoping_02/test.desc | 10 + .../assigns_enforce_side_effects_1/main.c | 26 + .../assigns_enforce_side_effects_1/test.desc | 12 + .../assigns_enforce_side_effects_2/main.c | 17 + .../assigns_enforce_side_effects_2/test.desc | 10 + .../assigns_enforce_side_effects_3/main.c | 17 + .../assigns_enforce_side_effects_3/test.desc | 10 + .../assigns_enforce_statics/main.c | 20 + .../assigns_enforce_statics/test.desc | 13 + .../assigns_enforce_structs_01/main.c | 22 + .../assigns_enforce_structs_01/test.desc | 13 + .../assigns_enforce_structs_02/main.c | 29 + .../assigns_enforce_structs_02/test.desc | 13 + .../assigns_enforce_structs_03/main.c | 29 + .../assigns_enforce_structs_03/test.desc | 14 + .../assigns_enforce_structs_04/main.c | 41 + .../assigns_enforce_structs_04/test-f1.desc | 11 + .../assigns_enforce_structs_04/test-f2.desc | 11 + .../assigns_enforce_structs_04/test-f3.desc | 11 + .../assigns_enforce_structs_04/test-f4.desc | 10 + .../assigns_enforce_structs_05/main.c | 27 + .../assigns_enforce_structs_05/test.desc | 16 + .../assigns_enforce_structs_06/main.c | 46 + .../assigns_enforce_structs_06/test-f1.desc | 15 + .../assigns_enforce_structs_06/test-f2.desc | 13 + .../assigns_enforce_structs_06/test-f3.desc | 13 + .../assigns_enforce_structs_07/main.c | 43 + .../assigns_enforce_structs_07/test-f1.desc | 15 + .../assigns_enforce_structs_07/test-f2.desc | 15 + .../assigns_enforce_structs_08/main.c | 48 + .../assigns_enforce_structs_08/test-f1.desc | 17 + .../assigns_enforce_structs_08/test-f2.desc | 17 + .../assigns_enforce_subfunction_calls/main.c | 27 + .../test.desc | 10 + .../assigns_function_pointer/main.c | 38 + .../assigns_function_pointer/test.desc | 13 + .../assigns_repeated_ignored/main.c | 13 + .../assigns_repeated_ignored/test.desc | 10 + .../contracts-dfcc/assigns_replace_01/main.c | 15 + .../assigns_replace_01/test.desc | 11 + .../contracts-dfcc/assigns_replace_02/main.c | 16 + .../assigns_replace_02/test.desc | 9 + .../contracts-dfcc/assigns_replace_03/main.c | 18 + .../assigns_replace_03/test.desc | 9 + .../contracts-dfcc/assigns_replace_04/main.c | 34 + .../assigns_replace_04/test.desc | 16 + .../contracts-dfcc/assigns_replace_05/main.c | 34 + .../assigns_replace_05/test.desc | 16 + .../contracts-dfcc/assigns_replace_06/main.c | 32 + .../assigns_replace_06/test.desc | 19 + .../contracts-dfcc/assigns_replace_07/main.c | 24 + .../assigns_replace_07/test.desc | 13 + .../contracts-dfcc/assigns_replace_08/main.c | 20 + .../assigns_replace_08/test.desc | 11 + .../contracts-dfcc/assigns_replace_09/main.c | 20 + .../assigns_replace_09/test.desc | 11 + .../main.c | 74 + .../test.desc | 19 + .../enforce.desc | 9 + .../main_enforce.c | 9 + .../main_replace.c | 8 + .../replace.desc | 13 + .../vect.h | 51 + .../enforce.desc | 9 + .../main_enforce.c | 9 + .../main_replace.c | 8 + .../replace.desc | 12 + .../vect.h | 51 + .../main.c | 11 + .../test.desc | 9 + .../main.c | 33 + .../test.desc | 19 + .../assigns_type_checking_valid_cases/main.c | 109 ++ .../test-foo1.desc | 8 + .../test-foo10.desc | 10 + .../test-foo2.desc | 8 + .../test-foo3.desc | 9 + .../test-foo4.desc | 10 + .../test-foo5.desc | 8 + .../test-foo6.desc | 10 + .../test-foo7.desc | 10 + .../test-foo8.desc | 18 + .../test-foo9.desc | 8 + .../assigns_validity_pointer_01/main.c | 36 + .../assigns_validity_pointer_01/test.desc | 25 + .../assigns_validity_pointer_02/main.c | 35 + .../assigns_validity_pointer_02/test.desc | 17 + .../assigns_validity_pointer_03/main.c | 33 + .../assigns_validity_pointer_03/test.desc | 28 + .../assigns_validity_pointer_04/main.c | 34 + .../assigns_validity_pointer_04/test.desc | 21 + regression/contracts-dfcc/chain.sh | 45 + .../contracts_with_function_pointers/main.c | 30 + .../test.desc | 12 + .../cprover-assignable-fail/main.c | 29 + .../cprover-assignable-fail/test.desc | 22 + .../cprover-assignable-pass/main.c | 25 + .../cprover-assignable-pass/test.desc | 21 + .../embedded_contract_fail_01/main.c | 18 + .../embedded_contract_fail_01/test.desc | 12 + .../embedded_contract_fail_02/main.c | 18 + .../embedded_contract_fail_02/test.desc | 12 + .../enforce.desc | 10 + .../enforce-replace-unknown-function/main.c | 4 + .../replace.desc | 10 + regression/contracts-dfcc/entry_point/main.c | 9 + .../contracts-dfcc/entry_point/test.desc | 10 + .../frees-clause-and-predicates-fail/main.c | 45 + .../test.desc | 10 + .../frees-clause-and-predicates-fail2/main.c | 44 + .../test.desc | 11 + .../main.c | 13 + .../test.desc | 10 + .../main.c | 15 + .../test.desc | 10 + .../frees-clause-and-predicates/main.c | 76 + .../frees-clause-and-predicates/test.desc | 16 + .../function-calls-01-enforce-failure/main.c | 19 + .../test.desc | 11 + .../function-calls-01-enforce-success/main.c | 19 + .../test.desc | 12 + .../function-calls-01-replace-failure/main.c | 26 + .../test.desc | 14 + .../function-calls-01-replace-success/main.c | 22 + .../test.desc | 12 + .../function-calls-02-failure/main.c | 30 + .../test-enf-f-repl-g.desc | 14 + .../function-calls-02-failure/test-enf-f.desc | 12 + .../function-calls-02-failure/test-enf-g.desc | 10 + .../function-calls-02-success/main.c | 29 + .../test-enf-f-repl-g.desc | 10 + .../function-calls-02-success/test-enf-f.desc | 10 + .../function-calls-02-success/test-enf-g.desc | 9 + .../function-calls-03-direct-recursion/main.c | 17 + .../test-norec.desc | 12 + .../test-rec.desc | 18 + .../even_odd.h | 27 + .../main_even.c | 7 + .../main_odd.c | 7 + .../test-enf-even-repl-odd.desc | 12 + .../test-enf-even.desc | 11 + .../test-enf-odd-repl-even.desc | 11 + .../test-enf-odd.desc | 11 + .../test-enf-rec-even.desc | 15 + .../test-enf-rec-odd.desc | 14 + .../even_odd.h | 17 + .../main_even.c | 8 + .../main_odd.c | 8 + .../main_unwind.c | 9 + .../test-enf-even-repl-odd.desc | 14 + .../test-enf-even.desc | 11 + .../test-enf-odd-repl-even.desc | 13 + .../test-enf-odd.desc | 11 + .../test-enf-rec-even.desc | 14 + .../test-enf-rec-odd.desc | 14 + .../test-unwind.desc | 10 + .../main.c | 29 + .../test.desc | 12 + .../main.c | 27 + .../test.desc | 11 + .../main.c | 23 + .../test-replace.desc | 11 + .../test-unwind.desc | 13 + .../main.c | 23 + .../test.desc | 12 + .../function-pointer-contracts-enforce/main.c | 65 + .../test-manual-swap.desc | 22 + .../test.desc | 22 + .../function-pointer-contracts-replace/main.c | 39 + .../test.desc | 15 + .../contracts-dfcc/function_apply_01/main.c | 19 + .../function_apply_01/test.desc | 12 + .../contracts-dfcc/function_check_01/main.c | 33 + .../function_check_01/test.desc | 9 + .../contracts-dfcc/function_check_02/main.c | 36 + .../function_check_02/test.desc | 10 + .../contracts-dfcc/function_check_03/main.c | 31 + .../function_check_03/test.desc | 10 + .../contracts-dfcc/function_check_04/main.c | 18 + .../function_check_04/test.desc | 13 + .../contracts-dfcc/function_check_05/main.c | 25 + .../function_check_05/test.desc | 12 + .../function_check_mem_01/main.c | 39 + .../function_check_mem_01/test.desc | 10 + .../function_loop_history_ensures_fail/main.c | 13 + .../test.desc | 10 + .../main.c | 13 + .../test.desc | 10 + .../function_no_apply_01/main.c | 19 + .../function_no_apply_01/test.desc | 12 + regression/contracts-dfcc/havoc-static/main.c | 20 + .../havoc-static/test-exclude.desc | 12 + .../contracts-dfcc/havoc-static/test.desc | 13 + .../contracts-dfcc/history-constant/main.c | 15 + .../contracts-dfcc/history-constant/test.desc | 10 + .../history-pointer-both-01/main.c | 20 + .../history-pointer-both-01/test.desc | 12 + .../history-pointer-enforce-01/main.c | 13 + .../history-pointer-enforce-01/test.desc | 13 + .../history-pointer-enforce-02/main.c | 13 + .../history-pointer-enforce-02/test.desc | 13 + .../history-pointer-enforce-03/main.c | 16 + .../history-pointer-enforce-03/test.desc | 14 + .../history-pointer-enforce-04/main.c | 15 + .../history-pointer-enforce-04/test.desc | 14 + .../history-pointer-enforce-05/main.c | 14 + .../history-pointer-enforce-05/test.desc | 14 + .../history-pointer-enforce-06/main.c | 16 + .../history-pointer-enforce-06/test.desc | 12 + .../history-pointer-enforce-07/main.c | 13 + .../history-pointer-enforce-07/test.desc | 14 + .../history-pointer-enforce-08/main.c | 24 + .../history-pointer-enforce-08/test.desc | 13 + .../history-pointer-enforce-09/main.c | 23 + .../history-pointer-enforce-09/test.desc | 13 + .../history-pointer-enforce-10/main.c | 50 + .../history-pointer-enforce-10/test-bar.desc | 17 + .../history-pointer-enforce-10/test-baz.desc | 16 + .../history-pointer-enforce-10/test-foo.desc | 18 + .../history-pointer-enforce-11/main.c | 23 + .../history-pointer-enforce-11/test.desc | 13 + .../history-pointer-replace-01/main.c | 19 + .../history-pointer-replace-01/test.desc | 16 + .../history-pointer-replace-02/main.c | 16 + .../history-pointer-replace-02/test.desc | 14 + .../history-pointer-replace-03/main.c | 17 + .../history-pointer-replace-03/test.desc | 11 + .../history-pointer-replace-04/main.c | 24 + .../history-pointer-replace-04/test.desc | 14 + .../contracts-dfcc/history-typecast/main.c | 23 + .../contracts-dfcc/history-typecast/test.desc | 10 + .../ignored_return_value/main.c | 14 + .../ignored_return_value/test.desc | 12 + .../is_fresh_indirect_calls/main.c | 43 + .../is_fresh_indirect_calls/test.desc | 23 + .../is_unique_01_replace/main.c | 84 + .../is_unique_01_replace/test.desc | 13 + .../contracts-dfcc/loop-freeness-check/main.c | 16 + .../loop-freeness-check/test.desc | 13 + .../main-contract-after-declaration.c | 28 + .../main-contract-after-definition.c | 26 + .../main-contract-incomplete.c | 22 + .../main-contract-signature-conflict.c | 30 + .../main-definition-after-contract.c | 26 + .../named-contracts/main-no-definition.c | 28 + .../test-contract-after-declaration.desc | 10 + .../test-contract-after-definition.desc | 10 + .../test-contract-incomplete.desc | 10 + .../test-contract-signature-conflict.desc | 10 + .../test-definition-after-contract.desc | 11 + .../named-contracts/test-no-definition.desc | 10 + .../contracts-dfcc/no_redudant_checks/main.c | 9 + .../no_redudant_checks/test.desc | 34 + .../quantifiers-exists-both-enforce/main.c | 20 + .../quantifiers-exists-both-enforce/test.desc | 16 + .../quantifiers-exists-both-replace/main.c | 42 + .../quantifiers-exists-both-replace/test.desc | 16 + .../quantifiers-exists-ensures-enforce/main.c | 52 + .../test-f1.desc | 15 + .../test-f2.desc | 15 + .../quantifiers-exists-ensures-replace/main.c | 41 + .../test.desc | 14 + .../main.c | 55 + .../test.desc | 14 + .../main.c | 34 + .../test.desc | 17 + .../quantifiers-forall-both-enforce/main.c | 20 + .../quantifiers-forall-both-enforce/test.desc | 16 + .../quantifiers-forall-both-replace/main.c | 38 + .../quantifiers-forall-both-replace/test.desc | 16 + .../quantifiers-forall-ensures-enforce/main.c | 46 + .../test.desc | 16 + .../quantifiers-forall-ensures-replace/main.c | 30 + .../test.desc | 16 + .../main.c | 32 + .../test.desc | 16 + .../main.c | 33 + .../test.desc | 14 + .../quantifiers-nested-01/main.c | 32 + .../quantifiers-nested-01/test.desc | 11 + .../quantifiers-nested-02/main.c | 23 + .../quantifiers-nested-02/test.desc | 11 + .../quantifiers-nested-03/main.c | 31 + .../quantifiers-nested-03/test.desc | 11 + .../quantifiers-nested-04/main.c | 26 + .../quantifiers-nested-04/test.desc | 11 + .../quantifiers-nested-05/main.c | 21 + .../quantifiers-nested-05/test.desc | 11 + .../quantifiers-nested-06/main.c | 38 + .../quantifiers-nested-06/test.desc | 11 + .../main.c | 11 + .../test.desc | 11 + .../main.c | 11 + .../test.desc | 11 + .../main.c | 10 + .../test.desc | 11 + .../main.c | 10 + .../test.desc | 11 + .../replace-nondet-return-value/main.c | 23 + .../replace-nondet-return-value/test.desc | 20 + .../test_aliasing_enforce/main.c | 34 + .../test_aliasing_enforce/test.desc | 15 + .../test_aliasing_ensure/main.c | 35 + .../test_aliasing_ensure/test.desc | 15 + .../test_aliasing_ensure_indirect/foo_bar.h | 25 + .../test_aliasing_ensure_indirect/main_bar.c | 8 + .../test_aliasing_ensure_indirect/main_foo.c | 8 + .../test-bar.desc | 14 + .../test-foo.desc | 19 + .../test_aliasing_replace/main.c | 32 + .../test_aliasing_replace/test.desc | 12 + .../test_array_memory_enforce/main.c | 42 + .../test_array_memory_enforce/test.desc | 16 + .../test_array_memory_replace/main.c | 46 + .../test_array_memory_replace/test.desc | 12 + .../main.c | 45 + .../test.desc | 13 + .../main.c | 23 + .../test.desc | 14 + .../main.c | 30 + .../test.desc | 15 + .../main.c | 20 + .../test.desc | 13 + .../test_is_fresh_enforce_ensures_pass/main.c | 30 + .../test.desc | 16 + .../main.c | 24 + .../test.desc | 16 + .../test_is_fresh_replace_ensures_pass/main.c | 28 + .../test-enforce.desc | 16 + .../test-replace.desc | 13 + .../main.c | 20 + .../test.desc | 11 + .../main.c | 14 + .../test.desc | 11 + .../main.c | 18 + .../test.desc | 11 + .../test_possibly_aliased_arguments/main.c | 26 + .../test_possibly_aliased_arguments/test.desc | 11 + .../test_scalar_memory_enforce/main.c | 37 + .../test_scalar_memory_enforce/test.desc | 12 + .../test_scalar_memory_replace/main.c | 41 + .../test_scalar_memory_replace/test.desc | 13 + .../contracts-dfcc/test_struct_enforce/main.c | 30 + .../test_struct_enforce/test.desc | 14 + .../test_struct_member_enforce/main.c | 30 + .../test_struct_member_enforce/test.desc | 13 + .../contracts-dfcc/test_struct_replace/main.c | 35 + .../test_struct_replace/test.desc | 13 + .../trivial_contract_enforce/main.c | 14 + .../trivial_contract_enforce/test.desc | 12 + .../trivial_contract_replace/main.c | 13 + .../trivial_contract_replace/test.desc | 12 + .../main.c | 10 + .../test.desc | 11 + .../typed_target_pointer/main.c | 13 + .../typed_target_pointer/test.desc | 14 + .../contracts-dfcc/used_return_value/main.c | 17 + .../used_return_value/test.desc | 8 + src/ansi-c/library/cprover_contracts.c | 1454 +++++++++++++++++ src/doxyfile | 1 + src/goto-instrument/Makefile | 11 + src/goto-instrument/contracts/cfg_info.h | 41 + src/goto-instrument/contracts/contracts.cpp | 189 +-- src/goto-instrument/contracts/contracts.h | 29 - .../contracts/doc}/contracts.md | 14 +- .../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 | 254 +++ .../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 | 22 + .../developer/contracts-dev-spec-reminder.md | 63 + .../contracts-dev-spec-spec-rewriting.md | 218 +++ .../contracts-dev-spec-transform-params.md | 26 + .../doc/developer/contracts-dev-spec.md | 21 + .../contracts/doc/developer/contracts-dev.md | 9 + .../contracts/doc/user}/contracts-assigns.md | 57 +- .../contracts/doc/user/contracts-cli.md | 28 + .../doc/user}/contracts-decreases.md | 33 +- .../contracts/doc/user}/contracts-frees.md | 24 +- .../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 | 42 +- .../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 + .../contracts/dynamic-frames/dfcc.cpp | 464 ++++++ .../contracts/dynamic-frames/dfcc.h | 291 ++++ .../dfcc_contract_functions.cpp | 411 +++++ .../dynamic-frames/dfcc_contract_functions.h | 160 ++ .../dynamic-frames/dfcc_contract_handler.cpp | 193 +++ .../dynamic-frames/dfcc_contract_handler.h | 148 ++ .../dynamic-frames/dfcc_contract_mode.h | 23 + .../dynamic-frames/dfcc_instrument.cpp | 1227 ++++++++++++++ .../dynamic-frames/dfcc_instrument.h | 357 ++++ .../dynamic-frames/dfcc_is_freeable.cpp | 82 + .../dynamic-frames/dfcc_is_freeable.h | 56 + .../dynamic-frames/dfcc_is_fresh.cpp | 69 + .../contracts/dynamic-frames/dfcc_is_fresh.h | 56 + .../contracts/dynamic-frames/dfcc_library.cpp | 544 ++++++ .../contracts/dynamic-frames/dfcc_library.h | 279 ++++ .../dynamic-frames/dfcc_spec_functions.cpp | 418 +++++ .../dynamic-frames/dfcc_spec_functions.h | 180 ++ .../dynamic-frames/dfcc_swap_and_wrap.cpp | 314 ++++ .../dynamic-frames/dfcc_swap_and_wrap.h | 121 ++ .../contracts/dynamic-frames/dfcc_utils.cpp | 539 ++++++ .../contracts/dynamic-frames/dfcc_utils.h | 236 +++ .../dynamic-frames/dfcc_wrapper_program.cpp | 1099 +++++++++++++ .../dynamic-frames/dfcc_wrapper_program.h | 281 ++++ .../dynamic-frames/module_dependencies.txt | 8 + src/goto-instrument/contracts/utils.cpp | 177 +- src/goto-instrument/contracts/utils.h | 28 + .../goto_instrument_parse_options.cpp | 69 +- .../goto_instrument_parse_options.h | 3 + 577 files changed, 21303 insertions(+), 476 deletions(-) delete mode 100644 doc/cprover-manual/contracts-requires-and-ensures.md create mode 100644 regression/contracts-dfcc/CMakeLists.txt create mode 100644 regression/contracts-dfcc/Makefile create mode 100644 regression/contracts-dfcc/assigns-enforce-malloc-zero/main.c create mode 100644 regression/contracts-dfcc/assigns-enforce-malloc-zero/test.desc create mode 100644 regression/contracts-dfcc/assigns-local-composite/main.c create mode 100644 regression/contracts-dfcc/assigns-local-composite/test.desc create mode 100644 regression/contracts-dfcc/assigns-replace-ignored-return-value/main.c create mode 100644 regression/contracts-dfcc/assigns-replace-ignored-return-value/test.desc create mode 100644 regression/contracts-dfcc/assigns-replace-malloc-zero/main.c create mode 100644 regression/contracts-dfcc/assigns-replace-malloc-zero/test.desc create mode 100644 regression/contracts-dfcc/assigns-slice-targets/main-enforce.c create mode 100644 regression/contracts-dfcc/assigns-slice-targets/main-replace.c create mode 100644 regression/contracts-dfcc/assigns-slice-targets/test-enforce.desc create mode 100644 regression/contracts-dfcc/assigns-slice-targets/test-replace.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_01/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_01/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_02/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_02/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_03/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_03/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_04/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_04/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_05/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_05/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_06/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_06/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_07/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_07/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_08/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_08/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_09/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_09/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_10/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_10/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_11/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_11/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_12/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_12/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_13/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_13/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_14/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_14/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_15/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_15/test-baz.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_15/test-foo.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_15/test-qux.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_16/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_16/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_17/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_17/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_18/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_18/test-bar.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_18/test-baz.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_18/test-foo.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_19_a/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_19_a/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_19_b/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_19_b/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_20/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_20/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_21/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_21/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_23/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_23/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_address_of/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_address_of/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_arrays_01/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_arrays_01/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_arrays_02/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_arrays_02/test-f1.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_arrays_02/test-f2.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_arrays_03/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_arrays_03/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_arrays_04/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_arrays_04/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_arrays_05/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_arrays_05/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_arrays_10/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_arrays_10/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_function_call_condition/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_function_call_condition/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_lvalue/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_lvalue/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_lvalue_list/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_lvalue_list/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target_list/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target_list/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_pointer_object/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_pointer_object/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_pointer_object_list/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_pointer_object_list/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_side_effect_condition/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_side_effect_condition/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target_list/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target_list/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_ternary_target/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_ternary_target/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_ternary_target_list/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_ternary_target_list/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_unions/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_unions/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_void_target/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_void_target/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_void_target_list/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_conditional_void_target_list/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_detect_local_statics/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_detect_local_statics/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_detect_replaced_local_statics/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_detect_replaced_local_statics/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_free_dead/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_free_dead/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_function_calls/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_function_calls/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_function_calls_ignored/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_function_calls_ignored/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_functions_in_contracts/header.h create mode 100644 regression/contracts-dfcc/assigns_enforce_functions_in_contracts/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_functions_in_contracts/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_functions_in_contracts/utility.h create mode 100644 regression/contracts-dfcc/assigns_enforce_havoc_object/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_havoc_object/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_literal/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_literal/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_malloc_01/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_malloc_01/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_malloc_02/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_malloc_02/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_malloc_03/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_malloc_03/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_multi_file_01/header.h create mode 100644 regression/contracts-dfcc/assigns_enforce_multi_file_01/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_multi_file_01/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_multi_file_02/header.h create mode 100644 regression/contracts-dfcc/assigns_enforce_multi_file_02/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_multi_file_02/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_object_wrong_args/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_object_wrong_args/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_offsets_1/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_offsets_1/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_offsets_2/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_offsets_2/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_offsets_3/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_offsets_3/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_offsets_4/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_offsets_4/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_scoping_01/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_scoping_01/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_scoping_02/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_scoping_02/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_side_effects_1/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_side_effects_1/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_side_effects_2/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_side_effects_2/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_side_effects_3/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_side_effects_3/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_statics/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_statics/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_01/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_01/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_02/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_02/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_03/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_03/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_04/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_04/test-f1.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_04/test-f2.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_04/test-f3.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_04/test-f4.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_05/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_05/test.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_06/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_06/test-f1.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_06/test-f2.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_06/test-f3.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_07/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_07/test-f1.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_07/test-f2.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_08/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_08/test-f1.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_structs_08/test-f2.desc create mode 100644 regression/contracts-dfcc/assigns_enforce_subfunction_calls/main.c create mode 100644 regression/contracts-dfcc/assigns_enforce_subfunction_calls/test.desc create mode 100644 regression/contracts-dfcc/assigns_function_pointer/main.c create mode 100644 regression/contracts-dfcc/assigns_function_pointer/test.desc create mode 100644 regression/contracts-dfcc/assigns_repeated_ignored/main.c create mode 100644 regression/contracts-dfcc/assigns_repeated_ignored/test.desc create mode 100644 regression/contracts-dfcc/assigns_replace_01/main.c create mode 100644 regression/contracts-dfcc/assigns_replace_01/test.desc create mode 100644 regression/contracts-dfcc/assigns_replace_02/main.c create mode 100644 regression/contracts-dfcc/assigns_replace_02/test.desc create mode 100644 regression/contracts-dfcc/assigns_replace_03/main.c create mode 100644 regression/contracts-dfcc/assigns_replace_03/test.desc create mode 100644 regression/contracts-dfcc/assigns_replace_04/main.c create mode 100644 regression/contracts-dfcc/assigns_replace_04/test.desc create mode 100644 regression/contracts-dfcc/assigns_replace_05/main.c create mode 100644 regression/contracts-dfcc/assigns_replace_05/test.desc create mode 100644 regression/contracts-dfcc/assigns_replace_06/main.c create mode 100644 regression/contracts-dfcc/assigns_replace_06/test.desc create mode 100644 regression/contracts-dfcc/assigns_replace_07/main.c create mode 100644 regression/contracts-dfcc/assigns_replace_07/test.desc create mode 100644 regression/contracts-dfcc/assigns_replace_08/main.c create mode 100644 regression/contracts-dfcc/assigns_replace_08/test.desc create mode 100644 regression/contracts-dfcc/assigns_replace_09/main.c create mode 100644 regression/contracts-dfcc/assigns_replace_09/test.desc create mode 100644 regression/contracts-dfcc/assigns_replace_conditional_targets/main.c create mode 100644 regression/contracts-dfcc/assigns_replace_conditional_targets/test.desc create mode 100644 regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/enforce.desc create mode 100644 regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/main_enforce.c create mode 100644 regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/main_replace.c create mode 100644 regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/replace.desc create mode 100644 regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/vect.h create mode 100644 regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/enforce.desc create mode 100644 regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/main_enforce.c create mode 100644 regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/main_replace.c create mode 100644 regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/replace.desc create mode 100644 regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/vect.h create mode 100644 regression/contracts-dfcc/assigns_type_checking_invalid_case_01/main.c create mode 100644 regression/contracts-dfcc/assigns_type_checking_invalid_case_01/test.desc create mode 100644 regression/contracts-dfcc/assigns_type_checking_invalid_case_02/main.c create mode 100644 regression/contracts-dfcc/assigns_type_checking_invalid_case_02/test.desc create mode 100644 regression/contracts-dfcc/assigns_type_checking_valid_cases/main.c create mode 100644 regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo1.desc create mode 100644 regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo10.desc create mode 100644 regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo2.desc create mode 100644 regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo3.desc create mode 100644 regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo4.desc create mode 100644 regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo5.desc create mode 100644 regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo6.desc create mode 100644 regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo7.desc create mode 100644 regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo8.desc create mode 100644 regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo9.desc create mode 100644 regression/contracts-dfcc/assigns_validity_pointer_01/main.c create mode 100644 regression/contracts-dfcc/assigns_validity_pointer_01/test.desc create mode 100644 regression/contracts-dfcc/assigns_validity_pointer_02/main.c create mode 100644 regression/contracts-dfcc/assigns_validity_pointer_02/test.desc create mode 100644 regression/contracts-dfcc/assigns_validity_pointer_03/main.c create mode 100644 regression/contracts-dfcc/assigns_validity_pointer_03/test.desc create mode 100644 regression/contracts-dfcc/assigns_validity_pointer_04/main.c create mode 100644 regression/contracts-dfcc/assigns_validity_pointer_04/test.desc create mode 100755 regression/contracts-dfcc/chain.sh create mode 100644 regression/contracts-dfcc/contracts_with_function_pointers/main.c create mode 100644 regression/contracts-dfcc/contracts_with_function_pointers/test.desc create mode 100644 regression/contracts-dfcc/cprover-assignable-fail/main.c create mode 100644 regression/contracts-dfcc/cprover-assignable-fail/test.desc create mode 100644 regression/contracts-dfcc/cprover-assignable-pass/main.c create mode 100644 regression/contracts-dfcc/cprover-assignable-pass/test.desc create mode 100644 regression/contracts-dfcc/embedded_contract_fail_01/main.c create mode 100644 regression/contracts-dfcc/embedded_contract_fail_01/test.desc create mode 100644 regression/contracts-dfcc/embedded_contract_fail_02/main.c create mode 100644 regression/contracts-dfcc/embedded_contract_fail_02/test.desc create mode 100644 regression/contracts-dfcc/enforce-replace-unknown-function/enforce.desc create mode 100644 regression/contracts-dfcc/enforce-replace-unknown-function/main.c create mode 100644 regression/contracts-dfcc/enforce-replace-unknown-function/replace.desc create mode 100644 regression/contracts-dfcc/entry_point/main.c create mode 100644 regression/contracts-dfcc/entry_point/test.desc create mode 100644 regression/contracts-dfcc/frees-clause-and-predicates-fail/main.c create mode 100644 regression/contracts-dfcc/frees-clause-and-predicates-fail/test.desc create mode 100644 regression/contracts-dfcc/frees-clause-and-predicates-fail2/main.c create mode 100644 regression/contracts-dfcc/frees-clause-and-predicates-fail2/test.desc create mode 100644 regression/contracts-dfcc/frees-clause-and-predicates-is_freeable-bad-arity/main.c create mode 100644 regression/contracts-dfcc/frees-clause-and-predicates-is_freeable-bad-arity/test.desc create mode 100644 regression/contracts-dfcc/frees-clause-and-predicates-is_freed-bad-arity/main.c create mode 100644 regression/contracts-dfcc/frees-clause-and-predicates-is_freed-bad-arity/test.desc create mode 100644 regression/contracts-dfcc/frees-clause-and-predicates/main.c create mode 100644 regression/contracts-dfcc/frees-clause-and-predicates/test.desc create mode 100644 regression/contracts-dfcc/function-calls-01-enforce-failure/main.c create mode 100644 regression/contracts-dfcc/function-calls-01-enforce-failure/test.desc create mode 100644 regression/contracts-dfcc/function-calls-01-enforce-success/main.c create mode 100644 regression/contracts-dfcc/function-calls-01-enforce-success/test.desc create mode 100644 regression/contracts-dfcc/function-calls-01-replace-failure/main.c create mode 100644 regression/contracts-dfcc/function-calls-01-replace-failure/test.desc create mode 100644 regression/contracts-dfcc/function-calls-01-replace-success/main.c create mode 100644 regression/contracts-dfcc/function-calls-01-replace-success/test.desc create mode 100644 regression/contracts-dfcc/function-calls-02-failure/main.c create mode 100644 regression/contracts-dfcc/function-calls-02-failure/test-enf-f-repl-g.desc create mode 100644 regression/contracts-dfcc/function-calls-02-failure/test-enf-f.desc create mode 100644 regression/contracts-dfcc/function-calls-02-failure/test-enf-g.desc create mode 100644 regression/contracts-dfcc/function-calls-02-success/main.c create mode 100644 regression/contracts-dfcc/function-calls-02-success/test-enf-f-repl-g.desc create mode 100644 regression/contracts-dfcc/function-calls-02-success/test-enf-f.desc create mode 100644 regression/contracts-dfcc/function-calls-02-success/test-enf-g.desc create mode 100644 regression/contracts-dfcc/function-calls-03-direct-recursion/main.c create mode 100644 regression/contracts-dfcc/function-calls-03-direct-recursion/test-norec.desc create mode 100644 regression/contracts-dfcc/function-calls-03-direct-recursion/test-rec.desc create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/even_odd.h create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/main_even.c create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/main_odd.c create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-even-repl-odd.desc create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-even.desc create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-odd-repl-even.desc create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-odd.desc create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-rec-even.desc create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-rec-odd.desc create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-success/even_odd.h create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-success/main_even.c create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-success/main_odd.c create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-success/main_unwind.c create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-even-repl-odd.desc create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-even.desc create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-odd-repl-even.desc create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-odd.desc create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-rec-even.desc create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-rec-odd.desc create mode 100644 regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-unwind.desc create mode 100644 regression/contracts-dfcc/function-calls-05-function-pointer-call-fail/main.c create mode 100644 regression/contracts-dfcc/function-calls-05-function-pointer-call-fail/test.desc create mode 100644 regression/contracts-dfcc/function-calls-05-function-pointer-call-pass/main.c create mode 100644 regression/contracts-dfcc/function-calls-05-function-pointer-call-pass/test.desc create mode 100644 regression/contracts-dfcc/function-calls-recursive-function-1/main.c create mode 100644 regression/contracts-dfcc/function-calls-recursive-function-1/test-replace.desc create mode 100644 regression/contracts-dfcc/function-calls-recursive-function-1/test-unwind.desc create mode 100644 regression/contracts-dfcc/function-calls-recursive-function-2/main.c create mode 100644 regression/contracts-dfcc/function-calls-recursive-function-2/test.desc create mode 100644 regression/contracts-dfcc/function-pointer-contracts-enforce/main.c create mode 100644 regression/contracts-dfcc/function-pointer-contracts-enforce/test-manual-swap.desc create mode 100644 regression/contracts-dfcc/function-pointer-contracts-enforce/test.desc create mode 100644 regression/contracts-dfcc/function-pointer-contracts-replace/main.c create mode 100644 regression/contracts-dfcc/function-pointer-contracts-replace/test.desc create mode 100644 regression/contracts-dfcc/function_apply_01/main.c create mode 100644 regression/contracts-dfcc/function_apply_01/test.desc create mode 100644 regression/contracts-dfcc/function_check_01/main.c create mode 100644 regression/contracts-dfcc/function_check_01/test.desc create mode 100644 regression/contracts-dfcc/function_check_02/main.c create mode 100644 regression/contracts-dfcc/function_check_02/test.desc create mode 100644 regression/contracts-dfcc/function_check_03/main.c create mode 100644 regression/contracts-dfcc/function_check_03/test.desc create mode 100644 regression/contracts-dfcc/function_check_04/main.c create mode 100644 regression/contracts-dfcc/function_check_04/test.desc create mode 100644 regression/contracts-dfcc/function_check_05/main.c create mode 100644 regression/contracts-dfcc/function_check_05/test.desc create mode 100644 regression/contracts-dfcc/function_check_mem_01/main.c create mode 100644 regression/contracts-dfcc/function_check_mem_01/test.desc create mode 100644 regression/contracts-dfcc/function_loop_history_ensures_fail/main.c create mode 100644 regression/contracts-dfcc/function_loop_history_ensures_fail/test.desc create mode 100644 regression/contracts-dfcc/function_loop_history_requires_fail/main.c create mode 100644 regression/contracts-dfcc/function_loop_history_requires_fail/test.desc create mode 100644 regression/contracts-dfcc/function_no_apply_01/main.c create mode 100644 regression/contracts-dfcc/function_no_apply_01/test.desc create mode 100644 regression/contracts-dfcc/havoc-static/main.c create mode 100644 regression/contracts-dfcc/havoc-static/test-exclude.desc create mode 100644 regression/contracts-dfcc/havoc-static/test.desc create mode 100644 regression/contracts-dfcc/history-constant/main.c create mode 100644 regression/contracts-dfcc/history-constant/test.desc create mode 100644 regression/contracts-dfcc/history-pointer-both-01/main.c create mode 100644 regression/contracts-dfcc/history-pointer-both-01/test.desc create mode 100644 regression/contracts-dfcc/history-pointer-enforce-01/main.c create mode 100644 regression/contracts-dfcc/history-pointer-enforce-01/test.desc create mode 100644 regression/contracts-dfcc/history-pointer-enforce-02/main.c create mode 100644 regression/contracts-dfcc/history-pointer-enforce-02/test.desc create mode 100644 regression/contracts-dfcc/history-pointer-enforce-03/main.c create mode 100644 regression/contracts-dfcc/history-pointer-enforce-03/test.desc create mode 100644 regression/contracts-dfcc/history-pointer-enforce-04/main.c create mode 100644 regression/contracts-dfcc/history-pointer-enforce-04/test.desc create mode 100644 regression/contracts-dfcc/history-pointer-enforce-05/main.c create mode 100644 regression/contracts-dfcc/history-pointer-enforce-05/test.desc create mode 100644 regression/contracts-dfcc/history-pointer-enforce-06/main.c create mode 100644 regression/contracts-dfcc/history-pointer-enforce-06/test.desc create mode 100644 regression/contracts-dfcc/history-pointer-enforce-07/main.c create mode 100644 regression/contracts-dfcc/history-pointer-enforce-07/test.desc create mode 100644 regression/contracts-dfcc/history-pointer-enforce-08/main.c create mode 100644 regression/contracts-dfcc/history-pointer-enforce-08/test.desc create mode 100644 regression/contracts-dfcc/history-pointer-enforce-09/main.c create mode 100644 regression/contracts-dfcc/history-pointer-enforce-09/test.desc create mode 100644 regression/contracts-dfcc/history-pointer-enforce-10/main.c create mode 100644 regression/contracts-dfcc/history-pointer-enforce-10/test-bar.desc create mode 100644 regression/contracts-dfcc/history-pointer-enforce-10/test-baz.desc create mode 100644 regression/contracts-dfcc/history-pointer-enforce-10/test-foo.desc create mode 100644 regression/contracts-dfcc/history-pointer-enforce-11/main.c create mode 100644 regression/contracts-dfcc/history-pointer-enforce-11/test.desc create mode 100644 regression/contracts-dfcc/history-pointer-replace-01/main.c create mode 100644 regression/contracts-dfcc/history-pointer-replace-01/test.desc create mode 100644 regression/contracts-dfcc/history-pointer-replace-02/main.c create mode 100644 regression/contracts-dfcc/history-pointer-replace-02/test.desc create mode 100644 regression/contracts-dfcc/history-pointer-replace-03/main.c create mode 100644 regression/contracts-dfcc/history-pointer-replace-03/test.desc create mode 100644 regression/contracts-dfcc/history-pointer-replace-04/main.c create mode 100644 regression/contracts-dfcc/history-pointer-replace-04/test.desc create mode 100644 regression/contracts-dfcc/history-typecast/main.c create mode 100644 regression/contracts-dfcc/history-typecast/test.desc create mode 100644 regression/contracts-dfcc/ignored_return_value/main.c create mode 100644 regression/contracts-dfcc/ignored_return_value/test.desc create mode 100644 regression/contracts-dfcc/is_fresh_indirect_calls/main.c create mode 100644 regression/contracts-dfcc/is_fresh_indirect_calls/test.desc create mode 100644 regression/contracts-dfcc/is_unique_01_replace/main.c create mode 100644 regression/contracts-dfcc/is_unique_01_replace/test.desc create mode 100644 regression/contracts-dfcc/loop-freeness-check/main.c create mode 100644 regression/contracts-dfcc/loop-freeness-check/test.desc create mode 100644 regression/contracts-dfcc/named-contracts/main-contract-after-declaration.c create mode 100644 regression/contracts-dfcc/named-contracts/main-contract-after-definition.c create mode 100644 regression/contracts-dfcc/named-contracts/main-contract-incomplete.c create mode 100644 regression/contracts-dfcc/named-contracts/main-contract-signature-conflict.c create mode 100644 regression/contracts-dfcc/named-contracts/main-definition-after-contract.c create mode 100644 regression/contracts-dfcc/named-contracts/main-no-definition.c create mode 100644 regression/contracts-dfcc/named-contracts/test-contract-after-declaration.desc create mode 100644 regression/contracts-dfcc/named-contracts/test-contract-after-definition.desc create mode 100644 regression/contracts-dfcc/named-contracts/test-contract-incomplete.desc create mode 100644 regression/contracts-dfcc/named-contracts/test-contract-signature-conflict.desc create mode 100644 regression/contracts-dfcc/named-contracts/test-definition-after-contract.desc create mode 100644 regression/contracts-dfcc/named-contracts/test-no-definition.desc create mode 100644 regression/contracts-dfcc/no_redudant_checks/main.c create mode 100644 regression/contracts-dfcc/no_redudant_checks/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-exists-both-enforce/main.c create mode 100644 regression/contracts-dfcc/quantifiers-exists-both-enforce/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-exists-both-replace/main.c create mode 100644 regression/contracts-dfcc/quantifiers-exists-both-replace/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-exists-ensures-enforce/main.c create mode 100644 regression/contracts-dfcc/quantifiers-exists-ensures-enforce/test-f1.desc create mode 100644 regression/contracts-dfcc/quantifiers-exists-ensures-enforce/test-f2.desc create mode 100644 regression/contracts-dfcc/quantifiers-exists-ensures-replace/main.c create mode 100644 regression/contracts-dfcc/quantifiers-exists-ensures-replace/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-exists-requires-enforce/main.c create mode 100644 regression/contracts-dfcc/quantifiers-exists-requires-enforce/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-exists-requires-replace/main.c create mode 100644 regression/contracts-dfcc/quantifiers-exists-requires-replace/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-forall-both-enforce/main.c create mode 100644 regression/contracts-dfcc/quantifiers-forall-both-enforce/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-forall-both-replace/main.c create mode 100644 regression/contracts-dfcc/quantifiers-forall-both-replace/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-forall-ensures-enforce/main.c create mode 100644 regression/contracts-dfcc/quantifiers-forall-ensures-enforce/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-forall-ensures-replace/main.c create mode 100644 regression/contracts-dfcc/quantifiers-forall-ensures-replace/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-forall-requires-enforce/main.c create mode 100644 regression/contracts-dfcc/quantifiers-forall-requires-enforce/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-forall-requires-replace/main.c create mode 100644 regression/contracts-dfcc/quantifiers-forall-requires-replace/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-nested-01/main.c create mode 100644 regression/contracts-dfcc/quantifiers-nested-01/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-nested-02/main.c create mode 100644 regression/contracts-dfcc/quantifiers-nested-02/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-nested-03/main.c create mode 100644 regression/contracts-dfcc/quantifiers-nested-03/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-nested-04/main.c create mode 100644 regression/contracts-dfcc/quantifiers-nested-04/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-nested-05/main.c create mode 100644 regression/contracts-dfcc/quantifiers-nested-05/test.desc create mode 100644 regression/contracts-dfcc/quantifiers-nested-06/main.c create mode 100644 regression/contracts-dfcc/quantifiers-nested-06/test.desc create mode 100644 regression/contracts-dfcc/reject_history_expr_in_assigns_clause/main.c create mode 100644 regression/contracts-dfcc/reject_history_expr_in_assigns_clause/test.desc create mode 100644 regression/contracts-dfcc/reject_history_expr_in_preconditions/main.c create mode 100644 regression/contracts-dfcc/reject_history_expr_in_preconditions/test.desc create mode 100644 regression/contracts-dfcc/reject_return_value_in_assigns_clause/main.c create mode 100644 regression/contracts-dfcc/reject_return_value_in_assigns_clause/test.desc create mode 100644 regression/contracts-dfcc/reject_return_value_in_preconditions/main.c create mode 100644 regression/contracts-dfcc/reject_return_value_in_preconditions/test.desc create mode 100644 regression/contracts-dfcc/replace-nondet-return-value/main.c create mode 100644 regression/contracts-dfcc/replace-nondet-return-value/test.desc create mode 100644 regression/contracts-dfcc/test_aliasing_enforce/main.c create mode 100644 regression/contracts-dfcc/test_aliasing_enforce/test.desc create mode 100644 regression/contracts-dfcc/test_aliasing_ensure/main.c create mode 100644 regression/contracts-dfcc/test_aliasing_ensure/test.desc create mode 100644 regression/contracts-dfcc/test_aliasing_ensure_indirect/foo_bar.h create mode 100644 regression/contracts-dfcc/test_aliasing_ensure_indirect/main_bar.c create mode 100644 regression/contracts-dfcc/test_aliasing_ensure_indirect/main_foo.c create mode 100644 regression/contracts-dfcc/test_aliasing_ensure_indirect/test-bar.desc create mode 100644 regression/contracts-dfcc/test_aliasing_ensure_indirect/test-foo.desc create mode 100644 regression/contracts-dfcc/test_aliasing_replace/main.c create mode 100644 regression/contracts-dfcc/test_aliasing_replace/test.desc create mode 100644 regression/contracts-dfcc/test_array_memory_enforce/main.c create mode 100644 regression/contracts-dfcc/test_array_memory_enforce/test.desc create mode 100644 regression/contracts-dfcc/test_array_memory_replace/main.c create mode 100644 regression/contracts-dfcc/test_array_memory_replace/test.desc create mode 100644 regression/contracts-dfcc/test_array_memory_too_small_replace/main.c create mode 100644 regression/contracts-dfcc/test_array_memory_too_small_replace/test.desc create mode 100644 regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_ensures/main.c create mode 100644 regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_ensures/test.desc create mode 100644 regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_requires/main.c create mode 100644 regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_requires/test.desc create mode 100644 regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_size/main.c create mode 100644 regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_size/test.desc create mode 100644 regression/contracts-dfcc/test_is_fresh_enforce_ensures_pass/main.c create mode 100644 regression/contracts-dfcc/test_is_fresh_enforce_ensures_pass/test.desc create mode 100644 regression/contracts-dfcc/test_is_fresh_enforce_requires_pass/main.c create mode 100644 regression/contracts-dfcc/test_is_fresh_enforce_requires_pass/test.desc create mode 100644 regression/contracts-dfcc/test_is_fresh_replace_ensures_pass/main.c create mode 100644 regression/contracts-dfcc/test_is_fresh_replace_ensures_pass/test-enforce.desc create mode 100644 regression/contracts-dfcc/test_is_fresh_replace_ensures_pass/test-replace.desc create mode 100644 regression/contracts-dfcc/test_is_fresh_replace_requires_fail_separation/main.c create mode 100644 regression/contracts-dfcc/test_is_fresh_replace_requires_fail_separation/test.desc create mode 100644 regression/contracts-dfcc/test_is_fresh_replace_requires_fail_size/main.c create mode 100644 regression/contracts-dfcc/test_is_fresh_replace_requires_fail_size/test.desc create mode 100644 regression/contracts-dfcc/test_is_fresh_replace_requires_pass/main.c create mode 100644 regression/contracts-dfcc/test_is_fresh_replace_requires_pass/test.desc create mode 100644 regression/contracts-dfcc/test_possibly_aliased_arguments/main.c create mode 100644 regression/contracts-dfcc/test_possibly_aliased_arguments/test.desc create mode 100644 regression/contracts-dfcc/test_scalar_memory_enforce/main.c create mode 100644 regression/contracts-dfcc/test_scalar_memory_enforce/test.desc create mode 100644 regression/contracts-dfcc/test_scalar_memory_replace/main.c create mode 100644 regression/contracts-dfcc/test_scalar_memory_replace/test.desc create mode 100644 regression/contracts-dfcc/test_struct_enforce/main.c create mode 100644 regression/contracts-dfcc/test_struct_enforce/test.desc create mode 100644 regression/contracts-dfcc/test_struct_member_enforce/main.c create mode 100644 regression/contracts-dfcc/test_struct_member_enforce/test.desc create mode 100644 regression/contracts-dfcc/test_struct_replace/main.c create mode 100644 regression/contracts-dfcc/test_struct_replace/test.desc create mode 100644 regression/contracts-dfcc/trivial_contract_enforce/main.c create mode 100644 regression/contracts-dfcc/trivial_contract_enforce/test.desc create mode 100644 regression/contracts-dfcc/trivial_contract_replace/main.c create mode 100644 regression/contracts-dfcc/trivial_contract_replace/test.desc create mode 100644 regression/contracts-dfcc/typed_target_fail_wrong_nof_operand/main.c create mode 100644 regression/contracts-dfcc/typed_target_fail_wrong_nof_operand/test.desc create mode 100644 regression/contracts-dfcc/typed_target_pointer/main.c create mode 100644 regression/contracts-dfcc/typed_target_pointer/test.desc create mode 100644 regression/contracts-dfcc/used_return_value/main.c create mode 100644 regression/contracts-dfcc/used_return_value/test.desc create mode 100644 src/ansi-c/library/cprover_contracts.c 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-transform-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 (90%) 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 (85%) 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 (85%) 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 create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc.h create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_contract_functions.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_contract_functions.h create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_contract_handler.cpp 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 create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_instrument.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_instrument.h 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 create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_library.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_library.h 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 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 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/dfcc_wrapper_program.cpp create mode 100644 src/goto-instrument/contracts/dynamic-frames/dfcc_wrapper_program.h create mode 100644 src/goto-instrument/contracts/dynamic-frames/module_dependencies.txt 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/doc/man/goto-instrument.1 b/doc/man/goto-instrument.1 index bceea00546b..8d6b0444650 100644 --- a/doc/man/goto-instrument.1 +++ b/doc/man/goto-instrument.1 @@ -1003,6 +1003,12 @@ replace calls to \fIfun\fR with \fIfun\fR's contract .TP \fB\-\-enforce\-contract\fR \fIfun\fR wrap \fIfun\fR with an assertion of its contract +.TP +\fB\-\-enforce\-contract\-rec\fR \fIfun\fR +wrap \fIfun\fR with an assertion of its contract that can handle recursive calls +.TP +\fB\-\-dfcc\fR \fIfun\fR +instrument dynamic frame condition checks method using \fIfun\fR as entry point .SS "User-interface options:" .TP \fB\-\-flush\fR diff --git a/regression/CMakeLists.txt b/regression/CMakeLists.txt index 5f54b47b43d..04bdfce5694 100644 --- a/regression/CMakeLists.txt +++ b/regression/CMakeLists.txt @@ -65,6 +65,7 @@ add_subdirectory(goto-analyzer-simplify) add_subdirectory(statement-list) add_subdirectory(systemc) add_subdirectory(contracts) +add_subdirectory(contracts-dfcc) add_subdirectory(acceleration) add_subdirectory(k-induction) add_subdirectory(goto-harness) diff --git a/regression/Makefile b/regression/Makefile index ce46e300bfe..9578500dcb5 100644 --- a/regression/Makefile +++ b/regression/Makefile @@ -38,6 +38,7 @@ DIRS = cbmc \ statement-list \ systemc \ contracts \ + contracts-dfcc \ acceleration \ k-induction \ goto-harness \ diff --git a/regression/contracts-dfcc/CMakeLists.txt b/regression/contracts-dfcc/CMakeLists.txt new file mode 100644 index 00000000000..0f9ad7ca49c --- /dev/null +++ b/regression/contracts-dfcc/CMakeLists.txt @@ -0,0 +1,43 @@ +if(WIN32) + set(is_windows true) +else() + set(is_windows false) +endif() + +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + set(gcc_only -X gcc-only) + set(gcc_only_string "-X;gcc-only;") +else() + set(gcc_only "") + set(gcc_only_string "") +endif() + + +add_test_pl_tests( + "${CMAKE_CURRENT_SOURCE_DIR}/chain.sh $ $ $ ${is_windows}" +) + +## Enabling these causes a very significant increase in the time taken to run the regressions + +#add_test_pl_profile( +# "cbmc-z3" +# "${CMAKE_CURRENT_SOURCE_DIR}/chain.sh $ $ '$ --z3' ${is_windows}" +# "-C;-X;broken-smt-backend;-X;thorough-smt-backend;-X;broken-z3-backend;-X;thorough-z3-backend;${gcc_only_string}-s;z3" +# "CORE" +#) + +#add_test_pl_profile( +# "cbmc-cprover-smt2" +# "${CMAKE_CURRENT_SOURCE_DIR}/chain.sh $ $ '$ --cprover-smt2' ${is_windows}" +# "-C;-X;broken-smt-backend;-X;thorough-smt-backend;-X;broken-cprover-smt2-backend;-X;thorough-cprover-smt2-backend;${gcc_only_string}-s;cprover-smt2" +# "CORE" +#) + +# solver appears on the PATH in Windows already +#if(NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") +# set_property( +# TEST "cbmc-cprover-smt2-CORE" +# PROPERTY ENVIRONMENT +# "PATH=$ENV{PATH}:$" +# ) +#endif() diff --git a/regression/contracts-dfcc/Makefile b/regression/contracts-dfcc/Makefile new file mode 100644 index 00000000000..a1700f3d9ab --- /dev/null +++ b/regression/contracts-dfcc/Makefile @@ -0,0 +1,42 @@ +default: test + +include ../../src/config.inc +include ../../src/common + +ifeq ($(BUILD_ENV_),MSVC) + exe=../../../src/goto-cc/goto-cl + is_windows=true + GCC_ONLY = -X gcc-only +else + exe=../../../src/goto-cc/goto-cc + is_windows=false + GCC_ONLY = +endif + +test: + @../test.pl -e -p -c '../chain.sh $(exe) ../../../src/goto-instrument/goto-instrument ../../../src/cbmc/cbmc $(is_windows)' -X smt-backend $(GCC_ONLY) + +test-cprover-smt2: + @../test.pl -e -p -c '../chain.sh $(exe) ../../../src/goto-instrument/goto-instrument "../../../src/cbmc/cbmc --cprover-smt2" $(is_windows)' \ + -X broken-smt-backend -X thorough-smt-backend \ + -X broken-cprover-smt-backend -X thorough-cprover-smt-backend \ + -s cprover-smt2 $(GCC_ONLY) + +test-z3: + @../test.pl -e -p -c '../chain.sh $(exe) ../../../src/goto-instrument/goto-instrument "../../../src/cbmc/cbmc --z3" $(is_windows)' \ + -X broken-smt-backend -X thorough-smt-backend \ + -X broken-z3-smt-backend -X thorough-z3-smt-backend \ + -s z3 $(GCC_ONLY) + +tests.log: ../test.pl test + + +clean: + @for dir in *; do \ + $(RM) tests.log; \ + if [ -d "$$dir" ]; then \ + cd "$$dir"; \ + $(RM) *.out *.gb *.smt2; \ + cd ..; \ + fi \ + done diff --git a/regression/contracts-dfcc/assigns-enforce-malloc-zero/main.c b/regression/contracts-dfcc/assigns-enforce-malloc-zero/main.c new file mode 100644 index 00000000000..61f105f8a43 --- /dev/null +++ b/regression/contracts-dfcc/assigns-enforce-malloc-zero/main.c @@ -0,0 +1,31 @@ +#include + +// returns the index at which the write was performed if any +// -1 otherwise +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_object_whole(a)) + __CPROVER_ensures( + a && __CPROVER_return_value >= 0 ==> a[__CPROVER_return_value] == 0) +// clang-format on +{ + if(!a) + return -1; + int i; + if(0 <= i && i < size) + { + a[i] = 0; + return i; + } + return -1; +} + +int main() +{ + size_t size; + char *a; + foo(a, size); + return 0; +} diff --git a/regression/contracts-dfcc/assigns-enforce-malloc-zero/test.desc b/regression/contracts-dfcc/assigns-enforce-malloc-zero/test.desc new file mode 100644 index 00000000000..7d82f4cf938 --- /dev/null +++ b/regression/contracts-dfcc/assigns-enforce-malloc-zero/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--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$ +^VERIFICATION SUCCESSFUL$ +-- +This test checks that objects of size zero or __CPROVER_max_malloc_size +do not cause spurious falsifications in assigns clause instrumentation +in contract enforcement mode. diff --git a/regression/contracts-dfcc/assigns-local-composite/main.c b/regression/contracts-dfcc/assigns-local-composite/main.c new file mode 100644 index 00000000000..7b102af7cdd --- /dev/null +++ b/regression/contracts-dfcc/assigns-local-composite/main.c @@ -0,0 +1,104 @@ +struct st1 +{ + int a; + int arr[10]; +}; + +struct st2 +{ + int a; + struct st1 arr[10]; +}; + +struct st3 +{ + struct st1 *s1; + struct st2 *s2; + struct st1 arr1[10]; + struct st2 arr2[10]; +}; + +enum tagt +{ + CHAR, + INT, + CHAR_PTR, + INT_ARR, + STRUCT_ST1_ARR +}; + +// clang-format off +struct taggedt { + enum tagt tag; + union { + struct{ char a; }; + struct{ int b; }; + struct{ char *ptr; }; + struct{ int arr[10]; }; + struct{ struct st1 arr1[10]; }; + }; +}; +// clang-format on + +int foo(int i) __CPROVER_assigns() +{ + // all accesses to locals should pass + int arr[10]; + struct st1 s1; + struct st2 s2; + struct st1 dirty_s1; + struct st3 s3; + s3.s1 = &dirty_s1; + s3.s2 = malloc(sizeof(struct st2)); + + if(0 <= i && i < 10) + { + arr[i] = 0; + s1.a = 0; + s1.arr[i] = 0; + s2.a = 0; + s2.arr[i].a = 0; + s2.arr[i].arr[i] = 0; + s3.s1->a = 0; + s3.s1->arr[i] = 0; + s3.s2->a = 0; + s3.s2->arr[i].a = 0; + s3.s2->arr[i].arr[i] = 0; + *(&(s3.s2->arr[i].arr[0]) + i) = 0; + (&(s3.arr1[0]) + i)->arr[i] = 0; + (&((&(s3.arr2[0]) + i)->arr[i]))->a = 0; + } + + struct taggedt tagged; + switch(tagged.tag) + { + case CHAR: + tagged.a = 0; + break; + case INT: + tagged.b = 0; + break; + case CHAR_PTR: + tagged.ptr = 0; + break; + case INT_ARR: + if(0 <= i && i < 10) + tagged.arr[i] = 0; + break; + case STRUCT_ST1_ARR: + if(0 <= i && i < 10) + { + tagged.arr1[i].a = 0; + tagged.arr1[i].arr[i] = 0; + } + break; + } + + return 0; +} + +void main() +{ + int i; + foo(i); +} diff --git a/regression/contracts-dfcc/assigns-local-composite/test.desc b/regression/contracts-dfcc/assigns-local-composite/test.desc new file mode 100644 index 00000000000..261546f1591 --- /dev/null +++ b/regression/contracts-dfcc/assigns-local-composite/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +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. +will be detected by assigns clause checking. diff --git a/regression/contracts-dfcc/assigns-replace-ignored-return-value/main.c b/regression/contracts-dfcc/assigns-replace-ignored-return-value/main.c new file mode 100644 index 00000000000..2ebe8aa5e84 --- /dev/null +++ b/regression/contracts-dfcc/assigns-replace-ignored-return-value/main.c @@ -0,0 +1,37 @@ +#include +#include + +int bar(int l, int r) __CPROVER_requires(0 <= l && l <= 10) + __CPROVER_requires(0 <= r && r <= 10) __CPROVER_assigns() __CPROVER_ensures( + __CPROVER_return_value == __CPROVER_old(l) + __CPROVER_old(r)) +{ + return l + r; +} + +int *baz(int l, int r) __CPROVER_requires(0 <= l && l <= 10) + __CPROVER_requires(0 <= r && r <= 10) __CPROVER_assigns() __CPROVER_ensures( + *__CPROVER_return_value == __CPROVER_old(l) + __CPROVER_old(r)) +{ + int *res = malloc(sizeof(int)); + *res = l + r; + return res; +} + +int foo(int l, int r) __CPROVER_requires(0 <= l && l <= 10) + __CPROVER_requires(0 <= r && r <= 10) __CPROVER_assigns() __CPROVER_ensures( + __CPROVER_return_value == __CPROVER_old(l) + __CPROVER_old(r)) +{ + bar(l, r); + bar(l, r); + baz(l, r); + baz(l, r); + return l + r; +} + +int main() +{ + int l; + int r; + foo(l, r); + return 0; +} diff --git a/regression/contracts-dfcc/assigns-replace-ignored-return-value/test.desc b/regression/contracts-dfcc/assigns-replace-ignored-return-value/test.desc new file mode 100644 index 00000000000..92d57115adf --- /dev/null +++ b/regression/contracts-dfcc/assigns-replace-ignored-return-value/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --replace-call-with-contract bar --replace-call-with-contract baz --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +^\[.*assigns.*\].*ignored_return_value.*FAILURE +-- +This test checks that when replacing a call by a contract for a call that +ignores the return value of the function, the temporary introduced to +receive the call result does not trigger errors with assigns clause Checking +in the function under verification. diff --git a/regression/contracts-dfcc/assigns-replace-malloc-zero/main.c b/regression/contracts-dfcc/assigns-replace-malloc-zero/main.c new file mode 100644 index 00000000000..444b33ce29f --- /dev/null +++ b/regression/contracts-dfcc/assigns-replace-malloc-zero/main.c @@ -0,0 +1,37 @@ +#include + +// returns the index at which the write was performed if any +// -1 otherwise +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_object_whole(a)) +__CPROVER_ensures( + a && __CPROVER_return_value >= 0 ==> a[__CPROVER_return_value] == 0) +// clang-format on +{ + if(!a) + return -1; + int i; + if(0 <= i && i < size) + { + a[i] = 0; + return i; + } + return -1; +} + +int main() +{ + int size; + if(size < 0) + size = 0; + if(size > __CPROVER_max_malloc_size) + size = __CPROVER_max_malloc_size; + char *a = malloc(size * sizeof(*a)); + int res = foo(a, size); + if(a && res >= 0) + __CPROVER_assert(a[res] == 0, "expecting SUCCESS"); + return 0; +} diff --git a/regression/contracts-dfcc/assigns-replace-malloc-zero/test.desc b/regression/contracts-dfcc/assigns-replace-malloc-zero/test.desc new file mode 100644 index 00000000000..2e410cec503 --- /dev/null +++ b/regression/contracts-dfcc/assigns-replace-malloc-zero/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--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$ +^VERIFICATION SUCCESSFUL$ +-- +This test checks that objects of size zero or of __CPROVER_max_malloc_size +do not cause spurious falsifications in assigns clause instrumentation +in contract replacement mode. diff --git a/regression/contracts-dfcc/assigns-slice-targets/main-enforce.c b/regression/contracts-dfcc/assigns-slice-targets/main-enforce.c new file mode 100644 index 00000000000..115573b17a4 --- /dev/null +++ b/regression/contracts-dfcc/assigns-slice-targets/main-enforce.c @@ -0,0 +1,67 @@ +struct st +{ + int a; + char arr1[10]; + int b; + char arr2[10]; + int c; +}; + +void foo(struct st *s, struct st *ss) + // clang-format off + __CPROVER_requires(__CPROVER_is_fresh(s, sizeof(*s))) + __CPROVER_assigns( + __CPROVER_object_upto(s->arr1, 5); + __CPROVER_object_from(s->arr2 + 5); + __CPROVER_object_whole(ss); +) +// clang-format on +{ + // PASS + s->arr1[0] = 0; + s->arr1[1] = 0; + s->arr1[2] = 0; + s->arr1[3] = 0; + s->arr1[4] = 0; + + // FAIL + s->arr1[5] = 0; + s->arr1[6] = 0; + s->arr1[7] = 0; + s->arr1[8] = 0; + s->arr1[9] = 0; + + // FAIL + s->arr2[0] = 0; + s->arr2[1] = 0; + s->arr2[2] = 0; + s->arr2[3] = 0; + s->arr2[4] = 0; + + // PASS + s->arr2[5] = 0; + s->arr2[6] = 0; + 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, &ss); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns-slice-targets/main-replace.c b/regression/contracts-dfcc/assigns-slice-targets/main-replace.c new file mode 100644 index 00000000000..79d73588cc0 --- /dev/null +++ b/regression/contracts-dfcc/assigns-slice-targets/main-replace.c @@ -0,0 +1,150 @@ +struct st +{ + int a; + char arr1[10]; + int b; + char arr2[10]; + int c; +}; + +void foo(struct st *s, struct st *ss) + // clang-format off + __CPROVER_requires(__CPROVER_is_fresh(s, sizeof(*s))) + __CPROVER_assigns( + __CPROVER_object_upto(s->arr1, 5); + __CPROVER_object_from(s->arr2 + 5); + __CPROVER_object_whole(ss); + ) +// clang-format on +{ + s->arr1[0] = 0; + s->arr1[1] = 0; + s->arr1[2] = 0; + s->arr1[3] = 0; + s->arr1[4] = 0; + + s->arr2[5] = 0; + s->arr2[6] = 0; + s->arr2[7] = 0; + s->arr2[8] = 0; + s->arr2[9] = 0; +} + +int main() +{ + struct st s; + s.a = 0; + s.arr1[0] = 0; + s.arr1[1] = 0; + s.arr1[2] = 0; + s.arr1[3] = 0; + s.arr1[4] = 0; + s.arr1[5] = 0; + s.arr1[6] = 0; + s.arr1[7] = 0; + s.arr1[8] = 0; + s.arr1[9] = 0; + + s.arr2[0] = 0; + s.arr2[1] = 0; + s.arr2[2] = 0; + s.arr2[3] = 0; + s.arr2[4] = 0; + s.arr2[5] = 0; + s.arr2[6] = 0; + s.arr2[7] = 0; + s.arr2[8] = 0; + s.arr2[9] = 0; + s.c = 0; + + 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); + + // FAIL + assert(s.arr1[0] == 0); + assert(s.arr1[1] == 0); + assert(s.arr1[2] == 0); + assert(s.arr1[3] == 0); + assert(s.arr1[4] == 0); + + // PASS + assert(s.arr1[5] == 0); + assert(s.arr1[6] == 0); + assert(s.arr1[7] == 0); + assert(s.arr1[8] == 0); + assert(s.arr1[9] == 0); + + // PASS + assert(s.b == 0); + + // PASS + assert(s.arr2[0] == 0); + assert(s.arr2[1] == 0); + assert(s.arr2[2] == 0); + assert(s.arr2[3] == 0); + assert(s.arr2[4] == 0); + + // FAIL + assert(s.arr2[5] == 0); + assert(s.arr2[6] == 0); + assert(s.arr2[7] == 0); + assert(s.arr2[8] == 0); + assert(s.arr2[9] == 0); + + // 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-dfcc/assigns-slice-targets/test-enforce.desc b/regression/contracts-dfcc/assigns-slice-targets/test-enforce.desc new file mode 100644 index 00000000000..e1df873e83e --- /dev/null +++ b/regression/contracts-dfcc/assigns-slice-targets/test-enforce.desc @@ -0,0 +1,38 @@ +CORE +main-enforce.c +--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$ +^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)3\] is assignable: SUCCESS$ +^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)4\] is assignable: SUCCESS$ +^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)5\] is assignable: FAILURE$ +^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)6\] is assignable: FAILURE$ +^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)7\] is assignable: FAILURE$ +^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)8\] is assignable: FAILURE$ +^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)9\] is assignable: FAILURE$ +^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)0\] is assignable: FAILURE$ +^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)1\] is assignable: FAILURE$ +^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)2\] is assignable: FAILURE$ +^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)3\] is assignable: FAILURE$ +^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)4\] is assignable: FAILURE$ +^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)5\] is assignable: SUCCESS$ +^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)6\] is assignable: SUCCESS$ +^\[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$ +-- +-- +Checks that assigns clause checking of slice expressions works as expected when +enforcing a contract. diff --git a/regression/contracts-dfcc/assigns-slice-targets/test-replace.desc b/regression/contracts-dfcc/assigns-slice-targets/test-replace.desc new file mode 100644 index 00000000000..b1eeafd252c --- /dev/null +++ b/regression/contracts-dfcc/assigns-slice-targets/test-replace.desc @@ -0,0 +1,57 @@ +CORE +main-replace.c +--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$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr1\[\(.*\)2\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr1\[\(.*\)3\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr1\[\(.*\)4\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr1\[\(.*\)5\] == 0: SUCCESS$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr1\[\(.*\)6\] == 0: SUCCESS$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr1\[\(.*\)7\] == 0: SUCCESS$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr1\[\(.*\)8\] == 0: SUCCESS$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr1\[\(.*\)9\] == 0: SUCCESS$ +^\[main.assertion.\d+\].*assertion s.b == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr2\[\(.*\)0\] == 0: SUCCESS$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr2\[\(.*\)1\] == 0: SUCCESS$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr2\[\(.*\)2\] == 0: SUCCESS$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr2\[\(.*\)3\] == 0: SUCCESS$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr2\[\(.*\)4\] == 0: SUCCESS$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr2\[\(.*\)5\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr2\[\(.*\)6\] == 0: FAILURE$ +^\[main.assertion.\d+\].*assertion \(.*\)s.arr2\[\(.*\)7\] == 0: FAILURE$ +^\[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$ +-- +-- +Checks that havocking of slice expressions works as expected when +replacing a call by a contract. We manually express frame conditions as +assertions in the main function. diff --git a/regression/contracts-dfcc/assigns_enforce_01/main.c b/regression/contracts-dfcc/assigns_enforce_01/main.c new file mode 100644 index 00000000000..102a3870a72 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_01/main.c @@ -0,0 +1,14 @@ +int foo(int *x) __CPROVER_assigns(*x) + __CPROVER_ensures(__CPROVER_return_value == *x + 5) +{ + *x = *x + 0; + return *x + 5; +} + +int main() +{ + int n = 4; + n = foo(&n); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_01/test.desc b/regression/contracts-dfcc/assigns_enforce_01/test.desc new file mode 100644 index 00000000000..1dd64604f87 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_01/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that verification succeeds if only expressions inside the assigns clause are assigned within the function. + +Note: For all 'enforce' tests, nothing can be assumed about the return value of the function (as the function call is not replaced at this point). + +To make such assumptions would cause verification to fail. diff --git a/regression/contracts-dfcc/assigns_enforce_02/main.c b/regression/contracts-dfcc/assigns_enforce_02/main.c new file mode 100644 index 00000000000..e581b827ee6 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_02/main.c @@ -0,0 +1,16 @@ +int z; + +int foo(int *x) __CPROVER_assigns(z) + __CPROVER_ensures(__CPROVER_return_value == *x + 5) +{ + *x = *x + 0; + return *x + 5; +} + +int main() +{ + int n = 4; + n = foo(&n); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_02/test.desc b/regression/contracts-dfcc/assigns_enforce_02/test.desc new file mode 100644 index 00000000000..9b05008a350 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_02/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^\[foo.assigns.\d+\] line 6 Check that \*x is assignable: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This test checks that verification fails if an expression outside the assigns clause is assigned within the function. diff --git a/regression/contracts-dfcc/assigns_enforce_03/main.c b/regression/contracts-dfcc/assigns_enforce_03/main.c new file mode 100644 index 00000000000..433eab01049 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_03/main.c @@ -0,0 +1,27 @@ +void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1) +{ + f2(x1, y1, z1); +} + +void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2) +{ + f3(x2, y2, z2); +} + +void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*x3) __CPROVER_assigns(*y3) + __CPROVER_assigns(*z3) +{ + *x3 = *x3 + 1; + *y3 = *y3 + 1; + *z3 = *z3 + 1; +} + +int main() +{ + int p = 1; + int q = 2; + int r = 3; + f1(&p, &q, &r); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_03/test.desc b/regression/contracts-dfcc/assigns_enforce_03/test.desc new file mode 100644 index 00000000000..39067aa46f6 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_03/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--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$ +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +This test checks that verification succeeds when assigns clauses are respected +through multiple function calls. diff --git a/regression/contracts-dfcc/assigns_enforce_04/main.c b/regression/contracts-dfcc/assigns_enforce_04/main.c new file mode 100644 index 00000000000..0d0e4e4da39 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_04/main.c @@ -0,0 +1,26 @@ +void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1) +{ + f2(x1, y1, z1); +} + +void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2) +{ + f3(x2, y2, z2); +} + +void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*y3, *z3) +{ + *x3 = *x3 + 1; + *y3 = *y3 + 1; + *z3 = *z3 + 1; +} + +int main() +{ + int p = 1; + int q = 2; + int r = 3; + f1(&p, &q, &r); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_04/test.desc b/regression/contracts-dfcc/assigns_enforce_04/test.desc new file mode 100644 index 00000000000..dd4edf44d7c --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_04/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--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$ +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +This test checks that verification only considers assigns clause from enforced function. diff --git a/regression/contracts-dfcc/assigns_enforce_05/main.c b/regression/contracts-dfcc/assigns_enforce_05/main.c new file mode 100644 index 00000000000..fb891d002db --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_05/main.c @@ -0,0 +1,23 @@ +void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1) +{ + f2(x1, y1, z1); +} + +void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2) +{ + f3(x2, y2, z2); +} + +void f3(int *x3, int *y3, int *z3) +{ +} + +int main() +{ + int p = 1; + int q = 2; + int r = 3; + f1(&p, &q, &r); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_05/test.desc b/regression/contracts-dfcc/assigns_enforce_05/test.desc new file mode 100644 index 00000000000..4432da69532 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_05/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that verification succeeds when enforcing a contract +for functions, without assigns clauses, that don't modify anything. diff --git a/regression/contracts-dfcc/assigns_enforce_06/main.c b/regression/contracts-dfcc/assigns_enforce_06/main.c new file mode 100644 index 00000000000..5987494a45e --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_06/main.c @@ -0,0 +1,44 @@ +void f1(int *x, int *y, int *z) __CPROVER_assigns(*x, *y, *z) +{ + f2(x, y, z); +} + +void f2(int *x, int *y, int *z) __CPROVER_assigns(*x, *y, *z) +{ + f3(x, y, z); +} + +void f3(int *x, int *y, int *z) __CPROVER_assigns(*x, *y, *z) +{ + *x = *x + 1; + *y = *y + 1; + *z = *z + 1; +} + +void f(int *x, int *y, int *z) __CPROVER_assigns(*x, *y, *z) +{ + for(int i = 0; i < 3; ++i) + { + if(i == 0) + { + f1(x, y, z); + } + if(i == 1) + { + f2(x, y, z); + } + if(i == 2) + { + 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-dfcc/assigns_enforce_06/test.desc b/regression/contracts-dfcc/assigns_enforce_06/test.desc new file mode 100644 index 00000000000..db14528b092 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_06/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract f +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that verification succeeds when functions +are called from within a loop. diff --git a/regression/contracts-dfcc/assigns_enforce_07/main.c b/regression/contracts-dfcc/assigns_enforce_07/main.c new file mode 100644 index 00000000000..4d513625106 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_07/main.c @@ -0,0 +1,46 @@ +void f1(int *x, int *y, int *z) __CPROVER_assigns(*x, *y) +{ + *x = *x + 1; + *y = *y + 1; +} + +void f2(int *x, int *y, int *z) __CPROVER_assigns(*x, *y) +{ + *x = *x + 1; + *y = *y + 1; +} + +void f3(int *x, int *y, int *z) __CPROVER_assigns(*x, *y, *z) +{ + *x = *x + 1; + *y = *y + 1; + *z = *z + 1; +} + +void f(int *x, int *y, int *z) __CPROVER_assigns(*x, *y) +{ + for(int i = 0; i < 3; ++i) + { + if(i == 0) + { + f1(x, y, z); + } + if(i == 1) + { + f2(x, y, z); + } + if(i == 2) + { + 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-dfcc/assigns_enforce_07/test.desc b/regression/contracts-dfcc/assigns_enforce_07/test.desc new file mode 100644 index 00000000000..e9f2d4f52df --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_07/test.desc @@ -0,0 +1,9 @@ +CORE +main.c +--dfcc main --enforce-contract f +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This test checks that verification fails when functions with assigns clauses are called within a loop and one of them does not obey its assigns clause. diff --git a/regression/contracts-dfcc/assigns_enforce_08/main.c b/regression/contracts-dfcc/assigns_enforce_08/main.c new file mode 100644 index 00000000000..8f562a91cee --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_08/main.c @@ -0,0 +1,17 @@ +void f1(int *x) __CPROVER_assigns(*x) +{ + int *a = x; + f2(&a); +} +void f2(int **y) __CPROVER_assigns(**y) +{ + **y = 5; +} + +int main() +{ + int n = 3; + f1(&n); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_08/test.desc b/regression/contracts-dfcc/assigns_enforce_08/test.desc new file mode 100644 index 00000000000..6936ddd4c53 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_08/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that verification succeeds when a function with an assigns +clause calls another with an additional level of indirection, and that +functions respects the assigns clause of the caller. diff --git a/regression/contracts-dfcc/assigns_enforce_09/main.c b/regression/contracts-dfcc/assigns_enforce_09/main.c new file mode 100644 index 00000000000..f0ddf187eca --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_09/main.c @@ -0,0 +1,17 @@ +void f1(int **x) __CPROVER_assigns(*x) +{ + f2(x); +} +void f2(int **y) __CPROVER_assigns(**y) +{ + **y = 5; +} + +int main() +{ + int p = 3; + int *q = &p; + f1(&q); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_09/test.desc b/regression/contracts-dfcc/assigns_enforce_09/test.desc new file mode 100644 index 00000000000..a75d1d66a9a --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_09/test.desc @@ -0,0 +1,9 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This test checks that verification fails when a function with an assigns clause calls another with an assigns clause that adds one too many dereferences. diff --git a/regression/contracts-dfcc/assigns_enforce_10/main.c b/regression/contracts-dfcc/assigns_enforce_10/main.c new file mode 100644 index 00000000000..232d68a4ea7 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_10/main.c @@ -0,0 +1,20 @@ +void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1) +{ + f2(x1, y1, z1); +} + +void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*y2, *z2) +{ + *y2 = 5; + *z2 = 5; +} + +int main() +{ + int p = 1; + int q = 2; + int r = 3; + f1(&p, &q, &r); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_10/test.desc b/regression/contracts-dfcc/assigns_enforce_10/test.desc new file mode 100644 index 00000000000..627c95d33ed --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_10/test.desc @@ -0,0 +1,9 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This test checks that verification fails when a function with an assigns clause calls another function with an assigns clause that is incompatible with the caller's assigns clause. diff --git a/regression/contracts-dfcc/assigns_enforce_11/main.c b/regression/contracts-dfcc/assigns_enforce_11/main.c new file mode 100644 index 00000000000..ac7a36c56d0 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_11/main.c @@ -0,0 +1,20 @@ +void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1) +{ + f2(x1, y1, z1); +} + +void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2) +{ + *y2 = 5; + *z2 = 5; +} + +int main() +{ + int p = 1; + int q = 2; + int r = 3; + f1(&p, &q, &r); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_11/test.desc b/regression/contracts-dfcc/assigns_enforce_11/test.desc new file mode 100644 index 00000000000..c707b222dc7 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_11/test.desc @@ -0,0 +1,9 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This test checks that verification fails when a function with an assigns clause calls another function with a compatible assigns clause, but the callee does not abide by the shared assigns clause. diff --git a/regression/contracts-dfcc/assigns_enforce_12/main.c b/regression/contracts-dfcc/assigns_enforce_12/main.c new file mode 100644 index 00000000000..0af9f968420 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_12/main.c @@ -0,0 +1,13 @@ +void f1(int *x) __CPROVER_assigns(*x) +{ + int *a = x; + *a = 5; +} + +int main() +{ + int n = 3; + f1(&n); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_12/test.desc b/regression/contracts-dfcc/assigns_enforce_12/test.desc new file mode 100644 index 00000000000..ca93247d8ce --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_12/test.desc @@ -0,0 +1,9 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that verification still succeeds if an expression in the assigns clause is written via an aliasing variable. diff --git a/regression/contracts-dfcc/assigns_enforce_13/main.c b/regression/contracts-dfcc/assigns_enforce_13/main.c new file mode 100644 index 00000000000..65e7795a3b7 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_13/main.c @@ -0,0 +1,14 @@ +void f1(int *x, int *y) __CPROVER_assigns(*y) +{ + int *a = x; + *a = 5; +} + +int main() +{ + int m = 3; + int n = 3; + f1(&n, &m); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_13/test.desc b/regression/contracts-dfcc/assigns_enforce_13/test.desc new file mode 100644 index 00000000000..c56e7843e73 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_13/test.desc @@ -0,0 +1,9 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This test checks that verification fails if an expression outside of the assigns clause is written via an aliasing local variable. diff --git a/regression/contracts-dfcc/assigns_enforce_14/main.c b/regression/contracts-dfcc/assigns_enforce_14/main.c new file mode 100644 index 00000000000..9d205896302 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_14/main.c @@ -0,0 +1,19 @@ +int z; + +// z is not assigned, but it *may* be assigned. +// The assigns clause does not need to exactly match the +// set of variables which are assigned in the function. +int foo(int *x) __CPROVER_assigns(z, *x) + __CPROVER_ensures(__CPROVER_return_value == *x + 5) +{ + *x = *x + 0; + return *x + 5; +} + +int main() +{ + int n = 4; + n = foo(&n); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_14/test.desc b/regression/contracts-dfcc/assigns_enforce_14/test.desc new file mode 100644 index 00000000000..1dd64604f87 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_14/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that verification succeeds if only expressions inside the assigns clause are assigned within the function. + +Note: For all 'enforce' tests, nothing can be assumed about the return value of the function (as the function call is not replaced at this point). + +To make such assumptions would cause verification to fail. diff --git a/regression/contracts-dfcc/assigns_enforce_15/main.c b/regression/contracts-dfcc/assigns_enforce_15/main.c new file mode 100644 index 00000000000..b940ae60b4a --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_15/main.c @@ -0,0 +1,35 @@ +int global = 0; + +int foo(int *x) __CPROVER_assigns(*x) + __CPROVER_ensures(__CPROVER_return_value == *x + 5) +{ + int z = 5; + int y = bar(&z); + return *x + 5; +} + +int bar(int *a) __CPROVER_ensures(__CPROVER_return_value >= 5) +{ + *a = *a + 2; + return *a; +} + +int baz() __CPROVER_ensures(__CPROVER_return_value == global) +{ + global = global + 1; + return global; +} + +void qux(void) __CPROVER_assigns() +{ + global = global + 1; +} + +int main() +{ + int n; + n = foo(&n); + n = baz(); + qux(); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_15/test-baz.desc b/regression/contracts-dfcc/assigns_enforce_15/test-baz.desc new file mode 100644 index 00000000000..8ff04fc5ef8 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_15/test-baz.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract baz +^\[baz.assigns.\d+\] line \d+ Check that global is assignable: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Checks whether verification fails when enforcing a contract +for functions, without assigns clauses, that modify an input. diff --git a/regression/contracts-dfcc/assigns_enforce_15/test-foo.desc b/regression/contracts-dfcc/assigns_enforce_15/test-foo.desc new file mode 100644 index 00000000000..9a7a3e9061c --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_15/test-foo.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +Checks whether verification fails when enforcing a contract +for functions, without assigns clauses, that modify an input. diff --git a/regression/contracts-dfcc/assigns_enforce_15/test-qux.desc b/regression/contracts-dfcc/assigns_enforce_15/test-qux.desc new file mode 100644 index 00000000000..cc6dde4c28c --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_15/test-qux.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract qux +^\[qux.assigns.\d+\] line \d+ Check that global is assignable: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Checks whether verification fails when enforcing a contract +for functions, without assigns clauses, that modify an input. diff --git a/regression/contracts-dfcc/assigns_enforce_16/main.c b/regression/contracts-dfcc/assigns_enforce_16/main.c new file mode 100644 index 00000000000..a042f3aaba7 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_16/main.c @@ -0,0 +1,16 @@ +void foo(int *xp) __CPROVER_assigns(*xp) +{ + { + int y; + y = 2; + } + int z = 3; + *xp = 1; +} + +int main() +{ + int *xp = malloc(sizeof(*xp)); + foo(xp); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_16/test.desc b/regression/contracts-dfcc/assigns_enforce_16/test.desc new file mode 100644 index 00000000000..467a9462968 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_16/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo _ --pointer-primitive-check +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether verification fails when enforcing a contract +for functions, without assigns clauses, that modify an input. diff --git a/regression/contracts-dfcc/assigns_enforce_17/main.c b/regression/contracts-dfcc/assigns_enforce_17/main.c new file mode 100644 index 00000000000..acd818b56d7 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_17/main.c @@ -0,0 +1,17 @@ +#include + +int x; + +void pure() __CPROVER_assigns() +{ + int x; + x++; +} + +int main() +{ + x = 0; + pure(); + assert(x == 0); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_17/test.desc b/regression/contracts-dfcc/assigns_enforce_17/test.desc new file mode 100644 index 00000000000..f3c2404e6af --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_17/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract pure +^EXIT=0$ +^SIGNAL=0$ +^\[main.assertion.\d+\] line \d+ assertion x \=\= 0: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether verification correctly distinguishes local variables +and global variables with same name when checking frame conditions. diff --git a/regression/contracts-dfcc/assigns_enforce_18/main.c b/regression/contracts-dfcc/assigns_enforce_18/main.c new file mode 100644 index 00000000000..47c0633b07d --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_18/main.c @@ -0,0 +1,43 @@ +#include +#include + +int x = 0; +int y = 0; + +void foo(int *xp, int *xq, int a) __CPROVER_assigns(*xp) +{ + a = 2; + int *yp = malloc(sizeof(int)); + free(yp); + int z = 3; + *xp = 1; + y = -1; +} + +void bar(int *a, int *b) __CPROVER_assigns(*b) __CPROVER_frees(a) +{ + free(a); + *b = 0; +} + +void baz(int *a, int *c) __CPROVER_assigns(*a) +{ + free(c); + *a = 0; +} + +int main() +{ + int z = 0; + foo(&x, &z, z); + assert(x == 1); + assert(y == -1); + assert(z == 0); + int *a = malloc(sizeof(*a)); + int b = 1; + bar(a, &b); + assert(b == 0); + int *c = malloc(sizeof(*c)); + baz(&y, c); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_18/test-bar.desc b/regression/contracts-dfcc/assigns_enforce_18/test-bar.desc new file mode 100644 index 00000000000..87c73afa6e5 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_18/test-bar.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract bar _ --pointer-primitive-check +^\[bar.assigns.\d+\] line 20 Check that \*b is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +Checks whether contract enforcement works with functions that deallocate memory. +We had problems before when freeing a variable, but still keeping it on +the writable set, which lead to deallocated variables issues. +Now, if a memory is deallocated, we remove it from the our freely assignable set. diff --git a/regression/contracts-dfcc/assigns_enforce_18/test-baz.desc b/regression/contracts-dfcc/assigns_enforce_18/test-baz.desc new file mode 100644 index 00000000000..4df2ffcc40a --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_18/test-baz.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --enforce-contract baz _ --pointer-primitive-check +^\[free.frees.\d+\].*Check that ptr is freeable: FAILURE +^\[baz.assigns.\d+\].*Check that \*a is assignable: SUCCESS$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Checks whether contract enforcement works with functions that deallocate memory. +We had problems before when freeing a variable, but still keeping it on +the writable set, which lead to deallocated variables issues. +Now, if a memory is deallocated, we remove it from the our freely assignable set. diff --git a/regression/contracts-dfcc/assigns_enforce_18/test-foo.desc b/regression/contracts-dfcc/assigns_enforce_18/test-foo.desc new file mode 100644 index 00000000000..fd4678a44d1 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_18/test-foo.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --enforce-contract foo _ --pointer-primitive-check +^\[foo.assigns.\d+\] line 13 Check that \*xp is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line 14 Check that y is assignable: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Checks whether contract enforcement works with functions that deallocate memory. +We had problems before when freeing a variable, but still keeping it on +the writable set, which lead to deallocated variables issues. +Now, if a memory is deallocated, we remove it from the our freely assignable set. diff --git a/regression/contracts-dfcc/assigns_enforce_19_a/main.c b/regression/contracts-dfcc/assigns_enforce_19_a/main.c new file mode 100644 index 00000000000..6aad0fca379 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_19_a/main.c @@ -0,0 +1,43 @@ +#include +#include + +const int MIN = 0; +const int MAX = 10; + +bool f_state_invariant(); + +bool f_state_transition(int old_state, int return_value) +{ + return; +} + +int f() + // clang-format off +__CPROVER_assigns() + +// state invariant: state variable is in [MIN,MAX] +__CPROVER_requires( + MIN <= __CPROVER_ID "f::1::a" && __CPROVER_ID "f::1::a" <= MAX) +__CPROVER_ensures( + MIN <= __CPROVER_ID "f::1::a" && __CPROVER_ID "f::1::a" <= MAX) + +// state tansition: state var is either incremented or stays the same +__CPROVER_ensures( + (__CPROVER_ID "f::1::a" == __CPROVER_old(__CPROVER_ID "f::1::a") + 1) || + (__CPROVER_ID "f::1::a" == __CPROVER_old(__CPROVER_ID "f::1::a"))) + +// state var value is returned +__CPROVER_ensures(__CPROVER_ID "f::1::a" == __CPROVER_return_value) +// clang-format on +{ + static int a = 0; + if(a < MAX) + a++; + return a; +} + +int main() +{ + f(); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_19_a/test.desc b/regression/contracts-dfcc/assigns_enforce_19_a/test.desc new file mode 100644 index 00000000000..b5b502a31b0 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_19_a/test.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --enforce-contract f +^\[f.assigns.\d+\] .* Check that a is assignable: SUCCESS$ +^\[f.postcondition.\d+\] .* Check ensures clause of contract contract::f for function f: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +This test checks that locally declared static variables are automatically +added to the write set of a function, and illustrates how the local static `a` +defined in `f` can be referred to in contract clauses using +__CPROVER_ID "f::1::a" to express state invariants and state transitions. diff --git a/regression/contracts-dfcc/assigns_enforce_19_b/main.c b/regression/contracts-dfcc/assigns_enforce_19_b/main.c new file mode 100644 index 00000000000..ebbf2a106bb --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_19_b/main.c @@ -0,0 +1,20 @@ +#include + +static int b = 0; +static int c = 0; + +int f(int *points_to_b, int *points_to_c) __CPROVER_assigns(b) +{ + // must pass + b = 1; + *points_to_b = 1; + // must fail + c = 2; + *points_to_c = 2; +} + +int main() +{ + f(&b, &c); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_19_b/test.desc b/regression/contracts-dfcc/assigns_enforce_19_b/test.desc new file mode 100644 index 00000000000..b383c12ad19 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_19_b/test.desc @@ -0,0 +1,17 @@ +CORE +main.c +--dfcc main --enforce-contract f +^\[f.assigns.\d+\] line \d+ Check that b is assignable: SUCCESS$ +^\[f.assigns.\d+\] line \d+ Check that \*points_to_b is assignable: SUCCESS$ +^\[f.assigns.\d+\] line \d+ Check that c is assignable: FAILURE$ +^\[f.assigns.\d+\] line \d+ Check that \*points_to_c is assignable: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Checks that: +- assignments to global static b declared in the assigns clause are +*not* detected as errors, when directly assigned or assigned through pointers. +- assignments to global static c *not* declared in the assigns clause are +detected as errors, when directly assigned or assigned through pointers. diff --git a/regression/contracts-dfcc/assigns_enforce_20/main.c b/regression/contracts-dfcc/assigns_enforce_20/main.c new file mode 100644 index 00000000000..5ff7211df3b --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_20/main.c @@ -0,0 +1,19 @@ +#include +#include + +int x = 0; + +void foo(int *y) __CPROVER_assigns() +{ + __CPROVER_havoc_object(y); + x = 2; +} + +int main() +{ + int *y = malloc(sizeof(*y)); + *y = 0; + foo(y); + assert(x == 2); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_20/test.desc b/regression/contracts-dfcc/assigns_enforce_20/test.desc new file mode 100644 index 00000000000..baaba4884c9 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_20/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=10$ +^SIGNAL=0$ +^\[foo.assigns.\d+\] line \d+ Check that x is assignable: FAILURE$ +^VERIFICATION FAILED$ +-- +-- +Checks whether contract enforcement works with __CPROVER_havoc_object. diff --git a/regression/contracts-dfcc/assigns_enforce_21/main.c b/regression/contracts-dfcc/assigns_enforce_21/main.c new file mode 100644 index 00000000000..d7ff8847fc4 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_21/main.c @@ -0,0 +1,42 @@ +#include +#include + +int x = 0; + +void quz() __CPROVER_assigns(x) __CPROVER_ensures(x == -1) +{ + x = -1; +} + +int baz() __CPROVER_assigns() +{ + return 5; +} + +void bar(int *y, int w) __CPROVER_assigns(*y) +{ + *y = 3; + w = baz(); + assert(w == 5); + quz(); +} + +void foo(int *y, int z) __CPROVER_assigns(*y) +{ + int w = 5; + assert(w == 5); + bar(y, w); + z = -2; +} + +int main() +{ + int *y = malloc(sizeof(*y)); + *y = 0; + int z = 1; + foo(y, z); + assert(x == -1); + assert(*y == 3); + assert(z == 1); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_21/test.desc b/regression/contracts-dfcc/assigns_enforce_21/test.desc new file mode 100644 index 00000000000..5098faf183e --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_21/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo --replace-call-with-contract quz +^\[bar.assigns.\d+\].*Check that \*y is assignable: SUCCESS$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Checks whether checks write set for sub-function calls. diff --git a/regression/contracts-dfcc/assigns_enforce_23/main.c b/regression/contracts-dfcc/assigns_enforce_23/main.c new file mode 100644 index 00000000000..1dba3cfb6da --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_23/main.c @@ -0,0 +1,41 @@ +#include +#include + +typedef struct +{ + uint8_t *buf; + size_t size; +} blob; + +void foo(blob *b, uint8_t *value) + // clang-format off +__CPROVER_requires(b->size == 5) +__CPROVER_assigns(__CPROVER_object_whole(b->buf)) +__CPROVER_assigns(__CPROVER_object_whole(value)) +__CPROVER_ensures(b->buf[0] == 1) +__CPROVER_ensures(b->buf[1] == 1) +__CPROVER_ensures(b->buf[2] == 1) +__CPROVER_ensures(b->buf[3] == 1) +__CPROVER_ensures(b->buf[4] == 1) +// clang-format on +{ + b->buf[0] = *value; + b->buf[1] = *value; + b->buf[2] = *value; + b->buf[3] = *value; + b->buf[4] = *value; + + *value = 2; +} + +int main() +{ + blob b; + b.size = 5; + b.buf = malloc(b.size * (sizeof(*(b.buf)))); + uint8_t value = 1; + + foo(&b, &value); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_23/test.desc b/regression/contracts-dfcc/assigns_enforce_23/test.desc new file mode 100644 index 00000000000..59b3a464de9 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_23/test.desc @@ -0,0 +1,9 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that __CPROVER_object_whole can be used both on arrays and scalars. diff --git a/regression/contracts-dfcc/assigns_enforce_address_of/main.c b/regression/contracts-dfcc/assigns_enforce_address_of/main.c new file mode 100644 index 00000000000..c7762c97037 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_address_of/main.c @@ -0,0 +1,16 @@ +#include +#include +#include + +int foo(int *x) __CPROVER_assigns(&x) +{ + *x = 0; + return 0; +} + +int main() +{ + int x; + foo(&x); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_address_of/test.desc b/regression/contracts-dfcc/assigns_enforce_address_of/test.desc new file mode 100644 index 00000000000..49df58734e7 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_address_of/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=(1|64)$ +^SIGNAL=0$ +^.*error: assigns clause target must be a non-void lvalue, a call to __CPROVER_POINTER_OBJECT or a call to a function returning void$ +^CONVERSION ERROR$ +-- +-- +Check that address_of expressions are rejected in assigns clauses. diff --git a/regression/contracts-dfcc/assigns_enforce_arrays_01/main.c b/regression/contracts-dfcc/assigns_enforce_arrays_01/main.c new file mode 100644 index 00000000000..ee8405c3ce4 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_arrays_01/main.c @@ -0,0 +1,16 @@ +void f1(int a[], int len) __CPROVER_assigns() +{ + int b[10]; + a = b; + int *indr = a + 2; + *indr = 5; + a[5] = 2; +} + +int main() +{ + int arr[10]; + f1(arr, 10); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_arrays_01/test.desc b/regression/contracts-dfcc/assigns_enforce_arrays_01/test.desc new file mode 100644 index 00000000000..165ce080db3 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_arrays_01/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks their assigns clause behavior when it reasons (indirectly) +over a freshly-allocated variable. diff --git a/regression/contracts-dfcc/assigns_enforce_arrays_02/main.c b/regression/contracts-dfcc/assigns_enforce_arrays_02/main.c new file mode 100644 index 00000000000..59f8aa213b5 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_arrays_02/main.c @@ -0,0 +1,25 @@ +#include +#include + +int *arr; + +void f1(int *a, int len) __CPROVER_assigns(*a) +{ + a[0] = 0; + a[5] = 5; +} + +void f2(int *a, int len) __CPROVER_assigns(__CPROVER_object_whole(a)) + __CPROVER_frees(a) +{ + a[0] = 0; + a[5] = 5; + free(a); +} + +int main() +{ + arr = malloc(100 * sizeof(int)); + f1(arr, 100); + f2(arr, 100); +} diff --git a/regression/contracts-dfcc/assigns_enforce_arrays_02/test-f1.desc b/regression/contracts-dfcc/assigns_enforce_arrays_02/test-f1.desc new file mode 100644 index 00000000000..7f208f6f3c1 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_arrays_02/test-f1.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^\[f1.assigns.\d+\] line 8 Check that a\[.*0\] is assignable: SUCCESS$ +^\[f1.assigns.\d+\] line 9 Check that a\[.*5\] is assignable: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This test ensures that assigns clauses correctly check for global variables, +and access using *ptr vs POINTER_OBJECT(ptr). diff --git a/regression/contracts-dfcc/assigns_enforce_arrays_02/test-f2.desc b/regression/contracts-dfcc/assigns_enforce_arrays_02/test-f2.desc new file mode 100644 index 00000000000..7f65ab9c23f --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_arrays_02/test-f2.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract f2 +^\[f2.assigns.\d+\] line \d+ Check that a\[.*0\] is assignable: SUCCESS$ +^\[f2.assigns.\d+\] line \d+ Check that a\[.*5\] is assignable: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test ensures that assigns clauses correctly check for global variables, +and access using *ptr vs POINTER_OBJECT(ptr). diff --git a/regression/contracts-dfcc/assigns_enforce_arrays_03/main.c b/regression/contracts-dfcc/assigns_enforce_arrays_03/main.c new file mode 100644 index 00000000000..2511fbe4103 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_arrays_03/main.c @@ -0,0 +1,12 @@ +void assign_out_under(int a[], int len) __CPROVER_assigns() +{ + a[1] = 5; +} + +int main() +{ + int arr[10]; + assign_out_under(arr, 10); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_arrays_03/test.desc b/regression/contracts-dfcc/assigns_enforce_arrays_03/test.desc new file mode 100644 index 00000000000..58533c9a316 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_arrays_03/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract assign_out_under +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +Checks whether verification fails when a function has an array +as parameter, an empty assigns clause and attempts to modify the object +pointed to by the pointer. diff --git a/regression/contracts-dfcc/assigns_enforce_arrays_04/main.c b/regression/contracts-dfcc/assigns_enforce_arrays_04/main.c new file mode 100644 index 00000000000..0ac8d6f93a6 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_arrays_04/main.c @@ -0,0 +1,19 @@ +void assigns_single(int a[], int len) +{ + int i; + __CPROVER_assume(0 <= i && i < len); + a[i] = 0; +} + +void uses_assigns(int a[], int len) __CPROVER_assigns(__CPROVER_object_whole(a)) +{ + assigns_single(a, len); +} + +int main() +{ + int arr[10]; + uses_assigns(arr, 10); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_arrays_04/test.desc b/regression/contracts-dfcc/assigns_enforce_arrays_04/test.desc new file mode 100644 index 00000000000..e701be665e9 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_arrays_04/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract uses_assigns +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether verification succeeds when an array is assigned through +calls to functions with array assigns clauses which are compatible with +that of the caller. diff --git a/regression/contracts-dfcc/assigns_enforce_arrays_05/main.c b/regression/contracts-dfcc/assigns_enforce_arrays_05/main.c new file mode 100644 index 00000000000..01ec9cf6b98 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_arrays_05/main.c @@ -0,0 +1,23 @@ +void assigns_ptr(int *x) +{ + *x = 200; +} + +void uses_assigns(int a[], int i, int len) + // clang-format off + __CPROVER_requires(0 <= i && i < len) + __CPROVER_assigns(*(&a[i])) +// clang-format on +{ + int *ptr = &(a[i]); + assigns_ptr(ptr); +} + +int main() +{ + int arr[10]; + int i; + __CPROVER_assume(0 <= i && i < 10); + uses_assigns(arr, i, 10); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_arrays_05/test.desc b/regression/contracts-dfcc/assigns_enforce_arrays_05/test.desc new file mode 100644 index 00000000000..34c6a0d0873 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_arrays_05/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract uses_assigns +^\[assigns_ptr.assigns.\d+\] line \d+ Check that \*x is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +Checks whether verification succeeds for array elements at a symbolic index. diff --git a/regression/contracts-dfcc/assigns_enforce_arrays_10/main.c b/regression/contracts-dfcc/assigns_enforce_arrays_10/main.c new file mode 100644 index 00000000000..c8c8c16bf89 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_arrays_10/main.c @@ -0,0 +1,24 @@ +#include + +int idx = 4; + +int nextIdx() __CPROVER_assigns(idx) +{ + idx++; + return idx; +} + +void f1(int a[], int len) __CPROVER_assigns(*a) +{ + a[nextIdx()] = 5; +} + +int main() +{ + int arr[10]; + f1(arr, 10); + + assert(idx == 5); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_arrays_10/test.desc b/regression/contracts-dfcc/assigns_enforce_arrays_10/test.desc new file mode 100644 index 00000000000..fcb0640119a --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_arrays_10/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This test also ensures that assigns clauses correctly check for global +variables modified by subcalls. In this case, since the assigns clause +doesn't include the modified global variable, the verification should failed. diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_function_call_condition/main.c b/regression/contracts-dfcc/assigns_enforce_conditional_function_call_condition/main.c new file mode 100644 index 00000000000..26bb269263e --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_function_call_condition/main.c @@ -0,0 +1,33 @@ +#include + +bool cond() +{ + return true; +} + +int foo(int a, int *x, int *y) + // clang-format off +__CPROVER_assigns(a && cond() : *x) +// clang-format on +{ + if(a) + { + if(cond()) + *x = 0; // must pass + } + else + { + *x = 0; // must fail + } + return 0; +} + +int main() +{ + bool a; + int x; + int y; + + foo(a, &x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_function_call_condition/test.desc b/regression/contracts-dfcc/assigns_enforce_conditional_function_call_condition/test.desc new file mode 100644 index 00000000000..2b8d3e05bcc --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_function_call_condition/test.desc @@ -0,0 +1,17 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^main.c function foo$ +^\[foo.assigns.\d+\] line 16 Check that \*x is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line 20 Check that \*x is assignable: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Checks that function calls are allowed in conditions. +Remark: we allow function calls without further checks for now because they +are mandatory for some applications. +The next step must be to check that the called functions have a contract +with an empty assigns clause and that they indeed satisfy their contract +using a CI check. diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_lvalue/main.c b/regression/contracts-dfcc/assigns_enforce_conditional_lvalue/main.c new file mode 100644 index 00000000000..a40a3d2b837 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_lvalue/main.c @@ -0,0 +1,29 @@ +#include + +int foo(bool a, int *x, int *y) __CPROVER_assigns(a : *x; !a : *y) +{ + if(a) + { + *x = 0; // must pass + *y = 0; // must fail + } + else + { + *x = 0; // must fail + *y = 0; // must pass + } + + *x = 0; // must fail + *y = 0; // must fail + return 0; +} + +int main() +{ + bool a; + int x; + int y; + + foo(a, &x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_lvalue/test.desc b/regression/contracts-dfcc/assigns_enforce_conditional_lvalue/test.desc new file mode 100644 index 00000000000..299ea4bf7cd --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_lvalue/test.desc @@ -0,0 +1,17 @@ +CORE +main.c +--dfcc main --enforce-contract foo +main.c function foo +^\[foo.assigns.\d+\] line 7 Check that \*x is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line 8 Check that \*y is assignable: FAILURE$ +^\[foo.assigns.\d+\] line 12 Check that \*x is assignable: FAILURE$ +^\[foo.assigns.\d+\] line 13 Check that \*y is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line 16 Check that \*x is assignable: FAILURE$ +^\[foo.assigns.\d+\] line 17 Check that \*y is assignable: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Check that lvalue conditional assigns clause targets +match with control flow path conditions. diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_lvalue_list/main.c b/regression/contracts-dfcc/assigns_enforce_conditional_lvalue_list/main.c new file mode 100644 index 00000000000..71cd9844974 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_lvalue_list/main.c @@ -0,0 +1,29 @@ +#include + +int foo(bool a, int *x, int *y) __CPROVER_assigns(a : *x, *y) +{ + if(a) + { + *x = 0; // must pass + *y = 0; // must pass + } + else + { + *x = 0; // must fail + *y = 0; // must fail + } + + *x = 0; // must fail + *y = 0; // must fail + return 0; +} + +int main() +{ + bool a; + int x; + int y; + + foo(a, &x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_lvalue_list/test.desc b/regression/contracts-dfcc/assigns_enforce_conditional_lvalue_list/test.desc new file mode 100644 index 00000000000..d14dfada8dd --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_lvalue_list/test.desc @@ -0,0 +1,17 @@ +CORE +main.c +--dfcc main --enforce-contract foo +main.c function foo +^\[foo.assigns.\d+\] line 7 Check that \*x is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line 8 Check that \*y is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line 12 Check that \*x is assignable: FAILURE$ +^\[foo.assigns.\d+\] line 13 Check that \*y is assignable: FAILURE$ +^\[foo.assigns.\d+\] line 16 Check that \*x is assignable: FAILURE$ +^\[foo.assigns.\d+\] line 17 Check that \*y is assignable: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Check that conditional assigns clause `c ? {lvalue, ..}` +match with control flow path conditions. diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target/main.c b/regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target/main.c new file mode 100644 index 00000000000..ca2fb2d98a0 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target/main.c @@ -0,0 +1,21 @@ +#include + +int *identity(int *ptr) +{ + return ptr; +} + +int foo(bool a, int *x, int offset) __CPROVER_assigns(a : !x) +{ + return 0; +} + +int main() +{ + bool a; + int x; + int y; + + foo(a, &x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target/test.desc b/regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target/test.desc new file mode 100644 index 00000000000..b9ee423738e --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--enforce-contract foo +^.*error: assigns clause target must be a non-void lvalue, a call to __CPROVER_POINTER_OBJECT or a call to a function returning void$ +^CONVERSION ERROR +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +Checks that non-lvalue targets are rejected from conditional targets. diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target_list/main.c b/regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target_list/main.c new file mode 100644 index 00000000000..3021057d429 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target_list/main.c @@ -0,0 +1,21 @@ +#include + +int *identity(int *ptr) +{ + return ptr; +} + +int foo(bool a, int *x, int *y) __CPROVER_assigns(a : !x, *y) +{ + return 0; +} + +int main() +{ + bool a; + int x; + int y; + + foo(a, &x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target_list/test.desc b/regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target_list/test.desc new file mode 100644 index 00000000000..b9ee423738e --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_non_lvalue_target_list/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--enforce-contract foo +^.*error: assigns clause target must be a non-void lvalue, a call to __CPROVER_POINTER_OBJECT or a call to a function returning void$ +^CONVERSION ERROR +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +Checks that non-lvalue targets are rejected from conditional targets. diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_pointer_object/main.c b/regression/contracts-dfcc/assigns_enforce_conditional_pointer_object/main.c new file mode 100644 index 00000000000..27857e726a9 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_pointer_object/main.c @@ -0,0 +1,35 @@ +#include + +int foo(bool a, char *x, char *y) + // clang-format off + __CPROVER_assigns( + a: __CPROVER_object_whole(x); + !a: __CPROVER_object_whole(y); + ) +// clang-format on +{ + if(a) + { + x[0] = 0; // must pass + y[0] = 0; // must fail + } + else + { + x[0] = 0; // must fail + y[0] = 0; // must pass + } + + x[0] = 0; // must fail + y[0] = 0; // must fail + return 0; +} + +int main() +{ + bool a; + char x[2]; + char y[2]; + + foo(a, &x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_pointer_object/test.desc b/regression/contracts-dfcc/assigns_enforce_conditional_pointer_object/test.desc new file mode 100644 index 00000000000..1cc5dd49d4a --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_pointer_object/test.desc @@ -0,0 +1,17 @@ +CORE +main.c +--dfcc main --enforce-contract foo +main.c function foo +^\[foo.assigns.\d+\] line 13 Check that x\[\(signed (long )?long int\)0\] is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line 14 Check that y\[\(signed (long )?long int\)0\] is assignable: FAILURE$ +^\[foo.assigns.\d+\] line 18 Check that x\[\(signed (long )?long int\)0\] is assignable: FAILURE$ +^\[foo.assigns.\d+\] line 19 Check that y\[\(signed (long )?long int\)0\] is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line 22 Check that x\[\(signed (long )?long int\)0\] is assignable: FAILURE$ +^\[foo.assigns.\d+\] line 23 Check that y\[\(signed (long )?long int\)0\] is assignable: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Check that conditional assigns clause `c ? __CPROVER_object_whole((p)` +match with control flow path conditions. diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_pointer_object_list/main.c b/regression/contracts-dfcc/assigns_enforce_conditional_pointer_object_list/main.c new file mode 100644 index 00000000000..cc6cd3df511 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_pointer_object_list/main.c @@ -0,0 +1,34 @@ +#include + +int foo(bool a, char *x, char *y) + // clang-format off +__CPROVER_assigns( + a : __CPROVER_object_whole(x), __CPROVER_object_whole(y) +) +// clang-format on +{ + if(a) + { + x[0] = 0; // must pass + y[0] = 0; // must pass + } + else + { + x[0] = 0; // must fail + y[0] = 0; // must fail + } + + x[0] = 0; // must fail + y[0] = 0; // must fail + return 0; +} + +int main() +{ + bool a; + char x[2]; + char y[2]; + + foo(a, &x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_pointer_object_list/test.desc b/regression/contracts-dfcc/assigns_enforce_conditional_pointer_object_list/test.desc new file mode 100644 index 00000000000..3c494980417 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_pointer_object_list/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --enforce-contract foo +main.c function foo +^\[foo.assigns.\d+\] line 12 Check that x\[\(signed (long )?long int\)0\] is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line 13 Check that y\[\(signed (long )?long int\)0\] is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line 17 Check that x\[\(signed (long )?long int\)0\] is assignable: FAILURE$ +^\[foo.assigns.\d+\] line 18 Check that y\[\(signed (long )?long int\)0\] is assignable: FAILURE$ +^\[foo.assigns.\d+\] line 21 Check that x\[\(signed (long )?long int\)0\] is assignable: FAILURE$ +^\[foo.assigns.\d+\] line 22 Check that y\[\(signed (long )?long int\)0\] is assignable: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Check that conditional target groups match with control flow path conditions. diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_condition/main.c b/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_condition/main.c new file mode 100644 index 00000000000..3242a0914b8 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_condition/main.c @@ -0,0 +1,20 @@ +#include + +int foo(int a, int *x, int *y) __CPROVER_assigns(a++ : *x) +{ + if(a) + { + *x = 0; + } + return 0; +} + +int main() +{ + bool a; + int x; + int y; + + foo(a, &x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_condition/test.desc b/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_condition/test.desc new file mode 100644 index 00000000000..ebf88009775 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_condition/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^.* error: side-effects not allowed in assigns clause conditions$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +Checks that side-effect expressions in target conditions cause hard errors. diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target/main.c b/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target/main.c new file mode 100644 index 00000000000..3fe8b0569c8 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target/main.c @@ -0,0 +1,16 @@ +#include + +int foo(bool a, int *x, int *y) __CPROVER_assigns(a : *x++) +{ + return 0; +} + +int main() +{ + bool a; + int x; + int y; + + foo(a, &x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target/test.desc b/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target/test.desc new file mode 100644 index 00000000000..3da73390e56 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^.* error: side-effects not allowed in assigns clause targets$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +Checks that side-effect expressions are rejected from conditional targets. diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target_list/main.c b/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target_list/main.c new file mode 100644 index 00000000000..a83f23b4a10 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target_list/main.c @@ -0,0 +1,16 @@ +#include + +int foo(bool a, int *x, int *y) __CPROVER_assigns(a : *x++, *y) +{ + return 0; +} + +int main() +{ + bool a; + int x; + int y; + + foo(a, &x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target_list/test.desc b/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target_list/test.desc new file mode 100644 index 00000000000..3da73390e56 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_side_effect_target_list/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^.* error: side-effects not allowed in assigns clause targets$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +Checks that side-effect expressions are rejected from conditional targets. diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_ternary_target/main.c b/regression/contracts-dfcc/assigns_enforce_conditional_ternary_target/main.c new file mode 100644 index 00000000000..54a0fb18a54 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_ternary_target/main.c @@ -0,0 +1,16 @@ +#include + +int foo(bool a, int *x, int *y) __CPROVER_assigns(a : (x ? *x : *y)) +{ + return 0; +} + +int main() +{ + bool a; + int x; + int y; + + foo(a, &x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_ternary_target/test.desc b/regression/contracts-dfcc/assigns_enforce_conditional_ternary_target/test.desc new file mode 100644 index 00000000000..060b71aab96 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_ternary_target/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^.* error: ternary expressions not allowed in assigns clause targets$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +Checks that ternary expressions are rejected from conditional targets. diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_ternary_target_list/main.c b/regression/contracts-dfcc/assigns_enforce_conditional_ternary_target_list/main.c new file mode 100644 index 00000000000..9ac5893434e --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_ternary_target_list/main.c @@ -0,0 +1,16 @@ +#include + +int foo(bool a, int *x, int *y) __CPROVER_assigns(a : *x, (x ? *x : *y)) +{ + return 0; +} + +int main() +{ + bool a; + int x; + int y; + + foo(a, &x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_ternary_target_list/test.desc b/regression/contracts-dfcc/assigns_enforce_conditional_ternary_target_list/test.desc new file mode 100644 index 00000000000..060b71aab96 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_ternary_target_list/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^.* error: ternary expressions not allowed in assigns clause targets$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +Checks that ternary expressions are rejected from conditional targets. diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_unions/main.c b/regression/contracts-dfcc/assigns_enforce_conditional_unions/main.c new file mode 100644 index 00000000000..87302915af3 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_unions/main.c @@ -0,0 +1,118 @@ +#include + +typedef struct CTX +{ + int data[16]; +} CTX; + +union low_level_t +{ + CTX md5; +}; + +typedef struct HIGH_LEVEL_MD +{ + unsigned long flags; +} HIGH_LEVEL_MD; + +typedef struct HIGH_LEVEL_MD_CTX +{ + HIGH_LEVEL_MD *digest; + unsigned long flags; +} HIGH_LEVEL_MD_CTX; + +struct high_level_digest_t +{ + HIGH_LEVEL_MD_CTX *ctx; +}; + +struct high_level_t +{ + struct high_level_digest_t first; + struct high_level_digest_t second; +}; + +typedef struct state_t +{ + union + { + union low_level_t low_level; + struct high_level_t high_level; + } digest; +} state_t; + +bool nondet_bool(); + +bool is_high_level() +{ + static bool latch = false; + static bool once = false; + if(!once) + { + latch = nondet_bool(); + once = true; + } + return latch; +} + +int update(state_t *state, bool also_second) + // clang-format off +__CPROVER_requires(__CPROVER_is_fresh(state, sizeof(*state))) +__CPROVER_requires( + is_high_level() ==> + __CPROVER_is_fresh(state->digest.high_level.first.ctx, + sizeof(*(state->digest.high_level.first.ctx))) && + __CPROVER_is_fresh(state->digest.high_level.first.ctx->digest, + sizeof(*(state->digest.high_level.first.ctx->digest)))) +__CPROVER_requires( + is_high_level() && also_second ==> + __CPROVER_is_fresh(state->digest.high_level.second.ctx, + sizeof(*(state->digest.high_level.second.ctx))) && + __CPROVER_is_fresh(state->digest.high_level.second.ctx->digest, + sizeof(*(state->digest.high_level.second.ctx->digest)))) +__CPROVER_assigns( + is_high_level(): + __CPROVER_object_whole(state->digest.high_level.first.ctx->digest), + state->digest.high_level.first.ctx->flags; + is_high_level() && also_second: + __CPROVER_object_whole(state->digest.high_level.second.ctx->digest), + state->digest.high_level.second.ctx->flags; +) +// clang-format on +{ + if(is_high_level()) + { + // must pass + __CPROVER_havoc_object(state->digest.high_level.first.ctx->digest); + state->digest.high_level.first.ctx->flags = 0; + + if(also_second) + { + // must pass + __CPROVER_havoc_object(state->digest.high_level.second.ctx->digest); + state->digest.high_level.second.ctx->flags = 0; + } + + // must fail + __CPROVER_havoc_object(state->digest.high_level.second.ctx->digest); + state->digest.high_level.second.ctx->flags = 0; + } + + // must fail + __CPROVER_havoc_object(state->digest.high_level.first.ctx->digest); + state->digest.high_level.first.ctx->flags = 0; + + // must fail + __CPROVER_havoc_object(state->digest.high_level.second.ctx->digest); + state->digest.high_level.second.ctx->flags = 0; + + return 0; +} + +int main() +{ + state_t *state; + bool also_second; + update(state, also_second); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_unions/test.desc b/regression/contracts-dfcc/assigns_enforce_conditional_unions/test.desc new file mode 100644 index 00000000000..74afb7c85e2 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_unions/test.desc @@ -0,0 +1,17 @@ +CORE +main.c +--dfcc main --enforce-contract update _ --pointer-check --pointer-overflow-check --signed-overflow-check --unsigned-overflow-check --conversion-check +^\[is_high_level.assigns.\d+\] line 52 Check that latch is assignable: SUCCESS$ +^\[is_high_level.assigns.\d+\] line 53 Check that once is assignable: SUCCESS$ +^\[update.assigns.\d+\] line 87 Check that state->digest.high_level.first.ctx->flags is assignable: SUCCESS$ +^\[update.assigns.\d+\] line 93 Check that state->digest.high_level.second.ctx->flags is assignable: SUCCESS$ +^\[update.assigns.\d+\] line 98 Check that state->digest.high_level.second.ctx->flags is assignable: FAILURE$ +^\[update.assigns.\d+\] line 103 Check that state->digest.high_level.first.ctx->flags is assignable: FAILURE$ +^\[update.assigns.\d+\] line 107 Check that state->digest.high_level.second.ctx->flags is assignable: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Tests that conditional assignments to whole objects and lvalues are supported, +when conditions determine how to interpret a union type. diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_void_target/main.c b/regression/contracts-dfcc/assigns_enforce_conditional_void_target/main.c new file mode 100644 index 00000000000..5eb7ce636b4 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_void_target/main.c @@ -0,0 +1,15 @@ +#include + +int foo(bool a, void *x) __CPROVER_assigns(a : *x) +{ + return 0; +} + +int main() +{ + bool a; + int x; + + foo(a, &x); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_void_target/test.desc b/regression/contracts-dfcc/assigns_enforce_conditional_void_target/test.desc new file mode 100644 index 00000000000..b275f2d9be1 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_void_target/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^.* error: (dereferencing void pointer|lvalue expressions with void type not allowed in assigns clauses)$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +Checks that void-typed expressions are rejected from conditional targets. diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_void_target_list/main.c b/regression/contracts-dfcc/assigns_enforce_conditional_void_target_list/main.c new file mode 100644 index 00000000000..0d6c98acd68 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_void_target_list/main.c @@ -0,0 +1,16 @@ +#include + +int foo(bool a, void *x, int *y) __CPROVER_assigns(a : *x, *y) +{ + return 0; +} + +int main() +{ + bool a; + int x; + + foo(a, &x, &x); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_conditional_void_target_list/test.desc b/regression/contracts-dfcc/assigns_enforce_conditional_void_target_list/test.desc new file mode 100644 index 00000000000..b275f2d9be1 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_conditional_void_target_list/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^.* error: (dereferencing void pointer|lvalue expressions with void type not allowed in assigns clauses)$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +Checks that void-typed expressions are rejected from conditional targets. diff --git a/regression/contracts-dfcc/assigns_enforce_detect_local_statics/main.c b/regression/contracts-dfcc/assigns_enforce_detect_local_statics/main.c new file mode 100644 index 00000000000..3b637dd2f59 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_detect_local_statics/main.c @@ -0,0 +1,31 @@ +#include +#include + +static int x; +static int xx; + +void foo() +{ + int *y = &x; + int *yy = &xx; + + static int x; + // must pass (modifies local static) + x++; + + // must pass (modifies assignable global static ) + (*y)++; + + // must fail (modifies non-assignable global static) + (*yy)++; +} + +void bar() __CPROVER_assigns(x) +{ + foo(); +} + +int main() +{ + bar(); +} diff --git a/regression/contracts-dfcc/assigns_enforce_detect_local_statics/test.desc b/regression/contracts-dfcc/assigns_enforce_detect_local_statics/test.desc new file mode 100644 index 00000000000..097f37d6881 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_detect_local_statics/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract bar +^\[foo.assigns.\d+\] line 17 Check that \*y is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line 20 Check that \*yy is assignable: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Checks whether static local and global variables are correctly tracked +in assigns clause verification, accross subfunction calls. diff --git a/regression/contracts-dfcc/assigns_enforce_detect_replaced_local_statics/main.c b/regression/contracts-dfcc/assigns_enforce_detect_replaced_local_statics/main.c new file mode 100644 index 00000000000..d9db6026342 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_detect_replaced_local_statics/main.c @@ -0,0 +1,33 @@ +#include +#include +#include + +int baz() __CPROVER_requires(true) __CPROVER_ensures(true) __CPROVER_assigns() +{ + static int __local_static = 0; + __local_static = 0; + + return __local_static; +} + +int bar() __CPROVER_requires(true) __CPROVER_ensures(true) __CPROVER_assigns() +{ + static int __local_static_arr[2]; + __local_static_arr[0] = 0; + __local_static_arr[1] = 0; + + baz(); + return __local_static_arr[0] | __local_static_arr[1]; +} + +void foo() __CPROVER_requires(true) __CPROVER_ensures(true) __CPROVER_assigns() +{ + bar(); +} + +int main() +{ + foo(); + __CPROVER_assert(baz() == 0, "expecting FAILURE"); + __CPROVER_assert(bar() == 0, "expecting FAILURE"); +} diff --git a/regression/contracts-dfcc/assigns_enforce_detect_replaced_local_statics/test.desc b/regression/contracts-dfcc/assigns_enforce_detect_replaced_local_statics/test.desc new file mode 100644 index 00000000000..57deaaacb87 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_detect_replaced_local_statics/test.desc @@ -0,0 +1,21 @@ +CORE +main.c +--dfcc main --replace-call-with-contract bar --replace-call-with-contract baz --enforce-contract foo _ --pointer-check +^\[main.assertion.\d+\] line \d+ expecting FAILURE: FAILURE$ +^\[main.assertion.\d+\] line \d+ expecting FAILURE: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +This test checks that locally declared static variables are correctly detected +and tracked when function contract replacement is used. +Here, baz declares a local static variable, +bar calls baz, and we replace the call to baz in bar. +bar also declares a local static array variable. +the call to bar is replaced by its contract in foo. +We see that in foo, two assignments to these local statics are checked, +showing that the replacement of bar in foo is modeled as nondet assignments. +We also see that these assignments are succesfully checked against the empty +assigns clause of foo, which shows that they are automatically propagated to +the assigns clause of foo as expected. diff --git a/regression/contracts-dfcc/assigns_enforce_free_dead/main.c b/regression/contracts-dfcc/assigns_enforce_free_dead/main.c new file mode 100644 index 00000000000..3d634a39a31 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_free_dead/main.c @@ -0,0 +1,44 @@ +#include +#include + +int foo(int *x, int **y) __CPROVER_assigns(*x; y : *y; y && *y : **y) +{ + *x = 0; // must pass + + if(y && *y) + **y = 0; // must yass + + if(nondet_int()) + { + int z = 1; + + if(y) + *y = &z; + } + // *y now yossibly yoints to a DEAD memory location + + if(y && *y) + **y = 0; // attemys to assign to the dead y location, must fail + + if(nondet_int()) + { + int *z = malloc(sizeof(int)); + x = &(*z); + if(x) + *x = 0; + __CPROVER_deallocate(z); + } + // now is possibly yoints to a deallocated object, must fail + if(x) + *x = 0; +} + +int main() +{ + int _x; + int *x = &x; + int __y; + int *_y = &__y; + int **y = &_y; + foo(x, y); +} diff --git a/regression/contracts-dfcc/assigns_enforce_free_dead/test.desc b/regression/contracts-dfcc/assigns_enforce_free_dead/test.desc new file mode 100644 index 00000000000..07703974f08 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_free_dead/test.desc @@ -0,0 +1,14 @@ +CORE +main.c +--malloc-may-fail --malloc-fail-null --dfcc main --enforce-contract foo _ --pointer-primitive-check +^\[foo.assigns.\d+\] line 6 Check that \*x is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line 9 Check that \*\(\*y\) is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line 21 Check that \*\(\*y\) is assignable: FAILURE$ +^\[foo.assigns.\d+\] line 33 Check that \*x is assignable: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +Checks that assignments to potentially freed or DEAD memory locations are +detected as errors. diff --git a/regression/contracts-dfcc/assigns_enforce_function_calls/main.c b/regression/contracts-dfcc/assigns_enforce_function_calls/main.c new file mode 100644 index 00000000000..c4ce468f413 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_function_calls/main.c @@ -0,0 +1,16 @@ +int *bar(int *x) +{ + return *x; +} + +int foo(int *x) __CPROVER_assigns(bar(x)) +{ + *x = 0; + return 0; +} + +int main() +{ + int x; + foo(&x); +} diff --git a/regression/contracts-dfcc/assigns_enforce_function_calls/test.desc b/regression/contracts-dfcc/assigns_enforce_function_calls/test.desc new file mode 100644 index 00000000000..f0a71683444 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_function_calls/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=(1|64)$ +^SIGNAL=0$ +^.*error: expecting void return type for function 'bar' called in assigns clause$ +^CONVERSION ERROR$ +-- +-- +Check that non-void function call expressions are rejected in assigns clauses. diff --git a/regression/contracts-dfcc/assigns_enforce_function_calls_ignored/main.c b/regression/contracts-dfcc/assigns_enforce_function_calls_ignored/main.c new file mode 100644 index 00000000000..01faaaa4d0b --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_function_calls_ignored/main.c @@ -0,0 +1,17 @@ +void bar(int *x) +{ + if(x) + __CPROVER_typed_target(x); +} + +int foo(int *x) __CPROVER_assigns(bar(x)) +{ + *x = 0; + return 0; +} + +int main() +{ + int x; + foo(&x); +} diff --git a/regression/contracts-dfcc/assigns_enforce_function_calls_ignored/test.desc b/regression/contracts-dfcc/assigns_enforce_function_calls_ignored/test.desc new file mode 100644 index 00000000000..2b8cf826108 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_function_calls_ignored/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--enforce-contract foo +^call to function 'bar' in assigns clause not supported yet$ +^EXIT=(127|134)$ +^SIGNAL=0$ +-- +-- +Check that void function call expressions in assigns clauses make +instrumentation fail. diff --git a/regression/contracts-dfcc/assigns_enforce_functions_in_contracts/header.h b/regression/contracts-dfcc/assigns_enforce_functions_in_contracts/header.h new file mode 100644 index 00000000000..fd56abc45ba --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_functions_in_contracts/header.h @@ -0,0 +1,12 @@ +#include "utility.h" + +#include +#include + +int foo(int *x) __CPROVER_requires(s2n_result_is_ok(validity3(x))) + __CPROVER_assigns(*x) __CPROVER_ensures( + __CPROVER_return_value == *x + 5 && s2n_result_is_ok(validity3(x))) +{ + *x = *x + 0; + return *x + 5; +} diff --git a/regression/contracts-dfcc/assigns_enforce_functions_in_contracts/main.c b/regression/contracts-dfcc/assigns_enforce_functions_in_contracts/main.c new file mode 100644 index 00000000000..18f577178e7 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_functions_in_contracts/main.c @@ -0,0 +1,9 @@ +#include "header.h" + +int main() +{ + int *n = malloc(sizeof(*n)); + *n = foo(n); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_functions_in_contracts/test.desc b/regression/contracts-dfcc/assigns_enforce_functions_in_contracts/test.desc new file mode 100644 index 00000000000..692d283d68e --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_functions_in_contracts/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks whether verification succeeds when requires and ensures +contain functions. + +Note: We still don't check for function purity, i.e., +functions in contracts must only work as predicates. diff --git a/regression/contracts-dfcc/assigns_enforce_functions_in_contracts/utility.h b/regression/contracts-dfcc/assigns_enforce_functions_in_contracts/utility.h new file mode 100644 index 00000000000..e7ab9b16956 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_functions_in_contracts/utility.h @@ -0,0 +1,40 @@ +#include +#include + +/* Function return code */ +#define S2N_SUCCESS 0 +#define S2N_FAILURE -1 + +/* A value which indicates the outcome of a function */ +typedef struct +{ + int __error_signal; +} s2n_result; + +#define S2N_RESULT s2n_result +#define S2N_RESULT_OK ((s2n_result){S2N_SUCCESS}) +#define S2N_RESULT_ERROR ((s2n_result){S2N_FAILURE}) + +bool s2n_result_is_ok(s2n_result result) +{ + return result.__error_signal == S2N_SUCCESS; +} + +bool validity1(int *x) +{ + return (x > 0); +} + +bool validity2(int *x) +{ + return (x == 0); +} + +S2N_RESULT validity3(int *x) +{ + if(x == NULL) + return S2N_RESULT_ERROR; + if(!validity1(x)) + return S2N_RESULT_ERROR; + return S2N_RESULT_OK; +} diff --git a/regression/contracts-dfcc/assigns_enforce_havoc_object/main.c b/regression/contracts-dfcc/assigns_enforce_havoc_object/main.c new file mode 100644 index 00000000000..072c4bc9dce --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_havoc_object/main.c @@ -0,0 +1,59 @@ +#include +#include + +int x = 0; +typedef struct stc +{ + int i; + int j; +} stc; + +typedef struct stb +{ + stc *c; +} stb; + +typedef struct sta +{ + union + { + stb *b; + int i; + int j; + } u; +} sta; + +int nondet_int(); + +void bar(sta *a) +{ + if(nondet_bool()) + return; + __CPROVER_havoc_object(a->u.b->c); + return; +} + +void foo(sta *a1, sta *a2) __CPROVER_assigns(__CPROVER_object_whole(a1->u.b->c)) +{ + bar(a1); + bar(a2); + return; +} + +sta *allocate_sta() +{ + stc *c = malloc(sizeof(*c)); + stb *b = malloc(sizeof(*b)); + sta *a = malloc(sizeof(*a)); + b->c = c; + a->u.b = b; + return a; +} + +int main() +{ + sta *a1 = allocate_sta(); + sta *a2 = allocate_sta(); + foo(a1, a2); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_havoc_object/test.desc b/regression/contracts-dfcc/assigns_enforce_havoc_object/test.desc new file mode 100644 index 00000000000..a1f4df3728b --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_havoc_object/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=10$ +^SIGNAL=0$ +^\[bar.assigns.\d+\] line \d+ Check that havoc_object\(\(void \*\)a->u\.b->c\) is allowed by the assigns clause: FAILURE$ +^VERIFICATION FAILED$ +-- +-- +Checks that __CPROVER_havoc_object(x) is detected as a write to POINTER_OBJECT(x). diff --git a/regression/contracts-dfcc/assigns_enforce_literal/main.c b/regression/contracts-dfcc/assigns_enforce_literal/main.c new file mode 100644 index 00000000000..ee309bb6101 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_literal/main.c @@ -0,0 +1,16 @@ +#include +#include +#include + +int foo(int *x) __CPROVER_assigns(12) +{ + *x = 0; + return 0; +} + +int main() +{ + int x; + foo(&x); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_literal/test.desc b/regression/contracts-dfcc/assigns_enforce_literal/test.desc new file mode 100644 index 00000000000..289616526e2 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_literal/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=(1|64)$ +^SIGNAL=0$ +^.*error: assigns clause target must be a non-void lvalue, a call to __CPROVER_POINTER_OBJECT or a call to a function returning void$ +^CONVERSION ERROR$ +-- +-- +Check that literal constants are rejected from assigns clauses. diff --git a/regression/contracts-dfcc/assigns_enforce_malloc_01/main.c b/regression/contracts-dfcc/assigns_enforce_malloc_01/main.c new file mode 100644 index 00000000000..7896a93b158 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_malloc_01/main.c @@ -0,0 +1,15 @@ +#include + +int f(int *a) __CPROVER_assigns() +{ + a = (int *)malloc(sizeof(int)); + *a = 5; +} + +int main() +{ + int m = 4; + f(&m); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_malloc_01/test.desc b/regression/contracts-dfcc/assigns_enforce_malloc_01/test.desc new file mode 100644 index 00000000000..d4cad6d9d72 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_malloc_01/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract f +^EXIT=0$ +^SIGNAL=0$ +^\[f\.assigns.\d+\] line \d+ Check that \*a is assignable: SUCCESS +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that verification succeeds when a formal parameter +with a pointer type is first updated to point to a locally malloc'd object +before being assigned to. diff --git a/regression/contracts-dfcc/assigns_enforce_malloc_02/main.c b/regression/contracts-dfcc/assigns_enforce_malloc_02/main.c new file mode 100644 index 00000000000..5b5ddfb6a69 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_malloc_02/main.c @@ -0,0 +1,23 @@ +#include + +int f(int n, int *ptr) __CPROVER_assigns() +{ + while(n > 0) + { + ptr = (int *)malloc(sizeof(int)); + // this is OK because writes + // only happen on freshly allocated + // object and not the initial + // object b was pointing to + *ptr = 5; + n--; + } +} + +int main() +{ + int b = 3; + f(5, &b); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_malloc_02/test.desc b/regression/contracts-dfcc/assigns_enforce_malloc_02/test.desc new file mode 100644 index 00000000000..cf9a0f9ca7b --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_malloc_02/test.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --enforce-contract f +main.c function f +^\[f.assigns.\d+\] line 7 Check that ptr is assignable: SUCCESS$ +^\[f.assigns.\d+\] line 12 Check that \*ptr is assignable: SUCCESS$ +^\[f.assigns.\d+\] line 13 Check that n is assignable: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that objects locally malloc'd in loop are considered +assignable. diff --git a/regression/contracts-dfcc/assigns_enforce_malloc_03/main.c b/regression/contracts-dfcc/assigns_enforce_malloc_03/main.c new file mode 100644 index 00000000000..0c45c0b99a5 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_malloc_03/main.c @@ -0,0 +1,18 @@ +#include + +void foo() __CPROVER_assigns() +{ + char *loc1 = malloc(1); + char *loc2 = malloc(1); + int c; + if(c) + *loc1 = 0; + else + *loc2 = 0; +} + +int main() +{ + foo(); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_malloc_03/test.desc b/regression/contracts-dfcc/assigns_enforce_malloc_03/test.desc new file mode 100644 index 00000000000..7914bd088d1 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_malloc_03/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^\[foo.assigns.\d+\].* Check that \*loc1 is assignable: SUCCESS$ +^\[foo.assigns.\d+\].* Check that \*loc2 is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +Checks that multiple malloc'd objects are tracked by assigns clause checking. diff --git a/regression/contracts-dfcc/assigns_enforce_multi_file_01/header.h b/regression/contracts-dfcc/assigns_enforce_multi_file_01/header.h new file mode 100644 index 00000000000..170fd37498c --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_multi_file_01/header.h @@ -0,0 +1,22 @@ +void f1(int *x1, int *y1, int *z1); + +void f2(int *x2, int *y2, int *z2); + +void f3(int *x3, int *y3, int *z3); + +void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1) +{ + f2(x1, y1, z1); +} + +void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2) +{ + f3(x2, y2, z2); +} + +void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*y3, *z3) +{ + *x3 = *x3 + 1; + *y3 = *y3 + 1; + *z3 = *z3 + 1; +} diff --git a/regression/contracts-dfcc/assigns_enforce_multi_file_01/main.c b/regression/contracts-dfcc/assigns_enforce_multi_file_01/main.c new file mode 100644 index 00000000000..01e3878af6e --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_multi_file_01/main.c @@ -0,0 +1,11 @@ +#include "header.h" + +int main() +{ + int p = 1; + int q = 2; + int r = 3; + f1(&p, &q, &r); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_multi_file_01/test.desc b/regression/contracts-dfcc/assigns_enforce_multi_file_01/test.desc new file mode 100644 index 00000000000..00af5d84bbb --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_multi_file_01/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test replicates the behavior of assigns_enforce_04, but separates +the function headers and contracts into a separate file header.h. diff --git a/regression/contracts-dfcc/assigns_enforce_multi_file_02/header.h b/regression/contracts-dfcc/assigns_enforce_multi_file_02/header.h new file mode 100644 index 00000000000..267565a2ed5 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_multi_file_02/header.h @@ -0,0 +1,23 @@ +#include + +struct pair +{ + int x; + int y; +}; + +struct pair_of_pairs +{ + struct pair p1; + struct pair p2; +}; + +int f1(int *a, struct pair *b); + +int f1(int *a, struct pair *b) __CPROVER_assigns(*a) +{ + struct pair_of_pairs *pop = + (struct pair_of_pairs *)malloc(sizeof(struct pair_of_pairs)); + b = &(pop->p2); + b->y = 5; +} diff --git a/regression/contracts-dfcc/assigns_enforce_multi_file_02/main.c b/regression/contracts-dfcc/assigns_enforce_multi_file_02/main.c new file mode 100644 index 00000000000..f18a61dd1c2 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_multi_file_02/main.c @@ -0,0 +1,10 @@ +#include "header.h" + +int main() +{ + int m = 4; + struct pair n; + f1(&m, &n); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_multi_file_02/test.desc b/regression/contracts-dfcc/assigns_enforce_multi_file_02/test.desc new file mode 100644 index 00000000000..aade2cb196d --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_multi_file_02/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test replicates the behavior of assigns_enforce_structs_03, but separates +the structs, function headers, and contracts into a separate file header.h. diff --git a/regression/contracts-dfcc/assigns_enforce_object_wrong_args/main.c b/regression/contracts-dfcc/assigns_enforce_object_wrong_args/main.c new file mode 100644 index 00000000000..563ae82cf5f --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_object_wrong_args/main.c @@ -0,0 +1,15 @@ +#include +#include +#include + +int baz(int *x) __CPROVER_assigns(__CPROVER_object_whole()) +{ + *x = 0; + return 0; +} + +int main() +{ + int x; + baz(&x); +} diff --git a/regression/contracts-dfcc/assigns_enforce_object_wrong_args/test.desc b/regression/contracts-dfcc/assigns_enforce_object_wrong_args/test.desc new file mode 100644 index 00000000000..d112ec924f2 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_object_wrong_args/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract baz +^EXIT=(1|64)$ +^SIGNAL=0$ +^.*error: wrong number of function arguments: expected 1, but got 0$ +^CONVERSION ERROR$ +-- +-- +Check that incorrect uses of `__CPROVER_object_whole` in assigns clauses are detected. diff --git a/regression/contracts-dfcc/assigns_enforce_offsets_1/main.c b/regression/contracts-dfcc/assigns_enforce_offsets_1/main.c new file mode 100644 index 00000000000..ec36da4817f --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_offsets_1/main.c @@ -0,0 +1,17 @@ +#include +#include +#include + +int foo(int *x) __CPROVER_assigns(*(x + 1)) +{ + // should fail because we write before *(x+1) + *x = 0; + return 0; +} + +int main() +{ + int x; + foo(&x); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_offsets_1/test.desc b/regression/contracts-dfcc/assigns_enforce_offsets_1/test.desc new file mode 100644 index 00000000000..6ff427c5e00 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_offsets_1/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +Check that a write at *x fails when the assigns clause specifies *(x + 1) and +the actual underlying object is of size 1. diff --git a/regression/contracts-dfcc/assigns_enforce_offsets_2/main.c b/regression/contracts-dfcc/assigns_enforce_offsets_2/main.c new file mode 100644 index 00000000000..05cb1553665 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_offsets_2/main.c @@ -0,0 +1,17 @@ +#include +#include +#include + +int foo(int *x) __CPROVER_assigns(*x) +{ + // should fail because we write after *x + *(x + 1) = 0; + return 0; +} + +int main() +{ + int x; + foo(&x); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_offsets_2/test.desc b/regression/contracts-dfcc/assigns_enforce_offsets_2/test.desc new file mode 100644 index 00000000000..585b51684c1 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_offsets_2/test.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --enforce-contract foo _ --pointer-check +^\[foo.assigns.*\d+\].* line 8 Check that x\[\(.*\)1\] is assignable: (SUCCESS|FAILURE)$ +^\[foo.assigns.*\d+\].* line 8 Check that x\[\(.*\)1\] is assignable: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +Check that a write at *x+1 fails when the assigns clause specifies a valid *x +and the actual underlying object is of size 1. +In this case the specified target is valid, the lhs of the assignment is invalid +so the inclusion check passes, but the pointer check must fail with an OOB. diff --git a/regression/contracts-dfcc/assigns_enforce_offsets_3/main.c b/regression/contracts-dfcc/assigns_enforce_offsets_3/main.c new file mode 100644 index 00000000000..977d74b6f52 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_offsets_3/main.c @@ -0,0 +1,20 @@ +#include +#include +#include + +int foo(int *x) __CPROVER_assigns(*(x + 1)) +{ + // should pass + *(x + 1) = 0; + return 0; +} + +int main() +{ + int *x = malloc(2 * sizeof(int)); + *x = 0; + *(x + 1) = 12; + foo(x); + assert(*(x + 1) == 0); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_offsets_3/test.desc b/regression/contracts-dfcc/assigns_enforce_offsets_3/test.desc new file mode 100644 index 00000000000..e5da8f3ffb8 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_offsets_3/test.desc @@ -0,0 +1,9 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Check that a write at *x fails when the assigns clause specifies *(x + 1) and the actual underlying object is of size 1. diff --git a/regression/contracts-dfcc/assigns_enforce_offsets_4/main.c b/regression/contracts-dfcc/assigns_enforce_offsets_4/main.c new file mode 100644 index 00000000000..e55c9b4895a --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_offsets_4/main.c @@ -0,0 +1,20 @@ +#include +#include +#include + +int foo(int *x) __CPROVER_assigns(*(x + 10)) +{ + // should fail in the context of main because the allocated object is too small + *(x + 10) = 0; + return 0; +} + +int main() +{ + int *x = malloc(2 * sizeof(int)); + *x = 0; + *(x + 1) == 0; + // write should fail because x points to a size 2 object and the contracts expects size 10 at least. + foo(x); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_offsets_4/test.desc b/regression/contracts-dfcc/assigns_enforce_offsets_4/test.desc new file mode 100644 index 00000000000..8805c219480 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_offsets_4/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract foo _ --pointer-check +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +Check that a write at *(x+10) fails when the assigns clause specifies *(x + 10) +and the actual underlying object is too small. +In that case the target inclusion succeeds because the LHS is in an invalid +state, but the target validity check must fail. diff --git a/regression/contracts-dfcc/assigns_enforce_scoping_01/main.c b/regression/contracts-dfcc/assigns_enforce_scoping_01/main.c new file mode 100644 index 00000000000..e5ae6e48376 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_scoping_01/main.c @@ -0,0 +1,18 @@ +int f1(int *a, int *b) __CPROVER_assigns(*a) +{ + if(*a > 0) + { + int *b = a; + *b = 5; + } + *b = 5; +} + +int main() +{ + int m = 4; + int n = 3; + f1(&m, &n); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_scoping_01/test.desc b/regression/contracts-dfcc/assigns_enforce_scoping_01/test.desc new file mode 100644 index 00000000000..3837e4ea994 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_scoping_01/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=10$ +^SIGNAL=0$ +^\[f1.assigns.\d+\] line \d+ Check that \*b is assignable: FAILURE$ +^VERIFICATION FAILED$ +-- +-- +This test checks that variables which mask a formal parameter are logically distinct from the formal parameter itself. Specifically, we check that the masked variable may alias a parameter in the assigns clause, while the formal parameter does not, so verification fails, but not because of the masking variable. diff --git a/regression/contracts-dfcc/assigns_enforce_scoping_02/main.c b/regression/contracts-dfcc/assigns_enforce_scoping_02/main.c new file mode 100644 index 00000000000..89e176e1e76 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_scoping_02/main.c @@ -0,0 +1,20 @@ +#include + +int f1(int *a, int *b) __CPROVER_assigns(*a) +{ + if(*a > 0) + { + int *b = (int *)malloc(sizeof(int)); + *b = 5; + } + *b = 5; +} + +int main() +{ + int m = 4; + int n = 3; + f1(&m, &n); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_scoping_02/test.desc b/regression/contracts-dfcc/assigns_enforce_scoping_02/test.desc new file mode 100644 index 00000000000..6df55dcc0b7 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_scoping_02/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=10$ +^SIGNAL=0$ +^\[f1.assigns.\d+\] line \d+ Check that \*b is assignable: FAILURE$ +^VERIFICATION FAILED$ +-- +-- +This test checks that variables which mask a formal parameter are logically distinct from the formal parameter itself. In this test, we check that the masked variable may point to freshly-allocated memory, while the masked parameter may not be assigned, so verification fails, but not because of the masking variable. diff --git a/regression/contracts-dfcc/assigns_enforce_side_effects_1/main.c b/regression/contracts-dfcc/assigns_enforce_side_effects_1/main.c new file mode 100644 index 00000000000..bfbb88df565 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_side_effects_1/main.c @@ -0,0 +1,26 @@ +#include +#include +#include + +int foo(bool a, int *x, long long *y) __CPROVER_assigns(*(a ? x : y++)) +{ + if(a) + { + *x = 0; + } + else + { + *y = 0; + } + return 0; +} + +int main() +{ + bool a; + int x; + long y; + + foo(true, &x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_side_effects_1/test.desc b/regression/contracts-dfcc/assigns_enforce_side_effects_1/test.desc new file mode 100644 index 00000000000..59a4f0e6507 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_side_effects_1/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract foo +activate-multi-line-match +.*error: (dereferencing void pointer|lvalue expressions with void type not allowed in assigns clauses) +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +Check that expressions with void type, side effects and/or ternay operators are +rejected from assigns clauses. diff --git a/regression/contracts-dfcc/assigns_enforce_side_effects_2/main.c b/regression/contracts-dfcc/assigns_enforce_side_effects_2/main.c new file mode 100644 index 00000000000..fe09acb7cb8 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_side_effects_2/main.c @@ -0,0 +1,17 @@ +#include +#include +#include + +int foo(int *x) __CPROVER_assigns(++x) +{ + *x = 2; + return 0; +} + +int main() +{ + int x; + + foo(&x); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_side_effects_2/test.desc b/regression/contracts-dfcc/assigns_enforce_side_effects_2/test.desc new file mode 100644 index 00000000000..88cdc250dca --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_side_effects_2/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=(1|64)$ +^SIGNAL=0$ +^.*error: assigns clause target must be a non-void lvalue, a call to __CPROVER_POINTER_OBJECT or a call to a function returning void$ +^CONVERSION ERROR$ +-- +-- +Check that expressions with side effects are rejected from assigns clauses. diff --git a/regression/contracts-dfcc/assigns_enforce_side_effects_3/main.c b/regression/contracts-dfcc/assigns_enforce_side_effects_3/main.c new file mode 100644 index 00000000000..cd4f0f51154 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_side_effects_3/main.c @@ -0,0 +1,17 @@ +#include +#include +#include + +int foo(int *x) __CPROVER_assigns(x++) +{ + *x = 2; + return 0; +} + +int main() +{ + int x; + + foo(&x); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_side_effects_3/test.desc b/regression/contracts-dfcc/assigns_enforce_side_effects_3/test.desc new file mode 100644 index 00000000000..88cdc250dca --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_side_effects_3/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=(1|64)$ +^SIGNAL=0$ +^.*error: assigns clause target must be a non-void lvalue, a call to __CPROVER_POINTER_OBJECT or a call to a function returning void$ +^CONVERSION ERROR$ +-- +-- +Check that expressions with side effects are rejected from assigns clauses. diff --git a/regression/contracts-dfcc/assigns_enforce_statics/main.c b/regression/contracts-dfcc/assigns_enforce_statics/main.c new file mode 100644 index 00000000000..966c40b0332 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_statics/main.c @@ -0,0 +1,20 @@ +static int x = 0; + +void foo() __CPROVER_assigns() +{ + int *y = &x; + + static int x = 0; + + // should pass (assigns local x) + x++; + + // should fail (assigns global x) + (*y)++; +} + +int main() +{ + foo(); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_statics/test.desc b/regression/contracts-dfcc/assigns_enforce_statics/test.desc new file mode 100644 index 00000000000..8bd0fe00c8b --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_statics/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract foo _ --pointer-primitive-check +^\[foo.assigns.\d+\] line \d+ Check that y is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line \d+ Check that foo\$\$1\$\$x is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line \d+ Check that \*y is assignable: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Checks wether assigning a global static that's not declared +in the assigns clause through a pointer is detected as an error. diff --git a/regression/contracts-dfcc/assigns_enforce_structs_01/main.c b/regression/contracts-dfcc/assigns_enforce_structs_01/main.c new file mode 100644 index 00000000000..ee4a59c2b1f --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_01/main.c @@ -0,0 +1,22 @@ +#include + +struct pair +{ + int x; + int y; +}; + +int f(int *a) __CPROVER_assigns() +{ + struct pair *p = (struct pair *)malloc(sizeof(struct pair)); + a = &(p->y); + *a = 5; +} + +int main() +{ + int m = 4; + f(&m); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_structs_01/test.desc b/regression/contracts-dfcc/assigns_enforce_structs_01/test.desc new file mode 100644 index 00000000000..171bb04eb34 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_01/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract f +^EXIT=0$ +^SIGNAL=0$ +^\[f.assigns.\d+\] line \d+ Check that \*a is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +^\[.*assigns.*\].*: FAILURE$ +-- +Checks whether verification succeeds when a pointer deref that is not +specified in the assigns clause is first pointed at a member of a +locally malloc'd struct before being assigned. diff --git a/regression/contracts-dfcc/assigns_enforce_structs_02/main.c b/regression/contracts-dfcc/assigns_enforce_structs_02/main.c new file mode 100644 index 00000000000..fd544aa9148 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_02/main.c @@ -0,0 +1,29 @@ +#include + +struct pair +{ + int x; + int y; +}; + +struct pair_of_pairs +{ + struct pair p1; + struct pair p2; +}; + +int f(int *a) __CPROVER_assigns() +{ + struct pair_of_pairs *pop = + (struct pair_of_pairs *)malloc(sizeof(struct pair_of_pairs)); + a = &(pop->p2.x); + *a = 5; +} + +int main() +{ + int m = 4; + f(&m); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_structs_02/test.desc b/regression/contracts-dfcc/assigns_enforce_structs_02/test.desc new file mode 100644 index 00000000000..6d1aceb2d75 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_02/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract f +^EXIT=0$ +^SIGNAL=0$ +^\[f.assigns.\d+\] line \d+ Check that \*a is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +^\[.*assigns.*\].*: FAILURE$ +-- +Checks whether verification succeeds when a pointer deref that is not +specified in the assigns clause is first pointed at a member of a locally +malloc'd struct before being assigned (with extra nesting). diff --git a/regression/contracts-dfcc/assigns_enforce_structs_03/main.c b/regression/contracts-dfcc/assigns_enforce_structs_03/main.c new file mode 100644 index 00000000000..a4d282edc25 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_03/main.c @@ -0,0 +1,29 @@ +#include + +struct pair +{ + int x; + int y; +}; + +struct pair_of_pairs +{ + struct pair p1; + struct pair p2; +}; + +int f(struct pair *a) __CPROVER_assigns() +{ + struct pair_of_pairs *pop = + (struct pair_of_pairs *)malloc(sizeof(struct pair_of_pairs)); + a = &(pop->p2); + a->y = 5; +} + +int main() +{ + struct pair m; + f(&m); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_structs_03/test.desc b/regression/contracts-dfcc/assigns_enforce_structs_03/test.desc new file mode 100644 index 00000000000..53273486c05 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_03/test.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --enforce-contract f +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether verification succeeds when a member of formal parameter +(with type of pointer to struct) outside of the assigns clause is assigned +after being pointed at the location of a member sub-struct of a freshly +allocated struct before being assigned. This is meant to show that all +contained members (and their contained members) of assignable structs +are valid to assign. diff --git a/regression/contracts-dfcc/assigns_enforce_structs_04/main.c b/regression/contracts-dfcc/assigns_enforce_structs_04/main.c new file mode 100644 index 00000000000..75b747ad5d2 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_04/main.c @@ -0,0 +1,41 @@ +#include +#include + +struct pair +{ + int x; + int y; +}; + +void f1(struct pair *p) __CPROVER_assigns(p->x) +{ + p->y = 2; +} + +void f2(struct pair *p) __CPROVER_assigns(p->y) +{ + p->x = 2; +} + +void f3(struct pair *p) __CPROVER_assigns(p->y) +{ + p->y = 0; +} + +void f4(struct pair *p) __CPROVER_assigns(*p) +{ + p = NULL; +} + +int main() +{ + struct pair p = {0}; + f1(&p); + f2(&p); + assert(p.y == 2); + assert(p.x == 2); + f3(&p); + assert(p.y == 0); + f4(&p); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_structs_04/test-f1.desc b/regression/contracts-dfcc/assigns_enforce_structs_04/test-f1.desc new file mode 100644 index 00000000000..5072468b9db --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_04/test-f1.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=10$ +^SIGNAL=0$ +^\[f1.assigns.\d+\] line \d+ Check that p->y is assignable: FAILURE$ +^VERIFICATION FAILED$ +-- +-- +Checks whether CBMC properly evaluates write set of members +from the same object. diff --git a/regression/contracts-dfcc/assigns_enforce_structs_04/test-f2.desc b/regression/contracts-dfcc/assigns_enforce_structs_04/test-f2.desc new file mode 100644 index 00000000000..47dee4059e7 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_04/test-f2.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract f2 +^EXIT=10$ +^SIGNAL=0$ +^\[f2.assigns.\d+\] line \d+ Check that p->x is assignable: FAILURE$ +^VERIFICATION FAILED$ +-- +-- +Checks whether CBMC properly evaluates write set of members +from the same object. diff --git a/regression/contracts-dfcc/assigns_enforce_structs_04/test-f3.desc b/regression/contracts-dfcc/assigns_enforce_structs_04/test-f3.desc new file mode 100644 index 00000000000..7d09fc92b32 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_04/test-f3.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract f3 +^EXIT=0$ +^SIGNAL=0$ +^\[f3.assigns.\d+\] line \d+ Check that p->y is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether CBMC properly evaluates write set of members +from the same object. diff --git a/regression/contracts-dfcc/assigns_enforce_structs_04/test-f4.desc b/regression/contracts-dfcc/assigns_enforce_structs_04/test-f4.desc new file mode 100644 index 00000000000..ac292fdff74 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_04/test-f4.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract f4 +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether CBMC properly evaluates write set of members +from the same object. diff --git a/regression/contracts-dfcc/assigns_enforce_structs_05/main.c b/regression/contracts-dfcc/assigns_enforce_structs_05/main.c new file mode 100644 index 00000000000..c0223382cbb --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_05/main.c @@ -0,0 +1,27 @@ +#include + +struct pair +{ + int x[3]; + int y; +}; + +int f1(struct pair *p) __CPROVER_assigns(p->x) +{ + p->y = 2; + p->x[0] = 0; + p->x[1] = 1; + p->x[2] = 2; + return 0; +} + +int main() +{ + struct pair p = {0}; + f1(&p); + assert(p.y == 2); + assert(p.x[0] == 0); + assert(p.x[1] == 1); + assert(p.x[2] == 2); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_structs_05/test.desc b/regression/contracts-dfcc/assigns_enforce_structs_05/test.desc new file mode 100644 index 00000000000..fbb81d3f3c3 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_05/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=10$ +^SIGNAL=0$ +^\[f1.assigns.\d+\] line \d+ Check that p->y is assignable: FAILURE$ +^\[f1.assigns.\d+\] line \d+ Check that p->x\[\(.*\)0\] is assignable: SUCCESS$ +^\[f1.assigns.\d+\] line \d+ Check that p->x\[\(.*\)1\] is assignable: SUCCESS$ +^\[f1.assigns.\d+\] line \d+ Check that p->x\[\(.*\)2\] is assignable: SUCCESS$ +^VERIFICATION FAILED$ +-- +-- +Checks whether CBMC properly evaluates write set of members +from the same object. In this case, we have an assigns clause +with a struct member `x[3]` and an assignment to the struct member `y`. +CBMC must considers only the region of `x[3]` is assignable. diff --git a/regression/contracts-dfcc/assigns_enforce_structs_06/main.c b/regression/contracts-dfcc/assigns_enforce_structs_06/main.c new file mode 100644 index 00000000000..2f3b3b27b18 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_06/main.c @@ -0,0 +1,46 @@ +#include +#include +#include + +struct pair +{ + uint8_t *buf; + size_t size; +}; + +void f1(struct pair *p) __CPROVER_assigns(__CPROVER_object_whole(p->buf)) +{ + p->buf[0] = 0; + p->buf[1] = 1; + p->buf[2] = 2; + p->size = 4; +} + +void f2(struct pair *p) __CPROVER_assigns(p->size) +{ + p->buf[0] = 0; + p->size = 0; +} + +void f3(struct pair *p) __CPROVER_assigns(*p) +{ + p->buf = NULL; + p->size = 0; +} + +int main() +{ + struct pair *p = malloc(sizeof(*p)); + p->size = 3; + p->buf = malloc(p->size); + f1(p); + assert(p->buf[0] == 0); + assert(p->buf[1] == 1); + assert(p->buf[2] == 2); + f2(p); + assert(p->size == 0); + f3(p); + assert(p->buf == NULL); + assert(p->size == 0); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_structs_06/test-f1.desc b/regression/contracts-dfcc/assigns_enforce_structs_06/test-f1.desc new file mode 100644 index 00000000000..8d53fa2ed5a --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_06/test-f1.desc @@ -0,0 +1,15 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=10$ +^SIGNAL=0$ +^\[f1.assigns.\d+\] line \d+ Check that p->buf\[\(.*\)0\] is assignable: SUCCESS$ +^\[f1.assigns.\d+\] line \d+ Check that p->buf\[\(.*\)1\] is assignable: SUCCESS$ +^\[f1.assigns.\d+\] line \d+ Check that p->buf\[\(.*\)2\] is assignable: SUCCESS$ +^\[f1.assigns.\d+\] line \d+ Check that p->size is assignable: FAILURE$ +^VERIFICATION FAILED$ +-- +-- +Checks whether CBMC properly evaluates write set of members +from the same object. In this case, we have a dynamic object +as one of the struct members. diff --git a/regression/contracts-dfcc/assigns_enforce_structs_06/test-f2.desc b/regression/contracts-dfcc/assigns_enforce_structs_06/test-f2.desc new file mode 100644 index 00000000000..2ce4f87b181 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_06/test-f2.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract f2 +^EXIT=10$ +^SIGNAL=0$ +^\[f2.assigns.\d+\] line \d+ Check that p->buf\[\(.*\)0\] is assignable: FAILURE$ +^\[f2.assigns.\d+\] line \d+ Check that p->size is assignable: SUCCESS$ +^VERIFICATION FAILED$ +-- +-- +Checks whether CBMC properly evaluates write set of members +from the same object. In this case, we have a dynamic object +as one of the struct members. diff --git a/regression/contracts-dfcc/assigns_enforce_structs_06/test-f3.desc b/regression/contracts-dfcc/assigns_enforce_structs_06/test-f3.desc new file mode 100644 index 00000000000..3fda9761a16 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_06/test-f3.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract f3 +^EXIT=0$ +^SIGNAL=0$ +^\[f3.assigns.\d+\] line \d+ Check that p->buf is assignable: SUCCESS$ +^\[f3.assigns.\d+\] line \d+ Check that p->size is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether CBMC properly evaluates write set of members +from the same object. In this case, we have a dynamic object +as one of the struct members. diff --git a/regression/contracts-dfcc/assigns_enforce_structs_07/main.c b/regression/contracts-dfcc/assigns_enforce_structs_07/main.c new file mode 100644 index 00000000000..48ab563ee83 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_07/main.c @@ -0,0 +1,43 @@ +#include +#include +#include + +struct pair +{ + uint8_t *buf; + size_t size; +}; + +struct pair_of_pairs +{ + struct pair *p; +}; + +void f1(struct pair *p) __CPROVER_assigns(*(p->buf)) +{ + p->buf[0] = 0; +} + +void f2(struct pair_of_pairs *pp) __CPROVER_assigns(*(pp->p->buf)) +{ + pp->p->buf[0] = 0; +} + +int main() +{ + struct pair *p = malloc(sizeof(*p)); + if(p) + p->buf = malloc(100 * sizeof(uint8_t)); + f1(p); + + struct pair_of_pairs *pp = malloc(sizeof(*pp)); + if(pp) + { + pp->p = malloc(sizeof(*(pp->p))); + if(pp->p) + pp->p->buf = malloc(100 * sizeof(uint8_t)); + } + f2(pp); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_structs_07/test-f1.desc b/regression/contracts-dfcc/assigns_enforce_structs_07/test-f1.desc new file mode 100644 index 00000000000..fa59915921b --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_07/test-f1.desc @@ -0,0 +1,15 @@ +CORE +main.c +--malloc-may-fail --malloc-fail-null --dfcc main --enforce-contract f1 _ --pointer-check +^EXIT=10$ +^SIGNAL=0$ +^\[f1.assigns.\d+\].*line 18 Check that p->buf\[\(.*\)0\] is assignable: FAILURE$ +^VERIFICATION FAILED$ +-- +-- +In f1, the assigns clause specifies `*(p->buf)` as target (which can be invalid) +and assigns `p->buf[0]` unconditionally. When both target and lhs are invalid, +its inclusion check can be trivially satisfied (or not) but we expect the target +validity check to fail. + +In f2 tests this behaviour with chained dereferences. diff --git a/regression/contracts-dfcc/assigns_enforce_structs_07/test-f2.desc b/regression/contracts-dfcc/assigns_enforce_structs_07/test-f2.desc new file mode 100644 index 00000000000..f9a8a8cab3b --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_07/test-f2.desc @@ -0,0 +1,15 @@ +CORE +main.c +--malloc-may-fail --malloc-fail-null --dfcc main --enforce-contract f2 _ --pointer-check +^EXIT=10$ +^SIGNAL=0$ +^\[f2.assigns.\d+\].*line 23 Check that pp->p->buf\[\(.*\)0\] is assignable: FAILURE$ +^VERIFICATION FAILED$ +-- +-- +In f1, the assigns clause specifies `*(p->buf)` as target (which can be invalid) +and assigns `p->buf[0]` unconditionally. When both target and lhs are invalid, +its inclusion check can be trivially satisfied (or not) but we expect the target +validity check to fail. + +In f2 tests this behaviour with chained dereferences. diff --git a/regression/contracts-dfcc/assigns_enforce_structs_08/main.c b/regression/contracts-dfcc/assigns_enforce_structs_08/main.c new file mode 100644 index 00000000000..1ea4a832155 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_08/main.c @@ -0,0 +1,48 @@ +#include +#include +#include + +struct pair +{ + uint8_t *buf; + size_t size; +}; + +struct pair_of_pairs +{ + struct pair *p; +}; + +void f1(struct pair *p) __CPROVER_assigns(p && p->buf : *(p->buf)) +{ + if(p && p->buf) + p->buf[0] = 0; +} + +void f2(struct pair_of_pairs *pp) + // clang-format off +__CPROVER_assigns(pp && pp->p && pp->p->buf: *(pp->p->buf)) +// clang-format on +{ + if(pp && pp->p && pp->p->buf) + pp->p->buf[0] = 0; +} + +int main() +{ + struct pair *p = malloc(sizeof(*p)); + if(p) + p->buf = malloc(100 * sizeof(uint8_t)); + f1(p); + + struct pair_of_pairs *pp = malloc(sizeof(*pp)); + if(pp) + { + pp->p = malloc(sizeof(*(pp->p))); + if(pp->p) + pp->p->buf = malloc(100 * sizeof(uint8_t)); + } + f2(pp); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_structs_08/test-f1.desc b/regression/contracts-dfcc/assigns_enforce_structs_08/test-f1.desc new file mode 100644 index 00000000000..b4cff17df09 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_08/test-f1.desc @@ -0,0 +1,17 @@ +CORE +main.c +--dfcc main --enforce-contract f1 _ --malloc-may-fail --malloc-fail-null --pointer-check +^\[f1.assigns.\d+\] line \d+ Check that p->buf\[\(.*\)0\] is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +^\[.*assigns.*\].*: FAILURE$ +-- +In f1, the assigns clause specifies `*(p->buf)` as target (which can be invalid) +and assigns `p->buf[0]` unconditionally. When both target and lhs are invalid, +its inclusion check can be trivially satisfied or not but we expect in all +cases a null pointer failure and an invalid pointer error to occur +on the assignment. + +In f2 tests this behaviour with chained dereferences. diff --git a/regression/contracts-dfcc/assigns_enforce_structs_08/test-f2.desc b/regression/contracts-dfcc/assigns_enforce_structs_08/test-f2.desc new file mode 100644 index 00000000000..260ac025c13 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_structs_08/test-f2.desc @@ -0,0 +1,17 @@ +CORE +main.c +--dfcc main --enforce-contract f2 _ --malloc-may-fail --malloc-fail-null --pointer-check +^\[f2.assigns.\d+\] line \d+ Check that pp->p->buf\[\(.*\)0\] is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +^\[.*assigns.*\].*: FAILURE$ +-- +In f1, the assigns clause specifies `*(p->buf)` as target (which can be invalid) +and assigns `p->buf[0]` unconditionally. When both target and lhs are invalid, +its inclusion check can be trivially satisfied or not but we expect in all +cases a null pointer failure and an invalid pointer error to occur +on the assignment. + +In f2 tests this behaviour with chained dereferences. diff --git a/regression/contracts-dfcc/assigns_enforce_subfunction_calls/main.c b/regression/contracts-dfcc/assigns_enforce_subfunction_calls/main.c new file mode 100644 index 00000000000..dda1ce6aff7 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_subfunction_calls/main.c @@ -0,0 +1,27 @@ +#include +#include + +void baz(int *x, int y) +{ + *x = y; + return; +} + +void bar(int *x, int y) +{ + baz(x, y); + return; +} + +void foo(int *x) __CPROVER_assigns() +{ + // not allowed, failed check in baz + bar(x, 2); +} + +int main() +{ + int x; + foo(&x); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_enforce_subfunction_calls/test.desc b/regression/contracts-dfcc/assigns_enforce_subfunction_calls/test.desc new file mode 100644 index 00000000000..278edf9bc38 --- /dev/null +++ b/regression/contracts-dfcc/assigns_enforce_subfunction_calls/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^\[baz.assigns.\d+\].*Check that \*x is assignable: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Check that write-sets are propagated through function calls. diff --git a/regression/contracts-dfcc/assigns_function_pointer/main.c b/regression/contracts-dfcc/assigns_function_pointer/main.c new file mode 100644 index 00000000000..e17486bebfb --- /dev/null +++ b/regression/contracts-dfcc/assigns_function_pointer/main.c @@ -0,0 +1,38 @@ +#include +#include + +int x; + +struct fptr_t +{ + void (*f)(); +}; + +void foo() +{ + x = 1; +} + +void foofoo() +{ + x = 2; +} + +void bar(struct fptr_t *s, void (**f)()) __CPROVER_assigns(s->f, *f) +{ + s->f = &foo; + *f = &foofoo; +} + +int main() +{ + x = 0; + struct fptr_t s; + void (*f)(); + bar(&s, &f); + s.f(); + assert(x == 1); + f(); + assert(x == 2); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_function_pointer/test.desc b/regression/contracts-dfcc/assigns_function_pointer/test.desc new file mode 100644 index 00000000000..29064487bef --- /dev/null +++ b/regression/contracts-dfcc/assigns_function_pointer/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract bar +^EXIT=0$ +^SIGNAL=0$ +^\[bar.assigns.\d+\] line \d+ Check that s->f is assignable: SUCCESS$ +^\[bar.assigns.\d+\] line \d+ Check that \*f is assignable: SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion x \=\= 1: SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion x \=\= 2: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +Checks whether assigns clause accepts function pointers +and pointers to function pointers. diff --git a/regression/contracts-dfcc/assigns_repeated_ignored/main.c b/regression/contracts-dfcc/assigns_repeated_ignored/main.c new file mode 100644 index 00000000000..2a270ca8367 --- /dev/null +++ b/regression/contracts-dfcc/assigns_repeated_ignored/main.c @@ -0,0 +1,13 @@ +int foo(int *x) __CPROVER_assigns(*x, *x) +{ + *x = *x + 0; + return *x + 5; +} + +int main() +{ + int n = 4; + n = foo(&n); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_repeated_ignored/test.desc b/regression/contracts-dfcc/assigns_repeated_ignored/test.desc new file mode 100644 index 00000000000..22db5721515 --- /dev/null +++ b/regression/contracts-dfcc/assigns_repeated_ignored/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +This test checks that repeated expressions in assigns clauses are handled +gracefully. diff --git a/regression/contracts-dfcc/assigns_replace_01/main.c b/regression/contracts-dfcc/assigns_replace_01/main.c new file mode 100644 index 00000000000..d3f93979d10 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_01/main.c @@ -0,0 +1,15 @@ +#include + +void foo(int *x) __CPROVER_assigns(*x) +{ + *x = 7; +} + +int main() +{ + int n = 6; + foo(&n); + assert(n == 7); + assert(n == 6); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_replace_01/test.desc b/regression/contracts-dfcc/assigns_replace_01/test.desc new file mode 100644 index 00000000000..77171a7b816 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_01/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^EXIT=10$ +^SIGNAL=0$ +assertion n == 7: FAILURE +assertion n == 6: FAILURE +^VERIFICATION FAILED$ +-- +-- +This test checks that a variable inside the assigns clause is havocked. diff --git a/regression/contracts-dfcc/assigns_replace_02/main.c b/regression/contracts-dfcc/assigns_replace_02/main.c new file mode 100644 index 00000000000..8487c6564c9 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_02/main.c @@ -0,0 +1,16 @@ +#include + +void foo(int *x, int *y) __CPROVER_assigns(*x) +{ + *x = 7; +} + +int main() +{ + int n; + int m = 4; + bar(&n); + assert(m == 4); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_replace_02/test.desc b/regression/contracts-dfcc/assigns_replace_02/test.desc new file mode 100644 index 00000000000..e409ccc30bb --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_02/test.desc @@ -0,0 +1,9 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that a variable outside the assigns clause is not havocked. diff --git a/regression/contracts-dfcc/assigns_replace_03/main.c b/regression/contracts-dfcc/assigns_replace_03/main.c new file mode 100644 index 00000000000..c93ac4e2ebd --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_03/main.c @@ -0,0 +1,18 @@ +#include +#include + +int y; +double z; + +void bar(char *c) __CPROVER_assigns(y, z, *c) __CPROVER_ensures(*c == 6) +{ +} + +int main() +{ + char *b = malloc(sizeof(*b)); + bar(b); + assert(*b == 6); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_replace_03/test.desc b/regression/contracts-dfcc/assigns_replace_03/test.desc new file mode 100644 index 00000000000..d2df29aae1f --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_03/test.desc @@ -0,0 +1,9 @@ +CORE +main.c +--dfcc main --replace-call-with-contract bar _ --pointer-primitive-check +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that a havocked variable can be constrained by a function post-condition. diff --git a/regression/contracts-dfcc/assigns_replace_04/main.c b/regression/contracts-dfcc/assigns_replace_04/main.c new file mode 100644 index 00000000000..a462e9cdc84 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_04/main.c @@ -0,0 +1,34 @@ +#include + +void f2(int *x2, int *y2) __CPROVER_assigns(*x2) __CPROVER_ensures(*x2 == 10) +{ + *x2 = 10; +} + +void f3(int *x3, int *y3) __CPROVER_assigns(*x3) __CPROVER_ensures(*x3 > 100) +{ + *x3 = 101; +} + +int main() +{ + int p = 1; + int q = 2; + + for(int i = 0; i < 5; ++i) + { + if(p < 3) + { + f2(&p, &q); + } + else + { + f3(&p, &q); + } + } + assert(p > 100); + assert(q == 2); + __CPROVER_assert(0, "reachability test"); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_replace_04/test.desc b/regression/contracts-dfcc/assigns_replace_04/test.desc new file mode 100644 index 00000000000..31a4a850db4 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_04/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --replace-call-with-contract f2 --replace-call-with-contract f3 +main.c function main +^\[.*\d+\] line 29 assertion p > 100: SUCCESS$ +^\[.*\d+\] line 30 assertion q == 2: SUCCESS$ +^\[.*\d+\] line 31 reachability test: FAILURE$ +^\*\* 1 of \d+ failed +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +This test checks that replacing function calls with their contracts within a +loop, when the contracts impose contradictory post conditions at different loop +iterations on a same program variable, do not cause vacuity. diff --git a/regression/contracts-dfcc/assigns_replace_05/main.c b/regression/contracts-dfcc/assigns_replace_05/main.c new file mode 100644 index 00000000000..500edd9b80b --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_05/main.c @@ -0,0 +1,34 @@ +#include + +void f2(int *x2, int *y2) __CPROVER_assigns(*x2) __CPROVER_ensures(*x2 < 5) +{ + *x2 = 1; +} + +void f3(int *x3, int *y3) __CPROVER_ensures(*x3 > 100) +{ + *x3 = 101; +} + +int main() +{ + int p = 1; + int q = 2; + + for(int i = 0; i < 5; ++i) + { + if(i < 3) + { + f2(&p, &q); + } + else + { + f3(&p, &q); + } + } + assert(p < 0); + assert(q == 32); + __CPROVER_assert(0, "reachability test"); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_replace_05/test.desc b/regression/contracts-dfcc/assigns_replace_05/test.desc new file mode 100644 index 00000000000..82a94ab49b6 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_05/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --replace-call-with-contract f2 --replace-call-with-contract f3 +main.c function main +^\[.*\d+\] line 29 assertion p < 0: SUCCESS$ +^\[.*\d+\] line 30 assertion q == 32: SUCCESS$ +^\[.*\d+\] line 31 reachability test: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +This test demonstrates that replacing a function call with a contract that has +an empty assigns clause and a post condition involving its input parameters can +causes vacuous proofs. Checking the contract against the function would fail +the assigns clause checks. *This is not a bug*. diff --git a/regression/contracts-dfcc/assigns_replace_06/main.c b/regression/contracts-dfcc/assigns_replace_06/main.c new file mode 100644 index 00000000000..60633d89ed5 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_06/main.c @@ -0,0 +1,32 @@ +#include +#include + +void foo(char c[]) __CPROVER_assigns(__CPROVER_object_whole(c)) +{ +} + +void bar(char *d) __CPROVER_assigns(*d) +{ +} + +int main() +{ + char b[4] = {'a', 'b', 'c', 'd'}; + + foo(b); + + assert(b[0] == 'a'); + assert(b[1] == 'b'); + assert(b[2] == 'c'); + assert(b[3] == 'd'); + + b[1] = '1'; + b[3] = '3'; + + bar(b + 3); + + assert(b[0] == 'a'); + assert(b[1] == '1'); + assert(b[2] == 'c'); + assert(b[3] == '3'); +} diff --git a/regression/contracts-dfcc/assigns_replace_06/test.desc b/regression/contracts-dfcc/assigns_replace_06/test.desc new file mode 100644 index 00000000000..9af213922eb --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_06/test.desc @@ -0,0 +1,19 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo --replace-call-with-contract bar _ --pointer-primitive-check +^\[main.assertion.1\] line \d+ assertion b\[0\] == 'a': FAILURE$ +^\[main.assertion.2\] line \d+ assertion b\[1\] == 'b': FAILURE$ +^\[main.assertion.3\] line \d+ assertion b\[2\] == 'c': FAILURE$ +^\[main.assertion.4\] line \d+ assertion b\[3\] == 'd': FAILURE$ +^\[main.assertion.5\] line \d+ assertion b\[0\] == 'a': FAILURE$ +^\[main.assertion.6\] line \d+ assertion b\[1\] == '1': SUCCESS$ +^\[main.assertion.7\] line \d+ assertion b\[2\] == 'c': FAILURE$ +^\[main.assertion.8\] line \d+ assertion b\[3\] == '3': FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +^\[.+\.pointer_primitives\.\d+] line .*: FAILURE$ +-- +Checks that entire arrays and fixed single elements are correctly havoced +when functions are replaced by contracts. diff --git a/regression/contracts-dfcc/assigns_replace_07/main.c b/regression/contracts-dfcc/assigns_replace_07/main.c new file mode 100644 index 00000000000..afa44dc1bf8 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_07/main.c @@ -0,0 +1,24 @@ +#include +#include +#include + +struct test +{ + uint8_t buf[8]; +}; + +void f1(struct test *p) __CPROVER_assigns(p->buf) + __CPROVER_ensures((p == NULL) || p->buf[0] == 0) +{ + if(p != NULL) + p->buf[0] = 0; +} + +int main() +{ + struct test *p = malloc(sizeof(*p)); + uint8_t buf_1 = (p == NULL) ? 0 : p->buf[1]; + f1(p); + assert(p == NULL || p->buf[0] == 0); + assert(p == NULL || p->buf[1] == buf_1); +} diff --git a/regression/contracts-dfcc/assigns_replace_07/test.desc b/regression/contracts-dfcc/assigns_replace_07/test.desc new file mode 100644 index 00000000000..16d9cf8f925 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_07/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --replace-call-with-contract f1 _ --malloc-may-fail --malloc-fail-null --pointer-check +^EXIT=10$ +^SIGNAL=0$ +^\[main.assertion.\d+\] line \d+ assertion p == NULL \|\| p->buf\[0\] == 0: SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion p == NULL \|\| p->buf\[1\] == buf_1: FAILURE$ +^VERIFICATION FAILED$ +-- +-- +Checks whether CBMC properly evaluates write set of members from invalid objects. +Functions are not expected to write to invalid locations; CBMC flags such writes. +For contract checking, we ignore invalid targets in assigns clauses and assignment LHS. diff --git a/regression/contracts-dfcc/assigns_replace_08/main.c b/regression/contracts-dfcc/assigns_replace_08/main.c new file mode 100644 index 00000000000..4f6cc637f64 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_08/main.c @@ -0,0 +1,20 @@ +#include + +int *z; + +void bar() __CPROVER_assigns(*z) +{ +} + +void foo() __CPROVER_assigns() +{ + bar(); +} + +int main() +{ + int x; + z = &x; + foo(); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_replace_08/test.desc b/regression/contracts-dfcc/assigns_replace_08/test.desc new file mode 100644 index 00000000000..e1ac05d3378 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_08/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract foo --replace-call-with-contract bar _ --pointer-primitive-check +^\[bar.assigns.\d+\].*Check that the assigns clause of contract::bar is included in the caller's assigns clause: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Checks whether CBMC properly evaluates subset relationship on assigns +during replacement when the targets are global variables. diff --git a/regression/contracts-dfcc/assigns_replace_09/main.c b/regression/contracts-dfcc/assigns_replace_09/main.c new file mode 100644 index 00000000000..62a3a7fe806 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_09/main.c @@ -0,0 +1,20 @@ +#include + +int *z; + +void bar() __CPROVER_assigns(*z) +{ +} + +static void foo() __CPROVER_assigns(*z) +{ + bar(); +} + +int main() +{ + int x; + z = &x; + foo(); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_replace_09/test.desc b/regression/contracts-dfcc/assigns_replace_09/test.desc new file mode 100644 index 00000000000..1f9bcd841b2 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_09/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --replace-call-with-contract bar --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^\[bar.assigns.\d+\].*Check that the assigns clause of contract::bar is included in the caller's assigns clause: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that assigns clause inclusion checks succeed when targets are +global variables and the checked function is a static function. diff --git a/regression/contracts-dfcc/assigns_replace_conditional_targets/main.c b/regression/contracts-dfcc/assigns_replace_conditional_targets/main.c new file mode 100644 index 00000000000..7226f2250f2 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_conditional_targets/main.c @@ -0,0 +1,74 @@ +#include + +bool nz(int x) +{ + return x == 0; +} + +int foo(bool a, int *x, int *y, char *z) + // clang-format off +__CPROVER_requires(x && y && z) +__CPROVER_assigns( + a && nz(*x): *x; + !a && nz(*y): *y; + !nz(*x) && !nz(*y): __CPROVER_object_whole(z); +) +__CPROVER_ensures(true) +// clang-format on +{ + if(!nz(*x) && !nz(*y)) + __CPROVER_havoc_object(z); + + if(a && x) + { + if(nz(*x)) + *x = 0; + } + + if(~!a && y) + { + if(nz(*y)) + *y = 0; + } + + return 0; +} + +int main() +{ + bool a, old_a; + old_a = a; + + int x, old_x; + old_x = x; + + int y, old_y; + old_y = y; + + char *z = malloc(1); + *z = '0'; + + foo(a, &x, &y, z); + + // check frame conditions + // clang-format off + __CPROVER_assert(old_a == a, "a unchanged, expecting SUCCESS"); + + __CPROVER_assert( + old_a && nz(old_x) ==> x == old_x, "x changed, expecting FAILURE"); + __CPROVER_assert( + !(old_a && nz(old_x)) ==> x == old_x, "x unchanged, expecting SUCCESS"); + + __CPROVER_assert( + !old_a && nz(old_y) ==> y == old_y, "y changed, expecting FAILURE"); + __CPROVER_assert( + !(!old_a && nz(old_y)) ==> y == old_y, "y unchanged, expecting SUCCESS"); + + __CPROVER_assert( + !(nz(old_x) || nz(old_y)) ==> *z == '0', "z changed, expecting FAILURE"); + __CPROVER_assert( + nz(old_x) || nz(old_y) ==> *z == '0', "z unchanged, expecting SUCCESS"); + // clang-format on + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_replace_conditional_targets/test.desc b/regression/contracts-dfcc/assigns_replace_conditional_targets/test.desc new file mode 100644 index 00000000000..99921e61ebd --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_conditional_targets/test.desc @@ -0,0 +1,19 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^main.c function main$ +^\[main\.assertion\.\d+\] line 55 a unchanged, expecting SUCCESS: SUCCESS$ +^\[main\.assertion\.\d+\] line 57 x changed, expecting FAILURE: FAILURE$ +^\[main\.assertion\.\d+\] line 59 x unchanged, expecting SUCCESS: SUCCESS$ +^\[main\.assertion\.\d+\] line 62 y changed, expecting FAILURE: FAILURE$ +^\[main\.assertion\.\d+\] line 64 y unchanged, expecting SUCCESS: SUCCESS$ +^\[main\.assertion\.\d+\] line 67 z changed, expecting FAILURE: FAILURE$ +^\[main\.assertion\.\d+\] line 69 z unchanged, expecting SUCCESS: SUCCESS$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Checks that havocking of conditional targets works as expected when +replacing a call by a contract. We manually express frame conditions as +assertions in the main function. diff --git a/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/enforce.desc b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/enforce.desc new file mode 100644 index 00000000000..5b3e7400a5b --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/enforce.desc @@ -0,0 +1,9 @@ +CORE +main_enforce.c +--dfcc main --enforce-contract resize_vec _ --signed-overflow-check --unsigned-overflow-check --pointer-overflow-check +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +Verifies that the contract being replaced in `replace.desc` holds on resize_vec. diff --git a/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/main_enforce.c b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/main_enforce.c new file mode 100644 index 00000000000..9780886b632 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/main_enforce.c @@ -0,0 +1,9 @@ +#include "vect.h" + +int main() +{ + vect *v; + size_t incr; + resize_vec(v, incr); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/main_replace.c b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/main_replace.c new file mode 100644 index 00000000000..f6060603800 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/main_replace.c @@ -0,0 +1,8 @@ +#include "vect.h" + +int main() +{ + vect *v; + resize_vec_incr10(v); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/replace.desc b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/replace.desc new file mode 100644 index 00000000000..fc1a96c380b --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/replace.desc @@ -0,0 +1,13 @@ +CORE +main_replace.c +--dfcc main --replace-call-with-contract resize_vec --enforce-contract resize_vec_incr10 _ --signed-overflow-check --unsigned-overflow-check --pointer-overflow-check +^\[resize_vec.assigns.\d+\].*Check that the assigns clause of contract::resize_vec is included in the caller's assigns clause: FAILURE$ +^\[resize_vec.frees.\d+\].*Check that the frees clause of contract::resize_vec is included in the caller's frees clause: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +This test checks that assigns clause and frees clause inclusion checks +are falsified when a callee (resize_vec) assigns outside the assigns clause +of the caller (resize_vec_incr10). \ No newline at end of file diff --git a/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/vect.h b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/vect.h new file mode 100644 index 00000000000..47a2acdfce3 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_fail/vect.h @@ -0,0 +1,51 @@ +#include +#include + +typedef struct vect +{ + char *arr; + size_t size; +} vect; + +void resize_vec(vect *v, size_t incr) + // clang-format off +__CPROVER_requires( + __CPROVER_is_fresh(v, sizeof(vect)) && + __CPROVER_is_fresh(v->arr, v->size) && + 0 < v->size && v->size <= __CPROVER_max_malloc_size && + 0 < incr && incr < __CPROVER_max_malloc_size - v->size +) +__CPROVER_assigns(v->size, v->arr, __CPROVER_object_whole(v->arr)) +__CPROVER_frees(v->arr) +__CPROVER_ensures( + v->size == __CPROVER_old(v->size) + __CPROVER_old(incr) && + __CPROVER_is_fresh(v->arr, v->size) +) +// clang-format on +{ + free(v->arr); + v->size += incr; + v->arr = malloc(v->size); + __CPROVER_array_set(v->arr, 0); + return; +} + +void resize_vec_incr10(vect *v) + // clang-format off +__CPROVER_requires( + __CPROVER_is_fresh(v, sizeof(vect)) && + __CPROVER_is_fresh(v->arr, v->size) && + 0 < v->size && v->size <= __CPROVER_max_malloc_size && + v->size + 10 < __CPROVER_max_malloc_size +) +__CPROVER_assigns(v->size) +__CPROVER_ensures( + v->size == __CPROVER_old(v->size) + 10 && + __CPROVER_is_fresh(v->arr, v->size) +) +// clang-format on +{ + // assigns clause inclusion and frees clause inclusion checks must fail + resize_vec(v, 10); + return; +} diff --git a/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/enforce.desc b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/enforce.desc new file mode 100644 index 00000000000..710034739f0 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/enforce.desc @@ -0,0 +1,9 @@ +CORE +main_enforce.c +--dfcc main --enforce-contract resize_vec _ --signed-overflow-check --unsigned-overflow-check --pointer-overflow-check +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +Verifies the contract being replaced in `replace.desc`. diff --git a/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/main_enforce.c b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/main_enforce.c new file mode 100644 index 00000000000..9780886b632 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/main_enforce.c @@ -0,0 +1,9 @@ +#include "vect.h" + +int main() +{ + vect *v; + size_t incr; + resize_vec(v, incr); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/main_replace.c b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/main_replace.c new file mode 100644 index 00000000000..f6060603800 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/main_replace.c @@ -0,0 +1,8 @@ +#include "vect.h" + +int main() +{ + vect *v; + resize_vec_incr10(v); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/replace.desc b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/replace.desc new file mode 100644 index 00000000000..ddd7b761393 --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/replace.desc @@ -0,0 +1,12 @@ +CORE +main_replace.c +--dfcc main --replace-call-with-contract resize_vec --enforce-contract resize_vec_incr10 _ --signed-overflow-check --unsigned-overflow-check --pointer-overflow-check +^\[resize_vec.assigns.\d+\].*Check that the assigns clause of contract::resize_vec is included in the caller's assigns clause: SUCCESS$ +^\[resize_vec.frees.\d+\].*Check that the frees clause of contract::resize_vec is included in the caller's frees clause: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +Shows that we can successfully check that the assigns and frees clause of +a callee (resize_vec) are included in the caller's (resize_vec_incr10). \ No newline at end of file diff --git a/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/vect.h b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/vect.h new file mode 100644 index 00000000000..22b29307bfb --- /dev/null +++ b/regression/contracts-dfcc/assigns_replace_havoc_dependent_targets_pass/vect.h @@ -0,0 +1,51 @@ +#include +#include + +typedef struct vect +{ + char *arr; + size_t size; +} vect; + +void resize_vec(vect *v, size_t incr) + // clang-format off +__CPROVER_requires( + __CPROVER_is_fresh(v, sizeof(vect)) && + __CPROVER_is_fresh(v->arr, v->size) && + 0 < v->size && v->size <= __CPROVER_max_malloc_size && + 0 < incr && incr < __CPROVER_max_malloc_size - v->size +) +__CPROVER_assigns(v->size, v->arr, __CPROVER_object_whole(v->arr)) +__CPROVER_frees(v->arr) +__CPROVER_ensures( + v->size == __CPROVER_old(v->size) + __CPROVER_old(incr) && + __CPROVER_is_fresh(v->arr, v->size) +) +// clang-format on +{ + free(v->arr); + v->size += incr; + v->arr = malloc(v->size); + __CPROVER_array_set(v->arr, 0); + return; +} + +void resize_vec_incr10(vect *v) + // clang-format off +__CPROVER_requires( + __CPROVER_is_fresh(v, sizeof(vect)) && + __CPROVER_is_fresh(v->arr, v->size) && + 0 < v->size && v->size <= __CPROVER_max_malloc_size && + v->size + 10 < __CPROVER_max_malloc_size +) +__CPROVER_assigns(*v, __CPROVER_object_whole(v->arr)) +__CPROVER_frees(v->arr) +__CPROVER_ensures( + v->size == __CPROVER_old(v->size) + 10 && + __CPROVER_is_fresh(v->arr, v->size) +) +// clang-format on +{ + resize_vec(v, 10); + return; +} diff --git a/regression/contracts-dfcc/assigns_type_checking_invalid_case_01/main.c b/regression/contracts-dfcc/assigns_type_checking_invalid_case_01/main.c new file mode 100644 index 00000000000..dcbb1dc2956 --- /dev/null +++ b/regression/contracts-dfcc/assigns_type_checking_invalid_case_01/main.c @@ -0,0 +1,11 @@ +void foo(int a) __CPROVER_assigns(0) +{ + a = 0; +} + +int main() +{ + int n; + foo(n); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_type_checking_invalid_case_01/test.desc b/regression/contracts-dfcc/assigns_type_checking_invalid_case_01/test.desc new file mode 100644 index 00000000000..853af05bb8d --- /dev/null +++ b/regression/contracts-dfcc/assigns_type_checking_invalid_case_01/test.desc @@ -0,0 +1,9 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=(1|64)$ +^SIGNAL=0$ +^CONVERSION ERROR$ +^.*error: assigns clause target must be a non-void lvalue, a call to __CPROVER_POINTER_OBJECT or a call to a function returning void$ +-- +Checks whether type checking rejects literal constants in assigns clause. diff --git a/regression/contracts-dfcc/assigns_type_checking_invalid_case_02/main.c b/regression/contracts-dfcc/assigns_type_checking_invalid_case_02/main.c new file mode 100644 index 00000000000..380604fd314 --- /dev/null +++ b/regression/contracts-dfcc/assigns_type_checking_invalid_case_02/main.c @@ -0,0 +1,33 @@ +#include + +void bar(char d[]) __CPROVER_assigns(d[7]) +{ +} + +int main() +{ + char b[10]; + b[0] = 'a'; + b[1] = 'b'; + b[2] = 'c'; + b[3] = 'd'; + b[4] = 'e'; + b[5] = 'f'; + b[6] = 'g'; + b[7] = 'h'; + b[8] = 'i'; + b[9] = 'j'; + bar(b); + assert(b[0] == 'a'); + assert(b[1] == 'b'); + assert(b[2] == 'c'); + assert(b[3] == 'd'); + assert(b[4] == 'e'); + assert(b[5] == 'f'); + assert(b[6] == 'g'); + assert(b[7] == 'h'); + assert(b[8] == 'i'); + assert(b[9] == 'j'); + + return 0; +} diff --git a/regression/contracts-dfcc/assigns_type_checking_invalid_case_02/test.desc b/regression/contracts-dfcc/assigns_type_checking_invalid_case_02/test.desc new file mode 100644 index 00000000000..7c062ab47ef --- /dev/null +++ b/regression/contracts-dfcc/assigns_type_checking_invalid_case_02/test.desc @@ -0,0 +1,19 @@ +CORE +main.c +--dfcc main --replace-call-with-contract bar +^EXIT=(10)$ +^SIGNAL=0$ +^\[main.assertion.\d+\] line \d+ assertion b\[0\] \=\= \'a\': SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion b\[1\] \=\= \'b\': SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion b\[2\] \=\= \'c\': SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion b\[3\] \=\= \'d\': SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion b\[4\] \=\= \'e\': SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion b\[5\] \=\= \'f\': SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion b\[6\] \=\= \'g\': SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion b\[7\] \=\= \'h\': FAILURE$ +^\[main.assertion.\d+\] line \d+ assertion b\[8\] \=\= \'i\': SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion b\[9\] \=\= \'j\': SUCCESS$ +^VERIFICATION FAILED$ +-- +-- +Checks whether CBMC properly havocs a single instance of an array. diff --git a/regression/contracts-dfcc/assigns_type_checking_valid_cases/main.c b/regression/contracts-dfcc/assigns_type_checking_valid_cases/main.c new file mode 100644 index 00000000000..cc60cad45d0 --- /dev/null +++ b/regression/contracts-dfcc/assigns_type_checking_valid_cases/main.c @@ -0,0 +1,109 @@ +#include + +int *x; +int y; + +struct blob +{ + int allocated; +}; +struct buf +{ + int *data; + size_t len; + struct blob aux; +}; + +void foo1(int a) __CPROVER_assigns() +{ + a = 0; +} + +void foo2(int *b) __CPROVER_assigns() +{ + b = NULL; +} + +void foo3(int a, int *b) __CPROVER_assigns(x, y) +{ + b = NULL; + x = malloc(sizeof(*x)); + y = 0; +} + +void foo4(int a, int *b, int *c) __CPROVER_requires(c != NULL) + __CPROVER_requires(x != NULL) __CPROVER_assigns(*c, *x) +{ + b = NULL; + *c = 0; + *x = 0; +} + +void foo5(struct buf buffer) __CPROVER_assigns() +{ + // these are assignments to the function parameter which is a local symbol + // and should not generate checks + buffer.data = NULL; + buffer.len = 0; +} + +void foo6(struct buf *buffer) __CPROVER_assigns(buffer->data, buffer->len) +{ + buffer->data = malloc(sizeof(*(buffer->data))); + *(buffer->data) = 1; + buffer->len = 1; +} + +void foo7(int a, struct buf *buffer) __CPROVER_assigns(*buffer) +{ + buffer->data = malloc(sizeof(*(buffer->data))); + *(buffer->data) = 1; + buffer->len = 1; +} + +void foo8(int array[]) __CPROVER_assigns(__CPROVER_object_whole(array)) +{ + array[0] = 1; + array[1] = 1; + array[2] = 1; + array[3] = 1; + array[4] = 1; + array[5] = 1; + array[6] = 1; + array[7] = 1; + array[8] = 1; + array[9] = 1; +} + +void foo9(int array[]) __CPROVER_assigns() +{ + int *new_array = NULL; + array = new_array; +} + +void foo10(struct buf *buffer) __CPROVER_requires(buffer != NULL) + __CPROVER_assigns(buffer->len, buffer->aux.allocated) +{ + buffer->len = 0; + buffer->aux.allocated = 0; +} + +int main() +{ + int n; + int *m; + int *o = malloc(sizeof(*o)); + struct buf buffer; + int array_call[10] = {0}; + foo1(n); + foo2(&n); + foo3(n, m); + foo4(n, m, o); + foo5(buffer); + foo6(&buffer); + foo7(n, &buffer); + foo8(array_call); + foo9(array_call); + foo10(&buffer); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo1.desc b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo1.desc new file mode 100644 index 00000000000..a70cbd6b984 --- /dev/null +++ b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo1.desc @@ -0,0 +1,8 @@ +CORE +main.c +--dfcc main --enforce-contract foo1 _ --pointer-primitive-check +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +Checks whether CBMC parses correctly all standard cases for assigns clauses. diff --git a/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo10.desc b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo10.desc new file mode 100644 index 00000000000..dd08132adec --- /dev/null +++ b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo10.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo10 _ --pointer-primitive-check +^EXIT=0$ +^SIGNAL=0$ +^\[foo10.assigns.\d+\] line \d+ Check that buffer->len is assignable: SUCCESS$ +^\[foo10.assigns.\d+\] line \d+ Check that buffer->aux\.allocated is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +Checks whether CBMC parses correctly all standard cases for assigns clauses. diff --git a/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo2.desc b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo2.desc new file mode 100644 index 00000000000..7aa0d231826 --- /dev/null +++ b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo2.desc @@ -0,0 +1,8 @@ +CORE +main.c +--dfcc main --enforce-contract foo2 _ --pointer-primitive-check +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +Checks whether CBMC parses correctly all standard cases for assigns clauses. diff --git a/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo3.desc b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo3.desc new file mode 100644 index 00000000000..16d22bb961e --- /dev/null +++ b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo3.desc @@ -0,0 +1,9 @@ +CORE +main.c +--dfcc main --enforce-contract foo3 _ --pointer-primitive-check +^EXIT=0$ +^SIGNAL=0$ +^\[foo3.assigns.\d+\] line \d+ Check that y is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +Checks whether CBMC parses correctly all standard cases for assigns clauses. diff --git a/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo4.desc b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo4.desc new file mode 100644 index 00000000000..08eb9d5d689 --- /dev/null +++ b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo4.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo4 _ --pointer-primitive-check +^EXIT=0$ +^SIGNAL=0$ +^\[foo4.assigns.\d+\] line \d+ Check that \*c is assignable: SUCCESS$ +^\[foo4.assigns.\d+\] line \d+ Check that \*x is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +Checks whether CBMC parses correctly all standard cases for assigns clauses. diff --git a/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo5.desc b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo5.desc new file mode 100644 index 00000000000..53fc0727ec4 --- /dev/null +++ b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo5.desc @@ -0,0 +1,8 @@ +CORE +main.c +--dfcc main --enforce-contract foo5 _ --pointer-primitive-check +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +Checks whether CBMC parses correctly all standard cases for assigns clauses. diff --git a/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo6.desc b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo6.desc new file mode 100644 index 00000000000..98af88c6bed --- /dev/null +++ b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo6.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo6 _ --pointer-primitive-check +^EXIT=0$ +^SIGNAL=0$ +^\[foo6.assigns.\d+\] line \d+ Check that \*buffer->data is assignable: SUCCESS$ +^\[foo6.assigns.\d+\] line \d+ Check that buffer->len is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +Checks whether CBMC parses correctly all standard cases for assigns clauses. diff --git a/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo7.desc b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo7.desc new file mode 100644 index 00000000000..18bd4e774e8 --- /dev/null +++ b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo7.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo7 _ --pointer-primitive-check +^EXIT=0$ +^SIGNAL=0$ +^\[foo7.assigns.\d+\] line \d+ Check that \*buffer->data is assignable: SUCCESS$ +^\[foo7.assigns.\d+\] line \d+ Check that buffer->len is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +Checks whether CBMC parses correctly all standard cases for assigns clauses. diff --git a/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo8.desc b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo8.desc new file mode 100644 index 00000000000..d8d2b35ff8b --- /dev/null +++ b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo8.desc @@ -0,0 +1,18 @@ +CORE +main.c +--dfcc main --enforce-contract foo8 _ --pointer-primitive-check +^EXIT=0$ +^SIGNAL=0$ +^\[foo8.assigns.\d+\] line \d+ Check that array\[\(.* int\)0\] is assignable: SUCCESS$ +^\[foo8.assigns.\d+\] line \d+ Check that array\[\(.* int\)1\] is assignable: SUCCESS$ +^\[foo8.assigns.\d+\] line \d+ Check that array\[\(.* int\)2\] is assignable: SUCCESS$ +^\[foo8.assigns.\d+\] line \d+ Check that array\[\(.* int\)3\] is assignable: SUCCESS$ +^\[foo8.assigns.\d+\] line \d+ Check that array\[\(.* int\)4\] is assignable: SUCCESS$ +^\[foo8.assigns.\d+\] line \d+ Check that array\[\(.* int\)5\] is assignable: SUCCESS$ +^\[foo8.assigns.\d+\] line \d+ Check that array\[\(.* int\)6\] is assignable: SUCCESS$ +^\[foo8.assigns.\d+\] line \d+ Check that array\[\(.* int\)7\] is assignable: SUCCESS$ +^\[foo8.assigns.\d+\] line \d+ Check that array\[\(.* int\)8\] is assignable: SUCCESS$ +^\[foo8.assigns.\d+\] line \d+ Check that array\[\(.* int\)9\] is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +Checks whether CBMC parses correctly all standard cases for assigns clauses. diff --git a/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo9.desc b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo9.desc new file mode 100644 index 00000000000..b8dc351c084 --- /dev/null +++ b/regression/contracts-dfcc/assigns_type_checking_valid_cases/test-foo9.desc @@ -0,0 +1,8 @@ +CORE +main.c +--dfcc main --enforce-contract foo9 _ --pointer-primitive-check +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +Checks whether CBMC parses correctly all standard cases for assigns clauses. diff --git a/regression/contracts-dfcc/assigns_validity_pointer_01/main.c b/regression/contracts-dfcc/assigns_validity_pointer_01/main.c new file mode 100644 index 00000000000..ab5750df7f7 --- /dev/null +++ b/regression/contracts-dfcc/assigns_validity_pointer_01/main.c @@ -0,0 +1,36 @@ +#include +#include + +int *z; + +void bar(int *x, int *y) __CPROVER_assigns(*x, *y) __CPROVER_requires(*x > 0) + __CPROVER_ensures(*x == 3 && (y == NULL || *y == 5)) +{ + *x = 3; + if(y != NULL) + *y = 5; +} + +void baz() __CPROVER_assigns(*z) __CPROVER_ensures(z == NULL || *z == 7) +{ + if(z != NULL) + *z = 7; +} + +void foo(int *x) __CPROVER_assigns(*x, *z) __CPROVER_requires(*x > 0) + __CPROVER_ensures(*x == 3) +{ + bar(x, NULL); + baz(); +} + +int main() +{ + int n; + z = malloc(sizeof(*z)); + foo(&n); + + assert(n == 3); + assert(z == NULL || *z == 7); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_validity_pointer_01/test.desc b/regression/contracts-dfcc/assigns_validity_pointer_01/test.desc new file mode 100644 index 00000000000..039bc1d229d --- /dev/null +++ b/regression/contracts-dfcc/assigns_validity_pointer_01/test.desc @@ -0,0 +1,25 @@ +KNOWNBUG +main.c +--dfcc main --enforce-contract foo --replace-call-with-contract bar --replace-call-with-contract baz +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +SUCCESS +// bar +ASSERT \*foo::x > 0 +IF ¬\(\*foo::x = 3\) THEN GOTO \d +ASSIGN .*::tmp_if_expr := \(\*\(.*0.*\) = 5 \? true : false\) +ASSIGN .*::tmp_if_expr\$\d := .*::tmp_if_expr \? true : false +ASSUME .*::tmp_if_expr\$\d +// baz +IF ¬\(z ≠ NULL\) THEN GOTO \d +ASSIGN .*::tmp_if_expr\$\d := \(\*z = 7 \? true : false\) +ASSUME .*::tmp_if_expr\$\d +-- +\[3\] file main\.c line 6 assertion: FAILURE +-- +Verification: +This test checks support for a NULL pointer that is assigned to by +a function (bar and baz). Both functions bar and baz are being replaced by +their function contracts, while the calling function foo is being checked +(by enforcing it's function contracts). diff --git a/regression/contracts-dfcc/assigns_validity_pointer_02/main.c b/regression/contracts-dfcc/assigns_validity_pointer_02/main.c new file mode 100644 index 00000000000..ea747322195 --- /dev/null +++ b/regression/contracts-dfcc/assigns_validity_pointer_02/main.c @@ -0,0 +1,35 @@ +#include +#include + +int *z; + +void bar(int *x, int *y) +{ + *x = 3; + if(y != NULL) + *y = 5; +} + +void baz(int c) +{ + // does a side effect on a global, but + // in the calling context of foo the branch is dead + if(c) + *z = 7; +} + +void foo(int *x) __CPROVER_assigns(*x) __CPROVER_requires(*x > 0) + __CPROVER_ensures(*x == 3) +{ + bar(x, NULL); + *x = 3; + baz(0); +} + +int main() +{ + int n; + foo(&n); + assert(n == 3); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_validity_pointer_02/test.desc b/regression/contracts-dfcc/assigns_validity_pointer_02/test.desc new file mode 100644 index 00000000000..a309412cd6e --- /dev/null +++ b/regression/contracts-dfcc/assigns_validity_pointer_02/test.desc @@ -0,0 +1,17 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^\[foo.postcondition.\d+\].*Check ensures clause of contract contract::foo for function foo: SUCCESS$ +^\[bar.assigns.\d+\] line \d+ Check that \*x is assignable: SUCCESS$ +^\[bar.assigns.\d+\] line \d+ Check that \*y is assignable: SUCCESS$ +^\[baz.assigns.\d+\] line \d+ Check that \*z is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line \d+ Check that \*x is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that assigns clause checking +is control-flow sensitive. The assignment to the global *z +in baz is inhibited in the calling context of foo, so it does +not violate the assigns clause of foo. diff --git a/regression/contracts-dfcc/assigns_validity_pointer_03/main.c b/regression/contracts-dfcc/assigns_validity_pointer_03/main.c new file mode 100644 index 00000000000..a3e68c4f79e --- /dev/null +++ b/regression/contracts-dfcc/assigns_validity_pointer_03/main.c @@ -0,0 +1,33 @@ +#include +#include + +int *z; + +void bar(int *x, int *y) __CPROVER_assigns(*x, *y) __CPROVER_requires(*x > 0) + __CPROVER_ensures(*x == 3 && *y == 5) +{ +} + +void baz() __CPROVER_assigns(*z) __CPROVER_ensures(*z == 7) +{ +} + +void foo(int *x) __CPROVER_assigns(*x) __CPROVER_requires(*x > 0) + __CPROVER_ensures(*x == 3) +{ + int *y; + bar(x, y); + assert(*y == 5); + + baz(); + assert(*z == 7); +} + +int main() +{ + int n; + foo(&n); + + assert(n == 3); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_validity_pointer_03/test.desc b/regression/contracts-dfcc/assigns_validity_pointer_03/test.desc new file mode 100644 index 00000000000..2c6d282ed02 --- /dev/null +++ b/regression/contracts-dfcc/assigns_validity_pointer_03/test.desc @@ -0,0 +1,28 @@ +KNOWNBUG +main.c +--dfcc main --enforce-contract foo --replace-call-with-contract bar --replace-call-with-contract baz +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +// bar +ASSERT \*x > 0 +IF !\(\*x == 3\) THEN GOTO \d +tmp_if_expr = \*y == 5 \? true : false; +ASSUME tmp_if_expr +// baz +ASSUME \*z == 7 +// foo +ASSUME \*tmp_cc\$\d > 0 +ASSERT \*tmp_cc\$\d == 3 +-- +-- +Verification: +This test checks support for an uninitialized pointer that is assigned to by +a function (bar and baz). Both functions bar and baz are being replaced by +their function contracts, while the calling function foo is being checked +(by enforcing it's function contracts). + +Known Bug: +Currently, there is a known issue with __CPROVER_w_ok(ptr, 0) such that it +returns true if ptr is uninitialized. This is not the expected behavior, +therefore, the outcome of this test case is currently incorrect. diff --git a/regression/contracts-dfcc/assigns_validity_pointer_04/main.c b/regression/contracts-dfcc/assigns_validity_pointer_04/main.c new file mode 100644 index 00000000000..1042131e090 --- /dev/null +++ b/regression/contracts-dfcc/assigns_validity_pointer_04/main.c @@ -0,0 +1,34 @@ +#include +#include + +int *z; + +void bar(int *x, int *y) __CPROVER_assigns(*x, *y) __CPROVER_requires(*x > 0) + __CPROVER_ensures(*x == 3 && *y == 5) +{ +} + +void baz() __CPROVER_assigns(*z) __CPROVER_ensures(*z == 7) +{ +} + +void foo(int *x) __CPROVER_assigns(*x) __CPROVER_requires(*x > 0) + __CPROVER_ensures(*x == 3) +{ + int *y = malloc(sizeof(int)); + bar(x, y); + assert(*y == 5); + + z = malloc(sizeof(int)); + baz(); + assert(*z == 7); +} + +int main() +{ + int n; + foo(&n); + + assert(n == 3); + return 0; +} diff --git a/regression/contracts-dfcc/assigns_validity_pointer_04/test.desc b/regression/contracts-dfcc/assigns_validity_pointer_04/test.desc new file mode 100644 index 00000000000..8147fdda02f --- /dev/null +++ b/regression/contracts-dfcc/assigns_validity_pointer_04/test.desc @@ -0,0 +1,21 @@ +KNOWNBUG +main.c +--dfcc main --enforce-contract foo --replace-call-with-contract bar --replace-call-with-contract baz _ --pointer-primitive-check +^EXIT=10$ +^SIGNAL=0$ +^\[foo.assigns.\d+\] line \d+ Check that z is assignable: FAILURE$ +^.* 1 of \d+ failed \(\d+ iteration.*\)$ +^VERIFICATION FAILED$ +// bar +ASSERT \*foo::x > 0 +IF ¬\(\*foo::x = 3\) THEN GOTO \d +ASSIGN goto_convertt::tmp_if_expr := \(\*foo::1::y = 5 \? true : false\) +ASSUME .*::tmp_if_expr +// baz +ASSUME \*z = 7 +-- +-- +This test checks support for a malloced pointer that is assigned to by +a function (bar and baz). Both functions bar and baz are being replaced by +their function contracts, while the calling function foo is being checked +(by enforcing it's function contracts). diff --git a/regression/contracts-dfcc/chain.sh b/regression/contracts-dfcc/chain.sh new file mode 100755 index 00000000000..615fd4568d6 --- /dev/null +++ b/regression/contracts-dfcc/chain.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +set -e + +goto_cc=$1 +goto_instrument=$2 +cbmc=$3 +is_windows=$4 + +name=${*:$#} +name=${name%.c} + +args=${*:5:$#-5} +if [[ "$args" != *" _ "* ]] +then + args_inst=$args + args_cbmc="" +else + args_inst="${args%%" _ "*}" + args_cbmc="${args#*" _ "}" +fi + +if [[ "${is_windows}" == "true" ]]; then + $goto_cc "${name}.c" "/Fe${name}.gb" +else + $goto_cc -o "${name}.gb" "${name}.c" +fi + +rm -f "${name}-mod.gb" +$goto_instrument ${args_inst} "${name}.gb" "${name}-mod.gb" +if [ ! -e "${name}-mod.gb" ] ; then + cp "$name.gb" "${name}-mod.gb" +elif echo $args_inst | grep -q -- "--dump-c" ; then + mv "${name}-mod.gb" "${name}-mod.c" + + if [[ "${is_windows}" == "true" ]]; then + $goto_cc "${name}-mod.c" "/Fe${name}-mod.gb" + else + $goto_cc -o "${name}-mod.gb" "${name}-mod.c" + fi + + rm "${name}-mod.c" +fi +$goto_instrument --show-goto-functions "${name}-mod.gb" +$cbmc "${name}-mod.gb" ${args_cbmc} diff --git a/regression/contracts-dfcc/contracts_with_function_pointers/main.c b/regression/contracts-dfcc/contracts_with_function_pointers/main.c new file mode 100644 index 00000000000..7bcab49df95 --- /dev/null +++ b/regression/contracts-dfcc/contracts_with_function_pointers/main.c @@ -0,0 +1,30 @@ +#include +#include + +int x; + +void foo(int *y) +{ + *y = 1; +} + +int *baz() +{ + return &x; +} + +void bar(void (*fun_ptr)(), int *x) __CPROVER_requires(fun_ptr != NULL) + __CPROVER_requires(x != NULL) __CPROVER_assigns(*x) __CPROVER_ensures(*x == 1) +{ + *(baz()) = 1; + fun_ptr(x); +} + +int main() +{ + x = 0; + void (*fun_ptr)() = foo; + bar(fun_ptr, &x); + assert(x == 1); + return 0; +} diff --git a/regression/contracts-dfcc/contracts_with_function_pointers/test.desc b/regression/contracts-dfcc/contracts_with_function_pointers/test.desc new file mode 100644 index 00000000000..85ee25b00ea --- /dev/null +++ b/regression/contracts-dfcc/contracts_with_function_pointers/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract bar +^EXIT=0$ +^SIGNAL=0$ +^\[bar.postcondition.\d+\].*Check ensures clause of contract contract::bar for function bar: SUCCESS$ +^\[bar.assigns.\d+\].*Check that \*return\_value\_baz is assignable: SUCCESS$ +^\[foo.assigns.\d+\].*Check that \*y is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +Checks whether CBMC properly instrument functions with function pointers +during contract enforcement. diff --git a/regression/contracts-dfcc/cprover-assignable-fail/main.c b/regression/contracts-dfcc/cprover-assignable-fail/main.c new file mode 100644 index 00000000000..610ef93dc8a --- /dev/null +++ b/regression/contracts-dfcc/cprover-assignable-fail/main.c @@ -0,0 +1,29 @@ +#include + +void 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_object_whole(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-dfcc/cprover-assignable-fail/test.desc b/regression/contracts-dfcc/cprover-assignable-fail/test.desc new file mode 100644 index 00000000000..6f587ce7f43 --- /dev/null +++ b/regression/contracts-dfcc/cprover-assignable-fail/test.desc @@ -0,0 +1,22 @@ +CORE +main.c +--dfcc main +CALL __CPROVER_object_whole +CALL __CPROVER_object_upto +CALL __CPROVER_object_from +CALL __CPROVER_assignable +^\[__CPROVER_object_(assignable|from|upto|whole).*.\d+\] Built-in __CPROVER_object_(assignable|from|upto|whole) should not be called after contracts transformation: FAILURE$ +^\[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-ins __CPROVER_assignable, __CPROVER_object_whole, __CPROVER_object_from, + __CPROVER_object_upto are supported; +- GOTO conversion preserves calls to __CPROVER_object_whole, + __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-dfcc/cprover-assignable-pass/main.c b/regression/contracts-dfcc/cprover-assignable-pass/main.c new file mode 100644 index 00000000000..b7097e9cb10 --- /dev/null +++ b/regression/contracts-dfcc/cprover-assignable-pass/main.c @@ -0,0 +1,25 @@ +#include + +void 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_object_whole(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-dfcc/cprover-assignable-pass/test.desc b/regression/contracts-dfcc/cprover-assignable-pass/test.desc new file mode 100644 index 00000000000..41ee1b8545d --- /dev/null +++ b/regression/contracts-dfcc/cprover-assignable-pass/test.desc @@ -0,0 +1,21 @@ +CORE +main.c +--dfcc main +CALL __CPROVER_object_whole +CALL __CPROVER_object_upto +CALL __CPROVER_object_from +CALL __CPROVER_assignable +^\[__CPROVER_object_(assignable|from|upto|whole).*.\d+\] Built-in __CPROVER_object_(assignable|from|upto|whole) should not be called after contracts transformation: FAILURE$ +^\[my_write_set.assertion.\d+\] .* target null or writable: SUCCESS$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +CALL __CPROVER_typed_target +-- +This test checks that: +- built-in __CPROVER_assignable_t functions are supported; +- GOTO conversion preserves calls to __CPROVER_object_whole, + __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-dfcc/embedded_contract_fail_01/main.c b/regression/contracts-dfcc/embedded_contract_fail_01/main.c new file mode 100644 index 00000000000..00b38ce7e21 --- /dev/null +++ b/regression/contracts-dfcc/embedded_contract_fail_01/main.c @@ -0,0 +1,18 @@ +typedef void (*fun_ptr_t)(int x); + +void bar(int x) +{ + return; +} + +void foo(void (*fun_ptr)(int x) __CPROVER_requires(x != 0)) +{ + return; +} + +void main() +{ + fun_ptr_t fun_ptr = bar; + foo(fun_ptr); + return; +} diff --git a/regression/contracts-dfcc/embedded_contract_fail_01/test.desc b/regression/contracts-dfcc/embedded_contract_fail_01/test.desc new file mode 100644 index 00000000000..284fc6b655b --- /dev/null +++ b/regression/contracts-dfcc/embedded_contract_fail_01/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main +^.*: Function contracts allowed only at top-level declarations. .*$ +^PARSING ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +Checks if function contracts can be attached to function pointers +(with non-empty parameter lists) in function parameters. This should +fail. Exit code 64 for Windows servers. diff --git a/regression/contracts-dfcc/embedded_contract_fail_02/main.c b/regression/contracts-dfcc/embedded_contract_fail_02/main.c new file mode 100644 index 00000000000..bdd0a1bd645 --- /dev/null +++ b/regression/contracts-dfcc/embedded_contract_fail_02/main.c @@ -0,0 +1,18 @@ +typedef int (*fun_ptr_t)(); + +int bar() +{ + return 1; +} + +void foo(int (*fun_ptr)() __CPROVER_ensures(__CPROVER_return_value == 1)) +{ + return; +} + +void main() +{ + fun_ptr_t fun_ptr = bar; + foo(fun_ptr); + return; +} diff --git a/regression/contracts-dfcc/embedded_contract_fail_02/test.desc b/regression/contracts-dfcc/embedded_contract_fail_02/test.desc new file mode 100644 index 00000000000..1dccf94878f --- /dev/null +++ b/regression/contracts-dfcc/embedded_contract_fail_02/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main +^.*: Function contracts allowed only at top-level declarations. .*$ +^PARSING ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +Checks if function contracts can be attached to function pointers +(with empty parameter lists) in function parameters. This should +fail. Exit code 64 for Windows servers. diff --git a/regression/contracts-dfcc/enforce-replace-unknown-function/enforce.desc b/regression/contracts-dfcc/enforce-replace-unknown-function/enforce.desc new file mode 100644 index 00000000000..772a4322242 --- /dev/null +++ b/regression/contracts-dfcc/enforce-replace-unknown-function/enforce.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract goo +^Function to check 'goo' either not found or has no body$ +^EXIT=(0|127|134|137)$ +^SIGNAL=0$ +-- +-- +Checks that attempting to enforce the contract of an unknown function creates +an error. diff --git a/regression/contracts-dfcc/enforce-replace-unknown-function/main.c b/regression/contracts-dfcc/enforce-replace-unknown-function/main.c new file mode 100644 index 00000000000..f8b643afbf2 --- /dev/null +++ b/regression/contracts-dfcc/enforce-replace-unknown-function/main.c @@ -0,0 +1,4 @@ +int main() +{ + return 0; +} diff --git a/regression/contracts-dfcc/enforce-replace-unknown-function/replace.desc b/regression/contracts-dfcc/enforce-replace-unknown-function/replace.desc new file mode 100644 index 00000000000..206770d2ae0 --- /dev/null +++ b/regression/contracts-dfcc/enforce-replace-unknown-function/replace.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --replace-call-with-contract goo +^Function to replace 'goo' not found$ +^EXIT=(127|134)$ +^SIGNAL=0$ +-- +-- +Checks that attempting call replacement with an unknown function creates an +error. diff --git a/regression/contracts-dfcc/entry_point/main.c b/regression/contracts-dfcc/entry_point/main.c new file mode 100644 index 00000000000..151bc7306c2 --- /dev/null +++ b/regression/contracts-dfcc/entry_point/main.c @@ -0,0 +1,9 @@ +int foo(char *arr, unsigned int size) + // clang-format off +__CPROVER_requires(__CPROVER_is_fresh(arr, size)) +__CPROVER_assigns(arr &&size > 0: arr[0]) +// clang-format on +{ + if(arr && size > 0) + arr[0] = 1; +} diff --git a/regression/contracts-dfcc/entry_point/test.desc b/regression/contracts-dfcc/entry_point/test.desc new file mode 100644 index 00000000000..ee6f79d660d --- /dev/null +++ b/regression/contracts-dfcc/entry_point/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--enforce-contract foo _ --function foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that we can use a function with a contract as entry point +for the analysis when its contract gets enforced. diff --git a/regression/contracts-dfcc/frees-clause-and-predicates-fail/main.c b/regression/contracts-dfcc/frees-clause-and-predicates-fail/main.c new file mode 100644 index 00000000000..b15195d2ec9 --- /dev/null +++ b/regression/contracts-dfcc/frees-clause-and-predicates-fail/main.c @@ -0,0 +1,45 @@ +#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 + // error was_freed cannot be used in preconditions +__CPROVER_requires(!__CPROVER_was_freed(arr)) +__CPROVER_requires(__CPROVER_is_freeable(arr)) +__CPROVER_assigns(__CPROVER_object_whole(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_was_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-dfcc/frees-clause-and-predicates-fail/test.desc b/regression/contracts-dfcc/frees-clause-and-predicates-fail/test.desc new file mode 100644 index 00000000000..a6edf9e399c --- /dev/null +++ b/regression/contracts-dfcc/frees-clause-and-predicates-fail/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--enforce-contract foo +^main.c.* error: __CPROVER_was_freed is not allowed in preconditions.$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test checks that the front end rejects __CPROVER_was_freed in preconditions. diff --git a/regression/contracts-dfcc/frees-clause-and-predicates-fail2/main.c b/regression/contracts-dfcc/frees-clause-and-predicates-fail2/main.c new file mode 100644 index 00000000000..b10cb56d486 --- /dev/null +++ b/regression/contracts-dfcc/frees-clause-and-predicates-fail2/main.c @@ -0,0 +1,44 @@ +#include + +// A function defining a conditionally freeable target +int foo_frees(char *arr, const size_t size, const size_t new_size) +{ + __CPROVER_freeable(arr); + return 0; +} + +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_object_whole(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_was_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-dfcc/frees-clause-and-predicates-fail2/test.desc b/regression/contracts-dfcc/frees-clause-and-predicates-fail2/test.desc new file mode 100644 index 00000000000..b1484cf2b62 --- /dev/null +++ b/regression/contracts-dfcc/frees-clause-and-predicates-fail2/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--enforce-contract foo +^main.c.* error: expecting void 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-void-typed +function calls in frees clauses. diff --git a/regression/contracts-dfcc/frees-clause-and-predicates-is_freeable-bad-arity/main.c b/regression/contracts-dfcc/frees-clause-and-predicates-is_freeable-bad-arity/main.c new file mode 100644 index 00000000000..10d45ca3c87 --- /dev/null +++ b/regression/contracts-dfcc/frees-clause-and-predicates-is_freeable-bad-arity/main.c @@ -0,0 +1,13 @@ +#include + +void foo(char *arr) __CPROVER_requires(__CPROVER_is_freeable(arr, 1)) +{ +} + +int main() +{ + size_t size; + char arr[size]; + foo(arr); + return 0; +} diff --git a/regression/contracts-dfcc/frees-clause-and-predicates-is_freeable-bad-arity/test.desc b/regression/contracts-dfcc/frees-clause-and-predicates-is_freeable-bad-arity/test.desc new file mode 100644 index 00000000000..cfea1068a38 --- /dev/null +++ b/regression/contracts-dfcc/frees-clause-and-predicates-is_freeable-bad-arity/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--enforce-contract foo +^main.c.*error: wrong number of function arguments: expected 1, but got 2$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test checks bad uses of __CPROVER_is_freeable are rejected. diff --git a/regression/contracts-dfcc/frees-clause-and-predicates-is_freed-bad-arity/main.c b/regression/contracts-dfcc/frees-clause-and-predicates-is_freed-bad-arity/main.c new file mode 100644 index 00000000000..51209b124a2 --- /dev/null +++ b/regression/contracts-dfcc/frees-clause-and-predicates-is_freed-bad-arity/main.c @@ -0,0 +1,15 @@ +#include + +void foo(char *arr) __CPROVER_requires(__CPROVER_is_freeable(arr)) + __CPROVER_ensures(__CPROVER_was_freed(__CPROVER_old(arr), 1)) +{ + free(arr); +} + +int main() +{ + size_t size; + char arr[size]; + foo(arr); + return 0; +} diff --git a/regression/contracts-dfcc/frees-clause-and-predicates-is_freed-bad-arity/test.desc b/regression/contracts-dfcc/frees-clause-and-predicates-is_freed-bad-arity/test.desc new file mode 100644 index 00000000000..845d8bdde01 --- /dev/null +++ b/regression/contracts-dfcc/frees-clause-and-predicates-is_freed-bad-arity/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--enforce-contract foo +^main.c.*error: wrong number of function arguments: expected 1, but got 2$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test checks bad uses of __CPROVER_was_freed are rejected. diff --git a/regression/contracts-dfcc/frees-clause-and-predicates/main.c b/regression/contracts-dfcc/frees-clause-and-predicates/main.c new file mode 100644 index 00000000000..eb52d226ebe --- /dev/null +++ b/regression/contracts-dfcc/frees-clause-and-predicates/main.c @@ -0,0 +1,76 @@ +#include +#include + +// A function defining an assignable target +void foo_assigns(char *arr, const size_t size) +{ + __CPROVER_object_upto(arr, size); +} + +// A function defining an freeable target +void foo_frees(char *arr, const size_t size) +{ + __CPROVER_freeable(arr); +} + +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(ptr)) +__CPROVER_assigns(foo_assigns(ptr, size)) +__CPROVER_frees(foo_frees(ptr, size)) +__CPROVER_ensures( + (ptr && new_size > size) ==> + __CPROVER_is_fresh(__CPROVER_return_value, new_size)) +__CPROVER_ensures( + (ptr && new_size > size) ==> + __CPROVER_was_freed(ptr)) +__CPROVER_ensures( + !(ptr && new_size > size) ==> + __CPROVER_return_value == __CPROVER_old(ptr)) +// clang-format on +{ + // 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(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 + { + // write at some nondet i + size_t i; + if(i < size) + ptr[i] = 0; + + return ptr; + } +} + +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-dfcc/frees-clause-and-predicates/test.desc b/regression/contracts-dfcc/frees-clause-and-predicates/test.desc new file mode 100644 index 00000000000..4ee5db81a2b --- /dev/null +++ b/regression/contracts-dfcc/frees-clause-and-predicates/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +This test checks that the front end parses and typechecks correct uses of +- void function calls as frees clause targets +- the predicate __CPROVER_freeable +- the predicate __CPROVER_is_freeable +- the predicate __CPROVER_was_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-dfcc/function-calls-01-enforce-failure/main.c b/regression/contracts-dfcc/function-calls-01-enforce-failure/main.c new file mode 100644 index 00000000000..8290fecdfbf --- /dev/null +++ b/regression/contracts-dfcc/function-calls-01-enforce-failure/main.c @@ -0,0 +1,19 @@ +#include +int f(int *x) + // clang-format off +__CPROVER_requires(0 <= *x && *x <= 10000) +__CPROVER_ensures(__CPROVER_return_value == *x + 1) +__CPROVER_ensures(1 <= __CPROVER_return_value && __CPROVER_return_value <= 10001) +// clang-format on +{ + if(*x >= 10000) + return 0; // reachable under preconditions + return *x + 1; +} + +int main() +{ + int x; + f(&x); + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-01-enforce-failure/test.desc b/regression/contracts-dfcc/function-calls-01-enforce-failure/test.desc new file mode 100644 index 00000000000..a92e773c386 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-01-enforce-failure/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract f +^\[f.postcondition.\d+\] .* Check ensures clause of contract contract::f for function f: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This test checks that contract checking fails for a function that does not +satisfy its contract. diff --git a/regression/contracts-dfcc/function-calls-01-enforce-success/main.c b/regression/contracts-dfcc/function-calls-01-enforce-success/main.c new file mode 100644 index 00000000000..b94c24ff836 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-01-enforce-success/main.c @@ -0,0 +1,19 @@ +#include +int f(int *x) + // clang-format off +__CPROVER_requires(0 <= *x && *x <= 10000) +__CPROVER_ensures(__CPROVER_return_value == *x + 1) +__CPROVER_ensures(1 <= __CPROVER_return_value && __CPROVER_return_value <= 10001) +// clang-format on +{ + if(*x > 10000) + return 0; // unreachable under preconditions + return *x + 1; +} + +int main() +{ + int x; + f(&x); + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-01-enforce-success/test.desc b/regression/contracts-dfcc/function-calls-01-enforce-success/test.desc new file mode 100644 index 00000000000..04f08b1fb27 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-01-enforce-success/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract f +^\[f.postcondition.\d+\] .* Check ensures clause of contract contract::f for function f: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that contract checking is successfull for a function satifying +its contract. The harness does not restrain the input parameter in any way and +so this test shows that requires clauses are turned into assumptions. diff --git a/regression/contracts-dfcc/function-calls-01-replace-failure/main.c b/regression/contracts-dfcc/function-calls-01-replace-failure/main.c new file mode 100644 index 00000000000..53c84174dd9 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-01-replace-failure/main.c @@ -0,0 +1,26 @@ +#include +int f(int *x) + // clang-format off +__CPROVER_requires(0 <= *x && *x <= 10000) +__CPROVER_ensures(__CPROVER_return_value == *x + 1) +__CPROVER_ensures(1 <= __CPROVER_return_value && __CPROVER_return_value <= 10001) +// clang-format on +{ + return *x + 1; +} + +int main() +{ + int x; + if(0 <= x && x <= 10001) + { + // f gets called outside of its preconditions we expect checks to fail + int __return_value = f(&x); + // we expect this to hold regardless after contract replacement + // because post conditions are assumed regardless of the contract's + // precondition status + assert(__return_value == x + 1); + assert(1 <= __return_value && __return_value <= 10001); + } + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-01-replace-failure/test.desc b/regression/contracts-dfcc/function-calls-01-replace-failure/test.desc new file mode 100644 index 00000000000..7e9dfbc2dfe --- /dev/null +++ b/regression/contracts-dfcc/function-calls-01-replace-failure/test.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --replace-call-with-contract f +^\[f.precondition.\d+\] .* Check requires clause of contract contract::f for function f: FAILURE$ +^\[main.assertion.\d+\] .* assertion __return_value == x \+ 1: SUCCESS$ +^\[main.assertion.\d+\] .* assertion 1 <= __return_value && __return_value <= 10001: SUCCESS$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +Heere we test contract replacement at a call site where contract pre-conditions +do not hold and precondition checks must fail, while having downstream +assertions satisfied by virtue of assuming the contract post conditions. diff --git a/regression/contracts-dfcc/function-calls-01-replace-success/main.c b/regression/contracts-dfcc/function-calls-01-replace-success/main.c new file mode 100644 index 00000000000..bf61fd53ca1 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-01-replace-success/main.c @@ -0,0 +1,22 @@ +#include +int f(int *x) + // clang-format off +__CPROVER_requires(0 <= *x && *x <= 10000) +__CPROVER_ensures(__CPROVER_return_value == *x + 1) +__CPROVER_ensures(1 <= __CPROVER_return_value && __CPROVER_return_value <= 10001) +// clang-format on +{ + return *x + 1; +} + +int main() +{ + int x; + if(0 <= x && x <= 10000) + { + int __return_value = f(&x); + assert(__return_value == x + 1); + assert(1 <= __return_value && __return_value <= 10001); + } + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-01-replace-success/test.desc b/regression/contracts-dfcc/function-calls-01-replace-success/test.desc new file mode 100644 index 00000000000..a974401a4ba --- /dev/null +++ b/regression/contracts-dfcc/function-calls-01-replace-success/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --replace-call-with-contract f +^\[f.precondition.\d+\] .* Check requires clause of contract contract::f for function f: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Here we test contract replacement at a call site where contract pre-conditions +hold. Assuming the contract post conditions during replacement allows to satisfy +downstream assertions. diff --git a/regression/contracts-dfcc/function-calls-02-failure/main.c b/regression/contracts-dfcc/function-calls-02-failure/main.c new file mode 100644 index 00000000000..1ca035c974a --- /dev/null +++ b/regression/contracts-dfcc/function-calls-02-failure/main.c @@ -0,0 +1,30 @@ +#include +int f(int *x) __CPROVER_requires(0 <= *x && *x <= 10000) + __CPROVER_ensures(__CPROVER_return_value == *x + 2) +{ + return g(x) + 1; +} + +// preconditions of g are stricter wrt that of f +int g(int *x) __CPROVER_requires(0 < *x && *x < 10000) + __CPROVER_ensures(__CPROVER_return_value == *x + 1) +{ + // violate the post condition if g is called outside of its preconditions + if(!(0 < *x && *x < 10000)) + return 0; + return *x + 1; +} + +bool nondet_bool(); +int main() +{ + int x; + + // call either f or g + if(nondet_bool()) + f(&x); + else + g(&x); + + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-02-failure/test-enf-f-repl-g.desc b/regression/contracts-dfcc/function-calls-02-failure/test-enf-f-repl-g.desc new file mode 100644 index 00000000000..dbf977345a0 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-02-failure/test-enf-f-repl-g.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --enforce-contract f --replace-call-with-contract g +^\[f.postcondition.\d+\] .* Check ensures clause of contract contract::f for function f: SUCCESS$ +^\[g.precondition.\d+\] .* Check requires clause of contract contract::g for function g: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +f is checked against its contract. f calls g and g is replaced by its +contract. The analysis must fail since f violates the preconditions of g. +However we expect the post conditions of f to hold since contract replacement +assumes post conditions hold. diff --git a/regression/contracts-dfcc/function-calls-02-failure/test-enf-f.desc b/regression/contracts-dfcc/function-calls-02-failure/test-enf-f.desc new file mode 100644 index 00000000000..247a0513649 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-02-failure/test-enf-f.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract f +^\[f.postcondition.\d+\] .* Check ensures clause of contract contract::f for function f: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +f is checked against its contract. f calls g and g is not replaced by its +contract. f violates the preconditions of g, in turn g violates its post +conditions and in turn f violates its post conditions. diff --git a/regression/contracts-dfcc/function-calls-02-failure/test-enf-g.desc b/regression/contracts-dfcc/function-calls-02-failure/test-enf-g.desc new file mode 100644 index 00000000000..02416dba3f2 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-02-failure/test-enf-g.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract g +^\[g.postcondition.\d+\] .* Check ensures clause of contract contract::g for function g: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +We check g against its contract, the post conditions are expected to hold. diff --git a/regression/contracts-dfcc/function-calls-02-success/main.c b/regression/contracts-dfcc/function-calls-02-success/main.c new file mode 100644 index 00000000000..9e5283f8569 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-02-success/main.c @@ -0,0 +1,29 @@ +#include +int f(int *x) __CPROVER_requires(0 < *x && *x < 10000) + __CPROVER_ensures(__CPROVER_return_value == *x + 2) +{ + return g(x) + 1; +} + +int g(int *x) __CPROVER_requires(0 <= *x && *x <= 10000) + __CPROVER_ensures(__CPROVER_return_value == *x + 1) +{ + return *x + 1; +} + +bool nondet_bool(); +int main() +{ + int x; + + // call either f or g + if(nondet_bool()) + f(&x); + else + { + __CPROVER_assume(0 <= x && x <= 10000); + g(&x); + } + + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-02-success/test-enf-f-repl-g.desc b/regression/contracts-dfcc/function-calls-02-success/test-enf-f-repl-g.desc new file mode 100644 index 00000000000..ffcee261725 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-02-success/test-enf-f-repl-g.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract f --replace-call-with-contract g +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +We check f against its contract while replacing g by its contract. +Verification is expected to succeed. diff --git a/regression/contracts-dfcc/function-calls-02-success/test-enf-f.desc b/regression/contracts-dfcc/function-calls-02-success/test-enf-f.desc new file mode 100644 index 00000000000..737dbb8330d --- /dev/null +++ b/regression/contracts-dfcc/function-calls-02-success/test-enf-f.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract f +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +f calls g. We check f against its contract without replacing g. +The post conditions for f are expected to hold. diff --git a/regression/contracts-dfcc/function-calls-02-success/test-enf-g.desc b/regression/contracts-dfcc/function-calls-02-success/test-enf-g.desc new file mode 100644 index 00000000000..a3e967cf3c7 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-02-success/test-enf-g.desc @@ -0,0 +1,9 @@ +CORE +main.c +--dfcc main --enforce-contract g +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +We check g against its contract, the post conditions are expected to hold. diff --git a/regression/contracts-dfcc/function-calls-03-direct-recursion/main.c b/regression/contracts-dfcc/function-calls-03-direct-recursion/main.c new file mode 100644 index 00000000000..061becab529 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-03-direct-recursion/main.c @@ -0,0 +1,17 @@ +int f(int *x) __CPROVER_assigns() __CPROVER_requires(0 <= *x) + __CPROVER_ensures(__CPROVER_return_value == *x) +{ + if(*x < 1) + return 0; + int loc = *x - 1; // subtract 1 + return f(&loc) + 1; // add 1, cancels out +} + +int nondet_int(); +int main() +{ + int x = nondet_int(); + __CPROVER_assume(0 <= x); + f(&x); + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-03-direct-recursion/test-norec.desc b/regression/contracts-dfcc/function-calls-03-direct-recursion/test-norec.desc new file mode 100644 index 00000000000..66de5244e59 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-03-direct-recursion/test-norec.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract f +^\[.*\].*No recursive call to function f when checking contract f: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This test checks that when checking a contract against a function using +--enforce-contract, if the function is recursive then the verification fails. +The switch --enforce-contract-rec needs to be used instead. diff --git a/regression/contracts-dfcc/function-calls-03-direct-recursion/test-rec.desc b/regression/contracts-dfcc/function-calls-03-direct-recursion/test-rec.desc new file mode 100644 index 00000000000..58fea7903ed --- /dev/null +++ b/regression/contracts-dfcc/function-calls-03-direct-recursion/test-rec.desc @@ -0,0 +1,18 @@ +CORE +main.c +--dfcc main --enforce-contract-rec f +^\[f.postcondition.\d+\].*Check ensures clause of contract contract::f for function f: SUCCESS$ +^\[f.precondition.\d+\].*Check requires clause of contract contract::f for function f: SUCCESS$ +^\[f.assigns.\d+\].*Check that the assigns clause of contract::f is included in the caller's assigns clause: SUCCESS$ +^\[f.frees.\d+\].*Check that the frees clause of contract::f is included in the caller's frees clause: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that when checking a contract against a function using +--enforce-contract-rec, if the function is recursive and satsifies the contract +then the verification succeeds. +We want to see that post conditions are checked (top level call) +and preconditions are checked (recursive call). +We also want to see assigns and frees clause inclusion checks. diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/even_odd.h b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/even_odd.h new file mode 100644 index 00000000000..bcd20c4f84e --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/even_odd.h @@ -0,0 +1,27 @@ +#include +bool odd(int i); +bool even(int i) __CPROVER_requires(0 <= i && i <= 40) + __CPROVER_ensures(__CPROVER_return_value == (i % 2 == 0)) +{ + // patch infinite recursion bug introduced by odd and make sure even still + // works + if(i == 42) + i = 2; + + if(i == 0) + return true; + + // BUG: add spurious offset + return odd(i == 11 ? i - 2 : i - 1); +} + +bool odd(int i) __CPROVER_requires(0 <= i && i <= 40) + __CPROVER_ensures(__CPROVER_return_value == (i % 2 != 0)) +{ + if(i == 0) + return false; + + // BUG: call even outside of its preconditions + // BUG: break monotonous decrease and potentially cause infinite recursion + return even(i == 13 ? 42 : i - 1); +} diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/main_even.c b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/main_even.c new file mode 100644 index 00000000000..a03f73b8065 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/main_even.c @@ -0,0 +1,7 @@ +#include "even_odd.h" +int main() +{ + int i; + even(i); + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/main_odd.c b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/main_odd.c new file mode 100644 index 00000000000..1371311cabc --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/main_odd.c @@ -0,0 +1,7 @@ +#include "even_odd.h" +int main() +{ + int i; + odd(i); + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-even-repl-odd.desc b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-even-repl-odd.desc new file mode 100644 index 00000000000..6eb5a0e9f96 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-even-repl-odd.desc @@ -0,0 +1,12 @@ +CORE +main_even.c +--dfcc main --enforce-contract even --replace-call-with-contract odd +^\[even.postcondition.\d+\] .* Check ensures clause of contract contract::even for function even: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This model tries to proves the contract of even by assuming the contract of odd. +We exepect the analysis to fail since even itself contains a bug which falsifies +its postconditions. diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-even.desc b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-even.desc new file mode 100644 index 00000000000..1b1756e3774 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-even.desc @@ -0,0 +1,11 @@ +CORE +main_even.c +--dfcc main --enforce-contract even +^\[.*\].*No recursive call to function even when checking contract even: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +Must fail because even calls itself through odd and the recursive call is not +handled when using --enforce-contract (--enforce-contract-rec must be used). diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-odd-repl-even.desc b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-odd-repl-even.desc new file mode 100644 index 00000000000..9b00a041322 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-odd-repl-even.desc @@ -0,0 +1,11 @@ +CORE +main_odd.c +--dfcc main --enforce-contract odd --replace-call-with-contract even +^\[even.precondition.\d+\] .* Check requires clause of contract contract::even for function even: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This model tries to prove the contract of odd by assuming the contract of even. +We expect this to fail since odd calls even outside of its preconditions. diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-odd.desc b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-odd.desc new file mode 100644 index 00000000000..d9d818a6b10 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-odd.desc @@ -0,0 +1,11 @@ +CORE +main_odd.c +--dfcc main --enforce-contract odd +^\[.*\].*No recursive call to function odd when checking contract odd: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +Must fail because odd calls itself through even and the recursive call is not +handled when using --enforce-contract (--enforce-contract-rec must be used). diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-rec-even.desc b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-rec-even.desc new file mode 100644 index 00000000000..994d86580ee --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-rec-even.desc @@ -0,0 +1,15 @@ +CORE +main_even.c +--dfcc main --enforce-contract-rec even +^\[even.precondition.\d+\] .* Check requires clause of contract contract::even for function even: FAILURE$ +^\[even.postcondition.\d+\] .* Check ensures clause of contract contract::even for function even: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This model proves the contract of even by handling the recursive call using +--enforce-contract-rec. +We expect the analysis to fail since even calls odd and odd calls even again +while falsifying its preconditions, and even contains itself a bug which +falsifying its post conditions. diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-rec-odd.desc b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-rec-odd.desc new file mode 100644 index 00000000000..8e38faa26c2 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-failure/test-enf-rec-odd.desc @@ -0,0 +1,14 @@ +CORE +main_odd.c +--dfcc main --enforce-contract-rec odd +^\[odd.precondition.\d+\] .* Check requires clause of contract contract::odd for function odd: SUCCESS$ +^\[odd.postcondition.\d+\] .* Check ensures clause of contract contract::odd for function odd: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This model tries to prove the contract of odd handling the recursive call using +--enforce-contract-rec. +We expect the analysis to fail because odd contains a bug which allows to falsify +its post conditions. diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/even_odd.h b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/even_odd.h new file mode 100644 index 00000000000..6b5b41cc246 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/even_odd.h @@ -0,0 +1,17 @@ +#include +bool odd(int i); +bool even(int i) __CPROVER_requires(0 <= i && i <= 40) + __CPROVER_ensures(__CPROVER_return_value == (i % 2 == 0)) +{ + if(i == 0) + return true; + return odd(i - 1); +} + +bool odd(int i) __CPROVER_requires(0 <= i && i <= 40) + __CPROVER_ensures(__CPROVER_return_value == (i % 2 != 0)) +{ + if(i == 0) + return false; + return even(i - 1); +} diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/main_even.c b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/main_even.c new file mode 100644 index 00000000000..862a811fb46 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/main_even.c @@ -0,0 +1,8 @@ +#include "even_odd.h" + +int main() +{ + int i; + even(i); + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/main_odd.c b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/main_odd.c new file mode 100644 index 00000000000..9afb2d4e555 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/main_odd.c @@ -0,0 +1,8 @@ +#include "even_odd.h" + +int main() +{ + int i; + odd(i); + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/main_unwind.c b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/main_unwind.c new file mode 100644 index 00000000000..c49e535c1b8 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/main_unwind.c @@ -0,0 +1,9 @@ +#include "even_odd.h" + +int main() +{ + int i; + if(0 <= i && i <= 40) + odd(i); + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-even-repl-odd.desc b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-even-repl-odd.desc new file mode 100644 index 00000000000..fb0117b23d1 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-even-repl-odd.desc @@ -0,0 +1,14 @@ +CORE +main_even.c +--dfcc main --enforce-contract even --replace-call-with-contract odd +^\[odd.assigns.\d+\].*Check that the assigns clause of contract::odd is included in the caller's assigns clause: SUCCESS$ +^\[odd.frees.\d+\].*Check that the frees clause of contract::odd is included in the caller's frees clause: SUCCESS$ +^\[odd.precondition.\d+\].*Check requires clause of contract contract::odd for function odd: SUCCESS$ +^\[even.postcondition.\d+\].*Check ensures clause of contract contract::even for function even: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This model proves the contract of even by assuming the contract of odd. +Verification is expected to succeed. diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-even.desc b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-even.desc new file mode 100644 index 00000000000..b4a8f24cb8b --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-even.desc @@ -0,0 +1,11 @@ +CORE +main_even.c +--dfcc main --enforce-contract even +^\[.*\].*No recursive call to function even when checking contract even: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +Must fails because even calls itself through odd and the recursive call is not +handled when using --enforce-contract (--enforce-contract-rec must be used). diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-odd-repl-even.desc b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-odd-repl-even.desc new file mode 100644 index 00000000000..554c7a7f851 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-odd-repl-even.desc @@ -0,0 +1,13 @@ +CORE +main_odd.c +--dfcc main --enforce-contract odd --replace-call-with-contract even +^\[even.assigns.\d+\].*Check that the assigns clause of contract::even is included in the caller's assigns clause: SUCCESS$ +^\[even.frees.\d+\].*Check that the frees clause of contract::even is included in the caller's frees clause: SUCCESS$ +^\[even.precondition.\d+\].*Check requires clause of contract contract::even for function even: SUCCESS$ +^\[odd.postcondition.\d+\].*Check ensures clause of contract contract::odd for function odd: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This model proves the contract of odd by assuming the contract of even. diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-odd.desc b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-odd.desc new file mode 100644 index 00000000000..195ce5e638b --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-odd.desc @@ -0,0 +1,11 @@ +CORE +main_odd.c +--dfcc main --enforce-contract odd +^\[.*\].*No recursive call to function odd when checking contract odd: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +Must fails because odd calls itself through even and the recursive call is not +handled when using --enforce-contract (--enforce-contract-rec must be used). diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-rec-even.desc b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-rec-even.desc new file mode 100644 index 00000000000..7f66c51bea1 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-rec-even.desc @@ -0,0 +1,14 @@ +CORE +main_even.c +--dfcc main --enforce-contract-rec even +^\[even.assigns.\d+\].*Check that the assigns clause of contract::even is included in the caller's assigns clause: SUCCESS$ +^\[even.frees.\d+\].*Check that the frees clause of contract::even is included in the caller's frees clause: SUCCESS$ +^\[even.precondition.\d+\].*Check requires clause of contract contract::even for function even: SUCCESS$ +^\[even.postcondition.\d+\].*Check ensures clause of contract contract::even for function even: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This model proves the contract of even by handling the recursive call through +the contract-checking wrapper. diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-rec-odd.desc b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-rec-odd.desc new file mode 100644 index 00000000000..a61f0b86800 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-enf-rec-odd.desc @@ -0,0 +1,14 @@ +CORE +main_odd.c +--dfcc main --enforce-contract-rec odd +^\[odd.assigns.\d+\].*Check that the assigns clause of contract::odd is included in the caller's assigns clause: SUCCESS$ +^\[odd.frees.\d+\].*Check that the frees clause of contract::odd is included in the caller's frees clause: SUCCESS$ +^\[odd.precondition.\d+\].*Check requires clause of contract contract::odd for function odd: SUCCESS$ +^\[odd.postcondition.\d+\].*Check ensures clause of contract contract::odd for function odd: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This model proves the contract of odd by handling the recursive call using +--enforce-contract-rec. diff --git a/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-unwind.desc b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-unwind.desc new file mode 100644 index 00000000000..195793e3c3c --- /dev/null +++ b/regression/contracts-dfcc/function-calls-04-mutual-recursion-success/test-unwind.desc @@ -0,0 +1,10 @@ +CORE +main_unwind.c +--dfcc main _ --unwind 20 --unwinding-assertions +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This model is solved by unwinding to establish the ground truth for both +even and odd post conditions. diff --git a/regression/contracts-dfcc/function-calls-05-function-pointer-call-fail/main.c b/regression/contracts-dfcc/function-calls-05-function-pointer-call-fail/main.c new file mode 100644 index 00000000000..f9de02cf064 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-05-function-pointer-call-fail/main.c @@ -0,0 +1,29 @@ +#include +#include + +static int add_operator(int *x, int *y) +{ + int old_y = *y; + *y = 0; // BUG + return (*x + old_y); // mask the error +} + +// clang-format off +int foo(int *x, int *y) + __CPROVER_requires(x != NULL) + __CPROVER_requires(y != NULL) + __CPROVER_ensures( + __CPROVER_return_value == (__CPROVER_old(*x) + __CPROVER_old(*y))) +// clang-format on +{ + int (*sum)(int *, int *) = add_operator; + return sum(x, y); +} + +int main() +{ + int x; + int y; + foo(&x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-05-function-pointer-call-fail/test.desc b/regression/contracts-dfcc/function-calls-05-function-pointer-call-fail/test.desc new file mode 100644 index 00000000000..6d03b4b2a97 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-05-function-pointer-call-fail/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^\[foo.postcondition.\d+\] line \d+ Check ensures clause of contract contract::foo for function foo: SUCCESS$ +^\[add_operator.assigns.\d+\] line 7 Check that \*y is assignable: FAILURE$ +^VERIFICATION FAILED$ +^EXIT=10$ +^SIGNAL=0$ +-- +-- +Checks that frame condition checking is propagated +through function pointer calls. diff --git a/regression/contracts-dfcc/function-calls-05-function-pointer-call-pass/main.c b/regression/contracts-dfcc/function-calls-05-function-pointer-call-pass/main.c new file mode 100644 index 00000000000..520c43203e4 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-05-function-pointer-call-pass/main.c @@ -0,0 +1,27 @@ +#include +#include + +static int add_operator(int *x, int *y) +{ + return (*x + *y); +} + +// clang-format off +int foo(int *x, int *y) + __CPROVER_requires(x != NULL) + __CPROVER_requires(y != NULL) + __CPROVER_ensures( + __CPROVER_return_value == (__CPROVER_old(*x) + __CPROVER_old(*y))) +// clang-format on +{ + int (*sum)(int *, int *) = add_operator; + return sum(x, y); +} + +int main() +{ + int x; + int y; + foo(&x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-05-function-pointer-call-pass/test.desc b/regression/contracts-dfcc/function-calls-05-function-pointer-call-pass/test.desc new file mode 100644 index 00000000000..e9aafc8d995 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-05-function-pointer-call-pass/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract foo +\[foo.postcondition.\d+\] line \d+ Check ensures clause of contract contract::foo for function foo: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +Checks that frame condition checking is propagated +through function pointer calls. diff --git a/regression/contracts-dfcc/function-calls-recursive-function-1/main.c b/regression/contracts-dfcc/function-calls-recursive-function-1/main.c new file mode 100644 index 00000000000..d610fb1a711 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-recursive-function-1/main.c @@ -0,0 +1,23 @@ +int f(int *x) __CPROVER_requires(0 <= *x && *x < 20) + __CPROVER_ensures(__CPROVER_return_value == *x) +{ + return g(x); +} + +int g(int *x) __CPROVER_requires(0 <= *x && *x < 20) + __CPROVER_ensures(__CPROVER_return_value == *x) +{ + if(*x < 1) + return 0; + int loc = *x - 1; // subtract 1 + return g(&loc) + 1; // add 1, cancels out +} + +int nondet_int(); +int main() +{ + int x = nondet_int(); + __CPROVER_assume(0 <= x && x < 20); + f(&x); + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-recursive-function-1/test-replace.desc b/regression/contracts-dfcc/function-calls-recursive-function-1/test-replace.desc new file mode 100644 index 00000000000..ef38606c385 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-recursive-function-1/test-replace.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract f --replace-call-with-contract g +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +f calls g, g is recursive, g is replaced by its contract. +This test shows that we can prove the contract on f by abstracting the recursive +function g with its contract. diff --git a/regression/contracts-dfcc/function-calls-recursive-function-1/test-unwind.desc b/regression/contracts-dfcc/function-calls-recursive-function-1/test-unwind.desc new file mode 100644 index 00000000000..03481cc044b --- /dev/null +++ b/regression/contracts-dfcc/function-calls-recursive-function-1/test-unwind.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract f _ --unwind 20 --unwinding-assertions +^\[f.postcondition.\d+].*Check ensures clause of contract contract::f for function f: SUCCESS$ +^\[g.recursion\].*recursion unwinding assertion: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +f calls g and g is recursive, and is not replaced by its contract. +This shows that we can unwind recursion to completion on g using and prove the +contract on f. The frame condition checking scales with the unwinding. diff --git a/regression/contracts-dfcc/function-calls-recursive-function-2/main.c b/regression/contracts-dfcc/function-calls-recursive-function-2/main.c new file mode 100644 index 00000000000..b69a675e874 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-recursive-function-2/main.c @@ -0,0 +1,23 @@ +#include + +int sum_rec(int i, int acc) +{ + if(i >= 0) + return sum_rec(i - 1, acc + i); + return acc; +} + +int sum(int i) __CPROVER_requires(0 <= i && i <= 50) + __CPROVER_ensures(__CPROVER_return_value >= 0) __CPROVER_assigns() +{ + int j = i; + int res = sum_rec(j, 0); + return res; +} + +int main() +{ + int result = sum(10); + __CPROVER_assert(result == 55, "result == 55"); + return 0; +} diff --git a/regression/contracts-dfcc/function-calls-recursive-function-2/test.desc b/regression/contracts-dfcc/function-calls-recursive-function-2/test.desc new file mode 100644 index 00000000000..a9fe38c78a5 --- /dev/null +++ b/regression/contracts-dfcc/function-calls-recursive-function-2/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract sum +^\[sum.postcondition.\d+\].*Check ensures clause of contract contract::sum for function sum: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +The main function calls a recursive function for which unwinding and constant +propagation suffices to prove termination. The frame condition checking scales +automatically with the unwinding. diff --git a/regression/contracts-dfcc/function-pointer-contracts-enforce/main.c b/regression/contracts-dfcc/function-pointer-contracts-enforce/main.c new file mode 100644 index 00000000000..e91358d6577 --- /dev/null +++ b/regression/contracts-dfcc/function-pointer-contracts-enforce/main.c @@ -0,0 +1,65 @@ +#include +#include + +bool nondet_bool(); + +// type of functions that manipulate arrays +typedef void (*arr_fun_t)(char *arr, size_t size); + +// A contract for the arr_fun_t type +// requires a fresh array and positive size +// resets the first element to zero +void arr_fun_contract(char *arr, size_t size) + // clang-format off +__CPROVER_requires(__CPROVER_is_fresh(arr, size) && size > 0) +__CPROVER_assigns(arr[0]) +__CPROVER_ensures(arr[0] == 0) +// clang-format on +{ +} + +arr_fun_t bar() + // clang-format off + __CPROVER_ensures_contract(__CPROVER_return_value, arr_fun_contract) +// clang-format on +{ + // return a function that satisfies the contract + return arr_fun_contract; +} + +// Testing pre-conditions constructs +// Takes a function pointer as input, uses it if its preconditions are met +int foo(char *arr, size_t size, arr_fun_t arr_fun) + // clang-format off +__CPROVER_requires_contract(arr_fun, arr_fun_contract) +__CPROVER_requires(arr == NULL || __CPROVER_is_fresh(arr, size)) +__CPROVER_assigns(arr && size > 0: arr[0]) +__CPROVER_ensures(arr && size > 0 ==> (arr[0] == 0 && __CPROVER_return_value == 0)) +__CPROVER_ensures(!(arr && size > 0) ==> __CPROVER_return_value == -1) +// clang-format on +{ + if(nondet_bool()) + arr_fun = bar(); // non-deterministically get a function pointer from bar() + + int retval = -1; + if(arr && size > 0) // check the preconditions of the function pointer + { + // calls the function pointer to do update the array + arr_fun(arr, size); + retval = 0; + } + // the post conditions of foo are established thanks to the post conditions + // of arr_fun + return retval; +} + +void main() +{ + size_t size; + char *arr; + arr_fun_t arr_fun; + // The precondition that `arr_fun` obeys `arr_fun` + // and that bar returns a function that obeys `arr_fun` + // are used establish the post-conditions of `foo` + foo(arr, size, arr_fun); +} diff --git a/regression/contracts-dfcc/function-pointer-contracts-enforce/test-manual-swap.desc b/regression/contracts-dfcc/function-pointer-contracts-enforce/test-manual-swap.desc new file mode 100644 index 00000000000..716ddc6b4ff --- /dev/null +++ b/regression/contracts-dfcc/function-pointer-contracts-enforce/test-manual-swap.desc @@ -0,0 +1,22 @@ +CORE +main.c +--restrict-function-pointer foo.function_pointer_call.1/arr_fun_contract --dfcc main --enforce-contract foo --replace-call-with-contract bar --replace-call-with-contract arr_fun_contract +^\[arr_fun_contract.assigns.\d+\].*Check that the assigns clause of contract::arr_fun_contract is included in the caller's assigns clause: SUCCESS$ +^\[arr_fun_contract.frees.\d+\].*Check that the frees clause of contract::arr_fun_contract is included in the caller's frees clause: SUCCESS$ +^\[arr_fun_contract.precondition.\d+\].*Check requires clause of contract contract::arr_fun_contract for function arr_fun_contract: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +foo requires that a function pointer given as input satisfies +the contract arr_fun_contract. It nondeterministically updates the function +pointer by calling the function bar. The new function pointer is also assumed +to satisfy the same contract by virtue of bar's post conditions. +Foo then calls the function pointer, after making sure that its preconditions +are satified. +We check that the preconditions of arr_fun_contract are checked and satisfied +and that the assigns clause and frees clause inclusion checks of the function +pointer against foo are also checked and satisfied. +In this test, the function pointer is manually swapped with itself so +goto-instrument should not attempt to wrap it again. \ No newline at end of file diff --git a/regression/contracts-dfcc/function-pointer-contracts-enforce/test.desc b/regression/contracts-dfcc/function-pointer-contracts-enforce/test.desc new file mode 100644 index 00000000000..324d9eea078 --- /dev/null +++ b/regression/contracts-dfcc/function-pointer-contracts-enforce/test.desc @@ -0,0 +1,22 @@ +CORE +main.c +--restrict-function-pointer foo.function_pointer_call.1/arr_fun_contract --dfcc main --enforce-contract foo --replace-call-with-contract bar +^\[arr_fun_contract.assigns.\d+\].*Check that the assigns clause of contract::arr_fun_contract is included in the caller's assigns clause: SUCCESS$ +^\[arr_fun_contract.frees.\d+\].*Check that the frees clause of contract::arr_fun_contract is included in the caller's frees clause: SUCCESS$ +^\[arr_fun_contract.precondition.\d+\].*Check requires clause of contract contract::arr_fun_contract for function arr_fun_contract: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +foo requires that a function pointer given as input satisfies +the contract arr_fun_contract. It nondeterministically updates the function +pointer by calling the function bar. The new function pointer is also assumed +to satisfy the same contract by virtue of bar's post conditions. +Foo then calls the function pointer, after making sure that its preconditions +are satified. +We check that the preconditions of arr_fun_contract are checked and satisfied +and that the assigns clause and frees clause inclusion checks of the function +pointer against foo are also checked and satisfied. +In this test, the function pointer contract is automatically discovered and +swapped with itself. \ No newline at end of file diff --git a/regression/contracts-dfcc/function-pointer-contracts-replace/main.c b/regression/contracts-dfcc/function-pointer-contracts-replace/main.c new file mode 100644 index 00000000000..bcdbd1de4bb --- /dev/null +++ b/regression/contracts-dfcc/function-pointer-contracts-replace/main.c @@ -0,0 +1,39 @@ +#include +#include + +// type of functions that manipulate arrays +typedef void (*arr_fun_t)(char *arr, size_t size); + +// A contract for the arr_fun_t type +void arr_fun_contract(char *arr, size_t size) + // clang-format off +__CPROVER_requires((size > 0 && __CPROVER_is_fresh(arr, size))) +__CPROVER_assigns(arr[0]) +__CPROVER_ensures(arr[0] == 0) +// clang-format on +{ +} + +arr_fun_t foo(arr_fun_t infun, arr_fun_t *outfun) + // clang-format off +__CPROVER_requires_contract(infun, arr_fun_contract) +__CPROVER_ensures_contract(*outfun, arr_fun_contract) +__CPROVER_ensures_contract(__CPROVER_return_value, arr_fun_contract) +// clang-format on +{ + *outfun = arr_fun_contract; + return infun; +} + +void main() +{ + // establish pre-conditions before replacement + arr_fun_t infun = arr_fun_contract; + + arr_fun_t outfun1 = NULL; + arr_fun_t outfun2 = foo(infun, &outfun1); + + // checking post-conditions after replacement + assert(outfun1 == arr_fun_contract); + assert(outfun2 == arr_fun_contract); +} diff --git a/regression/contracts-dfcc/function-pointer-contracts-replace/test.desc b/regression/contracts-dfcc/function-pointer-contracts-replace/test.desc new file mode 100644 index 00000000000..6a07d76a425 --- /dev/null +++ b/regression/contracts-dfcc/function-pointer-contracts-replace/test.desc @@ -0,0 +1,15 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^\[foo.precondition.\d+\].*Assert function pointer 'infun_wrapper' obeys contract 'arr_fun_contract': SUCCESS$ +^\[main.assertion.\d+\].*assertion outfun1 == arr_fun_contract: SUCCESS$ +^\[main.assertion.\d+\].*assertion outfun2 == arr_fun_contract: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that, when replacing a call by its contract, +requires_contract clauses are translated to equality checks +and that ensures_contract are translated to assignments of the function pointer +variable to the contract symbol. diff --git a/regression/contracts-dfcc/function_apply_01/main.c b/regression/contracts-dfcc/function_apply_01/main.c new file mode 100644 index 00000000000..f12e9a8033b --- /dev/null +++ b/regression/contracts-dfcc/function_apply_01/main.c @@ -0,0 +1,19 @@ +// function_apply_01 + +// Note that this test is supposed to have an incorrect contract. +// We verify that applying (without checking) the contract yields success, +// and that checking the contract yields failure. + +#include + +int foo() __CPROVER_ensures(__CPROVER_return_value == 0) +{ + return 1; +} + +int main() +{ + int x = foo(); + assert(x == 0); + return 0; +} diff --git a/regression/contracts-dfcc/function_apply_01/test.desc b/regression/contracts-dfcc/function_apply_01/test.desc new file mode 100644 index 00000000000..26399ed97d9 --- /dev/null +++ b/regression/contracts-dfcc/function_apply_01/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +This code is purposely unsound (the function does not abide by its +contract). Verifying the function in isolation should fail, and +verifying its caller should succeed. diff --git a/regression/contracts-dfcc/function_check_01/main.c b/regression/contracts-dfcc/function_check_01/main.c new file mode 100644 index 00000000000..12526c16ac5 --- /dev/null +++ b/regression/contracts-dfcc/function_check_01/main.c @@ -0,0 +1,33 @@ +// function_check_01 + +// This tests a simple example of a function with requires and +// ensures which should both be satisfied. + +#include + +int min(int a, int b) + // clang-format off + __CPROVER_requires(a >= 0 && b >= 0) + __CPROVER_ensures(__CPROVER_return_value <= a && + __CPROVER_return_value <= b && + (__CPROVER_return_value == a || __CPROVER_return_value == b) + ) +// clang-format on +{ + if(a <= b) + { + return a; + } + else + { + return b; + } +} + +int main() +{ + int x, y, z; + __CPROVER_assume(x >= 0 && y >= 0); + z = min(x, y); + assert(z <= x); +} diff --git a/regression/contracts-dfcc/function_check_01/test.desc b/regression/contracts-dfcc/function_check_01/test.desc new file mode 100644 index 00000000000..5400a943e42 --- /dev/null +++ b/regression/contracts-dfcc/function_check_01/test.desc @@ -0,0 +1,9 @@ +CORE +main.c +--dfcc main --replace-call-with-contract min +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This tests a simple example of a function with requires and ensures which should both be satisfied. diff --git a/regression/contracts-dfcc/function_check_02/main.c b/regression/contracts-dfcc/function_check_02/main.c new file mode 100644 index 00000000000..d065822bece --- /dev/null +++ b/regression/contracts-dfcc/function_check_02/main.c @@ -0,0 +1,36 @@ +// function_check_02 + +// This test checks the use of quantifiers in ensures clauses. +// A known bug (resolved in PR #2278) causes the use of quantifiers +// in ensures to fail. + +// clang-format off +int initialize(int *arr) + __CPROVER_assigns(__CPROVER_object_whole(arr)) + __CPROVER_ensures( + __CPROVER_forall { + int i; + (0 <= i && i < 10) ==> arr[i] == i + } + ) +// clang-format on +{ + arr[0] = 0; + arr[1] = 1; + arr[2] = 2; + arr[3] = 3; + arr[4] = 4; + arr[5] = 5; + arr[6] = 6; + arr[7] = 7; + arr[8] = 8; + arr[9] = 9; + + return 0; +} + +int main() +{ + int arr[10]; + initialize(arr); +} diff --git a/regression/contracts-dfcc/function_check_02/test.desc b/regression/contracts-dfcc/function_check_02/test.desc new file mode 100644 index 00000000000..cf06d4b8c54 --- /dev/null +++ b/regression/contracts-dfcc/function_check_02/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract initialize +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Ensures statements currently do not allow quantified predicates unless the +function has void return type. diff --git a/regression/contracts-dfcc/function_check_03/main.c b/regression/contracts-dfcc/function_check_03/main.c new file mode 100644 index 00000000000..cbb82d01207 --- /dev/null +++ b/regression/contracts-dfcc/function_check_03/main.c @@ -0,0 +1,31 @@ +// function_check_03 + +// This extends function_check_02's test of quantifiers in ensures +// and adds in a loop invariant which can be used to prove the ensures. +// This currently fails because side-effect checking in loop invariants is +// incorrect. + +void initialize(int *arr, int len) + // clang-format off + + __CPROVER_ensures( + __CPROVER_forall {int i; (0 <= i && i < len) ==> arr[i] == i} + ) +// clang-format on +{ + for(int i = 0; i < len; i++) + // clang-format off + __CPROVER_loop_invariant( + __CPROVER_forall {int j; (0 <= j && j < i) ==> arr[j] == j} + ) + // clang-format on + { + arr[i] = i; + } +} + +int main() +{ + int arr[10]; + initialize(arr, 10); +} diff --git a/regression/contracts-dfcc/function_check_03/test.desc b/regression/contracts-dfcc/function_check_03/test.desc new file mode 100644 index 00000000000..9bb23a02927 --- /dev/null +++ b/regression/contracts-dfcc/function_check_03/test.desc @@ -0,0 +1,10 @@ +KNOWNBUG +main.c +--dfcc main --check-code-contracts +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Loop invariants currently do not support memory reads in at least some +circumstances. diff --git a/regression/contracts-dfcc/function_check_04/main.c b/regression/contracts-dfcc/function_check_04/main.c new file mode 100644 index 00000000000..417e11c9132 --- /dev/null +++ b/regression/contracts-dfcc/function_check_04/main.c @@ -0,0 +1,18 @@ +// function_check_04 + +// Note that this test is supposed to have an incorrect contract. +// We verify that checking this faulty contract (correctly) yields a failure. + +#include + +int foo() __CPROVER_ensures(__CPROVER_return_value == 0) +{ + return 1; +} + +int main() +{ + int x = foo(); + assert(x == 0); + return 0; +} diff --git a/regression/contracts-dfcc/function_check_04/test.desc b/regression/contracts-dfcc/function_check_04/test.desc new file mode 100644 index 00000000000..8b620b567ee --- /dev/null +++ b/regression/contracts-dfcc/function_check_04/test.desc @@ -0,0 +1,13 @@ +KNOWNBUG +main.c +--dfcc main --apply-code-contracts +^EXIT=10$ +^SIGNAL=0$ +^\[main.assertion.\d+\] .* assertion x == 0: SUCCESS$ +^\[foo.\d+\] .* : FAILURE$ +^VERIFICATION FAILED$ +-- +-- +--check-code-contracts not implemented yet. +--apply-code-contracts is the current name for the flag. This should be +updated as the flag changes. diff --git a/regression/contracts-dfcc/function_check_05/main.c b/regression/contracts-dfcc/function_check_05/main.c new file mode 100644 index 00000000000..7274f49d1a0 --- /dev/null +++ b/regression/contracts-dfcc/function_check_05/main.c @@ -0,0 +1,25 @@ +// function_check_05 + +// This test checks that when a function call is replaced by an invariant, +// it adequately havocs the locations modified by the function. +// This test currently fails because the analysis of what is modified by +// a function is flawed. + +#include + +int foo(int *x) __CPROVER_ensures(__CPROVER_return_value == 1) +{ + *x = 1; + return 1; +} + +int main() +{ + int y = 0; + int z = foo(&y); + // This assert should fail. + assert(y == 0); + // This one should succeed. + assert(z == 1); + return 0; +} diff --git a/regression/contracts-dfcc/function_check_05/test.desc b/regression/contracts-dfcc/function_check_05/test.desc new file mode 100644 index 00000000000..a9157dcc978 --- /dev/null +++ b/regression/contracts-dfcc/function_check_05/test.desc @@ -0,0 +1,12 @@ +KNOWNBUG +main.c +--dfcc main --check-code-contracts +^EXIT=0$ +^SIGNAL=0$ +^\[main.assertion.\d+\] assertion y == 0: FAILURE$ +^\[main.assertion.\d+\] assertion z == 1: SUCCESS$ +^\[foo.\d+\] : SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Contract checking does not properly havoc function calls. diff --git a/regression/contracts-dfcc/function_check_mem_01/main.c b/regression/contracts-dfcc/function_check_mem_01/main.c new file mode 100644 index 00000000000..0f42e0b9fe0 --- /dev/null +++ b/regression/contracts-dfcc/function_check_mem_01/main.c @@ -0,0 +1,39 @@ +// function_check_mem_01 + +// This test checks the use of pointer-related predicates in assumptions and +// requires. +// This test currently fails because of the lack of support for assuming +// pointer predicates. + +#include + +#define __CPROVER_VALID_MEM(ptr, size) \ + __CPROVER_POINTER_OBJECT((ptr)) != __CPROVER_POINTER_OBJECT(NULL) && \ + !__CPROVER_is_invalid_pointer((ptr)) && \ + __CPROVER_POINTER_OBJECT((ptr)) != \ + __CPROVER_POINTER_OBJECT(__CPROVER_deallocated) && \ + __CPROVER_POINTER_OBJECT((ptr)) != \ + __CPROVER_POINTER_OBJECT(__CPROVER_dead_object) && \ + (__builtin_object_size((ptr), 1) >= (size) && \ + __CPROVER_POINTER_OFFSET((ptr)) >= 0l) + +typedef struct bar +{ + int x; + int y; + int z; +} bar; + +void foo(bar *x) __CPROVER_requires(__CPROVER_VALID_MEM(x, sizeof(bar))) +{ + x->x += 1; + return; +} + +int main() +{ + bar *y; + __CPROVER_assume(__CPROVER_VALID_MEM(y, sizeof(bar))); + y->x = 0; + return 0; +} diff --git a/regression/contracts-dfcc/function_check_mem_01/test.desc b/regression/contracts-dfcc/function_check_mem_01/test.desc new file mode 100644 index 00000000000..ad947b626ae --- /dev/null +++ b/regression/contracts-dfcc/function_check_mem_01/test.desc @@ -0,0 +1,10 @@ +KNOWNBUG +main.c +--dfcc main --check-code-contracts +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +CBMC currently does not support assumptions about pointers in the general way +that other assumptions are supported. diff --git a/regression/contracts-dfcc/function_loop_history_ensures_fail/main.c b/regression/contracts-dfcc/function_loop_history_ensures_fail/main.c new file mode 100644 index 00000000000..8ff92262bf0 --- /dev/null +++ b/regression/contracts-dfcc/function_loop_history_ensures_fail/main.c @@ -0,0 +1,13 @@ +void foo(int *x) __CPROVER_assigns(*x) + __CPROVER_ensures(*x == __CPROVER_loop_entry(*x) + 5) +{ + *x = *x + 5; +} + +int main() +{ + int n; + foo(&n); + + return 0; +} diff --git a/regression/contracts-dfcc/function_loop_history_ensures_fail/test.desc b/regression/contracts-dfcc/function_loop_history_ensures_fail/test.desc new file mode 100644 index 00000000000..a76c403bcd3 --- /dev/null +++ b/regression/contracts-dfcc/function_loop_history_ensures_fail/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^main.c.* error: __CPROVER_loop_entry is not allowed in postconditions.$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test ensures that __CPROVER_loop_entry cannot be used within ensures clause. diff --git a/regression/contracts-dfcc/function_loop_history_requires_fail/main.c b/regression/contracts-dfcc/function_loop_history_requires_fail/main.c new file mode 100644 index 00000000000..f0f4aeeba22 --- /dev/null +++ b/regression/contracts-dfcc/function_loop_history_requires_fail/main.c @@ -0,0 +1,13 @@ +void bar(int *x) __CPROVER_assigns(*x) + __CPROVER_requires(*x == __CPROVER_loop_entry(*x) + 5) +{ + *x = *x + 5; +} + +int main() +{ + int n; + foo(&n); + + return 0; +} diff --git a/regression/contracts-dfcc/function_loop_history_requires_fail/test.desc b/regression/contracts-dfcc/function_loop_history_requires_fail/test.desc new file mode 100644 index 00000000000..dac98674eb9 --- /dev/null +++ b/regression/contracts-dfcc/function_loop_history_requires_fail/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract bar +^main.c.* error: __CPROVER_loop_entry is not allowed in preconditions.$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test ensures that __CPROVER_loop_entry cannot be used within requires clause. diff --git a/regression/contracts-dfcc/function_no_apply_01/main.c b/regression/contracts-dfcc/function_no_apply_01/main.c new file mode 100644 index 00000000000..f12e9a8033b --- /dev/null +++ b/regression/contracts-dfcc/function_no_apply_01/main.c @@ -0,0 +1,19 @@ +// function_apply_01 + +// Note that this test is supposed to have an incorrect contract. +// We verify that applying (without checking) the contract yields success, +// and that checking the contract yields failure. + +#include + +int foo() __CPROVER_ensures(__CPROVER_return_value == 0) +{ + return 1; +} + +int main() +{ + int x = foo(); + assert(x == 0); + return 0; +} diff --git a/regression/contracts-dfcc/function_no_apply_01/test.desc b/regression/contracts-dfcc/function_no_apply_01/test.desc new file mode 100644 index 00000000000..b0b6e135789 --- /dev/null +++ b/regression/contracts-dfcc/function_no_apply_01/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main +^EXIT=10$ +^SIGNAL=0$ +^\[main.assertion.1\] line 17 assertion x == 0: FAILURE$ +^VERIFICATION FAILED$ +-- +^warning: ignoring +-- +We don't actually replace the function call with its contract here, so +CBMC should notice that the program is unsound. diff --git a/regression/contracts-dfcc/havoc-static/main.c b/regression/contracts-dfcc/havoc-static/main.c new file mode 100644 index 00000000000..eb7cc42c847 --- /dev/null +++ b/regression/contracts-dfcc/havoc-static/main.c @@ -0,0 +1,20 @@ +int a = 0; // should be havoced +const int b = 0; // should not be havoced (const) +int c = 0; // should be havoced + +void foo() __CPROVER_requires(1) __CPROVER_ensures(1) __CPROVER_assigns() +{ + if(a) + __CPROVER_assert(0, "guarded by a"); + + if(b) + __CPROVER_assert(0, "guarded by b"); + + if(c) + __CPROVER_assert(0, "guarded by c"); +} + +void main() +{ + foo(); +} diff --git a/regression/contracts-dfcc/havoc-static/test-exclude.desc b/regression/contracts-dfcc/havoc-static/test-exclude.desc new file mode 100644 index 00000000000..f4136a5f8b9 --- /dev/null +++ b/regression/contracts-dfcc/havoc-static/test-exclude.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract foo --nondet-static-exclude main.c:a --nondet-static-exclude main.c:c +^\[foo.assertion.\d+\].* guarded by a: SUCCESS$ +^\[foo.assertion.\d+\].* guarded by b: SUCCESS$ +^\[foo.assertion.\d+\].* guarded by c: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks that we can exclude some statics from havocing. diff --git a/regression/contracts-dfcc/havoc-static/test.desc b/regression/contracts-dfcc/havoc-static/test.desc new file mode 100644 index 00000000000..af0f8a3cb16 --- /dev/null +++ b/regression/contracts-dfcc/havoc-static/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^\[foo.assertion.\d+\].* guarded by a: FAILURE$ +^\[foo.assertion.\d+\].* guarded by b: SUCCESS$ +^\[foo.assertion.\d+\].* guarded by c: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +Checks that statics are havoced when using contracts, +unless they are marked const. diff --git a/regression/contracts-dfcc/history-constant/main.c b/regression/contracts-dfcc/history-constant/main.c new file mode 100644 index 00000000000..3806fc67c58 --- /dev/null +++ b/regression/contracts-dfcc/history-constant/main.c @@ -0,0 +1,15 @@ +#include + +int foo(int l) __CPROVER_requires(-10 <= l && l <= 10) __CPROVER_ensures( + __CPROVER_return_value == __CPROVER_old(l) + __CPROVER_old(10)) +{ + return l + 10; +} + +int main() +{ + int l; + __CPROVER_assume(-10 <= l && l <= 10); + foo(l); + return 0; +} diff --git a/regression/contracts-dfcc/history-constant/test.desc b/regression/contracts-dfcc/history-constant/test.desc new file mode 100644 index 00000000000..52c8840acb0 --- /dev/null +++ b/regression/contracts-dfcc/history-constant/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +^Tracking history of constant expressions is not supported yet +-- +This test checks that history variables are supported for constant expressions. diff --git a/regression/contracts-dfcc/history-pointer-both-01/main.c b/regression/contracts-dfcc/history-pointer-both-01/main.c new file mode 100644 index 00000000000..e562ea1af5e --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-both-01/main.c @@ -0,0 +1,20 @@ +#include + +void bar(int *l) __CPROVER_assigns(*l) __CPROVER_requires(l != NULL) + __CPROVER_ensures(__CPROVER_old(*l) == *l) +{ +} + +void foo(int *n) __CPROVER_assigns(*n) __CPROVER_requires(n != NULL) + __CPROVER_ensures(__CPROVER_old(*n) == *n) +{ + bar(n); +} + +int main() +{ + int m; + foo(&m); + + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-both-01/test.desc b/regression/contracts-dfcc/history-pointer-both-01/test.desc new file mode 100644 index 00000000000..218f72ca971 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-both-01/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract foo --replace-call-with-contract bar +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that history variables are supported for parameters of the +the function under test. By using the --enforce-contract flag, +the post-condition (which contains the history variable) is asserted. +In this case, this assertion should pass. diff --git a/regression/contracts-dfcc/history-pointer-enforce-01/main.c b/regression/contracts-dfcc/history-pointer-enforce-01/main.c new file mode 100644 index 00000000000..07cba9ccd7a --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-01/main.c @@ -0,0 +1,13 @@ +void foo(int *x) __CPROVER_assigns(*x) + __CPROVER_ensures(*x == __CPROVER_old(*x) + 5) +{ + *x = *x + 5; +} + +int main() +{ + int n; + foo(&n); + + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-enforce-01/test.desc b/regression/contracts-dfcc/history-pointer-enforce-01/test.desc new file mode 100644 index 00000000000..a8926a7fb1c --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-01/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Verification: +This test checks that history variables are supported for parameters of the +the function under test. By using the --enforce-contract flag, +the post-condition (which contains the history variable) is asserted. +In this case, this assertion should pass. diff --git a/regression/contracts-dfcc/history-pointer-enforce-02/main.c b/regression/contracts-dfcc/history-pointer-enforce-02/main.c new file mode 100644 index 00000000000..36f2ce3c80c --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-02/main.c @@ -0,0 +1,13 @@ +void foo(int *x) __CPROVER_assigns(*x) + __CPROVER_ensures(*x < __CPROVER_old(*x) + 5) +{ + *x = *x + 5; +} + +int main() +{ + int n; + foo(&n); + + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-enforce-02/test.desc b/regression/contracts-dfcc/history-pointer-enforce-02/test.desc new file mode 100644 index 00000000000..7a41c6aafd4 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-02/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +Verification: +This test checks that history variables are supported for parameters of the +the function under test. By using the --enforce-contract flag, +the post-condition (which contains the history variable) is asserted. +In this case, this assertion should fail. diff --git a/regression/contracts-dfcc/history-pointer-enforce-03/main.c b/regression/contracts-dfcc/history-pointer-enforce-03/main.c new file mode 100644 index 00000000000..69eb8f972cc --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-03/main.c @@ -0,0 +1,16 @@ +#include + +void foo(int *x) __CPROVER_assigns(*x) + __CPROVER_requires(*x > 0 && *x < INT_MAX - 5) __CPROVER_ensures( + *x >= __CPROVER_old(*x) + 4 && *x <= __CPROVER_old(*x) + 6) +{ + *x = *x + 5; +} + +int main() +{ + int n; + foo(&n); + + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-enforce-03/test.desc b/regression/contracts-dfcc/history-pointer-enforce-03/test.desc new file mode 100644 index 00000000000..48f6ba1b3e3 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-03/test.desc @@ -0,0 +1,14 @@ +KNOWNBUG +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +ASSERT .*::tmp_if_expr\$\d +-- +-- +Verification: +This test checks that history variables are supported in the case where a +history variable is referred to multiple times within an ensures clause. +By using the --enforce-contract flag, the post-condition (which contains +the history variable) is asserted. In this case, this assertion should pass. diff --git a/regression/contracts-dfcc/history-pointer-enforce-04/main.c b/regression/contracts-dfcc/history-pointer-enforce-04/main.c new file mode 100644 index 00000000000..87da56243ee --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-04/main.c @@ -0,0 +1,15 @@ +void foo(int *x, int *y) __CPROVER_assigns(*x, *y) + __CPROVER_ensures(*x == __CPROVER_old(*y) + 1 && *y == __CPROVER_old(*x) + 2) +{ + int x_initial = *x; + *x = *y + 1; + *y = x_initial + 2; +} + +int main() +{ + int x, y; + foo(&x, &y); + + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-enforce-04/test.desc b/regression/contracts-dfcc/history-pointer-enforce-04/test.desc new file mode 100644 index 00000000000..3cd437f7b15 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-04/test.desc @@ -0,0 +1,14 @@ +KNOWNBUG +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +ASSERT .*::tmp_if_expr +-- +-- +Verification: +This test checks that history variables are supported in the case where the +function under test has multiple parameters. By using the +--enforce-contract flag, the post-condition (which contains the history +variables) is asserted. In this case, this assertion should pass. diff --git a/regression/contracts-dfcc/history-pointer-enforce-05/main.c b/regression/contracts-dfcc/history-pointer-enforce-05/main.c new file mode 100644 index 00000000000..33783befa60 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-05/main.c @@ -0,0 +1,14 @@ +void foo(int *x, int *y) __CPROVER_assigns(*x, *y) + __CPROVER_ensures(*x == __CPROVER_old(*x) + 2 || *y == __CPROVER_old(*y) + 3) +{ + *x = *x + 1; + *y = *y + 2; +} + +int main() +{ + int x, y; + foo(&x, &y); + + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-enforce-05/test.desc b/regression/contracts-dfcc/history-pointer-enforce-05/test.desc new file mode 100644 index 00000000000..e7937067990 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-05/test.desc @@ -0,0 +1,14 @@ +KNOWNBUG +main.c +--dfcc main --enforce-contract foo +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +ASSERT .*::tmp_if_expr +-- +-- +Verification: +This test checks that history variables are supported in the case where the +function under test has multiple parameters. By using the +--enforce-contract flag, the post-condition (which contains the history +variables) is asserted. In this case, this assertion should fail. diff --git a/regression/contracts-dfcc/history-pointer-enforce-06/main.c b/regression/contracts-dfcc/history-pointer-enforce-06/main.c new file mode 100644 index 00000000000..b8c7bef2b03 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-06/main.c @@ -0,0 +1,16 @@ +#include + +void foo(int *x) __CPROVER_assigns(*x) + __CPROVER_ensures(*x == __CPROVER_old(*x) + 5) +{ + assert(__CPROVER_old(*x) == *x); + *x = *x + 5; +} + +int main() +{ + int n; + foo(&n); + + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-enforce-06/test.desc b/regression/contracts-dfcc/history-pointer-enforce-06/test.desc new file mode 100644 index 00000000000..5957c01b86c --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-06/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +warning: ignoring old +-- +-- +Verification: +This test checks that history variables are not supported when referred to from +a function body. In such a case, verification should fail. diff --git a/regression/contracts-dfcc/history-pointer-enforce-07/main.c b/regression/contracts-dfcc/history-pointer-enforce-07/main.c new file mode 100644 index 00000000000..13c04214e73 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-07/main.c @@ -0,0 +1,13 @@ +void foo(int *x) __CPROVER_assigns(*x) + __CPROVER_ensures(*x == __CPROVER_old(*y) + 5) +{ + *x = *x + 5; +} + +int main() +{ + int n; + foo(&n); + + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-enforce-07/test.desc b/regression/contracts-dfcc/history-pointer-enforce-07/test.desc new file mode 100644 index 00000000000..e3e745d5e52 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-07/test.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=(1|64)$ +^SIGNAL=0$ +^CONVERSION ERROR$ +error: failed to find symbol 'y' +-- +-- +Verification: +This test checks that history variables may only be used with existing +symbols. In other words, including a new symbol as part of __CPROVER_old() +is not alowed. In such a case, the program should not parse and there +should be a conversion error. diff --git a/regression/contracts-dfcc/history-pointer-enforce-08/main.c b/regression/contracts-dfcc/history-pointer-enforce-08/main.c new file mode 100644 index 00000000000..49e3e74ce70 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-08/main.c @@ -0,0 +1,24 @@ +#include + +struct pair +{ + int *x; + int *y; +}; + +void foo(struct pair p) __CPROVER_assigns(*(p.y)) + __CPROVER_ensures(*(p.y) == __CPROVER_old(*(p.y)) + 5) +{ + *(p.y) = *(p.y) + 5; +} + +int main() +{ + struct pair p; + p.x = malloc(sizeof(int)); + p.y = malloc(sizeof(int)); + + foo(p); + + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-enforce-08/test.desc b/regression/contracts-dfcc/history-pointer-enforce-08/test.desc new file mode 100644 index 00000000000..bea2bc50716 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-08/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Verification: +This test checks that history variables are supported for dereferences over +pointers to struct members. By using the --enforce-contract flag, the +post-condition (which contains the history variable) is asserted. In this +case, this assertion should pass. diff --git a/regression/contracts-dfcc/history-pointer-enforce-09/main.c b/regression/contracts-dfcc/history-pointer-enforce-09/main.c new file mode 100644 index 00000000000..a7b9db78e32 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-09/main.c @@ -0,0 +1,23 @@ +#include + +struct pair +{ + int x; + int y; +}; + +void foo(struct pair *p) __CPROVER_assigns(p->y) + __CPROVER_ensures(p->y == __CPROVER_old(p->y) + 5) +{ + p->y = p->y + 5; +} + +int main() +{ + struct pair *p = malloc(sizeof(*p)); + p->x = 2; + p->y = 2; + foo(p); + + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-enforce-09/test.desc b/regression/contracts-dfcc/history-pointer-enforce-09/test.desc new file mode 100644 index 00000000000..32a3402230a --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-09/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^\[foo.postcondition.\d+\] line \d+ Check ensures clause of contract contract::foo for function foo: SUCCESS$ +^\[foo.assigns.\d+\] line \d+ Check that p->y is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that history variables are supported for struct members. +By using the --enforce-contract flag, the post-condition (which contains +the history variable) is asserted. In this case, this assertion should pass. diff --git a/regression/contracts-dfcc/history-pointer-enforce-10/main.c b/regression/contracts-dfcc/history-pointer-enforce-10/main.c new file mode 100644 index 00000000000..54c6dcabf2e --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-10/main.c @@ -0,0 +1,50 @@ +#include +#include + +struct pair +{ + int x; + int *y; +}; + +int z = 5; + +void foo(struct pair *p) __CPROVER_assigns(*(p->y), z) + __CPROVER_ensures(*(p->y) == __CPROVER_old(*(p->y)) + __CPROVER_old(z)) +{ + *(p->y) = *(p->y) + z; + z = 10; +} + +void bar(struct pair *p) __CPROVER_assigns(p->y) + __CPROVER_ensures(p->y == __CPROVER_old(p->y) + 5) +{ + p->y = (p->y + 5); +} + +void baz(struct pair p) __CPROVER_assigns() + __CPROVER_ensures(p == __CPROVER_old(p)) +{ + struct pair pp = p; + struct pair empty = {0}; + p = empty; + p = pp; +} + +int main() +{ + z = 5; + int w[10] = {0}; + struct pair *p = malloc(sizeof(*p)); + p->y = malloc(sizeof(*(p->y))); + p->x = 2; + *(p->y) = 2; + foo(p); + assert(*(p->y) == 7); + p->y = w; + w[5] = -1; + bar(p); + assert(*(p->y) == -1); + baz(*p); + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-enforce-10/test-bar.desc b/regression/contracts-dfcc/history-pointer-enforce-10/test-bar.desc new file mode 100644 index 00000000000..983171495b6 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-10/test-bar.desc @@ -0,0 +1,17 @@ +CORE +main.c +--dfcc main --enforce-contract bar +^EXIT=0$ +^SIGNAL=0$ +^\[bar.postcondition.\d+\] line \d+ Check ensures clause of contract contract::bar for function bar: SUCCESS$ +^\[bar.assigns.\d+\] line \d+ Check that p->y is assignable: SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion \*\(p->y\) == 7: SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion \*\(p->y\) == -1: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that history variables are supported for structs, symbols, and +struct members. By using the --enforce-contract flag, the postcondition +(with history variable) is asserted. In this case, this assertion should pass. +Note: A function is always authorized to assign the variables that store +its arguments, there is no need to mention them in the assigns clause. diff --git a/regression/contracts-dfcc/history-pointer-enforce-10/test-baz.desc b/regression/contracts-dfcc/history-pointer-enforce-10/test-baz.desc new file mode 100644 index 00000000000..c580f0654db --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-10/test-baz.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --enforce-contract baz +^EXIT=0$ +^SIGNAL=0$ +^\[baz.postcondition.\d+\] line \d+ Check ensures clause of contract contract::baz for function baz: SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion \*\(p->y\) == 7: SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion \*\(p->y\) == -1: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that history variables are supported for structs, symbols, and +struct members. By using the --enforce-contract flag, the postcondition +(with history variable) is asserted. In this case, this assertion should pass. +Note: A function is always authorized to assign the variables that store +its arguments, there is no need to mention them in the assigns clause. diff --git a/regression/contracts-dfcc/history-pointer-enforce-10/test-foo.desc b/regression/contracts-dfcc/history-pointer-enforce-10/test-foo.desc new file mode 100644 index 00000000000..6ea058791a9 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-10/test-foo.desc @@ -0,0 +1,18 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^\[foo.postcondition.\d+\] line \d+ Check ensures clause of contract contract::foo for function foo: SUCCESS$ +^\[foo.assigns.\d+\] line \d+ Check that \*p->y is assignable: SUCCESS$ +^\[foo.assigns.\d+\] line \d+ Check that z is assignable: SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion \*\(p->y\) == 7: SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion \*\(p->y\) == -1: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that history variables are supported for structs, symbols, and +struct members. By using the --enforce-contract flag, the postcondition +(with history variable) is asserted. In this case, this assertion should pass. +Note: A function is always authorized to assign the variables that store +its arguments, there is no need to mention them in the assigns clause. diff --git a/regression/contracts-dfcc/history-pointer-enforce-11/main.c b/regression/contracts-dfcc/history-pointer-enforce-11/main.c new file mode 100644 index 00000000000..eef1467ed41 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-11/main.c @@ -0,0 +1,23 @@ +#include + +struct pair +{ + int x; + int y; +}; + +void foo(struct pair *p) __CPROVER_assigns(p->y) + __CPROVER_ensures((p != NULL) == > (p->y == __CPROVER_old(p->y) + 5)) + __CPROVER_ensures((p == NULL) == > (p->y == __CPROVER_old(p->y))) +{ + if(p != NULL) + p->y = p->y + 5; +} + +int main() +{ + struct pair *p = NULL; + foo(p); + + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-enforce-11/test.desc b/regression/contracts-dfcc/history-pointer-enforce-11/test.desc new file mode 100644 index 00000000000..82556d81f25 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-enforce-11/test.desc @@ -0,0 +1,13 @@ +KNOWNBUG +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^\[postcondition.\d+\] Check ensures clause: SUCCESS$ +^\[foo.\d+\] line \d+ Check that p->y is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that history variables handle NULL pointers. +History variables currently do not check for nullness while +storing values of objects, which may lead to NULL pointer dereferences. diff --git a/regression/contracts-dfcc/history-pointer-replace-01/main.c b/regression/contracts-dfcc/history-pointer-replace-01/main.c new file mode 100644 index 00000000000..a518e5f35f7 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-replace-01/main.c @@ -0,0 +1,19 @@ +#include +#include + +void foo(int *x) __CPROVER_assigns(*x) + __CPROVER_requires(0 <= *x && *x < INT_MAX) + __CPROVER_ensures(*x == __CPROVER_old(*x) + 1) +{ + *x = *x + 1; +} + +int main() +{ + int x; + __CPROVER_assume(0 <= x && x < INT_MAX); + int old_x = x; + foo(&x); + assert(x == old_x + 1); + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-replace-01/test.desc b/regression/contracts-dfcc/history-pointer-replace-01/test.desc new file mode 100644 index 00000000000..c7553f74137 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-replace-01/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^\[foo.precondition.\d+\].*Check requires clause of contract contract::foo for function foo: SUCCESS$ +^\[main.assertion.\d+\].*assertion x == old_x \+ 1: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Verification: +This test checks that history variables are supported with the use of the +--replace-call-with-contract flag. +We check that the contracts preconditions are satisfied and that post conditions +involving history variables are established after replacement by checking +them at the call site using a manual assertion. diff --git a/regression/contracts-dfcc/history-pointer-replace-02/main.c b/regression/contracts-dfcc/history-pointer-replace-02/main.c new file mode 100644 index 00000000000..f001fa21615 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-replace-02/main.c @@ -0,0 +1,16 @@ +#include + +void foo(int *x) __CPROVER_assigns(*x) __CPROVER_requires(*x == 0) + __CPROVER_ensures(*x >= __CPROVER_old(*x)) +{ + *x = *x + 1; +} + +int main() +{ + int x = 0; + int old_x = x; + foo(&x); + assert(x >= old_x); + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-replace-02/test.desc b/regression/contracts-dfcc/history-pointer-replace-02/test.desc new file mode 100644 index 00000000000..22d90636c95 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-replace-02/test.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^\[foo.precondition.\d+\].*Check requires clause of contract contract::foo for function foo: SUCCESS$ +^\[main.assertion.\d+\].*assertion x >= old_x: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Verification: +This test checks that history variables are supported with the use of the +--replace-call-with-contract flag. + diff --git a/regression/contracts-dfcc/history-pointer-replace-03/main.c b/regression/contracts-dfcc/history-pointer-replace-03/main.c new file mode 100644 index 00000000000..fb2439b567c --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-replace-03/main.c @@ -0,0 +1,17 @@ +#include + +void foo(int *x) __CPROVER_assigns(*x) + __CPROVER_requires(*x == __CPROVER_old(*x)) + __CPROVER_ensures(*x == __CPROVER_old(*x) + 1) +{ + *x = *x + 1; +} + +int main() +{ + int x; + int old_x = x; + foo(&x); + assert(x == old_x + 1); + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-replace-03/test.desc b/regression/contracts-dfcc/history-pointer-replace-03/test.desc new file mode 100644 index 00000000000..aa9379f2d0f --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-replace-03/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^main.c.* error: __CPROVER_old is not allowed in preconditions.$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test checks that history variables cannot be used as part of the +pre-condition (requires) contract. diff --git a/regression/contracts-dfcc/history-pointer-replace-04/main.c b/regression/contracts-dfcc/history-pointer-replace-04/main.c new file mode 100644 index 00000000000..f2d99d1f3c3 --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-replace-04/main.c @@ -0,0 +1,24 @@ +#include +#include +struct pair +{ + int x; + int y; +}; + +void foo(struct pair *p) __CPROVER_requires(p) + __CPROVER_requires(0 <= p->y && p->y < INT_MAX) __CPROVER_assigns(p->y) + __CPROVER_ensures(p->y == __CPROVER_old(p->y) + 1) +{ + p->y = p->y + 1; +} + +int main() +{ + struct pair p; + __CPROVER_assume(0 <= p.y && p.y < INT_MAX); + int old_y = p.y; + foo(&p); + assert(p.y == old_y + 1); + return 0; +} diff --git a/regression/contracts-dfcc/history-pointer-replace-04/test.desc b/regression/contracts-dfcc/history-pointer-replace-04/test.desc new file mode 100644 index 00000000000..6c66cd45efc --- /dev/null +++ b/regression/contracts-dfcc/history-pointer-replace-04/test.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^\[foo.precondition.\d+\] line \d+ Check requires clause of contract contract::foo for function foo: SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion p.y == old_y \+ 1: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +This test checks that history variables are supported for struct members. +We use the --replace-call-with-contract flag and use a manual assertion +assertion in main to show that post conditions are established when replacing +the call by the contract. diff --git a/regression/contracts-dfcc/history-typecast/main.c b/regression/contracts-dfcc/history-typecast/main.c new file mode 100644 index 00000000000..db496514605 --- /dev/null +++ b/regression/contracts-dfcc/history-typecast/main.c @@ -0,0 +1,23 @@ +#include + +long bar(long l, long r) __CPROVER_requires(-10 <= l && l <= 10) + __CPROVER_requires(-10 <= r && r <= 10) __CPROVER_ensures( + __CPROVER_return_value == __CPROVER_old(l) + __CPROVER_old(r)) +{ + return l + r; +} + +int foo(int l, int r) __CPROVER_requires(-10 <= l && l <= 10) + __CPROVER_requires(-10 <= r && r <= 10) __CPROVER_ensures( + __CPROVER_return_value == __CPROVER_old(l) + __CPROVER_old(r)) +{ + return bar((long)l, (long)r); +} + +int main() +{ + int n; + __CPROVER_assume(-10 <= n && n <= 10); + foo(n, n); + return 0; +} diff --git a/regression/contracts-dfcc/history-typecast/test.desc b/regression/contracts-dfcc/history-typecast/test.desc new file mode 100644 index 00000000000..c19905b23ac --- /dev/null +++ b/regression/contracts-dfcc/history-typecast/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--dfcc main --replace-call-with-contract bar --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +^Tracking history of typecast expressions is not supported yet +-- +This test checks that history variables are supported for typecast expressions. diff --git a/regression/contracts-dfcc/ignored_return_value/main.c b/regression/contracts-dfcc/ignored_return_value/main.c new file mode 100644 index 00000000000..4dc6c0631c0 --- /dev/null +++ b/regression/contracts-dfcc/ignored_return_value/main.c @@ -0,0 +1,14 @@ +#include + +int get_at_idx(int const *const arr, const size_t len, const size_t idx) + __CPROVER_requires(__CPROVER_r_ok(arr, len) && idx < len) + __CPROVER_ensures(__CPROVER_return_value == arr[idx]) +{ + return arr[idx]; +} + +void main() +{ + int a[5] = {0}; + get_at_idx(a, 5, 3); +} diff --git a/regression/contracts-dfcc/ignored_return_value/test.desc b/regression/contracts-dfcc/ignored_return_value/test.desc new file mode 100644 index 00000000000..5a717faa4f0 --- /dev/null +++ b/regression/contracts-dfcc/ignored_return_value/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --replace-call-with-contract get_at_idx +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +This test exposes a bug where CBMC would crash on a program where a function +with a return value has a postcondition that mentions __CPROVER_return_value, +but the caller does not assign the return value to anything. diff --git a/regression/contracts-dfcc/is_fresh_indirect_calls/main.c b/regression/contracts-dfcc/is_fresh_indirect_calls/main.c new file mode 100644 index 00000000000..b528514086e --- /dev/null +++ b/regression/contracts-dfcc/is_fresh_indirect_calls/main.c @@ -0,0 +1,43 @@ +#include +#include + +char nondet_char(); + +char *foo(char *a, char *b, size_t s) + // clang-format off +__CPROVER_requires(s > 0) +__CPROVER_requires(__CPROVER_is_fresh(a, s)) +__CPROVER_requires(__CPROVER_is_fresh(b, s)) +__CPROVER_assigns(a[0]) +__CPROVER_ensures(__CPROVER_is_fresh(__CPROVER_return_value, s)) +// clang-format on +{ + a[0] = nondet_char(); + return malloc(s); +} + +char *bar(char *a, char *b, size_t s) +{ + return foo(a, b, s); +} + +int main() +{ + size_t s; + __CPROVER_assume(0 < s && s < __CPROVER_max_malloc_size); + char *a = malloc(s); + char *b = malloc(s); + + char *c = bar(a, b, s); + __CPROVER_assert(__CPROVER_rw_ok(c, s), "c is rw_ok"); + __CPROVER_assert(c != a, "c and a are distinct"); + __CPROVER_assert(c != b, "c and b are distinct"); + + char *d = bar(a, b, s); + __CPROVER_assert(__CPROVER_rw_ok(d, s), "d is rw_ok"); + __CPROVER_assert(d != a, "d and a are distinct"); + __CPROVER_assert(d != b, "d and b are distinct"); + __CPROVER_assert(d != c, "d and c distinct"); + + return 0; +} diff --git a/regression/contracts-dfcc/is_fresh_indirect_calls/test.desc b/regression/contracts-dfcc/is_fresh_indirect_calls/test.desc new file mode 100644 index 00000000000..9934d9515a4 --- /dev/null +++ b/regression/contracts-dfcc/is_fresh_indirect_calls/test.desc @@ -0,0 +1,23 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^\[main.assertion.\d+\].*c is rw_ok: SUCCESS$ +^\[main.assertion.\d+\].*c and a are distinct: SUCCESS$ +^\[main.assertion.\d+\].*c and b are distinct: SUCCESS$ +^\[main.assertion.\d+\].*d is rw_ok: SUCCESS$ +^\[main.assertion.\d+\].*d and a are distinct: SUCCESS$ +^\[main.assertion.\d+\].*d and b are distinct: SUCCESS$ +^\[main.assertion.\d+\].*d and c distinct: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that the interpretation of is_fresh predicates is +local to a call. `bar` is called twice with the same arguments. +`bar` calls `foo` so `foo` also gets called twice with the same arguments. +The is_fresh preconditions of `foo` are checked and satisfied independently +for each call. +This shows that the memory_map which keeps track of objects seen by the +is_fresh predicates and the input and output of a function call is local to +that function call. diff --git a/regression/contracts-dfcc/is_unique_01_replace/main.c b/regression/contracts-dfcc/is_unique_01_replace/main.c new file mode 100644 index 00000000000..b81f917e90c --- /dev/null +++ b/regression/contracts-dfcc/is_unique_01_replace/main.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include + +bool dummy_for_definitions(int *n) +{ + assert(__CPROVER_is_fresh(&n, sizeof(int))); + int *x = malloc(sizeof(int)); +} + +bool ptr_ok(int *x) +{ + return *x < 5; +} + +/* + Here are the meanings of the predicates: + +static _Bool __foo_memory_map[__CPROVER_constant_infinity_uint]; + +bool __foo_requires_is_fresh(void **elem, size_t size) { + *elem = malloc(size); + if (!*elem || (__foo_memory_map[__CPROVER_POINTER_OBJECT(*elem)] != 0)) return false; + + __foo_memory_map[__CPROVER_POINTER_OBJECT(*elem)] = 1; + return true; +} + +bool __foo_ensures_is_fresh(void *elem, size_t size) { + bool ok = (__foo_memory_map[__CPROVER_POINTER_OBJECT(elem)] == 0 && + __CPROVER_r_ok(elem, size)); + __foo_memory_map[__CPROVER_POINTER_OBJECT(elem)] = 1; + return ok; +} + + +_Bool __call_foo_requires_is_fresh(void *elem, size_t size) { + _Bool r_ok = __CPROVER_r_ok(elem, size); + if (!__CPROVER_r_ok(elem, size) || + __foo_memory_map[__CPROVER_POINTER_OBJECT(elem)]) return 0; + __foo_memory_map[__CPROVER_POINTER_OBJECT(elem)] = 1; + return 1; +} + +// In the calling context, we assume freshness means new +// allocation from within the function. +bool __call_foo_ensures_is_fresh(void **elem, size_t size) { + *elem = malloc(size); + return (*elem != NULL); +} +*/ + +bool return_ok(int ret_value, int *x) +{ + int a; + a = *x; + return ret_value == *x + 5; +} + +// The 'ensures' __CPROVER_is_fresh test is unnecessary, but left in just to test the function is working correctly. +// If you remove the negation, the program will fail, because 'x' is not fresh. + +int foo(int *x, int y) __CPROVER_assigns(*x) + __CPROVER_requires(__CPROVER_is_fresh(x, sizeof(int)) && *x > 0 && ptr_ok(x)) + __CPROVER_ensures( + !ptr_ok(x) && !__CPROVER_is_fresh(x, sizeof(int)) && + return_ok(__CPROVER_return_value, x)) +{ + *x = *x + 4; + int y = *x + 5; + return *x + 5; +} + +int main() +{ + int *n = malloc(sizeof(int)); + assert(__CPROVER_r_ok(n, sizeof(int))); + *n = 3; + int o = foo(n, 10); + assert(o >= 10 && o == *n + 5); + return 0; +} diff --git a/regression/contracts-dfcc/is_unique_01_replace/test.desc b/regression/contracts-dfcc/is_unique_01_replace/test.desc new file mode 100644 index 00000000000..eb56439d295 --- /dev/null +++ b/regression/contracts-dfcc/is_unique_01_replace/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that verification succeeds if only expressions inside the assigns clause are assigned within the function. + +Note: For all 'enforce' tests, nothing can be assumed about the return value of the function (as the function call is not replaced at this point). + +To make such assumptions would cause verification to fail. diff --git a/regression/contracts-dfcc/loop-freeness-check/main.c b/regression/contracts-dfcc/loop-freeness-check/main.c new file mode 100644 index 00000000000..73398202715 --- /dev/null +++ b/regression/contracts-dfcc/loop-freeness-check/main.c @@ -0,0 +1,16 @@ +int foo(int *arr) + // clang-format off + __CPROVER_assigns(__CPROVER_object_whole(arr)) +// clang-format off +{ + for(int i = 0; i < 10; i++) + arr[i] = i; + + return 0; +} + +int main() +{ + int arr[10]; + foo(arr); +} diff --git a/regression/contracts-dfcc/loop-freeness-check/test.desc b/regression/contracts-dfcc/loop-freeness-check/test.desc new file mode 100644 index 00000000000..fcfbdc7a242 --- /dev/null +++ b/regression/contracts-dfcc/loop-freeness-check/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^\[foo.assigns.\d+\].*Check that i is assignable: SUCCESS$ +^\[foo.assigns.\d+\].*Check that arr\[\(.*\)i\] is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +This test checks that program with loops can be instrumented and that +loops can be automatically unwound by cbmc in a second step, with the +instrumentation scaling automatically. diff --git a/regression/contracts-dfcc/named-contracts/main-contract-after-declaration.c b/regression/contracts-dfcc/named-contracts/main-contract-after-declaration.c new file mode 100644 index 00000000000..af7174a40e6 --- /dev/null +++ b/regression/contracts-dfcc/named-contracts/main-contract-after-declaration.c @@ -0,0 +1,28 @@ +int foo(int *arr, int size); + +int foo(int *arr, int size) + // clang-format off +__CPROVER_requires(size > 0 && __CPROVER_is_fresh(arr, size)) +__CPROVER_assigns( + arr[0], arr[size-1]; + size >= 10: arr[5]; +) +__CPROVER_ensures(arr[0] == 0 && arr[size-1] == 0) +__CPROVER_ensures(size >= 10 ==> arr[5] == __CPROVER_return_value) + // clang-format on + ; + +int foo(int *arr, int size) +{ + arr[0] = 0; + arr[size - 1] = 0; + return size < 10 ? 0 : arr[5]; +} + +int main() +{ + int arr[10]; + int retval = foo(arr, 10); + __CPROVER_assert(retval == arr[5], "should succeed"); + return 0; +} diff --git a/regression/contracts-dfcc/named-contracts/main-contract-after-definition.c b/regression/contracts-dfcc/named-contracts/main-contract-after-definition.c new file mode 100644 index 00000000000..639f4a95678 --- /dev/null +++ b/regression/contracts-dfcc/named-contracts/main-contract-after-definition.c @@ -0,0 +1,26 @@ +int foo(int *arr, int size) +{ + arr[0] = 0; + arr[size - 1] = 0; + return size < 10 ? 0 : arr[5]; +} + +int foo(int *arr, int size) + // clang-format off +__CPROVER_requires(size > 0 && __CPROVER_is_fresh(arr, size)) +__CPROVER_assigns( + arr[0], arr[size-1]; + size >= 10: arr[5]; +) +__CPROVER_ensures(arr[0] == 0 && arr[size-1] == 0) +__CPROVER_ensures(size >= 10 ==> arr[5] == __CPROVER_return_value) + // clang-format on + ; + +int main() +{ + int arr[10]; + int retval = foo(arr, 10); + __CPROVER_assert(retval == arr[5], "should succeed"); + return 0; +} diff --git a/regression/contracts-dfcc/named-contracts/main-contract-incomplete.c b/regression/contracts-dfcc/named-contracts/main-contract-incomplete.c new file mode 100644 index 00000000000..e91bdbf340e --- /dev/null +++ b/regression/contracts-dfcc/named-contracts/main-contract-incomplete.c @@ -0,0 +1,22 @@ +int foo(int *arr, int size); + +int foo() + // clang-format off +__CPROVER_ensures(__CPROVER_return_value != 0) + // clang-format on + ; + +int foo(int *arr, int size) +{ + arr[0] = 0; + arr[size - 1] = 0; + return size < 10 ? 0 : arr[5]; +} + +int main() +{ + int arr[10]; + int retval = foo(arr, 10); + __CPROVER_assert(retval == arr[5], "should succeed"); + return 0; +} diff --git a/regression/contracts-dfcc/named-contracts/main-contract-signature-conflict.c b/regression/contracts-dfcc/named-contracts/main-contract-signature-conflict.c new file mode 100644 index 00000000000..b0d1fe6838a --- /dev/null +++ b/regression/contracts-dfcc/named-contracts/main-contract-signature-conflict.c @@ -0,0 +1,30 @@ +int foo(int *arr, int size); + +#if 0 +int foo() + // clang-format off +__CPROVER_ensures(__CPROVER_return_value != 0) + // clang-format on + ; +#endif + +void foo(int *arr, int size) + // clang-format off +__CPROVER_requires(size > 0) + // clang-format on + ; + +int foo(int *arr, int size) +{ + arr[0] = 0; + arr[size - 1] = 0; + return size < 10 ? 0 : arr[5]; +} + +int main() +{ + int arr[10]; + int retval = foo(arr, 10); + __CPROVER_assert(retval == arr[5], "should succeed"); + return 0; +} diff --git a/regression/contracts-dfcc/named-contracts/main-definition-after-contract.c b/regression/contracts-dfcc/named-contracts/main-definition-after-contract.c new file mode 100644 index 00000000000..b8c458cb05e --- /dev/null +++ b/regression/contracts-dfcc/named-contracts/main-definition-after-contract.c @@ -0,0 +1,26 @@ +int foo(int *arr, int size) + // clang-format off +__CPROVER_requires(size > 0 && __CPROVER_is_fresh(arr, size)) +__CPROVER_assigns( + arr[0], arr[size-1]; + size >= 10: arr[5]; +) +__CPROVER_ensures(arr[0] == 0 && arr[size-1] == 0) +__CPROVER_ensures(size >= 10 ==> arr[5] == __CPROVER_return_value) + // clang-format on + ; + +int foo(int *arr, int size) +{ + arr[0] = 0; + arr[size - 1] = 0; + return size < 10 ? 0 : arr[5]; +} + +int main() +{ + int arr[10]; + int retval = foo(arr, 10); + __CPROVER_assert(retval == arr[5], "should succeed"); + return 0; +} diff --git a/regression/contracts-dfcc/named-contracts/main-no-definition.c b/regression/contracts-dfcc/named-contracts/main-no-definition.c new file mode 100644 index 00000000000..c28e2f188c5 --- /dev/null +++ b/regression/contracts-dfcc/named-contracts/main-no-definition.c @@ -0,0 +1,28 @@ +int foo(int *arr, int size) + // clang-format off +__CPROVER_requires(size > 0 && __CPROVER_is_fresh(arr, size)) +__CPROVER_assigns( + arr[0], arr[size-1]; + size >= 10: arr[5]; +) +__CPROVER_ensures(arr[0] == 0 && arr[size-1] == 0) +__CPROVER_ensures(size >= 10 ==> arr[5] == __CPROVER_return_value) + // clang-format on + ; + +int main() +{ + int arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}; + int retval = foo(arr, 10); + assert(arr[0] == 0); + assert(arr[1] == 9); + assert(arr[2] == 8); + assert(arr[3] == 7); + assert(arr[4] == 6); + assert(arr[5] == retval); + assert(arr[6] == 4); + assert(arr[7] == 3); + assert(arr[8] == 2); + assert(arr[9] == 0); + return 0; +} diff --git a/regression/contracts-dfcc/named-contracts/test-contract-after-declaration.desc b/regression/contracts-dfcc/named-contracts/test-contract-after-declaration.desc new file mode 100644 index 00000000000..8b93e6e38ac --- /dev/null +++ b/regression/contracts-dfcc/named-contracts/test-contract-after-declaration.desc @@ -0,0 +1,10 @@ +CORE +main-contract-after-declaration.c +--dfcc main --replace-call-with-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that we can have a function declaration with a contract after +having seen an earlier declaration of that same function. diff --git a/regression/contracts-dfcc/named-contracts/test-contract-after-definition.desc b/regression/contracts-dfcc/named-contracts/test-contract-after-definition.desc new file mode 100644 index 00000000000..5b3b4e56646 --- /dev/null +++ b/regression/contracts-dfcc/named-contracts/test-contract-after-definition.desc @@ -0,0 +1,10 @@ +CORE +main-contract-after-definition.c +--dfcc main --replace-call-with-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that we can have a function declaration with a contract after +having seen that function's definition. diff --git a/regression/contracts-dfcc/named-contracts/test-contract-incomplete.desc b/regression/contracts-dfcc/named-contracts/test-contract-incomplete.desc new file mode 100644 index 00000000000..ca5cad58702 --- /dev/null +++ b/regression/contracts-dfcc/named-contracts/test-contract-incomplete.desc @@ -0,0 +1,10 @@ +CORE +main-contract-incomplete.c +--dfcc main +error: code contract on incomplete function re-declaration +CONVERSION ERROR +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test checks that contracts on incomplete re-declarations are rejected. diff --git a/regression/contracts-dfcc/named-contracts/test-contract-signature-conflict.desc b/regression/contracts-dfcc/named-contracts/test-contract-signature-conflict.desc new file mode 100644 index 00000000000..1cbea95e2aa --- /dev/null +++ b/regression/contracts-dfcc/named-contracts/test-contract-signature-conflict.desc @@ -0,0 +1,10 @@ +CORE +main-contract-signature-conflict.c +--dfcc main --enforce-contract foo +^.*function 'foo' and the corresponding pure contract symbol 'contract::foo' have incompatible type signatures.*$ +^EXIT=6$ +^SIGNAL=0$ +-- +-- +This test checks that contracts on function declarations with a matching name +but different type are rejected. diff --git a/regression/contracts-dfcc/named-contracts/test-definition-after-contract.desc b/regression/contracts-dfcc/named-contracts/test-definition-after-contract.desc new file mode 100644 index 00000000000..b7e1b273e4d --- /dev/null +++ b/regression/contracts-dfcc/named-contracts/test-definition-after-contract.desc @@ -0,0 +1,11 @@ +CORE +main-definition-after-contract.c +--dfcc main --replace-call-with-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that we can have a function declaration with a contract and +without body, then the function definition, and successfully replace a call to +the function by the contract. diff --git a/regression/contracts-dfcc/named-contracts/test-no-definition.desc b/regression/contracts-dfcc/named-contracts/test-no-definition.desc new file mode 100644 index 00000000000..9dedf0d3f00 --- /dev/null +++ b/regression/contracts-dfcc/named-contracts/test-no-definition.desc @@ -0,0 +1,10 @@ +CORE +main-no-definition.c +--dfcc main --replace-call-with-contract foo +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that we can have a function declaration with a contract and +without body and replace a call to the function by the contract. diff --git a/regression/contracts-dfcc/no_redudant_checks/main.c b/regression/contracts-dfcc/no_redudant_checks/main.c new file mode 100644 index 00000000000..63377b4f588 --- /dev/null +++ b/regression/contracts-dfcc/no_redudant_checks/main.c @@ -0,0 +1,9 @@ +#include +const int N = 100; +int main() +{ + int *buf = malloc(N * sizeof(*buf)); + char *lb = (((char *)buf) - __CPROVER_POINTER_OFFSET(buf)); + char *ub = (((char *)buf) - __CPROVER_POINTER_OFFSET(buf)) + + __CPROVER_OBJECT_SIZE(buf) - 1; +} diff --git a/regression/contracts-dfcc/no_redudant_checks/test.desc b/regression/contracts-dfcc/no_redudant_checks/test.desc new file mode 100644 index 00000000000..e47b0738092 --- /dev/null +++ b/regression/contracts-dfcc/no_redudant_checks/test.desc @@ -0,0 +1,34 @@ +CORE +main.c +--dfcc main --pointer-overflow-check _ --pointer-overflow-check --unsigned-overflow-check +^EXIT=0$ +^SIGNAL=0$ +^\[main.overflow.1\].*: SUCCESS +^\[main.pointer_arithmetic.1\].*: SUCCESS +^\[main.pointer_arithmetic.2\].*: SUCCESS +^\[main.pointer_arithmetic.3\].*: SUCCESS +^\[main.pointer_arithmetic.4\].*: SUCCESS +^\[main.pointer_arithmetic.5\].*: SUCCESS +^\[main.pointer_arithmetic.6\].*: SUCCESS +^\[main.pointer_arithmetic.7\].*: SUCCESS +^\[main.pointer_arithmetic.8\].*: SUCCESS +^\[main.pointer_arithmetic.9\].*: SUCCESS +^\[main.pointer_arithmetic.10\].*: SUCCESS +^\[main.pointer_arithmetic.11\].*: SUCCESS +^\[main.pointer_arithmetic.12\].*: SUCCESS +^\[main.pointer_arithmetic.13\].*: SUCCESS +^\[main.pointer_arithmetic.14\].*: SUCCESS +^\[main.pointer_arithmetic.15\].*: SUCCESS +^\[main.pointer_arithmetic.16\].*: SUCCESS +^\[main.pointer_arithmetic.17\].*: SUCCESS +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks that assertions generated by the first --pointer-overflow-check: +- do not get duplicated by the second --unsigned-overflow-check +- do not get instrumented with --unsigned-overflow-check (would fail the proof) + +We expect 17 assertions caused by --pointer-overflow-check +In the final cbmc run: +- 0 caused by --pointer-overflow-check +- 1 caused by the --unsigned-overflow-check diff --git a/regression/contracts-dfcc/quantifiers-exists-both-enforce/main.c b/regression/contracts-dfcc/quantifiers-exists-both-enforce/main.c new file mode 100644 index 00000000000..d245284d067 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-exists-both-enforce/main.c @@ -0,0 +1,20 @@ +// clang-format off +int f1(int *arr) + __CPROVER_requires(__CPROVER_exists { + int i; + (0 <= i && i < 8) && arr[i] == 0 + }) + __CPROVER_ensures(__CPROVER_exists { + int i; + (0 <= i && i < 8) && arr[i] == 0 + }) +// clang-format on +{ + return 0; +} + +int main() +{ + int arr[8]; + f1(arr); +} diff --git a/regression/contracts-dfcc/quantifiers-exists-both-enforce/test.desc b/regression/contracts-dfcc/quantifiers-exists-both-enforce/test.desc new file mode 100644 index 00000000000..fef137d2254 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-exists-both-enforce/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^\[f1.postcondition.\d+\] line \d+ Check ensures clause of contract contract::f1 for function f1: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +The purpose of this test is to ensure that we can safely use __CPROVER_exists within both +positive and negative contexts (ENSURES and REQUIRES clauses). + +With the SAT backend existential quantifiers in a positive context, +e.g., the ENSURES clause being enforced in this case, +are supported only if the quantifier is bound to a constant range. diff --git a/regression/contracts-dfcc/quantifiers-exists-both-replace/main.c b/regression/contracts-dfcc/quantifiers-exists-both-replace/main.c new file mode 100644 index 00000000000..85360a7f075 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-exists-both-replace/main.c @@ -0,0 +1,42 @@ +#include + +#define MAX_LEN 8 + +// clang-format off +int f1(int *arr, int len) + __CPROVER_requires( + len > 0 ==> __CPROVER_exists { + int i; + // constant bounds for explicit unrolling with SAT backend + (0 <= i && i < MAX_LEN) && ( + // actual symbolic bound for `i` + i < len && arr[i] == 0 + ) + } + ) + __CPROVER_ensures( + len > 0 ==> __CPROVER_exists { + int i; + // constant bounds for explicit unrolling with SAT backend + (0 <= i && i < MAX_LEN) && ( + // actual symbolic bound for `i` + i < len && arr[i] == 0 + ) + } + ) +// clang-format on +{ + return 0; +} + +int main() +{ + int len; + __CPROVER_assume(0 <= len && len <= MAX_LEN); + + int *arr = malloc(len); + if(len > 0) + arr[0] = 0; + + f1(arr, len); +} diff --git a/regression/contracts-dfcc/quantifiers-exists-both-replace/test.desc b/regression/contracts-dfcc/quantifiers-exists-both-replace/test.desc new file mode 100644 index 00000000000..29de9e6d662 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-exists-both-replace/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --replace-call-with-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^\[f1.precondition.\d+\] line \d+ Check requires clause of contract contract::f1 for function f1: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +The purpose of this test is to ensure that we can safely use __CPROVER_exists within both +positive and negative contexts (ENSURES and REQUIRES clauses). + +With the SAT backend existential quantifiers in a positive context, +e.g., the REQUIRES clause being replaced in this case, +are supported only if the quantifier is bound to a constant range. diff --git a/regression/contracts-dfcc/quantifiers-exists-ensures-enforce/main.c b/regression/contracts-dfcc/quantifiers-exists-ensures-enforce/main.c new file mode 100644 index 00000000000..bfdc2e43d77 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-exists-ensures-enforce/main.c @@ -0,0 +1,52 @@ +// clang-format off +int f1(int *arr) + __CPROVER_assigns(*arr) + __CPROVER_ensures(__CPROVER_exists { + int i; + (0 <= i && i < 10) && arr[i] == i + }) +// clang-format on +{ + arr[0] = 0; + arr[1] = 1; + arr[2] = 2; + arr[3] = 3; + arr[4] = 4; + arr[5] = 5; + arr[6] = 6; + arr[7] = 7; + arr[8] = 8; + arr[9] = 9; + + return 0; +} + +// clang-format off +int f2(int *arr) + __CPROVER_assigns(*arr) + __CPROVER_ensures(__CPROVER_exists { + int i; + (0 <= i && i < 10) && arr[i] != 0 + }) +// clang-format on +{ + arr[0] = 0; + arr[1] = 1; + arr[2] = 2; + arr[3] = 3; + arr[4] = 4; + arr[5] = 5; + arr[6] = 6; + arr[7] = 7; + arr[8] = 8; + arr[9] = 9; + + return 0; +} + +int main() +{ + int arr[10]; + f2(arr); + f1(arr); +} diff --git a/regression/contracts-dfcc/quantifiers-exists-ensures-enforce/test-f1.desc b/regression/contracts-dfcc/quantifiers-exists-ensures-enforce/test-f1.desc new file mode 100644 index 00000000000..be900d3955a --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-exists-ensures-enforce/test-f1.desc @@ -0,0 +1,15 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +^warning: ignoring +-- +The purpose of this test is to ensure that we can safely use __CPROVER_exists +within positive contexts (enforced ENSURES clauses). + +With the SAT backend existential quantifiers in a positive context, +e.g., the ENSURES clause being enforced in this case, +are supported only if the quantifier is bound to a constant range. diff --git a/regression/contracts-dfcc/quantifiers-exists-ensures-enforce/test-f2.desc b/regression/contracts-dfcc/quantifiers-exists-ensures-enforce/test-f2.desc new file mode 100644 index 00000000000..91473c04d76 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-exists-ensures-enforce/test-f2.desc @@ -0,0 +1,15 @@ +CORE +main.c +--dfcc main --enforce-contract f2 +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +^warning: ignoring +-- +The purpose of this test is to ensure that we can safely use __CPROVER_exists +within positive contexts (enforced ENSURES clauses). + +With the SAT backend existential quantifiers in a positive context, +e.g., the ENSURES clause being enforced in this case, +are supported only if the quantifier is bound to a constant range. diff --git a/regression/contracts-dfcc/quantifiers-exists-ensures-replace/main.c b/regression/contracts-dfcc/quantifiers-exists-ensures-replace/main.c new file mode 100644 index 00000000000..caacb3e5ccb --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-exists-ensures-replace/main.c @@ -0,0 +1,41 @@ +#include +#include +#include + +#define MAX_LEN 128 + +// clang-format off +int f1(int *arr, int len) + __CPROVER_ensures( + len > 0 ==> __CPROVER_exists { + int i; + // test replacement with symbolic bound + (0 <= i && i < len) && arr[i] == 0 + } + ) +// clang-format on +{ + // we are only checking for contract replacement + return 0; +} + +int main() +{ + int len; + __CPROVER_assume(0 <= len && len <= MAX_LEN); + + int *arr = malloc(len * sizeof(int)); + + f1(arr, len); + + bool found_zero = false; + for(int i = 0; i <= MAX_LEN; i++) + { + if(i < len) + found_zero |= (arr[i] == 0); + } + + // clang-format off + assert(len > 0 ==> found_zero); + // clang-format on +} diff --git a/regression/contracts-dfcc/quantifiers-exists-ensures-replace/test.desc b/regression/contracts-dfcc/quantifiers-exists-ensures-replace/test.desc new file mode 100644 index 00000000000..199bfedfa0f --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-exists-ensures-replace/test.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --replace-call-with-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^\[main.assertion.1\] line .* assertion len > 0 ==> found_zero: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +The purpose of this test is to ensure that we can safely use __CPROVER_exists +within negative contexts (replaced ENSURES clauses). + +This is fully supported (without requiring full unrolling) with the SAT backend. diff --git a/regression/contracts-dfcc/quantifiers-exists-requires-enforce/main.c b/regression/contracts-dfcc/quantifiers-exists-requires-enforce/main.c new file mode 100644 index 00000000000..c74bbfbb009 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-exists-requires-enforce/main.c @@ -0,0 +1,55 @@ +#include +#include + +#define MAX_LEN 10 + +// clang-format off +bool f1(int *arr, int len) + __CPROVER_requires( + len > 0 ==> __CPROVER_exists { + int i; + // test enforcement with symbolic bound + (0 <= i && i < len) && arr[i] == 4 + } + ) + __CPROVER_ensures( + __CPROVER_return_value == true + ) +// clang-format on +{ + bool found_four = false; + if(0 < len) + found_four |= (arr[0] == 4); + if(1 < len) + found_four |= (arr[1] == 4); + if(2 < len) + found_four |= (arr[2] == 4); + if(3 < len) + found_four |= (arr[3] == 4); + if(4 < len) + found_four |= (arr[4] == 4); + if(5 < len) + found_four |= (arr[5] == 4); + if(6 < len) + found_four |= (arr[6] == 4); + if(7 < len) + found_four |= (arr[7] == 4); + if(8 < len) + found_four |= (arr[8] == 4); + + if(9 < len) + found_four |= (arr[9] == 4); + + // clang-format off + return (len > 0 ==> found_four); + // clang-format on +} + +int main() +{ + int len; + __CPROVER_assume(0 <= len && len <= MAX_LEN); + + int *arr = malloc(len * sizeof(int)); + f1(arr, len); +} diff --git a/regression/contracts-dfcc/quantifiers-exists-requires-enforce/test.desc b/regression/contracts-dfcc/quantifiers-exists-requires-enforce/test.desc new file mode 100644 index 00000000000..a73c545917c --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-exists-requires-enforce/test.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^\[f1.postcondition.\d+\] line \d+ Check ensures clause of contract contract::f1 for function f1: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +The purpose of this test is to ensure that we can safely use __CPROVER_exists +within both negative contexts (enforced REQUIRES clauses). + +This is fully supported (without requiring full unrolling) with the SAT backend. diff --git a/regression/contracts-dfcc/quantifiers-exists-requires-replace/main.c b/regression/contracts-dfcc/quantifiers-exists-requires-replace/main.c new file mode 100644 index 00000000000..f7c1e5f896a --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-exists-requires-replace/main.c @@ -0,0 +1,34 @@ +// clang-format off +int f1(int *arr) + __CPROVER_requires(__CPROVER_exists { + int i; + (0 <= i && i < 10) && arr[i] == 0 + }) + __CPROVER_ensures( + __CPROVER_return_value == 0 + ) +// clang-format on +{ + return 0; +} + +// clang-format off +int f2(int *arr) + __CPROVER_requires(__CPROVER_exists { + int i; + (0 <= i && i < 10) && arr[i] == 1 + }) + __CPROVER_ensures( + __CPROVER_return_value == 0 + ) +// clang-format on +{ + return 0; +} + +int main() +{ + int arr[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + f1(arr); + f2(arr); +} diff --git a/regression/contracts-dfcc/quantifiers-exists-requires-replace/test.desc b/regression/contracts-dfcc/quantifiers-exists-requires-replace/test.desc new file mode 100644 index 00000000000..604de50f38f --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-exists-requires-replace/test.desc @@ -0,0 +1,17 @@ +CORE +main.c +--dfcc main --replace-call-with-contract f1 --replace-call-with-contract f2 +^EXIT=10$ +^SIGNAL=0$ +^\[f1.precondition.\d+\] line \d+ Check requires clause of contract contract::f1 for function f1: SUCCESS$ +^\[f2.precondition.\d+\] line \d+ Check requires clause of contract contract::f2 for function f2: FAILURE$ +^VERIFICATION FAILED$ +-- +^warning: ignoring +-- +The purpose of this test is to ensure that we can safely use __CPROVER_exists +within both positive contexts (replaced REQUIRES clauses). + +With the SAT backend existential quantifiers in a positive context, +e.g., the REQUIRES clause being replaced in this case, +are supported only if the quantifier is bound to a constant range. diff --git a/regression/contracts-dfcc/quantifiers-forall-both-enforce/main.c b/regression/contracts-dfcc/quantifiers-forall-both-enforce/main.c new file mode 100644 index 00000000000..d47d59bcc3e --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-forall-both-enforce/main.c @@ -0,0 +1,20 @@ +// clang-format off +int f1(int *arr) + __CPROVER_requires(__CPROVER_forall { + int i; + (0 <= i && i < 8) ==> arr[i] == 0 + }) + __CPROVER_ensures(__CPROVER_forall { + int i; + (0 <= i && i < 8) ==> arr[i] == 0 + }) +// clang-format on +{ + return 0; +} + +int main() +{ + int arr[8]; + f1(arr); +} diff --git a/regression/contracts-dfcc/quantifiers-forall-both-enforce/test.desc b/regression/contracts-dfcc/quantifiers-forall-both-enforce/test.desc new file mode 100644 index 00000000000..91845ddc5f7 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-forall-both-enforce/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^\[f1.postcondition.\d+\] line \d+ Check ensures clause of contract contract::f1 for function f1: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +The purpose of this test is to ensure that we can safely use __CPROVER_forall within both +positive and negative contexts (ENSURES and REQUIRES clauses). + +With the SAT backend universal quantifiers in a negative context, +e.g., the REQUIRES clause being enforced in this case, +are supported only if the quantifier is bound to a constant range. diff --git a/regression/contracts-dfcc/quantifiers-forall-both-replace/main.c b/regression/contracts-dfcc/quantifiers-forall-both-replace/main.c new file mode 100644 index 00000000000..0bfda4af0e1 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-forall-both-replace/main.c @@ -0,0 +1,38 @@ +#include + +#define MAX_LEN 8 + +// clang-format off +int f1(int *arr, int len) + __CPROVER_requires(__CPROVER_forall { + int i; + // constant bounds for explicit unrolling with SAT backend + (0 <= i && i < MAX_LEN) ==> ( + // actual symbolic bound for `i` + i < len ==> arr[i] == 0 + ) + }) + __CPROVER_ensures(__CPROVER_forall { + int i; + // positive context, so symbolic bounds are fine + (0 <= i && i < len) ==> arr[i] == 0 + }) +// clang-format on +{ + return 0; +} + +int main() +{ + int len; + __CPROVER_assume(0 <= len && len <= MAX_LEN); + + int *arr = malloc(len); + for(int i = 0; i < MAX_LEN; ++i) + { + if(i < len) + arr[i] = 0; + } + + f1(arr, len); +} diff --git a/regression/contracts-dfcc/quantifiers-forall-both-replace/test.desc b/regression/contracts-dfcc/quantifiers-forall-both-replace/test.desc new file mode 100644 index 00000000000..fecf4e1253f --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-forall-both-replace/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --replace-call-with-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^\[f1.precondition.\d+\] line \d+ Check requires clause of contract contract::f1 for function f1: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +The purpose of this test is to ensure that we can safely use __CPROVER_forall within both +positive and negative contexts (ENSURES and REQUIRES clauses). + +With the SAT backend universal quantifiers in a negative context, +e.g., the ENSURES clause being replaced in this case, +are supported only if the quantifier is bound to a constant range. diff --git a/regression/contracts-dfcc/quantifiers-forall-ensures-enforce/main.c b/regression/contracts-dfcc/quantifiers-forall-ensures-enforce/main.c new file mode 100644 index 00000000000..3dd6afe4295 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-forall-ensures-enforce/main.c @@ -0,0 +1,46 @@ +#include + +#define MAX_LEN 10 + +// clang-format off +int f1(int *arr, int len) + __CPROVER_assigns(__CPROVER_object_whole(arr)) + __CPROVER_ensures(__CPROVER_forall { + int i; + // test enforcement with symbolic bound + (0 <= i && i < len) ==> arr[i] == 0 + }) +// clang-format on +{ + if(0 < len) + arr[0] = 0; + if(1 < len) + arr[1] = 0; + if(2 < len) + arr[2] = 0; + if(3 < len) + arr[3] = 0; + if(4 < len) + arr[4] = 0; + if(5 < len) + arr[5] = 0; + if(6 < len) + arr[6] = 0; + if(7 < len) + arr[7] = 0; + if(8 < len) + arr[8] = 0; + if(9 < len) + arr[9] = 0; + + return 0; +} + +int main() +{ + int len; + __CPROVER_assume(0 < len && len <= MAX_LEN); + + int *arr = malloc(len * sizeof(int)); + f1(arr, len); +} diff --git a/regression/contracts-dfcc/quantifiers-forall-ensures-enforce/test.desc b/regression/contracts-dfcc/quantifiers-forall-ensures-enforce/test.desc new file mode 100644 index 00000000000..74829d6610c --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-forall-ensures-enforce/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^\[f1.postcondition.\d+\] line \d+ Check ensures clause of contract contract::f1 for function f1: SUCCESS$ +^\[f1.assigns.\d+\] line \d+ Check that arr\[\(.*\)\d\] is assignable: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +^\[f1.assigns.\d+\] line \d+ Check that arr\[\(.*\)\d\] is assignable: FAILURE$ +-- +The purpose of this test is to ensure that we can safely use __CPROVER_forall +within positive contexts (enforced ENSURES clauses). + +This is fully supported (without requiring full unrolling) with the SAT backend. diff --git a/regression/contracts-dfcc/quantifiers-forall-ensures-replace/main.c b/regression/contracts-dfcc/quantifiers-forall-ensures-replace/main.c new file mode 100644 index 00000000000..8a9da439097 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-forall-ensures-replace/main.c @@ -0,0 +1,30 @@ +#include +#include + +// clang-format off +int f1(int *arr) + __CPROVER_ensures(__CPROVER_forall { + int i; + (0 <= i && i < 10) ==> arr[i] == i + }) +// clang-format on +{ + return 0; +} + +int main() +{ + int arr[10]; + + f1(arr); + + bool check = true; + for(int i = 0; i < 10; i++) + { + if(i == 0) + check &= (arr[i] = -1); + else + check &= (arr[i] = i); + } + assert(check); +} diff --git a/regression/contracts-dfcc/quantifiers-forall-ensures-replace/test.desc b/regression/contracts-dfcc/quantifiers-forall-ensures-replace/test.desc new file mode 100644 index 00000000000..a6670241215 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-forall-ensures-replace/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --replace-call-with-contract f1 +^EXIT=10$ +^SIGNAL=0$ +^\[main.assertion.1\] line .* assertion check: FAILURE$ +^VERIFICATION FAILED$ +-- +^warning: ignoring +-- +The purpose of this test is to ensure that we can safely use __CPROVER_forall +within negative contexts (replaced ENSURES clauses). + +With the SAT backend universal quantifiers within a negative context, +e.g., the ENSURES clause being replaced in this case, +are supported only if the quantifier is bound to a constant range. diff --git a/regression/contracts-dfcc/quantifiers-forall-requires-enforce/main.c b/regression/contracts-dfcc/quantifiers-forall-requires-enforce/main.c new file mode 100644 index 00000000000..2040549f0f4 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-forall-requires-enforce/main.c @@ -0,0 +1,32 @@ +#include + +// clang-format off +bool f1(int *arr) + __CPROVER_requires(__CPROVER_forall { + int i; + (0 <= i && i < 10) ==> arr[i] == i + }) + __CPROVER_ensures( + __CPROVER_return_value == true + ) +// clang-format on +{ + bool is_identity = true; + is_identity &= (arr[0] == 0); + is_identity &= (arr[1] == 1); + is_identity &= (arr[2] == 2); + is_identity &= (arr[3] == 3); + is_identity &= (arr[4] == 4); + is_identity &= (arr[5] == 5); + is_identity &= (arr[6] == 6); + is_identity &= (arr[7] == 7); + is_identity &= (arr[8] == 8); + is_identity &= (arr[9] == 9); + return is_identity; +} + +int main() +{ + int arr[10]; + f1(arr); +} diff --git a/regression/contracts-dfcc/quantifiers-forall-requires-enforce/test.desc b/regression/contracts-dfcc/quantifiers-forall-requires-enforce/test.desc new file mode 100644 index 00000000000..1ccc72461b5 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-forall-requires-enforce/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^\[f1.postcondition.\d+\] line \d+ Check ensures clause of contract contract::f1 for function f1: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +The purpose of this test is to ensure that we can safely use __CPROVER_forall +within negative contexts (enforced REQUIRES clauses). + +With the SAT backend universal quantifiers within a negative context, +e.g., the REQUIRES clause being enforced in this case, +are supported only if the quantifier is bound to a constant range. diff --git a/regression/contracts-dfcc/quantifiers-forall-requires-replace/main.c b/regression/contracts-dfcc/quantifiers-forall-requires-replace/main.c new file mode 100644 index 00000000000..6c086f98719 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-forall-requires-replace/main.c @@ -0,0 +1,33 @@ +#include + +#define MAX_LEN 16 + +// clang-format off +int f1(int *arr, int len) + __CPROVER_requires(__CPROVER_forall { + int i; + // test replacement with symbolic bound + (0 <= i && i < len) ==> arr[i] == i + }) + __CPROVER_ensures( + __CPROVER_return_value == 0 + ) +// clang-format on +{ + return 0; +} + +int main() +{ + int len; + __CPROVER_assume(0 <= len && len <= MAX_LEN); + + int *arr = malloc(len * sizeof(int)); + for(int i = 0; i < MAX_LEN; ++i) + { + if(i < len) + arr[i] = i; + } + + f1(arr, len); +} diff --git a/regression/contracts-dfcc/quantifiers-forall-requires-replace/test.desc b/regression/contracts-dfcc/quantifiers-forall-requires-replace/test.desc new file mode 100644 index 00000000000..d5fab66f510 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-forall-requires-replace/test.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --replace-call-with-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^\[f1.precondition.\d+\] line \d+ Check requires clause of contract contract::f1 for function f1: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +The purpose of this test is to ensure that we can safely use __CPROVER_forall +within positive contexts (replaced REQUIRES clauses). + +This is fully supported (without requiring full unrolling) with the SAT backend. \ No newline at end of file diff --git a/regression/contracts-dfcc/quantifiers-nested-01/main.c b/regression/contracts-dfcc/quantifiers-nested-01/main.c new file mode 100644 index 00000000000..cf274a29202 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-nested-01/main.c @@ -0,0 +1,32 @@ +// clang-format off +int f1(int *arr) + __CPROVER_assigns(__CPROVER_object_whole(arr)) + __CPROVER_ensures(__CPROVER_forall { + int i; + __CPROVER_forall + { + int j; + (0 <= i && i < 10 && i <= j && j < 10) ==> arr[i] <= arr[j] + } + }) +// clang-format on +{ + arr[0] = 0; + arr[1] = 1; + arr[2] = 2; + arr[3] = 3; + arr[4] = 4; + arr[5] = 5; + arr[6] = 6; + arr[7] = 7; + arr[8] = 8; + arr[9] = 9; + + return 0; +} + +int main() +{ + int arr[10]; + f1(arr); +} diff --git a/regression/contracts-dfcc/quantifiers-nested-01/test.desc b/regression/contracts-dfcc/quantifiers-nested-01/test.desc new file mode 100644 index 00000000000..f71cde34d42 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-nested-01/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +This test case checks the handling of a forall expression +nested within another forall expression. diff --git a/regression/contracts-dfcc/quantifiers-nested-02/main.c b/regression/contracts-dfcc/quantifiers-nested-02/main.c new file mode 100644 index 00000000000..0e43fb11d53 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-nested-02/main.c @@ -0,0 +1,23 @@ +// clang-format off +int f1(int *arr) + __CPROVER_requires( + __CPROVER_forall { + int i; + (0 <= i && i < 9) ==> __CPROVER_forall { + int j; + (i <= j && j < 10) ==> arr[i] <= arr[j] + }} + ) + __CPROVER_ensures( + __CPROVER_return_value == 0 + ) +// clang-format on +{ + return 0; +} + +int main() +{ + int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + f1(arr); +} diff --git a/regression/contracts-dfcc/quantifiers-nested-02/test.desc b/regression/contracts-dfcc/quantifiers-nested-02/test.desc new file mode 100644 index 00000000000..62366d94144 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-nested-02/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --replace-call-with-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +This test case checks the handling of a forall expression +nested within an implication. diff --git a/regression/contracts-dfcc/quantifiers-nested-03/main.c b/regression/contracts-dfcc/quantifiers-nested-03/main.c new file mode 100644 index 00000000000..75bf9ef37f0 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-nested-03/main.c @@ -0,0 +1,31 @@ +// clang-format off +int f1(int *arr) +__CPROVER_assigns(__CPROVER_object_whole(arr)) +__CPROVER_ensures( + __CPROVER_return_value == 0 && + __CPROVER_exists { + int i; + (0 <= i && i < 10) && arr[i] == i + } +) +// clang-format on +{ + arr[0] = 0; + arr[1] = 1; + arr[2] = 2; + arr[3] = 3; + arr[4] = 4; + arr[5] = 5; + arr[6] = 6; + arr[7] = 7; + arr[8] = 8; + arr[9] = 9; + + return 0; +} + +int main() +{ + int arr[10]; + f1(arr); +} diff --git a/regression/contracts-dfcc/quantifiers-nested-03/test.desc b/regression/contracts-dfcc/quantifiers-nested-03/test.desc new file mode 100644 index 00000000000..81573cba6fe --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-nested-03/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --enforce-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +This test case checks the handling of an exists expression +nested within a conjunction. diff --git a/regression/contracts-dfcc/quantifiers-nested-04/main.c b/regression/contracts-dfcc/quantifiers-nested-04/main.c new file mode 100644 index 00000000000..41d875577dd --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-nested-04/main.c @@ -0,0 +1,26 @@ +// clang-format off +int f1(int *arr) + __CPROVER_requires( + __CPROVER_forall { + int i; + (0 <= i && i < 10) ==> arr[i] == 0 + } || + arr[9] == -1 || + __CPROVER_exists { + int i; + (0 <= i && i < 10) && arr[i] == i + } + ) + __CPROVER_ensures( + __CPROVER_return_value == 0 + ) +// clang-format on +{ + return 0; +} + +int main() +{ + int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + f1(arr); +} diff --git a/regression/contracts-dfcc/quantifiers-nested-04/test.desc b/regression/contracts-dfcc/quantifiers-nested-04/test.desc new file mode 100644 index 00000000000..03487b6075c --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-nested-04/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --replace-call-with-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +This test case checks the handling of both a forall expression +and an exists expression nested within a disjunction. diff --git a/regression/contracts-dfcc/quantifiers-nested-05/main.c b/regression/contracts-dfcc/quantifiers-nested-05/main.c new file mode 100644 index 00000000000..f667b14b177 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-nested-05/main.c @@ -0,0 +1,21 @@ +// clang-format off +int f1(int *arr) + __CPROVER_requires( + !__CPROVER_forall { + int i; + (0 <= i && i < 10) ==> arr[i] == 0 + } + ) + __CPROVER_ensures( + __CPROVER_return_value == 0 + ) +// clang-format on +{ + return 0; +} + +int main() +{ + int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + f1(arr); +} diff --git a/regression/contracts-dfcc/quantifiers-nested-05/test.desc b/regression/contracts-dfcc/quantifiers-nested-05/test.desc new file mode 100644 index 00000000000..4dc7f647db4 --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-nested-05/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --replace-call-with-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +This test case checks the handling of a forall expression +nested within a negation. diff --git a/regression/contracts-dfcc/quantifiers-nested-06/main.c b/regression/contracts-dfcc/quantifiers-nested-06/main.c new file mode 100644 index 00000000000..fda936fb03e --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-nested-06/main.c @@ -0,0 +1,38 @@ +#include + +// clang-format off +int f1(int *arr) + __CPROVER_requires( + ( + __CPROVER_forall { + int i; + (0 <= i && i < 10) ==> arr[i] == 0 + } ? + __CPROVER_exists { + int i; + (0 <= i && i < 10) ==> arr[i] == 0 + } : false + ) && ( + __CPROVER_forall { + int i; + (0 <= i && i < 10) ==> arr[i] == i + } ? false : + __CPROVER_forall { + int i; + (0 <= i && i < 10) ==> arr[i] == 0 + } + ) + ) + __CPROVER_ensures( + __CPROVER_return_value == 0 + ) +// clang-format on +{ + return 0; +} + +int main() +{ + int arr[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + f1(arr); +} diff --git a/regression/contracts-dfcc/quantifiers-nested-06/test.desc b/regression/contracts-dfcc/quantifiers-nested-06/test.desc new file mode 100644 index 00000000000..30a382f44bd --- /dev/null +++ b/regression/contracts-dfcc/quantifiers-nested-06/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --replace-call-with-contract f1 +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +^warning: ignoring +-- +This test case checks the handling of forall and exists expressions +nested within ternary ITE expressions (condition ? true : false). diff --git a/regression/contracts-dfcc/reject_history_expr_in_assigns_clause/main.c b/regression/contracts-dfcc/reject_history_expr_in_assigns_clause/main.c new file mode 100644 index 00000000000..b0ba3b9841b --- /dev/null +++ b/regression/contracts-dfcc/reject_history_expr_in_assigns_clause/main.c @@ -0,0 +1,11 @@ +int foo(int *x) __CPROVER_assigns(__CPROVER_old(*x)) +{ + return 0; +} + +int main() +{ + int x; + int ret = foo(&x); + return 0; +} diff --git a/regression/contracts-dfcc/reject_history_expr_in_assigns_clause/test.desc b/regression/contracts-dfcc/reject_history_expr_in_assigns_clause/test.desc new file mode 100644 index 00000000000..2788121bb93 --- /dev/null +++ b/regression/contracts-dfcc/reject_history_expr_in_assigns_clause/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--enforce-contract foo +^main.c.*error: assigns clause target must be a non-void lvalue, a call to __CPROVER_POINTER_OBJECT or a call to a function returning void$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test checks that __CPROVER_old occurences in assigns clauses +are detected and rejected. diff --git a/regression/contracts-dfcc/reject_history_expr_in_preconditions/main.c b/regression/contracts-dfcc/reject_history_expr_in_preconditions/main.c new file mode 100644 index 00000000000..02e4e043a92 --- /dev/null +++ b/regression/contracts-dfcc/reject_history_expr_in_preconditions/main.c @@ -0,0 +1,11 @@ +int foo(int *x) __CPROVER_requires(__CPROVER_old(*x)) +{ + return 0; +} + +int main() +{ + int x; + int retval = foo(&x); + return 0; +} diff --git a/regression/contracts-dfcc/reject_history_expr_in_preconditions/test.desc b/regression/contracts-dfcc/reject_history_expr_in_preconditions/test.desc new file mode 100644 index 00000000000..6c548ddfcd2 --- /dev/null +++ b/regression/contracts-dfcc/reject_history_expr_in_preconditions/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--enforce-contract foo +^main.c.*error: __CPROVER_old is not allowed in preconditions.$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test checks that __CPROVER_old occurences in preconditions +are detected and rejected. diff --git a/regression/contracts-dfcc/reject_return_value_in_assigns_clause/main.c b/regression/contracts-dfcc/reject_return_value_in_assigns_clause/main.c new file mode 100644 index 00000000000..dfc0417b157 --- /dev/null +++ b/regression/contracts-dfcc/reject_return_value_in_assigns_clause/main.c @@ -0,0 +1,10 @@ +int foo() __CPROVER_assigns(__CPROVER_return_value) +{ + return 0; +} + +int main() +{ + int x = foo(); + return 0; +} diff --git a/regression/contracts-dfcc/reject_return_value_in_assigns_clause/test.desc b/regression/contracts-dfcc/reject_return_value_in_assigns_clause/test.desc new file mode 100644 index 00000000000..ed7730dd1ae --- /dev/null +++ b/regression/contracts-dfcc/reject_return_value_in_assigns_clause/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--enforce-contract foo +^main.c.*error: __CPROVER_return_value is not allowed in assigns clauses.$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test checks that __CPROVER_return_value occurences in assigns clauses +are detected and rejected. diff --git a/regression/contracts-dfcc/reject_return_value_in_preconditions/main.c b/regression/contracts-dfcc/reject_return_value_in_preconditions/main.c new file mode 100644 index 00000000000..ee9e6f00dd2 --- /dev/null +++ b/regression/contracts-dfcc/reject_return_value_in_preconditions/main.c @@ -0,0 +1,10 @@ +int foo() __CPROVER_requires(__CPROVER_return_value == 0) +{ + return 0; +} + +int main() +{ + int x = foo(); + return 0; +} diff --git a/regression/contracts-dfcc/reject_return_value_in_preconditions/test.desc b/regression/contracts-dfcc/reject_return_value_in_preconditions/test.desc new file mode 100644 index 00000000000..3ab8474ce0e --- /dev/null +++ b/regression/contracts-dfcc/reject_return_value_in_preconditions/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--enforce-contract foo +^main.c.*error: __CPROVER_return_value is not allowed in preconditions.$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test checks that __CPROVER_return_value occurences in preconditions +are detected and rejected. diff --git a/regression/contracts-dfcc/replace-nondet-return-value/main.c b/regression/contracts-dfcc/replace-nondet-return-value/main.c new file mode 100644 index 00000000000..9b2d3684cfa --- /dev/null +++ b/regression/contracts-dfcc/replace-nondet-return-value/main.c @@ -0,0 +1,23 @@ +int cmp(int i1, int i2) + // clang-format off + __CPROVER_ensures((i1 == i2) ==> (__CPROVER_return_value == 0)) + __CPROVER_ensures((i1 != i2) ==> (__CPROVER_return_value == -1)) +// clang-format on +{ + if(i1 == i2) + return 0; + else + return -1; +} + +int main() +{ + int ret = -1; + ret = cmp(0, 0); + __CPROVER_assert(ret == 0, "expecting SUCCESS"); + ret = cmp(0, 1); + __CPROVER_assert(ret == 0, "expecting FAILURE"); + __CPROVER_assert(ret == -1, "expecting SUCCESS"); + __CPROVER_assert(0, "expecting FAILURE"); + return 0; +} diff --git a/regression/contracts-dfcc/replace-nondet-return-value/test.desc b/regression/contracts-dfcc/replace-nondet-return-value/test.desc new file mode 100644 index 00000000000..fe336e5d534 --- /dev/null +++ b/regression/contracts-dfcc/replace-nondet-return-value/test.desc @@ -0,0 +1,20 @@ +CORE +main.c +--dfcc main --replace-call-with-contract cmp +^EXIT=10$ +^SIGNAL=0$ +^\[main\.assertion\.1\] line \d+ expecting SUCCESS: SUCCESS$ +^\[main\.assertion\.2\] line \d+ expecting FAILURE: FAILURE$ +^\[main\.assertion\.3\] line \d+ expecting SUCCESS: SUCCESS$ +^\[main\.assertion\.4\] line \d+ expecting FAILURE: FAILURE$ +^\*\* 2 of \d+ failed +^VERIFICATION FAILED$ +-- +-- +This test checks that the return value of a replaced function call is made +nondet at each replacement site. +The replaced function is called twice. Each call is expected to have a different +return value. If the return value of the call is not made nondet at each +replacement, it would be subject to contradictory constraints +(from the post conditions) and the assertions expected to fail would +be vacuously satisfied. diff --git a/regression/contracts-dfcc/test_aliasing_enforce/main.c b/regression/contracts-dfcc/test_aliasing_enforce/main.c new file mode 100644 index 00000000000..e79ef71fce3 --- /dev/null +++ b/regression/contracts-dfcc/test_aliasing_enforce/main.c @@ -0,0 +1,34 @@ +#include +#include +#include + +int z; + +// clang-format off +int foo(int *x, int *y) + __CPROVER_assigns(z, *x, *y) + __CPROVER_requires( + __CPROVER_is_fresh(x, sizeof(int)) && + __CPROVER_is_fresh(y, sizeof(int)) && + *x > 0 && + *x < 4) + __CPROVER_ensures( + x != NULL && + y != NULL && + x != y && + __CPROVER_return_value == *x + 5) +// clang-format on +{ + *x = *x + 4; + *y = *x; + z = *y; + return (*x + 5); +} + +int main() +{ + int n = 4; + n = foo(&n, &n); + assert(!(n < 4)); + return 0; +} diff --git a/regression/contracts-dfcc/test_aliasing_enforce/test.desc b/regression/contracts-dfcc/test_aliasing_enforce/test.desc new file mode 100644 index 00000000000..9bc845fd1f8 --- /dev/null +++ b/regression/contracts-dfcc/test_aliasing_enforce/test.desc @@ -0,0 +1,15 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +\[foo.postcondition.\d+\] line \d+ Check ensures clause of contract contract::foo for function foo: SUCCESS$ +\[foo.assigns.\d+\] line \d+ Check that \*x is assignable: SUCCESS +\[foo.assigns.\d+\] line \d+ Check that \*y is assignable: SUCCESS +\[foo.assigns.\d+\] line \d+ Check that z is assignable: SUCCESS +\[main.assertion.\d+\] line \d+ assertion \!\(n \< 4\): SUCCESS +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether assuming __CPROVER_is_fresh will guarantee a new freshly +allocated pointer (no aliasing) for structs. diff --git a/regression/contracts-dfcc/test_aliasing_ensure/main.c b/regression/contracts-dfcc/test_aliasing_ensure/main.c new file mode 100644 index 00000000000..9b66c5589cd --- /dev/null +++ b/regression/contracts-dfcc/test_aliasing_ensure/main.c @@ -0,0 +1,35 @@ +#include +#include +#include + +int z; + +// clang-format off +int foo(int *x, int *y) + __CPROVER_assigns(z, *x) + __CPROVER_requires( + __CPROVER_is_fresh(x, sizeof(int)) && + *x > 0 && + *x < 4) + __CPROVER_ensures( + __CPROVER_is_fresh(y, sizeof(int)) && + !__CPROVER_is_fresh(x, sizeof(int)) && + x != NULL && + x != y && + __CPROVER_return_value == *x + 5) +// clang-format on +{ + *x = *x + 4; + y = malloc(sizeof(*y)); + *y = *x; + z = *y; + return (*x + 5); +} + +int main() +{ + int n = 4; + n = foo(&n, &n); + assert(!(n < 4)); + return 0; +} diff --git a/regression/contracts-dfcc/test_aliasing_ensure/test.desc b/regression/contracts-dfcc/test_aliasing_ensure/test.desc new file mode 100644 index 00000000000..29bd2e5ab12 --- /dev/null +++ b/regression/contracts-dfcc/test_aliasing_ensure/test.desc @@ -0,0 +1,15 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +\[foo.postcondition.\d+\].*Check ensures clause of contract contract::foo for function foo: SUCCESS$ +\[foo.assigns.\d+\].*Check that \*x is assignable: SUCCESS +\[foo.assigns.\d+\].*Check that \*y is assignable: SUCCESS +\[foo.assigns.\d+\].*Check that z is assignable: SUCCESS +\[main.assertion.\d+\].*assertion \!\(n \< 4\): SUCCESS +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether ensures(is_fresh()) pass on functions that perform allocation +directly and return a new object. diff --git a/regression/contracts-dfcc/test_aliasing_ensure_indirect/foo_bar.h b/regression/contracts-dfcc/test_aliasing_ensure_indirect/foo_bar.h new file mode 100644 index 00000000000..a60b300eb67 --- /dev/null +++ b/regression/contracts-dfcc/test_aliasing_ensure_indirect/foo_bar.h @@ -0,0 +1,25 @@ +#include + +void bar(int **x) __CPROVER_assigns(*x) + __CPROVER_requires(__CPROVER_is_fresh(x, sizeof(*x))) + __CPROVER_ensures(__CPROVER_is_fresh(*x, sizeof(**x))) +{ + __CPROVER_assert(__CPROVER_r_ok(x, sizeof(*x)), "x is r_ok"); + *x = malloc(sizeof(**x)); + __CPROVER_assert(__CPROVER_r_ok(*x, sizeof(**x)), "deref x is r_ok"); +} + +void foo(int *x1, int **x2) __CPROVER_assigns(*x2) + __CPROVER_requires(__CPROVER_is_fresh(x1, sizeof(*x1))) + __CPROVER_requires(__CPROVER_is_fresh(x2, sizeof(*x2))) + __CPROVER_requires(__CPROVER_is_fresh(*x2, sizeof(**x2))) + __CPROVER_ensures(__CPROVER_is_fresh(*x2, sizeof(**x2))) +{ + __CPROVER_assert(__CPROVER_r_ok(x1, sizeof(*x1)), "x1 r_ok"); + __CPROVER_assert(__CPROVER_r_ok(x2, sizeof(*x2)), "x2 r_ok"); + __CPROVER_assert(__CPROVER_r_ok(*x2, sizeof(**x2)), "deref x2 r_ok"); + int *old_x2 = *x2; + bar(x2); + __CPROVER_assert(__CPROVER_r_ok(*x2, sizeof(**x2)), "deref x2 r_ok"); + __CPROVER_assert(!__CPROVER_same_object(*x2, old_x2), "x2 updated"); +} diff --git a/regression/contracts-dfcc/test_aliasing_ensure_indirect/main_bar.c b/regression/contracts-dfcc/test_aliasing_ensure_indirect/main_bar.c new file mode 100644 index 00000000000..38c2c86a51b --- /dev/null +++ b/regression/contracts-dfcc/test_aliasing_ensure_indirect/main_bar.c @@ -0,0 +1,8 @@ +#include "foo_bar.h" + +int main() +{ + int **x; + bar(x); + return 0; +} diff --git a/regression/contracts-dfcc/test_aliasing_ensure_indirect/main_foo.c b/regression/contracts-dfcc/test_aliasing_ensure_indirect/main_foo.c new file mode 100644 index 00000000000..8ed1852c5df --- /dev/null +++ b/regression/contracts-dfcc/test_aliasing_ensure_indirect/main_foo.c @@ -0,0 +1,8 @@ +#include "foo_bar.h" +int main() +{ + int *x1; + int **x2; + foo(x1, x2); + return 0; +} diff --git a/regression/contracts-dfcc/test_aliasing_ensure_indirect/test-bar.desc b/regression/contracts-dfcc/test_aliasing_ensure_indirect/test-bar.desc new file mode 100644 index 00000000000..a9717cfd938 --- /dev/null +++ b/regression/contracts-dfcc/test_aliasing_ensure_indirect/test-bar.desc @@ -0,0 +1,14 @@ +CORE +main_bar.c +--dfcc main --enforce-contract bar +^\[bar.postcondition.\d+\].*Check ensures clause of contract contract::bar for function bar: SUCCESS$ +^\[bar.assertion.\d+\].*x is r_ok: SUCCESS$ +^\[bar.assigns.\d+\].*Check that \*x is assignable: SUCCESS$ +^\[bar.assertion.\d+\].*deref x is r_ok: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks that bar satisfies its contract (bar gets replaced by its contract +test-foo.desc). diff --git a/regression/contracts-dfcc/test_aliasing_ensure_indirect/test-foo.desc b/regression/contracts-dfcc/test_aliasing_ensure_indirect/test-foo.desc new file mode 100644 index 00000000000..35338c33b08 --- /dev/null +++ b/regression/contracts-dfcc/test_aliasing_ensure_indirect/test-foo.desc @@ -0,0 +1,19 @@ +CORE +main_foo.c +--dfcc main --enforce-contract foo --replace-call-with-contract bar +^\[foo.postcondition.\d+\].*Check ensures clause of contract contract::foo for function foo: SUCCESS$ +^\[foo.assertion.\d+\].*x1 r_ok: SUCCESS$ +^\[foo.assertion.\d+\].*x2 r_ok: SUCCESS$ +^\[foo.assertion.\d+\].*deref x2 r_ok: SUCCESS$ +^\[foo.assertion.\d+\].*deref x2 r_ok: SUCCESS$ +^\[foo.assertion.\d+\].*x2 updated: SUCCESS$ +^\[bar.precondition.\d+\].*Check requires clause of contract contract::bar for function bar: SUCCESS$ +^\[bar.assigns.\d+\].*Check that the assigns clause of contract::bar is included in the caller's assigns clause: SUCCESS$ +^\[bar.frees.\d+\].*Check that the frees clause of contract::bar is included in the caller's frees clause: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks that __CPROVER_is_fresh assumed as postconditions in the contract of bar +allows to satisfy the __CPROVER_is_fresh post condition of foo. diff --git a/regression/contracts-dfcc/test_aliasing_replace/main.c b/regression/contracts-dfcc/test_aliasing_replace/main.c new file mode 100644 index 00000000000..f77721d1a2a --- /dev/null +++ b/regression/contracts-dfcc/test_aliasing_replace/main.c @@ -0,0 +1,32 @@ +#include +#include +#include + +int z; + +// clang-format off +int foo(int *x, int *y) + __CPROVER_assigns(z, *x, *y) + __CPROVER_requires( + __CPROVER_is_fresh(x, sizeof(int)) && + __CPROVER_is_fresh(y, sizeof(int)) && + *x > 0 + && *x <= 4) + __CPROVER_ensures( + x != NULL && + y != NULL && + x != y && + __CPROVER_return_value == *x + 5) +// clang-format on +{ + *x = *x + 4; + return (*x + 5); +} + +int main() +{ + int n = 4; + n = foo(&n, &n); + assert(!(n < 4)); + return 0; +} diff --git a/regression/contracts-dfcc/test_aliasing_replace/test.desc b/regression/contracts-dfcc/test_aliasing_replace/test.desc new file mode 100644 index 00000000000..81c0f13e6ca --- /dev/null +++ b/regression/contracts-dfcc/test_aliasing_replace/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^EXIT=10$ +^SIGNAL=0$ +^\[foo.precondition.\d+\] line \d+ Check requires clause of contract contract::foo for function foo: FAILURE$ +^\[main.assertion.\d+\] line \d+ assertion \!\(n \< 4\): SUCCESS$ +^VERIFICATION FAILED$ +-- +-- +Checks whether asserting __CPROVER_is_fresh will guarantee that objects +mapped to distinct objetcs. diff --git a/regression/contracts-dfcc/test_array_memory_enforce/main.c b/regression/contracts-dfcc/test_array_memory_enforce/main.c new file mode 100644 index 00000000000..937456af0e5 --- /dev/null +++ b/regression/contracts-dfcc/test_array_memory_enforce/main.c @@ -0,0 +1,42 @@ +#include + +bool ptr_ok(int *x) +{ + return (*x < 5); +} + +bool return_ok(int ret_value, int *x) +{ + int a; + a = *x; + return (ret_value == *x + 5); +} + +// clang-format off +int foo(int *x) + __CPROVER_assigns(__CPROVER_object_from(x)) + __CPROVER_requires( + __CPROVER_is_fresh(x, sizeof(int) * 10) && + x[0] > 0 && + ptr_ok(x)) + __CPROVER_ensures( + !ptr_ok(x) && + /* x is not fresh because it was seen by the preconditions. */ + !__CPROVER_is_fresh(x, sizeof(int) * 10) && + x[9] == 113 && + return_ok(__CPROVER_return_value, x)) +// clang-format on +{ + *x = *x + 4; + x[5] = 12; + x[9] = 113; + int y = *x + 5; + return *x + 5; +} + +int main() +{ + int *n; + int o = foo(n); + return 0; +} diff --git a/regression/contracts-dfcc/test_array_memory_enforce/test.desc b/regression/contracts-dfcc/test_array_memory_enforce/test.desc new file mode 100644 index 00000000000..4c145f64aa8 --- /dev/null +++ b/regression/contracts-dfcc/test_array_memory_enforce/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +\[foo.postcondition.\d+\].*Check ensures clause of contract contract::foo for function foo: SUCCESS +\[foo.assigns.\d+\].*Check that \*x is assignable: SUCCESS +\[foo.assigns.\d+\].*Check that x\[\(.* int\)5\] is assignable: SUCCESS +\[foo.assigns.\d+\].*Check that x\[\(.* int\)9\] is assignable: SUCCESS +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether __CPROVER_is_fresh behaves correctly for arrays both in +requires and ensures clauses. In the ensures clause, the __CPROVER_is_fresh +is unnecessary. By negating the predicate in the ensures clause, the test +proves that `x` is not fresh at end of the function. diff --git a/regression/contracts-dfcc/test_array_memory_replace/main.c b/regression/contracts-dfcc/test_array_memory_replace/main.c new file mode 100644 index 00000000000..76a7d7063ca --- /dev/null +++ b/regression/contracts-dfcc/test_array_memory_replace/main.c @@ -0,0 +1,46 @@ +#include +#include +#include + +bool ptr_ok(int *x) +{ + return (*x < 5); +} + +bool return_ok(int ret_value, int *x) +{ + int a; + a = *x; + return (ret_value == (*x + 5)); +} + +// clang-format off +int foo(int *x) + __CPROVER_assigns(*x) + __CPROVER_requires( + __CPROVER_is_fresh(x, sizeof(int) * 10) && + x[0] > 0 && + ptr_ok(x)) + __CPROVER_ensures( + !ptr_ok(x) && + !__CPROVER_is_fresh(x, sizeof(int) * 10) && + x[9] == 113 && + return_ok(__CPROVER_return_value, x)) +// clang-format on +{ + *x = *x + 4; + x[5] = 12; + x[9] = 113; + int y = *x + 5; + return *x + 5; +} + +int main() +{ + int *n = malloc(sizeof(int) * 10); + n[0] = 3; + int o = foo(n); + assert(o >= 10 && o == *n + 5); + assert(n[9] == 113); + return 0; +} diff --git a/regression/contracts-dfcc/test_array_memory_replace/test.desc b/regression/contracts-dfcc/test_array_memory_replace/test.desc new file mode 100644 index 00000000000..eaa05296653 --- /dev/null +++ b/regression/contracts-dfcc/test_array_memory_replace/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^EXIT=0$ +^SIGNAL=0$ +^\[foo.precondition.\d+\] line \d+ Check requires clause of contract contract::foo for function foo: SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion o >\= 10 \&\& o \=\= \*n \+ 5: SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion n\[9\] == 113: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether CBMC successfuly assert __CPROVER_is_fresh for arrays. diff --git a/regression/contracts-dfcc/test_array_memory_too_small_replace/main.c b/regression/contracts-dfcc/test_array_memory_too_small_replace/main.c new file mode 100644 index 00000000000..4c8cd24452e --- /dev/null +++ b/regression/contracts-dfcc/test_array_memory_too_small_replace/main.c @@ -0,0 +1,45 @@ +#include +#include +#include + +bool ptr_ok(int *x) +{ + return (*x < 5); +} + +bool return_ok(int ret_value, int *x) +{ + int a; + a = *x; + return (ret_value == (*x + 5)); +} + +// clang-format off +int foo(int *x) + __CPROVER_assigns(*x) + __CPROVER_requires( + __CPROVER_is_fresh(x, sizeof(int) * 10) && + x[0] > 0 && + ptr_ok(x)) + __CPROVER_ensures( + !ptr_ok(x) && + !__CPROVER_is_fresh(x, sizeof(int) * 10) && + x[9] == 113 && + return_ok(__CPROVER_return_value, x)) +// clang-format on +{ + *x = *x + 4; + x[5] = 12; + x[9] = 113; + int y = *x + 5; + return (*x + 5); +} + +int main() +{ + int *n = malloc(sizeof(int) * 9); + n[0] = 3; + int o = foo(n); + assert(o >= 10 && o == *n + 5); + return 0; +} diff --git a/regression/contracts-dfcc/test_array_memory_too_small_replace/test.desc b/regression/contracts-dfcc/test_array_memory_too_small_replace/test.desc new file mode 100644 index 00000000000..69cfcf98108 --- /dev/null +++ b/regression/contracts-dfcc/test_array_memory_too_small_replace/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^EXIT=10$ +^SIGNAL=0$ +^\[foo.precondition.\d+\] line \d+ Check requires clause of contract contract::foo for function foo: FAILURE$ +^\[main.assertion.\d+\] line \d+ assertion o >\= 10 \&\& o \=\= \*n \+ 5: SUCCESS$ +^VERIFICATION FAILED$ +-- +-- +Checks whether CBMC successfuly assert __CPROVER_is_fresh for arrays. +Test fails because allocated array was smaller then the one required +in the contract. diff --git a/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_ensures/main.c b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_ensures/main.c new file mode 100644 index 00000000000..02fd405e4b5 --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_ensures/main.c @@ -0,0 +1,23 @@ +#include + +#define SIZE 10 +int nondet_int(); + +void foo(char **out1, char **out2) + // clang-format off + __CPROVER_assigns(*out1, *out2) + __CPROVER_ensures(__CPROVER_is_fresh(*out1, SIZE)) + __CPROVER_ensures(__CPROVER_is_fresh(*out2, SIZE)) +// clang-format on +{ + *out1 = malloc(SIZE); + *out2 = nondet_int() ? malloc(SIZE) : *out1; +} + +int main() +{ + char *out1; + char *out2; + foo(&out1, &out2); + return 0; +} diff --git a/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_ensures/test.desc b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_ensures/test.desc new file mode 100644 index 00000000000..c4de8c4d834 --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_ensures/test.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^\[foo.assigns.\d+\].*Check that \*out1 is assignable: SUCCESS$ +^\[foo.assigns.\d+\].*Check that \*out2 is assignable: SUCCESS$ +^\[foo.postcondition.\d+\].*Check ensures clause of contract contract::foo for function foo: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This test checks that using __CPROVER_is_fresh in postconditions when checking +a contract will effectively fail if objects can alias with objects seen by +__CPROVER_is_fresh in postconditions. diff --git a/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_requires/main.c b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_requires/main.c new file mode 100644 index 00000000000..61bff47682b --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_requires/main.c @@ -0,0 +1,30 @@ +#include + +#define SIZE 10 +int nondet_int(); +void foo(char *in1, char *in2, char **out) + // clang-format off + __CPROVER_assigns(*out) + __CPROVER_requires( + __CPROVER_is_fresh(in1, SIZE) && + __CPROVER_is_fresh(in2, SIZE)) + __CPROVER_ensures( + __CPROVER_is_fresh(*out, SIZE)) +// clang-format on +{ + __CPROVER_assert(__CPROVER_rw_ok(in1, SIZE), "in1 is rw_ok"); + __CPROVER_assert(__CPROVER_rw_ok(in2, SIZE), "in2 is rw_ok"); + __CPROVER_assert( + !__CPROVER_same_object(in1, in2), "in1 and in2 do not alias"); + // nondeterministically fresh or aliased + *out = nondet_int() ? malloc(SIZE) : in1; +} + +int main() +{ + char *in1; + char *in2; + char *out; + foo(in1, in2, &out); + return 0; +} diff --git a/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_requires/test.desc b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_requires/test.desc new file mode 100644 index 00000000000..7e786e0a936 --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_separation_against_requires/test.desc @@ -0,0 +1,15 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^\[foo.assertion.\d+\].*in1 is rw_ok: SUCCESS$ +^\[foo.assertion.\d+\].*in2 is rw_ok: SUCCESS$ +^\[foo.assertion.\d+\].*in1 and in2 do not alias: SUCCESS$ +^\[foo.postcondition.\d+\].*Check ensures clause of contract contract::foo for function foo: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This test checks that using __CPROVER_is_fresh in postconditions when checking +a contract will effectively fail if objects can alias with objects seen by +__CPROVER_is_fresh in preconditions. diff --git a/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_size/main.c b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_size/main.c new file mode 100644 index 00000000000..46cdc85ce70 --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_size/main.c @@ -0,0 +1,20 @@ +#include + +#define SIZE 10 +int nondet_int(); + +void foo(char **out) + // clang-format off + __CPROVER_assigns(*out) + __CPROVER_ensures(__CPROVER_is_fresh(*out, SIZE)) +// clang-format on +{ + *out = malloc(nondet_int() ? SIZE : SIZE - 1); +} + +int main() +{ + char *out; + foo(&out); + return 0; +} diff --git a/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_size/test.desc b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_size/test.desc new file mode 100644 index 00000000000..0b210e82532 --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_fail_size/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^\[foo.assigns.\d+\].*Check that \*out is assignable: SUCCESS$ +^\[foo.postcondition.\d+\].*Check ensures clause of contract contract::foo for function foo: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This test checks that using __CPROVER_is_fresh in postconditions when checking +a contract will effectively fail if objects passed to __CPROVER_is_fresh have +the wrong size. diff --git a/regression/contracts-dfcc/test_is_fresh_enforce_ensures_pass/main.c b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_pass/main.c new file mode 100644 index 00000000000..af4a22a413a --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_pass/main.c @@ -0,0 +1,30 @@ +#include + +void foo(int *in1, int *in2, int **out1, int **out2) + // clang-format off + __CPROVER_assigns(*out1, *out2) + __CPROVER_requires( + __CPROVER_is_fresh(in1, sizeof(int)) && + __CPROVER_is_fresh(in2, sizeof(int))) + __CPROVER_ensures( + __CPROVER_is_fresh(*out1, sizeof(int)) && + __CPROVER_is_fresh(*out2, sizeof(int))) +// clang-format on +{ + __CPROVER_assert(__CPROVER_rw_ok(in1, sizeof(int)), "in1 is rw_ok"); + __CPROVER_assert(__CPROVER_rw_ok(in2, sizeof(int)), "in2 is rw_ok"); + __CPROVER_assert( + !__CPROVER_same_object(in1, in2), "in1 and in2 do not alias"); + *out1 = malloc(sizeof(int)); + *out2 = malloc(sizeof(int)); +} + +int main() +{ + int *in1; + int *in2; + int *out1; + int *out2; + foo(in1, in2, &out1, &out2); + return 0; +} diff --git a/regression/contracts-dfcc/test_is_fresh_enforce_ensures_pass/test.desc b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_pass/test.desc new file mode 100644 index 00000000000..98cfa40431e --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_enforce_ensures_pass/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^\[foo.postcondition.\d+\].*Check ensures clause of contract contract::foo for function foo: SUCCESS$ +^\[foo.assertion.\d+\].*in1 is rw_ok: SUCCESS$ +^\[foo.assertion.\d+\].*in2 is rw_ok: SUCCESS$ +^\[foo.assertion.\d+\].*in1 and in2 do not alias: SUCCESS$ +^\[foo.assigns.\d+\].*Check that \*out1 is assignable: SUCCESS$ +^\[foo.assigns.\d+\].*Check that \*out2 is assignable: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that using __CPROVER_is_fresh in postconditions when checking +a contract will effectively recognize fresh objects as fresh. diff --git a/regression/contracts-dfcc/test_is_fresh_enforce_requires_pass/main.c b/regression/contracts-dfcc/test_is_fresh_enforce_requires_pass/main.c new file mode 100644 index 00000000000..718196f9b36 --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_enforce_requires_pass/main.c @@ -0,0 +1,24 @@ +void foo(int *in1, int *in2) + // clang-format off + __CPROVER_requires( + __CPROVER_is_fresh(in1, sizeof(int)) && + __CPROVER_is_fresh(in2, sizeof(int)) && + *in1 == 0 && + *in2 == 0) +// clang-format on +{ + __CPROVER_assert(__CPROVER_rw_ok(in1, sizeof(int)), "in1 is rw_ok"); + __CPROVER_assert(__CPROVER_rw_ok(in2, sizeof(int)), "in2 is rw_ok"); + __CPROVER_assert( + !__CPROVER_same_object(in1, in2), "in1 and in2 do not alias"); + __CPROVER_assert(*in1 == 0, "in1 is zero"); + __CPROVER_assert(*in2 == 0, "in2 is zero"); +} + +int main() +{ + int *in1; + int *in2; + foo(in1, in2); + return 0; +} diff --git a/regression/contracts-dfcc/test_is_fresh_enforce_requires_pass/test.desc b/regression/contracts-dfcc/test_is_fresh_enforce_requires_pass/test.desc new file mode 100644 index 00000000000..f1a95b7d4d5 --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_enforce_requires_pass/test.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^\[foo.assertion.\d+\].*in1 is rw_ok: SUCCESS$ +^\[foo.assertion.\d+\].*in2 is rw_ok: SUCCESS$ +^\[foo.assertion.\d+\].*in1 and in2 do not alias: SUCCESS$ +^\[foo.assertion.\d+\].*in1 is zero: SUCCESS$ +^\[foo.assertion.\d+\].*in2 is zero: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that using __CPROVER_is_fresh in preconditions when checking +a contract will effectively allocate fresh and distinct objects. +The rw_ok and sepatation checks are done using assertions inside the function. diff --git a/regression/contracts-dfcc/test_is_fresh_replace_ensures_pass/main.c b/regression/contracts-dfcc/test_is_fresh_replace_ensures_pass/main.c new file mode 100644 index 00000000000..743a5f7560f --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_replace_ensures_pass/main.c @@ -0,0 +1,28 @@ +#include +#include +#include + +#define SIZE 10 + +void foo(char **out_ptr1, char **out_ptr2) + // clang-format off + __CPROVER_assigns(*out_ptr1, *out_ptr2) + __CPROVER_ensures(__CPROVER_is_fresh(*out_ptr1, SIZE)) + __CPROVER_ensures(__CPROVER_is_fresh(*out_ptr2, SIZE)) +// clang-format on +{ + *out_ptr1 = malloc(SIZE); + *out_ptr2 = malloc(SIZE); +} + +int main() +{ + char *out1; + char *out2; + foo(&out1, &out2); + __CPROVER_assert(__CPROVER_rw_ok(out1, SIZE), "out1 is rw_ok"); + __CPROVER_assert(__CPROVER_rw_ok(out2, SIZE), "out2 is rw_ok"); + __CPROVER_assert( + !__CPROVER_same_object(out1, out2), "out1 and out2 are not aliased"); + return 0; +} diff --git a/regression/contracts-dfcc/test_is_fresh_replace_ensures_pass/test-enforce.desc b/regression/contracts-dfcc/test_is_fresh_replace_ensures_pass/test-enforce.desc new file mode 100644 index 00000000000..d96a03388ce --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_replace_ensures_pass/test-enforce.desc @@ -0,0 +1,16 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^\[foo.assigns.\d+\].*Check that \*out_ptr1 is assignable: SUCCESS$ +^\[foo.assigns.\d+\].*Check that \*out_ptr2 is assignable: SUCCESS$ +^\[foo.postcondition.\d+\].*Check ensures clause of contract contract::foo for function foo: SUCCESS$ +^\[main.assertion.\d+\].*out1 is rw_ok: SUCCESS$ +^\[main.assertion.\d+\].*out2 is rw_ok: SUCCESS$ +^\[main.assertion.\d+\].*out1 and out2 are not aliased: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that foo satisfies its contract and serves as a baseline for +test-replace.desc. diff --git a/regression/contracts-dfcc/test_is_fresh_replace_ensures_pass/test-replace.desc b/regression/contracts-dfcc/test_is_fresh_replace_ensures_pass/test-replace.desc new file mode 100644 index 00000000000..986e0697290 --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_replace_ensures_pass/test-replace.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^\[main.assertion.\d+\].*out1 is rw_ok: SUCCESS$ +^\[main.assertion.\d+\].*out2 is rw_ok: SUCCESS$ +^\[main.assertion.\d+\].*out1 and out2 are not aliased: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that when __CPROVER_is_fresh in preconditions replacement checks +succeed when separation and size are as expected. diff --git a/regression/contracts-dfcc/test_is_fresh_replace_requires_fail_separation/main.c b/regression/contracts-dfcc/test_is_fresh_replace_requires_fail_separation/main.c new file mode 100644 index 00000000000..19a5f9ad0fd --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_replace_requires_fail_separation/main.c @@ -0,0 +1,20 @@ +#define SIZE 10 + +void foo(char *in1, char *in2) + // clang-format off + __CPROVER_requires( + __CPROVER_is_fresh(in1, SIZE) && + __CPROVER_is_fresh(in2, SIZE)) +// clang-format on +{ +} + +int nondet_int(); + +int main() +{ + char in1[SIZE]; + char in2[SIZE]; + foo(nondet_int() ? in1 : in2, in2); + return 0; +} diff --git a/regression/contracts-dfcc/test_is_fresh_replace_requires_fail_separation/test.desc b/regression/contracts-dfcc/test_is_fresh_replace_requires_fail_separation/test.desc new file mode 100644 index 00000000000..bf396083c82 --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_replace_requires_fail_separation/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^\[foo.precondition.\d+\].*Check requires clause of contract contract::foo for function foo: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This test checks that when __CPROVER_is_fresh in preconditions replacement checks +fail when separation is not respected. diff --git a/regression/contracts-dfcc/test_is_fresh_replace_requires_fail_size/main.c b/regression/contracts-dfcc/test_is_fresh_replace_requires_fail_size/main.c new file mode 100644 index 00000000000..8cbc4aa8dde --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_replace_requires_fail_size/main.c @@ -0,0 +1,14 @@ +#define SIZE 10 + +void foo(char *in) __CPROVER_requires(__CPROVER_is_fresh(in, SIZE)) +{ +} + +int nondet_int(); +int main() +{ + char in1[SIZE]; + char in2[SIZE - 1]; + foo(nondet_int() ? in1 : in2); + return 0; +} diff --git a/regression/contracts-dfcc/test_is_fresh_replace_requires_fail_size/test.desc b/regression/contracts-dfcc/test_is_fresh_replace_requires_fail_size/test.desc new file mode 100644 index 00000000000..529aaeb4159 --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_replace_requires_fail_size/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^\[foo.precondition.\d+\].*Check requires clause of contract contract::foo for function foo: FAILURE$ +^EXIT=10$ +^SIGNAL=0$ +^VERIFICATION FAILED$ +-- +-- +This test checks that when __CPROVER_is_fresh in preconditions replacement checks +fails when size is not as expected. diff --git a/regression/contracts-dfcc/test_is_fresh_replace_requires_pass/main.c b/regression/contracts-dfcc/test_is_fresh_replace_requires_pass/main.c new file mode 100644 index 00000000000..d7fa64ecbde --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_replace_requires_pass/main.c @@ -0,0 +1,18 @@ +#define SIZE 10 + +void foo(char *in1, char *in2) + // clang-format off + __CPROVER_requires( + __CPROVER_is_fresh(in1, SIZE) && + __CPROVER_is_fresh(in2, SIZE)) +// clang-format on +{ +} + +int main() +{ + char in1[SIZE]; + char in2[SIZE]; + foo(in1, in2); + return 0; +} diff --git a/regression/contracts-dfcc/test_is_fresh_replace_requires_pass/test.desc b/regression/contracts-dfcc/test_is_fresh_replace_requires_pass/test.desc new file mode 100644 index 00000000000..ad0cc452125 --- /dev/null +++ b/regression/contracts-dfcc/test_is_fresh_replace_requires_pass/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^\[foo.precondition.\d+\].*Check requires clause of contract contract::foo for function foo: SUCCESS$ +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- +This test checks that when __CPROVER_is_fresh in preconditions replacement checks +succeed when separation and size are as expected. diff --git a/regression/contracts-dfcc/test_possibly_aliased_arguments/main.c b/regression/contracts-dfcc/test_possibly_aliased_arguments/main.c new file mode 100644 index 00000000000..6f168a148d9 --- /dev/null +++ b/regression/contracts-dfcc/test_possibly_aliased_arguments/main.c @@ -0,0 +1,26 @@ +#include +#include +#include + +// When enforced, this contract should pass + +// clang-format off +bool sub_ptr_values(int *x, int *y) + __CPROVER_requires( + __CPROVER_is_fresh(x, sizeof(int)) && + (y == x || __CPROVER_is_fresh(y, sizeof(int))) + ) + __CPROVER_ensures( + __CPROVER_return_value == (*x - *y) + ) +// clang-format on +{ + return (*x - *y); +} + +// A function that uses `sub_ptr_values` +void main() +{ + int *n = malloc(sizeof(int)); + int diff = sub_ptr_values(n, n); +} diff --git a/regression/contracts-dfcc/test_possibly_aliased_arguments/test.desc b/regression/contracts-dfcc/test_possibly_aliased_arguments/test.desc new file mode 100644 index 00000000000..70c2d22a7d9 --- /dev/null +++ b/regression/contracts-dfcc/test_possibly_aliased_arguments/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--dfcc main --replace-call-with-contract sub_ptr_values +^EXIT=0$ +^SIGNAL=0$ +^\[sub_ptr_values.precondition.\d+\] line \d+ Check requires clause of contract contract::sub_ptr_values for function sub_ptr_values: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether __CPROVER_is_fresh can be used conditionally, +in order to allow possibly-aliased arguments. diff --git a/regression/contracts-dfcc/test_scalar_memory_enforce/main.c b/regression/contracts-dfcc/test_scalar_memory_enforce/main.c new file mode 100644 index 00000000000..b22c93fe00f --- /dev/null +++ b/regression/contracts-dfcc/test_scalar_memory_enforce/main.c @@ -0,0 +1,37 @@ +#include +#include + +bool ptr_ok(int *x) +{ + return (*x < 5); +} + +bool return_ok(int ret_value, int *x) +{ + int a = *x; + return (ret_value == (*x + 5)); +} + +// clang-format off +int foo(int *x) + __CPROVER_assigns(*x) + __CPROVER_requires( + __CPROVER_is_fresh(x, sizeof(int)) && + *x > 0 && + ptr_ok(x)) + __CPROVER_ensures( + !ptr_ok(x) && + !__CPROVER_is_fresh(x, sizeof(int)) && + return_ok(__CPROVER_return_value, x)) +// clang-format on +{ + *x = *x + 4; + return (*x + 5); +} + +int main() +{ + int *n; + int o = foo(n); + return 0; +} diff --git a/regression/contracts-dfcc/test_scalar_memory_enforce/test.desc b/regression/contracts-dfcc/test_scalar_memory_enforce/test.desc new file mode 100644 index 00000000000..73989a016f2 --- /dev/null +++ b/regression/contracts-dfcc/test_scalar_memory_enforce/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract foo +\[foo.postcondition.\d+\].*Check ensures clause of contract contract::foo for function foo: SUCCESS +\[foo.assigns.\d+\].*Check that \*x is assignable: SUCCESS +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +Checks whether __CPROVER_is_fresh works properly for scalars (enforce context). +It tests both positive and negative cases for __CPROVER_is_fresh. diff --git a/regression/contracts-dfcc/test_scalar_memory_replace/main.c b/regression/contracts-dfcc/test_scalar_memory_replace/main.c new file mode 100644 index 00000000000..888731fb375 --- /dev/null +++ b/regression/contracts-dfcc/test_scalar_memory_replace/main.c @@ -0,0 +1,41 @@ +#include +#include +#include + +bool ptr_ok(int *x) +{ + return (*x < 5); +} + +bool return_ok(int ret_value, int *x) +{ + int a = *x; + return (ret_value == (*x + 5)); +} + +// clang-format off +int foo(int *x) + __CPROVER_assigns(*x) + __CPROVER_requires( + __CPROVER_is_fresh(x, sizeof(int)) && + *x > 0 && + ptr_ok(x)) + __CPROVER_ensures( + !ptr_ok(x) && + !__CPROVER_is_fresh(x, sizeof(int)) && + return_ok(__CPROVER_return_value, x)) +// clang-format on +{ + *x = *x + 4; + return (*x + 5); +} + +int main() +{ + int *n = malloc(sizeof(int)); + assert(__CPROVER_r_ok(n, sizeof(int))); + *n = 3; + int o = foo(n); + assert(o >= 10 && o == *n + 5); + return 0; +} diff --git a/regression/contracts-dfcc/test_scalar_memory_replace/test.desc b/regression/contracts-dfcc/test_scalar_memory_replace/test.desc new file mode 100644 index 00000000000..7458eeed320 --- /dev/null +++ b/regression/contracts-dfcc/test_scalar_memory_replace/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^EXIT=0$ +^SIGNAL=0$ +^\[foo.precondition.\d+\] line \d+ Check requires clause of contract contract::foo for function foo: SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion \_\_CPROVER\_r\_ok\(n, sizeof\(int\)\): SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion o >\= 10 \&\& o \=\= \*n \+ 5: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether __CPROVER_is_fresh works properly for scalars (replace context). +It tests both positive and negative cases for __CPROVER_is_fresh. diff --git a/regression/contracts-dfcc/test_struct_enforce/main.c b/regression/contracts-dfcc/test_struct_enforce/main.c new file mode 100644 index 00000000000..7ea407d7af0 --- /dev/null +++ b/regression/contracts-dfcc/test_struct_enforce/main.c @@ -0,0 +1,30 @@ +#include +#include + +struct bar +{ + int baz; + unsigned int qux; +}; + +// clang-format off +int foo(struct bar *x) + __CPROVER_assigns(*x) + __CPROVER_requires( + __CPROVER_is_fresh(x, sizeof(struct bar))) + __CPROVER_ensures( + __CPROVER_return_value == (x->baz + x->qux)) +// clang-format on +{ + x->baz = 5; + x->qux = 5; + return (x->baz + x->qux); +} + +int main() +{ + struct bar *x; + int rval = foo(x); + assert(rval == 10); + return 0; +} diff --git a/regression/contracts-dfcc/test_struct_enforce/test.desc b/regression/contracts-dfcc/test_struct_enforce/test.desc new file mode 100644 index 00000000000..80ace046dec --- /dev/null +++ b/regression/contracts-dfcc/test_struct_enforce/test.desc @@ -0,0 +1,14 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +\[foo.postcondition.\d+\] line \d+ Check ensures clause of contract contract::foo for function foo: SUCCESS$ +\[foo.assigns.\d+\] line \d+ Check that x->baz is assignable: SUCCESS +\[foo.assigns.\d+\] line \d+ Check that x->qux is assignable: SUCCESS +\[main.assertion.\d+\] line \d+ assertion rval \=\= 10: SUCCESS +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether assuming __CPROVER_is_fresh will guarantee a new freshly +allocated pointer (no aliasing) for structs. diff --git a/regression/contracts-dfcc/test_struct_member_enforce/main.c b/regression/contracts-dfcc/test_struct_member_enforce/main.c new file mode 100644 index 00000000000..1133ef091cd --- /dev/null +++ b/regression/contracts-dfcc/test_struct_member_enforce/main.c @@ -0,0 +1,30 @@ +#include + +struct string +{ + int len; + char *str; +}; + +// clang-format off +int foo(struct string *x) + __CPROVER_assigns(x->str[x->len-1]) + __CPROVER_requires( + x->len == 128 && + __CPROVER_is_fresh(x->str, x->len * sizeof(char))) + __CPROVER_ensures( + __CPROVER_return_value == 128 && + x->str[x->len - 1] == '\0') +// clang-format on +{ + x->str[x->len - 1] = '\0'; + return x->len; +} + +int main() +{ + struct string x; + int rval = foo(&x); + assert(rval == 128); + return 0; +} diff --git a/regression/contracts-dfcc/test_struct_member_enforce/test.desc b/regression/contracts-dfcc/test_struct_member_enforce/test.desc new file mode 100644 index 00000000000..e9edcc77dae --- /dev/null +++ b/regression/contracts-dfcc/test_struct_member_enforce/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=0$ +^SIGNAL=0$ +\[foo.postcondition.\d+\] line \d+ Check ensures clause of contract contract::foo for function foo: SUCCESS$ +\[foo.assigns.\d+\] line \d+ Check that x->str\[\(.*\)\(x->len - 1\)\] is assignable: SUCCESS +\[main.assertion.\d+\] line \d+ assertion rval \=\= 128: SUCCESS +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether assuming __CPROVER_is_fresh will guarantee a new freshly +allocated pointer (no aliasing) for struct members. diff --git a/regression/contracts-dfcc/test_struct_replace/main.c b/regression/contracts-dfcc/test_struct_replace/main.c new file mode 100644 index 00000000000..b5e1a50df1a --- /dev/null +++ b/regression/contracts-dfcc/test_struct_replace/main.c @@ -0,0 +1,35 @@ +#include +#include + +struct bar +{ + int baz; + unsigned int qux; +}; + +// clang-format off +int foo(struct bar *x, struct bar *y) + __CPROVER_assigns(*x, *y) + __CPROVER_requires( + __CPROVER_is_fresh(x, sizeof(struct bar)) && + __CPROVER_is_fresh(y, sizeof(struct bar))) + __CPROVER_ensures( + __CPROVER_return_value == (x->baz + x->qux) && + *x == *y) +// clang-format on +{ + x->baz = 5; + x->qux = 5; + *y = *x; + return (x->baz + x->qux); +} + +int main() +{ + struct bar *x = malloc(sizeof(*x)); + struct bar *y = malloc(sizeof(*y)); + int rval = foo(x, y); + assert(rval == x->baz + x->qux); + assert(*x == *y); + return 0; +} diff --git a/regression/contracts-dfcc/test_struct_replace/test.desc b/regression/contracts-dfcc/test_struct_replace/test.desc new file mode 100644 index 00000000000..f167b955ac7 --- /dev/null +++ b/regression/contracts-dfcc/test_struct_replace/test.desc @@ -0,0 +1,13 @@ +CORE +main.c +--dfcc main --replace-call-with-contract foo +^EXIT=0$ +^SIGNAL=0$ +^\[foo.precondition.\d+\] line \d+ Check requires clause of contract contract::foo for function foo: SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion rval \=\= x->baz \+ x->qux: SUCCESS$ +^\[main.assertion.\d+\] line \d+ assertion \*x \=\= \*y: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +-- +-- +Checks whether asserting __CPROVER_is_fresh will guarantee that objects +mapped to distinct objects (for structs). diff --git a/regression/contracts-dfcc/trivial_contract_enforce/main.c b/regression/contracts-dfcc/trivial_contract_enforce/main.c new file mode 100644 index 00000000000..7c566446339 --- /dev/null +++ b/regression/contracts-dfcc/trivial_contract_enforce/main.c @@ -0,0 +1,14 @@ +#include +#include + +int foo(int *x) __CPROVER_requires(x != NULL) +{ + return *x + 5; +} + +int main() +{ + int n = 10; + assert(foo(&n) != 15); + return 0; +} diff --git a/regression/contracts-dfcc/trivial_contract_enforce/test.desc b/regression/contracts-dfcc/trivial_contract_enforce/test.desc new file mode 100644 index 00000000000..2c032a54dfb --- /dev/null +++ b/regression/contracts-dfcc/trivial_contract_enforce/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=10$ +^SIGNAL=0$ +^\[main.assertion.\d+\] line \d+ assertion foo\(\&n\) != 15: FAILURE$ +^VERIFICATION FAILED$ +-- +-- +Check whether CBMC doesn't crash when enforcing trivial contracts, i.e., +the postcondition is true (default when missing) and therefore there is +nothing to check/assert. diff --git a/regression/contracts-dfcc/trivial_contract_replace/main.c b/regression/contracts-dfcc/trivial_contract_replace/main.c new file mode 100644 index 00000000000..6e6cb282db6 --- /dev/null +++ b/regression/contracts-dfcc/trivial_contract_replace/main.c @@ -0,0 +1,13 @@ +#include + +int foo(int *x) __CPROVER_ensures(__CPROVER_return_value == *x + 5) +{ + return *x + 5; +} + +int main() +{ + int n = 10; + assert(foo(&n) != 15); + return 0; +} diff --git a/regression/contracts-dfcc/trivial_contract_replace/test.desc b/regression/contracts-dfcc/trivial_contract_replace/test.desc new file mode 100644 index 00000000000..83b79463322 --- /dev/null +++ b/regression/contracts-dfcc/trivial_contract_replace/test.desc @@ -0,0 +1,12 @@ +CORE +main.c +--dfcc main --enforce-contract foo +^EXIT=10$ +^SIGNAL=0$ +^\[main.assertion.\d+\] line \d+ assertion foo\(\&n\) != 15: FAILURE$ +^VERIFICATION FAILED$ +-- +-- +Check whether CBMC doesn't crash when replacing trivial contracts, i.e., +the precondition is true (default when missing) and therefore there is +nothing to check/assert. diff --git a/regression/contracts-dfcc/typed_target_fail_wrong_nof_operand/main.c b/regression/contracts-dfcc/typed_target_fail_wrong_nof_operand/main.c new file mode 100644 index 00000000000..b4c3509a20e --- /dev/null +++ b/regression/contracts-dfcc/typed_target_fail_wrong_nof_operand/main.c @@ -0,0 +1,10 @@ +int foo(int x, int y) __CPROVER_assigns(__CPROVER_typed_target(x, y)) +{ + return 0; +} + +int main() +{ + int ret = foo(1, 2); + return 0; +} diff --git a/regression/contracts-dfcc/typed_target_fail_wrong_nof_operand/test.desc b/regression/contracts-dfcc/typed_target_fail_wrong_nof_operand/test.desc new file mode 100644 index 00000000000..2bb63198217 --- /dev/null +++ b/regression/contracts-dfcc/typed_target_fail_wrong_nof_operand/test.desc @@ -0,0 +1,11 @@ +CORE +main.c +--enforce-contract foo +^main.c.*error: expected 1 argument for __CPROVER_typed_target, found 2$ +^CONVERSION ERROR$ +^EXIT=(1|64)$ +^SIGNAL=0$ +-- +-- +This test checks that incorrect uses of __CPROVER_typed_target are detected and +rejected. diff --git a/regression/contracts-dfcc/typed_target_pointer/main.c b/regression/contracts-dfcc/typed_target_pointer/main.c new file mode 100644 index 00000000000..c38602a9040 --- /dev/null +++ b/regression/contracts-dfcc/typed_target_pointer/main.c @@ -0,0 +1,13 @@ +int foo(int *x, int *y) + __CPROVER_assigns(__CPROVER_typed_target(x), __CPROVER_typed_target(*y)) +{ + return 0; +} + +int main() +{ + int x; + int y; + int ret = foo(&x, &y); + return 0; +} diff --git a/regression/contracts-dfcc/typed_target_pointer/test.desc b/regression/contracts-dfcc/typed_target_pointer/test.desc new file mode 100644 index 00000000000..10f4d922ccb --- /dev/null +++ b/regression/contracts-dfcc/typed_target_pointer/test.desc @@ -0,0 +1,14 @@ +CORE +main.c +--enforce-contract foo +^\[foo.assigns.\d+\].* Check that __CPROVER_assignable\(\(void \*\)&x, .*, TRUE\) is valid: SUCCESS$ +^\[foo.assigns.\d+\].* Check that __CPROVER_assignable\(\(void \*\)&\(\*y\), .*, FALSE\) is valid: SUCCESS$ +^VERIFICATION SUCCESSFUL$ +^EXIT=0$ +^SIGNAL=0$ +-- +-- +This test checks __CPROVER_typed_target calls with pointer arguments +get translated to __CPROVER_assignable(x, ... , TRUE), +and that calls with non pointer arguments get translated to +__CPROVER_assignable(x, ... , FALSE). diff --git a/regression/contracts-dfcc/used_return_value/main.c b/regression/contracts-dfcc/used_return_value/main.c new file mode 100644 index 00000000000..ce94428e5f3 --- /dev/null +++ b/regression/contracts-dfcc/used_return_value/main.c @@ -0,0 +1,17 @@ +#include +#include + +int get_at_idx(int const *const arr, const size_t len, const size_t idx) + __CPROVER_requires(__CPROVER_r_ok(arr, len) && idx < len) + __CPROVER_ensures(__CPROVER_return_value == arr[idx]) +{ + return arr[idx]; +} + +void main() +{ + int a[5] = {0}; + a[3] = 7; + int x = get_at_idx(a, 5, 3); + assert(x == 7); +} diff --git a/regression/contracts-dfcc/used_return_value/test.desc b/regression/contracts-dfcc/used_return_value/test.desc new file mode 100644 index 00000000000..5a2cacc1ee1 --- /dev/null +++ b/regression/contracts-dfcc/used_return_value/test.desc @@ -0,0 +1,8 @@ +CORE +main.c +--dfcc main --replace-call-with-contract get_at_idx +^EXIT=0$ +^SIGNAL=0$ +^VERIFICATION SUCCESSFUL$ +-- +-- diff --git a/src/ansi-c/library/cprover_contracts.c b/src/ansi-c/library/cprover_contracts.c new file mode 100644 index 00000000000..62297907cf8 --- /dev/null +++ b/src/ansi-c/library/cprover_contracts.c @@ -0,0 +1,1454 @@ +/// \file cprover_contracts.c +/// \brief Types and functions for dynamic frames instrumentation in contracts. + +/* FUNCTION: __CPROVER_contracts_library */ + +#ifndef __CPROVER_contracts_library_defined +#define __CPROVER_contracts_library_defined + +// external dependencies +extern __CPROVER_size_t __CPROVER_max_malloc_size; +extern void *__CPROVER_alloca_object; +extern const void *__CPROVER_deallocated; +extern const void *__CPROVER_new_object; +extern __CPROVER_bool __CPROVER_malloc_is_new_array; +int __builtin_clzll(unsigned long long); +char __VERIFIER_nondet_char(); +__CPROVER_bool __VERIFIER_nondet_CPROVER_bool(); + +/// \brief A conditionally writable range of bytes. +typedef struct +{ + /// \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 +{ + /// \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 +{ + /// \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 +{ + /// \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 Pointer to object set supporting the is_fresh predicate checks + /// (indexed mode) + __CPROVER_contracts_obj_set_ptr_t linked_is_fresh; + /// \brief Object set recording the is_fresh allocations in post conditions + /// (replacement mode only) + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + /// \brief Object set recording the deallocations (used by was_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; + +/// \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 +__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_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"); + return (__CPROVER_contracts_car_t){ + .is_writable = ptr != 0, .size = size, .lb = ptr, .ub = (char *)ptr + size}; +#pragma CPROVER check pop +} + +/// \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 +void __CPROVER_contracts_car_set_create( + __CPROVER_contracts_car_set_ptr_t set, + __CPROVER_size_t max_elems) +{ +__CPROVER_HIDE:; +#ifdef DFCC_DEBUG + __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); +} + +/// \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 +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_DEBUG + __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_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"); + __CPROVER_contracts_car_t *elem = set->elems + idx; + *elem = (__CPROVER_contracts_car_t){ + .is_writable = ptr != 0, .size = size, .lb = ptr, .ub = (char *)ptr + size}; +#pragma CPROVER check pop +} + +/// \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; + } +} + +/// \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 +__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; +} + +/// \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 +void __CPROVER_contracts_obj_set_create_indexed_by_object_id( + __CPROVER_contracts_obj_set_ptr_t set) +{ +__CPROVER_HIDE:; +#ifdef DFCC_DEBUG + __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 = (__CPROVER_contracts_obj_set_t){ + .max_elems = nof_objects, + .watermark = 0, + .nof_elems = 0, + .is_empty = 1, + .indexed_by_object_id = 1, + .elems = __CPROVER_allocate(nof_objects * sizeof(*(set->elems)), 1)}; +} + +/// \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. +void __CPROVER_contracts_obj_set_create_append( + __CPROVER_contracts_obj_set_ptr_t set, + __CPROVER_size_t max_elems) +{ +__CPROVER_HIDE:; +#ifdef DFCC_DEBUG + __CPROVER_assert( + __CPROVER_rw_ok(set, sizeof(__CPROVER_contracts_obj_set_t)), + "set writable"); +#endif + *set = (__CPROVER_contracts_obj_set_t){ + .max_elems = max_elems, + .watermark = 0, + .nof_elems = 0, + .is_empty = 1, + .indexed_by_object_id = 0, + .elems = __CPROVER_allocate(max_elems * sizeof(*(set->elems)), 1)}; +} + +/// @brief Releases resources used by \p set. +void __CPROVER_contracts_obj_set_release(__CPROVER_contracts_obj_set_ptr_t set) +{ +__CPROVER_HIDE:; +#ifdef DFCC_DEBUG + __CPROVER_assert( + __CPROVER_rw_ok(set, sizeof(__CPROVER_contracts_obj_set_t)), + "set readable"); + __CPROVER_assert(__CPROVER_rw_ok(&(set->elems), 0), "set->elems writable"); +#endif + __CPROVER_deallocate(set->elems); +} + +/// \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 +void __CPROVER_contracts_obj_set_add( + __CPROVER_contracts_obj_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); +#ifdef DFCC_DEBUG + __CPROVER_assert(set->indexed_by_object_id, "indexed by object id"); + __CPROVER_assert(object_id < set->max_elems, "no OOB access"); +#endif + set->nof_elems = set->elems[object_id] ? set->nof_elems : set->nof_elems + 1; + set->elems[object_id] = ptr; + set->is_empty = 0; +} + +/// \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 +void __CPROVER_contracts_obj_set_append( + __CPROVER_contracts_obj_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; +#ifdef DFCC_DEBUG + __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; +} + +/// \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 +void __CPROVER_contracts_obj_set_remove( + __CPROVER_contracts_obj_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); +#ifdef DFCC_DEBUG + __CPROVER_assert(set->indexed_by_object_id, "indexed by object id"); + __CPROVER_assert(object_id < set->max_elems, "no OOB access"); +#endif + 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; +} + +/// \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 +__CPROVER_bool __CPROVER_contracts_obj_set_contains( + __CPROVER_contracts_obj_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); +#ifdef DFCC_DEBUG + __CPROVER_assert(set->indexed_by_object_id, "indexed by object id"); + __CPROVER_assert(object_id < set->max_elems, "no OOB access"); +#endif + return set->elems[object_id] != 0; +} + +/// \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 +__CPROVER_bool __CPROVER_contracts_obj_set_contains_exact( + __CPROVER_contracts_obj_set_ptr_t set, + void *ptr) +{ +__CPROVER_HIDE:; + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); +#ifdef DFCC_DEBUG + __CPROVER_assert(set->indexed_by_object_id, "indexed by object id"); + __CPROVER_assert(object_id < set->max_elems, "no OOB access"); +#endif + return set->elems[object_id] == ptr; +} + +/// \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_DEBUG + __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)); + set->linked_is_fresh = 0; + set->linked_allocated = 0; + set->linked_deallocated = 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; +} + +/// \brief Releases resources used by \p set. +void __CPROVER_contracts_write_set_release( + __CPROVER_contracts_write_set_ptr_t set) +{ +__CPROVER_HIDE:; +#ifdef DFCC_DEBUG + __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"); +#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); + // do not free set->linked_is_fresh->elems or set->deallocated_linked->elems + // since they are owned by someone else. +} + +/// \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); +} + +/// \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_object_whole( + __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)); +} + +/// \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)); +} + +/// \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); +} + +/// \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_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); +#ifdef DFCC_DEBUG + // manually inlined below + __CPROVER_contracts_obj_set_add(&(set->contract_frees), ptr); + __CPROVER_assert(object_id < set->contract_frees.max_elems, "no OOB access"); +#else + set->contract_frees.nof_elems = (set->contract_frees.elems[object_id] != 0) + ? set->contract_frees.nof_elems + : set->contract_frees.nof_elems + 1; + set->contract_frees.elems[object_id] = ptr; + set->contract_frees.is_empty = 0; +#endif + + // append pointer if available +#ifdef DFCC_DEBUG + if(set->replacement) + __CPROVER_contracts_obj_set_append(&(set->contract_frees_replacement), ptr); +#else + if(set->replacement) + { + set->contract_frees_replacement.nof_elems = + set->contract_frees_replacement.watermark; + set->contract_frees_replacement + .elems[set->contract_frees_replacement.watermark] = ptr; + set->contract_frees_replacement.watermark += 1; + set->contract_frees_replacement.is_empty = 0; + } +#endif +} + +/// \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:; +#if DFCC_DEBUG + // call inlined below + __CPROVER_contracts_obj_set_add(&(set->allocated), ptr); +#else + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); + set->allocated.nof_elems = (set->allocated.elems[object_id] != 0) + ? set->allocated.nof_elems + : set->allocated.nof_elems + 1; + set->allocated.elems[object_id] = ptr; + set->allocated.is_empty = 0; +#endif +} + +/// \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:; +#ifdef DFCC_DEBUG + // manually inlined below + __CPROVER_contracts_obj_set_remove(&(set->allocated), ptr); +#else + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); + set->allocated.nof_elems = set->allocated.elems[object_id] + ? set->allocated.nof_elems - 1 + : set->allocated.nof_elems; + set->allocated.is_empty = set->allocated.nof_elems == 0; + set->allocated.elems[object_id] = 0; +#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_DEBUG + __CPROVER_assert(set->replacement == 0, "!replacement"); +#endif + +#if DFCC_DEBUG + // we record the deallocation to be able to evaluate was_freed post conditions + __CPROVER_contracts_obj_set_add(&(set->deallocated), ptr); + __CPROVER_contracts_obj_set_remove(&(set->allocated), ptr); + __CPROVER_contracts_obj_set_remove(&(set->contract_frees), ptr); + __CPROVER_contracts_car_set_remove(&(set->contract_assigns), ptr); + // Manually inlined below +#else + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); + + // __CPROVER_contracts_obj_set_add + set->deallocated.nof_elems = set->deallocated.elems[object_id] + ? set->deallocated.nof_elems + : set->deallocated.nof_elems + 1; + set->deallocated.elems[object_id] = ptr; + set->deallocated.is_empty = 0; + + // __CPROVER_contracts_obj_set_remove + set->allocated.nof_elems = set->allocated.elems[object_id] + ? set->allocated.nof_elems - 1 + : set->allocated.nof_elems; + set->allocated.is_empty = set->allocated.nof_elems == 0; + set->allocated.elems[object_id] = 0; + + // __CPROVER_contracts_obj_set_remove + set->contract_frees.nof_elems = set->contract_frees.elems[object_id] + ? set->contract_frees.nof_elems - 1 + : set->contract_frees.nof_elems; + set->contract_frees.is_empty = set->contract_frees.nof_elems == 0; + set->contract_frees.elems[object_id] = 0; + + // __CPROVER_contracts_car_set_remove + __CPROVER_size_t idx = set->contract_assigns.max_elems; + __CPROVER_contracts_car_t *elem = set->contract_assigns.elems; + while(idx != 0) + { + if(object_id == __CPROVER_POINTER_OBJECT(elem->lb)) + elem->is_writable = 0; + ++elem; + --idx; + } +#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 +__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; +} + +/// \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. +__CPROVER_bool __CPROVER_contracts_write_set_check_assignment( + __CPROVER_contracts_write_set_ptr_t set, + void *ptr, + __CPROVER_size_t size) +#if DFCC_DEBUG +// manually inlined below +{ +__CPROVER_HIDE:; + __CPROVER_assert(set->replacement == 0, "!replacement"); + __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 +{ +__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"); + + // the range is not writable + if(ptr == 0) + return 0; + + // is ptr pointing within some 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) <= offset) & + (__CPROVER_POINTER_OFFSET(ub) <= __CPROVER_POINTER_OFFSET(elem->ub)); + ++elem; + --idx; + } + return incl; +# pragma CPROVER check pop +} +#endif + +/// \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)); +} + +/// \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)); +} + +/// \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); +} + +/// \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)); +} + +/// \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_DEBUG + __CPROVER_assert(set->replacement == 0, "!replacement"); +#endif + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); + +#ifdef DFCC_DEBUG + __CPROVER_assert( + set->contract_frees.indexed_by_object_id, + "set->contract_frees is indexed by object id"); + __CPROVER_assert( + set->allocated.indexed_by_object_id, + "set->allocated is indexed by object id"); +#endif + return (ptr == 0) | (set->contract_frees.elems[object_id] == ptr) | + (set->allocated.elems[object_id] == ptr); +} + +/// \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 +__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_DEBUG + __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; +} + +/// \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 +__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_DEBUG + __CPROVER_assert( + reference->replacement == 0, "reference set in !replacement"); + __CPROVER_assert(candidate->replacement != 0, "candidate set in replacement"); + __CPROVER_assert( + reference->contract_frees.indexed_by_object_id, + "reference->contract_frees is indexed by object id"); + __CPROVER_assert( + reference->allocated.indexed_by_object_id, + "reference->allocated is indexed by object id"); +#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; + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); + all_incl &= (ptr == 0) | + (reference->contract_frees.elems[object_id] == ptr) | + (reference->allocated.elems[object_id] == ptr); + --idx; + ++current; + } + + return all_incl; +} + +/// \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); + +/// \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_DEBUG + __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" + // skip checks on r_ok, dynamic_object and pointer_offset + __CPROVER_bool preconditions = + (ptr == 0) | (__CPROVER_r_ok(ptr, 0) & __CPROVER_DYNAMIC_OBJECT(ptr) & + (__CPROVER_POINTER_OFFSET(ptr) == 0)); +#pragma CPROVER check pop + // If there is aliasing between the pointers in the freeable set, + // and we attempt to free again one of the already freed pointers, + // the r_ok condition above will fail, preventing us to deallocate + // the same pointer twice + if((ptr != 0) & preconditions & __VERIFIER_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; + } +} + +/// \brief Links \p is_fresh_set to +/// \p write_set->linked_is_fresh so that the is_fresh predicates +/// can be evaluated in requires and ensures clauses. +void __CPROVER_contracts_link_is_fresh( + __CPROVER_contracts_write_set_ptr_t write_set, + __CPROVER_contracts_obj_set_ptr_t is_fresh_set) +{ +__CPROVER_HIDE:; +#ifdef DFCC_DEBUG + __CPROVER_assert(write_set != 0, "write_set not NULL"); +#endif + if((is_fresh_set != 0)) + { + write_set->linked_is_fresh = is_fresh_set; + } + else + { + write_set->linked_is_fresh = 0; + } +} + +/// \brief Links \p write_set_to_link->allocated to +/// \p write_set_postconditions->linked_allocated so that allocations performed +/// by \ref __CPROVER_contracts_is_fresh when evaluating ensures clauses are +/// recorded in \p write_set_to_link. +void __CPROVER_contracts_link_allocated( + __CPROVER_contracts_write_set_ptr_t write_set_postconditions, + __CPROVER_contracts_write_set_ptr_t write_set_to_link) +{ +__CPROVER_HIDE:; +#ifdef DFCC_DEBUG + __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; + } +} + +/// \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_was_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_DEBUG + __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; + } +} + +/// \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); + +__CPROVER_bool __VERIFIER_nondet_bool(); +/// \brief Implementation of the `is_fresh` front-end predicate. +/// +/// 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_fresh` predicate +/// \param size Second argument of the `is_fresh` predicate +/// \param write_set Write set in which seen/allocated objects are recorded; +/// +/// \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->linked_is_fresh, 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 first computes wether \p *elem is in \p set->linked_is_fresh +/// and returns false if it is. Otherwise it records the object in +/// \p set->linked_is_fresh and returns the value of r_ok(*elem, size). +__CPROVER_bool __CPROVER_contracts_is_fresh( + void **elem, + __CPROVER_size_t size, + __CPROVER_contracts_write_set_ptr_t write_set) +{ + if(!write_set) + return __VERIFIER_nondet_bool(); +__CPROVER_HIDE:; +#ifdef DFCC_DEBUG + __CPROVER_assert( + __CPROVER_rw_ok(write_set, sizeof(__CPROVER_contracts_write_set_t)), + "set readable"); + __CPROVER_assert( + write_set->linked_is_fresh, "set->linked_is_fresh is not NULL"); +#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(write_set->assume_requires_ctx) + { +#ifdef DFCC_DEBUG + __CPROVER_assert( + (write_set->assert_requires_ctx == 0) & + (write_set->assume_ensures_ctx == 0) & + (write_set->assert_ensures_ctx == 0), + "only one context flag at a time"); +#endif + // pass a null write set pointer to the instrumented malloc + void *ptr = __CPROVER_contracts_malloc(size, 0); + *elem = ptr; + if(!ptr) + return 0; + // record fresh object in the object set +#ifdef DFCC_DEBUG + // manually inlined below + __CPROVER_contracts_obj_set_add(write_set->linked_is_fresh, ptr); +#else + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); + write_set->linked_is_fresh->nof_elems = + (write_set->linked_is_fresh->elems[object_id] != 0) + ? write_set->linked_is_fresh->nof_elems + : write_set->linked_is_fresh->nof_elems + 1; + write_set->linked_is_fresh->elems[object_id] = ptr; + write_set->linked_is_fresh->is_empty = 0; +#endif + return 1; + } + else if(write_set->assume_ensures_ctx) + { +#ifdef DFCC_DEBUG + __CPROVER_assert( + (write_set->assume_requires_ctx == 0) & + (write_set->assert_requires_ctx == 0) & + (write_set->assert_ensures_ctx == 0), + "only one context flag at a time"); +#endif + void *ptr = __CPROVER_contracts_malloc(size, 0); + *elem = ptr; + if(!ptr) + return 0; + // record fresh object in the caller's write set +#ifdef DFCC_DEBUG + __CPROVER_contracts_obj_set_add(write_set->linked_allocated, ptr); +#else + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); + write_set->linked_allocated->nof_elems = + (write_set->linked_allocated->elems[object_id] != 0) + ? write_set->linked_allocated->nof_elems + : write_set->linked_allocated->nof_elems + 1; + write_set->linked_allocated->elems[object_id] = ptr; + write_set->linked_allocated->is_empty = 0; +#endif + return 1; + } + else if(write_set->assert_requires_ctx | write_set->assert_ensures_ctx) + { +#ifdef DFCC_DEBUG + __CPROVER_assert( + (write_set->assume_requires_ctx == 0) & + (write_set->assume_ensures_ctx == 0), + "only one context flag at a time"); +#endif + __CPROVER_contracts_obj_set_ptr_t seen = write_set->linked_is_fresh; + void *ptr = *elem; + // null pointers or already seen pointers are not fresh +#ifdef DFCC_DEBUG + // manually inlined below + if((ptr == 0) || (__CPROVER_contracts_obj_set_contains(seen, ptr))) + return 0; +#else + if(ptr == 0) + return 0; + + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); + + if(seen->elems[object_id] != 0) + return 0; +#endif + // record fresh object in the object set +#ifdef DFCC_DEBUG + // manually inlined below + __CPROVER_contracts_obj_set_add(seen, ptr); +#else + seen->nof_elems = + (seen->elems[object_id] != 0) ? seen->nof_elems : seen->nof_elems + 1; + seen->elems[object_id] = ptr; + seen->is_empty = 0; +#endif + // check size + return __CPROVER_r_ok(ptr, size); + } + else + { + __CPROVER_assert( + 0, "__CPROVER_is_fresh is only called in requires or ensures clauses"); + __CPROVER_assume(0); + return 0; // to silence libcheck + } +#pragma CPROVER check pop +} + +/// \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:; + __CPROVER_contracts_car_t car = set->contract_assigns.elems[idx]; + if(car.is_writable) + return car.lb; + else + return (void *)0; +} + +/// \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_object_whole( + __CPROVER_contracts_write_set_ptr_t set, + __CPROVER_size_t idx) +{ +__CPROVER_HIDE:; + __CPROVER_assert(idx < set->contract_assigns.max_elems, "no OOB access"); + __CPROVER_contracts_car_t car = set->contract_assigns.elems[idx]; + if(car.is_writable) + __CPROVER_havoc_object(car.lb); +} + +/// \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_DEBUG + __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_slice(car.lb, car.size); +} + +/// \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; +} + +/// \brief Returns true iff the pointer \p ptr is found in \p set->deallocated. +__CPROVER_bool __CPROVER_contracts_was_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_was_freed is used only in ensures clauses"); + __CPROVER_assert( + (set->linked_deallocated != 0), "linked_deallocated is not null"); +#ifdef DFCC_DEBUG + // manually inlined below + return __CPROVER_contracts_obj_set_contains_exact( + set->linked_deallocated, ptr); +#else + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); + return set->linked_deallocated->elems[object_id] == ptr; +#endif +} + +/// \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 `was_freed(ptr)` holds as a post condition without causing a +/// contradiction. +void __CPROVER_contracts_check_replace_ensures_was_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_was_freed is used only in ensures clauses"); + + if(set->assume_ensures_ctx) + { +#ifdef DFCC_DEBUG + // manually inlined below + __CPROVER_assert( + __CPROVER_contracts_obj_set_contains_exact(&(set->contract_frees), ptr), + "assuming __CPROVER_was_freed(ptr) requires ptr to always exist in the " + "contract's frees clause"); +#else + __CPROVER_size_t object_id = __CPROVER_POINTER_OBJECT(ptr); + __CPROVER_assert( + set->contract_frees.elems[object_id] == ptr, + "assuming __CPROVER_was_freed(ptr) requires ptr to always exist in the " + "contract's frees clause"); +#endif + } +} +#endif diff --git a/src/doxyfile b/src/doxyfile index 0187757abd2..923fc3c0586 100644 --- a/src/doxyfile +++ b/src/doxyfile @@ -772,6 +772,7 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = . ../doc ../jbmc/src ../unit/testing-utils ../jbmc/unit/java-testing-utils +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 diff --git a/src/goto-instrument/Makefile b/src/goto-instrument/Makefile index cb37a281bfe..1784b8a9488 100644 --- a/src/goto-instrument/Makefile +++ b/src/goto-instrument/Makefile @@ -17,6 +17,17 @@ SRC = accelerate/accelerate.cpp \ branch.cpp \ call_sequences.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/dynamic-frames/dfcc_instrument.cpp \ + contracts/dynamic-frames/dfcc_spec_functions.cpp \ + contracts/dynamic-frames/dfcc_contract_functions.cpp \ + contracts/dynamic-frames/dfcc_wrapper_program.cpp \ + contracts/dynamic-frames/dfcc_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/cfg_info.h b/src/goto-instrument/contracts/cfg_info.h index e1723ed7141..c8d1c5f1482 100644 --- a/src/goto-instrument/contracts/cfg_info.h +++ b/src/goto-instrument/contracts/cfg_info.h @@ -193,4 +193,45 @@ 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 + goto_program.get_decl_identifiers(locals); + + // collect dirty locals + goto_functiont goto_function; + goto_function.body.copy_from(goto_program); + + dirtyt is_dirty(goto_function); + const 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::set locals; + std::set dirty; +}; + #endif diff --git a/src/goto-instrument/contracts/contracts.cpp b/src/goto-instrument/contracts/contracts.cpp index 8cd903aa6b3..58512048d59 100644 --- a/src/goto-instrument/contracts/contracts.cpp +++ b/src/goto-instrument/contracts/contracts.cpp @@ -71,7 +71,7 @@ void code_contractst::check_apply_loop_contracts( // replace bound variables by fresh instances if(has_subexpr(invariant, ID_exists) || has_subexpr(invariant, ID_forall)) - add_quantified_variable(invariant, mode); + add_quantified_variable(symbol_table, invariant, mode); // instrument // @@ -132,6 +132,7 @@ void code_contractst::check_apply_loop_contracts( // We find and replace all "__CPROVER_loop_entry" subexpressions in invariant. std::map history_var_map; replace_history_parameter( + symbol_table, invariant, history_var_map, loop_head_location, @@ -485,169 +486,11 @@ void code_contractst::check_apply_loop_contracts( } } -void code_contractst::add_quantified_variable( - exprt &expression, - const irep_idt &mode) -{ - if(expression.id() == ID_not || expression.id() == ID_typecast) - { - // For unary connectives, recursively check for - // nested quantified formulae in the term - auto &unary_expression = to_unary_expr(expression); - add_quantified_variable(unary_expression.op(), mode); - } - if(expression.id() == ID_notequal || expression.id() == ID_implies) - { - // For binary connectives, recursively check for - // nested quantified formulae in the left and right terms - auto &binary_expression = to_binary_expr(expression); - add_quantified_variable(binary_expression.lhs(), mode); - add_quantified_variable(binary_expression.rhs(), mode); - } - if(expression.id() == ID_if) - { - // For ternary connectives, recursively check for - // nested quantified formulae in all three terms - auto &if_expression = to_if_expr(expression); - add_quantified_variable(if_expression.cond(), mode); - add_quantified_variable(if_expression.true_case(), mode); - add_quantified_variable(if_expression.false_case(), mode); - } - if(expression.id() == ID_and || expression.id() == ID_or) - { - // For multi-ary connectives, recursively check for - // nested quantified formulae in all terms - auto &multi_ary_expression = to_multi_ary_expr(expression); - for(auto &operand : multi_ary_expression.operands()) - { - add_quantified_variable(operand, mode); - } - } - else if(expression.id() == ID_exists || expression.id() == ID_forall) - { - // When a quantifier expression is found, create a fresh symbol for each - // quantified variable and rewrite the expression to use those fresh - // symbols. - auto &quantifier_expression = to_quantifier_expr(expression); - std::vector fresh_variables; - fresh_variables.reserve(quantifier_expression.variables().size()); - for(const auto &quantified_variable : quantifier_expression.variables()) - { - // 1. create fresh symbol - symbolt new_symbol = new_tmp_symbol( - quantified_variable.type(), - quantified_variable.source_location(), - mode, - symbol_table); - - // 2. add created fresh symbol to expression map - fresh_variables.push_back(new_symbol.symbol_expr()); - } - - // use fresh symbols - exprt where = quantifier_expression.instantiate(fresh_variables); - - // recursively check for nested quantified formulae - add_quantified_variable(where, mode); - - // replace previous variables and body - quantifier_expression.variables() = fresh_variables; - quantifier_expression.where() = std::move(where); - } -} - -void code_contractst::replace_history_parameter( - exprt &expr, - std::map ¶meter2history, - source_locationt location, - const irep_idt &mode, - goto_programt &history, - const irep_idt &id) -{ - for(auto &op : expr.operands()) - { - replace_history_parameter( - op, parameter2history, location, mode, history, id); - } - - if(expr.id() == ID_old || expr.id() == ID_loop_entry) - { - const auto ¶meter = to_history_expr(expr, id).expression(); - - const auto &id = parameter.id(); - if( - id == ID_dereference || id == ID_member || id == ID_symbol || - id == ID_ptrmember || id == ID_constant || id == ID_typecast || - id == ID_index) - { - auto it = parameter2history.find(parameter); - - if(it == parameter2history.end()) - { - // 0. Create a skip target to jump to, if the parameter is invalid - goto_programt skip_program; - const auto skip_target = - skip_program.add(goto_programt::make_skip(location)); - - // 1. Create a temporary symbol expression that represents the - // history variable - symbol_exprt tmp_symbol = - new_tmp_symbol(parameter.type(), location, mode, symbol_table) - .symbol_expr(); - - // 2. Associate the above temporary variable to it's corresponding - // expression - parameter2history[parameter] = tmp_symbol; - - // 3. Add the required instructions to the instructions list - // 3.1. Declare the newly created temporary variable - history.add(goto_programt::make_decl(tmp_symbol, location)); - - // 3.2. Skip storing the history if the expression is invalid - history.add(goto_programt::make_goto( - skip_target, - not_exprt{all_dereferences_are_valid(parameter, ns)}, - location)); - - // 3.3. Add an assignment such that the value pointed to by the new - // temporary variable is equal to the value of the corresponding - // parameter - history.add( - goto_programt::make_assignment(tmp_symbol, parameter, location)); - - // 3.4. Add a skip target - history.destructive_append(skip_program); - } - - expr = parameter2history[parameter]; - } - else - { - log.error() << "Tracking history of " << parameter.id() - << " expressions is not supported yet." << messaget::eom; - throw 0; - } - } -} - -void code_contractst::generate_history_variables_initialization( - exprt &clause, - const irep_idt &mode, - goto_programt &program) -{ - std::map parameter2history; - goto_programt history; - // Find and replace "old" expression in the "expression" variable - replace_history_parameter( - clause, parameter2history, clause.source_location(), mode, history, ID_old); - // Add all the history variable initialization instructions - program.destructive_append(history); -} - /// This function generates instructions for all contract constraint, i.e., /// assumptions and assertions based on requires and ensures clauses. static void generate_contract_constraints( - code_contractst &contract, + symbol_tablet &symbol_table, + goto_convertt &converter, exprt &instantiated_clause, const irep_idt &mode, const std::function &is_fresh_update, @@ -658,19 +501,17 @@ static void generate_contract_constraints( has_subexpr(instantiated_clause, ID_exists) || has_subexpr(instantiated_clause, ID_forall)) { - contract.add_quantified_variable(instantiated_clause, mode); + add_quantified_variable(symbol_table, instantiated_clause, mode); } goto_programt constraint; if(location.get_property_class() == ID_assume) { - contract.get_converter().goto_convert( - code_assumet(instantiated_clause), constraint, mode); + converter.goto_convert(code_assumet(instantiated_clause), constraint, mode); } else { - contract.get_converter().goto_convert( - code_assertt(instantiated_clause), constraint, mode); + converter.goto_convert(code_assertt(instantiated_clause), constraint, mode); } constraint.instructions.back().source_location_nonconst() = location; is_fresh_update(constraint); @@ -804,7 +645,8 @@ void code_contractst::apply_function_contract( .append(function.c_str())); _location.set_property_class(ID_precondition); generate_contract_constraints( - *this, + symbol_table, + converter, instantiated_clause, mode, [&is_fresh](goto_programt &requires) { @@ -833,7 +675,7 @@ void code_contractst::apply_function_contract( to_lambda_expr(clause).application(instantiation_values); instantiated_clause.add_source_location() = clause.source_location(); generate_history_variables_initialization( - instantiated_clause, mode, new_program); + symbol_table, instantiated_clause, mode, new_program); instantiated_ensures_clauses.push_back(instantiated_clause); } @@ -893,7 +735,8 @@ void code_contractst::apply_function_contract( _location.set_comment("Assume ensures clause"); _location.set_property_class(ID_assume); generate_contract_constraints( - *this, + symbol_table, + converter, clause, mode, [&is_fresh](goto_programt &ensures) { is_fresh.update_ensures(ensures); }, @@ -1473,7 +1316,8 @@ void code_contractst::add_contract_check( _location.set_comment("Assume requires clause"); _location.set_property_class(ID_assume); generate_contract_constraints( - *this, + symbol_table, + converter, instantiated_clause, function_symbol.mode, [&visitor](goto_programt &requires) { @@ -1491,7 +1335,7 @@ void code_contractst::add_contract_check( to_lambda_expr(clause).application(instantiation_values); instantiated_clause.add_source_location() = clause.source_location(); generate_history_variables_initialization( - instantiated_clause, function_symbol.mode, check); + symbol_table, instantiated_clause, function_symbol.mode, check); instantiated_ensures_clauses.push_back(instantiated_clause); } @@ -1515,7 +1359,8 @@ void code_contractst::add_contract_check( _location.set_comment("Check ensures clause"); _location.set_property_class(ID_postcondition); generate_contract_constraints( - *this, + symbol_table, + converter, clause, function_symbol.mode, [&visitor](goto_programt &ensures) { visitor.update_ensures(ensures); }, diff --git a/src/goto-instrument/contracts/contracts.h b/src/goto-instrument/contracts/contracts.h index 6534eca8bf7..6c5839f1117 100644 --- a/src/goto-instrument/contracts/contracts.h +++ b/src/goto-instrument/contracts/contracts.h @@ -195,35 +195,6 @@ class code_contractst const irep_idt &wrapper_function, const irep_idt &mangled_function, goto_programt &dest); - - /// This function recursively searches \p expression to find nested or - /// non-nested quantified expressions. When a quantified expression is found, - /// a fresh quantified variable is added to the symbol table and \p expression - /// is updated to use this fresh variable. - void add_quantified_variable(exprt &expression, const irep_idt &mode); - - /// This function recursively identifies the "old" expressions within expr - /// and replaces them with correspoding history variables. - void replace_history_parameter( - exprt &expr, - std::map ¶meter2history, - source_locationt location, - const irep_idt &mode, - goto_programt &history, - const irep_idt &id); - - /// This function generates all the instructions required to initialize - /// history variables. - void generate_history_variables_initialization( - exprt &clause, - const irep_idt &mode, - goto_programt &program); - - // for "auxiliary" functions generate contract constrainst - goto_convertt &get_converter() - { - return converter; - } }; #endif // CPROVER_GOTO_INSTRUMENT_CONTRACTS_CONTRACTS_H diff --git a/doc/cprover-manual/contracts.md b/src/goto-instrument/contracts/doc/contracts.md similarity index 77% rename from doc/cprover-manual/contracts.md rename to src/goto-instrument/contracts/doc/contracts.md index 7741134f128..489ef2e9dc5 100644 --- a/doc/cprover-manual/contracts.md +++ b/src/goto-instrument/contracts/doc/contracts.md @@ -1,6 +1,4 @@ -[CPROVER Manual TOC](../) - -# Contracts +# Code Contracts in CBMC {#contracts-mainpage} Code contracts in CBMC provide way to safely abstract parts of a program, typically in order to accelerate the verification process. @@ -14,10 +12,8 @@ notes [Contract-based Design](https://www.georgefairbanks.com/york-university-contract-based-design-2021) by George Fairbanks. -CBMC currently supports contracts on functions and loops: - -- [Function Contracts](../contracts/functions/) -- [Loop Contracts](../contracts/loops/) - For extra steps required to compositionally reason about file-local functions -[see](static-functions/). +[please consult this link](todo-link-to-cprover-manual-static-functions). + +- @subpage contracts-user +- @subpage contracts-dev diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev-arch.md b/src/goto-instrument/contracts/doc/developer/contracts-dev-arch.md new file mode 100644 index 00000000000..76df7974016 --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev-arch.md @@ -0,0 +1,48 @@ +# Code Contracts Software Architecture {#contracts-dev-arch} + +Back to @ref contracts-dev + +@tableofcontents + +## Architecture Overview + +The code implementing @ref contracts-dev-spec is found in @ref dfcc-module + +We go over each class and explain how it works in relation to others. + +The @ref dfcct class is the main entry point into the transformation. + +The method @ref dfcct#transform_goto_model first separates the functions of the goto model in different groups (functions to instrument, pure contract symbols from which to generate code, functions to check against contracts, functions to replace with contracts) and applies the transformation +to the whole goto model, by scheduling the translation passes +described in @ref contracts-dev-spec : + +1. @ref contracts-dev-spec-codegen is applied to all contracts to check or replace; +2. @ref contracts-dev-spec-dfcc is applied to all library or user-defined goto functions; +3. @ref contracts-dev-spec-harness is applied to the harness function; +4. @ref contracts-dev-spec-contract-checking is applied to the function to be checked against a contract; +5. @ref contracts-dev-spec-contract-replacement is applied to each function to be replaced by a contract; + +Each of these translation passes is implemented in a specific class: + + Class | Specification + :-------------------------------|:--------------------------------------- + @ref dfcc_instrumentt | Implements @ref contracts-dev-spec-dfcc for @ref goto_functiont, @ref goto_programt, or subsequences of instructions of @ref goto_programt + @ref dfcc_is_fresht | Implements @ref contracts-dev-spec-is-fresh + @ref dfcc_is_freeablet | Implements @ref contracts-dev-spec-is-freeable + @ref dfcc_spec_functionst | Implements @ref contracts-dev-spec-spec-rewriting + @ref dfcc_wrapper_programt | Implements @ref contracts-dev-spec-contract-checking for contracts + ^ | Implements @ref contracts-dev-spec-contract-replacement for contracts + @ref dfcc_contract_handlert | Implements @ref contracts-dev-spec-codegen for contracts + ^ | Implements the interface @ref dfcc_contract_handlert for contract by delegating operations to @ref dfcc_wrapper_programt + @ref dfcc_swap_and_wrapt | Implements @ref contracts-dev-spec-contract-checking by delegating basic operations to @ref dfcc_contract_handlert + ^ | Implements @ref contracts-dev-spec-contract-replacement by delegating basic operations to @ref dfcc_contract_handlert + ^ | Implements @ref contracts-dev-spec-contract-checking-rec by delegating basic operations to @ref dfcc_contract_handlert + +The following classes contain utility methods: +- @ref dfcc_utilst : Provides basic utility methods to the other classes such as + locating a function symbol, adding a parameter to a function symbol, cloning + or renaming a function symbol, creating fresh symbols, inlining a function + body, etc. +- @ref dfcc_libraryt : Provides a C++ interface to access C library functions + defined in @ref cprover_contracts.c. Using this class it is possible to load + the library symbols and post-process them with loop unrolling or inlining, etc. diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-codegen.md b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-codegen.md new file mode 100644 index 00000000000..037f3ffed6e --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-codegen.md @@ -0,0 +1,143 @@ +# Generating GOTO Functions From Contract Clauses {#contracts-dev-spec-codegen} + +Back to top @ref contracts-dev-spec + +@tableofcontents + +## Translating Assigns Clauses to GOTO Functions {#contracts-dev-spec-codegen-assigns} + + +Let's consider a contract `foo` (with corresponding pure contract +`contract::foo`) with a `__CPROVER_assigns(A)` clause. + +```c +ret_t foo(foo-parameters) __CPROVER_assigns(A); +``` +We know the elements of A only depend on `foo-parameters` and global variables. + +A new GOTO function with the following signature is generated: + +```c +void contract::foo::assigns(foo-parameters); +``` + +The body of the function generated from the elements of A as follows: +- For each target `cond: target` where the target is an lvalue expression: + ```c + DECL __cond: bool; + ASSIGN __cond = ; + IF !__cond GOTO SKIP_TARGET; + CALL __CPROVER_assignable(&target, sizeof(target), has_ptr_type(target)); + SKIP_TARGET: SKIP; + DEAD __cond; + ``` + where `has_ptr_type(target)` returns true if the target has a pointer-type; +- For each target `cond: f(parameters)`, where `f` is a void-typed function: + ```c + DECL __cond: bool; + ASSIGN __cond = ; + IF !__cond GOTO SKIP_TARGET; + CALL f(parameters); + SKIP_TARGET: SKIP; + DEAD __cond; + ``` + +Remark: The condition expressions needs to be goto_converted during translation +since they come directly from the front-end and are allowed to contain function +calls. + +The rewriting pass @ref contracts-dev-spec-spec-rewriting-assigns is applied +to transform the function into a function that accepts two +write set parameters: The first one is the write set that gets populated with +assignable locations specified by the function, the second is a write set that +is used by the function to check its side effects. + +```c +void contract::foo::assigns( + + // function parameters + foo-parameters, + + // write set to populate with new targets + __CPROVER_contracts_write_set_ptr_t __write_set_to_fill, + + // write set against which to check itself for unwanted side effects + __CPROVER_contracts_write_set_ptr_t __write_set_to_check); +``` + +The rewriting step @ref contracts-dev-spec-spec-rewriting-havoc is also applied +to the function in order to generate a havoc function that can be used to model +contract replacement: + +```c +void contract::foo::havoc(__CPROVER_contracts_write_set_ptr_t __write_set_to_havoc); +``` + +## Translating Frees Clauses to GOTO Functions {#contracts-dev-spec-codegen-frees} + +Let's consider a contract `foo` (with corresponding pure contract +`contract::foo`) with a `__CPROVER_frees(F)` clause. + +```c +ret_t foo(foo-parameters) __CPROVER_frees(F); +``` +We know the elements of F only depend on `foo-parameters` and global variables. + +A new GOTO function with the following signature is generated: + +```c +void contract::foo::frees(foo-parameters); +``` + +The body of the function generated from the elements of F as follows: +- For each element of the form `cond: expr` where `expr` is a pointer-typed + expression: + ```c + DECL __cond: bool; + ASSIGN __cond = ; + IF !__cond GOTO SKIP_TARGET; + CALL __CPROVER_freeable(expr); + SKIP_TARGET: SKIP; + DEAD __cond; + ``` +- For each target of the form `cond: f(params)` where `f` is a void-typed + function: + ```c + DECL __cond: bool; + ASSIGN __cond = ; + IF !__cond GOTO SKIP_TARGET; + CALL f(params); + SKIP_TARGET: SKIP; + DEAD __cond; + ``` + +Remark: The condition expressions needs to be goto_converted during translation +since they come directly from the front-end and are allowed to contain function +calls. + +The resulting function is declarative, in the sense that it describes the +contents of the frees clause but does not have any runtime effects. + +The rewriting pass @ref contracts-dev-spec-spec-rewriting-frees is applied +to transform the function into a function that accepts two +write set parameters: The first one is the write set that gets populated with +freeable pointers specified by the function, the second is a write set that is +used by the function to check its side effects. + +```c +void contract::foo::frees( + + // function parameters + foo-parameters, + + // write set to populate with new targets + __CPROVER_contracts_write_set_ptr_t __write_set_to_fill, + + // write set against which to check itself for unwanted side effects + __CPROVER_contracts_write_set_ptr_t __write_set_to_check); +``` + +--- + Prev | Next +:-----|:------ + @ref contracts-dev-spec-transform-params | @ref contracts-dev-spec-spec-rewriting \ No newline at end of file diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-contract-checking-rec.md b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-contract-checking-rec.md new file mode 100644 index 00000000000..4c462ce7ff3 --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-contract-checking-rec.md @@ -0,0 +1,38 @@ +# Checking a Contract Against a Recursive Function {#contracts-dev-spec-contract-checking-rec} + +Back to top @ref contracts-dev-spec + +@tableofcontents + + +If the function to be checked is potentially recursive, we generate the wrapper +function body such that the first call triggers the contract checking logic +described in @ref contracts-dev-spec-contract-checking, and any subsequent call +triggers the contract replacement logic described in +@ref contracts-dev-spec-contract-replacement : + + +```c +static bool foo_check_started = false; +static bool foo_check_completed = false; + +ret_t foo(foo_params, write_set_t caller_write_set) { + assert(!foo_check_completed, "foo can only be called once from the harness"); + + // first call, check the contract + if(!foo_check_started) { + // non-recursive contract checking instructions go here + // ... + return retval; + } else { + // contract replacement instructions + // ... + return retval; + } +} +``` + +--- + Prev | Next +:-----|:------ + @ref contracts-dev-spec-contract-checking-rec | @ref contracts-dev-spec-contract-replacement \ No newline at end of file diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-contract-checking.md b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-contract-checking.md new file mode 100644 index 00000000000..2f453970dc4 --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-contract-checking.md @@ -0,0 +1,174 @@ +# Checking a Contract Against a Function {#contracts-dev-spec-contract-checking} + +Back to top @ref contracts-dev-spec + +@tableofcontents + +## Swapping-and-Wrapping Functions + +The first operation one can do with contracts is to check if a function satisfies +a contract. + +Let us consider a contract `c`, a function `foo` and a proof harness `main`. + +```c +// A contract +int c(int *a, int *b) +__CPROVER_requires(R) +__CPROVER_ensures(E) +__CPROVER_assigns(A) +__CPROVER_frees(F) +; + +// A function +int foo(int *a, int *b) +{ + // does something with a and b +} + +// A proof harness for foo +void main() +{ + // allocate non-deterministic parameters + int *a = ...; + int *b = ...; + + // call the function under verification + foo(a, b); +} +``` + +In order to check the contract `c` against `foo` in the context set up by the +harness `main`, we apply a _swap-and-wrap_ transformation to the function `foo`: + +The **swapping step** consists in creating a fresh function `foo_swapped` with the +same interface as `foo` and swapping the body of `foo` into `foo_swapped`, +while instrumenting it for [frame condition checking](@ref contracts-dev-spec-dfcc) +against A and F. + +```c +// The old foo function +int foo_swapped(int *a, int *b) +{ + // does something with a and b (now checked against A and F) +} +``` + +The **wrapping step** consists in creating a new body for `foo` +where the swapped function gets called between precondition and postcondition +checking instructions generated from the contract: + +```c +// The old foo function +int foo(int *a, int *b) +{ + // preconditions code goes here + // ... + + int reval = foo_swapped(a, b); + + // postconditions code code goes here + // ... + return retval; +} +``` + +After the *swap-and-wrap* transformation is applied, the `main` function calls +the contract-checking wrapper. Analysing this model with CBMC will effectively +check that the initial `foo` (now `foo_swapped`), under the assumption that the +contract preconditions hold, satisfies the postconditions and the frame +conditions of the contract. + +```c +// add these static variables to the model +static bool foo_check_started = false; +static bool foo_check_completed = false; + +ret_t foo(foo-parameters, write_set_t caller_write_set) { + + assert(!foo_check_started, "recursive calls to foo not allowed"); + assert(!foo_check_completed, "foo can only be called once from the harness"); + foo_check_started = true; + + // create an empty write set to check for side effects in requires clauses + __CPROVER_contracts_write_set_t __requires_write_set; + __CPROVER_contracts_write_set_ptr_t requires_write_set = & __requires_write_set; + __CPROVER_contracts_write_set_create(requires_write_set, 0, 0); + + // assume requires clauses + assume(contract::requires(foo_params, requires_write_set)); + assume(contract::requires_contract(foo_params, requires_write_set)); + + // check that requires clause do not allocate or deallocate dynamic memory + assert(__CPROVER_contracts_write_set_allocated_deallocated_is_empty(requires_write_set)); + __CPROVER_contracts_write_set_release(requires_write_set); + + // snapshot history variables + hist1_t hist1 = ...; + hist2_t hist2 = ...; + + // populate the contract write set + __CPROVER_contracts_write_set_t __contract_write_set; + __CPROVER_contracts_write_set_ptr_t contract_write_set = &__contract_write_set; + + // allocate and populate the contract write set + __CPROVER_contracts_write_set_create(contract_write_set, assigns_clause_size(c), frees_clause_size(c)); + contract::assigns(contract_write_set, empty_write_set); + contract::frees(contract_write_set, empty_write_set); + + // call the function + ret_t retval; + retval = foo_swapped(foo_params, contact_write_set); + __CPROVER_contracts_write_set_release(contract_write_set); + + // create an empty write set to check for side effects in requires clauses + __CPROVER_contracts_write_set_t __ensures_write_set; + __CPROVER_contracts_write_set_ptr_t ensures_write_set = & __ensures_write_set; + __CPROVER_contracts_write_set_create(ensures_write_set, 0, 0); + + // check post conditions + assert(contract::ensures(foo_params, ensures_write_set)); + assert(contract::ensures_contract(foo_params, ensures_write_set)); + + // check that requires clause do not allocate or deallocate dynamic memory + assert(__CPROVER_contracts_write_set_allocated_deallocated_is_empty(ensures_write_set)); + __CPROVER_contracts_write_set_release(ensures_write_set); + + foo_check_completed = true; + + return retval; +} +``` + +## Wrapping Recursive Functions + +If the function to be checked is potentially recursive, we generate the wrapper +function body such that the first call trigger the contract checking, and any +subsequent call triggers the contract replacement logic as described in +@ref contracts-dev-spec-contract-replacement : + + +```c +static bool foo_check_started = false; +static bool foo_check_completed = false; + +ret_t foo(foo_params, write_set_t caller_write_set) { + assert(!foo_check_completed, "foo can only be called once from the harness"); + + // first call, check the contract + if(!foo_check_started) { + // non-recursive contract checking instructions go here + // ... + return retval; + } else { + // contract replacement instructions + // ... + return retval; + } +} +``` + +--- + Prev | Next +:-----|:------ + @ref contracts-dev-spec-harness | @ref contracts-dev-spec-contract-replacement \ No newline at end of file diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-contract-replacement.md b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-contract-replacement.md new file mode 100644 index 00000000000..9b45b869501 --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-contract-replacement.md @@ -0,0 +1,101 @@ +# Replacing a Function by a Contract {#contracts-dev-spec-contract-replacement} + +Back to top @ref contracts-dev-spec + +@tableofcontents + +Let us now consider a function `bar`, called directly or indirectly from `main`. + +Assuming that `bar` satisfies a contract `d`, we want to replace `bar` with an +abstraction derived from `d`. Checking that `bar` actually satisfies `d` is the +responsibility of the user, and will usually be done using another proof harness. + +```c +int d(int *a) +__CPROVER_requires(R) +__CPROVER_ensures(E) +__CPROVER_assigns({a1, a2, ...}) +__CPROVER_frees({f1, f2, ...}) +; + +int bar(int *a) +{ + // does something with a +} +``` + +We abstract `bar` by replacing its instructions with instructions modelling +the non-deterministic behavior of the contract `d`, following this template: + +```c +ret_t foo(foo_params, write_set_t caller_write_set) { + + // create empty write set to check side effects in requires clauses + __CPROVER_contracts_write_set_t __requires_write_set; + __CPROVER_contracts_write_set_ptr_t requires_write_set; + __CPROVER_contracts_write_set_create(requires_write_set, 0, 0); + + // assert requires clauses + assert(contract::requires(foo_params, requires_write_set)); + assert(contract::requires_contract(foo_params, requires_write_set)); + + // snapshot history variables + hist1_t hist1 = ...; + hist2_t hist2 = ...; + + // create contract write set + __CPROVER_contracts_write_set_t __contract_write_set; + __CPROVER_contracts_write_set_ptr_t contract_write_set = &__contract_write_set; + __CPROVER_contracts_write_set_create(contract_write_set, assigns_clause_size(contract), frees_clause_size(contract)); + + // populate the write set + contract::assigns(contract_write_set, empty_write_set); + contract::frees(contract_write_set, empty_write_set); + assert(__CPROVER_contracts_write_set_check_allocated_deallocated_is_empty(requires_write_set)); + + __CPROVER_contracts_write_set_release(requires_write_set); + + // check inclusion with caller write set + assert(__CPROVER_contracts_write_set_check_assigns_clause_inclusion(caller_write_set, contract_write_set)); + + assert(__CPROVER_contracts_write_set_check_frees_clause_inclusion(caller_write_set, contract_write_set)); + + // havoc assigns clause targets + contract::havoc(contract_write_set); + ret_t retval = nondet(); + + // free freeable pointers + __CPROVER_contracts_write_set_deallocate_freeable(contract_write_set); + + // Create empty write set to check side effects in ensures clauses + __CPROVER_contracts_write_set_t __ensures_write_set; + __CPROVER_contracts_write_set_ptr_t ensures_write_set; + __CPROVER_contracts_write_set_create(ensures_write_set, 0, 0); + + // Link caller write set and write set so that allocations due to is_fresh + // in post conditions are recorded in the caller write set + __CPROVER_contracts_write_set_link_allocated(ensures_write_set, caller_write_set); + + // link the ensures write set to the contract write set so that the was_freed + // predicates in the postconditions get access to the deallocated pointers + __CPROVER_contracts_write_set_link_deallocated(ensures_write_set, contract_write_set); + + // assume post conditions + assume(contract::ensures(foo_params, ensures_write_set)); + assume(contract::ensures_contract(foo_params, ensures_write_set)); + + // postamble + assert(__CPROVER_contracts_write_set_check_allocated_deallocated_is_empty(ensures_write_set)); + __CPROVER_contracts_write_set_release(ensures_write_set); + + return retval; +} +``` + +After applying this transformation, any function that called the initial `bar` +now calls the abstraction derived from the contract. + +--- + Prev | Next +:-----|:------ + @ref contracts-dev | @ref contracts-dev-spec-reminder \ No newline at end of file diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-dfcc-instrument.md b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-dfcc-instrument.md new file mode 100644 index 00000000000..6f3f242b9ec --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-dfcc-instrument.md @@ -0,0 +1,254 @@ +# GOTO Function Instrumentation {#contracts-dev-spec-dfcc-instrument} + +Back to top @ref contracts-dev-spec + +@tableofcontents + +Applying the DFCC instrumentation to a function turns it into a function that +can be checked against a write set passed as parameter. + +## Signature Extension {#contracts-dev-spec-dfcc-instrument-signature} + +The signature of a function: + +```c +ret_t foo(foo-parameters); +``` + +is turned into the extended version: + +```c +ret_t foo(foo-parameters, __CPROVER_contracts_write_set_ptr_t write_set); +``` + +After this step the `write_set` parameter is in scope in the function and can +be used to implement checks. + +It is valid to pass a `NULL` pointer for the `write_set` parameter. When the +`write_set` parameter is NULL, no checks are performed in the function. + +## Body Instrumentation {#contracts-dev-spec-dfcc-instrument-body} + +Body instrumentation inserts additional goto instructions in the function's +GOTO instruction sequence. + +All instrumented checks are guarded by a null-check on the `write_set` +pointer parameter. Hence, passing a `NULL` pointer results in no checks being +performed in the function. + +### Instrumenting DECL Instructions + +```c +DECL x; +---- +IF !write_set GOTO skip_target; +CALL __CPROVER_contract_write_set_add_allocated(write_set, &x); +skip_target: SKIP; +``` + +### Instrumenting DEAD Instructions + +```c +IF !write_set GOTO skip_target; +CALL __CPROVER_contract_write_set_record_dead(write_set, &x); +skip_target: SKIP; +---- +DEAD x; +``` + +### Instrumenting ASSERT Instructions + +No instrumentation is performed. + +```c +ASSERT expr; +``` + +### Instrumenting ASSUME Instructions + +No instrumentation is performed. + +```c +ASSUME expr; +``` + +### Instrumenting ASSIGN Instructions + +Assign instructions trigger a check on the LHS but also triggers an update of +the write set if the RHS if it represents a dynamic allocation or deallocation. + +#### LHS Instrumentation + +Checks are inserted before the instruction. + +If the LHS is either: +- a `__CPROVER_`-prefixed symbol (these symbols are usually global variables + that serve instrumentation purposes and can be understood as living in a + namespace of their own) +- an expression that represents a composite access expression to a locally + stack-allocated object that is not dirty (i.e. its address is never computed) + or a to a function parameter (these are always implicitly allowed) + +Then no check is performed, i.e. `ASSERT true;` is inserted: + +```c +IF !write_set GOTO skip_target; +ASSERT(true, "comment describing why the assignment is always allowed"); +skip_target: SKIP; +---- +ASSIGN lhs := rhs; +``` + +Otherwise, we check that the LHS is found in the write set's `contract_assigns` +set or the `allocated` set as shown below: + +```c +IF !write_set GOTO skip_target; +DECL check_assign: bool; +CALL check_assign = + __CPROVER_contracts_write_set_check_assignment( + write_set, &lhs, sizeof(lhs)); +ASSERT check_assign; +DEAD check_assign; +skip_target: SKIP; +---- +ASSIGN lhs := rhs; +``` + +#### RHS Instrumentation + +For the write set updates we consider the following cases. + +If the RHS of the assignment is a `side_effect_exprt(statement = ID_allocate)` +expression, it represents a dynamic allocation. We record it in the write set: + +```c +CALL lhs := side_effect_exprt(statement = ID_allocate, args = {size, clear}); +---- +IF !write_set GOTO skip_target; +CALL __CPROVER_contracts_write_set_add_allocated(write_set, lhs); +skip_target: SKIP; +``` + +If the assignment is an nondeterministic update to the `__CPROVER_dead_object`, +it in fact models a dynamic deallocation. Such instructions are generated to +deallocate objects allocated with the dynamic stack allocation function +`__builtin_alloca` and are always legal. We just record the deallocation. + +```c +ASSIGN __CPROVER_dead_object := if_exprt(nondet, ptr, dead_object); +---- +IF !write_set GOTO skip_target; +CALL __CPROVER_contracts_write_set_record_deallocated(write_set, ptr); +skip_target: SKIP; +``` + +### Instrumenting CALL Instructions + +If the function call is a call to the `__CPROVER_deallocate` function, +it represents a dynamic deallocation and we check that the deallocated pointer +is allowed by the write set, and then record the deallocation in the write set. + +```c +IF !write_set GOTO skip_target; +DECL check_deallocate: bool; +CALL check_deallocate := + __CPROVER_contracts_write_set_check_deallocate(write_set, ptr); +ASSERT(check_deallocate); +DEAD check_deallocate; +CALL __CPROVER_contracts_write_set_record_deallocated(write_set, lhs); +skip_target: SKIP; +---- +CALL __CPROVER_deallocate(ptr); +``` + +Calls to __CPROVER_was_freed or __CPROVER_is_freeable are rewritten as described +in @subpage contracts-dev-spec-is-freeable + +Calls to __CPROVER_is_fresh are rewritten as described in @subpage contracts-dev-spec-is-fresh + +For all other function or function pointer calls, we proceed as follows. + +If the function call has an LHS (i.e. its result is assigned to a return value +variable), the LHS gets checked like for an assignment, and we pass the write +set as an extra parameter to the function (remember that all functions of the +goto models are extended with write_set parameters by the transformation). + +```c +// If the LHS exists +IF !write_set GOTO skip_target; +DECL check_assign: bool; +CALL check_assign = +__CPROVER_contracts_write_set_check_assignment(write_set, &lhs, sizeof(lhs)); +ASSERT(check_assign); +DEAD check_assign; +skip_target: SKIP; +---- +CALL [lhs] := function(parameters, write_set); +``` + +### Instrumenting OTHER Instructions + +`OTHER` instructions describe special built-in operations that have no explicit +C or GOTO representation (they are given a semantics directly by the symex +engine). From `goto_symext::symex_other` we see the possible operations are: + +* `ID_expression` +* `ID_array_equal` +* `ID_array_set` +* `ID_array_copy` +* `ID_array_replace` +* `ID_havoc_object` +* `ID_decl` +* `ID_cpp_delete` +* `ID_printf` +* `code_inputt` +* `code_outputt` +* `ID_nondet` +* `ID_asm` +* `ID_user_specified_predicate` +* `ID_user_specified_parameter_predicates` +* `ID_user_specified_return_predicates` +* `ID_fence` + +Remark: the instructions `code_inputt`, `code_outputt` and `ID_nondet` would +also need to be instrumented as they perform side effects and introduce +non-determinism, but this is not handled as of today and will trigger warnings. + +For DFCC we only instrument the `array_set`, `array_copy`, `array_replace` and +`havoc_object` operations. + +The example below is for `__CPROVER_array_set`, and the `dest` pointer must be +found in the `contract_assigns` set or the `allocated` set. + +```c +IF !write_set GOTO skip_target; +DECL check_array_set: bool; +CALL check_array_set = + __CPROVER_contracts_write_set_check_array_set(write_set, dest); +ASSERT(check_array_set); +DEAD check_array_set; +skip_target: SKIP; +---- +OTHER {statement = array_set, args = {dest, value}}; +``` + +The ranges of bytes `(void *lb, size_t size)` updated by the different operations are: + +* for `array_set(dest, value)`: + * `lb = dest;` + * `size = object_size(dest) - pointer_offset(ptr);` +* for `array_copy(dest, src)` + * `lb = dest;` + * `size = object_size(dest) - pointer_offset(dest);` +* for `array_replace(dest, src)` + * `lb = dest;` + * `size = MIN(object_size(dest) - pointer_offset(dest), object_size(src) - pointer_offset(src));` +* for `havoc_object(ptr)` + * `lb = (char *)ptr - pointer_offset(ptr);` + * `size = object_size(ptr);` + +--- + Prev | Next +:-----|:------ + @ref contracts-dev-spec-dfcc | @ref contracts-dev-spec-harness \ No newline at end of file diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-dfcc-runtime.md b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-dfcc-runtime.md new file mode 100644 index 00000000000..f1456324578 --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-dfcc-runtime.md @@ -0,0 +1,179 @@ +# Write Set Representation {#contracts-dev-spec-dfcc-runtime} + +Back to @ref contracts-dev-spec-dfcc + +@tableofcontents + +## Write Set Data Structure {#contracts-dev-spec-dfcc-runtime-data} + +The write set type and its basic operations are implemented in the file +@ref cprover_contracts.c. + +Assignable ranges of bytes specified in assigns clauses are represented by +the @ref __CPROVER_contracts_car_t : + +```c +// A conditionally writable range of bytes. +typedef struct __CPROVER_contracts_car_t +{ + // True iff __CPROVER_w_ok(lb, size) holds at creation + __CPROVER_bool is_writable; + + //Size of the range in bytes + __CPROVER_size_t size; + + //Lower bound address of the range + void *lb; + + //Upper bound address of the range + void *ub; +} __CPROVER_contracts_car_t; +``` + +Sets of @ref __CPROVER_contracts_car_t are represented as dynamic arrays +@ref __CPROVER_contracts_car_set_t, allocated by the +@ref __CPROVER_contracts_car_set_create function. + +```c +typedef struct __CPROVER_contracts_car_set_t +{ + __CPROVER_size_t max_elems; + __CPROVER_contracts_car_t *elems; +} __CPROVER_contracts_car_set_t; +``` +The allocated size of a set is determined by the maximum number of targets +found in the assigns clause of the contract of interest. + +Objects for which all bytes are assignable (or freeable) do not need to be +represented as @ref __CPROVER_contracts_car_t. They can be tracked using a +single pointer or object identifier. + +Such sets of pointers are represented by the @ref __CPROVER_contracts_obj_set_t +data type. + +```c +// A set of pointers. +typedef struct __CPROVER_contracts_obj_set_t +{ + // Maximum number of elements that can be stored in the set + __CPROVER_size_t max_elems; + + // next usable index in elems if less than max_elems + /// (1 + greatest used index in elems) + __CPROVER_size_t watermark; + + // Number of elements currently in the elems array + __CPROVER_size_t nof_elems; + + // True iff nof_elems is 0 + __CPROVER_bool is_empty; + + // True iff elems is indexed by the object id of the pointers + __CPROVER_bool indexed_by_object_id; + + // Array of void *pointers, indexed by their object ID or some arbitrary order + void **elems; +} __CPROVER_contracts_obj_set_t; +``` + +The `void **elem` array can be used in two distinct modes, controlled by +the field `indexed_by_object_id`: +1. When `indexed_by_object_id` is true, the container works in _indexed mode_. + The `elems` array is allocated to a size `2^OBJECT_BITS`, and adding a + pointer `p` to the set stores it at `elems[__CPROVER_POINTER_OBJECT(p)]`. + This mode allows for efficient insertions and lookups but results in a sparse + array; +2. When `indexed_by_object_id` is false, the container works in _append mode_. + `elems` is allocated to a manually specified size, and adding pointer `p` + stores it at `elems[watermark]` and increments `watermark` by one. + This mode densely packs all pointers at the beginning of the array and allows + for iteration over the stored pointers. + + +Last, actual write sets are represented by the type +@ref __CPROVER_contracts_write_set_t : + +```c +// Runtime representation of a write set. +typedef struct __CPROVER_contracts_write_set_t +{ + // Set of car derived from the contract + __CPROVER_contracts_car_set_t contract_assigns; + + // Set of freeable pointers derived from the contract (indexed mode) + __CPROVER_contracts_obj_set_t contract_frees; + + // Set of freeable pointers derived from the contract (append mode) + __CPROVER_contracts_obj_set_t contract_frees_replacement; + + // Set of objects allocated by the function under analysis (indexed mode) + __CPROVER_contracts_obj_set_t allocated; + + // Set of objects deallocated by the function under analysis (indexed mode) + __CPROVER_contracts_obj_set_t deallocated; + + // Object set supporting the is_fresh predicate checks (indexed mode) + __CPROVER_contracts_obj_set_ptr_t linked_is_fresh; + + // Object set recording the is_fresh allocations in post conditions + // (replacement mode only) + __CPROVER_contracts_obj_set_ptr_t linked_allocated; + + // Object set recording the deallocations (used by predicate was_freed) + __CPROVER_contracts_obj_set_ptr_t linked_deallocated; + + // True iff this write set is used for contract replacement + __CPROVER_bool replacement; + + // True iff the write set checks requires clauses in an assumption ctx + __CPROVER_bool assume_requires_ctx; + + // True iff the write set checks requires clauses in an assertion ctx + __CPROVER_bool assert_requires_ctx; + + // True iff the write set checks ensures clauses in an assumption ctx + __CPROVER_bool assume_ensures_ctx; + + // True iff this write set checks ensures clauses in an assertion ctx + __CPROVER_bool assert_ensures_ctx; +} __CPROVER_contracts_write_set_t; +``` + +## Write Set Operations {#contracts-dev-spec-dfcc-runtime-ops} + +Direct links to @ref __CPROVER_contracts_write_set_t operations documentation +(full documentation available at @ref cprover_contracts.c) : +- @ref __CPROVER_contracts_write_set_create +- @ref __CPROVER_contracts_write_set_release +- @ref __CPROVER_contracts_write_set_insert_assignable +- @ref __CPROVER_contracts_write_set_insert_object_whole +- @ref __CPROVER_contracts_write_set_insert_object_from +- @ref __CPROVER_contracts_write_set_insert_object_upto +- @ref __CPROVER_contracts_write_set_add_freeable +- @ref __CPROVER_contracts_write_set_add_allocated +- @ref __CPROVER_contracts_write_set_record_dead +- @ref __CPROVER_contracts_write_set_record_deallocated +- @ref __CPROVER_contracts_write_set_check_allocated_deallocated_is_empty +- @ref __CPROVER_contracts_write_set_check_assignment +- @ref __CPROVER_contracts_write_set_check_array_set +- @ref __CPROVER_contracts_write_set_check_array_copy +- @ref __CPROVER_contracts_write_set_check_array_replace +- @ref __CPROVER_contracts_write_set_check_havoc_object +- @ref __CPROVER_contracts_write_set_check_deallocate +- @ref __CPROVER_contracts_write_set_check_assigns_clause_inclusion +- @ref __CPROVER_contracts_write_set_check_frees_clause_inclusion +- @ref __CPROVER_contracts_write_set_deallocate_freeable +- @ref __CPROVER_contracts_link_allocated +- @ref __CPROVER_contracts_link_deallocated +- @ref __CPROVER_contracts_is_fresh +- @ref __CPROVER_contracts_write_set_havoc_get_assignable_target +- @ref __CPROVER_contracts_write_set_havoc_object_whole +- @ref __CPROVER_contracts_write_set_havoc_slice +- @ref __CPROVER_contracts_is_freeable +- @ref __CPROVER_contracts_was_freed +- @ref __CPROVER_contracts_check_replace_ensures_was_freed_preconditions + +--- + Prev | Next +:-----|:------ + @ref contracts-dev-spec-dfcc | @ref contracts-dev-spec-dfcc-instrument \ No newline at end of file diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-dfcc.md b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-dfcc.md new file mode 100644 index 00000000000..61feefc9992 --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-dfcc.md @@ -0,0 +1,103 @@ +# Dynamic Frame Condition Checking {#contracts-dev-spec-dfcc} + +Back to top @ref contracts-dev-spec + +@tableofcontents + +## Overview +Frame condition checking consists in checking that a function (and any +functions it calls) only assigns to memory locations allowed by the contract's +assigns clause, and only deallocates objects allowed by the contract's frees +clause. + +To implement frame condition checking we use a method inspired by +[Dynamic Frames](https://pm.inf.ethz.ch/publications/Kassios11.pdf). + +The method consists in: +- representing sets of assignable and freeable memory locations specified by + contracts as ghost data structures embedded in the program under verification; +- extending all functions of the model to accept an additional dynamic frame + parameter; +- instrumenting all functions to check that the side effects or the deallocation + they perform are within what is allowed by the dynamic frame parameter. + +In our approach a dynamic frame is represented by the +@ref __CPROVER_contracts_write_set_t data type defined in @ref cprover_contracts.c. + +From an simple point of view, a @ref __CPROVER_contracts_write_set_t +tracks 4 sets of memory locations: + +```c +struct __CPROVER_contracts_write_set_t { + + // from the contract's __CPROVER_assigns clause + __CPROVER_contracts_car_set_t contract_assign; + + // from the contract's __CPROVER_frees clause + __CPROVER_contracts_obj_set_t contract_frees; + + // records local allocations + __CPROVER_contracts_obj_set_t allocated; + + // records deallocations + __CPROVER_contracts_obj_set_t deallocated; + +} __CPROVER_contracts_write_set_t; +``` + +- `contract_assigns` is the set of memory locations specified in the + *assigns clause* of the contract of interest; +- `contract_frees` is the set of pointers specified in the *frees clause* + of the contract of interest; +- `allocated` is the set of identifiers of objects + (as given by `__CPROVER_POINTER_OBJECT(x)`) that were locally allocated + since first entering the function under verification + (on the stack using `DECL x` or on the heap using + `x = __CPROVER_allocate(...)`); +- `deallocated` is the set of pointers `p` that were deallocated using + `__CPROVER_deallocate(p)` since first entering the function under + verification. + +The @ref __CPROVER_contracts_write_set_t type is accompanied by functions +allowing to (cf @ref cprover_contracts.c): +- add contents to `contract_assigns`; +- add contents to `contract_frees`; +- record an object allocation in `allocated`; +- record an object deallocation in `deallocated`; +- check wether a given `car_t` describing a location about to be assigned is + contained within `contract_assigns` or `allocated`; +- check wether the object pointed to by a given pointer about to be freed is + contained in `contract_frees` or `allocated`; +- check weither all memory locations of a candidate write set are included in + some element of a given reference write set. + +The instrumentation adds a @ref __CPROVER_contracts_write_set_ptr_t parameter +to all functions of the GOTO model as follows: + +```c +ret_t f(, __CPROVER_contracts_write_set_ptr_t write_set); +``` + +The bodies of the functions are instrumented so that: +- when the given `write_set` is `NULL`, no checks are performed; +- when the given `write_set` is not `NULL`, the following checks are performed: + - The address ranges corresponding to the LHS of assignments instructions are + checked for inclusion in `contract_assigns` or `allocated`; + - stack-allocated objects (`DECL`) and heap-allocated objects + (`__CPROVER_allocate`) are recorded in `allocated` + - dynamic objects deallocated with `__CPROVER_deallocate` are checked for + inclusion in `contract_frees` or `allocated`, and are recorded in + `deallocated` (so that contract postconditions about deallocations + can be checked). + - the `write_set` parameter is propagated to functions calls or + function pointer calls. + +## Detailed Specifications + +- @subpage contracts-dev-spec-dfcc-runtime Gives details on the write set implementation +- @subpage contracts-dev-spec-dfcc-instrument Gives function instrumentation rules + +--- + Prev | Next +:-----|:------ + @ref contracts-dev | @ref contracts-dev-spec-dfcc-runtime \ No newline at end of file diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-harness.md b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-harness.md new file mode 100644 index 00000000000..629d2ebaf82 --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-harness.md @@ -0,0 +1,12 @@ +## Proof Harness Intrumentation {#contracts-dev-spec-harness} + +Back to top @ref contracts-dev-spec + +@tableofcontents + +The harness function is the entry point of the analysis and is meant to contain a direct or indirect call to the function being checked against a contract or potentially several functions replaced by contracts. The harness can also contain preamble instructions to set up proof assumptions or perform cleanup after the call to the checked function. We do not want to check these functions and instructions against any particular write set. 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. + +--- + Prev | Next +:-----|:------ + @ref contracts-dev-spec-dfcc | @ref contracts-dev-spec-contract-checking \ No newline at end of file diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-is-freed.md b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-is-freed.md new file mode 100644 index 00000000000..3433a87bb21 --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-is-freed.md @@ -0,0 +1,26 @@ +# Rewriting Calls to __CPROVER_is_freeable and __CPROVER_was_freed Predicates {#contracts-dev-spec-is-freeable} + +Back to top @ref contracts-dev-spec + +@tableofcontents + +In goto programs encoding pre or post conditions (generated from the contract clauses) and in all user-defined functions, we simply replace calls to `__CPROVER_is_freeable` with a calls to its library implementation: + +```c +CALL __CPROVER_contracts_is_freeable(void *ptr, __CPROVER_contracts_write_set_ptr_t write_set); +``` + +The behaviour of `__CPROVER_contracts_is_freeable` can only be used in requires clauses, and it needs to use a weaker definition when used in assumption contexts (contract checking vs replacement). Context flags are obtained from the write set instance an interpreted by the library function. + +For `__CPROVER_was_freed`, which can only be used in post conditions, we also map calls to a library implementation: + +```c +CALL __CPROVER_contracts_was_freed(void *ptr, __CPROVER_contracts_write_set_ptr_t write_set); +``` + +This function performs a lookup in the `write_set->deallocated` pointer set to check if the function under analysis indeed deallocated the object. The result of this check will then be either asserted for contract checking or assumed for contract replacement. on the context. When turned in an assumption, we instantiate an extra assertion before the assumption, in order to check that the pointer is in always found in the freeable set of the contract and that it is safe to assume it is freed, without causing an immediate contradiction. + +--- + Prev | Next +:-----|:------ + @ref contracts-dev | @ref contracts-dev-spec-reminder \ No newline at end of file diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-is-fresh.md b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-is-fresh.md new file mode 100644 index 00000000000..74f9ac52d2c --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-is-fresh.md @@ -0,0 +1,22 @@ +# Rewriting Calls to the __CPROVER_is_fresh Predicate {#contracts-dev-spec-is-fresh} + +Back to top @ref contracts-dev-spec + +@tableofcontents + +In goto programs encoding pre or post conditions (generated from the contract +clauses) and in all user-defined functions, we simply replace calls to +`__CPROVER_is_fresh` with calls to the library implementation + +```c +__CPROVER_contracts_is_fresh(void **ptr, size_t size, __CPROVER_contracts_write_set_ptr_t write_set); +``` + +This function implements the `__CPROVER_is_fresh` behaviour in all possible contexts +(contract checking vs replacement, requires vs ensures clause context, +as described by the flags carried by the write set parameter). + +--- + Prev | Next +:-----|:------ + @ref contracts-dev | @ref contracts-dev-spec-reminder \ No newline at end of file diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-reminder.md b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-reminder.md new file mode 100644 index 00000000000..a7fb8c259ad --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-reminder.md @@ -0,0 +1,63 @@ +# Function Contracts Reminder {#contracts-dev-spec-reminder} + +Back to top @ref contracts-dev-spec + +@tableofcontents + +The user documentation for function contracts is available at @ref contracts-functions, +but we briefly remind the developer of the structure of a contract below. + + +A contract is defined by adding one or more clauses to a function declaration or +definition: + +```c +ret_t foo(parameters) +// preconditions +__CPROVER_requires(R) +__CPROVER_requires_contract(P, C) + +// postconditions +__CPROVER_ensures(E) +__CPROVER_ensures_contract(P, C) + +// frame conditions +__CPROVER_assigns(A) +__CPROVER_frees(F) +; +``` + +- A `__CPROVER_requires` clause (@ref contracts-requires-ensures) specifies a + precondition as boolean expression R that may only depend on program globals, + function parameters, [memory predicates](@ref contracts-memory-predicates) and + deterministic, side effect-free function calls; +- A `__CPROVER_requires_contract` clause clause specifies the precondition that + a function pointer expression P satisfies a contract C, where P may only + depend on program globals and function parameters; +- A `__CPROVER_ensures` clause (@ref contracts-requires-ensures) specifies a + postcondition as boolean expression E that may only depend on program globals, + function parameters, [memory predicates](@ref contracts-memory-predicates), + deterministic, side effect-free function calls, + [history variables](@ref contracts-history-variables), and the special + variable `__CPROVER_return_value`; +- A `__CPROVER_ensures_contract` clause specifies the postcondition that a + function pointer expression P satisfies a contract C, where P may only depend + on program globals, function parameters, + [history variables](@ref contracts-history-variables) and the special + variable `__CPROVER_return_value`; +- A `__CPROVER_assigns` clause (@ref contracts-assigns) specifies a set A of + memory locations that may be assigned to by any function satisfying the + contract; +- A `__CPROVER_frees` clause (@ref contracts-frees) specifies a set F of + pointers that may be freed by any function satisfying the contract. + +For each such function `foo` carrying contract clauses, the ansi-c front-end of +CBMC creates a dedicated function symbol named `contract::foo` in the symbol table, +with the same signature as `foo`, and attaches the contract clauses to that new +symbol. We call `contract::foo` the **pure contract** associated with the function +`foo`. + +--- + Prev | Next +:-----|:------ + @ref contracts-dev-spec | @ref contracts-dev-spec-transform-params \ No newline at end of file diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-spec-rewriting.md b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-spec-rewriting.md new file mode 100644 index 00000000000..afc11386e11 --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-spec-rewriting.md @@ -0,0 +1,218 @@ +# Rewriting Declarative Assign and Frees Specification Functions {#contracts-dev-spec-spec-rewriting} + +Back to top @ref contracts-dev-spec + +@tableofcontents + +The front end allows to express parametric assignable sets and freeable sets +declaratively by calling special built-ins from regular C functions, +as described in @ref contracts-assigns and @ref contracts-frees. + +Such functions require a rewriting pass in order to turn them into active +functions that can effectively populate a write set with new targets. + +## Rewriting Assigns Clause Functions {#contracts-dev-spec-spec-rewriting-assigns} + +GOTO functions which specify assignable locations have the following type +signature: + +```c +void foo(parameter-list); +``` + +They can be generated from an assigns clause or written by the user. + +To convert them to active functions, the first step is to inline their body and +remove all function calls, the result of which must be loop-free. + +Then, a second pass adds the write set to be filled as new parameter to the +function: + +```c +void foo( + foo-parameters, + + // write set to populate with new targets + __CPROVER_contracts_write_set_ptr_t __write_set_to_fill); +``` + +In the inlined body, calls to built in functions `__CPROVER_assignable`, +`__CPROVER_object_upto`, `__CPROVER_object_from`, `__CPROVER_object_whole` +are numbered from 0 to N in increasing order from their position in the +instruction sequence: + +```c +CALL __CPROVER_object_upto(ptr, size); // index 0 +CALL __CPROVER_object_whole(ptr); // index 1 +CALL __CPROVER_object_from(ptr); // index 2 +... +CALL __CPROVER_assignable(ptr, size, is_ptr_to_ptr); // index N +``` + +They are then rewritten into calls to the corresponding instrumentation hook +provided by the @ref cprover_contracts.c library: + +```c +CALL __CPROVER_contracts_write_set_insert_object_upto( + __write_set_to_fill, 0, ptr, size); +CALL __CPROVER_contracts_write_set_insert_object_whole( + __write_set_to_fill, 1, ptr); +CALL __CPROVER_contracts_write_set_insert_object_from( + __write_set_to_fill, 2, ptr); +... +CALL __CPROVER_contracts_write_set_insert_assignable( + __write_set_to_fill, N, ptr, size, is_ptr_to_ptr); +``` + +The function is then instrumented as described in +@ref contracts-dev-spec-dfcc-instrument which adds a second write set parameter, +that allows to check that the function has no unintended side effects. + +```c +void foo( + , + + // write set to populate with new targets + __CPROVER_contracts_write_set_ptr_t __write_set_to_fill, + + // write set against which to check itself for unwanted side effects + __CPROVER_contracts_write_set_ptr_t __write_set_to_check); +``` + + +## Generating Havoc Functions from Assigns Clause Functions {#contracts-dev-spec-spec-rewriting-havoc} + +Contract replacement requires havocing the set of targets described in an +assigns clause. Since there can exist dependencies and aliasing between the +different targets, in order to havoc them in parallel, we first need to snapshot +the target locations into a write set data structure, and generate a function +that can havoc these snapshotted locations in a second step. + +GOTO functions which specify assignable locations have the following type +signature: + +```c +void foo(parameter-list); +``` + +They can be generated from an assigns clause or written by the user. + +To convert them to havoc functions, the first step is to inline their body and +remove all function calls, the result of which must be loop-free. + +Then, a second pass adds the write set to be havoced as new parameter to the +function: + +```c +void contract::foo::havoc( + // write set containing snapshots of the targets to havoc + __CPROVER_contracts_write_set_ptr_t __write_set_to_fill); +``` + +In the inlined body, calls to built in functions `__CPROVER_assignable`, +`__CPROVER_object_upto`, `__CPROVER_object_from`, `__CPROVER_object_whole` +are numbered from 0 to N in increasing order from their position in the +instruction sequence: + +```c +CALL __CPROVER_object_upto(ptr, size); // index 0 +CALL __CPROVER_object_whole(ptr); // index 1 +CALL __CPROVER_object_from(ptr); // index 2 +... +CALL __CPROVER_assignable(ptr, size, is_ptr_to_ptr); // index N +``` + +They are then rewritten into calls to the corresponding instrumentation hook +provided by the @ref cprover_contracts.c library: + +Targets specified with `__CPROVER_object_whole`: + +```c +CALL __CPROVER_object_whole(ptr); // at index i +``` + +Get rewritten to: +```c +CALL __CPROVER_contracts_havoc_object_whole(write_set, i); +``` + +Targets specified with `__CPROVER_object_from` or `__CPROVER_object_upto`: + +```c +CALL __CPROVER_object_from(ptr); // at index i +``` + +Get rewritten to: + +```c +CALL __CPROVER_contracts_havoc_slice(write_set, i); +``` + +Last, targets specified with `__CPROVER_assignable` + +```c +CALL __CPROVER_assignable(ptr, size, is_ptr_to_ptr); // at index i +``` + +Get rewritten to a typed nondeterministic assignment: + +```c +CALL void *target = __CPROVER_contracts_write_set_havoc_get_assignable_target(write_set, i); +ASSIGN *target = nondet_type_of_target(); +``` + +The generated function has the intended side effects by construction and nothing +else. + +## Rewriting Frees Clause Functions {#contracts-dev-spec-spec-rewriting-frees} + +User-defined functions which list freeable pointers have the following signature: + +```c +void foo(); +``` + +They are first inlined and checked to be loop free before being rewritten into +functions which accepts a write set parameter to be filled with freeable targets +defined by the function: + +```c +void foo( + , + + // write set to populate with new targets + __CPROVER_contracts_write_set_ptr_t __write_set_to_fill); +``` + +Calls to the built-in function: + +```c +CALL __CPROVER_freeable(ptr); +``` + +are rewritten into to calls to active instrumentation functions which insert into the write set + +```c +CALL __CPROVER_contracts_write_set_add_freeable( + __write_set_to_fill, ptr); +``` + +The function is then instrumented as described in +@ref contracts-dev-spec-dfcc-instrument which adds a second write set parameter, +that allows to check that the function has no unintended side effects. + +```c +void foo( + , + + // write set to populate with new targets + __CPROVER_contracts_write_set_ptr_t __write_set_to_fill, + + // write set against which to check itself for unwanted side effects + __CPROVER_contracts_write_set_ptr_t __write_set_to_check); +``` + +--- + Prev | Next +:-----|:------ + @ref contracts-dev-spec-spec-rewriting | @ref contracts-dev-spec-dfcc \ No newline at end of file diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-transform-params.md b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-transform-params.md new file mode 100644 index 00000000000..8db5296ccbf --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec-transform-params.md @@ -0,0 +1,26 @@ +# Program Transformation Overview {#contracts-dev-spec-transform-params} + +Back to top @ref contracts-dev-spec + +@tableofcontents + +These are the main parameter of the program transformation: + +- `harness_id` specifies the identifier of the proof harness; +- `(f_top,c_top)` an optional pair and specifies that + the function `f_top` must be checked against the contract carried by function + `c_top`, by the pure contract `contract::c_top`. +- `(f,c)` a possibly empty set of pairs where each `f` must be replaced + with the contract carried by function `c`, i.e. by the pure contract `contract::c`. + +The program transformation steps are applied as follows: +1. @ref contracts-dev-spec-codegen is applied to all contracts to check or replace; +2. @ref contracts-dev-spec-dfcc is applied to all library or user-defined goto functions; +3. @ref contracts-dev-spec-harness is applied to the harness function; +4. @ref contracts-dev-spec-contract-checking is applied to the function to be checked against a contract; +5. @ref contracts-dev-spec-contract-replacement is applied to each function to be replaced by a contract; + +--- + Prev | Next +:-----|:------ + @ref contracts-dev-spec-reminder | @ref contracts-dev-spec-codegen diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev-spec.md b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec.md new file mode 100644 index 00000000000..f70e74221ee --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev-spec.md @@ -0,0 +1,21 @@ +# Code Contracts Transformation Specification {#contracts-dev-spec} + +Back to top @ref contracts-dev + +The program transformation specification is broken down in the following +sections: + +1. @subpage contracts-dev-spec-reminder +2. @subpage contracts-dev-spec-transform-params +3. @subpage contracts-dev-spec-codegen +4. @subpage contracts-dev-spec-spec-rewriting +5. @subpage contracts-dev-spec-dfcc +6. @subpage contracts-dev-spec-harness +7. @subpage contracts-dev-spec-contract-checking +7. @subpage contracts-dev-spec-contract-checking-rec +8. @subpage contracts-dev-spec-contract-replacement + +--- + Prev | Next +:-----|:------ + @ref contracts-dev | @ref contracts-dev-spec-reminder \ No newline at end of file diff --git a/src/goto-instrument/contracts/doc/developer/contracts-dev.md b/src/goto-instrument/contracts/doc/developer/contracts-dev.md new file mode 100644 index 00000000000..7e5543c5912 --- /dev/null +++ b/src/goto-instrument/contracts/doc/developer/contracts-dev.md @@ -0,0 +1,9 @@ +# Code Contracts Developer Documentation {#contracts-dev} + +Back to @ref contracts-mainpage + +Function contracts are implemented using a `goto-instrument` pass that performs +a whole program transformation. + +- @subpage contracts-dev-spec gives a specification of the transformation +- @subpage contracts-dev-arch describes the software architecture implementing the transformation diff --git a/doc/cprover-manual/contracts-assigns.md b/src/goto-instrument/contracts/doc/user/contracts-assigns.md similarity index 90% rename from doc/cprover-manual/contracts-assigns.md rename to src/goto-instrument/contracts/doc/user/contracts-assigns.md index 2ed3a226ed7..5ee8ea7bb3a 100644 --- a/doc/cprover-manual/contracts-assigns.md +++ b/src/goto-instrument/contracts/doc/user/contracts-assigns.md @@ -1,37 +1,14 @@ -[CPROVER Manual TOC](../../) +# Assigns Clauses {#contracts-assigns} -# Assigns Clauses +Back to @ref contracts-user -An _assigns clause_ lets the user to specify which memory locations may be -assigned to by a function or a loop. - -Knowing the memory footprint of a function contract or a loop contract in turn -allows us to replace a function call or a loop by an nondeterministic -approximation in which assignable locations are havoced. To make sure this -approximation is sound and is an actual abstraction of the original function -call or loop, we need to verify that the actual function or loop never assigns -memory locations not listed in the clause. - -For a function contract, if no assigns clause is provided, the default is the -empty set. For a loop contract, if no assigns clause is provided, CBMC attempts -to infer the set of locations assigned by the loop from the loop body, -and then checks that the inferred set is correct (as if specified by the user). - -For both functions and loop contracts, if more than one assigns clause is -provided, their contents are unioned into a single clause. - -We use the _assigns_ interpretation for these memory locations, which means that -memory locations that are not listed in the assigns clause -(or the _inferred_ assigns clause for a loop contract) must not be assigned to -by the function (or the loop), even if they end up holding the same value as they -held before the function call (or before entering the loop). - -Memory locations that are locally stack- or heap-allocated during function -execution or loop execution can always be assigned to by the function or the -loop. +@tableofcontents ## 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(targets) ``` @@ -48,6 +25,13 @@ target ::= lvalue-expr | __CPROVER_object_upto(ptr-expr, uint-expr) ``` +The set of locations writable by a function is the union of the sets of locations +specified by its assigns clauses, or the empty set if 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. For function contracts, the condition and target expressions in the assigns clause may only involve function parameters, @@ -513,3 +497,18 @@ int foo() return rval; } ``` + +## Additional Resources + +- @ref contracts-functions + - @ref contracts-requires-ensures + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-loops + - @ref contracts-loop-invariants + - @ref contracts-decreases + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-memory-predicates +- @ref contracts-history-variables +- @ref contracts-quantifiers diff --git a/src/goto-instrument/contracts/doc/user/contracts-cli.md b/src/goto-instrument/contracts/doc/user/contracts-cli.md new file mode 100644 index 00000000000..f283851d389 --- /dev/null +++ b/src/goto-instrument/contracts/doc/user/contracts-cli.md @@ -0,0 +1,28 @@ +# Command Line Interface for Code Contracts {#contracts-user-cli} + +## Applying loop and/or function contracts transformations (without the dynamic frames method) + +The program transformation takes the following parameters: + +``` +goto-instrument [--apply-loop-contracts] [--enforce-contract f] (--replace-call-with-contract g)* in.gb out.gb +``` + +Where: +- `--apply-loop-contracts` is optional and specifies to apply loop contracts globally; +- `--enforce-contract f` is optional and specifies that `f` must be checked against its contract. +- `--replace-call-with-contract g` is optional and specifies that all calls to `g` must be replaced with its contract; + +## Applying the function contracts transformation (with the dynamic frames method) + +The program transformation takes the following parameters: + +``` +goto-instrument --dfcc harness [--enforce-contract f] (--replace-call-with-contract g)* [--apply-loop-contracts] in.gb out.gb +``` + +Where: +- `--dfcc harness` specifies the proof harness (i.e. the entry point of the analysis); +- `--enforce-contract f` is optional and specifies that `f` must be checked against its contract. +- `--replace-call-with-contract g` is optional and specifies that all calls to `g` must be replaced with its contract; + diff --git a/doc/cprover-manual/contracts-decreases.md b/src/goto-instrument/contracts/doc/user/contracts-decreases.md similarity index 88% rename from doc/cprover-manual/contracts-decreases.md rename to src/goto-instrument/contracts/doc/user/contracts-decreases.md index 0eb2fbc0e4e..1a90ab7fa04 100644 --- a/doc/cprover-manual/contracts-decreases.md +++ b/src/goto-instrument/contracts/doc/user/contracts-decreases.md @@ -1,6 +1,8 @@ -[CPROVER Manual TOC](../../) +# Decreases Clauses {#contracts-decreases} -# Decreases Clauses +Back to @ref contracts-user + +@tableofcontents A _decreases_ clause specifies a measure that must strictly decrease at every iteration of a loop. By demonstrating that the measure @@ -16,7 +18,7 @@ This technique for proving termination was proposed by Robert Floyd, and interested readers may refer to his seminal paper "[_Assigning Meaning to Programs_](https://people.eecs.berkeley.edu/~necula/Papers/FloydMeaning.pdf)". -### Syntax +## Syntax A one-dimensional (1D) decreases clause for a loop is an arithmetic expression `e` over the variables visible at the same scope as the loop, @@ -32,7 +34,7 @@ __CPROVER_decreases(n - i) { ... } ``` -Please see the [invariant clauses](../../contracts/invariants/) page +Please see the [loop invariant clauses](@ref contracts-loop-invariants) page for more examples on `for` and `do...while` loops. To help prove termination of more complex loops, @@ -69,9 +71,9 @@ Otherwise, CBMC raises an error message during compilation: Decreases clause is not side-effect free. (at: file main.c line 4 function main) ``` -### Semantics +## Semantics -A decreases clause extends the loop abstraction introduced in the [invariants clause](../../contracts/invariants/) manual. +A decreases clause extends the loop abstraction introduced in the [loop invariant clause](@ref contracts-loop-invariants) documentation. In addition to the inductiveness check asserted at the end of a single arbitrary iteration, CBMC would also assert the strict decrement of the measure specified in the decreases clause. At a high level, in addition to the assumptions and assertions introduced by the invariant clause, @@ -167,8 +169,23 @@ int binary_search(int val, int *buf, int size) The instrumented code points (5), (6), (8), and (9) are specific to the decreases clause. -**Important.** -Decreases clauses work in conjunction with [loop invariants](../../contracts/invariants/), +**Important.** +Decreases clauses work in conjunction with [loop invariant clauses](@ref contracts-loop-invariants), which model an arbitrary loop iteration at which the decreases clause is checked. If a decreases clause is annotated on a loop without an invariant clause, then the weakest possible invariant (i.e, `true`) is used to model an arbitrary iteration. + +## Additional Resources + +- @ref contracts-functions + - @ref contracts-requires-ensures + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-loops + - @ref contracts-loop-invariants + - @ref contracts-decreases + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-memory-predicates +- @ref contracts-history-variables +- @ref contracts-quantifiers diff --git a/doc/cprover-manual/contracts-frees.md b/src/goto-instrument/contracts/doc/user/contracts-frees.md similarity index 85% rename from doc/cprover-manual/contracts-frees.md rename to src/goto-instrument/contracts/doc/user/contracts-frees.md index 95351c65905..4229c842ba3 100644 --- a/doc/cprover-manual/contracts-frees.md +++ b/src/goto-instrument/contracts/doc/user/contracts-frees.md @@ -1,4 +1,4 @@ -[CPROVER Manual TOC](../../) +# Frees Clauses {#contracts-frees} # Frees Clauses @@ -67,15 +67,14 @@ pointers specified by the _frees clause_. ### For replacement of function calls by contracts When replacing a function call by a contract, each pointer of the -_frees clause_ is non-deterministically freed after the function call. +_frees clause_ gets non-deterministically freed between the evaluation of +preconditions and before the evaluation of post-conditions. ## Specifying parametric sets of freeable pointers using C functions Users can define parametric sets of freeable pointers by writing functions that -return the built-in type void and call the built-in function -`__CPROVER_freeable` (directly or indirectly through some other user-defined -function). The functions must be side-effect free and deterministic, -as well as loop-free and recursion-free. +return the `void` type and call (directly or indirectly) the built-in function +`__CPROVER_freeable`: ```c void my_freeable_set(char **arr, size_t size) @@ -90,18 +89,21 @@ void my_freeable_set(char **arr, size_t size) } ``` -The built-in function: +Calling the built-in function: ```c void __CPROVER_freeable(void *ptr); ``` -adds the given pointer to the freeable set described by the enclosing function. +in the context of a frees clause specifies that `ptr` is freeable in that +context. -Calls to such functions can be used as targets in `__CPROVER_frees` clauses: ```c void my_function(char **arr, size_t size) -__CPROVER_frees(my_freeable_set(arr, size)) +__CPROVER_frees( + // arr is considered freeable in the context of this clause. + my_freeable_set(arr, size) +) { - ... + // body ... } ``` diff --git a/doc/cprover-manual/contracts-functions.md b/src/goto-instrument/contracts/doc/user/contracts-functions.md similarity index 66% rename from doc/cprover-manual/contracts-functions.md rename to src/goto-instrument/contracts/doc/user/contracts-functions.md index f1bf5a6d09e..485d7bf4de7 100644 --- a/doc/cprover-manual/contracts-functions.md +++ b/src/goto-instrument/contracts/doc/user/contracts-functions.md @@ -1,19 +1,10 @@ -[CPROVER Manual TOC](../../) +# Function Contracts {#contracts-functions} -# Function Contracts +Back to @ref contracts-user -CBMC offers support for function contracts, which includes three basic clauses: -_requires_, _ensures_, and _assigns_. -These clauses formally describe the specification of a function. -CBMC also provides a series of built-in constructs to be used with functions -contracts (e.g., _history variables_, _quantifiers_, and _memory predicates_). +@tableofcontents -When a function contract is checked, the tool automatically havocs all static variables -of the program (to start the analysis in an arbitrary state), in the same way -as using `--nondet-static` would do. If one wishes not to havoc some static variables, -then `--nondet-static-exclude name-of-variable` can be used. ## Overview - Take a look at the example below. ```c @@ -31,36 +22,39 @@ int sum(const uint32_t a, const uint32_t b, uint32_t* out) return SUCCESS; } -int foo() +// the proof harness +int main() { uint32_t a; uint32_t b; uint32_t out; int rval = sum(a, b, &out); - if (rval == SUCCESS) - return out; - return rval; + return 0; } ``` Function `sum` writes the sum of `a` and `b` to `out`, and returns `SUCCESS`; -unless the result of the addition is too large to be represented as an `uint32_t`, in which case it returns `FAILURE`. Let's write -a function contract for this function. +unless the result of the addition is too large to be represented as an `uint32_t`, +in which case it returns `FAILURE`. +Let's write a function contract for this function. -A function contract has three parts: +Function contracts are specified by attaching specification clauses to function +declarations or definitions: +- @ref contracts-requires-ensures allow to specify preconditions and post conditions, +- @ref contracts-assigns allow to specify the assignable memory footprint of a function, +- @ref contracts-frees allow to specify the set of pointers that may be freed by the function. -- **Precondition** - describes what the function requires of the arguments - supplied by the caller and of global variables; -- **Postcondition** - describes the effect of the function; -- **Write Set** - describes the set of locations outside the function that - might be written to. +The following built-in constructs can also be used with functions contracts: +- @ref contracts-history-variables allow to refer to past versions of function parameters, +- @ref contracts-quantifiers allow to express quantified formulas, +- @ref contracts-memory-predicates allow to describe simple heap structures; In our example, the developer may require from the caller to properly allocate all arguments, thus, pointers must be valid. We can specify the preconditions of -a function using `__CPROVER_requires` (see [Requires \& Ensures -Clauses](../../contracts/requires-and-ensures/) for details) and we can +a function using `__CPROVER_requires` (see the [Requires \& Ensures +Clauses](../../contracts/requires-and-ensures/) documentation for details) and we can specify an allocated object using a predicate called `__CPROVER_is_fresh` (see -[Memory Predicate](../../contracts/memory-predicates/) for details). Thus, for the `sum` function, the set +[Memory Predicate](@ref contracts-memory-predicates) for details). Thus, for the `sum` function, the set of preconditions are ```c @@ -77,8 +71,8 @@ describe the main property of this function: if the function returns `SUCCESS`, then `*out` stores the result of `a + b`. We can also check that the value in `*out` will be preserved in case of failure by using `__CPROVER_old`, which refers to the value of a given object in the pre-state of a function (see -[History Variables](../../contracts/history-variables/) for details). Thus, for the `sum` function, the -set of postconditions are +[History Variables](@ref contracts-history-variables) for details). +Thus, for the `sum` function, the set of postconditions are ```c @@ -89,7 +83,7 @@ __CPROVER_ensures((__CPROVER_return_value == FAILURE) ==> (*out == __CPROVER_old ``` Finally, the _assigns_ clause allows developers to define a frame condition (see -[Assigns Clause](../../contracts/assigns/) for details). +[Assigns Clause](@ref contracts-assigns) for details). In general, systems for describing the frame condition of a function use either writes or modifies semantics; this design is based on the former. This means that memory not specified by the assigns clause must @@ -134,6 +128,13 @@ The first command just compiles the GOTO program as usual, the second command instruments the code to check the function satisfies the contract, and the third one runs CBMC to do the checking. +In order to start the analysis in an arbitrary state when a function contract +gets checked, the contract instrumentation pass automatically havocs all static +variables of the program. To avoid havocing certain static variables, +the command line switch `--nondet-static-exclude name-of-variable` can be passed +to `goto-instrument` in addition to the other swtiches specifying the contract +to check. + Now that we have proved that the function satisfies the contract, we can use the function contract in place of the function implementation wherever the function is called. @@ -151,8 +152,15 @@ program using contracts. ## Additional Resources -- [Requires \& Ensures Clauses](../../contracts/requires-and-ensures/) -- [Assigns Clause](../../contracts/assigns/) -- [Memory Predicates](../../contracts/memory-predicates/) -- [History Variables](../../contracts/history-variables/) -- [Quantifiers](../../contracts/quantifiers/) +- @ref contracts-functions + - @ref contracts-requires-ensures + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-loops + - @ref contracts-loop-invariants + - @ref contracts-decreases + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-memory-predicates +- @ref contracts-history-variables +- @ref contracts-quantifiers diff --git a/doc/cprover-manual/contracts-history-variables.md b/src/goto-instrument/contracts/doc/user/contracts-history-variables.md similarity index 67% rename from doc/cprover-manual/contracts-history-variables.md rename to src/goto-instrument/contracts/doc/user/contracts-history-variables.md index a68c46c1509..a82aef9be55 100644 --- a/doc/cprover-manual/contracts-history-variables.md +++ b/src/goto-instrument/contracts/doc/user/contracts-history-variables.md @@ -1,6 +1,8 @@ -[CPROVER Manual TOC](../../) +# History Variables {#contracts-history-variables} -# History Variables +Back to @ref contracts-user + +@tableofcontents ## In Function Contracts @@ -41,3 +43,18 @@ __CPROVER_assigns(*out) ## In Loop Contracts TODO: Document `__CPROVER_loop_entry` and `__CPROVER_loop_old`. + +## Additional Resources + +- @ref contracts-functions + - @ref contracts-requires-ensures + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-loops + - @ref contracts-loop-invariants + - @ref contracts-decreases + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-memory-predicates +- @ref contracts-history-variables +- @ref contracts-quantifiers diff --git a/doc/cprover-manual/contracts-invariants.md b/src/goto-instrument/contracts/doc/user/contracts-loop-invariants.md similarity index 89% rename from doc/cprover-manual/contracts-invariants.md rename to src/goto-instrument/contracts/doc/user/contracts-loop-invariants.md index 16c9e49583b..76db14076ad 100644 --- a/doc/cprover-manual/contracts-invariants.md +++ b/src/goto-instrument/contracts/doc/user/contracts-loop-invariants.md @@ -1,6 +1,8 @@ -[CPROVER Manual TOC](../../) +# Loop Invariant Clauses {#contracts-loop-invariants} -# Invariant Clauses +Back to @ref contracts-user + +@tableofcontents An _invariant_ clause specifies a property that must be preserved by every iteration of a loop. @@ -11,7 +13,7 @@ However, `true` is rarely useful -- it is an extremely imprecise abstraction of a loop, which is generally not _sufficient_ to prove any subsequent assertions. -### Syntax +## Syntax An _invariant_ clause accepts a valid Boolean expression over the variables visible at the same scope as the loop. @@ -53,7 +55,7 @@ Otherwise, CBMC raises an error message during compilation: Loop invariant is not side-effect free. (at: file main.c line 4 function main) ``` -### Semantics +## Semantics A loop invariant clause expands to several assumptions and assertions: 1. The invariant is _asserted_ just before the first iteration. @@ -143,7 +145,7 @@ A few things to note here: using alias analysis of l-values appearing in the loop body. However, the analysis is incomplete and may fail to characterize the set for complex loops. In such cases, the user must manually annotate the set of modified variables - using an [_assigns clause_](../../contracts/assigns/). + using an [_assigns clause_](@ref contracts-assigns). - At instrumented code point (6), when we _assume_ `false`, observe that this assumption only exists within the `lb <= ub` conditional. @@ -151,6 +153,17 @@ A few things to note here: The code outside of the conditional block continues to be symbolically executed, and subsequent assertions do not become vacuously `true`. -[history variables]: ../../contracts/history-variables/ - -[binary search]: https://en.wikipedia.org/wiki/Binary_search_algorithm +## Additional Resources + +- @ref contracts-functions + - @ref contracts-requires-ensures + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-loops + - @ref contracts-loop-invariants + - @ref contracts-decreases + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-memory-predicates +- @ref contracts-history-variables +- @ref contracts-quantifiers diff --git a/doc/cprover-manual/contracts-loops.md b/src/goto-instrument/contracts/doc/user/contracts-loops.md similarity index 82% rename from doc/cprover-manual/contracts-loops.md rename to src/goto-instrument/contracts/doc/user/contracts-loops.md index b20c5c6c813..3f9c27ef1b5 100644 --- a/doc/cprover-manual/contracts-loops.md +++ b/src/goto-instrument/contracts/doc/user/contracts-loops.md @@ -1,22 +1,19 @@ -[CPROVER Manual TOC](../../) +# Loop Contracts {#contracts-loops} -# Loop Contracts +Back to @ref contracts-user -CBMC offers support for loop contracts, which includes three basic clauses: +@tableofcontents -- _invariant_ clause for establishing safety properties -- _decreases_ clause for establishing termination, and -- _assigns_ clause for declaring the subset of variables that is modifiable in the loop. +CBMC offers support for loop contracts, which includes three basic clauses: +- an _invariant_ clause for establishing safety properties, +- a _decreases_ clause for establishing termination, +- an _assigns_ clause for declaring the memory locations assignable by the loop, +- a _frees_ clause for declaring the pointers freeable by the loop. These clauses formally describe an abstraction of a loop for the purpose of a proof. CBMC also provides a series of built-in constructs to aid writing loop contracts (e.g., _history variables_ and _quantifiers_). -When a function contract is checked, the tool automatically havocs all static variables -of the program (to start the analysis in an arbitrary state), in the same way -as using `--nondet-static` would do. If one wishes not to havoc some static variables, -then `--nondet-static-exclude name-of-variable` can be used. - ## Overview Consider an implementation of the [binary search algorithm] below. @@ -66,7 +63,7 @@ A developer might be interested in verifying two high-level properties on the lo 2. the loop must eventually always terminate To prove the first (memory-safety) property, -we may declare [_loop invariants_](../../contracts/invariants/) +we may declare a [_loop invariant_](@ref contracts-loop-invariants) that must be preserved across all loop iterations. In this case, two invariant clauses would together imply that `buf[mid]` lookup is always safe. The first invariant clause would establish that the bounds (`lb` and `ub`) are always valid: @@ -83,7 +80,7 @@ __CPROVER_loop_invariant(mid == (lb + ub) / 2L) ``` To prove the second (termination) property, -we may declare a [_decreases clause_](../../contracts/decreases/) +we may declare a [_decreases clause_](@ref contracts-decreases) that indicates a bounded numeric measure which must monotonically decrease with each loop iteration. In this case, it is easy to see that `lb` and `ub` are approaching closer together with each iteration, since either `lb` must increase or `ub` must decrease in each iteration. @@ -146,10 +143,17 @@ and finally we verify the instrumented GOTO binary with desired checks. ## Additional Resources -- [Assigns Clause](../../contracts/assigns/) -- [Decreases Clause](../../contracts/decreases/) -- [History Variables](../../contracts/history-variables/) -- [Invariant Clause](../../contracts/invariants/) -- [Quantifiers](../../contracts/quantifiers/) - [binary search algorithm]: https://en.wikipedia.org/wiki/Binary_search_algorithm + +- @ref contracts-functions + - @ref contracts-requires-ensures + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-loops + - @ref contracts-loop-invariants + - @ref contracts-decreases + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-memory-predicates +- @ref contracts-history-variables +- @ref contracts-quantifiers diff --git a/doc/cprover-manual/contracts-memory-predicates.md b/src/goto-instrument/contracts/doc/user/contracts-memory-predicates.md similarity index 85% rename from doc/cprover-manual/contracts-memory-predicates.md rename to src/goto-instrument/contracts/doc/user/contracts-memory-predicates.md index 318f70d2efb..5bfa5a10ee8 100644 --- a/doc/cprover-manual/contracts-memory-predicates.md +++ b/src/goto-instrument/contracts/doc/user/contracts-memory-predicates.md @@ -1,8 +1,10 @@ -[CPROVER Manual TOC](../../) +# Memory Predicates {#contracts-memory-predicates} -# Memory Predicates +Back to @ref contracts-user -### Syntax +@tableofcontents + +## Syntax ```c bool __CPROVER_is_fresh(void *p, size_t size); @@ -22,7 +24,7 @@ available at the pointer. It returns a `bool` value, indicating whether the pointer is fresh. -### Semantics +## Semantics To illustrate the semantics for `__CPROVER_is_fresh`, consider the following implementation of `sum` function. @@ -43,7 +45,7 @@ __CPROVER_assigns(*out, err_signal) } ``` -#### Enforcement +### Enforcement When checking the contract abstracts a function a `__CPROVER_is_fresh` in a _requires_ clause will cause fresh memory to be allocated. @@ -53,7 +55,7 @@ In an _ensures_ clause it will check that memory was freshly allocated. int *err_signal; // Global variable int __CPROVER_contracts_original_sum(const uint32_t a, const uint32_t b, uint32_t* out) -{ +{ const uint64_t result = ((uint64_t) a) + ((uint64_t) b); err_signal = malloc(sizeof(*err_signal)); if (!err_signal) return; @@ -72,7 +74,7 @@ int sum(const uint32_t a, const uint32_t b, uint32_t* out) } ``` -#### Replacement +### Replacement In our example, consider that a function `foo` may call `sum`. @@ -102,18 +104,33 @@ 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(); err_signal = nondet_int_pointer(); - + /* Postconditions */ __CPROVER_assume(__CPROVER_is_fresh(err_signal, sizeof(*err_signal))); // Assumes out is allocated return *err_signal; } ``` + +## Additional Resources + +- @ref contracts-functions + - @ref contracts-requires-ensures + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-loops + - @ref contracts-loop-invariants + - @ref contracts-decreases + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-memory-predicates +- @ref contracts-history-variables +- @ref contracts-quantifiers diff --git a/doc/cprover-manual/contracts-quantifiers.md b/src/goto-instrument/contracts/doc/user/contracts-quantifiers.md similarity index 82% rename from doc/cprover-manual/contracts-quantifiers.md rename to src/goto-instrument/contracts/doc/user/contracts-quantifiers.md index 7fa3bbe8767..8f356e4878b 100644 --- a/doc/cprover-manual/contracts-quantifiers.md +++ b/src/goto-instrument/contracts/doc/user/contracts-quantifiers.md @@ -1,8 +1,10 @@ -[CPROVER Manual TOC](../../) +# Quantifiers {#contracts-quantifiers} -# Quantifiers +Back to @ref contracts-user -### Syntax +@tableofcontents + +## Syntax ```c __CPROVER_forall { *type* *identifier*; *boolean expression* } @@ -30,7 +32,7 @@ or non-strict (e.g., `*upper bound* <= *id*`), but both the bounds **must** be constants. -### Semantics +## Semantics For `__CPROVER_forall` all `*type*` values for `*identifier*` must satisfy `*boolean expression*`. @@ -89,3 +91,18 @@ int bar_sat(int *arr, int len) /* at least one element in arr must be set to 1 */ } ``` + +## Additional Resources + +- @ref contracts-functions + - @ref contracts-requires-ensures + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-loops + - @ref contracts-loop-invariants + - @ref contracts-decreases + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-memory-predicates +- @ref contracts-history-variables +- @ref contracts-quantifiers diff --git a/src/goto-instrument/contracts/doc/user/contracts-requires-ensures.md b/src/goto-instrument/contracts/doc/user/contracts-requires-ensures.md new file mode 100644 index 00000000000..89b3474dd36 --- /dev/null +++ b/src/goto-instrument/contracts/doc/user/contracts-requires-ensures.md @@ -0,0 +1,191 @@ +# Requires and Ensures Clauses {#contracts-requires-ensures} + +Back to @ref contracts-user + +@tableofcontents + +## 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 function. Developers may see the +_requires_ clauses of a function as obligations on the caller when invoking the +function. The precondition of a function is the conjunction of the _requires_ +clauses, or `true` if none are specified. + +```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. + +Both _requires_ clauses and _ensures_ clauses take a Boolean expression over the +arguments of a function and/or global variables. The expression can include +calls to CBMC built-in functions or to +[Memory Predicates](@ref contracts-memory-predicates)). +User-defined functions can also be called inside _requires_ clauses as long as +they are deterministic and do not have any side-effects +(the absence of side effects is checked by the tool). In addition, _ensures_ +clauses also accept [History Variables](@ref contracts-history-variables) +and the special built-in symbol `__CPROVER_return_value` which represents the +return value of the function. + +## Semantics + +The semantics of _ensures_ and _requires_ clauses can be understood in two +contexts: enforcement and replacement. To illustrate these two perspectives, +consider the following implementation of the `sum` function. + +```c +int sum(const uint32_t a, const uint32_t b, uint32_t* out) +__CPROVER_requires(__CPROVER_is_fresh(out, sizeof(*out))) +__CPROVER_ensures( + __CPROVER_return_value == SUCCESS || __CPROVER_return_value == FAILURE) +__CPROVER_ensures( + (__CPROVER_return_value == SUCCESS) ==> (*out == (a + b))) +__CPROVER_assigns(*out) +{ + 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 +// The original sum function with a mangled name +int __CPROVER_contracts_original_sum( + const uint32_t a, const uint32_t b, uint32_t* out) +{ + const uint64_t result = ((uint64_t) a) + ((uint64_t) b); + if (result > UINT32_MAX) return FAILURE; + *out = (uint32_t) result; + return SUCCESS; +} + +/* Function contract enforcement wrapper */ +int sum(uint32_t a, uint32_t b, uint32_t* out) +{ + // assume the requires clause + __CPROVER_assume(__CPROVER_is_fresh(out, sizeof(*out))); + + // declare local to represetn __CPROVER_return_value + int __return_value; + + // call function under verification + __return_value = __CPROVER_contracts_original_sum(a, b, out); + + // check the first ensures clause + __CPROVER_assert( + __return_value == SUCCESS || __return_value == FAILURE, + "Check ensures clause"); + + // check the second requires clause + __CPROVER_assert( + (__return_value == SUCCESS) ==> (*out == (a + b)), + "Check ensures clause"); + + return __return_value; +} +``` + +### Replacement + +Assuming _requires_ and _ensures_ clauses are a sound abstraction of the +behavior of the function *f*, CBMC will use the function contract in place of +the function implementation as follows: + +1. Adds assertions for all preconditions specified in the `__CPROVER_requires` + clauses; +2. Adds non-deterministic assignments for each symbol listed in the + `__CPROVER_assigns` clause (see [Assigns Clauses](@ref contracts-assigns) + for details); +2. Adds non-deterministic free statements for each symbol listed in the + `__CPROVER_frees` clause (see [Frees Clauses](@ref contracts-frees) + 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 = 0; + int rval = sum(a, b, &out); + if (SUCCESS != rval) + return FAILURE; + + // else, use out + utin32_t result = out + ...; +} +``` + +CBMC will use the function contract in place of the function implementation +wherever the function is called. + +```c +int foo() +{ + uint32_t a = ...; + uint32_t b = ...; + uint32_t out = 0; + + // start of call-by-contract replacement + // check if preconditions hold + __CPROVER_assert( + __CPROVER_is_fresh(out, sizeof(*out)), "Check requires clause"); + + // nondet return value + int __return_value = nondet_int(); + + // assume postconditions + __CPROVER_assume(__return_value == SUCCESS || __return_value == FAILURE); + __CPROVER_assume((__return_value == SUCCESS) ==> (*out == (*a + *b))); + + int rval = __return_value; + // end of call-by-contract replacement + + if (SUCCESS != rval) + return FAILURE; + + // else, use out + utin32_t result = out + ...; +} +``` + +## Additional Resources + +- @ref contracts-functions + - @ref contracts-requires-ensures + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-loops + - @ref contracts-loop-invariants + - @ref contracts-decreases + - @ref contracts-assigns + - @ref contracts-frees +- @ref contracts-memory-predicates +- @ref contracts-history-variables +- @ref contracts-quantifiers diff --git a/src/goto-instrument/contracts/doc/user/contracts-user.md b/src/goto-instrument/contracts/doc/user/contracts-user.md new file mode 100644 index 00000000000..7c335835274 --- /dev/null +++ b/src/goto-instrument/contracts/doc/user/contracts-user.md @@ -0,0 +1,16 @@ +# Code Contracts User Documentation {#contracts-user} + + +CBMC supports @subpage contracts-functions and @subpage contracts-loops. + +The individual types of clauses for contracts are documented here: + +- @subpage contracts-requires-ensures +- @subpage contracts-assigns +- @subpage contracts-frees +- @subpage contracts-loop-invariants +- @subpage contracts-decreases +- @subpage contracts-memory-predicates +- @subpage contracts-history-variables +- @subpage contracts-quantifiers +- @subpage contracts-user-cli 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..4c45bac9839 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc.cpp @@ -0,0 +1,464 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking + +Author: Remi Delmas, delmarsd@amazon.com + +\*******************************************************************/ + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void dfcc( + const optionst &options, + goto_modelt &goto_model, + const irep_idt &harness_id, + const optionalt &to_check, + const bool allow_recursive_calls, + const std::set &to_replace, + const bool apply_loop_contracts, + const std::set &to_exclude_from_nondet_static, + message_handlert &message_handler) +{ + std::map to_replace_map; + for(const auto &function_id : to_replace) + to_replace_map.insert({function_id, function_id}); + + dfcc( + options, + goto_model, + harness_id, + to_check.has_value() + ? optionalt>( + std::pair(to_check.value(), to_check.value())) + : optionalt>{}, + allow_recursive_calls, + to_replace_map, + apply_loop_contracts, + to_exclude_from_nondet_static, + message_handler); +} + +void dfcc( + const optionst &options, + goto_modelt &goto_model, + const irep_idt &harness_id, + const optionalt> &to_check, + const bool allow_recursive_calls, + const std::map &to_replace, + const bool apply_loop_contracts, + const std::set &to_exclude_from_nondet_static, + message_handlert &message_handler) +{ + dfcct{ + options, + goto_model, + harness_id, + to_check, + allow_recursive_calls, + to_replace, + apply_loop_contracts, + message_handler, + to_exclude_from_nondet_static}; +} + +dfcct::dfcct( + const optionst &options, + goto_modelt &goto_model, + const irep_idt &harness_id, + const optionalt> &to_check, + const bool allow_recursive_calls, + const std::map &to_replace, + const bool apply_loop_contracts, + message_handlert &message_handler, + const std::set &to_exclude_from_nondet_static) + : options(options), + goto_model(goto_model), + harness_id(harness_id), + to_check(to_check), + allow_recursive_calls(allow_recursive_calls), + to_replace(to_replace), + apply_loop_contracts(apply_loop_contracts), + to_exclude_from_nondet_static(to_exclude_from_nondet_static), + message_handler(message_handler), + log(message_handler), + utils(goto_model, message_handler), + library(goto_model, utils, message_handler), + ns(goto_model.symbol_table), + instrument(goto_model, message_handler, utils, library), + spec_functions(goto_model, message_handler, utils, library, instrument), + contract_handler( + goto_model, + message_handler, + utils, + library, + instrument, + spec_functions), + swap_and_wrap( + goto_model, + message_handler, + utils, + library, + instrument, + spec_functions, + contract_handler), + max_assigns_clause_size(0) +{ + transform_goto_model(); +} + +void dfcct::check_transform_goto_model_preconditions() +{ + // check that harness function exists + PRECONDITION_WITH_DIAGNOSTICS( + utils.function_symbol_with_body_exists(harness_id), + "Harness function '" + id2string(harness_id) + + "' either not found or has no body"); + + if(to_check.has_value()) + { + auto pair = to_check.value(); + PRECONDITION_WITH_DIAGNOSTICS( + utils.function_symbol_with_body_exists(pair.first), + "Function to check '" + id2string(pair.first) + + "' either not found or has no body"); + + // triggers signature compatibility checking + contract_handler.get_pure_contract_symbol(pair.second); + + PRECONDITION_WITH_DIAGNOSTICS( + pair.first != harness_id, + "Function '" + id2string(pair.first) + + "' cannot be both be checked against a contract and be the harness"); + + PRECONDITION_WITH_DIAGNOSTICS( + pair.second != harness_id, + "Function '" + id2string(pair.first) + + "' cannot be both the contract to check and be the harness"); + + PRECONDITION_WITH_DIAGNOSTICS( + to_replace.find(pair.first) == to_replace.end(), + "Function '" + id2string(pair.first) + + "' cannot be both checked against contract and replaced by a contract"); + + PRECONDITION_WITH_DIAGNOSTICS( + !instrument.do_not_instrument(pair.first), + "CPROVER function or builtin '" + id2string(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), + "Function to replace '" + id2string(pair.first) + "' not found"); + + // triggers signature compatibility checking + contract_handler.get_pure_contract_symbol(pair.second); + + PRECONDITION_WITH_DIAGNOSTICS( + pair.first != harness_id, + "Function '" + id2string(pair.first) + + "' cannot both be replaced with a contract and be the harness"); + + PRECONDITION_WITH_DIAGNOSTICS( + pair.second != harness_id, + "Function '" + id2string(pair.first) + + "' cannot both be the contract to use for replacement and be the " + "harness"); + } +} + +void dfcct::partition_function_symbols( + std::set &contract_symbols, + std::set &other_symbols) +{ + // collect contract and other symbols + for(auto &entry : goto_model.symbol_table) + { + const symbolt &symbol = entry.second; + + // not a function symbol + if(symbol.type.id() != ID_code) + continue; + + // is it a pure contract ? + const irep_idt &sym_name = symbol.name; + if(symbol.is_property && has_prefix(id2string(sym_name), "contract::")) + { + contract_symbols.insert(sym_name); + } + else + { + // it is not a contract + other_symbols.insert(sym_name); + } + } +} + +void dfcct::link_model_and_load_dfcc_library() +{ + // 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); + + // this must be done before loading the dfcc lib + partition_function_symbols(pure_contract_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); +} + +void dfcct::instrument_harness_function() +{ + // 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); +} + +void dfcct::wrap_checked_function() +{ + // swap-and-wrap checked functions with contracts + if(to_check.has_value()) + { + const auto &pair = to_check.value(); + 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_check( + wrapper_id, + contract_id, + function_pointer_contracts, + allow_recursive_calls); + + if(other_symbols.find(wrapper_id) != other_symbols.end()) + other_symbols.erase(wrapper_id); + + // upate max contract size + const std::size_t assigns_clause_size = + contract_handler.get_assigns_clause_size(contract_id); + if(assigns_clause_size > max_assigns_clause_size) + max_assigns_clause_size = assigns_clause_size; + } +} + +void dfcct::wrap_replaced_functions() +{ + // 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_replace( + wrapper_id, contract_id, function_pointer_contracts); + + if(other_symbols.find(wrapper_id) != other_symbols.end()) + other_symbols.erase(wrapper_id); + } +} + +void dfcct::wrap_discovered_function_pointer_contracts() +{ + // swap-and-wrap function pointer contracts with themselves + for(const auto &fp_contract : function_pointer_contracts) + { + log.status() << "Discovered function pointer contract '" << fp_contract + << "'" << messaget::eom; + + // contracts for function pointers must be replaced with themselves + // so we need to check that: + // - the symbol exists as a function symbol + // - the symbol exists as a pure contract symbol + // - the function symbol is not already swapped for contract checking + // - the function symbol is not already swapped with another contract for + // replacement + + const auto str = id2string(fp_contract); + + // Is it already swapped with another function for contract checking ? + PRECONDITION_WITH_DIAGNOSTICS( + !to_check.has_value() || to_check.value().first != str, + "Function '" + str + + "' used as contract for function pointer cannot be itself the object " + "of a contract check."); + + // Is it already swapped with another function for contract checking ? + auto found = to_replace.find(str); + if(found != to_replace.end()) + { + PRECONDITION_WITH_DIAGNOSTICS( + found->first == found->second, + "Function '" + str + + "' used as contract for function pointer already the object of a " + "contract replacement with '" + + id2string(found->second) + "'"); + log.status() << "Function pointer contract '" << fp_contract + << "' already wrapped with itself in REPLACE mode" + << messaget::eom; + } + else + { + // we need to swap it with itself + PRECONDITION_WITH_DIAGNOSTICS( + utils.function_symbol_exists(str), + "Function pointer contract '" + str + "' not found."); + + // triggers signature compatibility checking + contract_handler.get_pure_contract_symbol(str); + + log.status() << "Wrapping function pointer contract '" << fp_contract + << "' with itself in REPLACE mode" << messaget::eom; + + swap_and_wrap.swap_and_wrap_replace( + fp_contract, fp_contract, function_pointer_contracts); + // remove it from the set of symbols to process + if(other_symbols.find(fp_contract) != other_symbols.end()) + other_symbols.erase(fp_contract); + } + } +} + +void dfcct::instrument_other_functions() +{ + // 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); + } + + goto_model.goto_functions.update(); + + if(to_check.has_value()) + { + 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); + } +} + +void dfcct::transform_goto_model() +{ + check_transform_goto_model_preconditions(); + link_model_and_load_dfcc_library(); + instrument_harness_function(); + wrap_checked_function(); + wrap_replaced_functions(); + wrap_discovered_function_pointer_contracts(); + instrument_other_functions(); + library.inhibit_front_end_builtins(); + + // TODO implement and use utils.inhibit_unreachable_functions(harness); + goto_model.goto_functions.update(); + + remove_skip(goto_model); + goto_model.goto_functions.update(); + + log.status() << "Removing unused functions" << messaget::eom; + + // This can prune too many functions if function pointers have not been + // yet been removed or if the entry point is not defined. + // Another solution would be to rewrite the bodies of functions that seem to + // be unreachable into assert(false);assume(false) + remove_unused_functions(goto_model, message_handler); + goto_model.goto_functions.update(); + + reinitialize_model(); +} + +void dfcct::reinitialize_model() +{ + // collect set of functions which are now instrumented + std::set instrumented_functions; + instrument.get_instrumented_functions(instrumented_functions); + swap_and_wrap.get_swapped_functions(instrumented_functions); + + log.status() << "Updating init function" << messaget::eom; + utils.create_initialize_function(); + goto_model.goto_functions.update(); + nondet_static(goto_model, to_exclude_from_nondet_static); + + // Initialize the map of instrumented functions by adding extra instructions + // to the harness function + auto &init_function = goto_model.goto_functions.function_map[harness_id]; + auto &body = init_function.body; + auto begin = body.instructions.begin(); + goto_programt payload; + library.add_instrumented_functions_map_init_instructions( + instrumented_functions, begin->source_location(), payload); + body.destructive_insert(begin, payload); + + // Define harness as the entry point, overriding any preexisting one. + log.status() << "Setting entry point to " << harness_id << messaget::eom; + // remove the CPROVER start function + goto_model.symbol_table.erase( + goto_model.symbol_table.symbols.find(goto_functionst::entry_point())); + // regenerate the CPROVER start function + generate_ansi_c_start_function( + utils.get_function_symbol(harness_id), + goto_model.symbol_table, + message_handler, + c_object_factory_parameterst(options)); + + goto_model.goto_functions.update(); +} 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..2e8521de6dc --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc.h @@ -0,0 +1,291 @@ +/*******************************************************************\ + +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) +/// +/// 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 "dfcc_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; +class optionst; + +#define FLAG_DFCC "dfcc" +#define OPT_DFCC "(" FLAG_DFCC "):" + +// clang-format off +#define HELP_DFCC \ + "--dfcc activate dynamic frame condition checking for function\n"\ + " contracts using given function as entry point" +// clang-format on + +// clang-format off +#define FLAG_ENFORCE_CONTRACT_REC "enforce-contract-rec" +#define OPT_ENFORCE_CONTRACT_REC "(" FLAG_ENFORCE_CONTRACT_REC "):" +#define HELP_ENFORCE_CONTRACT_REC \ + " --enforce-contract-rec wrap fun with an assertion of its contract\n"\ + " and assume recursive calls to fun satisfy \n"\ + " the contract" +// 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 options CLI options (used to lookup options for language config when +/// re-defining the model's entry point) +/// \param goto_model GOTO model to transform +/// \param harness_id proof harness name, must be the entry point of the model +/// \param to_check function to check against its contract +/// \param allow_recursive_calls Allow the checked function to be recursive +/// \param to_replace set of functions to replace with their contract +/// \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. +/// \param message_handler used for debug/warning/error messages +void dfcc( + const optionst &options, + goto_modelt &goto_model, + const irep_idt &harness_id, + const optionalt &to_check, + const bool allow_recursive_calls, + const std::set &to_replace, + const bool apply_loop_contracts, + const std::set &to_exclude_from_nondet_static, + message_handlert &message_handler); + +/// \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 options CLI options (used to lookup options for language config when +/// re-defining the model's entry point) +/// \param goto_model GOTO model to transform +/// \param harness_id Proof harness name, must be the entry point of the model +/// \param to_check (function,contract) pair for contract checking +/// \param allow_recursive_calls Allow the checked function to be recursive +/// \param to_replace Functions-to-contract mapping for replacement +/// \param apply_loop_contracts Spply loop contract transformations iff true +/// \param to_exclude_from_nondet_static Set of symbols to exclude when havocing +/// static program symbols. +/// \param message_handler used for debug/warning/error messages +void dfcc( + const optionst &options, + goto_modelt &goto_model, + const irep_idt &harness_id, + const optionalt> &to_check, + const bool allow_recursive_calls, + const std::map &to_replace, + const bool apply_loop_contracts, + const std::set &to_exclude_from_nondet_static, + message_handlert &message_handler); + +/// \ingroup dfcc-module +/// \brief Entry point into the contracts transformation +class dfcct +{ +public: + /// Class constructor + /// \param options CLI options (used to lookup options for language config + /// when re-defining the model's entry point) + /// \param goto_model GOTO model to transform + /// \param harness_id Proof harness name, must be the entry point of the model + /// \param to_check (function,contract) pair for contract checking + /// \param allow_recursive_calls Allow the checked function to be recursive + /// \param to_replace functions-to-contract mapping for replacement + /// \param apply_loop_contracts apply loop contract transformations iff true + /// havocing static program symbols. + /// \param message_handler used for debug/warning/error messages + /// \param to_exclude_from_nondet_static set of symbols to exclude when + dfcct( + const optionst &options, + goto_modelt &goto_model, + const irep_idt &harness_id, + const optionalt> &to_check, + const bool allow_recursive_calls, + const std::map &to_replace, + const bool apply_loop_contracts, + message_handlert &message_handler, + const std::set &to_exclude_from_nondet_static); + + /// 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. + /// + /// 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(); + +protected: + const optionst &options; + goto_modelt &goto_model; + const irep_idt &harness_id; + const optionalt> &to_check; + const bool allow_recursive_calls; + const std::map &to_replace; + const bool apply_loop_contracts; + const std::set &to_exclude_from_nondet_static; + message_handlert &message_handler; + messaget log; + + // 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_contract_handlert contract_handler; + dfcc_swap_and_wrapt swap_and_wrap; + + /// Tracks the maximum number of targets in any assigns clause handled in the + /// transformation (used to specialize/unwind loops in DFCC library functions) + std::size_t max_assigns_clause_size; + + // partition the set of functions of the goto_model + std::set pure_contract_symbols; + std::set other_symbols; + std::set function_pointer_contracts; + + /// 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 `to_exclude_from_nondet_static` exist in the model. + void check_transform_goto_model_preconditions(); + + /// Partitions the function symbols of the symbol table into pure contracts + /// and other function symbols symbols. + void partition_function_symbols( + std::set &pure_contract_symbols, + std::set &other_symbols); + + void link_model_and_load_dfcc_library(); + void instrument_harness_function(); + void wrap_checked_function(); + void wrap_replaced_functions(); + void wrap_discovered_function_pointer_contracts(); + void instrument_other_functions(); + + /// \brief Re-initialise the GOTO model. + /// + /// \details + /// The new entry point must be the proof harness function specified for + /// instrumentation. + /// + /// The "initialize" (aka INITIALIZE_FUNCTION) is the function that assigns + /// initial values to all statics of the model; + /// + /// The initialize function must do the following: + /// - assign a nondet value to all statics of the user program + /// - assign the specified initial value to all instrumentation statics + /// - add an entry in the static unbounded map of instrumented functions + /// for each instrumented function + /// + /// A call to `nondet_static` will re-generate the initialize function with + /// nondet values. The intrumentation statics will not get nondet initialised + /// by virtue of being tagged with ID_C_no_nondet_initialization. + /// + /// However, nondet_static expects instructions to be either function calls + /// or assignments with a symbol_exprt LHS. + /// Since entries for the instrumented function maps are not symbol_exprts but + /// index_exprts they need to be moved to the harness function. + /// + /// The "start" function (aka goto_functionst::entry_point()) is the + /// function from which SymEx starts. + /// + /// The start function must do the following: + /// - call the initialize function, + /// - generate nondet values for the arguments of the proof harness function + /// - call the harness function with said nondet arguments + /// + /// All of which can be done using a call to `generate_ansi_c_start_function`, + /// assuming the initialize function is already available in the symbol table. + /// + /// So we do the following: + /// - regenerate the "initialize" function + /// - call nondet_static + /// - add extra instructions at the end of the harness function for the + /// instrumented functions map + /// - call generate_ansi_c_start_function + /// Make all user-defined statics nondet. + /// Other statics added by the instrumentation will be unaffected because they + /// are tagged with ID_C_no_nondet_initialization when created + /// Update statics and static function maps + void reinitialize_model(); +}; + +#endif diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_functions.cpp b/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_functions.cpp new file mode 100644 index 00000000000..9fdfac71676 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_functions.cpp @@ -0,0 +1,411 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com +Date: August 2022 + +\*******************************************************************/ +#include "dfcc_contract_functions.h" + +#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_contract_functionst::dfcc_contract_functionst( + const symbolt &pure_contract_symbol, + goto_modelt &goto_model, + message_handlert &message_handler, + 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), + message_handler(message_handler), + log(message_handler), + 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_contract_functionst::get_spec_assigns_function_symbol() const +{ + return ns.lookup(spec_assigns_function_id); +} + +const symbolt & +dfcc_contract_functionst::get_spec_assigns_havoc_function_symbol() const +{ + return ns.lookup(spec_assigns_havoc_function_id); +} + +const symbolt &dfcc_contract_functionst::get_spec_frees_function_symbol() const +{ + return ns.lookup(spec_frees_function_id); +} + +const std::size_t dfcc_contract_functionst::get_nof_assigns_targets() const +{ + return nof_assigns_targets; +} + +const std::size_t dfcc_contract_functionst::get_nof_frees_targets() const +{ + return nof_frees_targets; +} + +void dfcc_contract_functionst::gen_spec_assigns_function() +{ + const auto &spec_function_symbol = utils.clone_and_rename_function( + pure_contract_symbol.name, spec_assigns_function_id, empty_typet()); + + const auto &spec_function_id = spec_function_symbol.name; + + auto &spec_code_type = to_code_type(spec_function_symbol.type); + + 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 + lambda_parameters.push_back( + symbol_exprt("dummy_return_value", code_with_contract.return_type())); + } + + 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()) + { + auto expr = to_lambda_expr(assigns_expr).application(lambda_parameters); + expr.add_source_location() = assigns_expr.source_location(); + 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(); + + inline_and_check_warnings(spec_function_id); + + PRECONDITION_WITH_DIAGNOSTICS( + utils.has_no_loops(spec_function_id), + "loops in assigns clause specification functions must be unwound before " + "model instrumentation"); + + goto_model.goto_functions.update(); +} + +void dfcc_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_contract_functionst::encode_assignable_target( + const exprt &target, + goto_programt &dest) +{ + const source_locationt &source_location = target.source_location(); + + if(can_cast_expr(target)) + { + // A function call target `foo(params)` becomes `CALL foo(params);` + // ``` + const auto &funcall = to_side_effect_expr_function_call(target); + 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()) + { + throw invalid_source_file_exceptiont{ + "no definite size for lvalue assigns clause target " + + from_expr_using_mode(ns, language_mode, target), + target.source_location()}; + } + // 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", empty_typet())); + 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(size.value()); + + // is_ptr_to_ptr + arguments.emplace_back(make_boolean_expr(target.type().id() == ID_pointer)); + + dest.add( + goto_programt::make_function_call(code_function_call, source_location)); + } + else + { + // any other type of target is unsupported + throw invalid_source_file_exceptiont( + "unsupported assigns clause target " + + from_expr_using_mode(ns, language_mode, target), + target.source_location()); + } +} + +void dfcc_contract_functionst::gen_spec_frees_function() +{ + // fetch pure contract symbol + const auto &code_with_contract = + to_code_with_contract_type(pure_contract_symbol.type); + + auto &spec_function_symbol = utils.clone_and_rename_function( + pure_contract_symbol.name, spec_frees_function_id, empty_typet()); + + const auto &spec_function_id = spec_function_symbol.name; + + auto &spec_code_type = to_code_type(spec_function_symbol.type); + + 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()) + { + auto expr = to_lambda_expr(frees_expr).application(lambda_parameters); + expr.add_source_location() = frees_expr.source_location(); + 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); + + PRECONDITION_WITH_DIAGNOSTICS( + utils.has_no_loops(spec_function_id), + "loops in frees clause specification functions must be unwound before " + "model instrumentation"); + + goto_model.goto_functions.update(); +} + +void dfcc_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(const auto &id : no_body) + { + INVARIANT( + library.is_front_end_builtin(id), + "no body for '" + id2string(id) + "' when inlining '" + + id2string(function_id) + "'"); + } + + for(auto it : missing_function) + { + INVARIANT( + library.is_front_end_builtin(it), + "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_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_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())) + { + // for calls to user-defined 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 + { + // any other type of target is unsupported + throw invalid_source_file_exceptiont( + "unsupported frees clause target " + + from_expr_using_mode(ns, language_mode, target), + target.source_location()); + } +} diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_functions.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_functions.h new file mode 100644 index 00000000000..523f4408f21 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_functions.h @@ -0,0 +1,160 @@ +/*******************************************************************\ + +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 function contract into goto +/// functions that allow to build and havoc write sets dynamically. + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_CONTRACT_FUNCTIONS_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_CONTRACT_FUNCTIONS_H + +#include + +#include +#include +#include +#include + +#include "dfcc_contract_mode.h" + +#include + +class goto_modelt; +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: +/// ```c +/// void 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 +/// void contract_id::frees( +/// function-params, +/// write_set_to_fill, +/// write_set_to_check); +/// ``` +class dfcc_contract_functionst +{ +public: + /// \param pure_contract_symbol the contract to generate code from + /// \param goto_model goto model being transformed + /// \param message_handler used debug/warning/error messages + /// \param utils utility class for dynamic frames + /// \param library the contracts instrumentation library + /// \param spec_functions provides translation methods for assignable set + /// or freeable set specification functions. + dfcc_contract_functionst( + const symbolt &pure_contract_symbol, + goto_modelt &goto_model, + message_handlert &message_handler, + 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 std::size_t get_nof_assigns_targets() const; + + /// Returns the maximum number of targets in the frees clause + const std::size_t 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; + message_handlert &message_handler; + messaget log; + dfcc_utilst &utils; + dfcc_libraryt &library; + dfcc_spec_functionst &spec_functions; + namespacet ns; + std::size_t nof_assigns_targets; + std::size_t nof_frees_targets; + + /// Translates the contract's assigns clause to a GOTO function that uses the + /// `assignable`, `object_upto`, `object_from`, object_whole` built-ins to + /// express targets. + void gen_spec_assigns_function(); + + /// Translates the contract's frees clause to a GOTO function that uses the + /// `freeable` built-in to express targets. + 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 + // void or __CPROVER_freeable_t functions, + /// and that no other warnings happened. + void inline_and_check_warnings(const irep_idt &function_id); +}; + +#endif diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_handler.cpp b/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_handler.cpp new file mode 100644 index 00000000000..1ff9513847d --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_handler.cpp @@ -0,0 +1,193 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com +Date: August 2022 + +\*******************************************************************/ + +#include "dfcc_contract_handler.h" + +#include +#include +#include +// #include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "dfcc_instrument.h" +#include "dfcc_library.h" +#include "dfcc_spec_functions.h" +#include "dfcc_utils.h" +#include "dfcc_wrapper_program.h" + +std::map + dfcc_contract_handlert::contract_cache; + +dfcc_contract_handlert::dfcc_contract_handlert( + goto_modelt &goto_model, + message_handlert &message_handler, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_instrumentt &instrument, + dfcc_spec_functionst &spec_functions) + : goto_model(goto_model), + message_handler(message_handler), + log(message_handler), + utils(utils), + library(library), + instrument(instrument), + spec_functions(spec_functions), + ns(goto_model.symbol_table) +{ +} + +const dfcc_contract_functionst & +dfcc_contract_handlert::get_contract_functions(const irep_idt &contract_id) +{ + auto iter = dfcc_contract_handlert::contract_cache.find(contract_id); + + // return existing value + if(iter != dfcc_contract_handlert::contract_cache.end()) + return iter->second; + + // insert new value + return dfcc_contract_handlert::contract_cache + .insert( + {contract_id, + dfcc_contract_functionst( + get_pure_contract_symbol(contract_id), + goto_model, + message_handler, + utils, + library, + spec_functions)}) + .first->second; +} + +const std::size_t +dfcc_contract_handlert::get_assigns_clause_size(const irep_idt &contract_id) +{ + return get_contract_functions(contract_id).get_nof_assigns_targets(); +} + +void dfcc_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_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, + message_handler, + utils, + library, + instrument) + .add_to_dest(dest, function_pointer_contracts); +} + +const symbolt & +dfcc_contract_handlert::get_pure_contract_symbol(const irep_idt &contract_id) +{ + const auto &contract_symbol = utils.get_function_symbol(contract_id); + auto pure_contract_id = "contract::" + id2string(contract_id); + const symbolt *pure_contract_symbol = nullptr; + if(!ns.lookup(pure_contract_id, pure_contract_symbol)) + { + check_signature_compat( + contract_id, + to_code_type(contract_symbol.type), + pure_contract_id, + to_code_type(pure_contract_symbol->type)); + return *pure_contract_symbol; + } + 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)); + INVARIANT( + entry.second, + "contract '" + id2string(contract_symbol.display_name()) + + "' already set at " + id2string(entry.first.location.as_string())); + // this lookup will work and set the pointer + // no need to check for signature compatibility + ns.lookup(pure_contract_id, pure_contract_symbol); + return *pure_contract_symbol; + } +} + +void dfcc_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) +{ + // can we turn a call to `contract` into a call to `pure_contract` ? + bool compatible = + function_is_type_compatible(true, contract_type, pure_contract_type, ns); + + if(!compatible) + { + std::ostringstream err_msg; + err_msg << "dfcc_contract_handlert: function '" << contract_id + << "' and the corresponding pure contract symbol '" + << pure_contract_id << "' have incompatible type signatures:\n"; + + err_msg << "- contract return type " + << from_type(ns, contract_id, contract_type.return_type()) << "\n"; + + for(const auto ¶m : contract_type.parameters()) + { + err_msg << "- contract param type " + << from_type(ns, contract_id, param.type()) << "\n"; + } + + err_msg << "- pure contract return type " + << from_type(ns, pure_contract_id, pure_contract_type.return_type()) + << "\n"; + + for(const auto ¶m : pure_contract_type.parameters()) + { + err_msg << "- pure contract param type " + << from_type(ns, pure_contract_id, param.type()) << "\n"; + } + + err_msg << "aborting." + << "\n"; + throw invalid_source_file_exceptiont( + err_msg.str(), contract_type.source_location()); + } +} 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..2d21a4c17f3 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_contract_handler.h @@ -0,0 +1,148 @@ +/*******************************************************************\ + +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 contracts + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_CONTRACT_HANDLER_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_CONTRACT_HANDLER_H + +#include + +#include +#include +#include +#include + +#include "dfcc_contract_functions.h" + +#include + +class goto_modelt; +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 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_contract_functionst). +/// +/// Translation results are cached so it is safe to call +/// `get_contract_functions` several times. +/// +/// This class also implements the \ref dfcc_contract_handlert interface +/// and allows to generate instructions modelling contract checking and +/// contract replacement. +class dfcc_contract_handlert +{ +public: + dfcc_contract_handlert( + goto_modelt &goto_model, + message_handlert &message_handler, + 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); + + /// Returns the size assigns clause of the given contract in number of targets + const std::size_t get_assigns_clause_size(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); + +protected: + goto_modelt &goto_model; + message_handlert &message_handler; + messaget log; + dfcc_utilst &utils; + dfcc_libraryt &library; + dfcc_instrumentt &instrument; + dfcc_spec_functionst &spec_functions; + namespacet ns; + + // Caches the functions generated from contracts + static std::map contract_cache; + + /// Returns the `dfcc_contract_functionst` object for the given contract + /// from the cache, creates it if it does not exists. + const dfcc_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_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 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..3691afa56c6 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_instrument.cpp @@ -0,0 +1,1227 @@ +/*******************************************************************\ + +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 + +#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, + message_handlert &message_handler, + dfcc_utilst &utils, + dfcc_libraryt &library) + : goto_model(goto_model), + message_handler(message_handler), + log(message_handler), + utils(utils), + library(library), + ns(goto_model.symbol_table) +{ + // these come from different assert.h implementation on different systems + // and eventually become ASSERT instructions and must not be instrumented + internal_symbols.insert("__assert_fail"); + internal_symbols.insert("_assert"); + internal_symbols.insert("__assert_c99"); + internal_symbols.insert("_wassert"); + internal_symbols.insert("__assert_rtn"); + internal_symbols.insert("__assert"); + internal_symbols.insert("__assert_func"); + + /// These builtins are translated to no-ops and must not be instrumented + internal_symbols.insert("__builtin_prefetch"); + internal_symbols.insert("__builtin_unreachable"); + + /// These builtins are valist management functions + /// and interpreted natively in src/goto-symex/symex_builtin_functions.cpp + /// and must not be instrumented + internal_symbols.insert(ID_gcc_builtin_va_arg); + internal_symbols.insert("__builtin_va_copy"); + internal_symbols.insert("__builtin_va_start"); + internal_symbols.insert("__va_start"); + internal_symbols.insert("__builtin_va_end"); + + /// These builtins get translated to CPROVER equivalents + /// and must not be instrumented + internal_symbols.insert("__builtin_isgreater"); + internal_symbols.insert("__builtin_isgreaterequal"); + internal_symbols.insert("__builtin_isless"); + internal_symbols.insert("__builtin_islessequal"); + internal_symbols.insert("__builtin_islessgreater"); + internal_symbols.insert("__builtin_isunordered"); +} + +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"}; +*/ + +/// True iff the symbol must not be instrumented +bool dfcc_instrumentt::is_internal_symbol(const irep_idt &id) const +{ + return internal_symbols.find(id) != internal_symbols.end(); +} + +/// 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 +{ + std::string str = id2string(id); + return has_prefix(str, CPROVER_PREFIX) || has_prefix(str, "__VERIFIER") || + has_prefix(str, "nondet") || has_suffix(str, "$object"); +} + +bool dfcc_instrumentt::do_not_instrument(const irep_idt &id) const +{ + return !has_prefix(id2string(id), CPROVER_PREFIX "file_local") && + (is_cprover_symbol(id) || is_internal_symbol(id)); +} + +void dfcc_instrumentt::instrument_harness_function(const irep_idt &function_id) +{ + bool inserted = dfcc_instrumentt::function_cache.insert(function_id).second; + if(!inserted) + return; + + const null_pointer_exprt null_expr( + 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, message_handler); + is_fresh.rewrite_calls(body, null_expr); + + // rewrite is_freeable/was_freed calls + dfcc_is_freeablet is_freeable(library, message_handler); + 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(); +} + +std::set +dfcc_instrumentt::get_local_statics(const irep_idt &function_id) +{ + std::set local_statics; + for(const auto &sym_pair : goto_model.symbol_table) + { + const auto &sym = sym_pair.second; + if(sym.is_static_lifetime) + { + const auto &loc = sym.location; + if(!loc.get_function().empty() && loc.get_function() == function_id) + { + local_statics.insert(sym.symbol_expr()); + } + } + } + return local_statics; +} + +void dfcc_instrumentt::instrument_function(const irep_idt &function_id) +{ + // use same name for local static search + instrument_function(function_id, function_id); +} + +void dfcc_instrumentt::instrument_wrapped_function( + const irep_idt &wrapped_function_id, + const irep_idt &initial_function_id) +{ + // use the initial name name for local static search + instrument_function(wrapped_function_id, initial_function_id); +} + +void dfcc_instrumentt::instrument_function( + const irep_idt &function_id, + const irep_idt &function_id_for_local_static_search) +{ + // never instrument a function twice + bool inserted = dfcc_instrumentt::function_cache.insert(function_id).second; + if(!inserted) + return; + + auto found = goto_model.goto_functions.function_map.find(function_id); + PRECONDITION_WITH_DIAGNOSTICS( + found != goto_model.goto_functions.function_map.end(), + "Function '" + id2string(function_id) + "' must exist in the model."); + + 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]); + + std::set local_statics = + get_local_statics(function_id_for_local_static_search); + + instrument_function_body( + function_id, write_set.symbol_expr(), cfg_info, local_statics); +} + +void dfcc_instrumentt::instrument_function_body( + const irep_idt &function_id, + const exprt &write_set, + cfg_infot &cfg_info, + const std::set &local_statics) +{ + auto &goto_function = goto_model.goto_functions.function_map.at(function_id); + + if(!goto_function.body_available()) + { + log.warning() << "DFCC instrumentation: '" << function_id + << "' body is not available. Results may be unsound if the " + "actual function has 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 + {}); + + // insert add/remove instructions for local statics + auto begin = body.instructions.begin(); + auto end = std::prev(body.instructions.end()); + + for(const auto &local_static : local_statics) + { + // automatically add local statics to the write set + insert_add_allocated_call( + function_id, write_set, local_static, begin, body); + + // automatically remove local statics to the write set + insert_record_dead_call(function_id, write_set, local_static, end, body); + } + + // cleanup + remove_skip(body); + + // recalculate numbers, etc. + goto_model.goto_functions.update(); + + // add loop ids + goto_model.goto_functions.compute_loop_numbers(); +} + +void dfcc_instrumentt::instrument_goto_program( + const irep_idt &function_id, + goto_programt &goto_program, + const exprt &write_set) +{ + 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); +} + +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) +{ + // rewrite is_fresh calls + dfcc_is_fresht is_fresh(library, message_handler); + is_fresh.rewrite_calls( + goto_program, first_instruction, last_instruction, write_set); + + // rewrite is_freeable/was_freed calls + dfcc_is_freeablet is_freeable(library, message_handler); + is_freeable.rewrite_calls( + goto_program, first_instruction, last_instruction, write_set); + + const namespacet ns(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()) + { + instrument_dead(function_id, write_set, target, goto_program, cfg_info); + } + 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++; + } +} + +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(), + "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); + + return retval; +} + +void dfcc_instrumentt::insert_add_allocated_call( + const irep_idt &function_id, + const exprt &write_set, + const symbol_exprt &symbol_expr, + goto_programt::targett &target, + goto_programt &goto_program) +{ + 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(symbol_expr)}}, + 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); +} + +/// \details +/// ```c +/// DECL x; +/// ---- +/// insert_add_allocated_call(...); +/// ``` +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; + + const auto &decl_symbol = target->decl_symbol(); + target++; + insert_add_allocated_call( + function_id, write_set, decl_symbol, target, goto_program); + target--; +} + +void dfcc_instrumentt::insert_record_dead_call( + const irep_idt &function_id, + const exprt &write_set, + const symbol_exprt &symbol_expr, + goto_programt::targett &target, + goto_programt &goto_program) +{ + 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(symbol_expr)}}, + 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); +} + +/// \details +/// ```c +/// insert_record_dead_call(...); +/// ---- +/// DEAD x; +/// ``` +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; + + const auto &decl_symbol = target->dead_symbol(); + insert_record_dead_call( + function_id, write_set, decl_symbol, target, goto_program); +} + +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) +{ + 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"); + + return false; + } + + 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"); + check_source_location.set_comment( + "Check that " + from_expr_using_mode(ns, mode, lhs) + " is assignable"); + + 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 = utils.create_symbol( + bool_typet(), + id2string(function_id), + "__check_lhs_assignment", + lhs_source_location, + mode, + utils.get_function_symbol(function_id).module, + false); + + 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)); + + 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. +optionalt +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 {}; + } +} + +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 + const auto dead_ptr = is_dead_object_update(lhs, rhs); + if(dead_ptr.has_value()) + { + // ``` + // 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.value()}}, + 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--; + } + + // 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, unsigned_char_type())); + 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())) + { + // pass write set argument only if function is known to be instrumented + target->call_arguments().push_back(write_set); + } + } + else + { + // This is a function pointer call. We insert a dynamic lookup into the + // map of instrumented functions to determine if the target function is + // able to accept a write set parameter. + instrument_fptr_call_instruction_dynamic_lookup( + write_set, target, goto_program); + } + } +} + +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) +{ + 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 || statement == ID_array_copy) + { + const bool is_array_set = 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), + is_array_set ? "__check_array_set" : "__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, target_location)); + + const auto &dest = target->get_other().operands().at(0); + + symbolt &check_fun = + library.dfcc_fun_symbol + [is_array_set ? dfcc_funt::WRITE_SET_CHECK_ARRAY_SET + : dfcc_funt::WRITE_SET_CHECK_ARRAY_COPY]; + payload.add(goto_programt::make_function_call( + code_function_callt{ + check_var, check_fun.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 fun_name = is_array_set ? "array_set" : "array_copy"; + + std::string comment = "Check that " + fun_name + "(" + + 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_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..001a2e4ab0c --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_instrument.h @@ -0,0 +1,357 @@ +/*******************************************************************\ + +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 + +#include "dfcc_contract_mode.h" + +#include +#include + +class goto_modelt; +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, + message_handlert &message_handler, + 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); + + /// \brief Instruments a GOTO function by adding an extra write set parameter + /// and inserting frame condition checks in its GOTO program, as well as + /// instructions to automatically insert and remove locally declared static + /// variables in the write set. + /// + /// \pre The function must *not* be one of the checked or replaced functions. + /// For checked/replaced functions \ref instrument_wrapped_function must be + /// used instead. + /// \param function_id The name of the function, used to retrieve the function + /// to instrument and used as prefix when generating new symbols during + /// instrumentation. + void instrument_function(const irep_idt &function_id); + + /// \brief Instruments a GOTO function by adding an extra write set parameter + /// and inserting frame condition checks in its GOTO program, as well as + /// instructions to automatically insert and remove locally declared static + /// variables in the write set. + /// + /// \pre The function must be a function wrapped for contract checking or + /// replacemend. For other functions \ref instrument_function must be used + /// instead. + /// + /// \param wrapped_function_id The name of the function, used to retrieve the + /// function to instrument and used as prefix when generating new symbols + /// during instrumentation. + /// \param initial_function_id The initial name of the function, + /// before mangling. This is the name used to identify statics symbols in the + /// symbol table that were locally declared in the function. + void instrument_wrapped_function( + const irep_idt &wrapped_function_id, + const irep_idt &initial_function_id); + + /// \brief Instruments a GOTO program against a given write set variable. + /// + /// \remark Only variables declared within the instruction sequence are + /// considered local and automatically assignable. In particular, occurrences + /// of symbols with the `is_parameter` which represent parameters of the + /// enclosing function are not considered as local to the program. + /// \remark Local statics declared in the program are *not* searched for and + /// are *not* added automatically to the write set. + /// \remark This function is meant to instrument instruction sequences that + /// were generated from contract clauses. + /// + /// \param function_id Name used as prefix when creating new symbols during + /// instrumentation. + /// \param goto_program Goto program to instrument. + /// \param write_set Write set variable to use for instrumentation. + void instrument_goto_program( + const irep_idt &function_id, + goto_programt &goto_program, + const exprt &write_set); + + /// Adds the names of instrumented functions to \p dest. + /// The names are kept track of in the \ref function_cache field. + void get_instrumented_functions(std::set &dest) const; + +protected: + goto_modelt &goto_model; + message_handlert &message_handler; + messaget log; + dfcc_utilst &utils; + dfcc_libraryt &library; + namespacet ns; + + /// \brief Keeps track of instrumented functions, so that no function gets + /// instrumented more than once. + static std::set function_cache; + + /// \brief Set of internal symbols which implementation is generated and + /// inlined into the model at conversion or symex time and must not be + /// instrumented + std::set internal_symbols; + + /// \brief Returns the set of names from the symbol table that + /// have the static flag set to true and have a source location where the + /// function field is equal to the given \p function_id . + /// \param[in] function_id Function name used to collect the statics. + std::set get_local_statics(const irep_idt &function_id); + + /// \brief Generates a guarded call to record a locally allocated symbol + /// and inserts it in the goto_program at the target, and moves the target + /// forward. + /// ``` + /// IF !write_set GOTO skip_target; + /// CALL __CPROVER_contracts_write_set_add_allocated(write_set, &x); + /// skip_target: SKIP; + /// ``` + /// \param function_id Name of the function in which the instructions is added + /// \param write_set The write set to the symbol expr to + /// \param symbol_expr The symbol to add to the write set + /// \param target The instruction pointer to insert at + /// \param goto_program the goto_program being instrumented + void insert_add_allocated_call( + const irep_idt &function_id, + const exprt &write_set, + const symbol_exprt &symbol_expr, + goto_programt::targett &target, + goto_programt &goto_program); + + /// \brief Generates a guarded call to record the death of a local symbol + /// and inserts it in the goto_program at the target, and moves the target + /// forward. + /// ```c + /// IF !write_set GOTO skip_target; + /// CALL __CPROVER_contracts_write_set_record_dead(write_set, &x); + /// skip_target: SKIP; + /// ``` + /// \param function_id Name of the function in which the instructions is added + /// \param write_set The write set to the symbol expr to + /// \param symbol_expr The symbol to add to the write set + /// \param target The instruction pointer to insert at + /// \param goto_program the goto_program being instrumented + void insert_record_dead_call( + const irep_idt &function_id, + const exprt &write_set, + const symbol_exprt &symbol_expr, + goto_programt::targett &target, + goto_programt &goto_program); + + /// Instruments a GOTO function by adding an extra write set parameter and + /// inserting frame condition checks in its goto program. + /// Uses \p function_id_for_local_static_search to search for local statics + /// and automatically to add/remove to the write set. + void instrument_function( + const irep_idt &function_id, + const irep_idt &function_id_for_local_static_search); + + /// Instruments the body of a GOTO function against a given write set. + /// Adds the given local statics to the write set in pre and removes them + /// post. + void instrument_function_body( + const irep_idt &function_id, + const exprt &write_set, + cfg_infot &cfg_info, + const std::set &local_statics); + + /// \brief Instruments the instructions found between \p first_instruction and + /// \p last_instruction in the instructions of \p goto_program against the + /// given \p write_set variable. + /// + /// \param function_id Name of the enclosing function used as prefix for new + /// variables generated during instrumentation. + /// \param write_set Write set variable to instrument against + /// \param goto_program Program to instrument the instructions of + /// \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 \p pred is not provided, + /// all instructions are instrumented. If \p pred is provided, only + /// instructions satisfying \p pred are instrumented. + 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); + + /// Returns `true` if the symbol `x` in `DECL x` or `DEAD x` must be added + /// explicitly to the write set. Returns `false` when assignments to `x` must + /// be implicitly allowed. + bool must_track_decl_or_dead( + const goto_programt::targett &target, + const cfg_infot &cfg_info) const; + + /// Instruments a `DECL x` instruction. + /// \pre \p target points to a `DECL` 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. + /// \pre \p target points to a `DEAD` 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. + /// Returns false if the assignment must be implicitly allowed. + /// Works in tandem with \ref must_track_decl_or_dead + 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); + + /// \brief Instruments the LHS of an assignment instruction instruction by + /// adding an inclusion check of \p lhs in \p write_set. + 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 \p lhs is the `dead_object`, and if \p rhs + /// is an `if_exprt(nondet, ptr, dead_object)` expression. + /// Returns a pointer to the `ptr` expression if the pattern was matched, + /// returns `nullptr` otherwise. + optionalt is_dead_object_update(const exprt &lhs, const exprt &rhs); + + /// Instrument the \p lhs of an `ASSIGN lhs := rhs` instruction by + /// adding an inclusion check of \p lhs in \p write_set. + /// If \ref is_dead_object_update returns a successfull match, the matched + /// pointer expression is removed from \p write_set. + /// If \p rhs is a `side_effect_expr(ID_allocate)`, the allocated pointer gets + /// added to the \p write_set. + /// \pre \p target points to an `ASSIGN` instruction. + void instrument_assign( + const irep_idt &function_id, + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program, + cfg_infot &cfg_info); + + /// Adds the \p write_set as extra argument to a function of function pointer + /// call instruction. + /// \pre \p target points to a `CALL` instruction. + void instrument_call_instruction( + const exprt &write_set, + goto_programt::targett &target, + goto_programt &goto_program); + + /// Before calling a function pointer, performs a dynamic lookup into + /// the map of instrumented function provided by + /// \ref dfcc_libraryt.get_instrumented_functions_map_symbol, + /// and passes the write_set parameter to the funciton pointer only if + /// it points to a function known to be instrumented and hence able to accept + /// this parameter. + /// \pre \p target points to a `CALL` instruction where the function + /// expression is not a \ref symbol_exprt. + 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. + /// \pre \p target points to a `CALL __CPROVER_deallocate` instruction. + 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. + /// \pre \p target points to a `CALL` instruction. + 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. + /// \pre \p target points to an `OTHER` 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 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..b02a286ab8e --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_is_freeable.cpp @@ -0,0 +1,82 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com +Date: August 2022 + +\*******************************************************************/ +#include "dfcc_is_freeable.h" + +#include +#include + +#include "dfcc_library.h" + +dfcc_is_freeablet::dfcc_is_freeablet( + dfcc_libraryt &library, + message_handlert &message_handler) + : library(library), message_handler(message_handler) +{ +} + +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 "was_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_WAS_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::WAS_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..8c5a1ab4c8c --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_is_freeable.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_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 + +class goto_modelt; +class message_handlert; +class dfcc_libraryt; +class exprt; + +/// Rewrites calls to is_freeable and was_freed predicates in goto programs +/// encoding pre and post conditions. +class dfcc_is_freeablet +{ +public: + /// \param library the contracts instrumentation library + /// \param message_handler used for messages + dfcc_is_freeablet(dfcc_libraryt &library, message_handlert &message_handler); + + /// Rewrites calls to is_freeable and was_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 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; + message_handlert &message_handler; + 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..cabf39696e8 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_is_fresh.cpp @@ -0,0 +1,69 @@ +/*******************************************************************\ + +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, + message_handlert &message_handler) + : library(library), message_handler(message_handler), log(message_handler) +{ +} + +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, + 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_fresh") + { + // 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(library.dfcc_fun_symbol[dfcc_funt::IS_FRESH].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..dfe2394a9ec --- /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 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 + +class goto_modelt; +class message_handlert; +class dfcc_libraryt; +class exprt; + +/// Rewrites calls to is_fresh predicates into calls +/// to the library implementation. +class dfcc_is_fresht +{ +public: + /// \param library The contracts instrumentation library + /// \param message_handler Used for messages + dfcc_is_fresht(dfcc_libraryt &library, message_handlert &message_handler); + + /// Rewrites calls to is_fresh 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 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, + const exprt &write_set); + +protected: + dfcc_libraryt &library; + message_handlert &message_handler; + messaget log; +}; + +#endif 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..91ba47ecc1b --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_library.cpp @@ -0,0 +1,544 @@ +/*******************************************************************\ + +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" + +/// 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; +} + +// NOLINTNEXTLINE(build/deprecated) +#define CONTRACTS_PREFIX CPROVER_PREFIX "contracts_" + +/// Creates the enum to type name mapping +const std::map create_dfcc_type_to_name() +{ + return std::map{ + {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"}}; +} + +const std::map create_dfcc_fun_to_name() +{ + return { + {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_RELEASE, CONTRACTS_PREFIX "obj_set_release"}, + {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_OBJECT_WHOLE, + CONTRACTS_PREFIX "write_set_insert_object_whole"}, + {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_OBJECT_WHOLE, + CONTRACTS_PREFIX "write_set_havoc_object_whole"}, + {dfcc_funt::WRITE_SET_HAVOC_SLICE, + CONTRACTS_PREFIX "write_set_havoc_slice"}, + {dfcc_funt::LINK_IS_FRESH, CONTRACTS_PREFIX "link_is_fresh"}, + {dfcc_funt::LINK_ALLOCATED, CONTRACTS_PREFIX "link_allocated"}, + {dfcc_funt::LINK_DEALLOCATED, CONTRACTS_PREFIX "link_deallocated"}, + {dfcc_funt::IS_FRESH, CONTRACTS_PREFIX "is_fresh"}, + {dfcc_funt::IS_FREEABLE, CONTRACTS_PREFIX "is_freeable"}, + {dfcc_funt::WAS_FREED, CONTRACTS_PREFIX "was_freed"}, + {dfcc_funt::REPLACE_ENSURES_WAS_FREED_PRECONDITIONS, + CONTRACTS_PREFIX "check_replace_ensures_was_freed_preconditions"}}; +} + +const std::map create_dfcc_hook() +{ + return { + {CPROVER_PREFIX "assignable", dfcc_funt::WRITE_SET_INSERT_ASSIGNABLE}, + {CPROVER_PREFIX "object_whole", dfcc_funt::WRITE_SET_INSERT_OBJECT_WHOLE}, + {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}}; +} + +const std::map create_havoc_hook() +{ + return { + {CPROVER_PREFIX "assignable", + dfcc_funt::WRITE_SET_HAVOC_GET_ASSIGNABLE_TARGET}, + {CPROVER_PREFIX "object_whole", dfcc_funt::WRITE_SET_HAVOC_OBJECT_WHOLE}, + {CPROVER_PREFIX "object_from", dfcc_funt::WRITE_SET_HAVOC_SLICE}, + {CPROVER_PREFIX "object_upto", dfcc_funt::WRITE_SET_HAVOC_SLICE}}; +} + +const std::set create_assignable_builtin_names() +{ + return { + CPROVER_PREFIX "assignable", + CPROVER_PREFIX "assignable_set_insert_assignable", + CPROVER_PREFIX "object_whole", + CPROVER_PREFIX "assignable_set_insert_object_whole", + 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"}; +} + +/// Class constructor +dfcc_libraryt::dfcc_libraryt( + goto_modelt &goto_model, + dfcc_utilst &utils, + message_handlert &message_handler) + : goto_model(goto_model), + utils(utils), + message_handler(message_handler), + log(message_handler), + dfcc_type_to_name(create_dfcc_type_to_name()), + dfcc_name_to_type(swap_map(dfcc_type_to_name)), + dfcc_fun_to_name(create_dfcc_fun_to_name()), + dfcc_name_to_fun(swap_map(dfcc_fun_to_name)), + dfcc_hook(create_dfcc_hook()), + havoc_hook(create_havoc_hook()), + assignable_builtin_names(create_assignable_builtin_names()) +{ + // Add the instrumented map symbol to the symbol table. + get_instrumented_functions_map_symbol(); +} + +/// Returns the instrumentation function to use for a given front-end function +bool dfcc_libraryt::is_front_end_builtin(const irep_idt &function_id) const +{ + return dfcc_hook.find(function_id) != dfcc_hook.end(); +} + +/// Returns the instrumentation function to use for a given front-end function +dfcc_funt dfcc_libraryt::get_hook(const irep_idt &function_id) const +{ + PRECONDITION(is_front_end_builtin(function_id)); + return dfcc_hook.find(function_id)->second; +} + +// 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 {}; +} + +std::set dfcc_libraryt::get_missing_funs() +{ + std::set missing; + + // add `malloc` since it is needed used by the `is_fresh` function + missing.insert("malloc"); + + // add `free` and `__CPROVER_deallocate` since they are used by the + // `write_set_deallocate_freeable` + missing.insert("free"); + + // used by `write_set_release` + missing.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 "object_whole"); + missing.insert(CPROVER_PREFIX "freeable"); + + // go over all library functions + 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); + } + } + return missing; +} + +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; + + // these will need to get instrumented as well + to_instrument.insert("malloc"); + to_instrument.insert("free"); + to_instrument.insert(CPROVER_PREFIX "deallocate"); + + std::set to_load; + + // add the whole library + to_load.insert(CPROVER_PREFIX "contracts_library"); + + // add front end functions + to_load.insert(CPROVER_PREFIX "assignable"); + to_load.insert(CPROVER_PREFIX "object_from"); + to_load.insert(CPROVER_PREFIX "object_upto"); + to_load.insert(CPROVER_PREFIX "object_whole"); + to_load.insert(CPROVER_PREFIX "freeable"); + + // add stdlib dependences + to_load.insert("malloc"); + to_load.insert("free"); + to_load.insert(CPROVER_PREFIX "deallocate"); + + symbol_tablet tmp_symbol_table; + cprover_c_library_factory_force_load( + to_load, tmp_symbol_table, message_handler); + + // compute missing library functions before modifying the symbol table + std::set missing = get_missing_funs(); + + // copy all loaded 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)) + goto_model.symbol_table.insert(symbol_pair.second); + } + + // compile all missing library functions to GOTO + for(const auto &id : missing) + { + 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); + + INVARIANT( + found != goto_model.goto_functions.function_map.end() && + found->second.body_available(), + "The body of DFCC library function " + id2string(pair.second) + + " could not be found"); + + 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_OBJECT_WHOLE, + 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() +{ + INVARIANT(!inlined, "inline_functions can only be called once"); + inlined = true; + for(const auto &function_id : to_inline) + { + utils.inline_function(dfcc_fun_to_name.at(function_id)); + } +} + +/// 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}, + {dfcc_funt::WRITE_SET_RECORD_DEALLOCATED, 0}}; + +bool dfcc_libraryt::specialized = false; + +void dfcc_libraryt::specialize(const std::size_t contract_assigns_size) +{ + INVARIANT( + !specialized, + "dfcc_libraryt::specialize_functions can only be called once"); + + specialized = true; + unwindsett unwindset{goto_model}; + std::list loop_names; + + 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(); + loop_names.push_back(str); + } + unwindset.parse_unwindset(loop_names, message_handler); + goto_unwindt goto_unwind; + goto_unwind( + goto_model, unwindset, goto_unwindt::unwind_strategyt::ASSERT_ASSUME); +} + +/// 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_FRESH}; + +/// 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)) + { + // 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); +} + +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(unsigned_char_type(), infinity_exprt(size_type())); + + return utils.create_static_symbol( + map_type, + "", + "__dfcc_instrumented_functions", + source_locationt{}, + ID_C, + "", + array_of_exprt(from_integer(0, map_type.element_type()), map_type), + true); +} + +void dfcc_libraryt::add_instrumented_functions_map_init_instructions( + const std::set &instrumented_functions, + const source_locationt &source_location, + 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, unsigned_char_type()), source_location)); + } + 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..465dd3c4a06 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_library.h @@ -0,0 +1,279 @@ +/*******************************************************************\ + +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 +#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 +{ + /// 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_release + OBJ_SET_RELEASE, + /// \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_object_whole + WRITE_SET_INSERT_OBJECT_WHOLE, + /// \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_object_whole + WRITE_SET_HAVOC_OBJECT_WHOLE, + /// \see __CPROVER_contracts_write_set_havoc_slice + WRITE_SET_HAVOC_SLICE, + /// \see __CPROVER_contracts_link_is_fresh + LINK_IS_FRESH, + /// \see __CPROVER_contracts_link_allocated + LINK_ALLOCATED, + /// \see __CPROVER_contracts_link_deallocated + LINK_DEALLOCATED, + /// \see __CPROVER_contracts_is_fresh + IS_FRESH, + /// \see __CPROVER_contracts_is_freeable + IS_FREEABLE, + /// \see __CPROVER_contracts_was_freed + WAS_FREED, + /// \see __CPROVER_contracts_check_replace_ensures_was_freed_preconditions + REPLACE_ENSURES_WAS_FREED_PRECONDITIONS +}; + +class goto_modelt; +class goto_functiont; +class goto_programt; +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, + message_handlert &lmessage_handler); + +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; + message_handlert &message_handler; + messaget log; + + /// Collects the names of all library functions currently missing from the + /// goto_model into `missing`. + std::set get_missing_funs(); + + /// 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(); + +private: + /// Enum to type name mapping + const std::map dfcc_type_to_name; + + /// Swapped dfcc_type_to_name + const std::map dfcc_name_to_type; + + /// enum to function name mapping + const std::map dfcc_fun_to_name; + + // Swapped dfcc_fun_to_name + const std::map dfcc_name_to_fun; + + /// Maps built-in function names to enums to use for instrumentation + const std::map dfcc_hook; + + /// Maps front-end functions to library functions giving their havoc semantics + const std::map havoc_hook; + + /// All built-in function names (front-end and instrumentation hooks) + const std::set assignable_builtin_names; + +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 std::size_t contract_assigns_size_hint); + + /// Adds an ASSERT(false) body to all front-end functions + /// __CPROVER_object_whole + /// __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 true iff the given function_id is one of `__CPROVER_assignable`, + /// `__CPROVER_object_whole`, `__CPROVER_object_from`, + /// `__CPROVER_object_upto`, `__CPROVER_freeable` + bool is_front_end_builtin(const irep_idt &function_id) const; + + /// Returns the library instrumentation hook for the given front-end function. + /// \pre \p function_id is a front end built-in as defined by + /// \ref is_front_end_builtin. + dfcc_funt 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_object_whole`, `__CPROVER_object_from`, `__CPROVER_object_upto` + optionalt get_havoc_hook(const irep_idt &function_id) const; + + /// \brief 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. + /// Initialisation instructions for this map are generated by + /// \ref add_instrumented_functions_map_init_instructions + 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, + const source_locationt &source_location, + goto_programt &dest); +}; + +#endif 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..1ef72e3491f --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_spec_functions.cpp @@ -0,0 +1,418 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking + +Author: Remi Delmas, delmarsd@amazon.com + +\*******************************************************************/ + +#include "dfcc_spec_functions.h" +#include + +#include +#include + +#include + +#include + +#include "dfcc_library.h" +#include "dfcc_utils.h" + +dfcc_spec_functionst::dfcc_spec_functionst( + goto_modelt &goto_model, + message_handlert &message_handler, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_instrumentt &instrument) + : goto_model(goto_model), + message_handler(message_handler), + log(message_handler), + utils(utils), + library(library), + instrument(instrument), + ns(goto_model.symbol_table) +{ +} + +std::set dfcc_spec_functionst::collect_spec_assigns_functions( + const std::vector &candidates) +{ + const std::set functions = { + CPROVER_PREFIX "assignable", + CPROVER_PREFIX "object_whole", + CPROVER_PREFIX "object_from", + CPROVER_PREFIX "object_upto"}; + std::set dest; + collect_functions_that_call(candidates, functions, dest); + return dest; +} + +std::set dfcc_spec_functionst::collect_spec_frees_functions( + const std::vector &candidates) +{ + std::set dest; + const std::set functions = {CPROVER_PREFIX "freeable"}; + collect_functions_that_call(candidates, functions, dest); + return dest; +} + +void dfcc_spec_functionst::collect_functions_that_call( + const std::vector &candidates, + const std::set &functions, + std::set &dest) +{ + bool changed = true; + while(changed) + { + changed = false; + for(const auto &function_id : candidates) + { + const auto &it = goto_model.goto_functions.function_map.find(function_id); + if(dest.find(function_id) == dest.end() && it->second.body_available()) + { + changed |= + insert_if_calls(function_id, it->second.body, functions, dest); + } + } + } +} + +bool dfcc_spec_functionst::insert_if_calls( + const irep_idt &function_id, + const goto_programt &goto_program, + const std::set &functions, + std::set &dest) +{ + PRECONDITION(dest.find(function_id) == dest.end()); + bool insert = false; + for(const auto &it : goto_program.instructions) + { + if(it.is_function_call() && it.call_function().id() == ID_symbol) + { + const irep_idt &called_function = + to_symbol_expr(it.call_function()).get_identifier(); + + insert = functions.find(called_function) != functions.end() || + dest.find(called_function) != dest.end(); + } + } + if(insert) + dest.insert(function_id); + return insert; +} + +const typet &dfcc_spec_functionst::get_target_type(const exprt &expr) +{ + INVARIANT( + expr.id() == ID_typecast && expr.type().id() == ID_pointer && + expr.operands().at(0).id() == ID_address_of, + "target expression must be of the form `cast(address_of(target), empty*)`"); + + return expr.operands().at(0).operands().at(0).type(); +} + +void dfcc_spec_functionst::generate_havoc_function( + const irep_idt &function_id, + const irep_idt &havoc_function_id, + std::size_t &nof_targets) +{ + INVARIANT( + !goto_model.symbol_table.has_symbol(havoc_function_id), + "DFCC: havoc function id '" + id2string(havoc_function_id) + + "' already exists"); + + 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[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(); + bool add_function_failed = goto_model.symbol_table.add(havoc_function_symbol); + INVARIANT( + !add_function_failed, + "DFCC: could not insert havoc function '" + id2string(havoc_function_id) + + "' in the symbol table"); + + // 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 + std::size_t 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) + { + throw invalid_source_file_exceptiont( + "Function pointer call '" + + from_expr(ns, function_id, ins_it->call_function()) + + "' in function '" + id2string(function_id) + "' is not supported", + ins_it->source_location()); + } + + 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); + INVARIANT( + hook_opt.has_value(), + "dfcc_spec_functionst::generate_havoc_function: function calls must " + "be inlined before calling this function"); + + // Use same source location as original call + source_locationt location(ins_it->source_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) + { + // ``` + // DECL __havoc_target; + // CALL __havoc_target = havoc_hook(set, next_idx); + // IF !__havoc_target GOTO label; + // ASSIGN *__havoc_target = nondet(target_type); + // label: DEAD __havoc_target; + // ``` + // 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", + 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 = body.add(goto_programt::make_dead(target_expr)); + goto_instruction->complete_goto(label); + } + else if( + hook == dfcc_funt::WRITE_SET_HAVOC_OBJECT_WHOLE || + 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; + } + 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.empty(), + "no body warnings when inlining " + id2string(havoc_function_id)); + INVARIANT( + missing_function.empty(), + "missing function warnings when inlining " + id2string(havoc_function_id)); + INVARIANT( + recursive_call.empty(), + "recursive calls when inlining " + id2string(havoc_function_id)); + INVARIANT( + not_enough_arguments.empty(), + "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, + std::size_t &nof_targets) +{ + // counts the number of calls to built-ins to get an over approximation + // of the size of the set + std::size_t 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) + { + throw invalid_source_file_exceptiont( + "Function pointer call '" + + from_expr(ns, function_id, ins_it->call_function()) + + "' in function '" + id2string(function_id) + "' is not supported", + ins_it->source_location()); + } + + 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 + INVARIANT( + library.is_front_end_builtin(callee_id), + "dfcc_spec_functionst::to_spec_assigns_function: function calls must " + "be inlined before calling this function"); + + auto hook = library.get_hook(callee_id); + // redirect the call to the hook + ins_it->call_function() = library.dfcc_fun_symbol.at(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 for insert assignable + if(hook == dfcc_funt::WRITE_SET_INSERT_ASSIGNABLE) + ins_it->call_arguments().pop_back(); + + ++next_idx; + } + } + + 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); +} + +void dfcc_spec_functionst::to_spec_frees_function( + const irep_idt &function_id, + std::size_t &nof_targets) +{ + // counts the number of calls to the `freeable` builtin + std::size_t 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) + { + throw invalid_source_file_exceptiont( + "Function pointer call '" + + from_expr(ns, function_id, ins_it->call_function()) + + "' in function '" + id2string(function_id) + "' is not supported", + ins_it->source_location()); + } + + 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 + INVARIANT( + callee_id == CPROVER_PREFIX "freeable", + "dfcc_spec_functionst::to_spec_frees_function: function calls must " + "be inlined before calling this function"); + + 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; + } + } + + 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); +} 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..beccd8723d7 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_spec_functions.h @@ -0,0 +1,180 @@ +/*******************************************************************\ + +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 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_object_whole`, +/// - `__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, + message_handlert &message_handler, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_instrumentt &instrument); + + /// From a function: + /// + /// ``` + /// void 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, + std::size_t &nof_targets); + + /// Transforms (in place) a function + /// + /// ``` + /// void function_id(params); + /// ``` + /// + /// into a function + /// + /// ``` + /// void 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, + std::size_t &nof_targets); + + /// Transforms (in place) a function + /// + /// ``` + /// void function_id(params); + /// ``` + /// + /// into a function + /// + /// ``` + /// void 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, std::size_t &nof_targets); + + /// \brief Returns the subset of \p candidates that call built-ins + /// `assignable`, `object_from`, `object_upto`, `object_whole` directly or + /// indirectly. + std::set + collect_spec_assigns_functions(const std::vector &candidates); + + /// \brief Returns the subset of \p candidates that call the built-in + /// `freeable` directly or indirectly. + std::set + collect_spec_frees_functions(const std::vector &candidates); + +protected: + goto_modelt &goto_model; + message_handlert &message_handler; + messaget log; + dfcc_utilst &utils; + dfcc_libraryt &library; + dfcc_instrumentt &instrument; + namespacet ns; + + /// Extracts the type of an assigns clause target expression + /// The expression must be of the form: + /// `expr = cast(address_of(target), empty*)` + const typet &get_target_type(const exprt &expr); + + /// \brief Collect functions that call one of the given \p functions directly + /// or indirectly into \p dest. May miss functions if function pointers + /// calls are not already removed. + void collect_functions_that_call( + const std::vector &candidates, + const std::set &functions, + std::set &dest); + + /// \brief Adds \p function_id to \p dest if a call to a function that's + /// either in \p functions or in \p dest is found in \p goto_program. + /// \return true if \p function_id was added to \p dest, false otherwise. + /// \pre dest does not already contain \p function_id + bool insert_if_calls( + const irep_idt &function_id, + const goto_programt &goto_program, + const std::set &functions, + std::set &dest); +}; + +#endif 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..1b598f83686 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_swap_and_wrap.cpp @@ -0,0 +1,314 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking + +Author: Remi Delmas, delmarsd@amazon.com + +\*******************************************************************/ + +#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, + message_handlert &message_handler, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_instrumentt &instrument, + dfcc_spec_functionst &spec_functions, + dfcc_contract_handlert &contract_handler) + : goto_model(goto_model), + message_handler(message_handler), + log(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) +{ + auto pair = cache.insert({function_id, {contract_id, contract_mode}}); + auto inserted = pair.second; + + if(!inserted) + { + auto old_contract_id = pair.first->second.first; + auto old_contract_mode = pair.first->second.second; + + // different swapp already performed, abort + if(old_contract_id != contract_id || old_contract_mode != contract_mode) + { + auto mode1 = (old_contract_mode == dfcc_contract_modet::REPLACE) + ? "REPLACE" + : "CHECK"; + auto mode2 = + (contract_mode == dfcc_contract_modet::REPLACE) ? "REPLACE" : "CHECK)"; + + std::ostringstream err_msg; + err_msg << "DFCC: multiple attempts to swap and wrap function '" + << function_id << "':\n"; + err_msg << "- with '" << old_contract_id << "' in mode " << mode1 << "\n"; + err_msg << "- with '" << contract_id << "' in mode " << mode2 << "\n"; + throw invalid_input_exceptiont(err_msg.str()); + } + // same swap already performed + return; + } + + // actually perform the translation + 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; + } + } +} + +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); + } +} + +/// \details Generates globals statics: +/// ```c +/// static bool __contract_check_in_progress = false; +/// static bool __contract_checked_once = false; +/// ``` +/// +/// Adds the following instructions in the wrapper function body: +/// ```c +/// IF __contract_check_in_progress GOTO replace; +/// ASSERT !__contract_checked_once "only a single top-level called allowed"; +/// __contract_check_in_progress = true; +/// ; +/// __contract_checked_once = true; +/// __contract_check_in_progress = false; +/// GOTO end; +/// replace: +/// // if allow_recursive_calls +/// ; +/// // if !allow_recursive_calls +/// ASSERT false, "no recursive calls"; +/// ASSUME false; +/// 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) +{ + // 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_in_progress", + 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_checked_once", + 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, wrapper_symbol.location)); + + // 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(wrapper_symbol.location); + sl.set_function(wrapper_symbol.name); + sl.set_property_class("single_top_level_call"); + sl.set_comment( + "Only a single top-level call to function " + id2string(function_id) + + " 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(), wrapper_symbol.location)); + + 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(), wrapper_symbol.location)); + body.add(goto_programt::make_assignment( + check_started, false_exprt(), wrapper_symbol.location)); + + // unconditionally Jump to the end after the check + auto goto_end_function = + body.add(goto_programt::make_incomplete_goto(wrapper_symbol.location)); + + // Jump to the replacement section if check already in progress + auto contract_replacement_label = + body.add(goto_programt::make_skip(wrapper_symbol.location)); + 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(wrapper_symbol.location); + sl.set_function(wrapper_symbol.name); + sl.set_property_class("no_recursive_call"); + sl.set_comment( + "No recursive call to function " + id2string(function_id) + + " when checking contract " + id2string(contract_id)); + body.add(goto_programt::make_assertion(false_exprt(), sl)); + body.add( + goto_programt::make_assumption(false_exprt(), wrapper_symbol.location)); + } + + auto end_function_label = + body.add(goto_programt::make_end_function(wrapper_symbol.location)); + 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_wrapped_function(wrapped_id, wrapper_id); + + goto_model.goto_functions.update(); +} + +void dfcc_swap_and_wrapt::replace_with_contract( + const irep_idt &function_id, + const irep_idt &contract_id, + std::set &function_pointer_contracts) +{ + 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.get_function_symbol(wrapper_id).location)); + + 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); +} 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..c3a6eefa385 --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_swap_and_wrap.h @@ -0,0 +1,121 @@ +/*******************************************************************\ + +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 + +#include "dfcc_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, + message_handlert &message_handler, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_instrumentt &instrument, + dfcc_spec_functionst &spec_functions, + dfcc_contract_handlert &contract_handler); + + void swap_and_wrap_check( + const irep_idt &function_id, + const irep_idt &contract_id, + std::set &function_pointer_contracts, + bool allow_recursive_calls) + { + swap_and_wrap( + dfcc_contract_modet::CHECK, + function_id, + contract_id, + function_pointer_contracts, + allow_recursive_calls); + } + + void swap_and_wrap_replace( + const irep_idt &function_id, + const irep_idt &contract_id, + std::set &function_pointer_contracts) + { + swap_and_wrap( + dfcc_contract_modet::REPLACE, + function_id, + contract_id, + function_pointer_contracts, + false); + } + + /// Adds the set of swapped functions to dest + void get_swapped_functions(std::set &dest) const; + +protected: + goto_modelt &goto_model; + message_handlert &message_handler; + messaget log; + 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; + + 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); + + /// 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 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..bfc012effdc --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_utils.cpp @@ -0,0 +1,539 @@ +/*******************************************************************\ + +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 + +#include + +dfcc_utilst::dfcc_utilst( + goto_modelt &goto_model, + message_handlert &message_handler) + : goto_model(goto_model), + message_handler(message_handler), + log(message_handler), + ns(goto_model.symbol_table) +{ +} + +const bool dfcc_utilst::symbol_exists( + const irep_idt &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 irep_idt &function_id) +{ + return symbol_exists(function_id, true, false); +} + +const bool +dfcc_utilst::function_symbol_with_body_exists(const irep_idt &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 irep_idt &prefix, + const irep_idt &base_name, + const source_locationt &source_location, + const irep_idt &mode, + const irep_idt &module, + bool is_parameter) +{ + symbolt &symbol = get_fresh_aux_symbol( + type, + id2string(prefix), + id2string(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 irep_idt &prefix, + const irep_idt &base_name, + const source_locationt &source_location, + const irep_idt &mode, + const irep_idt &module, + const exprt &initial_value, + const bool no_nondet_initialization) +{ + symbolt &symbol = get_fresh_aux_symbol( + type, + id2string(prefix), + id2string(base_name), + source_location, + mode, + goto_model.symbol_table); + symbol.is_static_lifetime = true; + symbol.value = initial_value; + symbol.value.set(ID_C_no_nondet_initialization, no_nondet_initialization); + 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 irep_idt &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 irep_idt &base_name, + const typet &type) +{ + 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) +{ + 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); + + 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(); + + INVARIANT( + !goto_model.symbol_table.add(new_function_symbol), + "DFCC: renamed function " + id2string(new_function_id) + " already exists"); + + // 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); +} + +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 + 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; + bool add_failed = goto_model.symbol_table.add(new_param_symbol); + INVARIANT( + !add_failed, + "DFCC: renamed parameter " + id2string(new_base_name) + + " already exists"); + } +} + +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: 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); + + // Remove previous entry + goto_functions.function_map.erase(old_function); + + // Add new symbol for the wrapped function in the symbol table + const symbolt *old_sym = symbol_table.lookup(function_id); + source_locationt sl(old_sym->location); + sl.set_file("wrapped functions for code contracts"); + sl.set_line("0"); + 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: 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 + bool remove_failed = goto_model.symbol_table.remove(function_id); + INVARIANT( + !remove_failed, + "DFCC: could not remove wrapped function '" + id2string(function_id) + + "' from the symbol table"); + + // 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: could not insert wrapper 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) +{ + const auto &size = + size_of_expr(expr.type(), namespacet(goto_model.symbol_table)); + + if(!size.has_value()) + { + throw invalid_source_file_exceptiont( + "DFCC: no definite size for lvalue target" + from_expr(expr), + expr.source_location()); + } + return size.value(); +} + +exprt dfcc_utilst::make_map_start_address(const exprt &expr) +{ + return typecast_exprt::conditional_cast( + address_of_exprt(index_exprt( + expr, from_integer(0, to_array_type(expr.type()).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(); +} + +bool dfcc_utilst::has_no_loops(const irep_idt &function_id) +{ + auto &goto_function = goto_model.goto_functions.function_map.at(function_id); + return is_loop_free(goto_function.body, ns, log); +} + +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); + } + } +} + +void dfcc_utilst::inhibit_unused_functions(const irep_idt &start) +{ + PRECONDITION_WITH_DIAGNOSTICS(false, "not yet implemented"); +} 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..0c2c42a2c1d --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_utils.h @@ -0,0 +1,236 @@ +/*******************************************************************\ + +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 + +#include + +class goto_modelt; +class message_handlert; +class symbolt; + +class dfcc_utilst +{ +public: + dfcc_utilst(goto_modelt &goto_model, message_handlert &message_handler); + +protected: + goto_modelt &goto_model; + message_handlert &message_handler; + messaget log; + namespacet ns; + +public: + /// Returns true iff the given symbol exists and satisfies requirements. + const bool symbol_exists( + const irep_idt &function_id, + const bool require_has_code_type = false, + const bool require_body_available = false); + + const bool function_symbol_exists(const irep_idt &function_id); + const bool function_symbol_with_body_exists(const irep_idt &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 irep_idt &prefix, + const irep_idt &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. + /// By default the symbol is be protected against nondet initialisation + /// by tagging the its value field with a ID_C_no_nondet_initialization + /// attribute as understood by \ref nondet_static. + /// This is because the static instrumentation variables are meant to + /// keep their initial values for the instrumentation to work as intended. + /// To allow nondet-initialisation of the symbol, + /// just set \p no_nondet_initialization to `false` when calling the method. + const symbolt &create_static_symbol( + const typet &type, + const irep_idt &prefix, + const irep_idt &base_name, + const source_locationt &source_location, + const irep_idt &mode, + const irep_idt &module, + const exprt &initial_value, + const bool no_nondet_initialization = true); + + /// 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 irep_idt &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 irep_idt &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); + + /// \returns True iff \p function_id is loop free. + bool has_no_loops(const irep_idt &function_id); + + /// \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); + + /// \brief Traverses the call tree from the given entry point to identify + /// functions symbols that are effectively called in the model, + /// Then goes over all functions of the model and turns the bodies of all + /// functions that are not in the used function set into: + /// ```c + /// assert(false, "function identified as unreachable"); + /// assume(false); + /// ``` + /// That way, if the analysis mistakenly pruned some functions, assertions + /// will be violated and the analysis will fail. + /// TODO: add a command line flag to tell the instrumentation to not prune + /// a function. + /// \param start name of the entry point + void inhibit_unused_functions(const irep_idt &start); +}; + +#endif diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_wrapper_program.cpp b/src/goto-instrument/contracts/dynamic-frames/dfcc_wrapper_program.cpp new file mode 100644 index 00000000000..d0b5df0913d --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_wrapper_program.cpp @@ -0,0 +1,1099 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com + +\*******************************************************************/ +#include "dfcc_wrapper_program.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "dfcc_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" + +/// Generate the contract write set +const symbol_exprt create_contract_write_set( + dfcc_utilst &utils, + dfcc_libraryt &library, + const symbolt &wrapper_symbol) +{ + return 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) + .symbol_expr(); +} + +/// Generate the contract write set pointer +const symbol_exprt create_addr_of_contract_write_set( + dfcc_utilst &utils, + dfcc_libraryt &library, + const symbolt &wrapper_symbol) +{ + return 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) + .symbol_expr(); +} + +// Generate the write set to check for side effects in requires clauses +const symbol_exprt create_requires_write_set( + dfcc_utilst &utils, + dfcc_libraryt &library, + const symbolt &wrapper_symbol) +{ + return 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) + .symbol_expr(); +} + +/// Generate the write set pointer to check side effects in requires clauses +const symbol_exprt create_addr_of_requires_write_set( + dfcc_utilst &utils, + dfcc_libraryt &library, + const symbolt &wrapper_symbol) +{ + return 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) + .symbol_expr(); +} + +/// Generate the write set to check side effects in ensures clauses +const symbol_exprt create_ensures_write_set( + dfcc_utilst &utils, + dfcc_libraryt &library, + const symbolt &wrapper_symbol) +{ + return 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) + .symbol_expr(); +} + +/// Generate the write set pointer to check side effects in ensures clauses +const symbol_exprt create_addr_of_ensures_write_set( + dfcc_utilst &utils, + dfcc_libraryt &library, + const symbolt &wrapper_symbol) +{ + return 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) + .symbol_expr(); +} + +/// Generate object set used to support is_fresh predicates +const symbol_exprt create_is_fresh_set( + dfcc_utilst &utils, + dfcc_libraryt &library, + const symbolt &wrapper_symbol) +{ + return utils + .create_symbol( + library.dfcc_type[dfcc_typet::OBJ_SET], + id2string(wrapper_symbol.name), + "__is_fresh_set", + wrapper_symbol.location, + wrapper_symbol.mode, + wrapper_symbol.module, + false) + .symbol_expr(); +} + +/// Generate object set pointer used to support is_fresh predicates +const symbol_exprt create_addr_of_is_fresh_set( + dfcc_utilst &utils, + dfcc_libraryt &library, + const symbolt &wrapper_symbol) +{ + return utils + .create_symbol( + library.dfcc_type[dfcc_typet::OBJ_SET_PTR], + id2string(wrapper_symbol.name), + "__address_of_is_fresh_set", + wrapper_symbol.location, + wrapper_symbol.mode, + wrapper_symbol.module, + false) + .symbol_expr(); +} + +dfcc_wrapper_programt::dfcc_wrapper_programt( + const dfcc_contract_modet contract_mode, + const symbolt &wrapper_symbol, + const symbolt &wrapped_symbol, + const dfcc_contract_functionst &contract_functions, + const symbolt &caller_write_set_symbol, + goto_modelt &goto_model, + message_handlert &message_handler, + 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(caller_write_set_symbol.symbol_expr()), + wrapper_sl(wrapper_symbol.location), + return_value_opt(), + contract_write_set( + create_contract_write_set(utils, library, wrapper_symbol)), + addr_of_contract_write_set( + create_addr_of_contract_write_set(utils, library, wrapper_symbol)), + requires_write_set( + create_requires_write_set(utils, library, wrapper_symbol)), + addr_of_requires_write_set( + create_addr_of_requires_write_set(utils, library, wrapper_symbol)), + ensures_write_set(create_ensures_write_set(utils, library, wrapper_symbol)), + addr_of_ensures_write_set( + create_addr_of_ensures_write_set(utils, library, wrapper_symbol)), + is_fresh_set(create_is_fresh_set(utils, library, wrapper_symbol)), + addr_of_is_fresh_set( + create_addr_of_is_fresh_set(utils, library, wrapper_symbol)), + function_pointer_contracts(), + goto_model(goto_model), + message_handler(message_handler), + log(message_handler), + 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 instantiate all contract lambdas) + if(contract_code_type.return_type().id() != ID_empty) + { + return_value_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) + .symbol_expr(); + + // build contract_lambda_parameters + contract_lambda_parameters.push_back(return_value_opt.value()); + } + + // build contract_lambda_parameters + 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_ensures_write_set(); + encode_is_fresh_set(); + encode_requires_clauses(); + encode_requires_contract_clauses(); + encode_contract_write_set(); + encode_function_call(); + encode_ensures_clauses(); + encode_ensures_contract_clauses(); +} + +void dfcc_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_wrapper_programt::add_to_dest(goto_programt &dest) +{ + // add code to dest in the right order + dest.destructive_append(preamble); + dest.destructive_append(link_is_fresh); + dest.destructive_append(preconditions); + dest.destructive_append(history); + dest.destructive_append(write_set_checks); + dest.destructive_append(function_call); + dest.destructive_append(link_allocated_caller); + dest.destructive_append(link_deallocated_contract); + dest.destructive_append(postconditions); + dest.destructive_append(postamble); +} + +void dfcc_wrapper_programt::encode_requires_write_set() +{ + // call write_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; + preamble.add(goto_programt::make_decl(write_set, wrapper_sl)); + + const auto address_of_write_set = addr_of_requires_write_set; + preamble.add(goto_programt::make_decl(address_of_write_set, wrapper_sl)); + preamble.add(goto_programt::make_assignment( + address_of_write_set, address_of_exprt(write_set), wrapper_sl)); + + auto function_symbol = + library.dfcc_fun_symbol[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.push_back(false_exprt()); + + if(contract_mode == dfcc_contract_modet::CHECK) + { + // assume_requires_ctx + arguments.push_back(true_exprt()); + + // assert_requires_ctx + arguments.push_back(false_exprt()); + } + else + { + // assume_requires_ctx + arguments.push_back(false_exprt()); + + // assert_requires_ctx mode + arguments.push_back(true_exprt()); + } + + // assume_ensures_ctx mode + arguments.push_back(false_exprt()); + + // assert_ensures_ctx mode + arguments.push_back(false_exprt()); + + preamble.add(goto_programt::make_function_call(call, wrapper_sl)); + + // check for absence of allocation/deallocation in requires clauses + // ```c + // DECL __check_no_alloc: bool; + // CALL __check_no_alloc = check_empty_alloc_dealloc(write_set); + // 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", + wrapper_sl, + wrapper_symbol.mode, + goto_model.symbol_table) + .symbol_expr(); + + postamble.add(goto_programt::make_decl(check_var, wrapper_sl)); + + 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}}, + wrapper_sl)); + + source_locationt check_sl(wrapper_sl); + check_sl.set_function(wrapper_symbol.name); + check_sl.set_property_class("no_alloc_dealloc_in_requires"); + check_sl.set_comment( + "Check that requires do not allocate or deallocate memory"); + postamble.add(goto_programt::make_assertion(check_var, check_sl)); + postamble.add(goto_programt::make_dead(check_var, wrapper_sl)); + + // generate write set release and DEAD instructions + { + code_function_callt call( + library.dfcc_fun_symbol[dfcc_funt::WRITE_SET_RELEASE].symbol_expr(), + {address_of_write_set}); + postamble.add(goto_programt::make_function_call(call, wrapper_sl)); + postamble.add(goto_programt::make_dead(write_set, wrapper_sl)); + } +} + +void dfcc_wrapper_programt::encode_ensures_write_set() +{ + // call write_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; + preamble.add(goto_programt::make_decl(write_set, wrapper_sl)); + + const auto address_of_write_set = addr_of_ensures_write_set; + preamble.add(goto_programt::make_decl(address_of_write_set, wrapper_sl)); + preamble.add(goto_programt::make_assignment( + address_of_write_set, address_of_exprt(write_set), wrapper_sl)); + + auto function_symbol = + library.dfcc_fun_symbol[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.push_back(false_exprt()); + + // assume_requires_ctx + arguments.push_back(false_exprt()); + + // assume_requires_ctx + arguments.push_back(false_exprt()); + + if(contract_mode == dfcc_contract_modet::CHECK) + { + // assume_ensures_ctx + arguments.push_back(false_exprt()); + + // assert_ensures_ctx + arguments.push_back(true_exprt()); + } + else + { + // assume_ensures_ctx + arguments.push_back(true_exprt()); + + // assert_ensures_ctx + arguments.push_back(false_exprt()); + } + + preamble.add(goto_programt::make_function_call(call)); + + // call link_allocated(pre_post, caller) if in REPLACE MODE + if(contract_mode == dfcc_contract_modet::REPLACE) + { + auto function_symbol = + library.dfcc_fun_symbol[dfcc_funt::LINK_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); + link_allocated_caller.add( + goto_programt::make_function_call(call, wrapper_sl)); + } + + // check for absence of allocation/deallocation in contract clauses + // ```c + // DECL __check_no_alloc: bool; + // CALL __check_no_alloc = check_empty_alloc_dealloc(write_set); + // 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", + wrapper_sl, + wrapper_symbol.mode, + goto_model.symbol_table) + .symbol_expr(); + + postamble.add(goto_programt::make_decl(check_var, wrapper_sl)); + + 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}}, + wrapper_sl)); + + source_locationt check_sl(wrapper_sl); + check_sl.set_function(wrapper_symbol.name); + check_sl.set_property_class("no_alloc_dealloc_in_ensures"); + check_sl.set_comment( + "Check that ensures do not allocate or deallocate memory"); + + postamble.add(goto_programt::make_assertion(check_var, check_sl)); + postamble.add(goto_programt::make_dead(check_var, wrapper_sl)); + + // generate write set release + postamble.add(goto_programt::make_function_call( + code_function_callt{ + library.dfcc_fun_symbol[dfcc_funt::WRITE_SET_RELEASE].symbol_expr(), + {address_of_write_set}}, + wrapper_sl)); + + // declare write set DEAD + postamble.add(goto_programt::make_dead(write_set, wrapper_sl)); +} + +void dfcc_wrapper_programt::encode_contract_write_set() +{ + const auto write_set = contract_write_set; + preamble.add(goto_programt::make_decl(write_set, wrapper_sl)); + + const auto address_of_contract_write_set = addr_of_contract_write_set; + preamble.add( + goto_programt::make_decl(address_of_contract_write_set, wrapper_sl)); + preamble.add(goto_programt::make_assignment( + address_of_contract_write_set, address_of_exprt(write_set), wrapper_sl)); + + // 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 + const auto address_of_requires_write_set = addr_of_requires_write_set; + + // call write_set_create + { + auto function_symbol = + library.dfcc_fun_symbol[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())); + + // not analysing ensures or requires clauses, set all context flags to false + + // assume_requires_ctx + arguments.push_back(false_exprt()); + + // assert_requires_ctx + arguments.push_back(false_exprt()); + + // assume_ensures_ctx + arguments.push_back(false_exprt()); + + // assert_ensures_ctx + arguments.push_back(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()) + { + 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, wrapper_sl)); + } + + // 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, wrapper_sl)); + } + + // generate write set release and DEAD instructions + { + code_function_callt call( + library.dfcc_fun_symbol[dfcc_funt::WRITE_SET_RELEASE].symbol_expr(), + {address_of_contract_write_set}); + postamble.add(goto_programt::make_function_call(call, wrapper_sl)); + postamble.add(goto_programt::make_dead(write_set, wrapper_sl)); + } +} + +void dfcc_wrapper_programt::encode_is_fresh_set() +{ + const auto object_set = is_fresh_set; + preamble.add(goto_programt::make_decl(object_set, wrapper_sl)); + + const auto address_of_object_set = addr_of_is_fresh_set; + preamble.add(goto_programt::make_decl(address_of_object_set, wrapper_sl)); + preamble.add(goto_programt::make_assignment( + address_of_object_set, address_of_exprt(object_set), wrapper_sl)); + + // CALL obj_set_create_indexed_by_object_id(is_fresh_set) in preamble + preamble.add(goto_programt::make_function_call( + code_function_callt{ + library.dfcc_fun_symbol[dfcc_funt::OBJ_SET_CREATE_INDEXED_BY_OBJECT_ID] + .symbol_expr(), + {address_of_object_set}}, + wrapper_sl)); + + // link to requires write set + link_is_fresh.add(goto_programt::make_function_call( + code_function_callt( + library.dfcc_fun_symbol[dfcc_funt::LINK_IS_FRESH].symbol_expr(), + {addr_of_requires_write_set, address_of_object_set}), + wrapper_sl)); + + // link to ensures write set + link_is_fresh.add(goto_programt::make_function_call( + code_function_callt( + library.dfcc_fun_symbol[dfcc_funt::LINK_IS_FRESH].symbol_expr(), + {addr_of_ensures_write_set, address_of_object_set}), + wrapper_sl)); + + // release call in postamble + postamble.add(goto_programt::make_function_call( + code_function_callt( + library.dfcc_fun_symbol[dfcc_funt::OBJ_SET_RELEASE].symbol_expr(), + {address_of_object_set}), + wrapper_sl)); + + // DEAD instructions in postamble + postamble.add(goto_programt::make_dead(object_set, wrapper_sl)); +} + +void dfcc_wrapper_programt::encode_requires_clauses() +{ + // we use this empty requires write set to check for the absence of side + // effects in the requires clauses + const auto &wrapper_id = wrapper_symbol.name; + const auto &language_mode = utils.get_function_symbol(wrapper_id).mode; + + // 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; + + // goto program where all requires are added + goto_programt requires_program; + + // translate each requires clause + for(const auto &r : contract_code_type.requires()) + { + exprt requires = to_lambda_expr(r).application(contract_lambda_parameters); + requires.add_source_location() = r.source_location(); + if(has_subexpr(requires, ID_exists) || has_subexpr(requires, ID_forall)) + add_quantified_variable(goto_model.symbol_table, requires, language_mode); + + source_locationt sl(r.source_location()); + if(statement_type == ID_assert) + { + sl.set_property_class(ID_precondition); + sl.set_comment( + "Check requires clause of contract " + id2string(contract_symbol.name) + + " for function " + id2string(wrapper_id)); + } + codet requires_statement(statement_type, {std::move(requires)}, sl); + converter.goto_convert(requires_statement, requires_program, language_mode); + } + + const auto address_of_requires_write_set = addr_of_requires_write_set; + + // rewrite is_fresh predicates + dfcc_is_fresht is_fresh(library, message_handler); + is_fresh.rewrite_calls(requires_program, address_of_requires_write_set); + + // rewrite is_freeable predicates + dfcc_is_freeablet is_freeable(library, message_handler); + 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); +} + +void dfcc_wrapper_programt::encode_ensures_clauses() +{ + const auto &language_mode = wrapper_symbol.mode; + const auto &wrapper_id = wrapper_symbol.name; + const auto &statement_type = + (contract_mode == dfcc_contract_modet::CHECK) ? ID_assert : ID_assume; + + // goto program where all history variable snapshots are added + goto_programt history_snapshot_program; + + // goto program where all requires are added + goto_programt ensures_program; + + // translate each ensures clause + for(const auto &e : contract_code_type.ensures()) + { + exprt ensures = to_lambda_expr(e).application(contract_lambda_parameters); + ensures.add_source_location() = e.source_location(); + + if(has_subexpr(ensures, ID_exists) || has_subexpr(ensures, ID_forall)) + add_quantified_variable(goto_model.symbol_table, ensures, language_mode); + + // this also rewrites ID_old expressions to fresh variables + generate_history_variables_initialization( + goto_model.symbol_table, + ensures, + language_mode, + history_snapshot_program); + + source_locationt sl(e.source_location()); + if(statement_type == ID_assert) + { + sl.set_property_class(ID_postcondition); + sl.set_comment( + "Check ensures clause of contract " + id2string(contract_symbol.name) + + " for function " + id2string(wrapper_id)); + } + + codet ensures_statement(statement_type, {std::move(ensures)}, sl); + converter.goto_convert(ensures_statement, ensures_program, language_mode); + } + + const auto address_of_ensures_write_set = addr_of_ensures_write_set; + + // rewrite is_fresh predicates + dfcc_is_fresht is_fresh(library, message_handler); + is_fresh.rewrite_calls(ensures_program, address_of_ensures_write_set); + + // rewrite was_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 was_freed predicate and perform the checks + { + auto function_symbol = + library.dfcc_fun_symbol[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); + link_deallocated_contract.add( + goto_programt::make_function_call(call, wrapper_sl)); + } + + dfcc_is_freeablet is_freeable(library, message_handler); + 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); +} + +void dfcc_wrapper_programt::encode_requires_contract_clauses() +{ + 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); + instance.add_source_location() = expr.source_location(); + 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); + instance.add_source_location() = expr.source_location(); + INVARIANT( + can_cast_expr(instance), + "instance ok"); + + assert_function_pointer_obeys_contract( + to_function_pointer_obeys_contract_expr(instance), + ID_precondition, + preconditions); + } + } +} + +void dfcc_wrapper_programt::encode_ensures_contract_clauses() +{ + const auto &ensures_contract = contract_code_type.ensures_contract(); + + if(contract_mode == dfcc_contract_modet::CHECK) + { + for(auto &expr : ensures_contract) + { + auto instance = + to_lambda_expr(expr).application(contract_lambda_parameters); + instance.add_source_location() = expr.source_location(); + INVARIANT( + can_cast_expr(instance), + "instance ok"); + assert_function_pointer_obeys_contract( + to_function_pointer_obeys_contract_expr(instance), + ID_postcondition, + postconditions); + } + } + else + { + for(auto &expr : ensures_contract) + { + auto instance = + to_lambda_expr(expr).application(contract_lambda_parameters); + instance.add_source_location() = expr.source_location(); + INVARIANT( + can_cast_expr(instance), + "instance ok"); + assume_function_pointer_obeys_contract( + to_function_pointer_obeys_contract_expr(instance), postconditions); + } + } +} + +void dfcc_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_wrapper_programt::assume_function_pointer_obeys_contract( + const function_pointer_obeys_contract_exprt &expr, + goto_programt &dest) +{ + 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)); +} + +void dfcc_wrapper_programt::encode_function_call() +{ + if(contract_mode == dfcc_contract_modet::CHECK) + encode_checked_function_call(); + else + encode_havoced_function_call(); +} + +void dfcc_wrapper_programt::encode_checked_function_call() +{ + // build call to the wrapped function + code_function_callt call(wrapped_symbol.symbol_expr()); + + if(return_value_opt) + { + symbol_exprt return_value = return_value_opt.value(); + // DECL + preamble.add(goto_programt::make_decl(return_value, wrapper_sl)); + call.lhs() = return_value; + + // SET_RETURN_VALUE + postamble.add( + goto_programt::make_set_return_value(return_value, wrapper_sl)); + + // DEAD + postamble.add(goto_programt::make_dead(return_value, wrapper_sl)); + } + + // 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); + + function_call.add(goto_programt::make_function_call(call, wrapper_sl)); +} + +void dfcc_wrapper_programt::encode_havoced_function_call() +{ + // 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; + + // 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 goto_instruction = + write_set_checks.add(goto_programt::make_incomplete_goto( + utils.make_null_check_expr(caller_write_set), wrapper_sl)); + + { + // assigns clause inclusion + auto check_var = get_fresh_aux_symbol( + bool_typet(), + id2string(wrapper_symbol.name), + "__check_assigns_clause_incl", + wrapper_sl, + wrapper_symbol.mode, + goto_model.symbol_table) + .symbol_expr(); + + write_set_checks.add(goto_programt::make_decl(check_var, wrapper_sl)); + + code_function_callt check_incl_call( + check_var, + library + .dfcc_fun_symbol[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, wrapper_sl)); + source_locationt check_sl(wrapper_sl); + check_sl.set_function(wrapper_symbol.name); + check_sl.set_property_class("assigns"); + check_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, check_sl)); + write_set_checks.add(goto_programt::make_dead(check_var, wrapper_sl)); + } + + { + // frees clause inclusion + auto check_var = get_fresh_aux_symbol( + bool_typet(), + id2string(wrapper_symbol.name), + "__check_frees_clause_incl", + wrapper_sl, + wrapper_symbol.mode, + goto_model.symbol_table) + .symbol_expr(); + + write_set_checks.add(goto_programt::make_decl(check_var, wrapper_sl)); + + code_function_callt check_incl_call( + check_var, + library.dfcc_fun_symbol[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, wrapper_sl)); + + source_locationt check_sl(wrapper_sl); + check_sl.set_function(wrapper_symbol.name); + check_sl.set_property_class("frees"); + check_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, check_sl)); + write_set_checks.add(goto_programt::make_dead(check_var, wrapper_sl)); + } + + 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, wrapper_sl)); + + // assign nondet to the return value + if(return_value_opt.has_value()) + { + symbol_exprt return_value = return_value_opt.value(); + + // DECL + preamble.add(goto_programt::make_decl(return_value, wrapper_sl)); + + // ASSIGN NONDET + function_call.add(goto_programt::make_assignment( + return_value, + side_effect_expr_nondett(return_value.type(), wrapper_sl), + wrapper_sl)); + + // SET RETURN VALUE + postamble.add( + goto_programt::make_set_return_value(return_value, wrapper_sl)); + + // DEAD + postamble.add(goto_programt::make_dead(return_value, wrapper_sl)); + } + + // 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[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, wrapper_sl)); +} diff --git a/src/goto-instrument/contracts/dynamic-frames/dfcc_wrapper_program.h b/src/goto-instrument/contracts/dynamic-frames/dfcc_wrapper_program.h new file mode 100644 index 00000000000..ed7210895bf --- /dev/null +++ b/src/goto-instrument/contracts/dynamic-frames/dfcc_wrapper_program.h @@ -0,0 +1,281 @@ +/*******************************************************************\ + +Module: Dynamic frame condition checking for function contracts + +Author: Remi Delmas, delmasrd@amazon.com + +\*******************************************************************/ + +/// \file +/// Generates the body of a wrapper function from a contract +/// for contract checking or contract replacement + +#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_WRAPPER_PROGRAM_H +#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_DYNAMIC_FRAMES_DFCC_WRAPPER_PROGRAM_H + +#include + +#include +#include +#include +#include + +#include "dfcc_contract_functions.h" +#include "dfcc_contract_mode.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; + +/// \brief Generates the body of a wrapper function from a contract +/// specified using requires, requires_contract, assigns, frees, ensures, +/// ensures_contract clauses attached to a function symbol. The desired mode +/// CHECK or REPLACE is given to the constructor of the class. +/// +/// \details The body of the wrapper is divided into a number of sections +/// represented as separate goto_programs: +/// - \ref preamble +/// - create is_fresh_set, requires_write_set, ensures_write_set, +/// contract_write_set variables +/// - \ref link_is_fresh +/// - link the is_fresh_set to requires_write_set and ensures_write_set +/// - in REPLACE mode, link the caller_write_set to the contract_write_set +/// so that deallocations and allocations are reflected in the caller +/// - \ref preconditions +/// - evaluate preconditions, checking side effects against requires_write_set +/// - \ref history +/// - declare and snapshot history variables +/// - \ref write_set_checks +/// - populate the contract write set and check it for inclusion against +/// the caller write set if REPLACE mode is selected. +/// - \ref function_call +/// - in CHECK mode +/// ```c +/// CALL retval = foo(params, contract_write_set); +/// ``` +/// - in REPLACE mode +/// ```c +/// CALL foo::havoc(contract_write_set); +/// CALL deallocate_freeable(contract_write_set, caller_write_set); +/// ASSIGN retval = nondet +/// ``` +/// - \ref link_allocated_caller +/// in REPLACE mode, links the allocated set of the caller to the +/// ensures_write_set, so that allocations performed by is fresh in post +/// conditions get recorded in the caller write set and are considered as +/// assignable for the caller (In CHECK mode, the caller write set comes from +/// the proof harness and is NULL and ignored). +/// ```c +/// CALL link_allocated(ensures_write_set, caller_write_set); +/// ``` +/// - \ref link_deallocated_contract +/// links the deallocated set of the contract_write_set +/// to the ensures_write_set, so that was_freed predicates can be evaluated +/// in the post conditions. +/// ```c +/// CALL link_allocated(ensures_write_set, caller_write_set); +/// ``` +/// - \ref postconditions +/// - evaluate preconditions, checking side effects against ensures_write_set +/// - \ref postamble +/// - release all object sets and write set variables +/// +/// There a private method per type of contract clause, which adds contents +/// encoding the semantics of the clause to the appropriate section when called. +/// +/// The public method \ref add_to_dest calls the private methods to populate the +/// sections, and then adds the contents of these sections in order to the +/// given destination program. +class dfcc_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 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 message_handler used 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_wrapper_programt( + const dfcc_contract_modet contract_mode, + const symbolt &wrapper_symbol, + const symbolt &wrapped_symbol, + const dfcc_contract_functionst &contract_functions, + const symbolt &caller_write_set_symbol, + goto_modelt &goto_model, + message_handlert &message_handler, + dfcc_utilst &utils, + dfcc_libraryt &library, + dfcc_instrumentt &instrument); + + /// 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_contract_functionst &contract_functions; + const symbolt &contract_symbol; + const code_with_contract_typet &contract_code_type; + const symbol_exprt caller_write_set; + + /// Source location with wrapper function name as function name + const source_locationt wrapper_sl; + + /// Symbol for the return value of the wrapped function + optionalt return_value_opt; + + /// Symbol for the write set object derived from the contract + const symbol_exprt contract_write_set; + + /// Symbol for the pointer to the write set object derived from the contract + const symbol_exprt addr_of_contract_write_set; + + /// Symbol for the write set used to check requires clauses for side effects + const symbol_exprt requires_write_set; + + /// Symbol for the pointer to the write set used to check requires clauses + const symbol_exprt addr_of_requires_write_set; + + /// Symbol for the write set used to check ensures clauses for side effects + const symbol_exprt ensures_write_set; + + /// Symbol for the pointer to the write set used to check ensures clauses + const symbol_exprt addr_of_ensures_write_set; + + /// Symbol for the object set used to evaluate is_fresh predicates. + const symbol_exprt is_fresh_set; + + /// Symbol for the pointer to the is_fresh object set. + const symbol_exprt addr_of_is_fresh_set; + + /// 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; + message_handlert &message_handler; + messaget log; + dfcc_utilst &utils; + dfcc_libraryt &library; + dfcc_instrumentt &instrument; + namespacet ns; + goto_convertt converter; + + /// Vector of arguments to use to instantiate 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 link_is_fresh; + goto_programt preconditions; + goto_programt history; + goto_programt write_set_checks; + goto_programt function_call; + goto_programt link_allocated_caller; + goto_programt link_deallocated_contract; + goto_programt postconditions; + goto_programt postamble; + + /// 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); + + /// Encodes the empty write set used to check for side effects in requires + /// - Adds declaration of write set and pointer to write set to \ref preamble + /// - Adds initialisation function call in \ref preamble + /// - Adds alloc/deallocation checking assertion in \ref postamble + /// - Adds release function call in \ref postamble + void encode_requires_write_set(); + + /// Encodes the empty write set used to check for side effects in ensures + /// - Adds declaration of write set and pointer to write set to \ref preamble + /// - Adds initialisation function call in \ref preamble + /// - Adds alloc/deallocation checking assertion in \ref postamble + /// - Adds release function call in \ref postamble + void encode_ensures_write_set(); + + /// Encodes the write set of the contract being checked/replaced + /// (populated by calling functions provided in contract_functions) + /// - Adds declaration of write set and pointer to write set to \ref preamble + /// - Adds initialisation function call in \ref write_set_checks + /// - Adds contract::assigns and contract::frees function call + /// in \ref write_set_checks + /// - Adds release function call in \ref postamble + void encode_contract_write_set(); + + /// Encodes the object set used to evaluate is fresh predicates, + /// - Adds declaration of object set and pointer to set to \ref preamble + /// - Adds initialisation function call in \ref preamble + /// - Adds release function call in \ref postamble + void encode_is_fresh_set(); + + /// Encodes preconditions, instruments them to check for side effects + void encode_requires_clauses(); + + /// Encodes function pointer preconditions + void encode_requires_contract_clauses(); + + /// Encodes postconditions, instruments them to check for side effects + void encode_ensures_clauses(); + + /// Encodes function pointer postconditions + void encode_ensures_contract_clauses(); + + /// 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 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 diff --git a/src/goto-instrument/contracts/utils.cpp b/src/goto-instrument/contracts/utils.cpp index b1f3642b5d9..4ab7c80df88 100644 --- a/src/goto-instrument/contracts/utils.cpp +++ b/src/goto-instrument/contracts/utils.cpp @@ -10,15 +10,18 @@ Date: September 2021 #include "utils.h" -#include - +#include #include #include +#include #include #include #include #include +#include + +#include #include static void append_safe_havoc_code_for_expr( @@ -268,3 +271,173 @@ void widen_assigns(assignst &assigns) } assigns = result; } + +void add_quantified_variable( + symbol_tablet &symbol_table, + exprt &expression, + const irep_idt &mode) +{ + if(expression.id() == ID_not || expression.id() == ID_typecast) + { + // For unary connectives, recursively check for + // nested quantified formulae in the term + auto &unary_expression = to_unary_expr(expression); + add_quantified_variable(symbol_table, unary_expression.op(), mode); + } + if(expression.id() == ID_notequal || expression.id() == ID_implies) + { + // For binary connectives, recursively check for + // nested quantified formulae in the left and right terms + auto &binary_expression = to_binary_expr(expression); + add_quantified_variable(symbol_table, binary_expression.lhs(), mode); + add_quantified_variable(symbol_table, binary_expression.rhs(), mode); + } + if(expression.id() == ID_if) + { + // For ternary connectives, recursively check for + // nested quantified formulae in all three terms + auto &if_expression = to_if_expr(expression); + add_quantified_variable(symbol_table, if_expression.cond(), mode); + add_quantified_variable(symbol_table, if_expression.true_case(), mode); + add_quantified_variable(symbol_table, if_expression.false_case(), mode); + } + if(expression.id() == ID_and || expression.id() == ID_or) + { + // For multi-ary connectives, recursively check for + // nested quantified formulae in all terms + auto &multi_ary_expression = to_multi_ary_expr(expression); + for(auto &operand : multi_ary_expression.operands()) + { + add_quantified_variable(symbol_table, operand, mode); + } + } + else if(expression.id() == ID_exists || expression.id() == ID_forall) + { + // When a quantifier expression is found, create a fresh symbol for each + // quantified variable and rewrite the expression to use those fresh + // symbols. + auto &quantifier_expression = to_quantifier_expr(expression); + std::vector fresh_variables; + fresh_variables.reserve(quantifier_expression.variables().size()); + for(const auto &quantified_variable : quantifier_expression.variables()) + { + // 1. create fresh symbol + symbolt new_symbol = new_tmp_symbol( + quantified_variable.type(), + quantified_variable.source_location(), + mode, + symbol_table); + + // 2. add created fresh symbol to expression map + fresh_variables.push_back(new_symbol.symbol_expr()); + } + + // use fresh symbols + exprt where = quantifier_expression.instantiate(fresh_variables); + + // recursively check for nested quantified formulae + add_quantified_variable(symbol_table, where, mode); + + // replace previous variables and body + quantifier_expression.variables() = fresh_variables; + quantifier_expression.where() = std::move(where); + } +} + +void replace_history_parameter( + symbol_tablet &symbol_table, + exprt &expr, + std::map ¶meter2history, + source_locationt location, + const irep_idt &mode, + goto_programt &history, + const irep_idt &id) +{ + for(auto &op : expr.operands()) + { + replace_history_parameter( + symbol_table, op, parameter2history, location, mode, history, id); + } + + if(expr.id() == ID_old || expr.id() == ID_loop_entry) + { + const auto ¶meter = to_history_expr(expr, id).expression(); + + const auto &id = parameter.id(); + if( + id == ID_dereference || id == ID_member || id == ID_symbol || + id == ID_ptrmember || id == ID_constant || id == ID_typecast || + id == ID_index) + { + auto it = parameter2history.find(parameter); + + if(it == parameter2history.end()) + { + // 0. Create a skip target to jump to, if the parameter is invalid + goto_programt skip_program; + const auto skip_target = + skip_program.add(goto_programt::make_skip(location)); + + // 1. Create a temporary symbol expression that represents the + // history variable + symbol_exprt tmp_symbol = + new_tmp_symbol(parameter.type(), location, mode, symbol_table) + .symbol_expr(); + + // 2. Associate the above temporary variable to it's corresponding + // expression + parameter2history[parameter] = tmp_symbol; + + // 3. Add the required instructions to the instructions list + // 3.1. Declare the newly created temporary variable + history.add(goto_programt::make_decl(tmp_symbol, location)); + + // 3.2. Skip storing the history if the expression is invalid + history.add(goto_programt::make_goto( + skip_target, + not_exprt{ + all_dereferences_are_valid(parameter, namespacet(symbol_table))}, + location)); + + // 3.3. Add an assignment such that the value pointed to by the new + // temporary variable is equal to the value of the corresponding + // parameter + history.add( + goto_programt::make_assignment(tmp_symbol, parameter, location)); + + // 3.4. Add a skip target + history.destructive_append(skip_program); + } + + expr = parameter2history[parameter]; + } + else + { + throw invalid_source_file_exceptiont( + "Tracking history of " + id2string(parameter.id()) + + " expressions is not supported yet.", + expr.source_location()); + } + } +} + +void generate_history_variables_initialization( + symbol_tablet &symbol_table, + exprt &clause, + const irep_idt &mode, + goto_programt &program) +{ + std::map parameter2history; + goto_programt history; + // Find and replace "old" expression in the "expression" variable + replace_history_parameter( + symbol_table, + clause, + parameter2history, + clause.source_location(), + mode, + history, + ID_old); + // Add all the history variable initialization instructions + program.destructive_append(history); +} diff --git a/src/goto-instrument/contracts/utils.h b/src/goto-instrument/contracts/utils.h index 11e327001eb..d6a10ad259a 100644 --- a/src/goto-instrument/contracts/utils.h +++ b/src/goto-instrument/contracts/utils.h @@ -187,4 +187,32 @@ bool is_assigns_clause_replacement_tracking_comment(const irep_idt &comment); /// *(b+i) when `i` is a known constant, keep the expression in the result. void widen_assigns(assignst &assigns); +/// This function recursively searches \p expression to find nested or +/// non-nested quantified expressions. When a quantified expression is found, +/// a fresh quantified variable is added to the symbol table and \p expression +/// is updated to use this fresh variable. +void add_quantified_variable( + symbol_tablet &symbol_table, + exprt &expression, + const irep_idt &mode); + +/// This function recursively identifies the "old" expressions within expr +/// and replaces them with correspoding history variables. +void replace_history_parameter( + symbol_tablet &symbol_table, + exprt &expr, + std::map ¶meter2history, + source_locationt location, + const irep_idt &mode, + goto_programt &history, + const irep_idt &id); + +/// This function generates all the instructions required to initialize +/// history variables. +void generate_history_variables_initialization( + symbol_tablet &symbol_table, + exprt &clause, + const irep_idt &mode, + goto_programt &program); + #endif // CPROVER_GOTO_INSTRUMENT_CONTRACTS_UTILS_H diff --git a/src/goto-instrument/goto_instrument_parse_options.cpp b/src/goto-instrument/goto_instrument_parse_options.cpp index cc286c59512..c70ec6145f7 100644 --- a/src/goto-instrument/goto_instrument_parse_options.cpp +++ b/src/goto-instrument/goto_instrument_parse_options.cpp @@ -1157,9 +1157,72 @@ void goto_instrument_parse_optionst::instrument_goto_program() annotate_invariants(synthesizer.synthesize_all(), goto_model, log); } + bool use_dfcc = cmdline.isset(FLAG_DFCC); + if(use_dfcc) + { + if(cmdline.get_values(FLAG_DFCC).size() != 1) + { + throw invalid_command_line_argument_exceptiont( + "Redundant options detected", + "--" FLAG_DFCC, + "Use a single " FLAG_DFCC " option"); + } + + if(cmdline.isset(FLAG_LOOP_CONTRACTS)) + { + throw invalid_command_line_argument_exceptiont( + "Incompatible options detected", + "--" FLAG_DFCC " and --" FLAG_LOOP_CONTRACTS, + "Use either --" FLAG_DFCC " or --" FLAG_LOOP_CONTRACTS); + } + + do_indirect_call_and_rtti_removal(); + + const irep_idt harness_id(cmdline.get_value(FLAG_DFCC)); + + std::set to_replace( + cmdline.get_values(FLAG_REPLACE_CALL).begin(), + cmdline.get_values(FLAG_REPLACE_CALL).end()); + + if( + !cmdline.get_values(FLAG_ENFORCE_CONTRACT).empty() && + !cmdline.get_values(FLAG_ENFORCE_CONTRACT_REC).empty()) + { + throw invalid_command_line_argument_exceptiont( + "Mutually exclusive options detected", + "--" FLAG_ENFORCE_CONTRACT " and --" FLAG_ENFORCE_CONTRACT_REC + "Use either --" FLAG_ENFORCE_CONTRACT + " or --" FLAG_ENFORCE_CONTRACT_REC); + } + + auto &to_enforce = !cmdline.get_values(FLAG_ENFORCE_CONTRACT_REC).empty() + ? cmdline.get_values(FLAG_ENFORCE_CONTRACT_REC) + : cmdline.get_values(FLAG_ENFORCE_CONTRACT); + + bool allow_recursive_calls = + !cmdline.get_values(FLAG_ENFORCE_CONTRACT_REC).empty(); + + std::set to_exclude_from_nondet_static( + cmdline.get_values("nondet-static-exclude").begin(), + cmdline.get_values("nondet-static-exclude").end()); + + dfcc( + options, + goto_model, + harness_id, + to_enforce.empty() ? optionalt{} + : optionalt{to_enforce.front()}, + allow_recursive_calls, + to_replace, + false, + to_exclude_from_nondet_static, + log.get_message_handler()); + } + 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,10 +1974,12 @@ 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 HELP_ENFORCE_CONTRACT + HELP_ENFORCE_CONTRACT_REC "\n" "User-interface options:\n" HELP_FLUSH diff --git a/src/goto-instrument/goto_instrument_parse_options.h b/src/goto-instrument/goto_instrument_parse_options.h index 905e7d191f1..4fa2375f901 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,9 +96,11 @@ 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 "):" \ + OPT_ENFORCE_CONTRACT_REC \ "(show-threaded)(list-calls-args)" \ "(undefined-function-is-assume-false)" \ "(remove-function-body):"\