Skip to content

Commit 09db86e

Browse files
committed
interpret: properly check for inhabitedness of nested references
1 parent 8f02e85 commit 09db86e

15 files changed

Lines changed: 303 additions & 34 deletions

File tree

compiler/rustc_const_eval/src/interpret/validity.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use super::{
3535
format_interp_error,
3636
};
3737
use crate::enter_trace_span;
38+
use crate::interpret::ensure_monomorphic_enough;
3839

3940
// for the validation errors
4041
#[rustfmt::skip]
@@ -734,11 +735,11 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
734735
)
735736
}
736737
// Do not allow references to uninhabited types.
737-
if place.layout.is_uninhabited() {
738+
if !place.layout.ty.is_opsem_inhabited(*self.ecx.tcx, self.ecx.typing_env) {
738739
let ty = place.layout.ty;
739740
throw_validation_failure!(
740741
self.path,
741-
format!("encountered a {ptr_kind} pointing to uninhabited type {ty}")
742+
format!("encountered a {ptr_kind} pointing to uninhabited type `{ty}`")
742743
)
743744
}
744745

@@ -1568,8 +1569,9 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
15681569
}
15691570

15701571
// Assert that we checked everything there is to check about this type.
1572+
// `is_opsem_inhabited` implies that the layout is inhabited (checked by layout invariants).
15711573
assert!(
1572-
!val.layout.is_uninhabited(),
1574+
val.layout.ty.is_opsem_inhabited(*self.ecx.tcx, self.ecx.typing_env),
15731575
"a value of type `{}` passed validation but that type is uninhabited",
15741576
val.layout.ty
15751577
);
@@ -1627,6 +1629,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
16271629
) -> InterpResult<'tcx> {
16281630
trace!("validate_operand_internal: {:?}, {:?}", *val, val.layout.ty);
16291631

