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

Define a struct-level #[safety_constraint(...)] attribute #3270

Merged
merged 49 commits into from
Jul 31, 2024

Conversation

adpaco-aws
Copy link
Contributor

@adpaco-aws adpaco-aws commented Jun 17, 2024

This PR defines a struct-level #[safety_constraint(<pred>)] macro attribute that allows users to define the type invariant as the predicate passed as an argument to the attribute. This safety constraint is picked up when deriving Arbitrary and Invariant implementations so that the values generated with any() or checked with is_safe() satisfy the constraint.

This attribute is similar to the helper attribute introduced in #3283, and they cannot be used together. It's preferable to use this attribute when the constraint implies some relation between different fields. But, at the moment, there's no practical difference between them because the helper attribute allows users to refer to any fields. If we imposed that restriction, this attribute would be the only way to specify struct-wide invariants.

An example:

#[derive(kani::Arbitrary)]
#[derive(kani::Invariant)]
#[safety_constraint(*x == *y)]
struct SameCoordsPoint {
    x: i32,
    y: i32,
}

#[kani::proof]
fn check_invariant() {
    let point: SameCoordsPoint = kani::any();
    assert!(point.is_safe());
}

Resolves #3095

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

@github-actions github-actions bot added the Z-BenchCI Tag a PR to run benchmark CI label Jun 17, 2024
@adpaco-aws
Copy link
Contributor Author

I've decided to add the helper attribute #[invariant(..)] in #3283 because it makes more sense to have it be part of the #[derive(Invariant)] macro than this one. So I'm hoping to give our users three options:

  1. Derive the Invariant implementation through the derive macro, adding #[invariant(..)] to fields which require stronger invariants (see Add a #[derive(Invariant)] macro #3250 and Enable an #[safety_constraint(...)] attribute helper for the Arbitrary and Invariant macros #3283). Here, Kani composes the struct invariant for the user.
  2. Declare the invariant for the whole struct through a #[kani::invariant(...)] macro (see Define a struct-level #[safety_constraint(...)] attribute #3270). Here, Kani just embeds the condition you pass it.
  3. Write the implementation for Invariant explicitly.

