diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 2600c1fbff7..f98ac80a4c9 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -2323,27 +2323,48 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { let src_tail = tcx.struct_tail_without_normalization(src.ty); let dst_tail = tcx.struct_tail_without_normalization(dst.ty); - if let ty::Dynamic(..) = src_tail.kind() + if let ty::Dynamic(src_tty, ..) = src_tail.kind() && let ty::Dynamic(dst_tty, ..) = dst_tail.kind() + && src_tty.principal().is_some() && dst_tty.principal().is_some() { - // Erase trait object lifetimes, to allow casts like `*mut dyn FnOnce()` -> `*mut dyn FnOnce() + 'static`. - let src_tail = - erase_single_trait_object_lifetime(tcx, src_tail); - let dst_tail = - erase_single_trait_object_lifetime(tcx, dst_tail); + // Erase trait object lifetimes, to allow casts like `*mut dyn FnOnce()` -> `*mut dyn FnOnce() + 'static` + // and remove auto traits. + let src_obj = tcx.mk_ty_from_kind(ty::Dynamic( + tcx.mk_poly_existential_predicates( + &src_tty.without_auto_traits().collect::>(), + ), + tcx.lifetimes.re_erased, + ty::Dyn, + )); + let dst_obj = tcx.mk_ty_from_kind(ty::Dynamic( + tcx.mk_poly_existential_predicates( + &dst_tty.without_auto_traits().collect::>(), + ), + tcx.lifetimes.re_erased, + ty::Dyn, + )); + + // FIXME: + // this currently does nothing, but once we make `ptr_cast_add_auto_to_object` + // into a hard error, we can remove the above removal of auto traits and only + // keep this. + let src_obj = erase_single_trait_object_lifetime(tcx, src_obj); + let dst_obj = erase_single_trait_object_lifetime(tcx, dst_obj); let trait_ref = ty::TraitRef::new( tcx, tcx.require_lang_item(LangItem::Unsize, Some(span)), - [src_tail, dst_tail], + [src_obj, dst_obj], ); + debug!(?src_tty, ?dst_tty, ?src_obj, ?dst_obj); + self.prove_trait_ref( trait_ref, location.to_locations(), ConstraintCategory::Cast { - unsize_to: Some(tcx.fold_regions(dst_tail, |r, _| { + unsize_to: Some(tcx.fold_regions(dst_obj, |r, _| { if let ty::ReVar(_) = r.kind() { tcx.lifetimes.re_erased } else { diff --git a/compiler/rustc_hir_typeck/messages.ftl b/compiler/rustc_hir_typeck/messages.ftl index d6f3f4d640b..f05da5ad48a 100644 --- a/compiler/rustc_hir_typeck/messages.ftl +++ b/compiler/rustc_hir_typeck/messages.ftl @@ -123,6 +123,11 @@ hir_typeck_option_result_asref = use `{$def_path}::as_ref` to convert `{$expecte hir_typeck_option_result_cloned = use `{$def_path}::cloned` to clone the value inside the `{$def_path}` hir_typeck_option_result_copied = use `{$def_path}::copied` to copy the value inside the `{$def_path}` +hir_typeck_ptr_cast_add_auto_to_object = adding an auto {$traits_len -> + [1] trait {$traits} + *[other] traits {$traits} +} to a trait object in a pointer cast may cause UB later on + hir_typeck_remove_semi_for_coerce = you might have meant to return the `match` expression hir_typeck_remove_semi_for_coerce_expr = this could be implicitly returned but it is a statement, not a tail expression hir_typeck_remove_semi_for_coerce_ret = the `match` arms can conform to this return type diff --git a/compiler/rustc_hir_typeck/src/cast.rs b/compiler/rustc_hir_typeck/src/cast.rs index 1809f5e4b04..c2f481e6c2f 100644 --- a/compiler/rustc_hir_typeck/src/cast.rs +++ b/compiler/rustc_hir_typeck/src/cast.rs @@ -32,6 +32,8 @@ use super::FnCtxt; use crate::errors; use crate::type_error_struct; +use itertools::Itertools; +use rustc_data_structures::fx::FxHashSet; use rustc_errors::{codes::*, Applicability, Diag, ErrorGuaranteed}; use rustc_hir::{self as hir, ExprKind, LangItem}; use rustc_infer::traits::Obligation; @@ -827,15 +829,16 @@ impl<'a, 'tcx> CastCheck<'tcx> { // trait object -> trait object? need to do additional checks (Some(PointerKind::VTable(src_tty)), Some(PointerKind::VTable(dst_tty))) => { match (src_tty.principal(), dst_tty.principal()) { - // A -> B. need to make sure - // - traits are the same + // A + SrcAuto> -> B + DstAuto>. need to make sure + // - `Src` and `Dst` traits are the same // - traits have the same generic arguments - // - Auto' is a subset of Auto + // - `SrcAuto` is a superset of `DstAuto` (Some(src_principal), Some(dst_principal)) => { let tcx = fcx.tcx; // Check that the traits are actually the same // (this is required as the `Unsize` check below would allow upcasting, etc) + // N.B.: this is only correct as long as we don't support `trait A: A<()>`. if src_principal.def_id() != dst_principal.def_id() { return Err(CastError::DifferingKinds); } @@ -845,18 +848,24 @@ impl<'a, 'tcx> CastCheck<'tcx> { // contain wrappers, which we do not care about. // // e.g. we want to allow `dyn T -> (dyn T,)`, etc. + // + // We also need to skip auto traits to emit an FCW and not an error. let src_obj = tcx.mk_ty_from_kind(ty::Dynamic( - src_tty, + tcx.mk_poly_existential_predicates( + &src_tty.without_auto_traits().collect::>(), + ), tcx.lifetimes.re_erased, ty::Dyn, )); let dst_obj = tcx.mk_ty_from_kind(ty::Dynamic( - dst_tty, + tcx.mk_poly_existential_predicates( + &dst_tty.without_auto_traits().collect::>(), + ), tcx.lifetimes.re_erased, ty::Dyn, )); - // `dyn Src: Unsize` + // `dyn Src: Unsize`, this checks for matching generics let cause = fcx.misc(self.span); let obligation = Obligation::new( tcx, @@ -871,6 +880,31 @@ impl<'a, 'tcx> CastCheck<'tcx> { fcx.register_predicate(obligation); + // Check that `SrcAuto` is a superset of `DstAuto`. + // Emit an FCW otherwise. + let src_auto = src_tty.auto_traits().collect::>(); + let added = dst_tty + .auto_traits() + .filter(|trait_did| !src_auto.contains(trait_did)) + .collect::>(); + + if !added.is_empty() { + tcx.emit_node_span_lint( + lint::builtin::PTR_CAST_ADD_AUTO_TO_OBJECT, + self.expr.hir_id, + self.span, + errors::PtrCastAddAutoToObject { + traits_len: added.len(), + traits: added + .into_iter() + .map(|trait_did| { + format!("`{}`", tcx.def_path_str(trait_did)) + }) + .join(", "), + }, + ) + } + // FIXME: ideally we'd maybe add a flag here, so that borrowck knows that // it needs to borrowck this ptr cast. this is made annoying by the // fact that `thir` does not have `CastKind` and mir restores it diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index 98add86252c..6c10047cfd4 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -253,6 +253,14 @@ pub struct LossyProvenanceInt2Ptr<'tcx> { pub sugg: LossyProvenanceInt2PtrSuggestion, } +#[derive(LintDiagnostic)] +#[diag(hir_typeck_ptr_cast_add_auto_to_object)] +//#[help] +pub struct PtrCastAddAutoToObject { + pub traits_len: usize, + pub traits: String, +} + #[derive(Subdiagnostic)] #[multipart_suggestion(hir_typeck_suggestion, applicability = "has-placeholders")] pub struct LossyProvenanceInt2PtrSuggestion { diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 2ade6964ca8..6af6f7c473e 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -80,6 +80,7 @@ declare_lint_pass! { PRIVATE_BOUNDS, PRIVATE_INTERFACES, PROC_MACRO_DERIVE_RESOLUTION_FALLBACK, + PTR_CAST_ADD_AUTO_TO_OBJECT, PUB_USE_OF_PRIVATE_EXTERN_CRATE, REDUNDANT_LIFETIMES, REFINING_IMPL_TRAIT_INTERNAL, @@ -4937,6 +4938,59 @@ declare_lint! { }; } +declare_lint! { + /// The `ptr_cast_add_auto_to_object` lint detects casts of raw pointers to trait + /// objects, which add auto traits. + /// + /// ### Example + /// + /// ```rust,edition2021,compile_fail + /// let ptr: *const dyn core::any::Any = &(); + /// _ = ptr as *const dyn core::any::Any + Send; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Adding an auto trait can make the vtable invalid, potentially causing + /// UB in safe code afterwards. For example: + /// + /// ```ignore (causes a warning) + /// #![feature(arbitrary_self_types)] + /// + /// trait Trait { + /// fn f(self: *const Self) + /// where + /// Self: Send; + /// } + /// + /// impl Trait for *const () { + /// fn f(self: *const Self) { + /// unreachable!() + /// } + /// } + /// + /// fn main() { + /// let unsend: *const () = &(); + /// let unsend: *const dyn Trait = &unsend; + /// let send_bad: *const (dyn Trait + Send) = unsend as _; + /// send_bad.f(); // this crashes, since vtable for `*const ()` does not have an entry for `f` + /// } + /// ``` + /// + /// Generally you must ensure that vtable is right for the pointer's type, + /// before passing the pointer to safe code. + pub PTR_CAST_ADD_AUTO_TO_OBJECT, + Warn, + "detects `as` casts from pointers to `dyn Trait` to pointers to `dyn Trait + Auto`", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::FutureReleaseErrorReportInDeps, + // FIXME: actually write an issue with an explanation + reference: "issue #125289 ", + }; +} + declare_lint! { /// The `out_of_scope_macro_calls` lint detects `macro_rules` called when they are not in scope, /// above their definition, which may happen in key-value attributes. diff --git a/compiler/rustc_middle/src/ty/predicate.rs b/compiler/rustc_middle/src/ty/predicate.rs index e9b37503bb3..3c327eb5b1d 100644 --- a/compiler/rustc_middle/src/ty/predicate.rs +++ b/compiler/rustc_middle/src/ty/predicate.rs @@ -341,6 +341,14 @@ impl<'tcx> ty::List> { _ => None, }) } + + pub fn without_auto_traits( + &self, + ) -> impl Iterator> + '_ { + self.iter().filter(|predicate| { + !matches!(predicate.as_ref().skip_binder(), ExistentialPredicate::AutoTrait(_)) + }) + } } pub type PolyTraitRef<'tcx> = ty::Binder<'tcx, TraitRef<'tcx>>; diff --git a/tests/ui/cast/ptr-to-trait-obj-add-auto.rs b/tests/ui/cast/ptr-to-trait-obj-add-auto.rs index 27c6ffb61cc..75b56816984 100644 --- a/tests/ui/cast/ptr-to-trait-obj-add-auto.rs +++ b/tests/ui/cast/ptr-to-trait-obj-add-auto.rs @@ -1,9 +1,11 @@ -//@ check-fail +//@ check-pass trait Trait<'a> {} fn add_auto<'a>(x: *mut dyn Trait<'a>) -> *mut (dyn Trait<'a> + Send) { - x as _ //~ error: the trait bound `dyn Trait<'_>: Unsize + Send>` is not satisfied + x as _ + //~^ warning: adding an auto trait `Send` to a trait object in a pointer cast may cause UB later on + //~| warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! } fn main() {} diff --git a/tests/ui/cast/ptr-to-trait-obj-add-auto.stderr b/tests/ui/cast/ptr-to-trait-obj-add-auto.stderr index 0c35db57b30..ff8c7057c96 100644 --- a/tests/ui/cast/ptr-to-trait-obj-add-auto.stderr +++ b/tests/ui/cast/ptr-to-trait-obj-add-auto.stderr @@ -1,11 +1,23 @@ -error[E0277]: the trait bound `dyn Trait<'_>: Unsize + Send>` is not satisfied +warning: adding an auto trait `Send` to a trait object in a pointer cast may cause UB later on --> $DIR/ptr-to-trait-obj-add-auto.rs:6:5 | LL | x as _ - | ^^^^^^ the trait `Unsize + Send>` is not implemented for `dyn Trait<'_>` + | ^^^^^^ | - = note: all implementations of `Unsize` are provided automatically by the compiler, see for more information + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #125289 + = note: `#[warn(ptr_cast_add_auto_to_object)]` on by default -error: aborting due to 1 previous error +warning: 1 warning emitted + +Future incompatibility report: Future breakage diagnostic: +warning: adding an auto trait `Send` to a trait object in a pointer cast may cause UB later on + --> $DIR/ptr-to-trait-obj-add-auto.rs:6:5 + | +LL | x as _ + | ^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #125289 + = note: `#[warn(ptr_cast_add_auto_to_object)]` on by default -For more information about this error, try `rustc --explain E0277`.