Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple Stubbing with Contracts #2746

Merged
merged 29 commits into from
Sep 27, 2023
Merged
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e6db205
Replacement basics
JustusAdam Sep 7, 2023
ff344b6
Fixing errors and formatting
JustusAdam Sep 7, 2023
16d6e50
Documentation and general fixes
JustusAdam Sep 7, 2023
6ab4834
More docs, more fixes
JustusAdam Sep 7, 2023
3347515
Mostly expanding the test case conditions
JustusAdam Sep 7, 2023
a1f12be
Stupid whitespace
JustusAdam Sep 7, 2023
2c6abd6
Turns out I did need the copies.
JustusAdam Sep 8, 2023
4f3c633
Fix test cases
JustusAdam Sep 8, 2023
a847af2
Added nicer errors + tests for missing contracts
JustusAdam Sep 10, 2023
29b128a
Incorporating suggestions
JustusAdam Sep 15, 2023
5651146
Apply suggestions from code review
JustusAdam Sep 15, 2023
cf9e4a5
Format
JustusAdam Sep 18, 2023
9429f47
Merge branch 'main' into simple-contract-replacement
JustusAdam Sep 18, 2023
a6dc282
Switch the notes
JustusAdam Sep 19, 2023
05abe4d
Added type annotation
JustusAdam Sep 19, 2023
05071f1
Sketch for module-level contracts documentation.
JustusAdam Sep 19, 2023
0777d0d
Formatting
JustusAdam Sep 19, 2023
272326a
Change code structure
JustusAdam Sep 21, 2023
686b626
Adding code review suggestions
JustusAdam Sep 21, 2023
5dc470b
Make the macro emit an `Arbitrary` bound for return types.
JustusAdam Sep 21, 2023
9ae06e4
Hehe
JustusAdam Sep 21, 2023
82121c5
Remove unused shadowing statement.
JustusAdam Sep 26, 2023
54bdfee
Expand the doc even more
JustusAdam Sep 26, 2023
415a0f5
Apply suggestions from code review
JustusAdam Sep 26, 2023
f2a93de
Formatting
JustusAdam Sep 26, 2023
d987640
Merge branch 'simple-contract-replacement' of github.com:JustusAdam/k…
JustusAdam Sep 26, 2023
85f59c7
Committed wrong file
JustusAdam Sep 26, 2023
d32e651
Update library/kani_macros/src/sysroot/contracts.rs
JustusAdam Sep 26, 2023
a9b36a3
Merge branch 'main' into simple-contract-replacement
JustusAdam Sep 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Expand the doc even more
  • Loading branch information