Therefore, this PR only needs a little better error handling (we should parse expressions like we're doing in #3283) and some cleanup.

@adpaco-aws adpaco-aws marked this pull request as ready for review July 9, 2024 21:09
@adpaco-aws adpaco-aws requested a review from a team as a code owner July 9, 2024 21:09
@adpaco-aws
Copy link
Contributor Author

Marking this as ready for review after the last changes. This is now rebased on top of #3283 so we can take advantage of some of the functions included there.

A major change is that we now generate both the Arbitrary and the Invariant implementations from the #[kani::invariant(...)] macro. I made this change when working on the tests. That's when I realized that it didn't make sense to derive the Arbitrary implementation with #[derive(Arbitrary)] while using the #[kani::invariant(...)] attribute because the derived implementation wouldn't respect the type invariant.

For example, if we wrote something like this:

#[kani::invariant(*x >= 0 && *y >= 0)]
struct Point {
    x: i32,
    y: i32,
}

#[kani::proof]
fn check_invariant() {
    let point: Point = kani::any();
    assert!(point.is_safe());
}

The harness would fail to verify because the value generated with kani::any() would not ensure *x >= 0 && *y >= 0. With the latest changes, this succeeds.

Aside from that change, I've added a few more tests and moved the code from derive.rs to lib.rs. The code could be moved to derive.rs again because we use a few functions from there, but it's not a problem to change that later.

@adpaco-aws adpaco-aws changed the title Define a kani::invariant attribute Define a struct-level #[safety_constraint(...)] attribute Jul 29, 2024
@adpaco-aws adpaco-aws marked this pull request as ready for review July 29, 2024 18:41
@adpaco-aws
Copy link
Contributor Author

This is ready for review again.

I have renamed the attribute and changed it so its behavior is very similar to the #[safety_constraint(...)] helper defined in #3283. The main difference w.r.t. the previous revision is that the attribute doesn't derive Arbitrary nor Invariant per se, but only modifies the behavior of the derive macros. In addition, I've refactored some code and added more tests as requested.

Copy link
Contributor

@zhassan-aws zhassan-aws left a comment

Choose a reason for hiding this comment

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

LGTM. The only thing I'm unsure about is that Invariant is not derived automatically when the attribute is specified. There's also no warning or error message if neither Invariant or Arbitrary is derived.

@adpaco-aws
Copy link
Contributor Author

LGTM. The only thing I'm unsure about is that Invariant is not derived automatically when the attribute is specified. There's also no warning or error message if neither Invariant or Arbitrary is derived.

This is also a concern of mine, especially the second case where there isn't any feedback when using the attribute without deriving Arbitrary nor Invariant. I have tried exploring solutions to it this morning, and here's what I found out:

  1. We would need to define this as a Kani attribute, even if it's only to trigger an error/warning. If I'm not mistaken, this implies that the attribute would become #[kani::safety_constraint(...)].
  2. We would need to enforce the attribute being placed on top of the derive macros (not my preferred placement). That's because if the attribute is placed below them, it cannot tell if #[derive(...)] attributes have been specified because they will have already been expanded/processed. This is also not convenient since #[safety_constraint(...)] is currently a helper attribute and using it before the derive macros would trigger a warning (to be a hard error in the future):
warning: derive helper attribute is used before it is introduced
  --> src/main.rs:31:3
   |
31 | #[safety_constraint(x >= 0)]
   |   ^^^^^^^^^^^^^^^^^
32 | #[derive(kani::Arbitrary)]
   |          --------------- the attribute is introduced here
   |
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #79202 <https://github.com/rust-lang/rust/issues/79202>
   = note: `#[warn(legacy_derive_helpers)]` on by default

I believe the best option would be to generate some sort of marker from the derive macros. In other words, if we are generating an Arbitrary or Invariant implementation and find #[safety_constraint(...)], we should emit some information that lets us know if the attribute has been picked up by the derive macros. Then, in the macro for the attribute, we could just error out if the marker has not been found. However, I think that'd be out-of-scope for this PR so I've created #3396 to track it.

The rest of comments have been addressed.

Copy link
Contributor

@zhassan-aws zhassan-aws left a comment

Choose a reason for hiding this comment

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

Thanks!

I wonder if it's possible to check from a proc macro whether a type implements a trait.

@adpaco-aws
Copy link
Contributor Author

I wonder if it's possible to check from a proc macro whether a type implements a trait.

As far as I know, there aren't great solutions to this because proc_macro operates at a syntactic level and doesn't have access to type information. That's because type-checking occurs during the actual compilation, which takes place after macro expansion.

@adpaco-aws adpaco-aws enabled auto-merge (squash) July 31, 2024 15:49
@adpaco-aws adpaco-aws merged commit b5d306a into model-checking:main Jul 31, 2024
27 checks passed
tautschnig pushed a commit that referenced this pull request Aug 2, 2024
… attribute for structs (#3405)

This PR updates the crates documentation we already had for the
`#[safety_constraint(...)]` attribute to account for the changes in
#3270.

The proposal includes notes/guidelines on where to use the attribute
(i.e., the struct or its fields) depending on the type safety condition
to be specified. Also, it removes the `rs` annotations on the code
blocks that appear in the documentation because they don't seem to have
the intended effect (the blocks are not being highlighted at all).

The current version of this documentation can be found
[here](https://model-checking.github.io/kani/crates/doc/kani/derive.Arbitrary.html)
and
[here](https://model-checking.github.io/kani/crates/doc/kani/derive.Invariant.html).

Related #3095
github-merge-queue bot pushed a commit that referenced this pull request Aug 9, 2024
## 0.54.0

### Major Changes
* We added support for slices in the `#[kani::modifies(...)]` clauses
when using function contracts.
* We introduce an `#[safety_constraint(...)]` attribute helper for the
`Arbitrary` and `Invariant` macros.
* We enabled support for concrete playback for harness that contains
stubs or function contracts.
* We added support for log2*, log10*, powif*, fma*, and sqrt*
intrisincs.

### Breaking Changes
* The `-Z ptr-to-ref-cast-checks` option has been removed, and pointer
validity checks when casting raw pointers to references are now run by
default.

## What's Changed
* Make Kani reject mutable pointer casts if padding is incompatible and
memory initialization is checked by @artemagvanian in
#3332
* Fix visibility of some Kani intrinsics by @artemagvanian in
#3323
* Function Contracts: Modify Slices by @pi314mm in
#3295
* Support for disabling automatically generated pointer checks to avoid
reinstrumentation by @artemagvanian in
#3344
* Add support for global transformations by @artemagvanian in
#3348
* Enable an `#[safety_constraint(...)]` attribute helper for the
`Arbitrary` and `Invariant` macros by @adpaco-aws in
#3283
* Fix contract handling of promoted constants and constant static by
@celinval in #3305
* Bump CBMC Viewer to 3.9 by @tautschnig in
#3373
* Update to CBMC version 6.1.1 by @tautschnig in
#2995
* Define a struct-level `#[safety_constraint(...)]` attribute by
@adpaco-aws in #3270
* Enable concrete playback for contract and stubs by @celinval in
#3389
* Add code scanner tool by @celinval in
#3120
* Enable contracts in associated functions by @celinval in
#3363
* Enable log2*, log10* intrinsics by @tautschnig in
#3001
* Enable powif* intrinsics by @tautschnig in
#2999
* Enable fma* intrinsics by @tautschnig in
#3002
* Enable sqrt* intrinsics by @tautschnig in
#3000
* Remove assigns clause for ZST pointers by @carolynzech in
#3417
* Instrumentation for delayed UB stemming from uninitialized memory by
@artemagvanian in #3374
* Unify kani library and kani core logic by @jaisnan in
#3333
* Stabilize pointer-to-reference cast validity checks by @artemagvanian
in #3426
* Rust toolchain upgraded to `nightly-2024-08-07` by @jaisnan @qinheping
@tautschnig @feliperodri

## New Contributors
* @carolynzech made their first contribution in
#3387

**Full Changelog**:
kani-0.53.0...kani-0.54.0

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

Signed-off-by: Felipe R. Monteiro <felisous@amazon.com>
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.

Add support to type invariants
3 participants