Skip to content

Autoharness: assume safety invariants of generated values - #4677

Open
tautschnig wants to merge 2 commits into
model-checking:mainfrom
tautschnig:autoharness-assume-invariants
Open

Autoharness: assume safety invariants of generated values#4677
tautschnig wants to merge 2 commits into
model-checking:mainfrom
tautschnig:autoharness-assume-invariants

Conversation

@tautschnig

Copy link
Copy Markdown
Member

Description

Automatic harnesses (kani autoharness) now assume that the nondeterministic values they generate respect the type's safety invariant (kani::Invariant), including for nested types.

Previously, automatic harnesses generated values purely via Arbitrary, so a safety invariant was respected only if it happened to be baked into the type's Arbitrary implementation (e.g. via #[safety_constraint(...)] or a manual impl Arbitrary that assumes is_safe()). Types with an Invariant implementation but an unconstrained (or compiler-synthesized) Arbitrary implementation received invariant-violating inputs, producing spurious counterexamples. Per the Unsafe Code Guidelines' definition of a safety invariant, safe code may assume its inputs uphold their types' safety invariants, so these counterexamples are noise.

This PR is split into two commits:

  1. Move the Invariant trait into kani_core (new generate_invariant! macro) so it is also available in no_core mode, and add an assume_safe<T: Invariant> model function (KaniModel::AssumeSafe) that assumes value.is_safe(). The trait remains available as kani::Invariant; no user-facing change.
  2. In the autoharness transformation passes, wrap every generated nondeterministic ADT value whose type implements Invariant in a call to the AssumeSafe model. Since this happens in call_kani_any_for_ty, which is shared by AutomaticHarnessPass (harness arguments, including behind references) and AutomaticArbitraryPass (fields of compiler-synthesized Arbitrary implementations), nested invariants are respected as well. A new implements_invariant check mirrors the existing implements_arbitrary resolution approach.

Note that automatic harnesses still do not assert type invariants (e.g. on return values); that remains the job of function contracts such as #[kani::ensures(|result| result.is_safe())]. The autoharness reference documentation now spells out both halves of this behavior.

Testing

New script-based test cargo_autoharness_assume_invariant with 8 automatic harnesses: manual Invariant with compiler-synthesized Arbitrary, manual Invariant with unconstrained derived Arbitrary, #[safety_constraint], two nested-invariant variants, an argument behind a reference, and two negative controls (verifying that the harness neither assumes more than the invariant nor invents invariants for types without an Invariant implementation). Before this change, three of these harnesses failed spuriously.

Manually verified no regressions in: all existing autoharness and autoderive script-based tests, the Invariant and derive-invariant suites, verify_std_cmd/std_codegen (exercising the no_core path), kani-compiler unit tests, and the kani library doctests.

Towards #3832 (ticks the "Types with invariants that the harnesses need to respect" box).

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

Copilot AI review requested due to automatic review settings July 28, 2026 00:08
@tautschnig
tautschnig requested a review from a team as a code owner July 28, 2026 00:08
@github-actions github-actions Bot added Z-EndToEndBenchCI Tag a PR to run benchmark CI Z-CompilerBenchCI Tag a PR to run benchmark CI labels Jul 28, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates Kani’s automatic harness generation (kani autoharness) to assume type safety invariants (kani::Invariant::is_safe()) for generated nondeterministic values, including for nested ADT fields, reducing spurious counterexamples caused by invariant-violating inputs.

Changes:

  • Move/generate the Invariant trait from kani into kani_core (via generate_invariant!) and add an assume_safe<T: Invariant>(T) -> T model function (KaniModel::AssumeSafe).
  • Update the autoharness and compiler-synthesized Arbitrary paths to wrap generated ADT values with AssumeSafe when the type implements Invariant.
  • Add a new script-based regression test and update autoharness reference docs to describe invariant assumptions.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/script-based-pre/cargo_autoharness_assume_invariant/src/lib.rs New regression crate covering invariant assumptions (including nested and negative controls).