1632+
// We can't check validity if there are any generics left.
1633+
ensure_monomorphic_enough(*self.tcx, val.layout.ty)?;
1634+
16301635
// Run the visitor.
16311636
self.run_for_validation_mut(|ecx| {
16321637
let reset_padding = reset_provenance_and_padding && {

compiler/rustc_middle/src/queries.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2145,6 +2145,11 @@ rustc_queries! {
21452145
desc { "computing the uninhabited predicate of `{}`", key }
21462146
}
21472147

2148+
/// Do not call this query directly: invoke `Ty::is_opsem_inhabited` instead.
2149+
query is_opsem_inhabited_raw(env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
2150+
desc { "computing whether `{}` is inhabited on the opsem level", env.value }
2151+
}
2152+
21482153
query crate_dep_kind(_: CrateNum) -> CrateDepKind {
21492154
eval_always
21502155
desc { "fetching what a dependency looks like" }

compiler/rustc_middle/src/ty/inhabitedness/mod.rs

Lines changed: 179 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
//! This code should only compile in modules where the uninhabitedness of `Foo`
4444
//! is visible.
4545
46+
use std::assert_matches;
47+
48+
use rustc_data_structures::fx::FxHashSet;
4649
use rustc_type_ir::TyKind::*;
4750
use tracing::instrument;
4851

@@ -54,7 +57,12 @@ pub mod inhabited_predicate;
5457
pub use inhabited_predicate::InhabitedPredicate;
5558

5659
pub(crate) fn provide(providers: &mut Providers) {
57-
*providers = Providers { inhabited_predicate_adt, inhabited_predicate_type, ..*providers };
60+
*providers = Providers {
61+
inhabited_predicate_adt,
62+
inhabited_predicate_type,
63+
is_opsem_inhabited_raw,
64+
..*providers
65+
};
5866
}
5967

6068
/// Returns an `InhabitedPredicate` that is generic over type parameters and
@@ -186,14 +194,27 @@ impl<'tcx> Ty<'tcx> {
186194
self.inhabited_predicate(tcx).apply(tcx, typing_env, module)
187195
}
188196

189-
/// Returns true if the type is uninhabited without regard to visibility
197+
/// Returns true if the type is uninhabited without regard to visibility.
198+
///
199+
/// This is still conservative; for instance, a `#[non_exhaustive]` enum *in another crate*
200+
/// is always considered inhabited.
190201
pub fn is_privately_uninhabited(
191202
self,
192203
tcx: TyCtxt<'tcx>,
193204
typing_env: ty::TypingEnv<'tcx>,
194205
) -> bool {
195206
!self.inhabited_predicate(tcx).apply_ignore_module(tcx, typing_env)
196207
}
208+
209+
/// Returns whether `self` is considered inhabited on the opsem level, i.e., its validity
210+
/// invariant might be satisfiable. `self` is expected to be monomorphic and normalized.
211+
pub fn is_opsem_inhabited(self, tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>) -> bool {
212+
// Handle simple cases directly, use the query with its cache for the rest.
213+
is_opsem_inhabited_recursor(self, tcx, &mut (), /* stop_at_ref */ false, &|ty, _, _| {
214+
// ADT handler: stop recursing, invoke the query.
215+
tcx.is_opsem_inhabited_raw(typing_env.as_query_input(ty))
216+
})
217+
}
197218
}
198219

199220
/// N.B. this query should only be called through `Ty::inhabited_predicate`
@@ -216,3 +237,159 @@ fn inhabited_predicate_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> InhabitedP
216237
_ => bug!("unexpected TyKind, use `Ty::inhabited_predicate`"),
217238
}
218239
}
240+
241+
/// Recurse over a type to determine whether it is inhabited on the opsem level.
242+
/// Key constraints are:
243+
/// - if a type's validity invariant is satisfiable, it must be opsem-inhabited.
244+
/// - if a type's layout is marked uninhabited, it must be opsem-uninhabited.
245+
///
246+
/// Beyond that, the value returned by this function is not a stable guarantee.
247+
///
248+
/// When we encounter an ADT, we call `adt_handler`, giving it as its last argument a closure that
249+
/// it can invoke to continue the recursion. This lets us share the logic for "simple" cases
250+
/// (i.e., everything except for ADTs) between `Ty::is_opsem_inhabited` and the query.
251+
///
252+
/// `seen` is used to detect infinite recursion: the set contains all ADTs that we encountered
253+
/// on our path to the current type.
254+
/// If `stop_at_ref` is true, we stop recursing at the next reference we encounter.
255+
fn is_opsem_inhabited_recursor<'tcx, SEEN>(
256+
ty: Ty<'tcx>,
257+
tcx: TyCtxt<'tcx>,
258+
seen: &mut SEEN,
259+
stop_at_ref: bool,
260+
adt_handler: &impl Fn(
261+
Ty<'tcx>,
262+
&mut SEEN,
263+
&dyn Fn(Ty<'tcx>, &mut SEEN, /* stop_at_ref */ bool) -> bool,
264+
) -> bool,
265+
) -> bool {
266+
match *ty.kind() {
267+
// Trivially (un)inhabited types
268+
ty::Int(_)
269+
| ty::Uint(_)
270+
| ty::Float(_)
271+
| ty::Bool
272+
| ty::Char
273+
| ty::Str
274+
| ty::Foreign(..)
275+
| ty::RawPtr(..)
276+
| ty::FnPtr(..)
277+
| ty::FnDef(..) => true,
278+
ty::Dynamic(..) => true, // We can't reason about traits, assume they are inhabited
279+
ty::Slice(..) => true, // Slices can always be empty
280+
ty::Never => false,
281+
282+
// Types where we recurse
283+
ty::Ref(_, pointee, _) => {
284+
if stop_at_ref {
285+
// Bailing out here is safe as the layout code always considers references
286+
// inhabited, so the implication ("layout uninhabited => opsem uninhabited")
287+
// is upheld.
288+
return true;
289+
}
290+
is_opsem_inhabited_recursor(pointee, tcx, seen, stop_at_ref, adt_handler)
291+
}
292+
ty::Tuple(tys) => tys
293+
.iter()
294+
.all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, stop_at_ref, adt_handler)),
295+
ty::Array(elem, len) => {
296+
len.try_to_target_usize(tcx).unwrap() == 0
297+
|| is_opsem_inhabited_recursor(elem, tcx, seen, stop_at_ref, adt_handler)
298+
}
299+
ty::Pat(inner, _pat) => {
300+
is_opsem_inhabited_recursor(inner, tcx, seen, stop_at_ref, adt_handler)
301+
}
302+
ty::Closure(_def, args) => {
303+
let args = args.as_closure();
304+
args.upvar_tys()
305+
.iter()
306+
.all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, stop_at_ref, adt_handler))
307+
}
308+
ty::Coroutine(_def, args) => {
309+
let args = args.as_coroutine();
310+
args.upvar_tys()
311+
.iter()
312+
.all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, stop_at_ref, adt_handler))
313+
}
314+
ty::CoroutineClosure(_def, args) => {
315+
let args = args.as_coroutine_closure();
316+
args.upvar_tys()
317+
.iter()
318+
.all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, stop_at_ref, adt_handler))
319+
}
320+
ty::UnsafeBinder(base) => {
321+
let base = tcx.instantiate_bound_regions_with_erased((*base).into());
322+
is_opsem_inhabited_recursor(base, tcx, seen, stop_at_ref, adt_handler)
323+
}
324+
ty::Adt(..) => {
325+
// ADTs need a special handler to avoid infinite recursion. That handler is meant to
326+
// call back into the recursor. Ideally it'd just call `is_opsem_inhabited_recursor` but
327+
// then it would have to pass itself as the adt_handler argument which is not possible
328+
// in Rust... so we provide the handler with a callback that it can use to continue the
329+
// recursion with the same `adt_handler`.
330+
adt_handler(ty, seen, &|ty, seen, stop_at_ref| {
331+
is_opsem_inhabited_recursor(ty, tcx, seen, stop_at_ref, adt_handler)
332+
})
333+
}
334+
335+
ty::Error(_)
336+
| ty::Infer(..)
337+
| ty::Placeholder(..)
338+
| ty::Bound(..)
339+
| ty::Param(..)
340+
| ty::Alias(..)
341+
| ty::CoroutineWitness(..) => {
342+
bug!("non-normalized type in `is_opsem_uninhabited`: `{ty}`")
343+
}
344+
}
345+
}
346+
347+
fn is_opsem_inhabited_raw<'tcx>(
348+
tcx: TyCtxt<'tcx>,
349+
env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
350+
) -> bool {
351+
let (ty, typing_env) = (env.value, env.typing_env);
352+
assert_matches!(
353+
ty.kind(),
354+
ty::Adt(..),
355+
"the query should only be invoked by `Ty::is_opsem_inhabited`"
356+
);
357+
358+
is_opsem_inhabited_recursor(
359+
ty,
360+
tcx,
361+
&mut FxHashSet::<DefId>::default(),
362+
/* stop_at_ref */ false,
363+
&|ty, seen, rec| {
364+
let ty::Adt(adt_def, adt_args) = *ty.kind() else {
365+
unreachable! {}
366+
};
367+
if adt_def.is_union() {
368+
// Unions are always inhabited.
369+
return true;
370+
}
371+
372+
let new_adt = seen.insert(adt_def.did());
373+
// If we have seen this ADT before, stop at the next reference to avoid infinite
374+
// recursion. We can't stop here since we have to ensure that "layout inhabited"
375+
// implies "opsem inhabited".
376+
let stop_at_ref = !new_adt;
377+
378+
// We are inhabited if in some variant all fields are inhabited.
379+
let inhabited = adt_def.variants().iter().any(|variant| {
380+
variant.fields.iter().all(|field| {
381+
let ty = field.ty(tcx, adt_args);
382+
let ty = tcx.normalize_erasing_regions(typing_env, ty);
383+
rec(ty, seen, stop_at_ref)
384+
})
385+
});
386+
387+
// Remove the type again so that we allow it to appear on other branches.
388+
if new_adt {
389+
seen.remove(&adt_def.did());
390+
}
391+
392+
inhabited
393+
},
394+
)
395+
}

