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

Add a #[derive(Invariant)] macro #3250

Merged
merged 7 commits into from
Jun 13, 2024

Conversation

adpaco-aws
Copy link
Contributor

This PR adds a #[derive(Invariant)] macro for structs which allows users to automatically derive the Invariant implementations for any struct. The derived implementation determines the invariant for the struct as the conjunction of invariants of its fields. In other words, the invariant is derived as true && self.field1.is_safe() && self.field2.is_safe() && ...

For example, for the struct

#[derive(kani::Invariant)]
struct Point<X, Y> {
    x: X,
    y: Y,
}

we derive the Invariant implementation as

impl<X: kani::Invariant, Y: kani::Invariant> kani::Invariant for Point<X, Y> {
    fn is_safe(&self) -> bool {
        true && self.x.is_safe() && self.y.is_safe()
    }
}

Related #3095

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.

@adpaco-aws adpaco-aws requested a review from a team as a code owner June 10, 2024 19:49
@github-actions github-actions bot added the Z-BenchCI Tag a PR to run benchmark CI label Jun 10, 2024
@adpaco-aws adpaco-aws removed their assignment Jun 10, 2024
Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a little test where an invariant created fails? Something like you have a type A with a custom safety invariant and a struct B with a derived Invariant that has a member of type A. Now ensure that breaking the A invariant in the B field also breaks B invariant. Thanks!

@adpaco-aws
Copy link
Contributor Author

Added a test where the invariant check fails but let me know if you have suggestions for the setup step (right now it's just an overly generic Arbitrary implementation).

Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to figure out how to ensure Arbitrary respects Invariant.

@adpaco-aws adpaco-aws enabled auto-merge (squash) June 13, 2024 23:14
@adpaco-aws adpaco-aws merged commit 5c7cd63 into model-checking:main Jun 13, 2024
23 checks passed
adpaco-aws added a commit that referenced this pull request Jul 22, 2024
…rary` and `Invariant` macros (#3283)

This PR enables an `#[safety_constraint(...)]` attribute helper for the
`#[derive(Arbitrary)]` and `#[derive(Invariant)]` macro.

For the `Invariant` derive macro, this allows users to derive more
sophisticated invariants for their data types by annotating individual
named fields with the `#[safety_constraint(<cond>)]` attribute, where
`<cond>` represents the predicate to be evaluated for the corresponding
field. In addition, the implementation always checks `#field.is_safe()`
for each field.

For example, let's say we are working with the `Point` type from #3250
```rs
#[derive(kani::Invariant)]
struct Point<X, Y> {
    x: X,
    y: Y,
}
```

and we need to extend it to only allow positive values for both `x` and
`y`.
With the `[safety_constraint(...)]` attribute, we can achieve this
without explicitly writing the `impl Invariant for ...` as follows:

```rs
#[derive(kani::Invariant)]
struct PositivePoint {
    #[safety_constraint(*x >= 0)]
    x: i32,
    #[safety_constraint(*y >= 0)]
    y: i32,
}
```

For the `Arbitrary` derive macro, this allows users to derive more
sophisticated `kani::any()` generators that respect the specified
invariants. In other words, the `kani::any()` will assume any invariants
specified through the `#[safety_constraint(...)]` attribute helper.
Going back to the `PositivePoint` example, we'd expect this harness to
be successful:

```rs
#[kani::proof]
fn check_invariant_helper_ok() {
    let pos_point: PositivePoint = kani::any();
    assert!(pos_point.x >= 0);
    assert!(pos_point.y >= 0);
}
```

The PR includes tests to ensure it's working as expected, in addition to
UI tests checking for cases where the arguments provided to the macro
are incorrect. Happy to add any other cases that you feel are missing.

Related #3095
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Z-BenchCI Tag a PR to run benchmark CI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants