Skip to content

Autoharness: support raw pointer arguments - #4678

Open
tautschnig wants to merge 2 commits into
model-checking:mainfrom
tautschnig:autoharness-raw-pointers
Open

Autoharness: support raw pointer arguments#4678
tautschnig wants to merge 2 commits into
model-checking:mainfrom
tautschnig:autoharness-raw-pointers

Conversation

@tautschnig

Copy link
Copy Markdown
Member

Description

Autoharness now generates harnesses for functions with raw pointer arguments (*const T/*mut T, including nested raw pointers), provided the pointee type implements or can derive Arbitrary. Previously such functions were skipped with "Missing Arbitrary implementation".

The generated harness produces each pointer in a nondeterministic allocation state via a new AnyPtr model:

  • null,
  • out of bounds (one past the end of the allocation, aligned but invalid for reads/writes), or
  • valid: pointing to a nondeterministic value of the pointee type, stored in a dedicated harness local so it stays allocated for the entire harness.

Key design decision: all generated pointers are aligned and carry valid provenance, so that Kani's memory predicates can reason about them. A function with a contract such as #[kani::requires(kani::mem::can_dereference(p))] verifies successfully, because the automatic contract harness assumes the precondition, which excludes the invalid states. I validated empirically that the two obvious alternatives break this property: both pointers cast from arbitrary integer addresses and pointers to deallocated objects make kani::mem::can_dereference fail with "Kani does not support reasoning about pointer to unallocated memory", so every dereferencing function would fail even with a correct contract. Consequently those states are not generated, and this and the other limitations (always aligned, no aliasing between distinct pointer arguments, initialized pointee, pointers only as immediate arguments rather than behind references or inside ADTs) are documented in the autoharness reference.

For functions without contracts, a dereference that cannot rule out the null/out-of-bounds states fails verification, with a counterexample showing the offending pointer state — for safe functions this is a genuine robustness finding, and the documentation points users to contracts with memory predicates for functions with caller obligations.

Testing

New script-based test cargo_autoharness_raw_pointers covering: unchecked dereference (fails), null-check-only dereference (fails: out-of-bounds state remains), address-only manipulation (passes), contract harnesses with can_dereference/can_write + modifies (pass), nested pointers (address-only passes, double-deref fails), and a cover check proving the valid state is reachable and readable.

Updated cargo_autoharness_filter since raw-pointer functions are now supported rather than skipped.

Manually verified no regressions in all autoharness and autoderive script-based tests, verify_std_cmd/std_codegen (the kani_core macro is shared with no_core mode), and kani-compiler unit tests.

Towards #3832 (ticks the "Pointers" box).

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

@tautschnig
tautschnig requested a review from a team as a code owner July 28, 2026 00:51
Copilot AI review requested due to automatic review settings July 28, 2026 00:51
@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 extends Kani’s autoharness generation to support functions that take raw pointer arguments by introducing a dedicated AnyPtr model and using it during automatic harness construction, plus adding documentation and regression tests to validate expected success/failure behavior.

Changes:

  • Added an AnyPtrModel in kani_core to generate raw pointers in a controlled nondeterministic allocation state.
  • Updated autoharness eligibility and harness construction to generate raw pointer arguments via AnyPtrModel (including nested raw pointers).
  • Added a new script-based test crate for raw-pointer autoharness behavior and updated the existing autoharness filter test output accordingly; documented the new raw-pointer behavior and limitations.

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_raw_pointers/src/lib.rs New test functions covering passing/failing cases for raw-pointer arguments and contract-based filtering via mem predicates.
tests/script-based-pre/cargo_autoharness_raw_pointers/raw-pointers.sh Script-based test entrypoint invoking cargo kani autoharness with required unstable flags.
tests/script-based-pre/cargo_autoharness_raw_pointers/raw-pointers.expected Expected output validating the new harness outcomes (success/failure + cover satisfaction).
tests/script-based-pre/cargo_autoharness_raw_pointers/config.yml Registers the new script-based test with expected output and non-zero exit code.
tests/script-based-pre/cargo_autoharness_raw_pointers/Cargo.toml Defines the new test crate used by the script-based test.
tests/script-based-pre/cargo_autoharness_filter/src/lib.rs Updates the filter test to treat raw-pointer arguments as supported (moves pointer cases into the “yes harness” set).
tests/script-based-pre/cargo_autoharness_filter/filter.expected Updates expected harness counts/rows/log ordering to reflect new raw-pointer support.
library/kani_core/src/arbitrary.rs Introduces any_ptr<T>(storage: &mut T) -> *mut T as the AnyPtrModel used by the compiler for autoharness generation.
kani-compiler/src/kani_middle/transform/automatic.rs Generates raw-pointer arguments using AnyPtrModel instead of kani::any() when building automatic harnesses.
kani-compiler/src/kani_middle/mod.rs Adds autoharness_supported_arg_ty to treat raw pointers as eligible when the pointee supports Arbitrary (or derivation).
kani-compiler/src/kani_middle/kani_functions.rs Registers the new KaniModel::AnyPtr function marker for lookup via QueryDb::kani_functions().
kani-compiler/src/kani_middle/codegen_units.rs Switches eligibility checking to the new autoharness_supported_arg_ty logic to include raw pointers.
docs/src/reference/experimental/autoharness.md Documents raw-pointer harness generation semantics and current limitations.
Comments suppressed due to low confidence (2)

kani-compiler/src/kani_middle/transform/automatic.rs:167

  • This comment assumes the storage local “stays alive for the entire harness”, but this helper is also used while constructing derived kani::any::<T>() bodies (AutomaticArbitraryPass). In that context, the local only lives until the end of the generated kani::any call, so the comment should avoid implying a harness-wide lifetime.
        // Generate the storage for the valid-pointer case: a local with a nondeterministic value
        // of the pointee type. Since it is a local of the harness body, it stays alive for the
        // entire harness.

kani-compiler/src/kani_middle/transform/automatic.rs:172

  • The comment describing the AnyPtr return states lists “dangling” and “dead object”, but AnyPtrModel only produces null, one-past-end (out-of-bounds), or a pointer to the provided storage. Keeping the terminology consistent helps readers understand what cases the harness actually explores.
        // Pass a mutable reference to the storage to the AnyPtr model, which returns a `*mut T`
        // that is either null, dangling, pointing to a dead object, or pointing to the storage.

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

Comment on lines +134 to +137
/// For raw pointer types, insert a call to the `KaniModel::AnyPtr` model instead, which generates
/// a pointer in a nondeterministic allocation state (null, dangling, dead object, or valid);
/// in the valid case, the pointer points to a nondeterministic value stored in a dedicated local,
/// which stays alive for the entire harness.
Comment on lines +359 to +361
/// implements or can derive `Arbitrary`: for those, the harness generates a pointer in a
/// nondeterministic allocation state (null, dangling, dead object, or valid),
/// c.f. `KaniModel::AnyPtr`.
tautschnig and others added 2 commits July 29, 2026 00:38
Previously, autoharness skipped any function with a raw pointer argument,
since raw pointers do not implement Arbitrary. Now, the generated harness
produces pointers in a nondeterministic allocation state via a new AnyPtr
model: null, out of bounds (one past the end of the allocation), or valid,
i.e. pointing to a nondeterministic value of the pointee type that is stored
in a dedicated local of the harness and hence stays allocated for the entire
harness. Nested raw pointers are supported as well.

All generated pointers are aligned and carry valid provenance, so that Kani's
memory predicates can reason about them: a function whose contract requires,
e.g., kani::mem::can_dereference(ptr) verifies successfully, because assuming
the precondition excludes the null and out-of-bounds states. We deliberately
do not generate pointers cast from arbitrary integer addresses or pointers to
deallocated objects, since Kani's memory predicates cannot reason about
either ('Kani does not support reasoning about pointer to unallocated
memory'), which would make it impossible to verify any dereferencing function
even with a contract. These and other limitations (always aligned, no
aliasing between distinct pointer arguments, initialized pointee) are
documented in the autoharness reference.

Raw pointers are only supported as immediate harness arguments (or nested in
other raw pointers), not behind references or inside user-defined types,
since the pointee storage that the harness allocates would not outlive a
generated compound value.

This addresses the 'Pointers' item of the automatic harness generation
tracking issue.

Towards model-checking#3832

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
The eligibility loop cached 'is this argument type supported?' in
ty_arbitrary_cache, which implements_arbitrary also populates with 'does this
type implement Arbitrary?' verdicts while walking ADT fields. The two
semantics differ for raw pointers (supported as arguments, not as fields), so
whichever check ran first poisoned the other: a struct with a raw-pointer
field processed before a raw-pointer argument made the argument spuriously
ineligible (observed on aho-corasick), and the reverse order would wrongly
accept the struct and later ICE during harness generation. Never cache
raw-pointer types in the shared cache; regression-tested in both orders.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
@tautschnig
tautschnig force-pushed the autoharness-raw-pointers branch from e11907b to df1847d Compare July 29, 2026 00:39
tautschnig added a commit to tautschnig/kani that referenced this pull request Jul 29, 2026
Functions taking &[T]/&mut [T] (with T implementing or capable of deriving
Arbitrary) or &str were previously skipped with 'Missing Arbitrary
implementation'; in an evaluation across the top-100 crates.io crates these
argument types alone accounted for ~1400 skipped functions.

The generated harness now produces a slice of nondeterministic length,
backed by a nondeterministic array stored in a dedicated harness local
(which stays alive for the entire harness), via two new models: AnySliceRef
returns '&mut storage[..len]' with nondeterministic len (reborrowed as
shared for &[T] arguments), bounded at 16 elements; AnyStrRef returns the
longest valid-UTF-8 prefix of the nondeterministic bytes, bounded at 8
bytes.

The string model reuses the algorithm of String's BoundedArbitrary
implementation: computing the valid prefix is a deterministic function of
the nondeterministic bytes, which symbolic execution handles well, whereas
*assuming* str::from_utf8(..).is_ok() over nondeterministic bytes and
length is intractable even at a bound of 4 bytes. All valid UTF-8 contents
up to the bound are covered, including multi-byte characters (demonstrated
by a cover check); the smaller string bound reflects measured verification
cost (bound 16 exceeds the default 60s harness timeout, bound 8 stays well
within it).

The length-bound caveat is documented in the autoharness reference. Both
bounds are deliberately below Kani's default unwinding bound of 20 so that
loops over generated slices can be fully unwound by default.

Like raw pointers (model-checking#4678), slice references are supported in argument
position only, where the harness owns the backing storage; slice references
nested in other references or inside user-defined types remain unsupported.
The eligibility cache is bypassed for slice-reference types, since their
argument-position verdict differs from their (unsupported) field-position
verdict.

Towards model-checking#3832

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
tautschnig added a commit to tautschnig/kani that referenced this pull request Jul 29, 2026
Functions taking &[T]/&mut [T] (with T implementing or capable of deriving
Arbitrary) or &str were previously skipped with 'Missing Arbitrary
implementation'; in an evaluation across the top-100 crates.io crates these
argument types alone accounted for ~1400 skipped functions.

The generated harness now produces a slice of nondeterministic length,
backed by a nondeterministic array stored in a dedicated harness local
(which stays alive for the entire harness), via two new models: AnySliceRef
returns '&mut storage[..len]' with nondeterministic len (reborrowed as
shared for &[T] arguments), bounded at 16 elements; AnyStrRef returns the
longest valid-UTF-8 prefix of the nondeterministic bytes, bounded at 8
bytes.

The string model reuses the algorithm of String's BoundedArbitrary
implementation: computing the valid prefix is a deterministic function of
the nondeterministic bytes, which symbolic execution handles well, whereas
*assuming* str::from_utf8(..).is_ok() over nondeterministic bytes and
length is intractable even at a bound of 4 bytes. All valid UTF-8 contents
up to the bound are covered, including multi-byte characters (demonstrated
by a cover check); the smaller string bound reflects measured verification
cost (bound 16 exceeds the default 60s harness timeout, bound 8 stays well
within it).

The length-bound caveat is documented in the autoharness reference. Both
bounds are deliberately below Kani's default unwinding bound of 20 so that
loops over generated slices can be fully unwound by default.

Like raw pointers (model-checking#4678), slice references are supported in argument
position only, where the harness owns the backing storage; slice references
nested in other references or inside user-defined types remain unsupported.
The eligibility cache is bypassed for slice-reference types, since their
argument-position verdict differs from their (unsupported) field-position
verdict.

Towards model-checking#3832

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
tautschnig added a commit to tautschnig/kani that referenced this pull request Jul 29, 2026
Functions taking &[T]/&mut [T] (with T implementing or capable of deriving
Arbitrary) or &str were previously skipped with 'Missing Arbitrary
implementation'; in an evaluation across the top-100 crates.io crates these
argument types alone accounted for ~1400 skipped functions.

With the new --bounded-arguments option, the generated harness produces a
slice of nondeterministic length, backed by a nondeterministic array stored
in a dedicated harness local, via two new models: AnySliceRef returns
'&mut storage[..len]' with nondeterministic len (reborrowed as shared for
&[T] arguments), bounded at 16 elements; AnyStrRef returns the longest
valid-UTF-8 prefix of the nondeterministic bytes, bounded at 8 bytes,
reusing the algorithm of String's BoundedArbitrary implementation
(computing the valid prefix is a deterministic function of the bytes, which
symbolic execution handles well, whereas assuming from_utf8().is_ok() is
intractable even at a bound of 4 bytes; measured cost 10s@4/28s@8/180s@16,
so bound 8 stays within the default 60s harness timeout).

Because a verification result for such a harness only holds up to the
bound -- a bug that requires a larger input will not be found -- bounded
harness generation is strictly opt-in and visibly reported:
- without --bounded-arguments, eligible functions are skipped with the new
  reason 'Requires --bounded-arguments for argument(s) ...';
- with the option, such harnesses are marked '(bounded)' in the summary
  table (via a new HarnessMetadata::is_bounded field), and a note after the
  table spells out the caveat.

Both bounds sit below Kani's default unwinding bound of 20 so that loops
over generated slices can be fully unwound by default. Like raw pointers
(model-checking#4678), slice references are supported in argument position only, where
the harness owns the backing storage. Top-level argument verdicts are no
longer inserted into the shared Arbitrary cache at all, eliminating the
argument-position vs. field-position cache-poisoning hazard class
(implements_arbitrary memoizes its own recursion, so repeated argument
types stay cheap).

Towards model-checking#3832

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
@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