diff --git a/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs b/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs index e5b4628374ce5..ba89f40b60254 100644 --- a/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs +++ b/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs @@ -17,7 +17,7 @@ use rustc_middle::ty::{ use rustc_span::def_id::DefId; use rustc_span::{DUMMY_SP, bug}; use rustc_trait_selection::traits; -use tracing::{debug, instrument}; +use tracing::instrument; use crate::cfi::typeid::TypeIdOptions; use crate::cfi::typeid::itanium_cxx_abi::encode::EncodeTyOptions; @@ -75,7 +75,7 @@ impl<'tcx> TypeFolder> for TransformTy<'tcx> { ty::Bool => { if self.options.contains(EncodeTyOptions::NORMALIZE_INTEGERS) { - // Note: on all platforms that Rust's currently supports, its size and alignment + // Note: on all platforms that Rust currently supports, its size and alignment // are 1, and its ABI class is INTEGER - see Rust Layout and ABIs. // // (See https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#bool.) @@ -100,8 +100,10 @@ impl<'tcx> TypeFolder> for TransformTy<'tcx> { ty::Int(..) | ty::Uint(..) => { if self.options.contains(EncodeTyOptions::NORMALIZE_INTEGERS) { // Note: C99 7.18.2.4 requires uintptr_t and intptr_t to be at least 16-bit - // wide. All platforms we currently support have a C platform, and as a - // consequence, isize/usize are at least 16-bit wide for all of them. + // wide. All platforms that Rust currently supports have a C platform, and as + // a consequence, isize/usize are at least 16-bit wide for all of them. This is + // also guaranteed by the stable `From for isize` and `From for usize` + // implementations. // // (See https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#isize-and-usize.) match t.kind() { @@ -162,7 +164,7 @@ impl<'tcx> TypeFolder> for TransformTy<'tcx> { // contains or references itself, to avoid a reference cycle. // If the self reference is not through a pointer, for example, due - // to using `PhantomData`, need to skip normalizing it if we hit it again. + // to using `PhantomData`, need to skip normalizing it if it is hit again. self.parents.push(t); let ty = if ty0.is_any_ptr() && ty0.contains(t) { let options = self.options; @@ -176,7 +178,7 @@ impl<'tcx> TypeFolder> for TransformTy<'tcx> { self.parents.pop(); ty } else { - // Transform repr(transparent) types without non-ZST field into () + // Transform repr(transparent) types without non-ZST field into (). self.tcx.types.unit } } else { @@ -232,13 +234,25 @@ impl<'tcx> TypeFolder> for TransformTy<'tcx> { } } +/// Returns whether a trait method may be called through a vtable. +fn may_be_called_through_vtable(tcx: TyCtxt<'_>, method_id: DefId) -> bool { + let trait_id = tcx.parent(method_id); + traits::is_vtable_safe_method(tcx, trait_id, tcx.associated_item(method_id)) + && tcx.is_dyn_compatible(trait_id) +} + +/// Returns the trait object type of a trait reference (i.e., a dyn Trait type with the trait as +/// its principal trait, and the associated types of the trait and its supertraits as its +/// projections), for self to be transformed into when performing type erasure. #[instrument(skip(tcx), ret)] fn trait_object_ty<'tcx>(tcx: TyCtxt<'tcx>, poly_trait_ref: ty::PolyTraitRef<'tcx>) -> Ty<'tcx> { - assert!(!poly_trait_ref.has_non_region_param()); - let principal_pred = poly_trait_ref.map_bound(|trait_ref| { + if poly_trait_ref.has_non_region_param() { + bug!("trait_object_ty: unexpected non-region param in `{:?}`", poly_trait_ref); + } + let principal_predicate = poly_trait_ref.map_bound(|trait_ref| { ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)) }); - let mut assoc_preds: Vec<_> = traits::supertraits(tcx, poly_trait_ref) + let mut assoc_predicates: Vec<_> = traits::supertraits(tcx, poly_trait_ref) .flat_map(|super_poly_trait_ref| { tcx.associated_items(super_poly_trait_ref.def_id()) .in_definition_order() @@ -253,27 +267,206 @@ fn trait_object_ty<'tcx>(tcx: TyCtxt<'tcx>, poly_trait_ref: ty::PolyTraitRef<'tc }; let projection_term = ty::AliasTerm::new_from_args(tcx, kind, super_trait_ref.args); - let term = projection_term.to_term(tcx, ty::IsRigid::No); - let normalized_term = tcx.normalize_erasing_regions( + let term = tcx.normalize_erasing_regions( ty::TypingEnv::fully_monomorphized(), - Unnormalized::new_wip(term), + Unnormalized::new_wip(projection_term.to_term(tcx, ty::IsRigid::No)), ); - debug!("Projection {term} -> {normalized_term}"); ty::ExistentialPredicate::Projection( ty::ExistentialProjection::erase_self_ty( tcx, - ty::ProjectionClause { projection_term, term: normalized_term }, + ty::ProjectionClause { projection_term, term }, ), ) }) }) }) .collect(); - assoc_preds.sort_by(|a, b| a.skip_binder().stable_cmp(tcx, &b.skip_binder())); - let preds = tcx.mk_poly_existential_predicates_from_iter( - iter::once(principal_pred).chain(assoc_preds.into_iter()), + assoc_predicates.sort_by(|a, b| a.skip_binder().stable_cmp(tcx, &b.skip_binder())); + let predicates = tcx.mk_poly_existential_predicates_from_iter( + iter::once(principal_predicate).chain(assoc_predicates), + ); + Ty::new_dynamic(tcx, predicates, tcx.lifetimes.re_erased) +} + +/// Performs type erasure for closure-likes (i.e., instances identified by the def id of a +/// closure, coroutine, or coroutine-closure) by transforming self into a trait object of the Fn, +/// FnMut, FnOnce, Coroutine, Future, Iterator, or AsyncIterator trait that defines the call +/// method they are called through, and the instance into a virtual call to that method, to match +/// the type erasure performed during code generation at call sites (see transform_virtual_call). +/// Returns None if the instance is not a closure-like. +/// +/// E.g.: +/// +/// ```ignore (illustrative) +/// // The closure is transformed into ::call. +/// let f: Box = Box::new(|_x| {}); +/// f(0); +/// ``` +fn transform_closure_like<'tcx>( + tcx: TyCtxt<'tcx>, + mut instance: Instance<'tcx>, +) -> Option> { + if !tcx.is_closure_like(instance.def_id()) { + return None; + } + let closure_like_ty = instance.ty(tcx, ty::TypingEnv::fully_monomorphized()); + let (trait_id, call_method_id, inputs) = match closure_like_ty.kind() { + ty::Closure(_, args) => { + let closure_args = args.as_closure(); + let closure_kind = closure_args.kind(); + let trait_id = tcx.fn_trait_kind_to_def_id(closure_kind).unwrap_or_else(|| { + bug!( + "transform_closure_like: couldn't get trait of closure kind `{:?}`", + closure_kind + ) + }); + let tuple_args = + tcx.instantiate_bound_regions_with_erased(closure_args.sig()).inputs()[0]; + (trait_id, None, Some(tuple_args)) + } + ty::Coroutine(_, args) => match tcx.coroutine_kind(instance.def_id()).unwrap_or_else(|| { + bug!("transform_closure_like: couldn't get coroutine kind of `{:?}`", instance.def_id()) + }) { + hir::CoroutineKind::Coroutine(..) => ( + tcx.require_lang_item(LangItem::Coroutine, DUMMY_SP), + Some(tcx.require_lang_item(LangItem::CoroutineResume, DUMMY_SP)), + Some(args.as_coroutine().resume_ty()), + ), + hir::CoroutineKind::Desugared(desugaring, _) => { + let (trait_lang_item, call_method_lang_item) = match desugaring { + hir::CoroutineDesugaring::Async => (LangItem::Future, LangItem::FuturePoll), + hir::CoroutineDesugaring::AsyncGen => { + (LangItem::AsyncIterator, LangItem::AsyncIteratorPollNext) + } + hir::CoroutineDesugaring::Gen => (LangItem::Iterator, LangItem::IteratorNext), + }; + ( + tcx.require_lang_item(trait_lang_item, DUMMY_SP), + Some(tcx.require_lang_item(call_method_lang_item, DUMMY_SP)), + None, + ) + } + }, + ty::CoroutineClosure(_, args) => ( + tcx.require_lang_item(LangItem::FnOnce, DUMMY_SP), + None, + Some( + tcx.instantiate_bound_regions_with_erased( + args.as_coroutine_closure().coroutine_closure_sig(), + ) + .tupled_inputs_ty, + ), + ), + _ => bug!("transform_closure_like: unexpected `{:?}`", closure_like_ty.kind()), + }; + let call_method_id = call_method_id.unwrap_or_else(|| { + // The Fn, FnMut, and FnOnce traits do not have lang items for their call methods, and each + // has a single method, which is the one being defined. + tcx.associated_items(trait_id) + .in_definition_order() + .find(|item| item.is_fn()) + .unwrap_or_else(|| { + bug!("transform_closure_like: couldn't get call method of `{:?}`", trait_id) + }) + .def_id + }); + let concrete_args = tcx.mk_args_trait(closure_like_ty, inputs.map(Into::into)); + let trait_ref = ty::TraitRef::new_from_args(tcx, trait_id, concrete_args); + let self_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); + let abstract_args = tcx.mk_args_trait(self_ty, trait_ref.args.into_iter().skip(1)); + + instance.def = ty::InstanceKind::Virtual(call_method_id, 0); + instance.args = abstract_args; + Some(instance) +} + +/// Adjusts the type ids of DropGlues to a synthesized Drop trait object. +/// +/// DropGlues may have indirect calls to one or more given types' drop function. Rust allows for +/// types to be erased to any trait object and retains the drop function for the original type, +/// which means at the indirect call sites in DropGlues, when typeid_for_fnabi is called a second +/// time, it only has information after type erasure and it could be a call on any arbitrary trait +/// object. They are normalized to a synthesized Drop trait object, both on declaration/definition, +/// and during code generation at call sites so they have the same type id and match. +/// +/// E.g.: +/// +/// ```ignore (illustrative) +/// struct Type1; +/// +/// impl Drop for Type1 { +/// fn drop(&mut self) {} +/// } +/// +/// let x: Box = Box::new(Type1); +/// // Dropping x calls the drop glue of Type1 through the vtable of the dyn Send trait object. +/// // Both the drop glue and the virtual drop call to it are transformed into +/// // drop_in_place::. +/// ``` +/// +/// FIXME(rcvalle): This allows a drop call on any trait object to call the drop function of any +/// other type. +/// +fn transform_drop_glue<'tcx>(tcx: TyCtxt<'tcx>, mut instance: Instance<'tcx>) -> Instance<'tcx> { + let trait_id = tcx + .lang_items() + .drop_trait() + .unwrap_or_else(|| bug!("transform_drop_glue: couldn't get drop_trait lang item")); + let predicate = ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::new_from_args( + tcx, + trait_id, + ty::List::empty(), + )); + let predicates = tcx.mk_poly_existential_predicates(&[ty::Binder::dummy(predicate)]); + let self_ty = Ty::new_dynamic(tcx, predicates, tcx.lifetimes.re_erased); + instance.args = tcx.mk_args_trait(self_ty, List::empty()); + instance +} + +/// Performs type erasure for trait method implementations in impl blocks by transforming self +/// into a trait object of the trait that defines the method, and the instance into a virtual call +/// to the trait method definition it implements, to match the type erasure performed during code +/// generation at call sites (see transform_virtual_call). Returns None if the instance is not a +/// trait method implementation in an impl block that may be called through a vtable. +/// +/// E.g.: +/// +/// ```ignore (illustrative) +/// trait Trait1 { +/// fn foo(&self); +/// } +/// +/// struct Type1; +/// +/// impl Trait1 for Type1 { +/// fn foo(&self) {} // ::foo is transformed into ::foo. +/// } +/// +/// let x: &dyn Trait1 = &Type1; +/// x.foo(); +/// ``` +fn transform_impl_method<'tcx>( + tcx: TyCtxt<'tcx>, + mut instance: Instance<'tcx>, +) -> Option> { + let assoc = tcx.opt_associated_item(instance.def_id())?; + let AssocContainer::TraitImpl(Ok(method_id)) = assoc.container else { + return None; + }; + if !may_be_called_through_vtable(tcx, method_id) { + return None; + } + let impl_id = tcx.parent(instance.def_id()); + let trait_ref = tcx.instantiate_and_normalize_erasing_regions( + instance.args, + ty::TypingEnv::fully_monomorphized(), + tcx.impl_trait_ref(impl_id), ); - Ty::new_dynamic(tcx, preds, tcx.lifetimes.re_erased) + let self_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); + instance.def = ty::InstanceKind::Virtual(method_id, 0); + let abstract_args = tcx.mk_args_trait(self_ty, trait_ref.args.into_iter().skip(1)); + instance.args = instance.args.rebase_onto(tcx, impl_id, abstract_args); + Some(instance) } /// Transforms an instance for LLVM CFI and cross-language LLVM CFI support using Itanium C++ ABI @@ -294,228 +487,305 @@ fn trait_object_ty<'tcx>(tcx: TyCtxt<'tcx>, poly_trait_ref: ty::PolyTraitRef<'tc /// most as much information that would be available in the second call (i.e., during code /// generation at call sites); otherwise, the type ids would not match. /// -/// For this, it: +/// For this, it covers each InstanceKind (and ShimKind) explicitly, and either: /// -/// * Adjust the type ids of DropGlues (see below). -/// * Adjusts the type ids of VTableShims to the type id expected in the call sites for the -/// entry in the vtable (i.e., by using the signature of the closure passed as an argument to the -/// shim, or by just removing self). +/// * Performs type erasure for closures and coroutines by transforming self into a trait object +/// of the Fn, FnMut, FnOnce, Coroutine, Future, Iterator, or AsyncIterator trait that defines +/// the call method they are called through (see transform_closure_like). /// * Performs type erasure for calls on trait objects by transforming self into a trait object of -/// the trait that defines the method. -/// * Performs type erasure for closures call methods by transforming self into a trait object of -/// the Fn trait that defines the method (for being attached as a secondary type id). +/// the trait that defines the method, both on declaration/definition (see +/// transform_impl_method and transform_provided_method) and during code generation at call +/// sites (see transform_virtual_call). +/// * Adjusts the type ids of VTableShims to the type id expected in the call sites for the +/// entry in the vtable by transforming self into a trait object of the trait that defines the +/// method (see transform_vtable_shim). +/// * Adjusts the type ids of DropGlues to a synthesized Drop trait object (see +/// transform_drop_glue). +/// * Does not transform the instance (i.e., encodes type ids for the instance as is). /// #[instrument(level = "trace", skip(tcx))] pub(crate) fn transform_instance<'tcx>( tcx: TyCtxt<'tcx>, - mut instance: Instance<'tcx>, + instance: Instance<'tcx>, options: TransformTyOptions, ) -> Instance<'tcx> { - // FIXME: account for async-drop-glue - if (matches!(instance.def, ty::InstanceKind::Virtual(..)) - && tcx.is_lang_item(instance.def_id(), LangItem::DropGlue)) - || matches!(instance.def, ty::InstanceKind::Shim(ty::ShimKind::DropGlue(..))) - { - // Adjust the type ids of DropGlues + // If the USE_CONCRETE_SELF option is set, type erasure is not performed for the instances + // that may also be called directly (i.e., type ids are encoded for them as is). The + // USE_CONCRETE_SELF option is set for encoding methods as concrete types for being attached + // as secondary type ids (see rustc_codegen_llvm::declare::declare_fn), and for ReifyShims + // created for function pointers (i.e., ReifyReason::FnPtr) when KCFI is enabled (see + // kcfi::typeid_for_instance). Note that DropGlues, virtual method calls, and VTableShims are + // transformed regardless of this option (see below). + let erase_self = !options.contains(TransformTyOptions::USE_CONCRETE_SELF); + match instance.def { + // User-defined callable items (i.e., fn items, closures, and coroutines): + // + // * Closures and coroutines are called through the call methods of the Fn, FnMut, FnOnce, + // Coroutine, Future, Iterator, or AsyncIterator traits they implement, either on the + // concrete type or through a vtable, so type erasure is performed for them (see + // transform_closure_like). + // * Fn items that implement a trait method may be called through a vtable, so type + // erasure is also performed for them, both for trait method implementations in impl + // blocks (see transform_impl_method) and for provided (default) trait methods in trait + // blocks (see transform_provided_method). + // * Other fn items (i.e., free functions, inherent methods, and trait methods that can + // not be called through a vtable) are not transformed (i.e., type ids are encoded for + // them as is). + ty::InstanceKind::Item(..) => { + if erase_self { + transform_closure_like(tcx, instance) + .or_else(|| transform_impl_method(tcx, instance)) + .or_else(|| transform_provided_method(tcx, instance)) + .unwrap_or(instance) + } else { + instance + } + } + + // Intrinsic fn items (i.e., fn items with #[rustc_intrinsic]) and LLVM intrinsic fn items + // (i.e., fn items with extern "llvm-intrinsic"): intrinsics lowered by codegen do not have + // their own callable MIR and can not be reified or called indirectly, so they are not + // transformed (i.e., type ids are encoded for them as is). (Note that the fallback body of + // an intrinsic uses InstanceKind::Item, handled above.) + ty::InstanceKind::Intrinsic(..) | ty::InstanceKind::LlvmIntrinsic(..) => instance, + + // Virtual method calls (i.e., dynamic dispatch through the vtable): // - // DropGlues may have indirect calls to one or more given types drop function. Rust allows - // for types to be erased to any trait object and retains the drop function for the original - // type, which means at the indirect call sites in DropGlues, when typeid_for_fnabi is - // called a second time, it only has information after type erasure and it could be a call - // on any arbitrary trait object. Normalize them to a synthesized Drop trait object, both on - // declaration/definition, and during code generation at call sites so they have the same - // type id and match. + // * Virtual drop glue calls (i.e., the drop function entry in vtables) are normalized to + // a synthesized Drop trait object to match the DropGlues (see transform_drop_glue). + // * Other virtual method calls have self transformed into a trait object of the trait + // that defines the method to match the type erasure performed on + // declaration/definition (see transform_virtual_call). + ty::InstanceKind::Virtual(def_id, _) => { + if tcx.is_lang_item(def_id, LangItem::DropGlue) { + transform_drop_glue(tcx, instance) + } else { + transform_virtual_call(tcx, instance) + } + } + + // VTableShims (i.e., shims for trait methods that receive an unsizeable `self: Self`): + // have their type ids adjusted to the type id expected in the call sites for the entry in + // the vtable (see transform_vtable_shim). + ty::InstanceKind::Shim(ty::ShimKind::VTable(..)) => transform_vtable_shim(tcx, instance), + + // ReifyShims (i.e., fn pointers created for methods that can not be directly reified, + // such as virtual methods and methods with #[track_caller]): // - // FIXME(rcvalle): This allows a drop call on any trait object to call the drop function of - // any other type. + // * ReifyShims for trait method implementations (in impl blocks) may be called through a + // vtable, so type erasure is performed for them (see transform_impl_method). + // * ReifyShims for trait method definitions (e.g., fn pointers created for virtual + // calls) and for free functions are not transformed (i.e., type ids are encoded for + // them as is). // - let def_id = tcx - .lang_items() - .drop_trait() - .unwrap_or_else(|| bug!("typeid_for_instance: couldn't get drop_trait lang item")); - let predicate = ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::new_from_args( - tcx, - def_id, - ty::List::empty(), - )); - let predicates = tcx.mk_poly_existential_predicates(&[ty::Binder::dummy(predicate)]); - let self_ty = Ty::new_dynamic(tcx, predicates, tcx.lifetimes.re_erased); - instance.args = tcx.mk_args_trait(self_ty, List::empty()); - } else if let ty::InstanceKind::Virtual(def_id, _) = instance.def { - // Transform self into a trait object of the trait that defines the method for virtual - // functions to match the type erasure done below. - let upcast_ty = match tcx.trait_of_assoc(def_id) { - Some(trait_id) => trait_object_ty( - tcx, - ty::Binder::dummy(ty::TraitRef::from_assoc(tcx, trait_id, instance.args)), - ), - // drop_in_place won't have a defining trait, skip the upcast - None => instance.args.type_at(0), - }; - let ty::Dynamic(preds, lifetime) = upcast_ty.kind() else { - bug!("Tried to remove autotraits from non-dynamic type {upcast_ty}"); - }; - let self_ty = if preds.principal().is_some() { - let filtered_preds = - tcx.mk_poly_existential_predicates_from_iter(preds.into_iter().filter(|pred| { - !matches!(pred.skip_binder(), ty::ExistentialPredicate::AutoTrait(..)) - })); - Ty::new_dynamic(tcx, filtered_preds, *lifetime) - } else { - // If there's no principal type, re-encode it as a unit, since we don't know anything - // about it. This technically discards the knowledge that it was a type that was made - // into a trait object at some point, but that's not a lot. - tcx.types.unit - }; - instance.args = tcx.mk_args_trait(self_ty, instance.args.into_iter().skip(1)); - } else if let ty::InstanceKind::Shim(ty::ShimKind::VTable(def_id)) = instance.def - && let Some(trait_id) = tcx.trait_of_assoc(def_id) - { - // Adjust the type ids of VTableShims to the type id expected in the call sites for the - // entry in the vtable (i.e., by using the signature of the closure passed as an argument - // to the shim, or by just removing self). - let trait_ref = ty::TraitRef::new_from_args(tcx, trait_id, instance.args); - let invoke_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); - instance.args = tcx.mk_args_trait(invoke_ty, trait_ref.args.into_iter().skip(1)); - } + // Note: when KCFI is enabled, ReifyShims created for function pointers (i.e., + // ReifyReason::FnPtr) have the USE_CONCRETE_SELF option set (see + // kcfi::typeid_for_instance), so type erasure is not performed for them. + ty::InstanceKind::Shim(ty::ShimKind::Reify(..)) => { + if erase_self { + transform_impl_method(tcx, instance).unwrap_or(instance) + } else { + instance + } + } - if !options.contains(TransformTyOptions::USE_CONCRETE_SELF) { - // Perform type erasure for calls on trait objects by transforming self into a trait object - // of the trait that defines the method. - if let Some((trait_ref, method_id, ancestor)) = implemented_method(tcx, instance) { - // Trait methods will have a Self polymorphic parameter, where the concreteized - // implementation will not. We need to walk back to the more general trait method - let trait_ref = tcx.instantiate_and_normalize_erasing_regions( - instance.args, - ty::TypingEnv::fully_monomorphized(), - trait_ref, - ); - let invoke_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); - - // At the call site, any call to this concrete function through a vtable will be - // `Virtual(method_id, idx)` with appropriate arguments for the method. Since we have the - // original method id, and we've recovered the trait arguments, we can make the callee - // instance we're computing the alias set for match the caller instance. - // - // Right now, our code ignores the vtable index everywhere, so we use 0 as a placeholder. - // If we ever *do* start encoding the vtable index, we will need to generate an alias set - // based on which vtables we are putting this method into, as there will be more than one - // index value when supertraits are involved. - instance.def = ty::InstanceKind::Virtual(method_id, 0); - let abstract_trait_args = - tcx.mk_args_trait(invoke_ty, trait_ref.args.into_iter().skip(1)); - instance.args = instance.args.rebase_onto(tcx, ancestor, abstract_trait_args); - } else if tcx.is_closure_like(instance.def_id()) { - // We're either a closure or a coroutine. Our goal is to find the trait we're defined on, - // instantiate it, and take the type of its only method as our own. - let closure_ty = instance.ty(tcx, ty::TypingEnv::fully_monomorphized()); - let (trait_id, inputs) = match closure_ty.kind() { - ty::Closure(..) => { - let closure_args = instance.args.as_closure(); - let trait_id = tcx.fn_trait_kind_to_def_id(closure_args.kind()).unwrap(); - let tuple_args = - tcx.instantiate_bound_regions_with_erased(closure_args.sig()).inputs()[0]; - (trait_id, Some(tuple_args)) - } - ty::Coroutine(..) => match tcx.coroutine_kind(instance.def_id()).unwrap() { - hir::CoroutineKind::Coroutine(..) => ( - tcx.require_lang_item(LangItem::Coroutine, DUMMY_SP), - Some(instance.args.as_coroutine().resume_ty()), - ), - hir::CoroutineKind::Desugared(desugaring, _) => { - let lang_item = match desugaring { - hir::CoroutineDesugaring::Async => LangItem::Future, - hir::CoroutineDesugaring::AsyncGen => LangItem::AsyncIterator, - hir::CoroutineDesugaring::Gen => LangItem::Iterator, - }; - (tcx.require_lang_item(lang_item, DUMMY_SP), None) - } - }, - ty::CoroutineClosure(..) => ( - tcx.require_lang_item(LangItem::FnOnce, DUMMY_SP), - Some( - tcx.instantiate_bound_regions_with_erased( - instance.args.as_coroutine_closure().coroutine_closure_sig(), - ) - .tupled_inputs_ty, - ), - ), - x => bug!("Unexpected type kind for closure-like: {x:?}"), - }; - let concrete_args = tcx.mk_args_trait(closure_ty, inputs.map(Into::into)); - let trait_ref = ty::TraitRef::new_from_args(tcx, trait_id, concrete_args); - let invoke_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); - let abstract_args = tcx.mk_args_trait(invoke_ty, trait_ref.args.into_iter().skip(1)); - // There should be exactly one method on this trait, and it should be the one we're - // defining. - let call = tcx - .associated_items(trait_id) - .in_definition_order() - .find(|it| it.is_fn()) - .expect("No call-family function on closure-like Fn trait?") - .def_id; + // FnPtrShims (i.e., `::call_*`, the generated Fn, FnMut, and FnOnce + // trait implementations for fn pointers): may be called through a vtable (e.g., a + // `dyn Fn` trait object created from a fn pointer or fn item), so type erasure is + // performed for them (see transform_impl_method and transform_provided_method). + ty::InstanceKind::Shim(ty::ShimKind::FnPtr(..)) => { + if erase_self { + transform_impl_method(tcx, instance) + .or_else(|| transform_provided_method(tcx, instance)) + .unwrap_or(instance) + } else { + instance + } + } - instance.def = ty::InstanceKind::Virtual(call, 0); - instance.args = abstract_args; + // ClosureOnceShims (i.e., `<[FnMut/Fn closure] as FnOnce>::call_once`): + // `FnOnce::call_once` receives an unsizeable `self: Self`, so when it is called through a + // `dyn FnOnce` trait object, the entry in the vtable is a VTableShim (handled above), + // which calls the ClosureOnceShim directly. ClosureOnceShims can not be called through a + // vtable, so they are not transformed (i.e., type ids are encoded for them as is). + ty::InstanceKind::Shim(ty::ShimKind::ClosureOnce { .. }) => instance, + + // ConstructCoroutineInClosureShims (i.e., `<[FnMut/Fn coroutine-closure] as + // FnOnce>::call_once`, identified by the def id of the coroutine-closure): may be called + // through a vtable (e.g., a `dyn FnOnce` trait object created from a coroutine-closure), + // so type erasure is performed for them (see transform_closure_like). + ty::InstanceKind::Shim(ty::ShimKind::ConstructCoroutineInClosure { .. }) => { + if erase_self { + transform_closure_like(tcx, instance).unwrap_or(instance) + } else { + instance + } } - } - instance -} + // ThreadLocalShims (i.e., compiler-generated accessors for thread locals): can not be + // reified or called indirectly, so they are not transformed (i.e., type ids are encoded + // for them as is). + ty::InstanceKind::Shim(ty::ShimKind::ThreadLocal(..)) => instance, -fn default_or_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> Option { - match instance.def { - ty::InstanceKind::Item(def_id) | ty::InstanceKind::Shim(ty::ShimKind::FnPtr(def_id, _)) => { - tcx.opt_associated_item(def_id).map(|item| item.def_id) + // FutureDropPollShims (i.e., proxy poll functions for async drop of futures) and + // AsyncDropGlueShims (i.e., poll functions of the `async_drop_in_place::::{closure}` + // coroutines): identified by the def id of the `async_drop_in_place::::{closure}` + // coroutine, so type erasure is performed for them like other coroutines (i.e., self is + // transformed into a Future trait object) (see transform_closure_like). + // + // FIXME: account for async-drop-glue: similarly to DropGlues (see transform_drop_glue), + // at the indirect call sites in async drop glue the receiver may have been erased to + // any trait object, so async drop glue may need to be normalized to a synthesized + // trait object instead. + ty::InstanceKind::Shim( + ty::ShimKind::FutureDropPoll(..) | ty::ShimKind::AsyncDropGlue(..), + ) => { + if erase_self { + transform_closure_like(tcx, instance).unwrap_or(instance) + } else { + instance + } + } + + // DropGlues (i.e., `core::ptr::drop_glue::`): normalized to a synthesized Drop trait + // object (see transform_drop_glue). + ty::InstanceKind::Shim(ty::ShimKind::DropGlue(..)) => transform_drop_glue(tcx, instance), + + // CloneShims (i.e., compiler-generated `::clone` implementations for types + // with builtin Clone impls, such as arrays, tuples, and closures): the Clone trait is not + // dyn compatible, so they can not be called through a vtable and are not transformed + // (i.e., type ids are encoded for them as is). + ty::InstanceKind::Shim(ty::ShimKind::Clone(..)) => instance, + + // FnPtrAsPtrShims and FnPtrFromPtrShims (i.e., compiler-generated `::as_ptr` + // and `::from_ptr` implementations): the FnPtr trait is not dyn compatible, so + // they can not be called through a vtable and are not transformed (i.e., type ids are + // encoded for them as is). + ty::InstanceKind::Shim(ty::ShimKind::FnPtrAsPtr(..) | ty::ShimKind::FnPtrFromPtr(..)) => { + instance } - _ => None, + + // AsyncDropGlueCtorShims (i.e., `core::future::async_drop::async_drop_in_place::<'_, + // T>`, the constructors of the async drop glue coroutines): do not implement any trait + // method and are not closure-likes, so they are not transformed (i.e., type ids are + // encoded for them as is). + // + // FIXME: account for async-drop-glue (see the FutureDropPollShims and AsyncDropGlueShims + // above). + ty::InstanceKind::Shim(ty::ShimKind::AsyncDropGlueCtor(..)) => instance, } } -/// Determines if an instance represents a trait method implementation and returns the necessary -/// information for type erasure. +/// Performs type erasure for provided (default) trait methods in trait blocks and synthetic +/// FnPtrShims by transforming self into a trait object of the trait that defines the method, and +/// the instance into a virtual call to the trait method definition, to match the type erasure +/// performed during code generation at call sites (see transform_virtual_call). Returns None if +/// the instance is not a provided (default) trait method or a synthetic FnPtrShim that may be +/// called through a vtable. +/// +/// E.g.: +/// +/// ```ignore (illustrative) +/// trait Trait1 { +/// fn foo(&self) {} // ::foo is transformed into ::foo. +/// } +/// +/// struct Type1; +/// +/// impl Trait1 for Type1 {} /// -/// This function handles two main cases: +/// let x: &dyn Trait1 = &Type1; +/// x.foo(); +/// ``` /// -/// * **Implementation in an `impl` block**: When the instance represents a concrete implementation -/// of a trait method in an `impl` block, it extracts the trait reference, method ID, and trait -/// ID from the implementation. The method ID is obtained from the `trait_item_def_id` field of -/// the associated item, which points to the original trait method definition. +/// And for synthetic FnPtrShims: /// -/// * **Provided method in a `trait` block or synthetic `shim`**: When the instance represents a -/// default implementation provided in the trait definition itself or a synthetic shim, it uses -/// the instance's own `def_id` as the method ID and determines the trait ID from the associated -/// item. +/// ```ignore (illustrative) +/// fn foo(_: i32) {} /// -fn implemented_method<'tcx>( +/// let f: Box = Box::new(foo); +/// f(0); +/// // The ::call FnPtrShim is transformed into +/// // ::call. +/// ``` +fn transform_provided_method<'tcx>( tcx: TyCtxt<'tcx>, - instance: Instance<'tcx>, -) -> Option<(ty::EarlyBinder<'tcx, TraitRef<'tcx>>, DefId, DefId)> { - let trait_ref; - let method_id; - let trait_id; - let trait_method; + mut instance: Instance<'tcx>, +) -> Option> { let assoc = tcx.opt_associated_item(instance.def_id())?; - let ancestor = if let AssocContainer::TraitImpl(Ok(trait_method_id)) = assoc.container { - let impl_id = tcx.parent(instance.def_id()); - trait_ref = tcx.impl_trait_ref(impl_id); - method_id = trait_method_id; - trait_method = tcx.associated_item(method_id); - trait_id = trait_ref.skip_binder().def_id; - impl_id - } else if let AssocContainer::Trait = assoc.container - && let Some(trait_method_def_id) = default_or_shim(tcx, instance) - { - // Provided method in a `trait` block or a synthetic `shim` - trait_method = assoc; - method_id = trait_method_def_id; - trait_id = tcx.parent(method_id); - trait_ref = ty::EarlyBinder::bind(tcx, TraitRef::from_assoc(tcx, trait_id, instance.args)); - trait_id - } else { + let AssocContainer::Trait = assoc.container else { return None; }; - let vtable_possible = traits::is_vtable_safe_method(tcx, trait_id, trait_method) - && tcx.is_dyn_compatible(trait_id); - vtable_possible.then_some((trait_ref, method_id, ancestor)) + let method_id = assoc.def_id; + if !may_be_called_through_vtable(tcx, method_id) { + return None; + } + let trait_id = tcx.parent(method_id); + let trait_ref = tcx.normalize_erasing_regions( + ty::TypingEnv::fully_monomorphized(), + Unnormalized::new_wip(TraitRef::from_assoc(tcx, trait_id, instance.args)), + ); + let self_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); + instance.def = ty::InstanceKind::Virtual(method_id, 0); + let abstract_args = tcx.mk_args_trait(self_ty, trait_ref.args.into_iter().skip(1)); + instance.args = instance.args.rebase_onto(tcx, trait_id, abstract_args); + Some(instance) +} + +/// Performs type erasure for virtual method calls (i.e., calls to methods through trait objects) +/// by transforming self into a trait object of the trait that defines the method, to match the +/// type erasure performed on declaration/definition (see transform_impl_method, +/// transform_provided_method, and transform_closure_like). +/// +/// E.g.: +/// +/// ```ignore (illustrative) +/// trait Trait1 { +/// fn foo(&self); +/// } +/// +/// struct Type1; +/// +/// impl Trait1 for Type1 { +/// fn foo(&self) {} +/// } +/// +/// let x: &dyn Trait1 = &Type1; +/// x.foo(); // The virtual method call is transformed into ::foo. +/// ``` +fn transform_virtual_call<'tcx>(tcx: TyCtxt<'tcx>, mut instance: Instance<'tcx>) -> Instance<'tcx> { + // Virtual method calls are either drop glue calls (handled above) or calls to trait methods, + // so they always have a defining trait. + let trait_id = tcx.trait_of_assoc(instance.def_id()).unwrap_or_else(|| { + bug!("transform_virtual_call: couldn't get defining trait of `{:?}`", instance.def_id()) + }); + let trait_ref = ty::TraitRef::from_assoc(tcx, trait_id, instance.args); + let self_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); + instance.args = tcx.mk_args_trait(self_ty, instance.args.into_iter().skip(1)); + instance +} + +/// Adjusts the type ids of VTableShims to the type id expected in the call sites for the entry in +/// the vtable by transforming self into a trait object of the trait that defines the method, to +/// match the type erasure performed during code generation at call sites (see +/// transform_virtual_call). +/// +/// E.g.: +/// +/// ```ignore (illustrative) +/// let f: Box = Box::new(|| {}); +/// f(); +/// // ::call_once receives an unsizeable `self: Self`, so the +/// // VTableShim for it in the vtable is transformed into ::call_once. +/// ``` +fn transform_vtable_shim<'tcx>(tcx: TyCtxt<'tcx>, mut instance: Instance<'tcx>) -> Instance<'tcx> { + // VTableShims are only created for trait methods (see Instance::expect_resolve_for_vtable), + // so they always have a defining trait. + let trait_id = tcx.trait_of_assoc(instance.def_id()).unwrap_or_else(|| { + bug!("transform_vtable_shim: couldn't get defining trait of `{:?}`", instance.def_id()) + }); + let trait_ref = ty::TraitRef::new_from_args(tcx, trait_id, instance.args); + let self_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); + instance.args = tcx.mk_args_trait(self_ty, trait_ref.args.into_iter().skip(1)); + instance } diff --git a/src/tools/tidy/src/issues.txt b/src/tools/tidy/src/issues.txt index 9b878307afc96..cb8707ae710f5 100644 --- a/src/tools/tidy/src/issues.txt +++ b/src/tools/tidy/src/issues.txt @@ -2395,8 +2395,6 @@ ui/rust-2018/uniform-paths/issue-55779.rs ui/rust-2018/uniform-paths/issue-56596-2.rs ui/rust-2018/uniform-paths/issue-56596.rs ui/rust-2018/uniform-paths/issue-87932.rs -ui/sanitizer/issue-111184-cfi-coroutine-witness.rs -ui/sanitizer/issue-114275-cfi-const-expr-in-arry-len.rs ui/sanitizer/issue-72154-address-lifetime-markers.rs ui/self/issue-61882-2.rs ui/self/issue-61882.rs diff --git a/tests/ui/sanitizer/issue-114275-cfi-const-expr-in-arry-len.rs b/tests/ui/sanitizer/cfi/const-expr-in-array-len-issue-114275.rs similarity index 100% rename from tests/ui/sanitizer/issue-114275-cfi-const-expr-in-arry-len.rs rename to tests/ui/sanitizer/cfi/const-expr-in-array-len-issue-114275.rs diff --git a/tests/ui/sanitizer/issue-111184-cfi-coroutine-witness.rs b/tests/ui/sanitizer/cfi/coroutine-witness-issue-111184.rs similarity index 100% rename from tests/ui/sanitizer/issue-111184-cfi-coroutine-witness.rs rename to tests/ui/sanitizer/cfi/coroutine-witness-issue-111184.rs index ac2b95b639820..e47d3b7c608bf 100644 --- a/tests/ui/sanitizer/issue-111184-cfi-coroutine-witness.rs +++ b/tests/ui/sanitizer/cfi/coroutine-witness-issue-111184.rs @@ -6,8 +6,8 @@ //@ edition: 2021 //@ no-prefer-dynamic //@ only-x86_64-unknown-linux-gnu -//@ build-pass //@ ignore-backends: gcc +//@ build-pass use std::future::Future; diff --git a/tests/ui/sanitizer/cfi/can-reveal-opaques.rs b/tests/ui/sanitizer/cfi/reveal-opaques-issue-114160.rs similarity index 88% rename from tests/ui/sanitizer/cfi/can-reveal-opaques.rs rename to tests/ui/sanitizer/cfi/reveal-opaques-issue-114160.rs index 310ce04c55240..3d9e70b8d7149 100644 --- a/tests/ui/sanitizer/cfi/can-reveal-opaques.rs +++ b/tests/ui/sanitizer/cfi/reveal-opaques-issue-114160.rs @@ -1,3 +1,6 @@ +// Regression test for issue 114160, where the return type of the call could not be normalized +// with a user-facing param-env, causing an ICE. (See the comment in main for the details.) +// //@ needs-sanitizer-cfi //@ compile-flags: -Ccodegen-units=1 -Clto -Ctarget-feature=-crt-static -Zsanitizer=cfi -C unsafe-allow-abi-mismatch=sanitizer //@ no-prefer-dynamic @@ -5,8 +8,6 @@ //@ ignore-backends: gcc //@ build-pass -// See comment below for why this test exists. - trait Tr { type Projection; } diff --git a/tests/ui/sanitizer/split-lto-unit-requires-lto.rs b/tests/ui/sanitizer/cfi/split-lto-unit-requires-lto.rs similarity index 100% rename from tests/ui/sanitizer/split-lto-unit-requires-lto.rs rename to tests/ui/sanitizer/cfi/split-lto-unit-requires-lto.rs diff --git a/tests/ui/sanitizer/split-lto-unit-requires-lto.stderr b/tests/ui/sanitizer/cfi/split-lto-unit-requires-lto.stderr similarity index 100% rename from tests/ui/sanitizer/split-lto-unit-requires-lto.stderr rename to tests/ui/sanitizer/cfi/split-lto-unit-requires-lto.stderr diff --git a/tests/ui/sanitizer/kcfi-arity-requires-kcfi.rs b/tests/ui/sanitizer/kcfi/arity-requires-kcfi.rs similarity index 100% rename from tests/ui/sanitizer/kcfi-arity-requires-kcfi.rs rename to tests/ui/sanitizer/kcfi/arity-requires-kcfi.rs diff --git a/tests/ui/sanitizer/kcfi-arity-requires-kcfi.stderr b/tests/ui/sanitizer/kcfi/arity-requires-kcfi.stderr similarity index 100% rename from tests/ui/sanitizer/kcfi-arity-requires-kcfi.stderr rename to tests/ui/sanitizer/kcfi/arity-requires-kcfi.stderr