compiler/rustc_ty_utils/src/layout/invariant.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::assert_matches;
22

33
use rustc_abi::{BackendRepr, FieldsShape, Scalar, Size, TagEncoding, Variants};
4+
use rustc_middle::ty::TypeVisitableExt;
45
use rustc_middle::ty::layout::{HasTyCtxt, LayoutCx, TyAndLayout};
56
use rustc_middle::{bug, ty};
67

@@ -34,6 +35,15 @@ pub(super) fn layout_sanity_check<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayou
3435
layout.ty
3536
);
3637
}
38+
// ABI uninhabitedness should imply opsem uninhabitedness. However, we can only check that if
39+
// the type is really monomorphic (while we can compute a layout for some generic types).
40+
if layout.is_uninhabited() && !layout.ty.has_param() {
41+
assert!(
42+
!layout.ty.is_opsem_inhabited(tcx, cx.typing_env),
43+
"{:?} is ABI-uninhabited but not opsem-uninhabited?",
44+
layout.ty
45+
);
46+
}
3747

3848
/// Yields non-ZST fields of the type
3949
fn non_zst_fields<'tcx, 'a>(

src/tools/miri/tests/fail/validity/ref_to_uninhabited1.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::mem::{forget, transmute};
33

44
fn main() {
55
unsafe {
6-
let x: Box<!> = transmute(&mut 42); //~ERROR: encountered a box pointing to uninhabited type !
6+
let x: Box<!> = transmute(&mut 42); //~ERROR: encountered a box pointing to uninhabited type `!`
77
forget(x);
88
}
99
}

src/tools/miri/tests/fail/validity/ref_to_uninhabited1.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: Undefined Behavior: constructing invalid value of type std::boxed::Box<!>: encountered a box pointing to uninhabited type !
1+
error: Undefined Behavior: constructing invalid value of type std::boxed::Box<!>: encountered a box pointing to uninhabited type `!`
22
--> tests/fail/validity/ref_to_uninhabited1.rs:LL:CC
33
|
44
LL | let x: Box<!> = transmute(&mut 42);

src/tools/miri/tests/fail/validity/ref_to_uninhabited2.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ enum Void {}
44

55
fn main() {
66
unsafe {
7-
let _x: &(i32, Void) = transmute(&42); //~ERROR: encountered a reference pointing to uninhabited type (i32, Void)
7+
let _x: &&(i32, Void) = transmute(&&42); //~ERROR: encountered a reference pointing to uninhabited type `&(i32, Void)`
88
}
99
}

src/tools/miri/tests/fail/validity/ref_to_uninhabited2.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: Undefined Behavior: constructing invalid value of type &(i32, Void): encountered a reference pointing to uninhabited type (i32, Void)
1+
error: Undefined Behavior: constructing invalid value of type &&(i32, Void): encountered a reference pointing to uninhabited type `&(i32, Void)`
22
--> tests/fail/validity/ref_to_uninhabited2.rs:LL:CC
33
|
4-
LL | let _x: &(i32, Void) = transmute(&42);
5-
| ^^^^^^^^^^^^^^ Undefined Behavior occurred here
4+
LL | let _x: &&(i32, Void) = transmute(&&42);
5+
| ^^^^^^^^^^^^^^^ Undefined Behavior occurred here
66
|
77
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
88
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

tests/ui/consts/const-eval/raw-bytes.32bit.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ LL | const DATA_FN_PTR: fn() = unsafe { mem::transmute(&13) };
218218
╾ALLOC_ID╼ │ ╾──╼
219219
}
220220

