Emit a diagnostic for a misapplied checked size/align intrinsic marker - #4648
Emit a diagnostic for a misapplied checked size/align intrinsic marker#4648MavenRain wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR prevents a compiler panic when the internal CheckedSizeOfIntrinsic / CheckedAlignOfIntrinsic fn_marker is attached to a function with an incompatible signature, by validating the marked function signature and emitting a proper diagnostic instead of panicking during intrinsic body generation.
Changes:
- Add a signature guard for
CheckedSizeOf/CheckedAlignOfmarker-handling to emit a diagnostic on mismatches. - Introduce
has_checked_intrinsic_sighelper to validate the expected argument/return shape before running the generators. - Add a new UI regression test reproducing issue #4589 and asserting the new diagnostic.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
kani-compiler/src/kani_middle/transform/kani_intrinsics.rs |
Adds signature validation + diagnostic to avoid panics in checked size/align intrinsic body generation. |
tests/ui/invalid-intrinsic-marker/bad_signature.rs |
Adds a reproducer that misapplies the internal marker to a wrong-signature function. |
tests/ui/invalid-intrinsic-marker/expected |
Asserts the expected diagnostic text for the new UI test. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fn has_checked_intrinsic_sig(body: &Body) -> bool { | ||
| let arg_is_ptr = matches!( | ||
| body.arg_locals().first().map(|arg| arg.ty.kind()), | ||
| Some(TyKind::RigidTy(RigidTy::RawPtr(..))) | ||
| ); | ||
| let ret_is_option = matches!( | ||
| body.ret_local().ty.kind(), | ||
| TyKind::RigidTy(RigidTy::Adt(def, _)) | ||
| if def.variants_iter().any(|var| !var.fields().is_empty()) | ||
| && def.variants_iter().any(|var| var.fields().is_empty()) | ||
| ); | ||
| arg_is_ptr && ret_is_option | ||
| } |
There was a problem hiding this comment.
Guard seems too permissive. has_checked_intrinsic_sig currently only checks:
- first argument is a raw pointer
- return type is some enum with one empty and one non-empty variant
That still allows cases like:
- extra parameters
Option<u8>-style returns- other enum shapes that just happen to look Option-like
The risk is the validation passes, but later code still assumes the exact intrinsic shape and could fail or misbehave.
The `CheckedSizeOf` and `CheckedAlignOf` intrinsic generators assume the marked
function has the signature of the corresponding Kani intrinsic: a raw pointer
argument and an `Option<usize>` return. The `fn_marker` attribute is internal,
but nothing stops user code from attaching it to a function with a different
signature. When that happened, `build_some` unwrapped a missing `Some` variant
and the compiler panicked:
thread 'rustc' panicked at kani_intrinsics.rs:
called `Option::unwrap()` on a `None` value
Validate the signature before generating the body and emit a normal compiler
error when it does not match, following how other misapplied kani-internal
attributes are already reported.
Resolves model-checking#4589
Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
afc0a1e to
60ef55f
Compare
feliperodri
left a comment
There was a problem hiding this comment.
There is a potential future maintenance hazard: the helper duplicates the intrinsic generator’s assumptions, so if the generator changes later the validation could drift unless it is kept in sync; a tighter type-shape check would reduce that risk. Also, while the change applies to both CheckedSizeOf and CheckedAlignOf, the test only covers CheckedSizeOfIntrinsic, so there is still no explicit proof that the sibling CheckedAlignOf path is equally safe, and a subtle edge case could remain if its body generation differs.
| fn has_checked_intrinsic_sig(body: &Body) -> bool { | ||
| let arg_is_ptr = matches!( | ||
| body.arg_locals().first().map(|arg| arg.ty.kind()), | ||
| Some(TyKind::RigidTy(RigidTy::RawPtr(..))) | ||
| ); | ||
| let ret_is_option = matches!( | ||
| body.ret_local().ty.kind(), | ||
| TyKind::RigidTy(RigidTy::Adt(def, _)) | ||
| if def.variants_iter().any(|var| !var.fields().is_empty()) | ||
| && def.variants_iter().any(|var| var.fields().is_empty()) | ||
| ); | ||
| arg_is_ptr && ret_is_option | ||
| } |
| fn has_checked_intrinsic_sig(body: &Body) -> bool { | ||
| let arg_is_ptr = matches!( | ||
| body.arg_locals().first().map(|arg| arg.ty.kind()), | ||
| Some(TyKind::RigidTy(RigidTy::RawPtr(..))) | ||
| ); | ||
| let ret_is_option = matches!( | ||
| body.ret_local().ty.kind(), | ||
| TyKind::RigidTy(RigidTy::Adt(def, _)) | ||
| if def.variants_iter().any(|var| !var.fields().is_empty()) | ||
| && def.variants_iter().any(|var| var.fields().is_empty()) | ||
| ); | ||
| arg_is_ptr && ret_is_option | ||
| } |
There was a problem hiding this comment.
Guard seems too permissive. has_checked_intrinsic_sig currently only checks:
- first argument is a raw pointer
- return type is some enum with one empty and one non-empty variant
That still allows cases like:
- extra parameters
Option<u8>-style returns- other enum shapes that just happen to look Option-like
The risk is the validation passes, but later code still assumes the exact intrinsic shape and could fail or misbehave.
| tcx.dcx().span_err( | ||
| rustc_internal::internal(tcx, body.span), | ||
| format!( | ||
| "the `{name}` intrinsic marker can only be applied to a function \ | ||
| with a raw pointer argument that returns `Option<usize>`" | ||
| ), | ||
| ); |
There was a problem hiding this comment.
The error says the marker can only be applied to a function returning Option<usize>, but the helper does not verify usize specifically. It looks like the users will get a precise error message, but the checker enforces a looser condition.
| #[kanitool::fn_marker = "CheckedSizeOfIntrinsic"] | ||
| fn fake_checked_size_of(ptr: *const u8) -> BadRet { | ||
| let _ = ptr; | ||
| BadRet::None | ||
| } | ||
|
|
There was a problem hiding this comment.
The new UI test covers one bad return type, but it does not cover:
- extra arguments
- wrong Option inner type
- the matching valid intrinsic signature
Replace the boolean signature guard with a checked_intrinsic_sig extractor requiring exactly one raw-pointer argument and a return type shaped exactly like Option<usize>. transform extracts once and passes the validated pieces into the generators and build_some/build_none, so validation cannot drift from what the generators assume. Add ui tests for extra arguments, a wrong Option<T> inner type, the CheckedAlignOf sibling path, and a positive control with the exact intrinsic signature. Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
|
@feliperodri , thanks for the review! All points addressed:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.
Suppressed comments (1)
kani-compiler/src/kani_middle/transform/kani_intrinsics.rs:634
- The new signature guard rejects non-raw-pointer arguments, but the added UI coverage only exercises wrong return type and wrong arity. Consider adding a UI test where the marker is attached to a function with exactly one argument of a non-pointer type (e.g.,
usize) to ensure this diagnostic path stays covered.
fn checked_intrinsic_sig(body: &Body) -> Option<CheckedIntrinsicSig> {
let ptr_arg = single(body.arg_locals())?;
let pointee_ty = if let TyKind::RigidTy(RigidTy::RawPtr(pointee, _)) = ptr_arg.ty.kind() {
Some(pointee)
} else {
None
}?;
|
@MavenRain could you add the extra UI test pointed out by Copilot? Can we resolve all the comments you have addressed? Almost ready to merge... |
Description
Fixes a compiler panic when the internal
CheckedSizeOfIntrinsic(orCheckedAlignOfIntrinsic)fn_markeris attached to a function whosesignature does not match the intrinsic.
Context
From the reproducer in #4589:
Kani panicked during codegen:
The
checked_size_of/checked_align_ofgenerators assume the markedfunction has the signature of the real intrinsic (a raw pointer argument and an
Option<usize>return), andbuild_some/build_nonelocate theSome/Nonevariants by looking for a variant with (or without) a field. Thefn_markerattribute is internal, but nothing stops user code from attachingit to a function returning some other type;
BadRethas no variant with afield, so
build_someunwrapped aNoneand the compiler panicked.Change
Validate the marked function's signature before generating the body. When it
is not a raw pointer argument with an
Option-shaped return, emit a normalcompiler error instead of panicking. This matches how Kani already reports
other misapplied kani-internal attributes. The valid intrinsic path is
unchanged.
Testing
Added
tests/ui/invalid-intrinsic-marker/, which runs the reproducer andchecks for the new diagnostic:
Verified it passes via
compiletest --suite ui --mode expected.Confirmed no regression to the real intrinsic path: the existing
tests/kani/SizeAndAlignOfDst/unsized_tail.rsstill verifies successfully(9/9 harnesses).
cargo check -p kani-compilerpasses and the file isrustfmt-clean.Resolves #4589
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.