Skip to content

Commit 2552ff6

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

15 files changed

Lines changed: 255 additions & 33 deletions

File tree

compiler/rustc_const_eval/src/interpret/validity.rs

Lines changed: 11 additions & 2 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

@@ -1573,6 +1574,11 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
15731574
);
15741575
}
15751576
if cfg!(debug_assertions) {
1577+
assert!(
1578+
val.layout.ty.is_opsem_inhabited(*self.ecx.tcx, self.ecx.typing_env),
1579+
"a value of type `{}` somehow passed validity but is not actually inhabited",
1580+
val.layout.ty,
1581+
);
15761582
// Check that we don't miss any new changes to layout computation in our checks above.
15771583
match val.layout.backend_repr {
15781584
BackendRepr::Scalar(scalar_layout) => {
@@ -1626,6 +1632,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
16261632
) -> InterpResult<'tcx> {
16271633
trace!("validate_operand_internal: {:?}, {:?}", *val, val.layout.ty);
16281634

1635+
// We can't check validity if there are any generics left.
1636+
ensure_monomorphic_enough(*self.tcx, val.layout.ty)?;
1637+
16291638
// Run the visitor.
16301639
self.run_for_validation_mut(|ecx| {
16311640
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
@@ -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: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
//! This code should only compile in modules where the uninhabitedness of `Foo`
4444
//! is visible.
4545
46+
use rustc_data_structures::fx::FxHashSet;
4647
use rustc_type_ir::TyKind::*;
4748
use tracing::instrument;
4849

@@ -54,7 +55,12 @@ pub mod inhabited_predicate;
5455
pub use inhabited_predicate::InhabitedPredicate;
5556

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

6066
/// Returns an `InhabitedPredicate` that is generic over type parameters and
@@ -186,14 +192,26 @@ impl<'tcx> Ty<'tcx> {
186192
self.inhabited_predicate(tcx).apply(tcx, typing_env, module)
187193
}
188194

189-
/// Returns true if the type is uninhabited without regard to visibility
195+
/// Returns true if the type is uninhabited without regard to visibility.
196+
///
197+
/// This is still conservative; for instance, a `#[non_exhaustive]` enum *in another crate*
198+
/// is always considered inhabited.
190199
pub fn is_privately_uninhabited(
191200
self,
192201
tcx: TyCtxt<'tcx>,
193202
typing_env: ty::TypingEnv<'tcx>,
194203
) -> bool {
195204
!self.inhabited_predicate(tcx).apply_ignore_module(tcx, typing_env)
196205
}
206+
207+
/// Returns whether `self` is considered inhabited on the opsem level, i.e., its validity
208+
/// invariant might be satisfiable. `self` is expected to be monomorphic and normalized.
209+
pub fn is_opsem_inhabited(self, tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>) -> bool {
210+
// Handle simple cases directly, use the query with its cache for the rest.
211+
is_opsem_inhabited_recursor(self, tcx, &mut FxHashSet::default(), &|ty, _, _| {
212+
tcx.is_opsem_inhabited_raw(typing_env.as_query_input(ty))
213+
})
214+
}
197215
}
198216

199217
/// N.B. this query should only be called through `Ty::inhabited_predicate`
@@ -216,3 +234,125 @@ fn inhabited_predicate_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> InhabitedP
216234
_ => bug!("unexpected TyKind, use `Ty::inhabited_predicate`"),
217235
}
218236
}
237+
238+
/// `seen` is used to bound the recursion: the set contains all ADTs that we encountered
239+
/// on our path to the current type.
240+
///
241+
/// When we encounter an ADT, we call `adt_handler`, giving it as its last argument a closure that
242+
/// it can invoke to continue the recursion.
243+
fn is_opsem_inhabited_recursor<'tcx>(
244+
ty: Ty<'tcx>,
245+
tcx: TyCtxt<'tcx>,
246+
seen: &mut FxHashSet<Ty<'tcx>>,
247+
adt_handler: &impl Fn(
248+
Ty<'tcx>,
249+
&mut FxHashSet<Ty<'tcx>>,
250+
&dyn Fn(Ty<'tcx>, &mut FxHashSet<Ty<'tcx>>) -> bool,
251+
) -> bool,
252+
) -> bool {
253+
match *ty.kind() {
254+
// Trivially (un)inhabited types
255+
ty::Int(_)
256+
| ty::Uint(_)
257+
| ty::Float(_)
258+
| ty::Bool
259+
| ty::Char
260+
| ty::Str
261+
| ty::Foreign(..)
262+
| ty::RawPtr(..)
263+
| ty::FnPtr(..)
264+
| ty::FnDef(..) => true,
265+
ty::Dynamic(..) => true, // We can't reason about traits, assume they are inhabited
266+
ty::Slice(..) => true, // Slices can always be empty
267+
ty::Never => false,
268+
269+
// Types where we recurse
270+
ty::Ref(_, pointee, _) => is_opsem_inhabited_recursor(pointee, tcx, seen, adt_handler),
271+
ty::Tuple(tys) => {
272+
tys.iter().all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, adt_handler))
273+
}
274+
ty::Array(elem, len) => {
275+
len.try_to_target_usize(tcx).unwrap() == 0
276+
|| is_opsem_inhabited_recursor(elem, tcx, seen, adt_handler)
277+
}
278+
ty::Pat(inner, _pat) => is_opsem_inhabited_recursor(inner, tcx, seen, adt_handler),
279+
ty::Closure(_def, args) => {
280+
let args = args.as_closure();
281+
args.upvar_tys()
282+
.iter()
283+
.all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, adt_handler))
284+
}
285+
ty::Coroutine(_def, args) => {
286+
let args = args.as_coroutine();
287+
args.upvar_tys()
288+
.iter()
289+
.all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, adt_handler))
290+
}
291+
ty::CoroutineClosure(_def, args) => {
292+
let args = args.as_coroutine_closure();
293+
args.upvar_tys()
294+
.iter()
295+
.all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, adt_handler))
296+
}
297+
ty::UnsafeBinder(..) => {
298+
true // FIXME(unsafe_binder) should these really be trivially inhabited?
299+
}
300+
ty::Adt(..) => {
301+
// ADTs need a special handler to avoid infinite recursion.
302+
adt_handler(ty, seen, &|ty, seen| {
303+
is_opsem_inhabited_recursor(ty, tcx, seen, adt_handler)
304+
})
305+
}
306+
307+
ty::Error(_)
308+
| ty::Infer(..)
309+
| ty::Placeholder(..)
310+
| ty::Bound(..)
311+
| ty::Param(..)
312+
| ty::Alias(..)
313+
| ty::CoroutineWitness(..) => {
314+
bug!("non-normalized type in `is_opsem_uninhabited_raw::rec`: `{ty}`")
315+
}
316+
}
317+
}
318+
319+
fn is_opsem_inhabited_raw<'tcx>(
320+
tcx: TyCtxt<'tcx>,
321+
env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
322+
) -> bool {
323+
let (ty, typing_env) = (env.value, env.typing_env);
324+
is_opsem_inhabited_recursor(ty, tcx, &mut FxHashSet::default(), &|ty, seen, rec| {
325+
let ty::Adt(adt_def, adt_args) = *ty.kind() else {
326+
unreachable! {}
327+
};
328+
if adt_def.is_union() {
329+
// Unions are always inhabited.
330+
return true;
331+
}
332+
333+
if !seen.insert(ty) {
334+
// We recursed to the same type. Coinductively assume that the type is inhabited.
335+
return true;
336+
}
337+
if !tcx.recursion_limit().value_within_limit(seen.len()) {
338+
// We seem to be stuck in an infinite recursion.
339+
// Bailing out here is unfortunate as it means that the recursion limit affects the
340+
// operational semantics... but what else could we do?
341+
return true;
342+
}
343+
344+
// We are inhabited if in some variant all fields are inhabited.
345+
let inhabited = adt_def.variants().iter().any(|variant| {
346+
variant.fields.iter().all(|field| {
347+
let ty = field.ty(tcx, adt_args);
348+
let ty = tcx.normalize_erasing_regions(typing_env, ty);
349+
rec(ty, seen)
350+
})
351+
});
352+
353+
// Remove the type again so that we allow it to appear on other branches.
354+
seen.remove(&ty);
355+
356+
inhabited
357+
})
358+
}

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)