From b909380321c260deccccd5f4ba08b24885758295 Mon Sep 17 00:00:00 2001 From: Ramon de C Valle Date: Tue, 4 Aug 2026 14:05:04 -0700 Subject: [PATCH 1/3] CFI: Outline transformations and rename variables in transform.rs Moves the DropGlue, virtual call, VTableShim, and closure-like transformations in transform_instance into the transform_drop_glue, transform_virtual_call, transform_vtable_shim, and transform_closure_like functions, and renames variables for consistency (e.g., invoke_ty to self_ty). --- .../cfi/typeid/itanium_cxx_abi/transform.rs | 270 ++++++++++-------- 1 file changed, 150 insertions(+), 120 deletions(-) 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..8b9755bff4d5f 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 @@ -235,10 +235,10 @@ impl<'tcx> TypeFolder> for TransformTy<'tcx> { #[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| { + 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() @@ -269,11 +269,101 @@ fn trait_object_ty<'tcx>(tcx: TyCtxt<'tcx>, poly_trait_ref: ty::PolyTraitRef<'tc }) }) .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.into_iter()), ); - Ty::new_dynamic(tcx, preds, tcx.lifetimes.re_erased) + Ty::new_dynamic(tcx, predicates, tcx.lifetimes.re_erased) +} + +/// 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. +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, inputs) = match closure_like_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_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)); + // There should be exactly one method on this trait, and it should be the one we're + // defining. + let call_method_id = tcx + .associated_items(trait_id) + .in_definition_order() + .find(|item| item.is_fn()) + .expect("No call-family function on closure-like Fn trait?") + .def_id; + + instance.def = ty::InstanceKind::Virtual(call_method_id, 0); + instance.args = abstract_args; + Some(instance) +} + +/// Adjust the type ids of DropGlues +/// +/// 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. +/// +/// 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!("typeid_for_instance: 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 } /// Transforms an instance for LLVM CFI and cross-language LLVM CFI support using Itanium C++ ABI @@ -316,67 +406,13 @@ pub(crate) fn transform_instance<'tcx>( && tcx.is_lang_item(instance.def_id(), LangItem::DropGlue)) || matches!(instance.def, ty::InstanceKind::Shim(ty::ShimKind::DropGlue(..))) { - // Adjust the type ids of DropGlues - // - // 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. - // - // FIXME(rcvalle): This allows a drop call on any trait object to call the drop function of - // any other type. - // - 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) + instance = transform_drop_glue(tcx, instance); + } else if matches!(instance.def, ty::InstanceKind::Virtual(..)) { + instance = transform_virtual_call(tcx, instance); + } else if matches!(instance.def, ty::InstanceKind::Shim(ty::ShimKind::VTable(..))) + && let Some(transformed) = transform_vtable_shim(tcx, instance) { - // 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)); + instance = transformed; } if !options.contains(TransformTyOptions::USE_CONCRETE_SELF) { @@ -390,7 +426,7 @@ pub(crate) fn transform_instance<'tcx>( ty::TypingEnv::fully_monomorphized(), trait_ref, ); - let invoke_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); + let self_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 @@ -403,66 +439,60 @@ pub(crate) fn transform_instance<'tcx>( // 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)); + tcx.mk_args_trait(self_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; - - instance.def = ty::InstanceKind::Virtual(call, 0); - instance.args = abstract_args; + } else if let Some(transformed) = transform_closure_like(tcx, instance) { + instance = transformed; } } instance } +/// Transform self into a trait object of the trait that defines the method for virtual +/// functions to match the type erasure done below. +fn transform_virtual_call<'tcx>(tcx: TyCtxt<'tcx>, mut instance: Instance<'tcx>) -> Instance<'tcx> { + let upcast_ty = match tcx.trait_of_assoc(instance.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)); + instance +} + +/// 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). +fn transform_vtable_shim<'tcx>( + tcx: TyCtxt<'tcx>, + mut instance: Instance<'tcx>, +) -> Option> { + let trait_id = tcx.trait_of_assoc(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)); + Some(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, _)) => { From e1a5d2a9857c4b0acb2a6516f2a0f4fa77d8a5a2 Mon Sep 17 00:00:00 2001 From: Ramon de C Valle Date: Tue, 4 Aug 2026 14:05:04 -0700 Subject: [PATCH 2/3] CFI: Cover each InstanceKind explicitly in transform_instance Changes transform_instance to cover each InstanceKind (and ShimKind) explicitly, similarly to how encoding is done, so the intent is expressed clearly and it is known when an instance is handled (or not) and the side effects of it (also clearly), instead of relying on fallthrough behavior. This also makes adding a new InstanceKind (or ShimKind) result in a compile-time error until it is explicitly handled. --- .../cfi/typeid/itanium_cxx_abi/transform.rs | 614 ++++++++++++------ 1 file changed, 427 insertions(+), 187 deletions(-) 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 8b9755bff4d5f..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,9 +234,21 @@ 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()); + 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)) }); @@ -253,16 +267,14 @@ 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 }, ), ) }) @@ -271,13 +283,25 @@ fn trait_object_ty<'tcx>(tcx: TyCtxt<'tcx>, poly_trait_ref: ty::PolyTraitRef<'tc .collect(); 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.into_iter()), + iter::once(principal_predicate).chain(assoc_predicates), ); Ty::new_dynamic(tcx, predicates, tcx.lifetimes.re_erased) } -/// 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. +/// 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>, @@ -286,75 +310,108 @@ fn transform_closure_like<'tcx>( return None; } let closure_like_ty = instance.ty(tcx, ty::TypingEnv::fully_monomorphized()); - let (trait_id, inputs) = match closure_like_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 (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, Some(tuple_args)) + (trait_id, None, Some(tuple_args)) } - ty::Coroutine(..) => match tcx.coroutine_kind(instance.def_id()).unwrap() { + 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(instance.args.as_coroutine().resume_ty()), + Some(tcx.require_lang_item(LangItem::CoroutineResume, DUMMY_SP)), + Some(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, + 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(lang_item, DUMMY_SP), None) + ( + tcx.require_lang_item(trait_lang_item, DUMMY_SP), + Some(tcx.require_lang_item(call_method_lang_item, DUMMY_SP)), + None, + ) } }, - ty::CoroutineClosure(..) => ( + ty::CoroutineClosure(_, args) => ( tcx.require_lang_item(LangItem::FnOnce, DUMMY_SP), + None, Some( tcx.instantiate_bound_regions_with_erased( - instance.args.as_coroutine_closure().coroutine_closure_sig(), + args.as_coroutine_closure().coroutine_closure_sig(), ) .tupled_inputs_ty, ), ), - x => bug!("Unexpected type kind for closure-like: {x:?}"), + _ => 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)); - // There should be exactly one method on this trait, and it should be the one we're - // defining. - let call_method_id = tcx - .associated_items(trait_id) - .in_definition_order() - .find(|item| item.is_fn()) - .expect("No call-family function on closure-like Fn trait?") - .def_id; instance.def = ty::InstanceKind::Virtual(call_method_id, 0); instance.args = abstract_args; Some(instance) } -/// Adjust the type ids of DropGlues +/// 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.: /// -/// 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. +/// ```ignore (illustrative) +/// struct Type1; /// -/// FIXME(rcvalle): This allows a drop call on any trait object to call the drop function of -/// any other type. +/// 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!("typeid_for_instance: couldn't get drop_trait lang item")); + .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, @@ -366,6 +423,52 @@ fn transform_drop_glue<'tcx>(tcx: TyCtxt<'tcx>, mut instance: Instance<'tcx>) -> 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), + ); + 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 /// mangling. /// @@ -384,168 +487,305 @@ fn transform_drop_glue<'tcx>(tcx: TyCtxt<'tcx>, mut instance: Instance<'tcx>) -> /// 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(..))) - { - instance = transform_drop_glue(tcx, instance); - } else if matches!(instance.def, ty::InstanceKind::Virtual(..)) { - instance = transform_virtual_call(tcx, instance); - } else if matches!(instance.def, ty::InstanceKind::Shim(ty::ShimKind::VTable(..))) - && let Some(transformed) = transform_vtable_shim(tcx, instance) - { - instance = transformed; - } + // 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 + } + } - 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 self_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(self_ty, trait_ref.args.into_iter().skip(1)); - instance.args = instance.args.rebase_onto(tcx, ancestor, abstract_trait_args); - } else if let Some(transformed) = transform_closure_like(tcx, instance) { - instance = transformed; + // 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): + // + // * 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) + } } - } - 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]): + // + // * 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). + // + // 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 + } + } -/// Transform self into a trait object of the trait that defines the method for virtual -/// functions to match the type erasure done below. -fn transform_virtual_call<'tcx>(tcx: TyCtxt<'tcx>, mut instance: Instance<'tcx>) -> Instance<'tcx> { - let upcast_ty = match tcx.trait_of_assoc(instance.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)); - instance -} + // 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 + } + } -/// 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). -fn transform_vtable_shim<'tcx>( - tcx: TyCtxt<'tcx>, - mut instance: Instance<'tcx>, -) -> Option> { - let trait_id = tcx.trait_of_assoc(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)); - Some(instance) -} + // 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 + } + } -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) + // 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, + + // 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. +/// } /// -/// This function handles two main cases: +/// struct Type1; /// -/// * **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. +/// impl Trait1 for Type1 {} /// -/// * **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. +/// let x: &dyn Trait1 = &Type1; +/// x.foo(); +/// ``` /// -fn implemented_method<'tcx>( +/// And for synthetic FnPtrShims: +/// +/// ```ignore (illustrative) +/// fn foo(_: i32) {} +/// +/// 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 } From 62e0501f9cd377218e13544c721bf9eaf32fd5cc Mon Sep 17 00:00:00 2001 From: Ramon de C Valle Date: Tue, 4 Aug 2026 14:05:18 -0700 Subject: [PATCH 3/3] CFI: Move and rename the CFI and KCFI UI tests Moves the CFI and KCFI UI tests that are at the top level into the cfi and kcfi directories (removing the now redundant kcfi- prefix from their names), and renames the regression tests to -issue- (removing their entries from issues.txt accordingly, and adding the missing regression test description to reveal-opaques-issue-114160.rs). --- src/tools/tidy/src/issues.txt | 2 -- .../const-expr-in-array-len-issue-114275.rs} | 0 .../coroutine-witness-issue-111184.rs} | 2 +- ...{can-reveal-opaques.rs => reveal-opaques-issue-114160.rs} | 5 +++-- tests/ui/sanitizer/{ => cfi}/split-lto-unit-requires-lto.rs | 0 .../sanitizer/{ => cfi}/split-lto-unit-requires-lto.stderr | 0 .../arity-requires-kcfi.rs} | 0 .../arity-requires-kcfi.stderr} | 0 8 files changed, 4 insertions(+), 5 deletions(-) rename tests/ui/sanitizer/{issue-114275-cfi-const-expr-in-arry-len.rs => cfi/const-expr-in-array-len-issue-114275.rs} (100%) rename tests/ui/sanitizer/{issue-111184-cfi-coroutine-witness.rs => cfi/coroutine-witness-issue-111184.rs} (100%) rename tests/ui/sanitizer/cfi/{can-reveal-opaques.rs => reveal-opaques-issue-114160.rs} (88%) rename tests/ui/sanitizer/{ => cfi}/split-lto-unit-requires-lto.rs (100%) rename tests/ui/sanitizer/{ => cfi}/split-lto-unit-requires-lto.stderr (100%) rename tests/ui/sanitizer/{kcfi-arity-requires-kcfi.rs => kcfi/arity-requires-kcfi.rs} (100%) rename tests/ui/sanitizer/{kcfi-arity-requires-kcfi.stderr => kcfi/arity-requires-kcfi.stderr} (100%) 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