Skip to content

Commit 323f521

Browse files
authored
Unrolled build for #130735
Rollup merge of #130735 - compiler-errors:validate-unsize, r=spastorino,RalfJung Simple validation for unsize coercion in MIR validation This adds the most basic validity check to unsize coercions in MIR. The src and target of an unsize cast must *at least* implement `Src: CoerceUnsized<Target>` for this to be valid. This doesn't the second, more subtle validity check that is taken of advantage in codegen [here](https://github.com/rust-lang/rust/blob/914193c8f40528fe82696e1054828de8c399882e/compiler/rustc_codegen_ssa/src/base.rs#L126), but I did leave a beefy FIXME for that explaining what it is. As a consequence, this also fixes an ICE with GVN and invalid unsize coercions. This is somewhat coincidental, since MIR inlining will check that a body is valid before inlining it; so now that we determine it to be invalid, we don't inline it, and we don't encounter the GVN ICE. I'm not certain if the same GVN ICE is triggerable without the inliner, and perhaps instead with trivial where clauses or something. cc `@RalfJung`
2 parents b511753 + 3209943 commit 323f521

File tree

6 files changed

+123
-32
lines changed

6 files changed

+123
-32
lines changed

compiler/rustc_codegen_cranelift/src/unsize.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,22 @@ pub(crate) fn unsized_info<'tcx>(
3434
let old_info =
3535
old_info.expect("unsized_info: missing old info for trait upcasting coercion");
3636
if data_a.principal_def_id() == data_b.principal_def_id() {
37-
// A NOP cast that doesn't actually change anything, should be allowed even with invalid vtables.
37+
// Codegen takes advantage of the additional assumption, where if the
38+
// principal trait def id of what's being casted doesn't change,
39+
// then we don't need to adjust the vtable at all. This
40+
// corresponds to the fact that `dyn Tr<A>: Unsize<dyn Tr<B>>`
41+
// requires that `A = B`; we don't allow *upcasting* objects
42+
// between the same trait with different args. If we, for
43+
// some reason, were to relax the `Unsize` trait, it could become
44+
// unsound, so let's assert here that the trait refs are *equal*.
45+
//
46+
// We can use `assert_eq` because the binders should have been anonymized,
47+
// and because higher-ranked equality now requires the binders are equal.
48+
debug_assert_eq!(
49+
data_a.principal(),
50+
data_b.principal(),
51+
"NOP unsize vtable changed principal trait ref: {data_a} -> {data_b}"
52+
);
3853
return old_info;
3954
}
4055

compiler/rustc_codegen_ssa/src/base.rs

+22-2
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,28 @@ fn unsized_info<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
125125
let old_info =
126126
old_info.expect("unsized_info: missing old info for trait upcasting coercion");
127127
if data_a.principal_def_id() == data_b.principal_def_id() {
128-
// A NOP cast that doesn't actually change anything, should be allowed even with
129-
// invalid vtables.
128+
// Codegen takes advantage of the additional assumption, where if the
129+
// principal trait def id of what's being casted doesn't change,
130+
// then we don't need to adjust the vtable at all. This
131+
// corresponds to the fact that `dyn Tr<A>: Unsize<dyn Tr<B>>`
132+
// requires that `A = B`; we don't allow *upcasting* objects
133+
// between the same trait with different args. If we, for
134+
// some reason, were to relax the `Unsize` trait, it could become
135+
// unsound, so let's assert here that the trait refs are *equal*.
136+
//
137+
// We can use `assert_eq` because the binders should have been anonymized,
138+
// and because higher-ranked equality now requires the binders are equal.
139+
debug_assert_eq!(
140+
data_a.principal(),
141+
data_b.principal(),
142+
"NOP unsize vtable changed principal trait ref: {data_a} -> {data_b}"
143+
);
144+
145+
// A NOP cast that doesn't actually change anything, let's avoid any
146+
// unnecessary work. This relies on the assumption that if the principal
147+
// traits are equal, then the associated type bounds (`dyn Trait<Assoc=T>`)
148+
// are also equal, which is ensured by the fact that normalization is
149+
// a function and we do not allow overlapping impls.
130150
return old_info;
131151
}
132152

compiler/rustc_mir_transform/src/validate.rs

