Skip to content

Commit e6d1440

Browse files
committed
interpret: properly check for inhabitedness of nested references
1 parent 1d59f66 commit e6d1440

12 files changed

Lines changed: 186 additions & 19 deletions

File tree

compiler/rustc_const_eval/src/interpret/validity.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -734,11 +734,11 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
734734
)
735735
}
736736
// Do not allow references to uninhabited types.
737-
if place.layout.is_uninhabited() {
737+
if !place.layout.ty.is_opsem_inhabited(*self.ecx.tcx, self.ecx.typing_env) {
738738
let ty = place.layout.ty;
739739
throw_validation_failure!(
740740
self.path,
741-
format!("encountered a {ptr_kind} pointing to uninhabited type {ty}")
741+
format!("encountered a {ptr_kind} pointing to uninhabited type `{ty}`")
742742
)
743743
}
744744

@@ -1573,6 +1573,11 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
15731573
);
15741574
}
15751575
if cfg!(debug_assertions) {
1576+
assert!(
1577+
val.layout.ty.is_opsem_inhabited(*self.ecx.tcx, self.ecx.typing_env),
1578+
"a value of type `{}` somehow passed validity but is not actually inhabited",
1579+
val.layout.ty,
1580+
);
15761581
// Check that we don't miss any new changes to layout computation in our checks above.
15771582
match val.layout.backend_repr {
15781583
BackendRepr::Scalar(scalar_layout) => {

compiler/rustc_middle/src/queries.rs

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

2174+
/// Do not call this query directly: invoke `Ty::is_opsem_inhabited` instead.
2175+
query is_opsem_inhabited_raw(env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
2176+
desc { "computing whether `{}` is inhabited on the opsem level", env.value }
2177+
}
2178+
21742179
query crate_dep_kind(_: CrateNum) -> CrateDepKind {
21752180
eval_always
21762181
desc { "fetching what a dependency looks like" }

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

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@ pub mod inhabited_predicate;
5454
pub use inhabited_predicate::InhabitedPredicate;
5555

5656
pub(crate) fn provide(providers: &mut Providers) {
57-
*providers = Providers { inhabited_predicate_adt, inhabited_predicate_type, ..*providers };
57+
*providers = Providers {
58+
inhabited_predicate_adt,
59+
inhabited_predicate_type,
60+
is_opsem_inhabited_raw,
61+
..*providers
62+
};
5863
}
5964

6065
/// Returns an `InhabitedPredicate` that is generic over type parameters and
@@ -186,14 +191,26 @@ impl<'tcx> Ty<'tcx> {
186191
self.inhabited_predicate(tcx).apply(tcx, typing_env, module)
187192
}
188193

189-
/// Returns true if the type is uninhabited without regard to visibility
194+
/// Returns true if the type is uninhabited without regard to visibility.
195+
///
196+
/// This is still conservative; for instance, a `#[non_exhaustive]` enum *in another crate*
197+
/// is always considered inhabited.
190198
pub fn is_privately_uninhabited(
191199
self,
192200
tcx: TyCtxt<'tcx>,
193201
typing_env: ty::TypingEnv<'tcx>,
194202
) -> bool {
195203
!self.inhabited_predicate(tcx).apply_ignore_module(tcx, typing_env)
196204
}
205+
206+
/// Returns whether `self` is considered inhabited on the opsem level, i.e., its validity
207+
/// invariant might be satisfiable. `self` is expected to be monomorphic and normalized.
208+
pub fn is_opsem_inhabited(self, tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>) -> bool {
209+
// Handle simple cases directly, use the query with its cache for the rest.
210+
is_opsem_inhabited_recursor(self, tcx, None, &|ty, _, _| {
211+
tcx.is_opsem_inhabited_raw(typing_env.as_query_input(ty))
212+
})
213+
}
197214
}
198215

199216
/// N.B. this query should only be called through `Ty::inhabited_predicate`
@@ -216,3 +233,107 @@ fn inhabited_predicate_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> InhabitedP
216233
_ => bug!("unexpected TyKind, use `Ty::inhabited_predicate`"),
217234
}
218235
}
236+
237+
/// `root` is used to bound the recursion: the first time we encounter an ADT, we
238+
/// store its `DefId` as the "root"; if we ever hit the same ADT again we stop.
239+
/// All recursive types must go through an ADT so this catches all possible recursion.
240+
///
241+
/// When we git an ADT, we call `adt_handler`, giving it as its last argument a closure that it
242+
/// can invoke to continue the recursion.
243+
fn is_opsem_inhabited_recursor<'tcx>(
244+
ty: Ty<'tcx>,
245+
tcx: TyCtxt<'tcx>,
246+
root: Option<DefId>,
247+
adt_handler: &impl Fn(Ty<'tcx>, Option<DefId>, &dyn Fn(Ty<'tcx>, Option<DefId>) -> bool) -> bool,
248+
) -> bool {
249+
match *ty.kind() {
250+
// Trivially (un)inhabited types
251+
ty::Int(_)
252+
| ty::Uint(_)
253+
| ty::Float(_)
254+
| ty::Bool
255+
| ty::Char
256+
| ty::Str
257+
| ty::Foreign(..)
258+
| ty::RawPtr(..)
259+
| ty::FnPtr(..)
260+
| ty::FnDef(..) => true,
261+
ty::Dynamic(..) => true, // We can't reason about traits, assume they are inhabited
262+
ty::Slice(..) => true, // Slices can always be empty
263+
ty::Never => false,
264+
265+
// Types where we recurse
266+
ty::Ref(_, pointee, _) => is_opsem_inhabited_recursor(pointee, tcx, root, adt_handler),
267+
ty::Tuple(tys) => {
268+
tys.iter().all(|ty| is_opsem_inhabited_recursor(ty, tcx, root, adt_handler))
269+
}
270+
ty::Array(elem, len) => {
271+
len.try_to_target_usize(tcx).unwrap() == 0
272+
|| is_opsem_inhabited_recursor(elem, tcx, root, adt_handler)
273+
}
274+
ty::Pat(inner, _pat) => is_opsem_inhabited_recursor(inner, tcx, root, adt_handler),
275+
ty::Closure(_closure_def, closure_args) => {
276+
let closure_args = closure_args.as_closure();
277+
closure_args
278+
.upvar_tys()
279+
.iter()
280+
.all(|ty| is_opsem_inhabited_recursor(ty, tcx, root, adt_handler))
281+
}
282+
ty::Coroutine(..) => {
283+
true // FIXME should these really be trivially inhabited?
284+
}
285+
ty::CoroutineClosure(..) => {
286+
true // FIXME should these really be trivially inhabited?
287+
}
288+
ty::UnsafeBinder(..) => {
289+
true // FIXME should these really be trivially inhabited?
290+
}
291+
ty::Adt(..) => {
292+
// ADTs need a special handler to avoid infinite recursion.
293+
adt_handler(ty, root, &|ty, root| {
294+
is_opsem_inhabited_recursor(ty, tcx, root, adt_handler)
295+
})
296+
}
297+
298+
ty::Error(_)
299+
| ty::Infer(..)
300+
| ty::Placeholder(..)
301+
| ty::Bound(..)
302+
| ty::Param(..)
303+
| ty::Alias(..)
304+
| ty::CoroutineWitness(..) => {
305+
bug!("non-normalized type in `is_opsem_uninhabited_raw::rec`: `{ty}`")
306+
}
307+
}
308+
}
309+
310+
fn is_opsem_inhabited_raw<'tcx>(
311+
tcx: TyCtxt<'tcx>,
312+
env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
313+
) -> bool {
314+
let (ty, typing_env) = (env.value, env.typing_env);
315+
is_opsem_inhabited_recursor(ty, tcx, None, &|ty, root, rec| {
316+
let ty::Adt(adt_def, adt_args) = *ty.kind() else {
317+
unreachable! {}
318+
};
319+
if adt_def.is_union() {
320+
// Unions are always inhabited.
321+
return true;
322+
}
323+
if Some(adt_def.did()) == root {
324+
// We recursed to (possibly another instance of) the same type.
325+
// Coinductively assume that the type is inhabited.
326+
return true;
327+
}
328+
let root = root.unwrap_or(adt_def.did());
329+
330+
// We are inhabited if in some variant all fields are inhabited.
331+
adt_def.variants().iter().any(|variant| {
332+
variant.fields.iter().all(|field| {
333+
let ty = field.ty(tcx, adt_args);
334+
let ty = tcx.normalize_erasing_regions(typing_env, ty);
335+
rec(ty, Some(root))
336+
})
337+
})
338+
})
339+
}

