Skip to content

Commit

Permalink
Validate unsize coercion in MIR validation
Browse files Browse the repository at this point in the history
  • Loading branch information
compiler-errors committed Sep 23, 2024
1 parent 702987f commit bb7137a
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 29 deletions.
50 changes: 47 additions & 3 deletions compiler/rustc_mir_transform/src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -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};

Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -1205,8 +1224,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::IntToInt | CastKind::IntToFloat => {
let input_valid = op_ty.is_integral() || op_ty.is_char() || op_ty.is_bool();
Expand Down
26 changes: 0 additions & 26 deletions tests/crashes/129219.rs

This file was deleted.

33 changes: 33 additions & 0 deletions tests/ui/mir/validate/validate-unsize-cast.rs
Original file line number Diff line number Diff line change
@@ -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]>();
}
20 changes: 20 additions & 0 deletions tests/ui/mir/validate/validate-unsize-cast.stderr
Original file line number Diff line number Diff line change
@@ -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`.

0 comments on commit bb7137a

Please sign in to comment.