+32-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
44
use rustc_hir::LangItem;
55
use rustc_index::IndexVec;
66
use rustc_index::bit_set::BitSet;
7-
use rustc_infer::traits::Reveal;
7+
use rustc_infer::infer::TyCtxtInferExt;
8+
use rustc_infer::traits::{Obligation, ObligationCause, Reveal};
89
use rustc_middle::mir::coverage::CoverageKind;
910
use rustc_middle::mir::visit::{NonUseContext, PlaceContext, Visitor};
1011
use rustc_middle::mir::*;
@@ -16,6 +17,8 @@ use rustc_middle::ty::{
1617
use rustc_middle::{bug, span_bug};
1718
use rustc_target::abi::{FIRST_VARIANT, Size};
1819
use rustc_target::spec::abi::Abi;
20+
use rustc_trait_selection::traits::ObligationCtxt;
21+
use rustc_type_ir::Upcast;
1922

2023
use crate::util::{is_within_packed, relate_types};
2124

@@ -586,6 +589,22 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
586589

587590
crate::util::relate_types(self.tcx, self.param_env, variance, src, dest)
588591
}
592+
593+
/// Check that the given predicate definitely holds in the param-env of this MIR body.
594+
fn predicate_must_hold_modulo_regions(
595+
&self,
596+
pred: impl Upcast<TyCtxt<'tcx>, ty::Predicate<'tcx>>,
597+
) -> bool {
598+
let infcx = self.tcx.infer_ctxt().build();
599+
let ocx = ObligationCtxt::new(&infcx);
600+
ocx.register_obligation(Obligation::new(
601+
self.tcx,
602+
ObligationCause::dummy(),
603+
self.param_env,
604+
pred,
605+
));
606+
ocx.select_all_or_error().is_empty()
607+
}
589608
}
590609

591610
impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
@@ -1202,8 +1221,18 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
12021221
}
12031222
}
12041223
CastKind::PointerCoercion(PointerCoercion::Unsize, _) => {
1205-
// This is used for all `CoerceUnsized` types,
1206-
// not just pointers/references, so is hard to check.
1224+
// Pointers being unsize coerced should at least implement
1225+
// `CoerceUnsized`.
1226+
if !self.predicate_must_hold_modulo_regions(ty::TraitRef::new(
1227+
self.tcx,
1228+
self.tcx.require_lang_item(
1229+
LangItem::CoerceUnsized,
1230+
Some(self.body.source_info(location).span),
1231+
),
1232+
[op_ty, *target_type],
1233+
)) {
1234+
self.fail(location, format!("Unsize coercion, but `{op_ty}` isn't coercible to `{target_type}`"));
1235+
}
12071236
}
12081237
CastKind::PointerCoercion(PointerCoercion::DynStar, _) => {
12091238
// FIXME(dyn-star): make sure nothing needs to be done here.

tests/crashes/129219.rs

-26
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//@ compile-flags: -Zmir-opt-level=0 -Zmir-enable-passes=+Inline,+GVN -Zvalidate-mir
2+
3+
#![feature(unsize)]
4+
5+
use std::marker::Unsize;
6+
7+
pub trait CastTo<U: ?Sized>: Unsize<U> {}
8+
9+
// Not well-formed!
10+
impl<T: ?Sized, U: ?Sized> CastTo<U> for T {}
11+
//~^ ERROR the trait bound `T: Unsize<U>` is not satisfied
12+
13+
pub trait Cast {
14+
fn cast<U: ?Sized>(&self)
15+
where
16+
Self: CastTo<U>;
17+
}
18+
impl<T: ?Sized> Cast for T {
19+
#[inline(always)]
20+
fn cast<U: ?Sized>(&self)
21+
where
22+
Self: CastTo<U>,
23+
{
24+
let x: &U = self;
25+
}
26+
}
27+
28+
fn main() {
29+
// When we inline this call, then we run GVN, then
30+
// GVN tries to evaluate the `() -> [i32]` unsize.
31+
// That's invalid!
32+
().cast::<[i32]>();
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error[E0277]: the trait bound `T: Unsize<U>` is not satisfied
2+
--> $DIR/validate-unsize-cast.rs:10:42
3+
|
4+
LL | impl<T: ?Sized, U: ?Sized> CastTo<U> for T {}
5+
| ^ the trait `Unsize<U>` is not implemented for `T`
6+
|
7+
= note: all implementations of `Unsize` are provided automatically by the compiler, see <https://doc.rust-lang.org/stable/std/marker/trait.Unsize.html> for more information
8+
note: required by a bound in `CastTo`
9+
--> $DIR/validate-unsize-cast.rs:7:30
10+
|
11+
LL | pub trait CastTo<U: ?Sized>: Unsize<U> {}
12+
| ^^^^^^^^^ required by this bound in `CastTo`
13+
help: consider further restricting this bound
14+
|
15+
LL | impl<T: ?Sized + std::marker::Unsize<U>, U: ?Sized> CastTo<U> for T {}
16+
| ++++++++++++++++++++++++
17+
18+
error: aborting due to 1 previous error
19+
20+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)