Conversation
Add mod verify harnesses for all 22 Challenge 24 targets in IntoIter and the spec_extend / spec_from_iter / spec_from_elem / extract_if helpers. Harnesses exercise the real shipped bodies (no cfg(kani) rewrites) over symbolic-length inputs, with every kani::assume paired with a satisfied kani::cover. Unbounded and generic-T are not literally met (committee gate) and are disclosed. Additive only.
…y, drop) Add harnesses instantiating the IntoIter / spec_* move and index paths over element shapes the existing u8/i8/() harnesses do not exercise: an over-aligned type, an odd-stride type, a validity-niche type, and a destructor type across the move/consume paths. Existing harnesses are unchanged; additive only.
kasimte
added a commit
to kasimte/kani
that referenced
this pull request
Sep 30, 2026
…king#4865) Contracts on a trait method whose type carries a concrete generic argument — `<CStr as Index<RangeFrom<usize>>>::index`, or a concrete instantiation of a generic self type — failed to resolve with `MissingTraitImpl`. `resolve_ty` returned the definition's identity type (`RangeFrom<usize>` came back as `RangeFrom<Idx>`), so the arguments written in the path were parsed but never applied. This PR applies them, in `resolve_ty`'s `Type::Path` arm, via the new `instantiate_path_args`. ## Verification The tests live in the standard `tests/kani` and `tests/expected` suites (CI runs them); each has a definite outcome: | test (`-Zfunction-contracts`) | expect | |---|---| | `tests/kani/FunctionContracts/generic_argument_instantiation.rs` | **6 targets verify.** A primitive argument resolved before (regression guard); the five generic shapes resolve *only* with this change — a generic argument, a generic self type (associated-type return, `where`-bounded method), a concrete DST self, a generic `impl` at a concrete instantiation, and a type with a lifetime parameter beside a type parameter (lifetime erased). | | `tests/expected/function-contract/generic_arg_unimplemented.rs` | **still fails `MissingTraitImpl`** *(expected-output test — this diagnostic is the pass)* — `<S as Generic<Wrap<u16>>>::generic` is genuinely not implemented, so valid targets resolve without over-resolving invalid ones. | | `tests/expected/function-contract/generic_method_unimplemented.rs` | **still fails `MissingTraitImpl`** *(expected-output test — this diagnostic is the pass)* — a trait method with its own generic parameter (`fn compute<T>`) is out of scope (see below). | Confirmed on nightly-2026-09-22 (Kani 0.68.0, CBMC 6.11.0): all six verify; with the fix reverted, the generic targets fail to resolve while the primitive resolves. The `cross_module_multiple_impls` (7/7), `multiple_inherent_impls` (3/3), and resolver unit (41/41) suites are unchanged. Mechanism: this is the "trait functions with generic parameters" limitation described in [model-checking#1997](model-checking#1997 (comment)). The trait's arguments already reach trait-impl resolution, but each came back uninstantiated from `resolve_ty`, and full `Instance::resolve` cannot match a concrete impl from a free parameter. Making the arguments concrete fixes that; any shape that cannot be resolved (const-generic arguments, omitted defaulted parameters) keeps the old uninstantiated type, so nothing that resolved before changes. No partial-resolution machinery is needed — with concrete arguments, the existing full resolution matches. These are not hypothetical. An iterator adapter's `__iterator_get_unchecked` (a generic self type) is another std-library method of this shape: the Challenge 16 and Challenge 24 submissions ([model-checking/verify-rust-std#549](model-checking/verify-rust-std#549), [model-checking/verify-rust-std#689](model-checking/verify-rust-std#689)) disclose it and fall back to `kani::assume` mirror harnesses. What this change reaches there: closure-free adapters resolve (`Cloned`, `Zip`); `Map`/`Filter` do not, since `fn`/closure types aren't supported by `resolve_ty`. Challenge 24's `<vec::IntoIter<u8> as Iterator>::__iterator_get_unchecked` resolves only with the allocator written out (`IntoIter<u8, std::alloc::Global>`, which needs `allocator_api`): omitted trailing parameters with declared defaults are not filled and keep the uninstantiated type. Related to model-checking#1997 — this handles a type carrying concrete generic arguments: a generic trait argument, or a generic self type instantiated to concrete types, against a concrete or generic `impl`. A trait method with its own generic parameter still fails to resolve — that parameter is not part of the path, so nothing here binds it. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
wodex1nhaoIeng
pushed a commit
to wodex1nhaoIeng/kani
that referenced
this pull request
Oct 2, 2026
…el-checking#4915) A `proof_for_contract` target that omits a trailing generic parameter with a declared default failed to resolve with `MissingTraitImpl`. For example `<Vec<u8> as Trait>::m`, or the natural spelling of `<vec::IntoIter<u8> as Iterator>::__iterator_get_unchecked`, a target in the Challenge 24 submission (model-checking/verify-rust-std#689); both omit the allocator parameter. The parameter-count check kept the uninstantiated type instead of filling the default. This PR fills an omitted trailing parameter from its declared default, instantiated with the arguments so far, matching what rustc does for omitted arguments. The logic lives in `default_type_arg`, called from `instantiate_path_args`'s substitution loop. A path written with no generic arguments at all (a bare `Wrapper` for `struct Wrapper<T = u8>`) is treated as an empty argument list, so an all-defaulted type resolves too. Parenthesized arguments keep the uninstantiated type. ## Verification | test (`-Zfunction-contracts`) | expect | |---|---| | `tests/kani/FunctionContracts/generic_default_argument_fill.rs` | **5 targets verify.** A std container default (`Vec<u8>` fills `A = Global`); a sibling impl with a distinct postcondition (`Vec<u16>`), so each harness only verifies if resolution picks its own impl; a default referencing an earlier parameter (`Pair<T, U = T>` at `Pair<u8>` fills `U = u8`); the explicit spelling (`Vec<u16, std::alloc::Global>`) unchanged; and an all-defaulted type written with no argument list (`AllDefault` for `struct AllDefault<T = u8>`) fills `T = u8`. | | `tests/expected/function-contract/generic_default_missing_required.rs` | **still fails `MissingTraitImpl`** *(expected-output test; this diagnostic is the pass)*. `NoDefault<u8>` omits a parameter with no default, so only declared defaults are filled. | | `tests/expected/function-contract/generic_default_missing_bare.rs` | **still fails `MissingTraitImpl`** *(expected-output test; this diagnostic is the pass)*. `NoDefaultBare` written with no argument list omits a parameter with no default, so the no-arguments path still keeps the uninstantiated type. | | `tests/expected/function-contract/generic_default_const_param.rs` | **still fails `MissingTraitImpl`** *(expected-output test; this diagnostic is the pass)*. `WithConst<u8>` omits a defaulted **const** parameter; only defaulted type parameters are filled, so the const default is not filled and the target keeps the uninstantiated type. | The four original targets were confirmed locally when this PR was first submitted (`generic_default_argument_fill` 4/4, the two negatives failing `MissingTraitImpl`, resolver suites unchanged at `generic_argument_instantiation` 6, `cross_module_multiple_impls` 7, `multiple_inherent_impls` 3). This revision adds the no-arguments case (`check_all_parameters_defaulted`) and the `generic_default_missing_bare` negative; CI runs the full suite on the pinned toolchain. Scope: type parameters only. A const parameter (with or without a default), and any argument that cannot be resolved, keep the uninstantiated type, as before. Defaults are instantiated structurally (no normalization), matching how `resolve_ty` already hands `tcx.type_of` results forward. Resolves model-checking#4914. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
This branch has not been deployed
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.
Towards #285.
Kani harnesses for Challenge 24: Verify the safety of
Vecfunctions part 2 —Vec::IntoIterand the specialization helpers it routes through (spec_extend,spec_from_iter,spec_from_iter_nested,spec_from_elem,from_elem,extract_if). 47 harnesses across all 22 listed functions. Each runs the function's real shipped body — no#[cfg(kani)]rewrite, nokani::assume(false)on a real branch — at symbolic length, and asserts the observable effect rather than just absence of UB. All pass viascripts/run-kani.sh.Unbounded length and generic
Tare not literally met. This is left as a committee question open across the sentence-pair challenges, and is disclosed under Known limitations.What the harnesses check
kani::slice::any_slice_of_array+to_vec, so the pointer-walking loops (next/next_back,fold/try_fold,advance_by/advance_back_by) run a symbolic number of iterations up to the backing size (64 forIntoIter, smaller where noted).next/next_backreturn the exact first/last element and shrink length by one;advance_by(k)returnsOkiffk <= len;foldvisits exactlylenelements;from_elem(e, n)yields lengthnwith everyv[j] == e;size_hint == (len, Some(len)).try_foldshort-circuits (Err) in both au8and aDrop-carrying variant, exercisingIntoIter's realDrop— the double-drop the body'sptr.add(1)-before-fordering exists to prevent.extract_if::nextwrites through the realvec.as_mut_ptr().add(i).T::IS_ZSTbranch (byte-walkingend, fixedptr) is verified onVec<()>at symbolic length.__iterator_get_unchecked. Already ships#[requires(i < self.len())]+kani::modifies(self)onmainwith no exercising harness; verified here via a mirroringkani::assume(see Upstream Kani contributions).kani::coverreachability witnesses across the suite.Element-type coverage (+8 harnesses)
These reach properties of
Ttheu8/()instantiations don't. Each body is written once for arbitraryTand instantiated per shape, mirroring its_u8counterpart.check_into_iter_next_al16next#[repr(align(16))]): 16-byte stridecheck_into_iter_next_boolnextcheck_into_iter_next_droptokennextDroptypecheck_into_iter_next_back_droptokennext_backcheck_into_iter_get_unchecked_arr3__iterator_get_uncheckedadd(i)check_spec_extend_intoiter_droptokenspec_extend(IntoIter)forget_remaining_elements, no double-dropcheck_from_iter_intoiter_droptokenSpecFromIter(IntoIter)ManuallyDrop+from_partsownership transfercheck_extract_if_next_droptokenExtractIf::nextShapes:
Al16(#[repr(align(16))]),[u8; 3](odd stride),bool(validity niche),DropToken(realDrop). Added CI cost ≈57 s total at the pinned Kani 0.67 / CBMC 6.10.0.Arbitrary reachable iterator states (+13 harnesses)
The harnesses above start from a fresh iterator (
ptr == buf). These re-check the surface from arbitrary reachable states: a symbolic number of elements consumed from each end, soptrsits at any valid interior position. State is built throughadvance_by/advance_back_by(each verified by its own harness), reaching every state iteration can produce, and results are checked against a pre-conversion snapshot at the advanced offsets.Covered from these states (12 of the 14
IntoItertargets):next,next_back,size_hint,as_slice,as_mut_slice(with a write-through check),advance_by,advance_back_by,fold,try_fold,next_chunk,into_vecdeque, andDropof a both-ends-consumedDropTokenvector.forget_allocation_drop_remainingshares the remaining-range drop path;__iterator_get_uncheckedremains the assume-mirror noted above.Upstream Kani contributions
proof_for_contractcan't resolve generic trait-impl methods at the pinned Kani, which is why__iterator_get_uncheckedis verified through a mirror rather than a contract. We fixed that upstream in model-checking/kani#4865 (merged; not yet in this pin).How to verify
Scoped to these harnesses, from the repo root:
Expected:
Complete - 47 successfully verified harnesses, 0 failures, 47 total., every cover satisfied.Limitations (disclosed)
IntoIter, smaller where noted). A loop-contract route to genuine unboundedness was measured, but CBMC does not terminate on thekani::mem::same_allocationinvariant the havocedIntoIterheap-pointer loops need; the same invariant over a stack array does verify, so the limit is specific to this heap-pointer shape, not the predicate.T: not met. Representative element types only (u8,(), aDroptoken, and the over-aligned / odd-stride / niche shapes above). This is the acceptance question open across the sentence-pair challenges; deferring to the committee on whether representative-type coverage satisfies the clause, and will complete a single-generic-body conversion promptly on a favorable ruling.extract_if::nextand the defaultfrom_iterexceed the CI-standard--object-bits 12budget at larger sizes; theDrop-token harnesses use a fixed size of 4 (drop obligations are per-element identical). Each bound is noted in-code.spec_extendpre-sizes the destination, so its copy path is verified; element-by-element growth routes throughextend_desugared, andSpecFromElem'sT: Clonedefault throughextend_with— both Challenge 23 targets, not claimed here.+977 / −0across 7 files); no runtime logic is modified.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.