diff options
| author | Michael Goulet <michael@errs.io> | 2024-09-23 03:56:20 -0400 |
|---|---|---|
| committer | Michael Goulet <michael@errs.io> | 2024-09-25 11:10:38 -0400 |
| commit | 8fc8e03150ed69cc37a3e21b319a4d9bf247292f (patch) | |
| tree | 9639eb69900a6bfec4c6b5ab17a7546777884dad | |
| parent | 38352b01ae4af9300be03b805d6db68c45e51068 (diff) | |
| download | rust-8fc8e03150ed69cc37a3e21b319a4d9bf247292f.tar.gz rust-8fc8e03150ed69cc37a3e21b319a4d9bf247292f.zip | |
Validate unsize coercion in MIR validation
| -rw-r--r-- | compiler/rustc_mir_transform/src/validate.rs | 50 | ||||
| -rw-r--r-- | tests/crashes/129219.rs | 26 | ||||
| -rw-r--r-- | tests/ui/mir/validate/validate-unsize-cast.rs | 33 | ||||
| -rw-r--r-- | tests/ui/mir/validate/validate-unsize-cast.stderr | 20 |
4 files changed, 100 insertions, 29 deletions
diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index eda0b8c75f3..c9a844e4734 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -4,7 +4,8 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::LangItem; use rustc_index::IndexVec; use rustc_index::bit_set::BitSet; -use rustc_infer::traits::Reveal; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_infer::traits::{Obligation, ObligationCause, Reveal}; use rustc_middle::mir::coverage::CoverageKind; use rustc_middle::mir::visit::{NonUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; @@ -16,6 +17,8 @@ use rustc_middle::ty::{ use rustc_middle::{bug, span_bug}; use rustc_target::abi::{FIRST_VARIANT, Size}; use rustc_target::spec::abi::Abi; +use rustc_trait_selection::traits::ObligationCtxt; +use rustc_type_ir::Upcast; use crate::util::{is_within_packed, relate_types}; @@ -586,6 +589,22 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { crate::util::relate_types(self.tcx, self.param_env, variance, src, dest) } + + /// Check that the given predicate definitely holds in the param-env of this MIR body. + fn predicate_must_hold_modulo_regions( + &self, + pred: impl Upcast<TyCtxt<'tcx>, ty::Predicate<'tcx>>, + ) -> bool { + let infcx = self.tcx.infer_ctxt().build(); + let ocx = ObligationCtxt::new(&infcx); + ocx.register_obligation(Obligation::new( + self.tcx, + ObligationCause::dummy(), + self.param_env, + pred, + )); + ocx.select_all_or_error().is_empty() + } } impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { @@ -1202,8 +1221,33 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } CastKind::PointerCoercion(PointerCoercion::Unsize, _) => { - // This is used for all `CoerceUnsized` types, - // not just pointers/references, so is hard to check. + // Pointers being unsize coerced should at least implement + // `CoerceUnsized`. + if !self.predicate_must_hold_modulo_regions(ty::TraitRef::new( + self.tcx, + self.tcx.require_lang_item( + LangItem::CoerceUnsized, + Some(self.body.source_info(location).span), + ), + [op_ty, *target_type], + )) { + self.fail(location, format!("Unsize coercion, but `{op_ty}` isn't coercible to `{target_type}`")); + } + + // FIXME: Codegen has an additional assumption, where if the + // principal trait def id of what's being casted doesn't change, + // then we don't need to adjust the vtable at all. This + // corresponds to the fact that `dyn Tr<A>: Unsize<dyn Tr<B>>` + // requires that `A = B`; we don't allow *upcasting* objects + // between the same trait with different args. Nothing actually + // validates this, though. While it's true right now, if we for + // some reason were to relax the `Unsize` trait, it could become + // unsound. We should eventually validate that, but it would + // require peeling `&Box<Struct<.., dyn Tr<A>, ..>>` down to + // the trait object that's being unsized, and that's rather + // annoying, and also it would need to be opportunistic since + // this MIR is not yet fully monomorphized, so we may bottom + // out in an alias or a projection or something. } CastKind::PointerCoercion(PointerCoercion::DynStar, _) => { // FIXME(dyn-star): make sure nothing needs to be done here. diff --git a/tests/crashes/129219.rs b/tests/crashes/129219.rs deleted file mode 100644 index effbfcd8b8e..00000000000 --- a/tests/crashes/129219.rs +++ /dev/null @@ -1,26 +0,0 @@ -//@ known-bug: rust-lang/rust#129219 -//@ compile-flags: -Zmir-opt-level=5 -Zvalidate-mir --edition=2018 - -use core::marker::Unsize; - -pub trait CastTo<T: ?Sized>: Unsize<T> {} - -impl<T: ?Sized, U: ?Sized> CastTo<T> for U {} - -impl<T: ?Sized> Cast for T {} -pub trait Cast { - fn cast<T: ?Sized>(&self) -> &T - where - Self: CastTo<T>, - { - self - } -} - -pub trait Foo {} -impl Foo for [i32; 0] {} - -fn main() { - let x: &dyn Foo = &[]; - let x = x.cast::<[i32]>(); -} diff --git a/tests/ui/mir/validate/validate-unsize-cast.rs b/tests/ui/mir/validate/validate-unsize-cast.rs new file mode 100644 index 00000000000..198af8d2e13 --- /dev/null +++ b/tests/ui/mir/validate/validate-unsize-cast.rs @@ -0,0 +1,33 @@ +//@ compile-flags: -Zmir-opt-level=0 -Zmir-enable-passes=+Inline,+GVN -Zvalidate-mir + +#![feature(unsize)] + +use std::marker::Unsize; + +pub trait CastTo<U: ?Sized>: Unsize<U> {} + +// Not well-formed! +impl<T: ?Sized, U: ?Sized> CastTo<U> for T {} +//~^ ERROR the trait bound `T: Unsize<U>` is not satisfied + +pub trait Cast { + fn cast<U: ?Sized>(&self) + where + Self: CastTo<U>; +} +impl<T: ?Sized> Cast for T { + #[inline(always)] + fn cast<U: ?Sized>(&self) + where + Self: CastTo<U>, + { + let x: &U = self; + } +} + +fn main() { + // When we inline this call, then we run GVN, then + // GVN tries to evaluate the `() -> [i32]` unsize. + // That's invalid! + ().cast::<[i32]>(); +} diff --git a/tests/ui/mir/validate/validate-unsize-cast.stderr b/tests/ui/mir/validate/validate-unsize-cast.stderr new file mode 100644 index 00000000000..cfb47b34e98 --- /dev/null +++ b/tests/ui/mir/validate/validate-unsize-cast.stderr @@ -0,0 +1,20 @@ +error[E0277]: the trait bound `T: Unsize<U>` is not satisfied + --> $DIR/validate-unsize-cast.rs:10:42 + | +LL | impl<T: ?Sized, U: ?Sized> CastTo<U> for T {} + | ^ the trait `Unsize<U>` is not implemented for `T` + | + = 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 +note: required by a bound in `CastTo` + --> $DIR/validate-unsize-cast.rs:7:30 + | +LL | pub trait CastTo<U: ?Sized>: Unsize<U> {} + | ^^^^^^^^^ required by this bound in `CastTo` +help: consider further restricting this bound + | +LL | impl<T: ?Sized + std::marker::Unsize<U>, U: ?Sized> CastTo<U> for T {} + | ++++++++++++++++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. |