compiler/rustc_ty_utils/src/layout/invariant.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ pub(super) fn layout_sanity_check<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayou
3434
layout.ty
3535
);
3636
}
37+
// ABI uninhabitedness should omply opsem uninhabitedness.
38+
if layout.is_uninhabited() {
39+
assert!(
40+
layout.ty.is_opsem_inhabited(tcx, cx.typing_env),
41+
"{:?} is ABI-uninhabited but not opsem-uninhabited?",
42+
layout.ty
43+
);
44+
}
3745

3846
/// Yields non-ZST fields of the type
3947
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.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]) };

tests/ui/consts/const-eval/ub-uninhabit.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,25 @@ union MaybeUninit<T: Copy> {
1818
}
1919

2020
const BAD_BAD_BAD: Bar = unsafe { MaybeUninit { uninit: () }.init };
21-
//~^ ERROR constructing invalid value
21+
//~^ ERROR value of uninhabited type `Bar`
2222

2323
const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) };
24-
//~^ ERROR constructing invalid value
24+
//~^ ERROR reference pointing to uninhabited type `Bar`
2525

2626
const BAD_BAD_ARRAY: [Bar; 1] = unsafe { MaybeUninit { uninit: () }.init };
27-
//~^ ERROR constructing invalid value
27+
//~^ ERROR value of uninhabited type `Bar`
2828

2929
const READ_NEVER: () = unsafe {
3030
let mem = [0u32; 8];
3131
let ptr = mem.as_ptr().cast::<!>();
3232
let _val = intrinsics::read_via_copy(ptr);
33-
//~^ ERROR constructing invalid value
33+
//~^ ERROR value of the never type
3434
};
3535

36+
const BAD_NESTED_REF: &&! = unsafe { mem::transmute(&&0) };
37+
//~^ ERROR reference pointing to uninhabited type `&!`
38+
39+
const BAD_NESTED_UNSIZED_REF: &&(!, [i32]) = unsafe { mem::transmute(&(&[0] as &[i32])) };
40+
//~^ ERROR reference pointing to uninhabited type `&(!, [i32])`
41+
3642
fn main() {}

0 commit comments

Comments
 (0)