1
Fork 0

Align the changes to the lang decision

This commit is contained in:
Maybe Lapkin 2024-06-23 19:55:17 +02:00
parent c7435571ad
commit 340d69be12
8 changed files with 165 additions and 21 deletions

View file

@ -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::<Vec<_>>(),
),
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::<Vec<_>>(),
),
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 {

View file

@ -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

View file

@ -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<dyn Trait + Auto> -> B<dyn Trait' + Auto'>. need to make sure
// - traits are the same
// A<dyn Src<...> + SrcAuto> -> B<dyn Dst<...> + 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<T>: 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::<Vec<_>>(),
),
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::<Vec<_>>(),
),
tcx.lifetimes.re_erased,
ty::Dyn,
));
// `dyn Src: Unsize<dyn Dst>`
// `dyn Src: Unsize<dyn Dst>`, 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::<FxHashSet<_>>();
let added = dst_tty
.auto_traits()
.filter(|trait_did| !src_auto.contains(trait_did))
.collect::<Vec<_>>();
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

View file

@ -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 {

View file

@ -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 <https://github.com/rust-lang/rust/issues/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.

View file

@ -341,6 +341,14 @@ impl<'tcx> ty::List<ty::PolyExistentialPredicate<'tcx>> {
_ => None,
})
}
pub fn without_auto_traits(
&self,
) -> impl Iterator<Item = ty::PolyExistentialPredicate<'tcx>> + '_ {
self.iter().filter(|predicate| {
!matches!(predicate.as_ref().skip_binder(), ExistentialPredicate::AutoTrait(_))
})
}
}
pub type PolyTraitRef<'tcx> = ty::Binder<'tcx, TraitRef<'tcx>>;

View file

@ -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<dyn Trait<'_> + 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() {}

View file

@ -1,11 +1,23 @@
error[E0277]: the trait bound `dyn Trait<'_>: Unsize<dyn Trait<'_> + 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<dyn Trait<'_> + Send>` is not implemented for `dyn Trait<'_>`
| ^^^^^^
|
= 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
= 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 <https://github.com/rust-lang/rust/issues/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 <https://github.com/rust-lang/rust/issues/125289>
= note: `#[warn(ptr_cast_add_auto_to_object)]` on by default
For more information about this error, try `rustc --explain E0277`.