Autoharness: support raw pointer arguments - #4678
Open
tautschnig wants to merge 2 commits into
Open
Conversation
Contributor
There was a problem hiding this comment.
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
AnyPtrModelinkani_coreto 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 generatedkani::anycall, 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
AnyPtrreturn states lists “dangling” and “dead object”, butAnyPtrModelonly 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`. |
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
force-pushed
the
autoharness-raw-pointers
branch
from
July 29, 2026 00:39
e11907b to
df1847d
Compare
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 deriveArbitrary. Previously such functions were skipped with "Missing Arbitrary implementation".The generated harness produces each pointer in a nondeterministic allocation state via a new
AnyPtrmodel: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 makekani::mem::can_dereferencefail 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_pointerscovering: unchecked dereference (fails), null-check-only dereference (fails: out-of-bounds state remains), address-only manipulation (passes), contract harnesses withcan_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_filtersince raw-pointer functions are now supported rather than skipped.Manually verified no regressions in all
autoharnessandautoderivescript-based tests,verify_std_cmd/std_codegen(thekani_coremacro is shared withno_coremode), andkani-compilerunit 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.