tests/script-based-pre/cargo_autoharness_assume_invariant/config.yml Script-based test configuration for the new regression test.
tests/script-based-pre/cargo_autoharness_assume_invariant/Cargo.toml New test crate manifest for the regression.
tests/script-based-pre/cargo_autoharness_assume_invariant/assume-invariant.sh Runs cargo kani autoharness -Z autoharness for the new test.
tests/script-based-pre/cargo_autoharness_assume_invariant/assume-invariant.expected Expected summary output: 6 successes / 2 failures (exit code 1).
library/kani/src/lib.rs Removes old invariant module exports (potential compatibility impact).
library/kani/src/invariant.rs Removes the prior Invariant trait/module implementation (now generated from kani_core).
library/kani_core/src/lib.rs Adds mod invariant; and invokes generate_invariant!() in kani_lib!.
library/kani_core/src/invariant.rs Introduces generate_invariant! macro, the Invariant trait, and assume_safe model.
kani-compiler/src/kani_middle/transform/automatic.rs Wraps generated ADT nondet values with AssumeSafe when Invariant is implemented.
kani-compiler/src/kani_middle/mod.rs Adds implements_invariant resolution helper mirroring implements_arbitrary.
kani-compiler/src/kani_middle/kani_functions.rs Adds KaniModel::AssumeSafe marker mapping.
docs/src/reference/experimental/autoharness.md Documents invariant assumptions (needs wording alignment with implementation scope).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +109 to +113
If a type implements the [`Invariant`](https://model-checking.github.io/kani/crates/doc/kani/trait.Invariant.html) trait,
Kani assumes that the nondeterministic values it generates for automatic harnesses respect the type's safety invariant,
i.e., each generated value `v` satisfies `v.is_safe()`.
This assumption applies to nested values as well: if a field of a generated value has a type that implements `Invariant`,
the field's safety invariant is assumed to hold, even if the enclosing type does not implement `Invariant` itself.
Comment thread library/kani/src/lib.rs
Comment on lines 33 to 34
pub mod futures;
pub mod invariant;
pub mod iter;
tautschnig and others added 2 commits July 29, 2026 00:37
Move the definition of the `kani::Invariant` trait and its implementations
for primitive types from the `kani` library into a new `generate_invariant!`
macro in `kani_core`, so that the trait is also available in `no_core` mode
(e.g. for `kani verify-std`). The trait is unchanged and remains available
as `kani::Invariant`.

Additionally, introduce an `assume_safe` model function (registered as
`KaniModel::AssumeSafe`), which assumes that a value respects its type's
safety invariant. The compiler will use this model to constrain the
nondeterministic values generated for automatic harnesses to values that
respect the type's safety invariant.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Previously, automatic harnesses generated nondeterministic values purely via
`Arbitrary`, so a type's safety invariant (`kani::Invariant`) was respected
only if it happened to be baked into the type's `Arbitrary` implementation
(e.g. via `#[safety_constraint(...)]`). Types with an `Invariant` impl but an
unconstrained (or compiler-synthesized) `Arbitrary` impl would receive
invariant-violating inputs, producing spurious counterexamples.

Now, whenever an automatic harness or a compiler-synthesized `Arbitrary`
implementation generates a nondeterministic value whose type implements
`Invariant`, we insert a call to the `AssumeSafe` model, which assumes that
the value satisfies `is_safe()`. This applies to nested values as well: fields
of synthesized `Arbitrary` implementations are constrained the same way.

This addresses the 'Types with invariants that the harnesses need to respect'
item of the automatic harness generation tracking issue.

Towards model-checking#3832

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
@tautschnig
tautschnig force-pushed the autoharness-assume-invariants branch from 1493e41 to 92d3b3b Compare July 29, 2026 00:38
@feliperodri feliperodri added the Z-Autoharness Issue related to autoharness subcommand label Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Z-Autoharness Issue related to autoharness subcommand Z-CompilerBenchCI Tag a PR to run benchmark CI Z-EndToEndBenchCI Tag a PR to run benchmark CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants