-
Notifications
You must be signed in to change notification settings - Fork 92
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enable contracts in associated functions (#3363)
Contracts could not be used with associated function unless they used Self. This is because at a proc-macro level, we cannot determine if we are inside a associated function or not, except in cases where `Self` is used. In cases where the contract generation could not identify an associated function, it would generate incorrect call, triggering a compilation error. Another problem with our encoding is that users could not annotate trait implementations with contract attributes. This would try to create new functions inside the trait implementation context, which is not allowed. In order to solve these issues, we decided to wrap contract logic using closures instead of functions. See the discussion in the original issue (#3206) for more details. The new solution is still split in two: 1. The proc-macro will now expand the code inside the original function to encode all possible scenarios (check, replace, recursive check, and original body). 2. Instead of using stub, we introduced a new transformation pass that chooses which scenario to pick according to the target harness configuration, and cleanup unused logic. The expanded function will have the following structure: ```rust #[kanitool::recursion_check = "__kani_recursion_check_modify"] #[kanitool::checked_with = "__kani_check_modify"] #[kanitool::replaced_with = "__kani_replace_modify"] #[kanitool::inner_check = "__kani_modifies_modify"] fn name_fn(ptr: &mut u32) { #[kanitool::fn_marker = "kani_register_contract"] pub const fn kani_register_contract<T, F: FnOnce() -> T>(f: F) -> T { kani::panic("internal error: entered unreachable code: ") } let kani_contract_mode = kani::internal::mode(); match kani_contract_mode { kani::internal::RECURSION_CHECK => { #[kanitool::is_contract_generated(recursion_check)] let mut __kani_recursion_check_name_fn = || { /* recursion check body */ }; kani_register_contract(__kani_recursion_check_modify) } kani::internal::REPLACE => { #[kanitool::is_contract_generated(replace)] let mut __kani_replace_name_fn = || { /* replace body */ }; kani_register_contract(__kani_replace_name_fn) } kani::internal::SIMPLE_CHECK => { #[kanitool::is_contract_generated(check)] let mut __kani_check_name_fn = || { /* check body */ }; kani_register_contract(__kani_check_name_fn) } _ => { /* original body */ } } } ``` In runtime, `kani::internal::mode()` will return kani::internal::ORIGINAL, which runs the original body. The transformation will replace this call by a different assignment in case the function needs to be replaced. The body of the unused closures will be replaced by a `UNREACHABLE` statement to avoid unnecessary code to be analyzed. This is still fairly hacky, but hopefully we can cleanup this logic once Rust adds contract support. :crossed_fingers: Resolves #3206
- Loading branch information
Showing
36 changed files
with
1,849 additions
and
1,458 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.