Skip to content

Autoharness: mine constructor assertions into value filters - #4718

Open
tautschnig wants to merge 3 commits into
model-checking:mainfrom
tautschnig:mining-pr
Open

Autoharness: mine constructor assertions into value filters#4718
tautschnig wants to merge 3 commits into
model-checking:mainfrom
tautschnig:mining-pr

Conversation

@tautschnig

Copy link
Copy Markdown
Member

Description

Stacked on #4716 and #4717 (review only the last commit).

Extends --constructor-args with assert mining: the constructor search now prefers assert-guarded representation constructors (unsafe / doc-hidden / _unchecked-named, returning Self), which are inlined into the synthesized kani::any body with every validity statement converted into a filter on the nondeterministic arguments:

  • kani::assert(cond, msg) calls (Kani's macro overrides have already rewritten user asserts/panics into these) become kani::assume(cond);
  • hint::assert_unchecked(cond) (UB-hint contracts, e.g. deranged's new_unchecked) becomes kani::assume(cond);
  • raw panic-entry calls become assume(false); unreachable;
  • MIR Assert terminators (overflow checks) become assume(cond == expected).

Calls within the inlined body whose callees contain such validity statements are recursively inlined (depth ≤ 3, ≤ 32 blocks per callee, plain-call fallback otherwise) — this covers nested patterns like time's Time::__from_hms_nanos_unchecked calling deranged's RangedU32::new_unchecked.

The insight: an unchecked representation constructor's assertions state the type's validity contract exactly (they were written as the caller's proof obligations), and the constructor is surjective onto the valid value space — so the generated set is precisely the values passing the type's own validity assertions. This is strictly better than assuming a checked constructor's success (which may reach only a subset of valid values and interferes with functions' own Result paths).

Measured on time-0.3.54 (baseline 341 verified / 500 failing): checked-ctor assumption gives 538/315, hand-written Invariant impls for three types give 490/363, assert mining gives 595/258 (251 harnesses fixed, 8 regressed — predominantly CBMC 60-second timeouts from formula growth of inlined generation, logged as a refinement).

Testing

The cargo_autoharness_constructor test gains a nested-unchecked-constructor case (a wrapper constructor calling an inner new_unchecked with debug_asserts): fails without --constructor-args, passes with it. Niche and autoderive suites pass.

Towards #3832.

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

tautschnig and others added 3 commits August 5, 2026 15:04
A layout niche (rustc_layout_scalar_valid_range, as used by std's NonZero
and core::time::Duration's Nanoseconds field) is a language-level validity
invariant: a value outside the niche is as invalid as a bool holding 3, and
rustc packs enum variants into the invalid patterns. Nondeterministic-value
generation for types without an Arbitrary implementation previously
produced such values, which is unsound in the garbage-in sense and causes
false alarms in every harness generating the type.

After each generated value of a scalar-ABI type whose valid range is
restricted, emit kani::assume(<raw bits> in valid_range), handling wrapping
ranges (NonZero's 1..=0). Sound by construction: no flag or report marker
needed.

Verified on the time crate: fixes the InstantExt/SystemTimeExt
signed_duration_since harnesses (std Duration receivers); the regression
test's covers confirm no over-constraining.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
The top-100 crates.io failure triage (model-checking#3832) showed the largest class of
genuine false alarms is generated receivers violating private type
invariants (e.g. time's Date packs a validated ordinal; raw field synthesis
produces invalid dates, failing every method harness). Under the new opt-in
--constructor-args flag, kani::any::<T> for private-field structs is
synthesized as: generate nondeterministic constructor arguments, call one
of T's public constructors, assume success (switching on the discriminant
for Option<Self>/Result<Self, E> returns), and return the payload.

Constructor search excludes non-public, doc-hidden (commonly _unchecked
variants exported for macros that assert preconditions), unsafe,
zero-argument (single-point coverage; Instant::now() reaches unsupported
clock_gettime), and generic constructors; it prefers Self over Option<Self>
over Result<Self, E> returns, then more arguments over fewer.

The option is opt-in because it under-approximates (only
constructor-reachable values are explored): harnesses are marked "(ctor)"
via new is_ctor_based metadata, with an explanatory note in the summary.

Measured on time-0.3.54: 341 -> 538 verified, 500 -> 315 failures.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Extend --constructor-args with assert mining: prefer assert-guarded
representation constructors (unsafe / doc-hidden / _unchecked-named,
returning Self; generic ADTs instantiated with their own args), inlined
into the synthesized kani::any body with every validity statement converted
into a filter on the nondeterministic arguments:
- kani::assert(cond, msg) calls (Kani's macro overrides have already
  rewritten user asserts/panics into these) -> kani::assume(cond);
- hint::assert_unchecked(cond) (UB-hint contracts, e.g. deranged's
  new_unchecked) -> kani::assume(cond);
- raw panic-entry calls -> assume(false) + unreachable;
- MIR Assert terminators (overflow checks) -> assume(cond == expected).
Calls within the inlined body whose callees contain such validity
statements are recursively inlined (depth <= 3, <= 32 blocks per callee,
plain-call fallback), covering nested patterns like time's
Time::__from_hms_nanos_unchecked calling deranged's new_unchecked.

Such a constructor is typically the raw representation builder whose
asserts state the type's validity contract exactly, and is surjective onto
the valid value space; the generated set is then precisely the values
passing the type's own validity assertions. New MutableBody primitives
push_raw_bb/split_with_terminator support the inlining; a whitelist
remapper bails out (falling back to checked-constructor generation) on
unsupported constructs.

Measured on time-0.3.54 (vs. 341 ok / 500 fail baseline): checked-ctor
assumption 538/315; hand-written invariants 490/363; assert mining 595/258
(251 fixed, 8 broke -- predominantly CBMC 60s-timeouts from formula
growth, a logged refinement).

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 5, 2026 15:53
@tautschnig
tautschnig requested a review from a team as a code owner August 5, 2026 15:53
@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 Aug 5, 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

Extends Kani’s autoharness value-generation pipeline to (optionally) generate values via constructors for private-field structs, and further improves coverage by mining validity assertions from “unchecked” representation constructors into assume-style filters during inlining. This targets reducing false alarms caused by invariant-violating nondeterministic inputs in automatically generated harnesses.

Changes:

  • Add --constructor-args plumbing and reporting for constructor-based autoharness generation, including (ctor) harness marking.
  • Implement MIR inlining + “assert mining” to convert constructor validity checks (asserts/panics/overflow asserts) into assumptions over nondeterministic constructor arguments (including limited recursive inlining).
  • Add new script-based regression tests for constructor-based generation and scalar layout niche constraints; update autoharness docs accordingly.

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/script-based-pre/cargo_autoharness_constructor/src/lib.rs New regression crate exercising constructor-based generation + nested unchecked constructors.
tests/script-based-pre/cargo_autoharness_constructor/constructor.sh Script to compare autoharness results with/without --constructor-args.
tests/script-based-pre/cargo_autoharness_constructor/constructor.expected Expected output capturing (ctor) marking and result deltas.
tests/script-based-pre/cargo_autoharness_constructor/config.yml Registers the new script-based-pre test.
tests/script-based-pre/cargo_autoharness_constructor/Cargo.toml New test crate manifest.
tests/script-based-pre/autoharness_niche/run.sh Script-based test for scalar valid-range niche assumptions.
tests/script-based-pre/autoharness_niche/niche_probe.rs New niche-probe test code (valid-range + cover checks).
tests/script-based-pre/autoharness_niche/expected Expected output for niche-probe run.
tests/script-based-pre/autoharness_niche/config.yml Registers the niche script-based-pre test.
kani-driver/src/sarif.rs Updates SARIF test scaffolding for new harness metadata field.
kani-driver/src/metadata.rs Updates driver metadata test scaffolding for new harness metadata field.
kani-driver/src/autoharness/mod.rs Forwards --constructor-args and prints (ctor)/note in summary output.
kani-driver/src/args/autoharness_args.rs Adds CLI flags for autoharness options (incl. constructor args).
kani-compiler/src/kani_middle/transform/body.rs Adds utilities for appending/splitting basic blocks used by inlining.
kani-compiler/src/kani_middle/transform/automatic.rs Core implementation: niche assumptions + constructor generation + assert-mining inlining.
kani-compiler/src/kani_middle/mod.rs Adds constructor discovery, ctor-based harness marking detection, and scalar niche computation.
kani-compiler/src/kani_middle/metadata.rs Plumbs is_ctor_based into generated harness metadata.
kani-compiler/src/kani_middle/codegen_units.rs Carries is_ctor_based through autoharness selection/codegen metadata.
kani-compiler/src/args.rs Adds compiler-side flags for autoharness options.
kani_metadata/src/harness.rs Adds is_ctor_based to harness metadata (serde defaulted).
docs/src/reference/experimental/autoharness.md Documents constructor-based generation option.
Cargo.lock Updates locked dependency version(s) (notably charon).

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

Comment on lines +27 to +31
/// nondeterministic values, e.g. slice references (`&[T]`, `&str`). Such harnesses are
/// marked "(bounded)" in the output, and their verification results only hold up to the
/// bounds; a bug that requires a larger input will not be found.
#[arg(long)]
pub bounded_arguments: bool,
pub bounded_arguments: bool,

/// Generate nondeterministic values for types without an Arbitrary implementation by
/// calling one of the type's own public constructors with nondeterministic arguments
Comment thread kani-compiler/src/args.rs
Comment on lines +114 to +118
/// If we are running the autoharness subcommand, whether to generate harnesses for
/// functions whose arguments require bounded nondeterministic values (e.g. slice
/// references). See kani_driver::autoharness_args for documentation.
#[arg(long = "autoharness-bounded-arguments")]
pub autoharness_bounded_arguments: bool,
Comment on lines +87 to +91
causing false alarms in every harness that generates the type. With `--constructor-args`, Kani
instead generates values of private-field struct types by calling one of the type's public
constructors with nondeterministic arguments, assuming success for constructors returning
`Option<Self>` or `Result<Self, E>`. Constructors that are doc-hidden, unsafe, zero-argument,
or generic are not considered.
Comment on lines +252 to +256
if any_ctor {
println!(
"Note: harnesses marked \"(ctor)\" generate some values through a type's public constructor (--constructor-args);\n\
their verification results only cover values reachable through that constructor."
);
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