From 66beb7e76467aa1b9a8f4cd7d5dff0e70a5d7c16 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 26 May 2026 17:37:55 +0200 Subject: [PATCH 1/3] interpret: properly check for inhabitedness of nested references --- .../src/interpret/validity.rs | 11 +- compiler/rustc_middle/src/queries.rs | 5 + .../rustc_middle/src/ty/inhabitedness/mod.rs | 181 +++++++++++++++++- .../rustc_ty_utils/src/layout/invariant.rs | 10 + .../fail/validity/ref_to_uninhabited1.rs | 2 +- .../fail/validity/ref_to_uninhabited1.stderr | 2 +- .../fail/validity/ref_to_uninhabited2.rs | 2 +- .../fail/validity/ref_to_uninhabited2.stderr | 6 +- .../consts/const-eval/raw-bytes.32bit.stderr | 4 +- .../consts/const-eval/raw-bytes.64bit.stderr | 4 +- tests/ui/consts/const-eval/ub-uninhabit.rs | 26 ++- .../ui/consts/const-eval/ub-uninhabit.stderr | 54 +++++- tests/ui/consts/cycle-static-promoted.rs | 12 -- tests/ui/consts/recursive-type.rs | 18 ++ tests/ui/consts/validate_never_arrays.stderr | 2 +- 15 files changed, 304 insertions(+), 35 deletions(-) delete mode 100644 tests/ui/consts/cycle-static-promoted.rs create mode 100644 tests/ui/consts/recursive-type.rs diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index 262ef6ba74ed0..74790f0fffab2 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -35,6 +35,7 @@ use super::{ format_interp_error, }; use crate::enter_trace_span; +use crate::interpret::ensure_monomorphic_enough; // for the validation errors #[rustfmt::skip] @@ -686,11 +687,11 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> { ) } // Do not allow references to uninhabited types. - if place.layout.is_uninhabited() { + if !place.layout.ty.is_opsem_inhabited(*self.ecx.tcx, self.ecx.typing_env) { let ty = place.layout.ty; throw_validation_failure!( self.path, - format!("encountered a {ptr_kind} pointing to uninhabited type {ty}") + format!("encountered a {ptr_kind} pointing to uninhabited type `{ty}`") ) } @@ -1524,8 +1525,9 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt, } // Assert that we checked everything there is to check about this type. + // `is_opsem_inhabited` implies that the layout is inhabited (checked by layout invariants). assert!( - !val.layout.is_uninhabited(), + val.layout.ty.is_opsem_inhabited(*self.ecx.tcx, self.ecx.typing_env), "a value of type `{}` passed validation but that type is uninhabited", val.layout.ty ); @@ -1583,6 +1585,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ) -> InterpResult<'tcx> { trace!("validate_operand_internal: {:?}, {:?}", *val, val.layout.ty); + // We can't check validity if there are any generics left. + ensure_monomorphic_enough(*self.tcx, val.layout.ty)?; + // Run the visitor. self.run_for_validation_mut(|ecx| { let reset_padding = reset_provenance_and_padding && { diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index f0557c3d3381a..0bd30dadd5acc 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -2149,6 +2149,11 @@ rustc_queries! { desc { "computing the uninhabited predicate of `{}`", key } } + /// Do not call this query directly: invoke `Ty::is_opsem_inhabited` instead. + query is_opsem_inhabited_raw(env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool { + desc { "computing whether `{}` is inhabited on the opsem level", env.value } + } + query crate_dep_kind(_: CrateNum) -> CrateDepKind { eval_always desc { "fetching what a dependency looks like" } diff --git a/compiler/rustc_middle/src/ty/inhabitedness/mod.rs b/compiler/rustc_middle/src/ty/inhabitedness/mod.rs index 57e48d3993019..8746434287159 100644 --- a/compiler/rustc_middle/src/ty/inhabitedness/mod.rs +++ b/compiler/rustc_middle/src/ty/inhabitedness/mod.rs @@ -43,6 +43,9 @@ //! This code should only compile in modules where the uninhabitedness of `Foo` //! is visible. +use std::assert_matches; + +use rustc_data_structures::fx::FxHashSet; use rustc_type_ir::TyKind::*; use tracing::instrument; @@ -54,7 +57,12 @@ pub mod inhabited_predicate; pub use inhabited_predicate::InhabitedPredicate; pub(crate) fn provide(providers: &mut Providers) { - *providers = Providers { inhabited_predicate_adt, inhabited_predicate_type, ..*providers }; + *providers = Providers { + inhabited_predicate_adt, + inhabited_predicate_type, + is_opsem_inhabited_raw, + ..*providers + }; } /// Returns an `InhabitedPredicate` that is generic over type parameters and @@ -190,7 +198,10 @@ impl<'tcx> Ty<'tcx> { self.inhabited_predicate(tcx).apply(tcx, typing_env, module) } - /// Returns true if the type is uninhabited without regard to visibility + /// Returns true if the type is uninhabited without regard to visibility. + /// + /// This is still conservative; for instance, a `#[non_exhaustive]` enum *in another crate* + /// is always considered inhabited. pub fn is_privately_uninhabited( self, tcx: TyCtxt<'tcx>, @@ -198,6 +209,16 @@ impl<'tcx> Ty<'tcx> { ) -> bool { !self.inhabited_predicate(tcx).apply_ignore_module(tcx, typing_env) } + + /// Returns whether `self` is considered inhabited on the opsem level, i.e., its validity + /// invariant might be satisfiable. `self` is expected to be monomorphic and normalized. + pub fn is_opsem_inhabited(self, tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>) -> bool { + // Handle simple cases directly, use the query with its cache for the rest. + is_opsem_inhabited_recursor(self, tcx, &mut (), /* stop_at_ref */ false, &|ty, _, _| { + // ADT handler: stop recursing, invoke the query. + tcx.is_opsem_inhabited_raw(typing_env.as_query_input(ty)) + }) + } } /// N.B. this query should only be called through `Ty::inhabited_predicate` @@ -220,3 +241,159 @@ fn inhabited_predicate_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> InhabitedP _ => bug!("unexpected TyKind, use `Ty::inhabited_predicate`"), } } + +/// Recurse over a type to determine whether it is inhabited on the opsem level. +/// Key constraints are: +/// - if a type's validity invariant is satisfiable, it must be opsem-inhabited. +/// - if a type's layout is marked uninhabited, it must be opsem-uninhabited. +/// +/// Beyond that, the value returned by this function is not a stable guarantee. +/// +/// When we encounter an ADT, we call `adt_handler`, giving it as its last argument a closure that +/// it can invoke to continue the recursion. This lets us share the logic for "simple" cases +/// (i.e., everything except for ADTs) between `Ty::is_opsem_inhabited` and the query. +/// +/// `seen` is used to detect infinite recursion: the set contains all ADTs that we encountered +/// on our path to the current type. +/// If `stop_at_ref` is true, we stop recursing at the next reference we encounter. +fn is_opsem_inhabited_recursor<'tcx, SEEN>( + ty: Ty<'tcx>, + tcx: TyCtxt<'tcx>, + seen: &mut SEEN, + stop_at_ref: bool, + adt_handler: &impl Fn( + Ty<'tcx>, + &mut SEEN, + &dyn Fn(Ty<'tcx>, &mut SEEN, /* stop_at_ref */ bool) -> bool, + ) -> bool, +) -> bool { + match *ty.kind() { + // Trivially (un)inhabited types + ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Bool + | ty::Char + | ty::Str + | ty::Foreign(..) + | ty::RawPtr(..) + | ty::FnPtr(..) + | ty::FnDef(..) => true, + ty::Dynamic(..) => true, // We can't reason about traits, assume they are inhabited + ty::Slice(..) => true, // Slices can always be empty + ty::Never => false, + + // Types where we recurse + ty::Ref(_, pointee, _) => { + if stop_at_ref { + // Bailing out here is safe as the layout code always considers references + // inhabited, so the implication ("layout uninhabited => opsem uninhabited") + // is upheld. + return true; + } + is_opsem_inhabited_recursor(pointee, tcx, seen, stop_at_ref, adt_handler) + } + ty::Tuple(tys) => tys + .iter() + .all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, stop_at_ref, adt_handler)), + ty::Array(elem, len) => { + len.try_to_target_usize(tcx).unwrap() == 0 + || is_opsem_inhabited_recursor(elem, tcx, seen, stop_at_ref, adt_handler) + } + ty::Pat(inner, _pat) => { + is_opsem_inhabited_recursor(inner, tcx, seen, stop_at_ref, adt_handler) + } + ty::Closure(_def, args) => { + let args = args.as_closure(); + args.upvar_tys() + .iter() + .all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, stop_at_ref, adt_handler)) + } + ty::Coroutine(_def, args) => { + let args = args.as_coroutine(); + args.upvar_tys() + .iter() + .all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, stop_at_ref, adt_handler)) + } + ty::CoroutineClosure(_def, args) => { + let args = args.as_coroutine_closure(); + args.upvar_tys() + .iter() + .all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, stop_at_ref, adt_handler)) + } + ty::UnsafeBinder(base) => { + let base = tcx.instantiate_bound_regions_with_erased((*base).into()); + is_opsem_inhabited_recursor(base, tcx, seen, stop_at_ref, adt_handler) + } + ty::Adt(..) => { + // ADTs need a special handler to avoid infinite recursion. That handler is meant to + // call back into the recursor. Ideally it'd just call `is_opsem_inhabited_recursor` but + // then it would have to pass itself as the adt_handler argument which is not possible + // in Rust... so we provide the handler with a callback that it can use to continue the + // recursion with the same `adt_handler`. + adt_handler(ty, seen, &|ty, seen, stop_at_ref| { + is_opsem_inhabited_recursor(ty, tcx, seen, stop_at_ref, adt_handler) + }) + } + + ty::Error(_) + | ty::Infer(..) + | ty::Placeholder(..) + | ty::Bound(..) + | ty::Param(..) + | ty::Alias(..) + | ty::CoroutineWitness(..) => { + bug!("non-normalized type in `is_opsem_uninhabited`: `{ty}`") + } + } +} + +fn is_opsem_inhabited_raw<'tcx>( + tcx: TyCtxt<'tcx>, + env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>, +) -> bool { + let (ty, typing_env) = (env.value, env.typing_env); + assert_matches!( + ty.kind(), + ty::Adt(..), + "the query should only be invoked by `Ty::is_opsem_inhabited`" + ); + + is_opsem_inhabited_recursor( + ty, + tcx, + &mut FxHashSet::::default(), + /* stop_at_ref */ false, + &|ty, seen, rec| { + let ty::Adt(adt_def, adt_args) = *ty.kind() else { + unreachable! {} + }; + if adt_def.is_union() { + // Unions are always inhabited. + return true; + } + + let new_adt = seen.insert(adt_def.did()); + // If we have seen this ADT before, stop at the next reference to avoid infinite + // recursion. We can't stop here since we have to ensure that "layout inhabited" + // implies "opsem inhabited". + let stop_at_ref = !new_adt; + + // We are inhabited if in some variant all fields are inhabited. + let inhabited = adt_def.variants().iter().any(|variant| { + variant.fields.iter().all(|field| { + let ty = field.ty(tcx, adt_args); + let ty = tcx.normalize_erasing_regions(typing_env, ty); + rec(ty, seen, stop_at_ref) + }) + }); + + // Remove the type again so that we allow it to appear on other branches. + if new_adt { + seen.remove(&adt_def.did()); + } + + inhabited + }, + ) +} diff --git a/compiler/rustc_ty_utils/src/layout/invariant.rs b/compiler/rustc_ty_utils/src/layout/invariant.rs index decf1ffb5570d..a6d40841b6f20 100644 --- a/compiler/rustc_ty_utils/src/layout/invariant.rs +++ b/compiler/rustc_ty_utils/src/layout/invariant.rs @@ -1,6 +1,7 @@ use std::assert_matches; use rustc_abi::{BackendRepr, FieldsShape, Scalar, Size, TagEncoding, Variants}; +use rustc_middle::ty::TypeVisitableExt; use rustc_middle::ty::layout::{HasTyCtxt, LayoutCx, TyAndLayout}; use rustc_middle::{bug, ty}; @@ -34,6 +35,15 @@ pub(super) fn layout_sanity_check<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayou layout.ty ); } + // ABI uninhabitedness should imply opsem uninhabitedness. However, we can only check that if + // the type is really monomorphic (while we can compute a layout for some generic types). + if layout.is_uninhabited() && !layout.ty.has_param() { + assert!( + !layout.ty.is_opsem_inhabited(tcx, cx.typing_env), + "{:?} is ABI-uninhabited but not opsem-uninhabited?", + layout.ty + ); + } /// Yields non-ZST fields of the type fn non_zst_fields<'tcx, 'a>( diff --git a/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.rs b/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.rs index 2e6be8b971c64..25e21375cd746 100644 --- a/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.rs +++ b/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.rs @@ -3,7 +3,7 @@ use std::mem::{forget, transmute}; fn main() { unsafe { - let x: Box = transmute(&mut 42); //~ERROR: encountered a box pointing to uninhabited type ! + let x: Box = transmute(&mut 42); //~ERROR: encountered a box pointing to uninhabited type `!` forget(x); } } diff --git a/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.stderr b/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.stderr index 645a9789207a0..889596092de60 100644 --- a/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.stderr +++ b/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.stderr @@ -1,4 +1,4 @@ -error: Undefined Behavior: constructing invalid value of type std::boxed::Box: encountered a box pointing to uninhabited type ! +error: Undefined Behavior: constructing invalid value of type std::boxed::Box: encountered a box pointing to uninhabited type `!` --> tests/fail/validity/ref_to_uninhabited1.rs:LL:CC | LL | let x: Box = transmute(&mut 42); diff --git a/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.rs b/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.rs index 8934a06b5d73a..3ac987309f783 100644 --- a/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.rs +++ b/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.rs @@ -4,6 +4,6 @@ enum Void {} fn main() { unsafe { - let _x: &(i32, Void) = transmute(&42); //~ERROR: encountered a reference pointing to uninhabited type (i32, Void) + let _x: &&(i32, Void) = transmute(&&42); //~ERROR: encountered a reference pointing to uninhabited type `&(i32, Void)` } } diff --git a/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.stderr b/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.stderr index 37b265a771bc1..cba028eeca3fe 100644 --- a/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.stderr +++ b/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: constructing invalid value of type &(i32, Void): encountered a reference pointing to uninhabited type (i32, Void) +error: Undefined Behavior: constructing invalid value of type &&(i32, Void): encountered a reference pointing to uninhabited type `&(i32, Void)` --> tests/fail/validity/ref_to_uninhabited2.rs:LL:CC | -LL | let _x: &(i32, Void) = transmute(&42); - | ^^^^^^^^^^^^^^ Undefined Behavior occurred here +LL | let _x: &&(i32, Void) = transmute(&&42); + | ^^^^^^^^^^^^^^^ Undefined Behavior occurred here | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/tests/ui/consts/const-eval/raw-bytes.32bit.stderr b/tests/ui/consts/const-eval/raw-bytes.32bit.stderr index 37c715e003e8f..ef45f840f70a5 100644 --- a/tests/ui/consts/const-eval/raw-bytes.32bit.stderr +++ b/tests/ui/consts/const-eval/raw-bytes.32bit.stderr @@ -218,7 +218,7 @@ LL | const DATA_FN_PTR: fn() = unsafe { mem::transmute(&13) }; ╾ALLOC$ID╼ │ ╾──╼ } -error[E0080]: constructing invalid value of type &Bar: encountered a reference pointing to uninhabited type Bar +error[E0080]: constructing invalid value of type &Bar: encountered a reference pointing to uninhabited type `Bar` --> $DIR/raw-bytes.rs:109:1 | LL | const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) }; @@ -458,7 +458,7 @@ LL | const RAW_TRAIT_OBJ_VTABLE_INVALID: *const dyn Trait = unsafe { mem::transm ╾ALLOC$ID╼ ╾ALLOC$ID╼ │ ╾──╼╾──╼ } -error[E0080]: constructing invalid value of type &[!; 1]: encountered a reference pointing to uninhabited type [!; 1] +error[E0080]: constructing invalid value of type &[!; 1]: encountered a reference pointing to uninhabited type `[!; 1]` --> $DIR/raw-bytes.rs:187:1 | LL | const _: &[!; 1] = unsafe { &*(1_usize as *const [!; 1]) }; diff --git a/tests/ui/consts/const-eval/raw-bytes.64bit.stderr b/tests/ui/consts/const-eval/raw-bytes.64bit.stderr index b74aee7980aa4..0e6ac4448efe6 100644 --- a/tests/ui/consts/const-eval/raw-bytes.64bit.stderr +++ b/tests/ui/consts/const-eval/raw-bytes.64bit.stderr @@ -218,7 +218,7 @@ LL | const DATA_FN_PTR: fn() = unsafe { mem::transmute(&13) }; ╾ALLOC$ID╼ │ ╾──────╼ } -error[E0080]: constructing invalid value of type &Bar: encountered a reference pointing to uninhabited type Bar +error[E0080]: constructing invalid value of type &Bar: encountered a reference pointing to uninhabited type `Bar` --> $DIR/raw-bytes.rs:109:1 | LL | const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) }; @@ -458,7 +458,7 @@ LL | const RAW_TRAIT_OBJ_VTABLE_INVALID: *const dyn Trait = unsafe { mem::transm ╾ALLOC$ID╼ ╾ALLOC$ID╼ │ ╾──────╼╾──────╼ } -error[E0080]: constructing invalid value of type &[!; 1]: encountered a reference pointing to uninhabited type [!; 1] +error[E0080]: constructing invalid value of type &[!; 1]: encountered a reference pointing to uninhabited type `[!; 1]` --> $DIR/raw-bytes.rs:187:1 | LL | const _: &[!; 1] = unsafe { &*(1_usize as *const [!; 1]) }; diff --git a/tests/ui/consts/const-eval/ub-uninhabit.rs b/tests/ui/consts/const-eval/ub-uninhabit.rs index 188ad768a0e98..0bd2e196d14e7 100644 --- a/tests/ui/consts/const-eval/ub-uninhabit.rs +++ b/tests/ui/consts/const-eval/ub-uninhabit.rs @@ -1,6 +1,6 @@ // Strip out raw byte dumps to make comparison platform-independent: //@ normalize-stderr: "(the raw bytes of the constant) \(size: [0-9]*, align: [0-9]*\)" -> "$1 (size: $$SIZE, align: $$ALIGN)" -//@ normalize-stderr: "([0-9a-f][0-9a-f] |╾─*ALLOC[0-9]+(\+[a-z0-9]+)?()?─*╼ )+ *│.*" -> "HEX_DUMP" +//@ normalize-stderr: "([0-9a-f][0-9a-f] |╾─*ALLOC\$ID(\+[a-z0-9]+)?()?─*╼ )+ *│.*" -> "HEX_DUMP" //@ dont-require-annotations: NOTE #![feature(core_intrinsics)] @@ -18,19 +18,35 @@ union MaybeUninit { } const BAD_BAD_BAD: Bar = unsafe { MaybeUninit { uninit: () }.init }; -//~^ ERROR constructing invalid value +//~^ ERROR value of zero-variant enum `Bar` const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) }; -//~^ ERROR constructing invalid value +//~^ ERROR reference pointing to uninhabited type `Bar` const BAD_BAD_ARRAY: [Bar; 1] = unsafe { MaybeUninit { uninit: () }.init }; -//~^ ERROR constructing invalid value +//~^ ERROR value of zero-variant enum `Bar` const READ_NEVER: () = unsafe { let mem = [0u32; 8]; let ptr = mem.as_ptr().cast::(); let _val = intrinsics::read_via_copy(ptr); - //~^ ERROR constructing invalid value + //~^ ERROR value of the never type }; +const BAD_NESTED_REF: &&! = unsafe { mem::transmute(&&0) }; +//~^ ERROR reference pointing to uninhabited type `&!` + +const BAD_NESTED_UNSIZED_REF: &&(!, [i32]) = unsafe { mem::transmute(&(&[0] as &[i32])) }; +//~^ ERROR reference pointing to uninhabited type `&(!, [i32])` + +const BAD_UNINHABITED_SLICE: &[!] = unsafe { mem::transmute(&[()] as &[()]) }; +//~^ ERROR value of the never type + +// This is an interesting type since it looks somewhat recursive even though it is not. +struct Wrap(T); +const WRAPPED_TWICE: Wrap> = unsafe { mem::transmute(()) }; +//~^ ERROR value of the never type +const WRAPPED_TWICE_REF: &Wrap> = unsafe { mem::transmute(&()) }; +//~^ ERROR reference pointing to uninhabited type `Wrap>` + fn main() {} diff --git a/tests/ui/consts/const-eval/ub-uninhabit.stderr b/tests/ui/consts/const-eval/ub-uninhabit.stderr index 64da16121b828..59c7e95adc7f7 100644 --- a/tests/ui/consts/const-eval/ub-uninhabit.stderr +++ b/tests/ui/consts/const-eval/ub-uninhabit.stderr @@ -4,7 +4,7 @@ error[E0080]: constructing invalid value of type Bar: encountered a value of zer LL | const BAD_BAD_BAD: Bar = unsafe { MaybeUninit { uninit: () }.init }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `BAD_BAD_BAD` failed here -error[E0080]: constructing invalid value of type &Bar: encountered a reference pointing to uninhabited type Bar +error[E0080]: constructing invalid value of type &Bar: encountered a reference pointing to uninhabited type `Bar` --> $DIR/ub-uninhabit.rs:23:1 | LL | const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) }; @@ -27,6 +27,56 @@ error[E0080]: constructing invalid value of type !: encountered a value of the n LL | let _val = intrinsics::read_via_copy(ptr); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `READ_NEVER` failed here -error: aborting due to 4 previous errors +error[E0080]: constructing invalid value of type &&!: encountered a reference pointing to uninhabited type `&!` + --> $DIR/ub-uninhabit.rs:36:1 + | +LL | const BAD_NESTED_REF: &&! = unsafe { mem::transmute(&&0) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value + | + = note: the rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: $SIZE, align: $ALIGN) { + HEX_DUMP + } + +error[E0080]: constructing invalid value of type &&(!, [i32]): encountered a reference pointing to uninhabited type `&(!, [i32])` + --> $DIR/ub-uninhabit.rs:39:1 + | +LL | const BAD_NESTED_UNSIZED_REF: &&(!, [i32]) = unsafe { mem::transmute(&(&[0] as &[i32])) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value + | + = note: the rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: $SIZE, align: $ALIGN) { + HEX_DUMP + } + +error[E0080]: constructing invalid value of type &[!]: at .[0], encountered a value of the never type `!` + --> $DIR/ub-uninhabit.rs:42:1 + | +LL | const BAD_UNINHABITED_SLICE: &[!] = unsafe { mem::transmute(&[()] as &[()]) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value + | + = note: the rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: $SIZE, align: $ALIGN) { + HEX_DUMP + } + +error[E0080]: constructing invalid value of type Wrap>: at .0.0, encountered a value of the never type `!` + --> $DIR/ub-uninhabit.rs:47:47 + | +LL | const WRAPPED_TWICE: Wrap> = unsafe { mem::transmute(()) }; + | ^^^^^^^^^^^^^^^^^^ evaluation of `WRAPPED_TWICE` failed here + +error[E0080]: constructing invalid value of type &Wrap>: encountered a reference pointing to uninhabited type `Wrap>` + --> $DIR/ub-uninhabit.rs:49:1 + | +LL | const WRAPPED_TWICE_REF: &Wrap> = unsafe { mem::transmute(&()) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value + | + = note: the rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: $SIZE, align: $ALIGN) { + HEX_DUMP + } + +error: aborting due to 9 previous errors For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/consts/cycle-static-promoted.rs b/tests/ui/consts/cycle-static-promoted.rs deleted file mode 100644 index d648d04861189..0000000000000 --- a/tests/ui/consts/cycle-static-promoted.rs +++ /dev/null @@ -1,12 +0,0 @@ -//@ check-pass - -struct Value { - values: &'static [&'static Value], -} - -// This `static` recursively points to itself through a promoted (the slice). -static VALUE: Value = Value { - values: &[&VALUE], -}; - -fn main() {} diff --git a/tests/ui/consts/recursive-type.rs b/tests/ui/consts/recursive-type.rs new file mode 100644 index 0000000000000..b0207733a2f3d --- /dev/null +++ b/tests/ui/consts/recursive-type.rs @@ -0,0 +1,18 @@ +//@ check-pass +use std::marker::PhantomData; + +struct Value { + values: &'static [&'static Value], +} + +// This `static` recursively points to itself through a promoted (the slice). +static VALUE: Value = Value { + values: &[&VALUE], +}; + +// If we just unfold this type going down the first variant of every enum, we'll never stop; we'll +// never even encounter the same type a second time. +struct S(&'static S<(T, T)>, PhantomData); +const C: &Result, ()> = &Err(()); + +fn main() {} diff --git a/tests/ui/consts/validate_never_arrays.stderr b/tests/ui/consts/validate_never_arrays.stderr index 517b632bdd7be..a006c19587f50 100644 --- a/tests/ui/consts/validate_never_arrays.stderr +++ b/tests/ui/consts/validate_never_arrays.stderr @@ -1,4 +1,4 @@ -error[E0080]: constructing invalid value of type &[!; 1]: encountered a reference pointing to uninhabited type [!; 1] +error[E0080]: constructing invalid value of type &[!; 1]: encountered a reference pointing to uninhabited type `[!; 1]` --> $DIR/validate_never_arrays.rs:6:1 | LL | const _: &[!; 1] = unsafe { &*(1_usize as *const [!; 1]) }; From 1888dec84c2fbd667943a35bf13d1883373c3930 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 25 Jun 2026 01:18:21 +0200 Subject: [PATCH 2/3] address some review feedback --- .../rustc_middle/src/ty/inhabitedness/mod.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_middle/src/ty/inhabitedness/mod.rs b/compiler/rustc_middle/src/ty/inhabitedness/mod.rs index 8746434287159..1efb4bffda40a 100644 --- a/compiler/rustc_middle/src/ty/inhabitedness/mod.rs +++ b/compiler/rustc_middle/src/ty/inhabitedness/mod.rs @@ -212,6 +212,12 @@ impl<'tcx> Ty<'tcx> { /// Returns whether `self` is considered inhabited on the opsem level, i.e., its validity /// invariant might be satisfiable. `self` is expected to be monomorphic and normalized. + /// + /// Key constraints are: + /// - if a type's validity invariant is satisfiable, it must be opsem-inhabited. + /// - if a type's layout is marked uninhabited, it must be opsem-uninhabited. + /// + /// Beyond that, the value returned by this function is not a stable guarantee. pub fn is_opsem_inhabited(self, tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>) -> bool { // Handle simple cases directly, use the query with its cache for the rest. is_opsem_inhabited_recursor(self, tcx, &mut (), /* stop_at_ref */ false, &|ty, _, _| { @@ -243,11 +249,7 @@ fn inhabited_predicate_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> InhabitedP } /// Recurse over a type to determine whether it is inhabited on the opsem level. -/// Key constraints are: -/// - if a type's validity invariant is satisfiable, it must be opsem-inhabited. -/// - if a type's layout is marked uninhabited, it must be opsem-uninhabited. -/// -/// Beyond that, the value returned by this function is not a stable guarantee. +/// See `is_opsem_inhabited` above for the spec of what we compute. /// /// When we encounter an ADT, we call `adt_handler`, giving it as its last argument a closure that /// it can invoke to continue the recursion. This lets us share the logic for "simple" cases @@ -375,8 +377,9 @@ fn is_opsem_inhabited_raw<'tcx>( let new_adt = seen.insert(adt_def.did()); // If we have seen this ADT before, stop at the next reference to avoid infinite - // recursion. We can't stop here since we have to ensure that "layout inhabited" - // implies "opsem inhabited". + // recursion. We can't stop here since we have to ensure that "layout uninhabited" + // implies "opsem uninhabited". References are always layout-inhabited so the + // implication is vacuously true. let stop_at_ref = !new_adt; // We are inhabited if in some variant all fields are inhabited. From a08ecfee56630edb018c95aa527dc27bf3c6db74 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 2 Jul 2026 17:40:26 +0200 Subject: [PATCH 3/3] remove a crash test that no longer crashes (but we didn't actually fix the underlying issue) --- tests/crashes/150296.rs | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 tests/crashes/150296.rs diff --git a/tests/crashes/150296.rs b/tests/crashes/150296.rs deleted file mode 100644 index f7d5a54ca9b65..0000000000000 --- a/tests/crashes/150296.rs +++ /dev/null @@ -1,14 +0,0 @@ -//@ known-bug: #150296 -#[derive(PartialEq)] -pub struct Thing; - -impl Thing { - const A: Self = Thing; -} - -fn broken(x: Thing) { - match x { - >::A => {} - _ => {} - } -}