JustusAdam committed Sep 26, 2023
commit 54bdfeec5a86c061c7d0d8101c089b3ed9987458
94 changes: 79 additions & 15 deletions library/kani_macros/src/sysroot/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,87 @@
//! on the same function correctly.
//!
//! ## How the handling for `requires` and `ensures` works.
//!
//! Our aim is to generate a "check" function that can be used to verify the
//! validity of the contract and a "replace" function that can be used as a
//! stub, generated from the contract that can be used instead of the original
//! function.
//!
//! Let me first introduce the constraints which we are operating under to
//! explain why we need the somewhat involved state machine to achieve this.
//!
//! Proc-macros are expanded one-at-a-time, outside-in and they can also be
//! renamed. Meaning the user can do `use kani::requires as precondition` and
//! then use `precondition` everywhere. We want to support this functionality
//! instead of throwing a hard error but this means we cannot detect if a given
//! function has further contract attributes placed on it during any given
//! expansion. As a result every expansion needs to leave the code in a valid
//! state that could be used for all contract functionality but it must alow
//! further contract attributes to compose with what was already generated. In
//! addition we also want to make sure to support non-contract attributes on
//! functions with contracts.
//!
//! To this end we use a state machine. The initial state is an "untouched"
//! function with possibly multiple contract attributes, none of which have been
//! expanded. When we expand the first (outermost) `requires` or `ensures`
//! attribute on such a function we re-emit the function unchanged but we also
//! generate fresh "check" and "replace" functions that enforce the condition
//! carried by the attribute currently being expanded. We copy all additional
//! attributes from the original function to both the "check" and the "replace".
//! This allows us to deal both with renaming and also support non-contract
//! attributes.
//!
//! In addition to copying attributes we also add new marker attributes to
//! advance the state machine. The "check" function gets a
//! `kanitool::is_contract_generated(check)` attributes and analogous for
//! replace. The re-emitted original meanwhile is decorated with
//! `kanitool::checked_with(name_of_generated_check_function)` and an analogous
//! `kanittool::replaced_with` attribute also. The next contract attribute that
//! is expanded will detect the presence of these markers in the attributes of
//! the item and be able to determine their position in the state machine this
//! way. If the state is either a "check" or "replace" then the body of the
//! function is augmented with the additional conditions carried by the macro.
//! If the state is the "original" function, no changes are performed.
//!
//! We place marker attributes at the bottom of the attribute stack (innermost),
//! otherwise they would not be visible to the future macro expansions.
//!
//! We generate a "check" function used to verify the validity of the contract
//! and a "replace" function that can be used as a stub, generated from the
//! contract that can be used instead of the original function.
//! Below you can see a graphical rendering where boxes are states and each
//! arrow represents the expansion of a `requires` or `ensures` macro.
//!
//! Each clause (requires or ensures) after the first clause will be ignored on
//! the original function (detected by finding the `kanitool::checked_with`
//! attribute). On the check function (detected by finding the
//! `kanitool::is_contract_generated` attribute) it expands into a new layer of
//! pre- or postconditions. This state machine is also explained in more detail
//! in code comments.
//! ```plain
//! v
//! +-----------+
//! | Untouched |
//! | Function |
//! +-----+-----+
//! |
//! Emit | Generate + Copy Attributes
//! +-----------------+------------------+
//! | | |
//! | | |
//! v v v
//! +----------+ +-------+ +---------+
//! | Original |<-+ | Check |<-+ | Replace |<-+
//! +--+-------+ | +---+---+ | +----+----+ |
//! | | Ignore | | Augment | | Augment
//! +----------+ +------+ +-------+
//!
//! | | | |
//! +--------------+ +------------------------------+
//! Presence of Presence of
//! "checked_with" "is_contract_generated"
//!
//! State is detected via
//! ```
//!
//! All named arguments of the annotated function are unsafely shallow-copied
//! with the `kani::untracked_deref` function to circumvent the borrow checker
//! for postconditions. We must ensure that those copies are not dropped
//! (causing a double-free) so after the postconditions we call `mem::forget` on
//! each copy.
//! for postconditions. The case where this is relevant is if you want to return
//! a mutable borrow from the function which means any immutable borrow in the
//! postcondition would be illegal. We must ensure that those copies are not
//! dropped (causing a double-free) so after the postconditions we call
//! `mem::forget` on each copy.
//!
//! ## Check function
//!
Expand Down Expand Up @@ -59,7 +123,7 @@
//!
//! # Complete example
//!
//! ```rs
//! ```
//! #[kani::requires(divisor != 0)]
//! #[kani::ensures(result <= dividend)]
//! fn div(dividend: u32, divisor: u32) -> u32 {
Expand All @@ -69,7 +133,7 @@
//!
//! Turns into
//!
//! ```rs
//! ```
//! #[kanitool::checked_with = "div_check_965916"]
//! #[kanitool::replaced_with = "div_replace_965916"]
//! fn div(dividend: u32, divisor: u32) -> u32 { dividend / divisor }
Expand Down Expand Up @@ -193,7 +257,7 @@ impl<'a> VisitMut for Renamer<'a> {

/// This restores shadowing. Without this we would rename all ident
/// occurrences, but not rebinding location. This is because our
/// [`visit_expr_path_mut`] is scope-unaware.
/// [`Self::visit_expr_path_mut`] is scope-unaware.
fn visit_pat_ident_mut(&mut self, i: &mut syn::PatIdent) {
if let Some(new) = self.0.get(&i.ident) {
i.ident = new.clone();
Expand Down