221-
error[E0080]: constructing invalid value of type &Bar: encountered a reference pointing to uninhabited type Bar
221+
error[E0080]: constructing invalid value of type &Bar: encountered a reference pointing to uninhabited type `Bar`
222222
--> $DIR/raw-bytes.rs:110:1
223223
|
224224
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
458458
╾ALLOC_ID╼ ╾ALLOC_ID╼ │ ╾──╼╾──╼
459459
}
460460

461-
error[E0080]: constructing invalid value of type &[!; 1]: encountered a reference pointing to uninhabited type [!; 1]
461+
error[E0080]: constructing invalid value of type &[!; 1]: encountered a reference pointing to uninhabited type `[!; 1]`
462462
--> $DIR/raw-bytes.rs:188:1
463463
|
464464
LL | const _: &[!; 1] = unsafe { &*(1_usize as *const [!; 1]) };

tests/ui/consts/const-eval/raw-bytes.64bit.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ LL | const DATA_FN_PTR: fn() = unsafe { mem::transmute(&13) };
218218
╾ALLOC_ID╼ │ ╾──────╼
219219
}
220220

221-
error[E0080]: constructing invalid value of type &Bar: encountered a reference pointing to uninhabited type Bar
221+
error[E0080]: constructing invalid value of type &Bar: encountered a reference pointing to uninhabited type `Bar`
222222
--> $DIR/raw-bytes.rs:110:1
223223
|
224224
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
458458
╾ALLOC_ID╼ ╾ALLOC_ID╼ │ ╾──────╼╾──────╼
459459
}
460460

461-
error[E0080]: constructing invalid value of type &[!; 1]: encountered a reference pointing to uninhabited type [!; 1]
461+
error[E0080]: constructing invalid value of type &[!; 1]: encountered a reference pointing to uninhabited type `[!; 1]`
462462
--> $DIR/raw-bytes.rs:188:1
463463
|
464464
LL | const _: &[!; 1] = unsafe { &*(1_usize as *const [!; 1]) };

0 commit comments

Comments
 (0)