From 01ac1ca4def33108f143960a2e8ec872186d6e39 Mon Sep 17 00:00:00 2001 From: Ramon de C Valle Date: Tue, 4 Aug 2026 14:05:04 -0700 Subject: [PATCH 1/4] 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 6b3554331b420..afc7077b5c70f 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 @@ -234,10 +234,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() @@ -268,11 +268,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 @@ -315,67 +405,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) { @@ -389,7 +425,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 @@ -402,66 +438,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 c68ed8707649fea00bc5fc9a21ae8e24140d5b7f Mon Sep 17 00:00:00 2001 From: Ramon de C Valle Date: Tue, 4 Aug 2026 14:05:04 -0700 Subject: [PATCH 2/4] 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 | 570 ++++++++++++------ 1 file changed, 396 insertions(+), 174 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 afc7077b5c70f..7628486d4afc8 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 @@ -16,7 +16,7 @@ use rustc_middle::ty::{ use rustc_span::DUMMY_SP; use rustc_span::def_id::DefId; 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; @@ -74,7 +74,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.) @@ -99,8 +99,8 @@ 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. // // (See https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#isize-and-usize.) match t.kind() { @@ -161,7 +161,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; @@ -175,7 +175,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 { @@ -231,9 +231,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)) }); @@ -254,10 +266,6 @@ fn trait_object_ty<'tcx>(tcx: TyCtxt<'tcx>, poly_trait_ref: ty::PolyTraitRef<'tc ty::TypingEnv::fully_monomorphized(), Unnormalized::new_wip(projection_term.to_term(tcx, ty::IsRigid::No)), ); - debug!( - "Projection {:?} -> {term}", - projection_term.to_term(tcx, ty::IsRigid::No) - ); ty::ExistentialPredicate::Projection( ty::ExistentialProjection::erase_self_ty( tcx, @@ -270,13 +278,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,17 +306,25 @@ fn transform_closure_like<'tcx>( } 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(); + 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)) } - 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(args.as_coroutine().resume_ty()), ), hir::CoroutineKind::Desugared(desugaring, _) => { let lang_item = match desugaring { @@ -307,28 +335,29 @@ fn transform_closure_like<'tcx>( (tcx.require_lang_item(lang_item, DUMMY_SP), None) } }, - ty::CoroutineClosure(..) => ( + ty::CoroutineClosure(_, args) => ( tcx.require_lang_item(LangItem::FnOnce, DUMMY_SP), 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 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. + // There should be exactly one method on this trait, and it should be the one being defined. 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?") + .unwrap_or_else(|| { + bug!("transform_closure_like: couldn't get call method of `{:?}`", trait_id) + }) .def_id; instance.def = ty::InstanceKind::Virtual(call_method_id, 0); @@ -336,24 +365,38 @@ fn transform_closure_like<'tcx>( 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, @@ -365,6 +408,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. /// @@ -383,168 +472,301 @@ 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 "unadjusted"): intrinsics do not have their own callable MIR + // (i.e., calls to them are lowered by codegen) and can not be reified or called + // indirectly, so they are not transformed (i.e., type ids are encoded for them as is). + 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): do not + // implement any trait method and 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::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 + } } - _ => None, + + // 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, + + // FnPtrAddrShims (i.e., compiler-generated `::addr` 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::FnPtrAddr(..)) => instance, + + // 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; /// -/// This function handles two main cases: +/// impl Trait1 for 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. +/// let x: &dyn Trait1 = &Type1; +/// x.foo(); +/// ``` /// -/// * **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. +/// And for synthetic FnPtrShims: /// -fn implemented_method<'tcx>( +/// ```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 ab902b11f56b9c2a010d6d0a35cc7a969a0e00f6 Mon Sep 17 00:00:00 2001 From: Ramon de C Valle Date: Tue, 4 Aug 2026 14:05:18 -0700 Subject: [PATCH 3/4] 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 c15bc3af026e5..f22186dbd843f 100644 --- a/src/tools/tidy/src/issues.txt +++ b/src/tools/tidy/src/issues.txt @@ -2397,8 +2397,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 From 72c6221e48c71ef8440235bb19dabe495fe02ead Mon Sep 17 00:00:00 2001 From: Ramon de C Valle Date: Tue, 4 Aug 2026 14:07:43 -0700 Subject: [PATCH 4/4] CFI: Reorganize the CFI and KCFI UI tests Reorganizes the CFI and KCFI UI tests under the cfi and kcfi directories similarly to how the CFI codegen tests are organized: each test now tests a type or a language construct or feature that is handled by the CFI and KCFI transform or encoding, with complete coverage of what can be exercised at run time, and is named works-with-, and tests that covered both CFI and KCFI using revisions are split into separate tests. It also adds tests for the types, language constructs, and features that were not covered (i.e., intrinsics, thread locals, builtin Clone and FnPtr implementations, pattern types, the never type, extern types, C variadics, and the generalize-pointers and normalize-integers options), removes the tests that became redundant, and changes the drop tests to run-pass, as they now pass at run time. Tests for flags/options and the regression tests are unchanged (moved and renamed in the previous commit). --- tests/ui/sanitizer/cfi/async-closures.rs | 33 ----- tests/ui/sanitizer/cfi/closures.rs | 91 ------------ tests/ui/sanitizer/cfi/complex-receiver.rs | 48 ------- tests/ui/sanitizer/cfi/const-generics.rs | 110 --------------- tests/ui/sanitizer/cfi/coroutine.rs | 68 --------- tests/ui/sanitizer/cfi/drop-in-place.rs | 22 --- tests/ui/sanitizer/cfi/drop-no-principal.rs | 22 --- .../ui/sanitizer/cfi/fn-ptr-type-mismatch.rs | 41 ------ tests/ui/sanitizer/cfi/fn-ptr.rs | 63 --------- tests/ui/sanitizer/cfi/fn-trait-objects.rs | 32 ----- tests/ui/sanitizer/cfi/self-ref.rs | 39 ------ tests/ui/sanitizer/cfi/sized-associated-ty.rs | 39 ------ tests/ui/sanitizer/cfi/supertraits.rs | 79 ----------- .../sanitizer/cfi/transparent-has-regions.rs | 19 --- tests/ui/sanitizer/cfi/virtual-auto.rs | 28 ---- .../cfi/works-with-associated-types.rs | 53 +++++++ .../cfi/works-with-async-closures.rs | 39 ++++++ .../sanitizer/cfi/works-with-auto-traits.rs | 31 +++++ .../ui/sanitizer/cfi/works-with-c-variadic.rs | 26 ++++ .../sanitizer/cfi/works-with-cfi-encoding.rs | 46 +++++++ tests/ui/sanitizer/cfi/works-with-clone.rs | 21 +++ tests/ui/sanitizer/cfi/works-with-closures.rs | 89 ++++++++++++ .../cfi/works-with-const-generics.rs | 129 ++++++++++++++++++ .../ui/sanitizer/cfi/works-with-coroutines.rs | 95 +++++++++++++ .../sanitizer/cfi/works-with-drop-in-place.rs | 27 ++++ .../sanitizer/cfi/works-with-fn-ptr-addr.rs | 23 ++++ .../sanitizer/cfi/works-with-fn-ptr-casts.rs | 70 ++++++++++ .../cfi/works-with-fn-trait-objects.rs | 42 ++++++ .../cfi/works-with-function-types.rs | 41 ++++++ .../cfi/works-with-generalized-pointers.rs | 53 +++++++ .../ui/sanitizer/cfi/works-with-intrinsics.rs | 28 ++++ .../ui/sanitizer/cfi/works-with-lifetimes.rs | 47 +++++++ .../cfi/works-with-normalized-integers.rs | 58 ++++++++ .../sanitizer/cfi/works-with-pattern-types.rs | 41 ++++++ .../sanitizer/cfi/works-with-pointer-types.rs | 51 +++++++ .../cfi/works-with-primitive-types.rs | 116 ++++++++++++++++ .../ui/sanitizer/cfi/works-with-receivers.rs | 58 ++++++++ .../cfi/works-with-repr-transparent-types.rs | 92 +++++++++++++ .../cfi/works-with-sequence-types.rs | 47 +++++++ .../sanitizer/cfi/works-with-supertraits.rs | 89 ++++++++++++ .../sanitizer/cfi/works-with-thread-locals.rs | 22 +++ .../sanitizer/cfi/works-with-trait-objects.rs | 67 +++++++++ .../sanitizer/cfi/works-with-trait-types.rs | 52 +++++++ .../cfi/works-with-user-defined-types.rs | 85 ++++++++++++ tests/ui/sanitizer/kcfi-c-variadic.rs | 18 --- tests/ui/sanitizer/kcfi-mangling.rs | 31 ----- tests/ui/sanitizer/kcfi/const-generics.rs | 109 --------------- tests/ui/sanitizer/kcfi/fn-trait-objects.rs | 32 ----- .../kcfi/works-with-associated-types.rs | 52 +++++++ .../kcfi/works-with-async-closures.rs | 38 ++++++ .../sanitizer/kcfi/works-with-auto-traits.rs | 30 ++++ .../sanitizer/kcfi/works-with-c-variadic.rs | 25 ++++ .../sanitizer/kcfi/works-with-cfi-encoding.rs | 45 ++++++ tests/ui/sanitizer/kcfi/works-with-clone.rs | 20 +++ .../ui/sanitizer/kcfi/works-with-closures.rs | 88 ++++++++++++ .../kcfi/works-with-const-generics.rs | 128 +++++++++++++++++ .../sanitizer/kcfi/works-with-coroutines.rs | 94 +++++++++++++ .../kcfi/works-with-drop-in-place.rs | 26 ++++ .../sanitizer/kcfi/works-with-fn-ptr-addr.rs | 22 +++ .../sanitizer/kcfi/works-with-fn-ptr-casts.rs | 69 ++++++++++ .../kcfi/works-with-fn-trait-objects.rs | 41 ++++++ .../kcfi/works-with-function-types.rs | 40 ++++++ .../kcfi/works-with-generalized-pointers.rs | 52 +++++++ .../sanitizer/kcfi/works-with-intrinsics.rs | 27 ++++ .../ui/sanitizer/kcfi/works-with-lifetimes.rs | 46 +++++++ .../kcfi/works-with-normalized-integers.rs | 57 ++++++++ .../kcfi/works-with-pattern-types.rs | 40 ++++++ .../kcfi/works-with-pointer-types.rs | 50 +++++++ .../kcfi/works-with-primitive-types.rs | 115 ++++++++++++++++ .../ui/sanitizer/kcfi/works-with-receivers.rs | 57 ++++++++ .../kcfi/works-with-repr-transparent-types.rs | 91 ++++++++++++ .../kcfi/works-with-sequence-types.rs | 46 +++++++ .../sanitizer/kcfi/works-with-supertraits.rs | 88 ++++++++++++ .../kcfi/works-with-symbol-mangling-v0.rs | 36 +++++ .../kcfi/works-with-thread-locals.rs | 21 +++ .../kcfi/works-with-trait-objects.rs | 66 +++++++++ .../sanitizer/kcfi/works-with-trait-types.rs | 51 +++++++ .../kcfi/works-with-user-defined-types.rs | 84 ++++++++++++ 78 files changed, 3283 insertions(+), 924 deletions(-) delete mode 100644 tests/ui/sanitizer/cfi/async-closures.rs delete mode 100644 tests/ui/sanitizer/cfi/closures.rs delete mode 100644 tests/ui/sanitizer/cfi/complex-receiver.rs delete mode 100644 tests/ui/sanitizer/cfi/const-generics.rs delete mode 100644 tests/ui/sanitizer/cfi/coroutine.rs delete mode 100644 tests/ui/sanitizer/cfi/drop-in-place.rs delete mode 100644 tests/ui/sanitizer/cfi/drop-no-principal.rs delete mode 100644 tests/ui/sanitizer/cfi/fn-ptr-type-mismatch.rs delete mode 100644 tests/ui/sanitizer/cfi/fn-ptr.rs delete mode 100644 tests/ui/sanitizer/cfi/fn-trait-objects.rs delete mode 100644 tests/ui/sanitizer/cfi/self-ref.rs delete mode 100644 tests/ui/sanitizer/cfi/sized-associated-ty.rs delete mode 100644 tests/ui/sanitizer/cfi/supertraits.rs delete mode 100644 tests/ui/sanitizer/cfi/transparent-has-regions.rs delete mode 100644 tests/ui/sanitizer/cfi/virtual-auto.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-associated-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-async-closures.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-auto-traits.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-c-variadic.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-cfi-encoding.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-clone.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-closures.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-const-generics.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-coroutines.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-drop-in-place.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-fn-ptr-addr.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-fn-ptr-casts.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-fn-trait-objects.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-function-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-generalized-pointers.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-intrinsics.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-lifetimes.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-normalized-integers.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-pattern-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-pointer-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-primitive-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-receivers.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-repr-transparent-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-sequence-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-supertraits.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-thread-locals.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-trait-objects.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-trait-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-user-defined-types.rs delete mode 100644 tests/ui/sanitizer/kcfi-c-variadic.rs delete mode 100644 tests/ui/sanitizer/kcfi-mangling.rs delete mode 100644 tests/ui/sanitizer/kcfi/const-generics.rs delete mode 100644 tests/ui/sanitizer/kcfi/fn-trait-objects.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-associated-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-async-closures.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-auto-traits.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-c-variadic.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-cfi-encoding.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-clone.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-closures.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-const-generics.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-coroutines.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-drop-in-place.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-fn-ptr-addr.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-fn-ptr-casts.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-fn-trait-objects.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-function-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-generalized-pointers.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-intrinsics.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-lifetimes.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-normalized-integers.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-pattern-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-pointer-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-primitive-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-receivers.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-repr-transparent-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-sequence-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-supertraits.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-symbol-mangling-v0.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-thread-locals.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-trait-objects.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-trait-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-user-defined-types.rs diff --git a/tests/ui/sanitizer/cfi/async-closures.rs b/tests/ui/sanitizer/cfi/async-closures.rs deleted file mode 100644 index 621a0882c91b2..0000000000000 --- a/tests/ui/sanitizer/cfi/async-closures.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Check various forms of dynamic closure calls - -//@ edition: 2021 -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -Z panic-abort-tests -C prefer-dynamic=off -//@ run-pass - -#![feature(async_fn_traits)] - -use std::ops::AsyncFn; - -#[inline(never)] -fn identity(x: T) -> T { x } - -// We can't actually create a `dyn AsyncFn()`, because it's dyn-incompatible, but we should check -// that we don't bug out when we encounter one. - -fn main() { - let f = identity(async || ()); - let _ = f.async_call(()); - let _ = f(); - let g: Box _> = Box::new(f) as _; - let _ = g(); -} diff --git a/tests/ui/sanitizer/cfi/closures.rs b/tests/ui/sanitizer/cfi/closures.rs deleted file mode 100644 index 7493dba4928b0..0000000000000 --- a/tests/ui/sanitizer/cfi/closures.rs +++ /dev/null @@ -1,91 +0,0 @@ -// Check various forms of dynamic closure calls - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -Z panic-abort-tests -C prefer-dynamic=off -//@ compile-flags: --test -//@ run-pass - -#![feature(fn_traits)] -#![feature(unboxed_closures)] - -fn foo<'a, T>() -> Box &'a T> { - Box::new(|x| x) -} - -#[test] -fn dyn_fn_with_params() { - let x = 3; - let f = foo(); - f(&x); - // FIXME remove once drops are working. - std::mem::forget(f); -} - -#[test] -fn call_fn_trait() { - let f: &dyn Fn() = &(|| {}) as _; - f.call(()); -} - -#[test] -fn fn_ptr_cast() { - let f: &fn() = &((|| ()) as _); - f(); -} - -fn use_fnmut(mut f: F) { - f() -} - -#[test] -fn fn_to_fnmut() { - let f: &dyn Fn() = &(|| {}) as _; - use_fnmut(f); -} - -fn hrtb_helper(f: &dyn for<'a> Fn(&'a usize)) { - f(&10) -} - -#[test] -fn hrtb_fn() { - hrtb_helper((&|x: &usize| println!("{}", *x)) as _) -} - -#[test] -fn fnonce() { - let f: Box = Box::new(|| {}) as _; - f(); -} - -fn use_closure(call: extern "rust-call" fn(&C, ()) -> i32, f: &C) -> i32 { - call(f, ()) -} - -#[test] -fn closure_addr_taken() { - let x = 3i32; - let f = || x; - let call = Fn::<()>::call; - use_closure(call, &f); -} - -fn use_closure_once(call: extern "rust-call" fn(C, ()) -> i32, f: C) -> i32 { - call(f, ()) -} - -#[test] -fn closure_once_addr_taken() { - let g = || 3; - let call2 = FnOnce::<()>::call_once; - use_closure_once(call2, g); -} diff --git a/tests/ui/sanitizer/cfi/complex-receiver.rs b/tests/ui/sanitizer/cfi/complex-receiver.rs deleted file mode 100644 index adacc0d6c5df7..0000000000000 --- a/tests/ui/sanitizer/cfi/complex-receiver.rs +++ /dev/null @@ -1,48 +0,0 @@ -// Check that more complex receivers work: -// * Arc as for custom receivers -// * &dyn Bar for type constraints - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -C prefer-dynamic=off -//@ run-pass - -use std::sync::Arc; - -trait Foo { - fn foo(self: Arc); -} - -struct FooImpl; - -impl Foo for FooImpl { - fn foo(self: Arc) {} -} - -trait Bar { - type T; - fn bar(&self) -> Self::T; -} - -struct BarImpl; - -impl Bar for BarImpl { - type T = i32; - fn bar(&self) -> Self::T { 7 } -} - -fn main() { - let foo: Arc = Arc::new(FooImpl); - foo.foo(); - - let bar: &dyn Bar = &BarImpl; - assert_eq!(bar.bar(), 7); -} diff --git a/tests/ui/sanitizer/cfi/const-generics.rs b/tests/ui/sanitizer/cfi/const-generics.rs deleted file mode 100644 index 42fff233dd84b..0000000000000 --- a/tests/ui/sanitizer/cfi/const-generics.rs +++ /dev/null @@ -1,110 +0,0 @@ -// Verifies that functions with types with const generics as argument types can -// be called through function pointers. -// -//@ needs-sanitizer-cfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer -//@ run-pass - -#![feature(adt_const_params)] -#![feature(unsized_const_params)] -#![allow(incomplete_features)] - -use std::marker::ConstParamTy; - -#[derive(PartialEq, Eq, ConstParamTy)] -struct Struct2 { - x: u16, - y: u16, -} - -#[derive(PartialEq, Eq, ConstParamTy)] -enum Enum1 { - Variant1, - Variant2(u8), -} - -struct Struct1([i32; N]); -struct BoolHolder(bool); -struct IntHolder(i32); -struct CharHolder(char); -struct StrHolder(&'static str); -struct StructHolder(Struct2); -struct EnumHolder(Enum1); -struct ArrayHolder([u16; 2]); -struct TupleHolder((u16, bool)); - -fn foo1(x: Struct1<2>) { - assert_eq!(x.0, [1, 2]); -} - -fn foo2(x: &Struct1<4>) { - assert_eq!(x.0, [1, 2, 3, 4]); -} - -fn foo3(x: BoolHolder) { - assert!(x.0); -} - -fn foo4(x: IntHolder<-1>) { - assert_eq!(x.0, -1); -} - -fn foo5(x: CharHolder<'x'>) { - assert_eq!(x.0, 'x'); -} - -fn foo6(x: StrHolder<"hello">) { - assert_eq!(x.0, "hello"); -} - -fn foo7(x: StructHolder<{ Struct2 { x: 1, y: 2 } }>) { - assert_eq!(x.0.x, 1); - assert_eq!(x.0.y, 2); -} - -fn foo8(x: EnumHolder<{ Enum1::Variant1 }>) { - assert!(matches!(x.0, Enum1::Variant1)); -} - -fn foo9(x: EnumHolder<{ Enum1::Variant2(5) }>) { - match x.0 { - Enum1::Variant1 => unreachable!(), - Enum1::Variant2(v) => assert_eq!(v, 5), - } -} - -fn foo10(x: ArrayHolder<{ [3, 4] }>) { - assert_eq!(x.0, [3, 4]); -} - -fn foo11(x: TupleHolder<{ (6, true) }>) { - assert_eq!(x.0, (6, true)); -} - -fn main() { - let f: fn(Struct1<2>) = foo1; - f(Struct1([1, 2])); - let f: fn(&Struct1<4>) = foo2; - f(&Struct1([1, 2, 3, 4])); - let f: fn(BoolHolder) = foo3; - f(BoolHolder(true)); - let f: fn(IntHolder<-1>) = foo4; - f(IntHolder(-1)); - let f: fn(CharHolder<'x'>) = foo5; - f(CharHolder('x')); - let f: fn(StrHolder<"hello">) = foo6; - f(StrHolder("hello")); - let f: fn(StructHolder<{ Struct2 { x: 1, y: 2 } }>) = foo7; - f(StructHolder(Struct2 { x: 1, y: 2 })); - let f: fn(EnumHolder<{ Enum1::Variant1 }>) = foo8; - f(EnumHolder(Enum1::Variant1)); - let f: fn(EnumHolder<{ Enum1::Variant2(5) }>) = foo9; - f(EnumHolder(Enum1::Variant2(5))); - let f: fn(ArrayHolder<{ [3, 4] }>) = foo10; - f(ArrayHolder([3, 4])); - let f: fn(TupleHolder<{ (6, true) }>) = foo11; - f(TupleHolder((6, true))); -} diff --git a/tests/ui/sanitizer/cfi/coroutine.rs b/tests/ui/sanitizer/cfi/coroutine.rs deleted file mode 100644 index d85615b597de2..0000000000000 --- a/tests/ui/sanitizer/cfi/coroutine.rs +++ /dev/null @@ -1,68 +0,0 @@ -// Verifies that we can call dynamic coroutines - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ edition: 2024 -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -//@ compile-flags: -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -Z panic-abort-tests -C prefer-dynamic=off -//@ compile-flags: --test -//@ run-pass - -#![feature(coroutines, stmt_expr_attributes)] -#![feature(coroutine_trait)] -#![feature(gen_blocks)] -#![feature(async_iterator)] - -use std::ops::{Coroutine, CoroutineState}; -use std::pin::{pin, Pin}; -use std::task::{Context, Poll, Waker}; -use std::async_iter::AsyncIterator; - -#[test] -fn general_coroutine() { - let coro = #[coroutine] |x: i32| { - yield x; - "done" - }; - let mut abstract_coro: Pin<&mut dyn Coroutine> = pin!(coro); - assert_eq!(abstract_coro.as_mut().resume(2), CoroutineState::Yielded(2)); - assert_eq!(abstract_coro.as_mut().resume(0), CoroutineState::Complete("done")); -} - -async fn async_fn() {} - -#[test] -fn async_coroutine() { - let f: fn() -> Pin>> = || Box::pin(async_fn()); - let _ = async { f().await; }; - assert_eq!(f().as_mut().poll(&mut Context::from_waker(Waker::noop())), Poll::Ready(())); -} - -async gen fn async_gen_fn() -> u8 { - yield 5; -} - -#[test] -fn async_gen_coroutine() { - let f: fn() -> Pin>> = || Box::pin(async_gen_fn()); - assert_eq!(f().as_mut().poll_next(&mut Context::from_waker(Waker::noop())), - Poll::Ready(Some(5))); -} - -gen fn gen_fn() -> u8 { - yield 6; -} - -#[test] -fn gen_coroutine() { - let f: fn() -> Box> = || Box::new(gen_fn()); - assert_eq!(f().next(), Some(6)); -} diff --git a/tests/ui/sanitizer/cfi/drop-in-place.rs b/tests/ui/sanitizer/cfi/drop-in-place.rs deleted file mode 100644 index fe59d54631248..0000000000000 --- a/tests/ui/sanitizer/cfi/drop-in-place.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Verifies that drops can be called on arbitrary trait objects. -// -// FIXME(#122848): Remove only-linux when fixed. -//@ only-linux -//@ ignore-backends: gcc -//@ needs-sanitizer-cfi -//@ compile-flags: -Clto -Copt-level=0 -Cprefer-dynamic=off -Ctarget-feature=-crt-static -Zsanitizer=cfi -//@ compile-flags: -C unsafe-allow-abi-mismatch=sanitizer -//@ run-pass - -struct EmptyDrop; - -struct NonEmptyDrop; - -impl Drop for NonEmptyDrop { - fn drop(&mut self) {} -} - -fn main() { - let _ = Box::new(EmptyDrop) as Box; - let _ = Box::new(NonEmptyDrop) as Box; -} diff --git a/tests/ui/sanitizer/cfi/drop-no-principal.rs b/tests/ui/sanitizer/cfi/drop-no-principal.rs deleted file mode 100644 index 4fb905eb51d05..0000000000000 --- a/tests/ui/sanitizer/cfi/drop-no-principal.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Check that dropping a trait object without a principal trait succeeds - -//@ needs-sanitizer-cfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries works -//@ only-linux -//@ ignore-backends: gcc -//@ compile-flags: --crate-type=bin -Cprefer-dynamic=off -Clto -Zsanitizer=cfi -C unsafe-allow-abi-mismatch=sanitizer -//@ compile-flags: -C target-feature=-crt-static -C codegen-units=1 -C opt-level=0 -// FIXME(#118761) Should be run-pass once the labels on drop are compatible. -// This test is being landed ahead of that to test that the compiler doesn't ICE while labeling the -// callsite for a drop, but the vtable doesn't have the correct label yet. -//@ build-pass - -struct CustomDrop; - -impl Drop for CustomDrop { - fn drop(&mut self) {} -} - -fn main() { - let _ = Box::new(CustomDrop) as Box; -} diff --git a/tests/ui/sanitizer/cfi/fn-ptr-type-mismatch.rs b/tests/ui/sanitizer/cfi/fn-ptr-type-mismatch.rs deleted file mode 100644 index 6fe7eec7c6920..0000000000000 --- a/tests/ui/sanitizer/cfi/fn-ptr-type-mismatch.rs +++ /dev/null @@ -1,41 +0,0 @@ -// Verifies that calling a function pointer with a mismatched type triggers a -// CFI violation and causes the process to trap. - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [cfi] needs-sanitizer-support -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -//@ compile-flags: -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C opt-level=0 -C codegen-units=1 -C lto -//@ [cfi] compile-flags: -C prefer-dynamic=off -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [cfi] compile-flags: -Z sanitizer-cfi-diag=true -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -C prefer-dynamic=off -//@ run-fail-or-crash - -use std::hint::black_box; -use std::mem; - -fn add_one(x: i32) -> i32 { - x + 1 -} - -// Accept a function pointer as a parameter so that the indirect call cannot -// be devirtualized by the compiler. -#[inline(never)] -fn call_with_mismatch(f: fn(i32) -> i32) { - // Transmute fn(i32) -> i32 into fn(i32, i32) -> i32, creating a - // function pointer type mismatch that CFI should catch. - let g: fn(i32, i32) -> i32 = unsafe { mem::transmute(f) }; - // This indirect call should fail the CFI type check and trap. - let _result = g(1, 2); -} - -fn main() { - call_with_mismatch(black_box(add_one)); -} diff --git a/tests/ui/sanitizer/cfi/fn-ptr.rs b/tests/ui/sanitizer/cfi/fn-ptr.rs deleted file mode 100644 index bdb8c7ceb328c..0000000000000 --- a/tests/ui/sanitizer/cfi/fn-ptr.rs +++ /dev/null @@ -1,63 +0,0 @@ -// Verifies that casting to a function pointer works. - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -//@ compile-flags: -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C opt-level=0 -C codegen-units=1 -C lto -//@ [cfi] compile-flags: -C prefer-dynamic=off -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -C prefer-dynamic=off -//@ run-pass - -trait Foo { - fn foo(&self); - fn bar(&self); -} - -struct S; - -impl Foo for S { - fn foo(&self) {} - #[track_caller] - fn bar(&self) {} -} - -struct S2 { - f: fn(&S) -} - -impl S2 { - fn foo(&self, s: &S) { - (self.f)(s) - } -} - -trait Trait1 { - fn foo(&self); -} - -struct Type1; - -impl Trait1 for Type1 { - fn foo(&self) {} -} - -fn foo(_: &T) {} - -fn main() { - let type1 = Type1 {}; - let f = ::foo; - f(&type1); - // Check again with different optimization barriers - S2 { f: ::foo }.foo(&S); - // Check mismatched #[track_caller] - S2 { f: ::bar }.foo(&S); - // Check non-method functions - S2 { f: foo }.foo(&S) -} diff --git a/tests/ui/sanitizer/cfi/fn-trait-objects.rs b/tests/ui/sanitizer/cfi/fn-trait-objects.rs deleted file mode 100644 index 977d4124fff0c..0000000000000 --- a/tests/ui/sanitizer/cfi/fn-trait-objects.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Verifies that types that implement the Fn, FnMut, or FnOnce traits can be -// called through their trait methods. -// -//@ needs-sanitizer-cfi -//@ only-linux -//@ ignore-backends: gcc -//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer --test -//@ run-pass - -#![feature(fn_traits)] -#![feature(unboxed_closures)] - -fn foo(_a: u32) {} - -#[test] -fn test_fn_trait() { - let f: Box = Box::new(foo); - Fn::call(&f, (0,)); -} - -#[test] -fn test_fnmut_trait() { - let mut a = 0; - let mut f: Box = Box::new(|x| a += x); - FnMut::call_mut(&mut f, (1,)); -} - -#[test] -fn test_fnonce_trait() { - let f: Box = Box::new(foo); - FnOnce::call_once(f, (2,)); -} diff --git a/tests/ui/sanitizer/cfi/self-ref.rs b/tests/ui/sanitizer/cfi/self-ref.rs deleted file mode 100644 index 827610a261064..0000000000000 --- a/tests/ui/sanitizer/cfi/self-ref.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Check that encoding self-referential types works with #[repr(transparent)] - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -C prefer-dynamic=off -//@ run-pass - -use std::marker::PhantomData; - -struct X { - _x: u8, - p: PhantomData, -} - -#[repr(transparent)] -struct Y(X); - -trait Fooable { - fn foo(&self, y: Y); -} - -struct Bar; - -impl Fooable for Bar { - fn foo(&self, _: Y) {} -} - -fn main() { - let x = &Bar as &dyn Fooable; - x.foo(Y(X {_x: 0, p: PhantomData})); -} diff --git a/tests/ui/sanitizer/cfi/sized-associated-ty.rs b/tests/ui/sanitizer/cfi/sized-associated-ty.rs deleted file mode 100644 index da8c385c6fc8b..0000000000000 --- a/tests/ui/sanitizer/cfi/sized-associated-ty.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Check that we only elaborate non-`Self: Sized` associated types when -// erasing the receiver from trait ref. - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -C prefer-dynamic=off -//@ run-pass - -trait Foo { - type Bar<'a> - where - Self: Sized; - - fn test(&self); -} - -impl Foo for () { - type Bar<'a> = () - where - Self: Sized; - - fn test(&self) {} -} - -fn test(x: &dyn Foo) { - x.test(); -} - -fn main() { - test(&()); -} diff --git a/tests/ui/sanitizer/cfi/supertraits.rs b/tests/ui/sanitizer/cfi/supertraits.rs deleted file mode 100644 index b2782dff5d555..0000000000000 --- a/tests/ui/sanitizer/cfi/supertraits.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Check that super-traits are callable. - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -C prefer-dynamic=off -//@ run-pass - -trait Parent1 { - type P1; - fn p1(&self) -> Self::P1; - fn d(&self) -> i32 { - 42 - } -} - -trait Parent2 { - type P2; - fn p2(&self) -> Self::P2; -} - -trait Child : Parent1 + Parent2 { - type C; - fn c(&self) -> Self::C; -} - -struct Foo; - -impl Parent1 for Foo { - type P1 = u16; - fn p1(&self) -> Self::P1 { - println!("p1"); - 1 - } -} - -impl Parent2 for Foo { - type P2 = u32; - fn p2(&self) -> Self::P2 { - println!("p2"); - 2 - } -} - -impl Child for Foo { - type C = u8; - fn c(&self) -> Self::C { - println!("c"); - 0 - } -} - -fn main() { - // Child can access its own methods and super methods. - let x = &Foo as &dyn Child; - x.c(); - x.p1(); - x.p2(); - x.d(); - // Parents can be created and access their methods. - let y = &Foo as &dyn Parent1; - y.p1(); - y.d(); - let z = &Foo as &dyn Parent2; - z.p2(); - // Trait upcasting works - let x1 = x as &dyn Parent1; - x1.p1(); - x1.d(); - let x2 = x as &dyn Parent2; - x2.p2(); -} diff --git a/tests/ui/sanitizer/cfi/transparent-has-regions.rs b/tests/ui/sanitizer/cfi/transparent-has-regions.rs deleted file mode 100644 index 3e9893df23c92..0000000000000 --- a/tests/ui/sanitizer/cfi/transparent-has-regions.rs +++ /dev/null @@ -1,19 +0,0 @@ -//@ needs-sanitizer-cfi -//@ compile-flags: -Ccodegen-units=1 -Clto -Ctarget-feature=-crt-static -Zsanitizer=cfi -C unsafe-allow-abi-mismatch=sanitizer -//@ no-prefer-dynamic -//@ only-x86_64-unknown-linux-gnu -//@ build-pass -//@ ignore-backends: gcc - -pub trait Trait {} - -impl Trait for i32 {} - -#[repr(transparent)] -struct BoxedTrait(Box); - -fn hello(x: BoxedTrait) {} - -fn main() { - hello(BoxedTrait(Box::new(1))); -} diff --git a/tests/ui/sanitizer/cfi/virtual-auto.rs b/tests/ui/sanitizer/cfi/virtual-auto.rs deleted file mode 100644 index d3a715c079aa6..0000000000000 --- a/tests/ui/sanitizer/cfi/virtual-auto.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Tests that calling a trait object method on a trait object with additional auto traits works. - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -C prefer-dynamic=off -//@ run-pass - -trait Foo { - fn foo(&self); -} - -struct Bar; -impl Foo for Bar { - fn foo(&self) {} -} - -pub fn main() { - let x: &(dyn Foo + Send) = &Bar; - x.foo(); -} diff --git a/tests/ui/sanitizer/cfi/works-with-associated-types.rs b/tests/ui/sanitizer/cfi/works-with-associated-types.rs new file mode 100644 index 0000000000000..d2b0805a95977 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-associated-types.rs @@ -0,0 +1,53 @@ +// Verifies that trait methods can be called through trait objects with +// associated types. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + type Output; + fn foo(&self) -> Self::Output; +} + +struct Type1; + +impl Trait1 for Type1 { + type Output = i32; + // ::foo is transformed into as Trait1>::foo + fn foo(&self) -> Self::Output { + 1 + } +} + +trait Trait2 { + type Output<'a> + where + Self: Sized; + + fn bar(&self) -> i32; +} + +impl Trait2 for () { + type Output<'a> + = () + where + Self: Sized; + + // <() as Trait2>::bar is transformed into ::bar + fn bar(&self) -> i32 { + 2 + } +} + +fn main() { + let x: &dyn Trait1 = &Type1; + // The virtual method call is transformed into as Trait1>::foo + assert_eq!(x.foo(), 1); + let x: &dyn Trait2 = &(); + // The virtual method call is transformed into ::bar + assert_eq!(x.bar(), 2); +} diff --git a/tests/ui/sanitizer/cfi/works-with-async-closures.rs b/tests/ui/sanitizer/cfi/works-with-async-closures.rs new file mode 100644 index 0000000000000..6dcc75f8b8c80 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-async-closures.rs @@ -0,0 +1,39 @@ +// Verifies that async closures can be called, including through dyn FnOnce +// trait objects. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ edition: 2021 +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(async_fn_traits)] + +use std::future::Future; +use std::ops::AsyncFn; +use std::pin::pin; +use std::task::{Context, Poll, Waker}; + +#[inline(never)] +fn identity(x: T) -> T { + x +} + +fn poll(future: F) -> Poll { + pin!(future).poll(&mut Context::from_waker(Waker::noop())) +} + +fn main() { + // The coroutine-closure is transformed into _ as FnOnce<()>>::call_once + let f = identity(async || 1); + assert_eq!(poll(f.async_call(())), Poll::Ready(1)); + assert_eq!(poll(f()), Poll::Ready(1)); + // The ConstructCoroutineInClosureShim and the VTableShim for + // <{async closure} as FnOnce<()>>::call_once are transformed into + // _ as FnOnce<()>>::call_once. + let g: Box _> = Box::new(f) as _; + // The virtual method call is transformed into _ as FnOnce<()>>::call_once + assert_eq!(poll(g()), Poll::Ready(1)); +} diff --git a/tests/ui/sanitizer/cfi/works-with-auto-traits.rs b/tests/ui/sanitizer/cfi/works-with-auto-traits.rs new file mode 100644 index 0000000000000..0dd24e5ea2428 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-auto-traits.rs @@ -0,0 +1,31 @@ +// Verifies that trait object methods can be called on trait objects with +// additional auto traits. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + fn foo(&self) -> i32; +} + +struct Type1; + +impl Trait1 for Type1 { + // ::foo is transformed into ::foo + fn foo(&self) -> i32 { + 1 + } +} + +fn main() { + let x: &(dyn Trait1 + Send) = &Type1; + // ::foo is transformed into ::foo + assert_eq!(x.foo(), 1); + let x: &(dyn Trait1 + Send + Sync) = &Type1; + // ::foo is transformed into ::foo + assert_eq!(x.foo(), 1); +} diff --git a/tests/ui/sanitizer/cfi/works-with-c-variadic.rs b/tests/ui/sanitizer/cfi/works-with-c-variadic.rs new file mode 100644 index 0000000000000..6dee396d62950 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-c-variadic.rs @@ -0,0 +1,26 @@ +// Verifies that C variadic trait methods can be called through function +// pointers. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + // ::foo is transformed into ::foo + unsafe extern "C" fn foo(x: i32, y: i32, mut ap: ...) -> i32 { + x + y + ap.next_arg::() + ap.next_arg::() + } +} + +struct Type1; + +impl Trait1 for Type1 {} + +fn main() { + let f = std::hint::black_box(Type1::foo as unsafe extern "C" fn(i32, i32, ...) -> i32); + // The indirect call is not transformed, as the type id is encoded from the fn pointer type + assert_eq!(unsafe { f(1, 2, 3, 4) }, 1 + 2 + 3 + 4); +} diff --git a/tests/ui/sanitizer/cfi/works-with-cfi-encoding.rs b/tests/ui/sanitizer/cfi/works-with-cfi-encoding.rs new file mode 100644 index 0000000000000..ce44e2b265636 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-cfi-encoding.rs @@ -0,0 +1,46 @@ +// Verifies that user-defined CFI encodings can be used. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(cfi_encoding, extern_types)] + +#[cfi_encoding = "3Foo"] +struct Type1(i32); + +unsafe extern "C" { + #[cfi_encoding = "3Bar"] + type Type2; +} + +// Type3 is not transformed, as it has an user-defined CFI encoding +#[cfi_encoding = "3Baz"] +#[repr(transparent)] +struct Type3(i32); + +fn foo1(x: Type1) -> i32 { + assert_eq!(x.0, 1); + 1 +} + +fn foo2(_: *const Type2) -> i32 { + 2 +} + +fn foo3(x: Type3) -> i32 { + assert_eq!(x.0, 3); + 3 +} + +fn main() { + let f: fn(Type1) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(Type1(1)), 1); + let f: fn(*const Type2) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(&() as *const () as *const Type2), 2); + let f: fn(Type3) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(Type3(3)), 3); +} diff --git a/tests/ui/sanitizer/cfi/works-with-clone.rs b/tests/ui/sanitizer/cfi/works-with-clone.rs new file mode 100644 index 0000000000000..6899f923a41d9 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-clone.rs @@ -0,0 +1,21 @@ +// Verifies that types with builtin Clone implementations (i.e., arrays, tuples, +// and closures) can be cloned. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +fn main() { + // The CloneShims for the values below are not transformed, as the Clone trait is not dyn + // compatible. + let array = [1i32, 2, 3]; + assert_eq!(array.clone(), array); + let tuple = (1i32, 2u8); + assert_eq!(tuple.clone(), tuple); + let x = 1i32; + let closure = move || x; + assert_eq!(closure.clone()(), closure()); +} diff --git a/tests/ui/sanitizer/cfi/works-with-closures.rs b/tests/ui/sanitizer/cfi/works-with-closures.rs new file mode 100644 index 0000000000000..b2a1589e13ad7 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-closures.rs @@ -0,0 +1,89 @@ +// Verifies that closures can be called through various forms of dynamic calls +// (i.e., through trait objects of the Fn, FnMut, and FnOnce traits, and as +// function pointers). +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(fn_traits)] +#![feature(unboxed_closures)] + +fn foo1<'a, T>() -> Box &'a T> { + // The closure is transformed into &T as Fn<(&T,)>>::call + Box::new(|x| x) +} + +fn use_fnmut i32>(mut f: F) -> i32 { + // The virtual method call is transformed into i32 as Fn<()>>::call + f() +} + +fn use_closure(call: extern "rust-call" fn(&C, ()) -> i32, f: &C) -> i32 { + // The indirect call is not transformed, as the type id is encoded from the fn pointer type + call(f, ()) +} + +fn use_closure_once(call: extern "rust-call" fn(C, ()) -> i32, f: C) -> i32 { + // The indirect call is not transformed, as the type id is encoded from the fn pointer type + call(f, ()) +} + +fn main() { + // Closures with parameters, through a dyn Fn trait object + let x = 1; + let f = foo1(); + // The virtual method call is transformed into &T as Fn<(&T,)>>::call + assert_eq!(*f(&x), 1); + + // Closures, through the Fn trait method + // The closure is transformed into i32 as Fn<()>>::call + let f: &dyn Fn() -> i32 = &(|| 2) as _; + // The virtual method call is transformed into i32 as Fn<()>>::call + assert_eq!(f.call(()), 2); + + // Fn closures passed where FnMut is expected + // The closure is transformed into i32 as Fn<()>>::call + let f: &dyn Fn() -> i32 = &(|| 3) as _; + assert_eq!(use_fnmut(f), 3); + + // FnOnce closures, through a dyn FnOnce trait object + // i32 as FnOnce<()>>::call_once receives an unsizeable `self: Self`, so the + // VTableShim for it in the vtable is transformed into + // i32 as FnOnce<()>>::call_once. + let f: Box i32> = Box::new(|| 4) as _; + // The virtual method call is transformed into i32 as FnOnce<()>>::call_once + assert_eq!(f(), 4); + + // Closures that move out of a capture, and so are FnOnce and not Fn or FnMut + let x = Box::new(5); + // The closure is transformed into i32 as FnOnce<()>>::call_once + let f: Box i32> = Box::new(move || { + drop(x); + 5 + }); + assert_eq!(f(), 5); + + // Closures cast to function pointers + // The closure is transformed into i32 as Fn<()>>::call + let f: fn() -> i32 = std::hint::black_box(|| 6); + // The indirect call is not transformed, as the type id is encoded from the fn pointer type + assert_eq!(f(), 6); + + // Closures with Fn::call cast to function pointers + let x = 7; + // The closure is transformed into i32 as Fn<()>>::call + let f = || x; + let call = std::hint::black_box(Fn::<()>::call); + assert_eq!(use_closure(call, &f), 7); + + // Closures with FnOnce::call_once cast to function pointers + // The closure is transformed into i32 as Fn<()>>::call + let g = || 8; + // The ClosureOnceShim is not transformed, as it can not be called through a vtable + let call = std::hint::black_box(FnOnce::<()>::call_once); + assert_eq!(use_closure_once(call, g), 8); +} diff --git a/tests/ui/sanitizer/cfi/works-with-const-generics.rs b/tests/ui/sanitizer/cfi/works-with-const-generics.rs new file mode 100644 index 0000000000000..63a6558d0d89c --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-const-generics.rs @@ -0,0 +1,129 @@ +// Verifies that functions with types with const generics as argument types can +// be called through function pointers. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(adt_const_params)] +#![feature(unsized_const_params)] +#![allow(incomplete_features)] + +use std::marker::ConstParamTy; + +#[derive(PartialEq, Eq, ConstParamTy)] +struct Struct1 { + x: u16, + y: u16, +} + +#[derive(PartialEq, Eq, ConstParamTy)] +enum Enum1 { + Variant1, + Variant2(u8), +} + +struct Struct2([i32; N]); + +struct Struct3(bool); + +struct Struct4(i32); + +struct Struct5(char); + +struct Struct6(&'static str); + +struct Struct7(Struct1); + +struct Struct8(Enum1); + +struct Struct9([u16; 2]); + +struct Struct10((u16, bool)); + +fn foo1(x: Struct2<2>) -> i32 { + assert_eq!(x.0, [1, 2]); + 1 +} + +fn foo2(x: &Struct2<4>) -> i32 { + assert_eq!(x.0, [1, 2, 3, 4]); + 2 +} + +fn foo3(x: Struct3) -> i32 { + assert!(x.0); + 3 +} + +fn foo4(x: Struct4<-1>) -> i32 { + assert_eq!(x.0, -1); + 4 +} + +fn foo5(x: Struct5<'x'>) -> i32 { + assert_eq!(x.0, 'x'); + 5 +} + +fn foo6(x: Struct6<"hello">) -> i32 { + assert_eq!(x.0, "hello"); + 6 +} + +fn foo7(x: Struct7<{ Struct1 { x: 1, y: 2 } }>) -> i32 { + assert_eq!(x.0.x, 1); + assert_eq!(x.0.y, 2); + 7 +} + +fn foo8(x: Struct8<{ Enum1::Variant1 }>) -> i32 { + assert!(matches!(x.0, Enum1::Variant1)); + 8 +} + +fn foo9(x: Struct8<{ Enum1::Variant2(5) }>) -> i32 { + match x.0 { + Enum1::Variant1 => unreachable!(), + Enum1::Variant2(v) => assert_eq!(v, 5), + } + 9 +} + +fn foo10(x: Struct9<{ [3, 4] }>) -> i32 { + assert_eq!(x.0, [3, 4]); + 10 +} + +fn foo11(x: Struct10<{ (6, true) }>) -> i32 { + assert_eq!(x.0, (6, true)); + 11 +} + +fn main() { + let f: fn(Struct2<2>) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(Struct2([1, 2])), 1); + let f: fn(&Struct2<4>) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(&Struct2([1, 2, 3, 4])), 2); + let f: fn(Struct3) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(Struct3(true)), 3); + let f: fn(Struct4<-1>) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(Struct4(-1)), 4); + let f: fn(Struct5<'x'>) -> i32 = std::hint::black_box(foo5); + assert_eq!(f(Struct5('x')), 5); + let f: fn(Struct6<"hello">) -> i32 = std::hint::black_box(foo6); + assert_eq!(f(Struct6("hello")), 6); + let f: fn(Struct7<{ Struct1 { x: 1, y: 2 } }>) -> i32 = std::hint::black_box(foo7); + assert_eq!(f(Struct7(Struct1 { x: 1, y: 2 })), 7); + let f: fn(Struct8<{ Enum1::Variant1 }>) -> i32 = std::hint::black_box(foo8); + assert_eq!(f(Struct8(Enum1::Variant1)), 8); + let f: fn(Struct8<{ Enum1::Variant2(5) }>) -> i32 = std::hint::black_box(foo9); + assert_eq!(f(Struct8(Enum1::Variant2(5))), 9); + let f: fn(Struct9<{ [3, 4] }>) -> i32 = std::hint::black_box(foo10); + assert_eq!(f(Struct9([3, 4])), 10); + let f: fn(Struct10<{ (6, true) }>) -> i32 = std::hint::black_box(foo11); + assert_eq!(f(Struct10((6, true))), 11); +} diff --git a/tests/ui/sanitizer/cfi/works-with-coroutines.rs b/tests/ui/sanitizer/cfi/works-with-coroutines.rs new file mode 100644 index 0000000000000..6f1f7a9f92125 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-coroutines.rs @@ -0,0 +1,95 @@ +// Verifies that coroutines (i.e., coroutines, async functions, gen functions, +// and async gen functions) can be called through their trait objects, and that +// functions with coroutine types as argument types can be called through +// function pointers. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ edition: 2024 +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(coroutines, stmt_expr_attributes)] +#![feature(coroutine_trait)] +#![feature(gen_blocks)] +#![feature(async_iterator)] + +use std::async_iter::AsyncIterator; +use std::ops::{Coroutine, CoroutineState}; +use std::pin::{Pin, pin}; +use std::task::{Context, Poll, Waker}; + +// The async fn coroutine is transformed into as Future>::poll +async fn async_fn() -> i32 { + 3 +} + +// The gen fn coroutine is transformed into as Iterator>::next +gen fn gen_fn() -> i32 { + yield 5; +} + +// The async gen fn coroutine is transformed into +// as AsyncIterator>::poll_next. +async gen fn async_gen_fn() -> i32 { + yield 6; +} + +fn generic_coroutine>(_: T) -> i32 { + 7 +} + +fn main() { + // Coroutines + // The coroutine is transformed into + // as Coroutine>::resume. + let coro = #[coroutine] + |_: i32| { + yield 1; + 2 + }; + let mut abstract_coro: Pin<&mut dyn Coroutine> = pin!(coro); + // The virtual method call is transformed into + // as Coroutine>::resume. + assert_eq!(abstract_coro.as_mut().resume(1), CoroutineState::Yielded(1)); + // The virtual method call is transformed into + // as Coroutine>::resume. + assert_eq!(abstract_coro.as_mut().resume(2), CoroutineState::Complete(2)); + + // Async fn coroutines + let f: fn() -> Pin>> = + std::hint::black_box(|| Box::pin(async_fn())); + // The virtual method call is transformed into as Future>::poll + assert_eq!(f().as_mut().poll(&mut Context::from_waker(Waker::noop())), Poll::Ready(3)); + + // Async block coroutines + // The async block coroutine is transformed into as Future>::poll + let g = async { + f().await; + 4 + }; + assert_eq!(pin!(g).poll(&mut Context::from_waker(Waker::noop())), Poll::Ready(4)); + + // Gen fn coroutines + let f: fn() -> Box> = std::hint::black_box(|| Box::new(gen_fn())); + // The virtual method call is transformed into as Iterator>::next + assert_eq!(f().next(), Some(5)); + + // Async gen fn coroutines + let f: fn() -> Pin>> = + std::hint::black_box(|| Box::pin(async_gen_fn())); + // The virtual method call is transformed into + // as AsyncIterator>::poll_next. + assert_eq!( + f().as_mut().poll_next(&mut Context::from_waker(Waker::noop())), + Poll::Ready(Some(6)) + ); + + // Concrete coroutine types + // The concrete coroutine type, and not a trait object of it, is used in the signature, so + // ty::CoroutineWitness is encoded (see issue #111184) + let f: fn(_) -> i32 = std::hint::black_box(generic_coroutine); + assert_eq!(f(async_fn()), 7); +} diff --git a/tests/ui/sanitizer/cfi/works-with-drop-in-place.rs b/tests/ui/sanitizer/cfi/works-with-drop-in-place.rs new file mode 100644 index 0000000000000..fe8573edfe94b --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-drop-in-place.rs @@ -0,0 +1,27 @@ +// Verifies that drops can be called on arbitrary trait objects, including trait +// objects without a principal trait. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +// A type without a Drop implementation +struct Type1; + +// A type with a Drop implementation +struct Type2; + +impl Drop for Type2 { + fn drop(&mut self) {} +} + +fn main() { + // Dropping the values below calls the drop glue of their types through the vtable of the + // dyn Send trait object (i.e., a trait object without a principal trait). Both the drop + // glue and the virtual drop calls to it are transformed into drop_in_place::. + let _ = Box::new(Type1) as Box; + let _ = Box::new(Type2) as Box; +} diff --git a/tests/ui/sanitizer/cfi/works-with-fn-ptr-addr.rs b/tests/ui/sanitizer/cfi/works-with-fn-ptr-addr.rs new file mode 100644 index 0000000000000..842c6eb6cc36e --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-fn-ptr-addr.rs @@ -0,0 +1,23 @@ +// Verifies that the addresses of function pointers can be compared (i.e., +// through the compiler-generated FnPtr implementations for them). +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +fn foo1() {} + +fn foo2() {} + +fn main() { + let f: fn() = std::hint::black_box(foo1); + let g: fn() = std::hint::black_box(foo1); + let h: fn() = std::hint::black_box(foo2); + // The ::addr FnPtrAddrShims are not transformed, as the FnPtr trait is not + // dyn compatible. + assert!(std::ptr::fn_addr_eq(f, g)); + assert!(!std::ptr::fn_addr_eq(f, h)); +} diff --git a/tests/ui/sanitizer/cfi/works-with-fn-ptr-casts.rs b/tests/ui/sanitizer/cfi/works-with-fn-ptr-casts.rs new file mode 100644 index 0000000000000..8764b92f62047 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-fn-ptr-casts.rs @@ -0,0 +1,70 @@ +// Verifies that methods and functions can be cast to function pointers. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +fn foo1(_: &Type2) -> i32 { + 1 +} + +trait Trait1 { + fn foo(&self) -> i32; +} + +struct Type1; + +impl Trait1 for Type1 { + fn foo(&self) -> i32 { + 2 + } +} + +trait Trait2 { + fn foo(&self) -> i32; + fn bar(&self) -> i32; +} + +struct Type2; + +impl Trait2 for Type2 { + fn foo(&self) -> i32 { + 3 + } + #[track_caller] + fn bar(&self) -> i32 { + 4 + } +} + +fn main() { + // Trait method implementations cast to function pointers + // The methods below are transformed, but CFI also attaches secondary type ids with the + // concrete self type to them (i.e., encoded with the USE_CONCRETE_SELF option), which are the + // ones tested at the indirect calls. + let f: fn(&Type1) -> i32 = std::hint::black_box(::foo); + assert_eq!(f(&Type1), 2); + let f: fn(&Type2) -> i32 = std::hint::black_box(::foo); + assert_eq!(f(&Type2), 3); + + // Non-method functions cast to function pointers + // foo1 is not transformed, as it is not a trait method or a closure-like + let f: fn(&Type2) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(&Type2), 1); + + // Trait method implementations with #[track_caller] cast to function pointers + // The ReifyShim for bar is transformed into ::bar, as bar is + // #[track_caller] and is reified. + let f: fn(&Type2) -> i32 = std::hint::black_box(::bar); + assert_eq!(f(&Type2), 4); + + // Trait method implementations with #[track_caller], through a vtable + // The ReifyShim for bar in the vtable is transformed into ::bar, as bar + // is #[track_caller] and is reified. + let x = &Type2 as &dyn Trait2; + // The virtual method call is transformed into ::bar + assert_eq!(x.bar(), 4); +} diff --git a/tests/ui/sanitizer/cfi/works-with-fn-trait-objects.rs b/tests/ui/sanitizer/cfi/works-with-fn-trait-objects.rs new file mode 100644 index 0000000000000..a89bdd01bff2f --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-fn-trait-objects.rs @@ -0,0 +1,42 @@ +// Verifies that types that implement the Fn, FnMut, or FnOnce traits can be +// called through their trait methods. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(fn_traits)] +#![feature(unboxed_closures)] + +fn foo1(x: i32) -> i32 { + x +} + +fn main() { + // Types that implement Fn + // The i32 as Fn<(i32,)>>::call FnPtrShim in the vtable is transformed into + // i32 as Fn<(i32,)>>::call. + let f: Box i32> = Box::new(foo1); + // The virtual method call is transformed into i32 as Fn<(i32,)>>::call + assert_eq!(Fn::call(&f, (1,)), 1); + + // Types that implement FnMut + let mut a = 0; + // The closure is transformed into >::call_mut + let mut f: Box = Box::new(|x| a += x); + // The virtual method call is transformed into >::call_mut + FnMut::call_mut(&mut f, (2,)); + drop(f); + assert_eq!(a, 2); + + // Types that implement FnOnce + // The i32 as FnOnce<(i32,)>>::call_once FnPtrShim in the vtable is transformed + // into i32 as FnOnce<(i32,)>>::call_once. + let f: Box i32> = Box::new(foo1); + // The virtual method call is transformed into + // i32 as FnOnce<(i32,)>>::call_once. + assert_eq!(FnOnce::call_once(f, (3,)), 3); +} diff --git a/tests/ui/sanitizer/cfi/works-with-function-types.rs b/tests/ui/sanitizer/cfi/works-with-function-types.rs new file mode 100644 index 0000000000000..5df5e1bde6729 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-function-types.rs @@ -0,0 +1,41 @@ +// Verifies that functions with function types (i.e., function pointers) as +// argument types can be called through function pointers. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +fn bar1(x: i32) -> i32 { + x +} + +unsafe fn bar2() {} + +extern "C" fn bar3() {} + +fn foo1(f: fn(i32) -> i32) -> i32 { + assert_eq!(f(1), 1); + 1 +} + +fn foo2(f: unsafe fn()) -> i32 { + unsafe { f() }; + 2 +} + +fn foo3(f: extern "C" fn()) -> i32 { + f(); + 3 +} + +fn main() { + let f: fn(fn(i32) -> i32) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(bar1), 1); + let f: fn(unsafe fn()) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(bar2), 2); + let f: fn(extern "C" fn()) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(bar3), 3); +} diff --git a/tests/ui/sanitizer/cfi/works-with-generalized-pointers.rs b/tests/ui/sanitizer/cfi/works-with-generalized-pointers.rs new file mode 100644 index 0000000000000..1d1664bd8d861 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-generalized-pointers.rs @@ -0,0 +1,53 @@ +// Verifies that functions that differ only in the pointee types of their +// pointer arguments can be called through function pointers when compiling with +// -Zsanitizer-cfi-generalize-pointers. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Zsanitizer-cfi-generalize-pointers -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +fn foo1(_: *const i32) -> i32 { + 1 +} + +fn foo2(_: *mut i32) -> i32 { + 2 +} + +fn foo3(_: &i32) -> i32 { + 3 +} + +fn foo4(_: &mut i32) -> i32 { + 4 +} + +fn foo5(_: fn(i32) -> i32) -> i32 { + 5 +} + +fn main() { + // Pointers and references are generalized to *const (), so the type ids encoded for the + // functions above and for the fn pointer types below are the same and match. + let mut x = 0; + let f: fn(*const i32) -> i32 = std::hint::black_box(foo1); + let f: fn(*const i8) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(&x as *const i32 as *const i8), 1); + let f: fn(*mut i32) -> i32 = std::hint::black_box(foo2); + let f: fn(*mut i8) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(&mut x as *mut i32 as *mut i8), 2); + let f: fn(&i32) -> i32 = std::hint::black_box(foo3); + let f: fn(&i8) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(unsafe { &*(&x as *const i32 as *const i8) }), 3); + let f: fn(&mut i32) -> i32 = std::hint::black_box(foo4); + let f: fn(&mut i8) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(unsafe { &mut *(&mut x as *mut i32 as *mut i8) }), 4); + + // Function pointers are generalized to *const () as well + let f: fn(fn(i32) -> i32) -> i32 = std::hint::black_box(foo5); + let f: fn(fn(u64) -> u64) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(|x| x), 5); +} diff --git a/tests/ui/sanitizer/cfi/works-with-intrinsics.rs b/tests/ui/sanitizer/cfi/works-with-intrinsics.rs new file mode 100644 index 0000000000000..f4da99fb24cbb --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-intrinsics.rs @@ -0,0 +1,28 @@ +// Verifies that intrinsics and LLVM intrinsics can be called. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(abi_unadjusted, link_llvm_intrinsics)] +#![allow(internal_features)] + +unsafe extern "unadjusted" { + #[link_name = "llvm.bitreverse.i32"] + fn bitreverse(x: i32) -> i32; +} + +fn main() { + // Intrinsics + // The black_box intrinsic (i.e., a fn item with #[rustc_intrinsic]) is not transformed, as + // it can not be reified or called indirectly. + assert_eq!(std::hint::black_box(1i32), 1); + + // LLVM intrinsics + // The bitreverse LLVM intrinsic (i.e., a fn item with extern "unadjusted") is not + // transformed, as it can not be reified or called indirectly. + assert_eq!(unsafe { bitreverse(1i32) }, i32::MIN); +} diff --git a/tests/ui/sanitizer/cfi/works-with-lifetimes.rs b/tests/ui/sanitizer/cfi/works-with-lifetimes.rs new file mode 100644 index 0000000000000..19df6fbbc99ff --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-lifetimes.rs @@ -0,0 +1,47 @@ +// Verifies that functions with lifetimes and higher-ranked trait bounds as +// argument types can be called through function pointers and trait objects. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +fn bar1(_: &i32) {} + +fn bar2(_: &i32, _: &i32) {} + +fn bar3(_: &dyn for<'b> Fn(&'b i32)) {} + +fn foo1(f: &dyn for<'a> Fn(&'a i32)) -> i32 { + f(&1); + 1 +} + +fn foo2(f: for<'a> fn(&'a i32)) -> i32 { + f(&2); + 2 +} + +fn foo3(f: for<'a, 'b> fn(&'a i32, &'b i32)) -> i32 { + f(&3, &4); + 3 +} + +// A higher-ranked trait bound nested in a higher-ranked function pointer type +fn foo4(f: for<'a> fn(&'a dyn for<'b> Fn(&'b i32))) -> i32 { + f(&|_x: &i32| {}); + 4 +} + +fn main() { + let f: fn(&dyn for<'a> Fn(&'a i32)) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(&|_x: &i32| {}), 1); + let f: fn(for<'a> fn(&'a i32)) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(bar1), 2); + let f: fn(for<'a, 'b> fn(&'a i32, &'b i32)) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(bar2), 3); + let f: fn(for<'a> fn(&'a dyn for<'b> Fn(&'b i32))) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(bar3), 4); +} diff --git a/tests/ui/sanitizer/cfi/works-with-normalized-integers.rs b/tests/ui/sanitizer/cfi/works-with-normalized-integers.rs new file mode 100644 index 0000000000000..86c6abc382acf --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-normalized-integers.rs @@ -0,0 +1,58 @@ +// Verifies that functions with bool and char argument types can be called +// through function pointers with u8 and u32 argument types when compiling with +// -Zsanitizer-cfi-normalize-integers. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Zsanitizer-cfi-normalize-integers -Cunsafe-allow-abi-mismatch=sanitizer,sanitizer-cfi-normalize-integers +//@ run-pass + +fn foo1(_: bool) -> i32 { + 1 +} + +fn foo2(_: char) -> i32 { + 2 +} + +fn foo3(_: isize) -> i32 { + 3 +} + +fn foo4(_: usize) -> i32 { + 4 +} + +#[cfg(target_pointer_width = "16")] +type Isize = i16; +#[cfg(target_pointer_width = "32")] +type Isize = i32; +#[cfg(target_pointer_width = "64")] +type Isize = i64; + +#[cfg(target_pointer_width = "16")] +type Usize = u16; +#[cfg(target_pointer_width = "32")] +type Usize = u32; +#[cfg(target_pointer_width = "64")] +type Usize = u64; + +fn main() { + // bool is normalized to u8 and char to u32 + let f: fn(bool) -> i32 = std::hint::black_box(foo1); + let f: fn(u8) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(true as u8), 1); + let f: fn(char) -> i32 = std::hint::black_box(foo2); + let f: fn(u32) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f('a' as u32), 2); + + // isize and usize are normalized to the integer of the target pointer width + let f: fn(isize) -> i32 = std::hint::black_box(foo3); + let f: fn(Isize) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(3), 3); + let f: fn(usize) -> i32 = std::hint::black_box(foo4); + let f: fn(Usize) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(4), 4); +} diff --git a/tests/ui/sanitizer/cfi/works-with-pattern-types.rs b/tests/ui/sanitizer/cfi/works-with-pattern-types.rs new file mode 100644 index 0000000000000..aedb837544619 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-pattern-types.rs @@ -0,0 +1,41 @@ +// Verifies that functions with pattern types as argument types can be called +// through function pointers. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(pattern_types)] +#![feature(pattern_type_macro)] + +use std::pat::pattern_type; + +fn foo1(x: pattern_type!(i32 is 1..)) -> i32 { + assert_eq!(unsafe { std::mem::transmute::<_, i32>(x) }, 1); + 1 +} + +fn foo2(x: pattern_type!(i32 is 1..=5)) -> i32 { + assert_eq!(unsafe { std::mem::transmute::<_, i32>(x) }, 2); + 2 +} + +fn foo3(x: pattern_type!(i32 is -5..=5)) -> i32 { + assert_eq!(unsafe { std::mem::transmute::<_, i32>(x) }, -3); + 3 +} + +fn main() { + let x: pattern_type!(i32 is 1..) = unsafe { std::mem::transmute(1i32) }; + let f: fn(pattern_type!(i32 is 1..)) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(x), 1); + let x: pattern_type!(i32 is 1..=5) = unsafe { std::mem::transmute(2i32) }; + let f: fn(pattern_type!(i32 is 1..=5)) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(x), 2); + let x: pattern_type!(i32 is -5..=5) = unsafe { std::mem::transmute(-3i32) }; + let f: fn(pattern_type!(i32 is -5..=5)) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(x), 3); +} diff --git a/tests/ui/sanitizer/cfi/works-with-pointer-types.rs b/tests/ui/sanitizer/cfi/works-with-pointer-types.rs new file mode 100644 index 0000000000000..ed76ec2cc4477 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-pointer-types.rs @@ -0,0 +1,51 @@ +// Verifies that functions with pointer types as argument types can be called +// through function pointers. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +use std::ffi::c_void; + +fn foo1(_: &i32) -> i32 { + 1 +} +fn foo2(_: &mut i32) -> i32 { + 2 +} +fn foo3(_: *const i32) -> i32 { + 3 +} +fn foo4(_: *mut i32) -> i32 { + 4 +} +fn foo5(_: *const c_void) -> i32 { + 5 +} +fn foo6(_: *mut c_void) -> i32 { + 6 +} +fn foo7(_: &&i32) -> i32 { + 7 +} + +fn main() { + let mut x = 0; + let f: fn(&i32) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(&x), 1); + let f: fn(&mut i32) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(&mut x), 2); + let f: fn(*const i32) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(&x as *const i32), 3); + let f: fn(*mut i32) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(&mut x as *mut i32), 4); + let f: fn(*const c_void) -> i32 = std::hint::black_box(foo5); + assert_eq!(f(&x as *const i32 as *const c_void), 5); + let f: fn(*mut c_void) -> i32 = std::hint::black_box(foo6); + assert_eq!(f(&mut x as *mut i32 as *mut c_void), 6); + let f: fn(&&i32) -> i32 = std::hint::black_box(foo7); + assert_eq!(f(&&0), 7); +} diff --git a/tests/ui/sanitizer/cfi/works-with-primitive-types.rs b/tests/ui/sanitizer/cfi/works-with-primitive-types.rs new file mode 100644 index 0000000000000..edf5640c38c3b --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-primitive-types.rs @@ -0,0 +1,116 @@ +// Verifies that functions with primitive types as argument and return types can +// be called through function pointers. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(f128)] +#![feature(f16)] +fn foo1(_: ()) -> i32 { + 1 +} +fn foo2(_: bool) -> i32 { + 2 +} +fn foo3(_: char) -> i32 { + 3 +} +fn foo4(_: f32) -> i32 { + 4 +} +fn foo5(_: f64) -> i32 { + 5 +} +fn foo6(_: i8) -> i32 { + 6 +} +fn foo7(_: i16) -> i32 { + 7 +} +fn foo8(_: i32) -> i32 { + 8 +} +fn foo9(_: i64) -> i32 { + 9 +} +fn foo10(_: i128) -> i32 { + 10 +} +fn foo11(_: isize) -> i32 { + 11 +} +fn foo12(_: u8) -> i32 { + 12 +} +fn foo13(_: u16) -> i32 { + 13 +} +fn foo14(_: u32) -> i32 { + 14 +} +fn foo15(_: u64) -> i32 { + 15 +} +fn foo16(_: u128) -> i32 { + 16 +} +fn foo17(_: usize) -> i32 { + 17 +} +fn foo18(_: f16) -> i32 { + 18 +} +fn foo19(_: f128) -> i32 { + 19 +} +fn foo20() -> ! { + std::process::exit(0) +} + +fn main() { + let f: fn(()) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(()), 1); + let f: fn(bool) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(false), 2); + let f: fn(char) -> i32 = std::hint::black_box(foo3); + assert_eq!(f('a'), 3); + let f: fn(f32) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(0f32), 4); + let f: fn(f64) -> i32 = std::hint::black_box(foo5); + assert_eq!(f(0f64), 5); + let f: fn(i8) -> i32 = std::hint::black_box(foo6); + assert_eq!(f(0i8), 6); + let f: fn(i16) -> i32 = std::hint::black_box(foo7); + assert_eq!(f(0i16), 7); + let f: fn(i32) -> i32 = std::hint::black_box(foo8); + assert_eq!(f(0i32), 8); + let f: fn(i64) -> i32 = std::hint::black_box(foo9); + assert_eq!(f(0i64), 9); + let f: fn(i128) -> i32 = std::hint::black_box(foo10); + assert_eq!(f(0i128), 10); + let f: fn(isize) -> i32 = std::hint::black_box(foo11); + assert_eq!(f(0isize), 11); + let f: fn(u8) -> i32 = std::hint::black_box(foo12); + assert_eq!(f(0u8), 12); + let f: fn(u16) -> i32 = std::hint::black_box(foo13); + assert_eq!(f(0u16), 13); + let f: fn(u32) -> i32 = std::hint::black_box(foo14); + assert_eq!(f(0u32), 14); + let f: fn(u64) -> i32 = std::hint::black_box(foo15); + assert_eq!(f(0u64), 15); + let f: fn(u128) -> i32 = std::hint::black_box(foo16); + assert_eq!(f(0u128), 16); + let f: fn(usize) -> i32 = std::hint::black_box(foo17); + assert_eq!(f(0usize), 17); + let f: fn(f16) -> i32 = std::hint::black_box(foo18); + assert_eq!(f(0f16), 18); + let f: fn(f128) -> i32 = std::hint::black_box(foo19); + assert_eq!(f(0f128), 19); + // The never type can only be returned, so this must be called last + let f: fn() -> ! = std::hint::black_box(foo20); + f(); +} diff --git a/tests/ui/sanitizer/cfi/works-with-receivers.rs b/tests/ui/sanitizer/cfi/works-with-receivers.rs new file mode 100644 index 0000000000000..74c6fb02675c8 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-receivers.rs @@ -0,0 +1,58 @@ +// Verifies that trait methods with custom receivers (e.g., Arc) can +// be called through trait objects. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +use std::pin::Pin; +use std::rc::Rc; +use std::sync::Arc; + +trait Trait1 { + fn foo(self: Arc) -> i32; + fn bar(self: Box) -> i32; + fn baz(self: Rc) -> i32; + fn qux(self: Pin<&mut Self>) -> i32; +} + +struct Type1; + +impl Trait1 for Type1 { + // ::foo is transformed into ::foo + fn foo(self: Arc) -> i32 { + 1 + } + // ::bar is transformed into ::bar + fn bar(self: Box) -> i32 { + 2 + } + // ::baz is transformed into ::baz + fn baz(self: Rc) -> i32 { + 3 + } + // ::qux is transformed into ::qux + fn qux(self: Pin<&mut Self>) -> i32 { + 4 + } +} + +fn main() { + let x: Arc = Arc::new(Type1); + // The virtual method call is transformed into ::foo + assert_eq!(x.foo(), 1); + let x: Box = Box::new(Type1); + // The virtual method call is transformed into ::bar + assert_eq!(x.bar(), 2); + let x: Rc = Rc::new(Type1); + // The virtual method call is transformed into ::baz + assert_eq!(x.baz(), 3); + let mut y = Type1; + let x: Pin<&mut Type1> = Pin::new(&mut y); + let x: Pin<&mut dyn Trait1> = x; + // The virtual method call is transformed into ::qux + assert_eq!(x.qux(), 4); +} diff --git a/tests/ui/sanitizer/cfi/works-with-repr-transparent-types.rs b/tests/ui/sanitizer/cfi/works-with-repr-transparent-types.rs new file mode 100644 index 0000000000000..8bf8ad2f4a45c --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-repr-transparent-types.rs @@ -0,0 +1,92 @@ +// Verifies that functions with repr(transparent) types (including +// self-referential repr(transparent) types) as argument types can be called +// through function pointers and trait objects. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +use std::marker::PhantomData; + +#[repr(transparent)] +struct Type1(i32); + +#[repr(transparent)] +struct Type2<'a>(&'a i32); + +trait Trait1 {} + +impl Trait1 for Type1 {} + +// A repr(transparent) type with a represented type that has regions +#[repr(transparent)] +struct Type3(Box); + +// A repr(transparent) type without a non-ZST field +#[repr(transparent)] +struct Type4(PhantomData); + +struct Struct1 { + _x: u8, + p: PhantomData, +} + +#[repr(transparent)] +struct Type5(Struct1); + +trait Trait2 { + fn foo(&self, x: Type5) -> i32; +} + +struct Type6; + +impl Trait2 for Type6 { + fn foo(&self, _: Type5) -> i32 { + 6 + } +} + +// A repr(transparent) type that is a pointer and references itself, which is generalized to avoid +// a reference cycle. +#[repr(transparent)] +struct Type7(*const Type7); + +fn foo1(x: Type1) -> i32 { + assert_eq!(x.0, 1); + 1 +} + +fn foo2(x: Type2<'_>) -> i32 { + assert_eq!(*x.0, 2); + 2 +} + +fn foo3(_: Type3) -> i32 { + 3 +} + +fn foo4(_: Type4) -> i32 { + 4 +} + +fn foo5(_: Type7) -> i32 { + 5 +} + +fn main() { + let f: fn(Type1) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(Type1(1)), 1); + let f: fn(Type2<'_>) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(Type2(&2)), 2); + let f: fn(Type3) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(Type3(Box::new(Type1(1)))), 3); + let f: fn(Type4) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(Type4(PhantomData)), 4); + let f: fn(Type7) -> i32 = std::hint::black_box(foo5); + assert_eq!(f(Type7(std::ptr::null())), 5); + let x = &Type6 as &dyn Trait2; + assert_eq!(x.foo(Type5(Struct1 { _x: 0, p: PhantomData })), 6); +} diff --git a/tests/ui/sanitizer/cfi/works-with-sequence-types.rs b/tests/ui/sanitizer/cfi/works-with-sequence-types.rs new file mode 100644 index 0000000000000..9182fa3fe3864 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-sequence-types.rs @@ -0,0 +1,47 @@ +// Verifies that functions with sequence types as argument types can be called +// through function pointers. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +fn foo1(x: (i32, i32)) -> i32 { + assert_eq!(x, (1, 2)); + 1 +} + +fn foo2(x: [i32; 4]) -> i32 { + assert_eq!(x, [1, 2, 3, 4]); + 2 +} + +fn foo3(x: &[i32]) -> i32 { + assert_eq!(x, &[1, 2, 3]); + 3 +} + +fn foo4(x: &str) -> i32 { + assert_eq!(x, "foo"); + 4 +} + +fn foo5(x: [i32; 2 * 2]) -> i32 { + assert_eq!(x, [1, 2, 3, 4]); + 5 +} + +fn main() { + let f: fn((i32, i32)) -> i32 = std::hint::black_box(foo1); + assert_eq!(f((1, 2)), 1); + let f: fn([i32; 4]) -> i32 = std::hint::black_box(foo2); + assert_eq!(f([1, 2, 3, 4]), 2); + let f: fn(&[i32]) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(&[1, 2, 3]), 3); + let f: fn(&str) -> i32 = std::hint::black_box(foo4); + assert_eq!(f("foo"), 4); + let f: fn([i32; 2 * 2]) -> i32 = std::hint::black_box(foo5); + assert_eq!(f([1, 2, 3, 4]), 5); +} diff --git a/tests/ui/sanitizer/cfi/works-with-supertraits.rs b/tests/ui/sanitizer/cfi/works-with-supertraits.rs new file mode 100644 index 0000000000000..7dc153df442ba --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-supertraits.rs @@ -0,0 +1,89 @@ +// Verifies that super-trait methods can be called through trait objects, and +// that trait objects can be upcast. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + type Output1; + fn foo(&self) -> Self::Output1; + // ::qux is transformed into as Trait1>::qux + fn qux(&self) -> i32 { + 4 + } +} + +trait Trait2 { + type Output2; + fn bar(&self) -> Self::Output2; +} + +trait Trait3: Trait1 + Trait2 { + type Output3; + fn baz(&self) -> Self::Output3; +} + +struct Type1; + +impl Trait1 for Type1 { + type Output1 = u16; + // ::foo is transformed into as Trait1>::foo + fn foo(&self) -> Self::Output1 { + 1 + } +} + +impl Trait2 for Type1 { + type Output2 = u32; + // ::bar is transformed into as Trait2>::bar + fn bar(&self) -> Self::Output2 { + 2 + } +} + +impl Trait3 for Type1 { + type Output3 = u8; + // ::baz is transformed into + // as Trait3>::baz. + fn baz(&self) -> Self::Output3 { + 3 + } +} + +fn main() { + // Methods of a trait and of its supertraits, through a child trait object + let x = &Type1 as &dyn Trait3; + // The virtual method call is transformed into + // as Trait3>::baz. + assert_eq!(x.baz(), 3); + // The virtual method call is transformed into as Trait1>::foo + assert_eq!(x.foo(), 1); + // The virtual method call is transformed into as Trait2>::bar + assert_eq!(x.bar(), 2); + // The virtual method call is transformed into as Trait1>::qux + assert_eq!(x.qux(), 4); + + // Methods of a supertrait, through a supertrait object + let y = &Type1 as &dyn Trait1; + // The virtual method call is transformed into as Trait1>::foo + assert_eq!(y.foo(), 1); + // The virtual method call is transformed into as Trait1>::qux + assert_eq!(y.qux(), 4); + let z = &Type1 as &dyn Trait2; + // The virtual method call is transformed into as Trait2>::bar + assert_eq!(z.bar(), 2); + + // Methods of a supertrait, through an upcast trait object + let x1 = x as &dyn Trait1; + // The virtual method call is transformed into as Trait1>::foo + assert_eq!(x1.foo(), 1); + // The virtual method call is transformed into as Trait1>::qux + assert_eq!(x1.qux(), 4); + let x2 = x as &dyn Trait2; + // The virtual method call is transformed into as Trait2>::bar + assert_eq!(x2.bar(), 2); +} diff --git a/tests/ui/sanitizer/cfi/works-with-thread-locals.rs b/tests/ui/sanitizer/cfi/works-with-thread-locals.rs new file mode 100644 index 0000000000000..6702f9de17e7d --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-thread-locals.rs @@ -0,0 +1,22 @@ +// Verifies that thread locals can be accessed (i.e., through the +// compiler-generated accessors for them). +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +use std::cell::Cell; + +// The ThreadLocalShim for the accessor below is not transformed, as it does not implement any +// trait method and can not be called through a vtable. +thread_local! { + static COUNTER: Cell = const { Cell::new(0) }; +} + +fn main() { + COUNTER.with(|counter| counter.set(counter.get() + 1)); + assert_eq!(COUNTER.with(|counter| counter.get()), 1); +} diff --git a/tests/ui/sanitizer/cfi/works-with-trait-objects.rs b/tests/ui/sanitizer/cfi/works-with-trait-objects.rs new file mode 100644 index 0000000000000..d6bbd8f825c85 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-trait-objects.rs @@ -0,0 +1,67 @@ +// Verifies that trait methods (i.e., both trait method implementations in impl +// blocks and provided (default) trait methods in trait blocks) can be called +// through trait objects. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + fn foo(&self) -> i32; + // ::bar is transformed into ::bar + fn bar(&self) -> i32 { + 2 + } + // ::baz is not transformed, as it can not be called through a vtable + fn baz(&self) -> i32 + where + Self: Sized, + { + 3 + } +} + +trait Trait2 { + fn qux(&self) -> i32; +} + +struct Type1; + +impl Trait1 for Type1 { + // ::foo is transformed into ::foo + fn foo(&self) -> i32 { + 1 + } +} + +impl Trait2 for Type1 { + // ::qux is not transformed, as Trait2 is not dyn compatible + fn qux(&self) -> i32 { + 4 + } +} + +fn main() { + // Trait methods, through a reference to a trait object + let x = &Type1 as &dyn Trait1; + // The virtual method call is transformed into ::foo + assert_eq!(x.foo(), 1); + // The virtual method call is transformed into ::bar + assert_eq!(x.bar(), 2); + + // Trait methods, through a boxed trait object + let x: Box = Box::new(Type1); + // The virtual method call is transformed into ::foo + assert_eq!(x.foo(), 1); + // The virtual method call is transformed into ::bar + assert_eq!(x.bar(), 2); + + // Trait methods that can not be called through a vtable + // ::baz is not transformed, as it can not be called through a vtable + assert_eq!(Type1.baz::(), 3); + // ::qux is not transformed, as Trait2 is not dyn compatible + assert_eq!(Type1.qux::(), 4); +} diff --git a/tests/ui/sanitizer/cfi/works-with-trait-types.rs b/tests/ui/sanitizer/cfi/works-with-trait-types.rs new file mode 100644 index 0000000000000..b0397dd40cb36 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-trait-types.rs @@ -0,0 +1,52 @@ +// Verifies that functions with trait types (i.e., trait objects) as argument +// types can be called through function pointers. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + fn foo(&self) -> i32; +} + +struct Type1; + +impl Trait1 for Type1 { + fn foo(&self) -> i32 { + 1 + } +} + +fn foo1(x: &dyn Trait1) -> i32 { + assert_eq!(x.foo(), 1); + 1 +} + +fn foo2(x: &mut dyn Trait1) -> i32 { + assert_eq!(x.foo(), 1); + 2 +} + +fn foo3(x: Box) -> i32 { + assert_eq!(x.foo(), 1); + 3 +} + +// A trait object without a principal trait, so its predicates are all auto traits +fn foo4(_: &dyn Send) -> i32 { + 4 +} + +fn main() { + let f: fn(&dyn Trait1) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(&Type1), 1); + let f: fn(&mut dyn Trait1) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(&mut Type1), 2); + let f: fn(Box) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(Box::new(Type1)), 3); + let f: fn(&dyn Send) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(&Type1), 4); +} diff --git a/tests/ui/sanitizer/cfi/works-with-user-defined-types.rs b/tests/ui/sanitizer/cfi/works-with-user-defined-types.rs new file mode 100644 index 0000000000000..bd6c5e7840fb9 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-user-defined-types.rs @@ -0,0 +1,85 @@ +// Verifies that functions with user-defined types (i.e., structs, enums, +// unions, and extern types) as argument types can be called through function +// pointers. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(extern_types)] + +struct Struct1(i32); + +enum Enum1 { + Variant1(i32), +} + +union Union1 { + f: i32, +} + +#[repr(C)] +struct Struct2 { + f: i32, +} + +struct Struct3(T); + +unsafe extern "C" { + type Type1; +} + +fn foo1(x: Struct1) -> i32 { + assert_eq!(x.0, 1); + 1 +} + +fn foo2(x: Enum1) -> i32 { + let Enum1::Variant1(y) = x; + assert_eq!(y, 2); + 2 +} + +fn foo3(x: Union1) -> i32 { + assert_eq!(unsafe { x.f }, 3); + 3 +} + +fn foo4(x: Struct2) -> i32 { + assert_eq!(x.f, 4); + 4 +} + +fn foo5(x: Struct3) -> i32 { + assert_eq!(x.0, 5); + 5 +} + +fn foo6(_: *const Type1) -> i32 { + 6 +} + +extern "C" fn foo7(x: Struct2) -> i32 { + assert_eq!(x.f, 7); + 7 +} + +fn main() { + let f: fn(Struct1) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(Struct1(1)), 1); + let f: fn(Enum1) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(Enum1::Variant1(2)), 2); + let f: fn(Union1) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(Union1 { f: 3 }), 3); + let f: fn(Struct2) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(Struct2 { f: 4 }), 4); + let f: fn(Struct3) -> i32 = std::hint::black_box(foo5); + assert_eq!(f(Struct3(5)), 5); + let f: fn(*const Type1) -> i32 = std::hint::black_box(foo6); + assert_eq!(f(&() as *const () as *const Type1), 6); + let f: extern "C" fn(Struct2) -> i32 = std::hint::black_box(foo7); + assert_eq!(f(Struct2 { f: 7 }), 7); +} diff --git a/tests/ui/sanitizer/kcfi-c-variadic.rs b/tests/ui/sanitizer/kcfi-c-variadic.rs deleted file mode 100644 index 2f88ccfb1269c..0000000000000 --- a/tests/ui/sanitizer/kcfi-c-variadic.rs +++ /dev/null @@ -1,18 +0,0 @@ -//@ needs-sanitizer-kcfi -//@ no-prefer-dynamic -//@ compile-flags: -Zsanitizer=kcfi -Cpanic=abort -Cunsafe-allow-abi-mismatch=sanitizer -//@ ignore-backends: gcc -//@ run-pass - -trait Trait { - unsafe extern "C" fn foo(x: i32, y: i32, mut ap: ...) -> i32 { - x + y + ap.next_arg::() + ap.next_arg::() - } -} - -impl Trait for i32 {} - -fn main() { - let f = i32::foo as unsafe extern "C" fn(i32, i32, ...) -> i32; - assert_eq!(unsafe { f(1, 2, 3, 4) }, 1 + 2 + 3 + 4); -} diff --git a/tests/ui/sanitizer/kcfi-mangling.rs b/tests/ui/sanitizer/kcfi-mangling.rs deleted file mode 100644 index 371f34ba72af2..0000000000000 --- a/tests/ui/sanitizer/kcfi-mangling.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Check KCFI extra mangling works correctly on v0 - -//@ needs-sanitizer-kcfi -//@ no-prefer-dynamic -//@ compile-flags: -C panic=abort -Zsanitizer=kcfi -C symbol-mangling-version=v0 -C unsafe-allow-abi-mismatch=sanitizer -//@ build-pass -//@ ignore-backends: gcc - -trait Foo { - fn foo(&self); -} - -struct Bar; -impl Foo for Bar { - fn foo(&self) {} -} - -struct Baz; -impl Foo for Baz { - #[track_caller] - fn foo(&self) {} -} - -fn main() { - // Produces `ReifyShim(_, ReifyReason::FnPtr)` - let f: fn(&Bar) = Bar::foo; - f(&Bar); - // Produces `ReifyShim(_, ReifyReason::Vtable)` - let v: &dyn Foo = &Baz as _; - v.foo(); -} diff --git a/tests/ui/sanitizer/kcfi/const-generics.rs b/tests/ui/sanitizer/kcfi/const-generics.rs deleted file mode 100644 index 86f487bb9ea1e..0000000000000 --- a/tests/ui/sanitizer/kcfi/const-generics.rs +++ /dev/null @@ -1,109 +0,0 @@ -// Verifies that functions with types with const generics as argument types can -// be called through function pointers. -// -//@ needs-sanitizer-kcfi -//@ only-linux -//@ ignore-backends: gcc -//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer -//@ run-pass - -#![feature(adt_const_params)] -#![feature(unsized_const_params)] -#![allow(incomplete_features)] - -use std::marker::ConstParamTy; - -#[derive(PartialEq, Eq, ConstParamTy)] -struct Struct2 { - x: u16, - y: u16, -} - -#[derive(PartialEq, Eq, ConstParamTy)] -enum Enum1 { - Variant1, - Variant2(u8), -} - -struct Struct1([i32; N]); -struct BoolHolder(bool); -struct IntHolder(i32); -struct CharHolder(char); -struct StrHolder(&'static str); -struct StructHolder(Struct2); -struct EnumHolder(Enum1); -struct ArrayHolder([u16; 2]); -struct TupleHolder((u16, bool)); - -fn foo1(x: Struct1<2>) { - assert_eq!(x.0, [1, 2]); -} - -fn foo2(x: &Struct1<4>) { - assert_eq!(x.0, [1, 2, 3, 4]); -} - -fn foo3(x: BoolHolder) { - assert!(x.0); -} - -fn foo4(x: IntHolder<-1>) { - assert_eq!(x.0, -1); -} - -fn foo5(x: CharHolder<'x'>) { - assert_eq!(x.0, 'x'); -} - -fn foo6(x: StrHolder<"hello">) { - assert_eq!(x.0, "hello"); -} - -fn foo7(x: StructHolder<{ Struct2 { x: 1, y: 2 } }>) { - assert_eq!(x.0.x, 1); - assert_eq!(x.0.y, 2); -} - -fn foo8(x: EnumHolder<{ Enum1::Variant1 }>) { - assert!(matches!(x.0, Enum1::Variant1)); -} - -fn foo9(x: EnumHolder<{ Enum1::Variant2(5) }>) { - match x.0 { - Enum1::Variant1 => unreachable!(), - Enum1::Variant2(v) => assert_eq!(v, 5), - } -} - -fn foo10(x: ArrayHolder<{ [3, 4] }>) { - assert_eq!(x.0, [3, 4]); -} - -fn foo11(x: TupleHolder<{ (6, true) }>) { - assert_eq!(x.0, (6, true)); -} - -fn main() { - let f: fn(Struct1<2>) = foo1; - f(Struct1([1, 2])); - let f: fn(&Struct1<4>) = foo2; - f(&Struct1([1, 2, 3, 4])); - let f: fn(BoolHolder) = foo3; - f(BoolHolder(true)); - let f: fn(IntHolder<-1>) = foo4; - f(IntHolder(-1)); - let f: fn(CharHolder<'x'>) = foo5; - f(CharHolder('x')); - let f: fn(StrHolder<"hello">) = foo6; - f(StrHolder("hello")); - let f: fn(StructHolder<{ Struct2 { x: 1, y: 2 } }>) = foo7; - f(StructHolder(Struct2 { x: 1, y: 2 })); - let f: fn(EnumHolder<{ Enum1::Variant1 }>) = foo8; - f(EnumHolder(Enum1::Variant1)); - let f: fn(EnumHolder<{ Enum1::Variant2(5) }>) = foo9; - f(EnumHolder(Enum1::Variant2(5))); - let f: fn(ArrayHolder<{ [3, 4] }>) = foo10; - f(ArrayHolder([3, 4])); - let f: fn(TupleHolder<{ (6, true) }>) = foo11; - f(TupleHolder((6, true))); -} diff --git a/tests/ui/sanitizer/kcfi/fn-trait-objects.rs b/tests/ui/sanitizer/kcfi/fn-trait-objects.rs deleted file mode 100644 index 3f6b78545a0a1..0000000000000 --- a/tests/ui/sanitizer/kcfi/fn-trait-objects.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Verifies that types that implement the Fn, FnMut, or FnOnce traits can be -// called through their trait methods. -// -//@ needs-sanitizer-kcfi -//@ only-linux -//@ ignore-backends: gcc -//@ compile-flags: -Ctarget-feature=-crt-static -Zpanic_abort_tests -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer --test -//@ run-pass - -#![feature(fn_traits)] -#![feature(unboxed_closures)] - -fn foo(_a: u32) {} - -#[test] -fn test_fn_trait() { - let f: Box = Box::new(foo); - Fn::call(&f, (0,)); -} - -#[test] -fn test_fnmut_trait() { - let mut a = 0; - let mut f: Box = Box::new(|x| a += x); - FnMut::call_mut(&mut f, (1,)); -} - -#[test] -fn test_fnonce_trait() { - let f: Box = Box::new(foo); - FnOnce::call_once(f, (2,)); -} diff --git a/tests/ui/sanitizer/kcfi/works-with-associated-types.rs b/tests/ui/sanitizer/kcfi/works-with-associated-types.rs new file mode 100644 index 0000000000000..016b0c29568d1 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-associated-types.rs @@ -0,0 +1,52 @@ +// Verifies that trait methods can be called through trait objects with +// associated types. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + type Output; + fn foo(&self) -> Self::Output; +} + +struct Type1; + +impl Trait1 for Type1 { + type Output = i32; + // ::foo is transformed into as Trait1>::foo + fn foo(&self) -> Self::Output { + 1 + } +} + +trait Trait2 { + type Output<'a> + where + Self: Sized; + + fn bar(&self) -> i32; +} + +impl Trait2 for () { + type Output<'a> + = () + where + Self: Sized; + + // <() as Trait2>::bar is transformed into ::bar + fn bar(&self) -> i32 { + 2 + } +} + +fn main() { + let x: &dyn Trait1 = &Type1; + // The virtual method call is transformed into as Trait1>::foo + assert_eq!(x.foo(), 1); + let x: &dyn Trait2 = &(); + // The virtual method call is transformed into ::bar + assert_eq!(x.bar(), 2); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-async-closures.rs b/tests/ui/sanitizer/kcfi/works-with-async-closures.rs new file mode 100644 index 0000000000000..ad12c80a7709c --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-async-closures.rs @@ -0,0 +1,38 @@ +// Verifies that async closures can be called, including through dyn FnOnce +// trait objects. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ edition: 2021 +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(async_fn_traits)] + +use std::future::Future; +use std::ops::AsyncFn; +use std::pin::pin; +use std::task::{Context, Poll, Waker}; + +#[inline(never)] +fn identity(x: T) -> T { + x +} + +fn poll(future: F) -> Poll { + pin!(future).poll(&mut Context::from_waker(Waker::noop())) +} + +fn main() { + // The coroutine-closure is transformed into _ as FnOnce<()>>::call_once + let f = identity(async || 1); + assert_eq!(poll(f.async_call(())), Poll::Ready(1)); + assert_eq!(poll(f()), Poll::Ready(1)); + // The ConstructCoroutineInClosureShim and the VTableShim for + // <{async closure} as FnOnce<()>>::call_once are transformed into + // _ as FnOnce<()>>::call_once. + let g: Box _> = Box::new(f) as _; + // The virtual method call is transformed into _ as FnOnce<()>>::call_once + assert_eq!(poll(g()), Poll::Ready(1)); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-auto-traits.rs b/tests/ui/sanitizer/kcfi/works-with-auto-traits.rs new file mode 100644 index 0000000000000..c815561a1e70c --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-auto-traits.rs @@ -0,0 +1,30 @@ +// Verifies that trait object methods can be called on trait objects with +// additional auto traits. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + fn foo(&self) -> i32; +} + +struct Type1; + +impl Trait1 for Type1 { + // ::foo is transformed into ::foo + fn foo(&self) -> i32 { + 1 + } +} + +fn main() { + let x: &(dyn Trait1 + Send) = &Type1; + // ::foo is transformed into ::foo + assert_eq!(x.foo(), 1); + let x: &(dyn Trait1 + Send + Sync) = &Type1; + // ::foo is transformed into ::foo + assert_eq!(x.foo(), 1); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-c-variadic.rs b/tests/ui/sanitizer/kcfi/works-with-c-variadic.rs new file mode 100644 index 0000000000000..34ffebaa142c2 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-c-variadic.rs @@ -0,0 +1,25 @@ +// Verifies that C variadic trait methods can be called through function +// pointers. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + // ::foo is transformed into ::foo + unsafe extern "C" fn foo(x: i32, y: i32, mut ap: ...) -> i32 { + x + y + ap.next_arg::() + ap.next_arg::() + } +} + +struct Type1; + +impl Trait1 for Type1 {} + +fn main() { + let f = std::hint::black_box(Type1::foo as unsafe extern "C" fn(i32, i32, ...) -> i32); + // The indirect call is not transformed, as the type id is encoded from the fn pointer type + assert_eq!(unsafe { f(1, 2, 3, 4) }, 1 + 2 + 3 + 4); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-cfi-encoding.rs b/tests/ui/sanitizer/kcfi/works-with-cfi-encoding.rs new file mode 100644 index 0000000000000..96e87932a2186 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-cfi-encoding.rs @@ -0,0 +1,45 @@ +// Verifies that user-defined CFI encodings can be used. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(cfi_encoding, extern_types)] + +#[cfi_encoding = "3Foo"] +struct Type1(i32); + +unsafe extern "C" { + #[cfi_encoding = "3Bar"] + type Type2; +} + +// Type3 is not transformed, as it has an user-defined CFI encoding +#[cfi_encoding = "3Baz"] +#[repr(transparent)] +struct Type3(i32); + +fn foo1(x: Type1) -> i32 { + assert_eq!(x.0, 1); + 1 +} + +fn foo2(_: *const Type2) -> i32 { + 2 +} + +fn foo3(x: Type3) -> i32 { + assert_eq!(x.0, 3); + 3 +} + +fn main() { + let f: fn(Type1) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(Type1(1)), 1); + let f: fn(*const Type2) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(&() as *const () as *const Type2), 2); + let f: fn(Type3) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(Type3(3)), 3); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-clone.rs b/tests/ui/sanitizer/kcfi/works-with-clone.rs new file mode 100644 index 0000000000000..2d60e379279f1 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-clone.rs @@ -0,0 +1,20 @@ +// Verifies that types with builtin Clone implementations (i.e., arrays, tuples, +// and closures) can be cloned. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +fn main() { + // The CloneShims for the values below are not transformed, as the Clone trait is not dyn + // compatible. + let array = [1i32, 2, 3]; + assert_eq!(array.clone(), array); + let tuple = (1i32, 2u8); + assert_eq!(tuple.clone(), tuple); + let x = 1i32; + let closure = move || x; + assert_eq!(closure.clone()(), closure()); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-closures.rs b/tests/ui/sanitizer/kcfi/works-with-closures.rs new file mode 100644 index 0000000000000..3348114760237 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-closures.rs @@ -0,0 +1,88 @@ +// Verifies that closures can be called through various forms of dynamic calls +// (i.e., through trait objects of the Fn, FnMut, and FnOnce traits, and as +// function pointers). +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(fn_traits)] +#![feature(unboxed_closures)] + +fn foo1<'a, T>() -> Box &'a T> { + // The closure is transformed into &T as Fn<(&T,)>>::call + Box::new(|x| x) +} + +fn use_fnmut i32>(mut f: F) -> i32 { + // The virtual method call is transformed into i32 as Fn<()>>::call + f() +} + +fn use_closure(call: extern "rust-call" fn(&C, ()) -> i32, f: &C) -> i32 { + // The indirect call is not transformed, as the type id is encoded from the fn pointer type + call(f, ()) +} + +fn use_closure_once(call: extern "rust-call" fn(C, ()) -> i32, f: C) -> i32 { + // The indirect call is not transformed, as the type id is encoded from the fn pointer type + call(f, ()) +} + +fn main() { + // Closures with parameters, through a dyn Fn trait object + let x = 1; + let f = foo1(); + // The virtual method call is transformed into &T as Fn<(&T,)>>::call + assert_eq!(*f(&x), 1); + + // Closures, through the Fn trait method + // The closure is transformed into i32 as Fn<()>>::call + let f: &dyn Fn() -> i32 = &(|| 2) as _; + // The virtual method call is transformed into i32 as Fn<()>>::call + assert_eq!(f.call(()), 2); + + // Fn closures passed where FnMut is expected + // The closure is transformed into i32 as Fn<()>>::call + let f: &dyn Fn() -> i32 = &(|| 3) as _; + assert_eq!(use_fnmut(f), 3); + + // FnOnce closures, through a dyn FnOnce trait object + // i32 as FnOnce<()>>::call_once receives an unsizeable `self: Self`, so the + // VTableShim for it in the vtable is transformed into + // i32 as FnOnce<()>>::call_once. + let f: Box i32> = Box::new(|| 4) as _; + // The virtual method call is transformed into i32 as FnOnce<()>>::call_once + assert_eq!(f(), 4); + + // Closures that move out of a capture, and so are FnOnce and not Fn or FnMut + let x = Box::new(5); + // The closure is transformed into i32 as FnOnce<()>>::call_once + let f: Box i32> = Box::new(move || { + drop(x); + 5 + }); + assert_eq!(f(), 5); + + // Closures cast to function pointers + // The closure is transformed into i32 as Fn<()>>::call + let f: fn() -> i32 = std::hint::black_box(|| 6); + // The indirect call is not transformed, as the type id is encoded from the fn pointer type + assert_eq!(f(), 6); + + // Closures with Fn::call cast to function pointers + let x = 7; + // The closure is transformed into i32 as Fn<()>>::call + let f = || x; + let call = std::hint::black_box(Fn::<()>::call); + assert_eq!(use_closure(call, &f), 7); + + // Closures with FnOnce::call_once cast to function pointers + // The closure is transformed into i32 as Fn<()>>::call + let g = || 8; + // The ClosureOnceShim is not transformed, as it can not be called through a vtable + let call = std::hint::black_box(FnOnce::<()>::call_once); + assert_eq!(use_closure_once(call, g), 8); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-const-generics.rs b/tests/ui/sanitizer/kcfi/works-with-const-generics.rs new file mode 100644 index 0000000000000..03c4bf40779d7 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-const-generics.rs @@ -0,0 +1,128 @@ +// Verifies that functions with types with const generics as argument types can +// be called through function pointers. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(adt_const_params)] +#![feature(unsized_const_params)] +#![allow(incomplete_features)] + +use std::marker::ConstParamTy; + +#[derive(PartialEq, Eq, ConstParamTy)] +struct Struct1 { + x: u16, + y: u16, +} + +#[derive(PartialEq, Eq, ConstParamTy)] +enum Enum1 { + Variant1, + Variant2(u8), +} + +struct Struct2([i32; N]); + +struct Struct3(bool); + +struct Struct4(i32); + +struct Struct5(char); + +struct Struct6(&'static str); + +struct Struct7(Struct1); + +struct Struct8(Enum1); + +struct Struct9([u16; 2]); + +struct Struct10((u16, bool)); + +fn foo1(x: Struct2<2>) -> i32 { + assert_eq!(x.0, [1, 2]); + 1 +} + +fn foo2(x: &Struct2<4>) -> i32 { + assert_eq!(x.0, [1, 2, 3, 4]); + 2 +} + +fn foo3(x: Struct3) -> i32 { + assert!(x.0); + 3 +} + +fn foo4(x: Struct4<-1>) -> i32 { + assert_eq!(x.0, -1); + 4 +} + +fn foo5(x: Struct5<'x'>) -> i32 { + assert_eq!(x.0, 'x'); + 5 +} + +fn foo6(x: Struct6<"hello">) -> i32 { + assert_eq!(x.0, "hello"); + 6 +} + +fn foo7(x: Struct7<{ Struct1 { x: 1, y: 2 } }>) -> i32 { + assert_eq!(x.0.x, 1); + assert_eq!(x.0.y, 2); + 7 +} + +fn foo8(x: Struct8<{ Enum1::Variant1 }>) -> i32 { + assert!(matches!(x.0, Enum1::Variant1)); + 8 +} + +fn foo9(x: Struct8<{ Enum1::Variant2(5) }>) -> i32 { + match x.0 { + Enum1::Variant1 => unreachable!(), + Enum1::Variant2(v) => assert_eq!(v, 5), + } + 9 +} + +fn foo10(x: Struct9<{ [3, 4] }>) -> i32 { + assert_eq!(x.0, [3, 4]); + 10 +} + +fn foo11(x: Struct10<{ (6, true) }>) -> i32 { + assert_eq!(x.0, (6, true)); + 11 +} + +fn main() { + let f: fn(Struct2<2>) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(Struct2([1, 2])), 1); + let f: fn(&Struct2<4>) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(&Struct2([1, 2, 3, 4])), 2); + let f: fn(Struct3) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(Struct3(true)), 3); + let f: fn(Struct4<-1>) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(Struct4(-1)), 4); + let f: fn(Struct5<'x'>) -> i32 = std::hint::black_box(foo5); + assert_eq!(f(Struct5('x')), 5); + let f: fn(Struct6<"hello">) -> i32 = std::hint::black_box(foo6); + assert_eq!(f(Struct6("hello")), 6); + let f: fn(Struct7<{ Struct1 { x: 1, y: 2 } }>) -> i32 = std::hint::black_box(foo7); + assert_eq!(f(Struct7(Struct1 { x: 1, y: 2 })), 7); + let f: fn(Struct8<{ Enum1::Variant1 }>) -> i32 = std::hint::black_box(foo8); + assert_eq!(f(Struct8(Enum1::Variant1)), 8); + let f: fn(Struct8<{ Enum1::Variant2(5) }>) -> i32 = std::hint::black_box(foo9); + assert_eq!(f(Struct8(Enum1::Variant2(5))), 9); + let f: fn(Struct9<{ [3, 4] }>) -> i32 = std::hint::black_box(foo10); + assert_eq!(f(Struct9([3, 4])), 10); + let f: fn(Struct10<{ (6, true) }>) -> i32 = std::hint::black_box(foo11); + assert_eq!(f(Struct10((6, true))), 11); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-coroutines.rs b/tests/ui/sanitizer/kcfi/works-with-coroutines.rs new file mode 100644 index 0000000000000..d259c93bba20f --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-coroutines.rs @@ -0,0 +1,94 @@ +// Verifies that coroutines (i.e., coroutines, async functions, gen functions, +// and async gen functions) can be called through their trait objects, and that +// functions with coroutine types as argument types can be called through +// function pointers. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ edition: 2024 +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(coroutines, stmt_expr_attributes)] +#![feature(coroutine_trait)] +#![feature(gen_blocks)] +#![feature(async_iterator)] + +use std::async_iter::AsyncIterator; +use std::ops::{Coroutine, CoroutineState}; +use std::pin::{Pin, pin}; +use std::task::{Context, Poll, Waker}; + +// The async fn coroutine is transformed into as Future>::poll +async fn async_fn() -> i32 { + 3 +} + +// The gen fn coroutine is transformed into as Iterator>::next +gen fn gen_fn() -> i32 { + yield 5; +} + +// The async gen fn coroutine is transformed into +// as AsyncIterator>::poll_next. +async gen fn async_gen_fn() -> i32 { + yield 6; +} + +fn generic_coroutine>(_: T) -> i32 { + 7 +} + +fn main() { + // Coroutines + // The coroutine is transformed into + // as Coroutine>::resume. + let coro = #[coroutine] + |_: i32| { + yield 1; + 2 + }; + let mut abstract_coro: Pin<&mut dyn Coroutine> = pin!(coro); + // The virtual method call is transformed into + // as Coroutine>::resume. + assert_eq!(abstract_coro.as_mut().resume(1), CoroutineState::Yielded(1)); + // The virtual method call is transformed into + // as Coroutine>::resume. + assert_eq!(abstract_coro.as_mut().resume(2), CoroutineState::Complete(2)); + + // Async fn coroutines + let f: fn() -> Pin>> = + std::hint::black_box(|| Box::pin(async_fn())); + // The virtual method call is transformed into as Future>::poll + assert_eq!(f().as_mut().poll(&mut Context::from_waker(Waker::noop())), Poll::Ready(3)); + + // Async block coroutines + // The async block coroutine is transformed into as Future>::poll + let g = async { + f().await; + 4 + }; + assert_eq!(pin!(g).poll(&mut Context::from_waker(Waker::noop())), Poll::Ready(4)); + + // Gen fn coroutines + let f: fn() -> Box> = std::hint::black_box(|| Box::new(gen_fn())); + // The virtual method call is transformed into as Iterator>::next + assert_eq!(f().next(), Some(5)); + + // Async gen fn coroutines + let f: fn() -> Pin>> = + std::hint::black_box(|| Box::pin(async_gen_fn())); + // The virtual method call is transformed into + // as AsyncIterator>::poll_next. + assert_eq!( + f().as_mut().poll_next(&mut Context::from_waker(Waker::noop())), + Poll::Ready(Some(6)) + ); + + // Concrete coroutine types + // The concrete coroutine type, and not a trait object of it, is used in the signature, so + // ty::CoroutineWitness is encoded (see issue #111184) + let f: fn(_) -> i32 = std::hint::black_box(generic_coroutine); + assert_eq!(f(async_fn()), 7); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-drop-in-place.rs b/tests/ui/sanitizer/kcfi/works-with-drop-in-place.rs new file mode 100644 index 0000000000000..c036d724adf3a --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-drop-in-place.rs @@ -0,0 +1,26 @@ +// Verifies that drops can be called on arbitrary trait objects, including trait +// objects without a principal trait. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +// A type without a Drop implementation +struct Type1; + +// A type with a Drop implementation +struct Type2; + +impl Drop for Type2 { + fn drop(&mut self) {} +} + +fn main() { + // Dropping the values below calls the drop glue of their types through the vtable of the + // dyn Send trait object (i.e., a trait object without a principal trait). Both the drop + // glue and the virtual drop calls to it are transformed into drop_in_place::. + let _ = Box::new(Type1) as Box; + let _ = Box::new(Type2) as Box; +} diff --git a/tests/ui/sanitizer/kcfi/works-with-fn-ptr-addr.rs b/tests/ui/sanitizer/kcfi/works-with-fn-ptr-addr.rs new file mode 100644 index 0000000000000..f9d5c80297934 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-fn-ptr-addr.rs @@ -0,0 +1,22 @@ +// Verifies that the addresses of function pointers can be compared (i.e., +// through the compiler-generated FnPtr implementations for them). +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +fn foo1() {} + +fn foo2() {} + +fn main() { + let f: fn() = std::hint::black_box(foo1); + let g: fn() = std::hint::black_box(foo1); + let h: fn() = std::hint::black_box(foo2); + // The ::addr FnPtrAddrShims are not transformed, as the FnPtr trait is not + // dyn compatible. + assert!(std::ptr::fn_addr_eq(f, g)); + assert!(!std::ptr::fn_addr_eq(f, h)); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-fn-ptr-casts.rs b/tests/ui/sanitizer/kcfi/works-with-fn-ptr-casts.rs new file mode 100644 index 0000000000000..282e54ab266d6 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-fn-ptr-casts.rs @@ -0,0 +1,69 @@ +// Verifies that methods and functions can be cast to function pointers. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +fn foo1(_: &Type2) -> i32 { + 1 +} + +trait Trait1 { + fn foo(&self) -> i32; +} + +struct Type1; + +impl Trait1 for Type1 { + fn foo(&self) -> i32 { + 2 + } +} + +trait Trait2 { + fn foo(&self) -> i32; + fn bar(&self) -> i32; +} + +struct Type2; + +impl Trait2 for Type2 { + fn foo(&self) -> i32 { + 3 + } + #[track_caller] + fn bar(&self) -> i32 { + 4 + } +} + +fn main() { + // Trait method implementations cast to function pointers + // The methods below are transformed, but KCFI can attach one type id to a function only, so + // they are reified, and the ReifyShims for them are not transformed, as they are created with + // ReifyReason::FnPtr (i.e., encoded with the USE_CONCRETE_SELF option), which are the ones + // tested at the indirect calls. + let f: fn(&Type1) -> i32 = std::hint::black_box(::foo); + assert_eq!(f(&Type1), 2); + let f: fn(&Type2) -> i32 = std::hint::black_box(::foo); + assert_eq!(f(&Type2), 3); + + // Non-method functions cast to function pointers + // foo1 is not transformed, as it is not a trait method or a closure-like + let f: fn(&Type2) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(&Type2), 1); + + // Trait method implementations with #[track_caller] cast to function pointers + // The ReifyShim for bar is not transformed, as it is created with ReifyReason::FnPtr + let f: fn(&Type2) -> i32 = std::hint::black_box(::bar); + assert_eq!(f(&Type2), 4); + + // Trait method implementations with #[track_caller], through a vtable + // The ReifyShim for bar in the vtable is transformed into ::bar, as bar + // is #[track_caller] and is reified. + let x = &Type2 as &dyn Trait2; + // The virtual method call is transformed into ::bar + assert_eq!(x.bar(), 4); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-fn-trait-objects.rs b/tests/ui/sanitizer/kcfi/works-with-fn-trait-objects.rs new file mode 100644 index 0000000000000..a23b4f11cef14 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-fn-trait-objects.rs @@ -0,0 +1,41 @@ +// Verifies that types that implement the Fn, FnMut, or FnOnce traits can be +// called through their trait methods. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(fn_traits)] +#![feature(unboxed_closures)] + +fn foo1(x: i32) -> i32 { + x +} + +fn main() { + // Types that implement Fn + // The i32 as Fn<(i32,)>>::call FnPtrShim in the vtable is transformed into + // i32 as Fn<(i32,)>>::call. + let f: Box i32> = Box::new(foo1); + // The virtual method call is transformed into i32 as Fn<(i32,)>>::call + assert_eq!(Fn::call(&f, (1,)), 1); + + // Types that implement FnMut + let mut a = 0; + // The closure is transformed into >::call_mut + let mut f: Box = Box::new(|x| a += x); + // The virtual method call is transformed into >::call_mut + FnMut::call_mut(&mut f, (2,)); + drop(f); + assert_eq!(a, 2); + + // Types that implement FnOnce + // The i32 as FnOnce<(i32,)>>::call_once FnPtrShim in the vtable is transformed + // into i32 as FnOnce<(i32,)>>::call_once. + let f: Box i32> = Box::new(foo1); + // The virtual method call is transformed into + // i32 as FnOnce<(i32,)>>::call_once. + assert_eq!(FnOnce::call_once(f, (3,)), 3); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-function-types.rs b/tests/ui/sanitizer/kcfi/works-with-function-types.rs new file mode 100644 index 0000000000000..2b8ea113fce25 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-function-types.rs @@ -0,0 +1,40 @@ +// Verifies that functions with function types (i.e., function pointers) as +// argument types can be called through function pointers. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +fn bar1(x: i32) -> i32 { + x +} + +unsafe fn bar2() {} + +extern "C" fn bar3() {} + +fn foo1(f: fn(i32) -> i32) -> i32 { + assert_eq!(f(1), 1); + 1 +} + +fn foo2(f: unsafe fn()) -> i32 { + unsafe { f() }; + 2 +} + +fn foo3(f: extern "C" fn()) -> i32 { + f(); + 3 +} + +fn main() { + let f: fn(fn(i32) -> i32) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(bar1), 1); + let f: fn(unsafe fn()) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(bar2), 2); + let f: fn(extern "C" fn()) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(bar3), 3); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-generalized-pointers.rs b/tests/ui/sanitizer/kcfi/works-with-generalized-pointers.rs new file mode 100644 index 0000000000000..bb280e946e2f5 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-generalized-pointers.rs @@ -0,0 +1,52 @@ +// Verifies that functions that differ only in the pointee types of their +// pointer arguments can be called through function pointers when compiling with +// -Zsanitizer-cfi-generalize-pointers. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Zsanitizer-cfi-generalize-pointers -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +fn foo1(_: *const i32) -> i32 { + 1 +} + +fn foo2(_: *mut i32) -> i32 { + 2 +} + +fn foo3(_: &i32) -> i32 { + 3 +} + +fn foo4(_: &mut i32) -> i32 { + 4 +} + +fn foo5(_: fn(i32) -> i32) -> i32 { + 5 +} + +fn main() { + // Pointers and references are generalized to *const (), so the type ids encoded for the + // functions above and for the fn pointer types below are the same and match. + let mut x = 0; + let f: fn(*const i32) -> i32 = std::hint::black_box(foo1); + let f: fn(*const i8) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(&x as *const i32 as *const i8), 1); + let f: fn(*mut i32) -> i32 = std::hint::black_box(foo2); + let f: fn(*mut i8) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(&mut x as *mut i32 as *mut i8), 2); + let f: fn(&i32) -> i32 = std::hint::black_box(foo3); + let f: fn(&i8) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(unsafe { &*(&x as *const i32 as *const i8) }), 3); + let f: fn(&mut i32) -> i32 = std::hint::black_box(foo4); + let f: fn(&mut i8) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(unsafe { &mut *(&mut x as *mut i32 as *mut i8) }), 4); + + // Function pointers are generalized to *const () as well + let f: fn(fn(i32) -> i32) -> i32 = std::hint::black_box(foo5); + let f: fn(fn(u64) -> u64) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(|x| x), 5); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-intrinsics.rs b/tests/ui/sanitizer/kcfi/works-with-intrinsics.rs new file mode 100644 index 0000000000000..98b757f633550 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-intrinsics.rs @@ -0,0 +1,27 @@ +// Verifies that intrinsics and LLVM intrinsics can be called. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(abi_unadjusted, link_llvm_intrinsics)] +#![allow(internal_features)] + +unsafe extern "unadjusted" { + #[link_name = "llvm.bitreverse.i32"] + fn bitreverse(x: i32) -> i32; +} + +fn main() { + // Intrinsics + // The black_box intrinsic (i.e., a fn item with #[rustc_intrinsic]) is not transformed, as + // it can not be reified or called indirectly. + assert_eq!(std::hint::black_box(1i32), 1); + + // LLVM intrinsics + // The bitreverse LLVM intrinsic (i.e., a fn item with extern "unadjusted") is not + // transformed, as it can not be reified or called indirectly. + assert_eq!(unsafe { bitreverse(1i32) }, i32::MIN); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-lifetimes.rs b/tests/ui/sanitizer/kcfi/works-with-lifetimes.rs new file mode 100644 index 0000000000000..25660abd6fda0 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-lifetimes.rs @@ -0,0 +1,46 @@ +// Verifies that functions with lifetimes and higher-ranked trait bounds as +// argument types can be called through function pointers and trait objects. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +fn bar1(_: &i32) {} + +fn bar2(_: &i32, _: &i32) {} + +fn bar3(_: &dyn for<'b> Fn(&'b i32)) {} + +fn foo1(f: &dyn for<'a> Fn(&'a i32)) -> i32 { + f(&1); + 1 +} + +fn foo2(f: for<'a> fn(&'a i32)) -> i32 { + f(&2); + 2 +} + +fn foo3(f: for<'a, 'b> fn(&'a i32, &'b i32)) -> i32 { + f(&3, &4); + 3 +} + +// A higher-ranked trait bound nested in a higher-ranked function pointer type +fn foo4(f: for<'a> fn(&'a dyn for<'b> Fn(&'b i32))) -> i32 { + f(&|_x: &i32| {}); + 4 +} + +fn main() { + let f: fn(&dyn for<'a> Fn(&'a i32)) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(&|_x: &i32| {}), 1); + let f: fn(for<'a> fn(&'a i32)) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(bar1), 2); + let f: fn(for<'a, 'b> fn(&'a i32, &'b i32)) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(bar2), 3); + let f: fn(for<'a> fn(&'a dyn for<'b> Fn(&'b i32))) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(bar3), 4); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-normalized-integers.rs b/tests/ui/sanitizer/kcfi/works-with-normalized-integers.rs new file mode 100644 index 0000000000000..73a87dc687397 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-normalized-integers.rs @@ -0,0 +1,57 @@ +// Verifies that functions with bool and char argument types can be called +// through function pointers with u8 and u32 argument types when compiling with +// -Zsanitizer-cfi-normalize-integers. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Zsanitizer-cfi-normalize-integers -Cunsafe-allow-abi-mismatch=sanitizer,sanitizer-cfi-normalize-integers +//@ run-pass + +fn foo1(_: bool) -> i32 { + 1 +} + +fn foo2(_: char) -> i32 { + 2 +} + +fn foo3(_: isize) -> i32 { + 3 +} + +fn foo4(_: usize) -> i32 { + 4 +} + +#[cfg(target_pointer_width = "16")] +type Isize = i16; +#[cfg(target_pointer_width = "32")] +type Isize = i32; +#[cfg(target_pointer_width = "64")] +type Isize = i64; + +#[cfg(target_pointer_width = "16")] +type Usize = u16; +#[cfg(target_pointer_width = "32")] +type Usize = u32; +#[cfg(target_pointer_width = "64")] +type Usize = u64; + +fn main() { + // bool is normalized to u8 and char to u32 + let f: fn(bool) -> i32 = std::hint::black_box(foo1); + let f: fn(u8) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(true as u8), 1); + let f: fn(char) -> i32 = std::hint::black_box(foo2); + let f: fn(u32) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f('a' as u32), 2); + + // isize and usize are normalized to the integer of the target pointer width + let f: fn(isize) -> i32 = std::hint::black_box(foo3); + let f: fn(Isize) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(3), 3); + let f: fn(usize) -> i32 = std::hint::black_box(foo4); + let f: fn(Usize) -> i32 = std::hint::black_box(unsafe { std::mem::transmute(f) }); + assert_eq!(f(4), 4); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-pattern-types.rs b/tests/ui/sanitizer/kcfi/works-with-pattern-types.rs new file mode 100644 index 0000000000000..78ddc7126baf9 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-pattern-types.rs @@ -0,0 +1,40 @@ +// Verifies that functions with pattern types as argument types can be called +// through function pointers. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(pattern_types)] +#![feature(pattern_type_macro)] + +use std::pat::pattern_type; + +fn foo1(x: pattern_type!(i32 is 1..)) -> i32 { + assert_eq!(unsafe { std::mem::transmute::<_, i32>(x) }, 1); + 1 +} + +fn foo2(x: pattern_type!(i32 is 1..=5)) -> i32 { + assert_eq!(unsafe { std::mem::transmute::<_, i32>(x) }, 2); + 2 +} + +fn foo3(x: pattern_type!(i32 is -5..=5)) -> i32 { + assert_eq!(unsafe { std::mem::transmute::<_, i32>(x) }, -3); + 3 +} + +fn main() { + let x: pattern_type!(i32 is 1..) = unsafe { std::mem::transmute(1i32) }; + let f: fn(pattern_type!(i32 is 1..)) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(x), 1); + let x: pattern_type!(i32 is 1..=5) = unsafe { std::mem::transmute(2i32) }; + let f: fn(pattern_type!(i32 is 1..=5)) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(x), 2); + let x: pattern_type!(i32 is -5..=5) = unsafe { std::mem::transmute(-3i32) }; + let f: fn(pattern_type!(i32 is -5..=5)) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(x), 3); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-pointer-types.rs b/tests/ui/sanitizer/kcfi/works-with-pointer-types.rs new file mode 100644 index 0000000000000..a936a24f4afdb --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-pointer-types.rs @@ -0,0 +1,50 @@ +// Verifies that functions with pointer types as argument types can be called +// through function pointers. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +use std::ffi::c_void; + +fn foo1(_: &i32) -> i32 { + 1 +} +fn foo2(_: &mut i32) -> i32 { + 2 +} +fn foo3(_: *const i32) -> i32 { + 3 +} +fn foo4(_: *mut i32) -> i32 { + 4 +} +fn foo5(_: *const c_void) -> i32 { + 5 +} +fn foo6(_: *mut c_void) -> i32 { + 6 +} +fn foo7(_: &&i32) -> i32 { + 7 +} + +fn main() { + let mut x = 0; + let f: fn(&i32) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(&x), 1); + let f: fn(&mut i32) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(&mut x), 2); + let f: fn(*const i32) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(&x as *const i32), 3); + let f: fn(*mut i32) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(&mut x as *mut i32), 4); + let f: fn(*const c_void) -> i32 = std::hint::black_box(foo5); + assert_eq!(f(&x as *const i32 as *const c_void), 5); + let f: fn(*mut c_void) -> i32 = std::hint::black_box(foo6); + assert_eq!(f(&mut x as *mut i32 as *mut c_void), 6); + let f: fn(&&i32) -> i32 = std::hint::black_box(foo7); + assert_eq!(f(&&0), 7); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-primitive-types.rs b/tests/ui/sanitizer/kcfi/works-with-primitive-types.rs new file mode 100644 index 0000000000000..aa8f06205a69f --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-primitive-types.rs @@ -0,0 +1,115 @@ +// Verifies that functions with primitive types as argument and return types can +// be called through function pointers. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(f128)] +#![feature(f16)] +fn foo1(_: ()) -> i32 { + 1 +} +fn foo2(_: bool) -> i32 { + 2 +} +fn foo3(_: char) -> i32 { + 3 +} +fn foo4(_: f32) -> i32 { + 4 +} +fn foo5(_: f64) -> i32 { + 5 +} +fn foo6(_: i8) -> i32 { + 6 +} +fn foo7(_: i16) -> i32 { + 7 +} +fn foo8(_: i32) -> i32 { + 8 +} +fn foo9(_: i64) -> i32 { + 9 +} +fn foo10(_: i128) -> i32 { + 10 +} +fn foo11(_: isize) -> i32 { + 11 +} +fn foo12(_: u8) -> i32 { + 12 +} +fn foo13(_: u16) -> i32 { + 13 +} +fn foo14(_: u32) -> i32 { + 14 +} +fn foo15(_: u64) -> i32 { + 15 +} +fn foo16(_: u128) -> i32 { + 16 +} +fn foo17(_: usize) -> i32 { + 17 +} +fn foo18(_: f16) -> i32 { + 18 +} +fn foo19(_: f128) -> i32 { + 19 +} +fn foo20() -> ! { + std::process::exit(0) +} + +fn main() { + let f: fn(()) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(()), 1); + let f: fn(bool) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(false), 2); + let f: fn(char) -> i32 = std::hint::black_box(foo3); + assert_eq!(f('a'), 3); + let f: fn(f32) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(0f32), 4); + let f: fn(f64) -> i32 = std::hint::black_box(foo5); + assert_eq!(f(0f64), 5); + let f: fn(i8) -> i32 = std::hint::black_box(foo6); + assert_eq!(f(0i8), 6); + let f: fn(i16) -> i32 = std::hint::black_box(foo7); + assert_eq!(f(0i16), 7); + let f: fn(i32) -> i32 = std::hint::black_box(foo8); + assert_eq!(f(0i32), 8); + let f: fn(i64) -> i32 = std::hint::black_box(foo9); + assert_eq!(f(0i64), 9); + let f: fn(i128) -> i32 = std::hint::black_box(foo10); + assert_eq!(f(0i128), 10); + let f: fn(isize) -> i32 = std::hint::black_box(foo11); + assert_eq!(f(0isize), 11); + let f: fn(u8) -> i32 = std::hint::black_box(foo12); + assert_eq!(f(0u8), 12); + let f: fn(u16) -> i32 = std::hint::black_box(foo13); + assert_eq!(f(0u16), 13); + let f: fn(u32) -> i32 = std::hint::black_box(foo14); + assert_eq!(f(0u32), 14); + let f: fn(u64) -> i32 = std::hint::black_box(foo15); + assert_eq!(f(0u64), 15); + let f: fn(u128) -> i32 = std::hint::black_box(foo16); + assert_eq!(f(0u128), 16); + let f: fn(usize) -> i32 = std::hint::black_box(foo17); + assert_eq!(f(0usize), 17); + let f: fn(f16) -> i32 = std::hint::black_box(foo18); + assert_eq!(f(0f16), 18); + let f: fn(f128) -> i32 = std::hint::black_box(foo19); + assert_eq!(f(0f128), 19); + // The never type can only be returned, so this must be called last + let f: fn() -> ! = std::hint::black_box(foo20); + f(); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-receivers.rs b/tests/ui/sanitizer/kcfi/works-with-receivers.rs new file mode 100644 index 0000000000000..ee443cb4d1c16 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-receivers.rs @@ -0,0 +1,57 @@ +// Verifies that trait methods with custom receivers (e.g., Arc) can +// be called through trait objects. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +use std::pin::Pin; +use std::rc::Rc; +use std::sync::Arc; + +trait Trait1 { + fn foo(self: Arc) -> i32; + fn bar(self: Box) -> i32; + fn baz(self: Rc) -> i32; + fn qux(self: Pin<&mut Self>) -> i32; +} + +struct Type1; + +impl Trait1 for Type1 { + // ::foo is transformed into ::foo + fn foo(self: Arc) -> i32 { + 1 + } + // ::bar is transformed into ::bar + fn bar(self: Box) -> i32 { + 2 + } + // ::baz is transformed into ::baz + fn baz(self: Rc) -> i32 { + 3 + } + // ::qux is transformed into ::qux + fn qux(self: Pin<&mut Self>) -> i32 { + 4 + } +} + +fn main() { + let x: Arc = Arc::new(Type1); + // The virtual method call is transformed into ::foo + assert_eq!(x.foo(), 1); + let x: Box = Box::new(Type1); + // The virtual method call is transformed into ::bar + assert_eq!(x.bar(), 2); + let x: Rc = Rc::new(Type1); + // The virtual method call is transformed into ::baz + assert_eq!(x.baz(), 3); + let mut y = Type1; + let x: Pin<&mut Type1> = Pin::new(&mut y); + let x: Pin<&mut dyn Trait1> = x; + // The virtual method call is transformed into ::qux + assert_eq!(x.qux(), 4); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-repr-transparent-types.rs b/tests/ui/sanitizer/kcfi/works-with-repr-transparent-types.rs new file mode 100644 index 0000000000000..c6d8f042f032f --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-repr-transparent-types.rs @@ -0,0 +1,91 @@ +// Verifies that functions with repr(transparent) types (including +// self-referential repr(transparent) types) as argument types can be called +// through function pointers and trait objects. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +use std::marker::PhantomData; + +#[repr(transparent)] +struct Type1(i32); + +#[repr(transparent)] +struct Type2<'a>(&'a i32); + +trait Trait1 {} + +impl Trait1 for Type1 {} + +// A repr(transparent) type with a represented type that has regions +#[repr(transparent)] +struct Type3(Box); + +// A repr(transparent) type without a non-ZST field +#[repr(transparent)] +struct Type4(PhantomData); + +struct Struct1 { + _x: u8, + p: PhantomData, +} + +#[repr(transparent)] +struct Type5(Struct1); + +trait Trait2 { + fn foo(&self, x: Type5) -> i32; +} + +struct Type6; + +impl Trait2 for Type6 { + fn foo(&self, _: Type5) -> i32 { + 6 + } +} + +// A repr(transparent) type that is a pointer and references itself, which is generalized to avoid +// a reference cycle. +#[repr(transparent)] +struct Type7(*const Type7); + +fn foo1(x: Type1) -> i32 { + assert_eq!(x.0, 1); + 1 +} + +fn foo2(x: Type2<'_>) -> i32 { + assert_eq!(*x.0, 2); + 2 +} + +fn foo3(_: Type3) -> i32 { + 3 +} + +fn foo4(_: Type4) -> i32 { + 4 +} + +fn foo5(_: Type7) -> i32 { + 5 +} + +fn main() { + let f: fn(Type1) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(Type1(1)), 1); + let f: fn(Type2<'_>) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(Type2(&2)), 2); + let f: fn(Type3) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(Type3(Box::new(Type1(1)))), 3); + let f: fn(Type4) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(Type4(PhantomData)), 4); + let f: fn(Type7) -> i32 = std::hint::black_box(foo5); + assert_eq!(f(Type7(std::ptr::null())), 5); + let x = &Type6 as &dyn Trait2; + assert_eq!(x.foo(Type5(Struct1 { _x: 0, p: PhantomData })), 6); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-sequence-types.rs b/tests/ui/sanitizer/kcfi/works-with-sequence-types.rs new file mode 100644 index 0000000000000..6187004782ac5 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-sequence-types.rs @@ -0,0 +1,46 @@ +// Verifies that functions with sequence types as argument types can be called +// through function pointers. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +fn foo1(x: (i32, i32)) -> i32 { + assert_eq!(x, (1, 2)); + 1 +} + +fn foo2(x: [i32; 4]) -> i32 { + assert_eq!(x, [1, 2, 3, 4]); + 2 +} + +fn foo3(x: &[i32]) -> i32 { + assert_eq!(x, &[1, 2, 3]); + 3 +} + +fn foo4(x: &str) -> i32 { + assert_eq!(x, "foo"); + 4 +} + +fn foo5(x: [i32; 2 * 2]) -> i32 { + assert_eq!(x, [1, 2, 3, 4]); + 5 +} + +fn main() { + let f: fn((i32, i32)) -> i32 = std::hint::black_box(foo1); + assert_eq!(f((1, 2)), 1); + let f: fn([i32; 4]) -> i32 = std::hint::black_box(foo2); + assert_eq!(f([1, 2, 3, 4]), 2); + let f: fn(&[i32]) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(&[1, 2, 3]), 3); + let f: fn(&str) -> i32 = std::hint::black_box(foo4); + assert_eq!(f("foo"), 4); + let f: fn([i32; 2 * 2]) -> i32 = std::hint::black_box(foo5); + assert_eq!(f([1, 2, 3, 4]), 5); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-supertraits.rs b/tests/ui/sanitizer/kcfi/works-with-supertraits.rs new file mode 100644 index 0000000000000..86499d229908a --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-supertraits.rs @@ -0,0 +1,88 @@ +// Verifies that super-trait methods can be called through trait objects, and +// that trait objects can be upcast. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + type Output1; + fn foo(&self) -> Self::Output1; + // ::qux is transformed into as Trait1>::qux + fn qux(&self) -> i32 { + 4 + } +} + +trait Trait2 { + type Output2; + fn bar(&self) -> Self::Output2; +} + +trait Trait3: Trait1 + Trait2 { + type Output3; + fn baz(&self) -> Self::Output3; +} + +struct Type1; + +impl Trait1 for Type1 { + type Output1 = u16; + // ::foo is transformed into as Trait1>::foo + fn foo(&self) -> Self::Output1 { + 1 + } +} + +impl Trait2 for Type1 { + type Output2 = u32; + // ::bar is transformed into as Trait2>::bar + fn bar(&self) -> Self::Output2 { + 2 + } +} + +impl Trait3 for Type1 { + type Output3 = u8; + // ::baz is transformed into + // as Trait3>::baz. + fn baz(&self) -> Self::Output3 { + 3 + } +} + +fn main() { + // Methods of a trait and of its supertraits, through a child trait object + let x = &Type1 as &dyn Trait3; + // The virtual method call is transformed into + // as Trait3>::baz. + assert_eq!(x.baz(), 3); + // The virtual method call is transformed into as Trait1>::foo + assert_eq!(x.foo(), 1); + // The virtual method call is transformed into as Trait2>::bar + assert_eq!(x.bar(), 2); + // The virtual method call is transformed into as Trait1>::qux + assert_eq!(x.qux(), 4); + + // Methods of a supertrait, through a supertrait object + let y = &Type1 as &dyn Trait1; + // The virtual method call is transformed into as Trait1>::foo + assert_eq!(y.foo(), 1); + // The virtual method call is transformed into as Trait1>::qux + assert_eq!(y.qux(), 4); + let z = &Type1 as &dyn Trait2; + // The virtual method call is transformed into as Trait2>::bar + assert_eq!(z.bar(), 2); + + // Methods of a supertrait, through an upcast trait object + let x1 = x as &dyn Trait1; + // The virtual method call is transformed into as Trait1>::foo + assert_eq!(x1.foo(), 1); + // The virtual method call is transformed into as Trait1>::qux + assert_eq!(x1.qux(), 4); + let x2 = x as &dyn Trait2; + // The virtual method call is transformed into as Trait2>::bar + assert_eq!(x2.bar(), 2); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-symbol-mangling-v0.rs b/tests/ui/sanitizer/kcfi/works-with-symbol-mangling-v0.rs new file mode 100644 index 0000000000000..671cbe03d4a47 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-symbol-mangling-v0.rs @@ -0,0 +1,36 @@ +// Verifies that KCFI works with the v0 symbol mangling version (i.e., that the +// KCFI extra mangling works correctly on v0). +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Csymbol-mangling-version=v0 -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + fn foo(&self); +} + +struct Type1; + +impl Trait1 for Type1 { + fn foo(&self) {} +} + +struct Type2; + +impl Trait1 for Type2 { + #[track_caller] + fn foo(&self) {} +} + +fn main() { + // The ReifyShim for foo is not transformed, as it is created with ReifyReason::FnPtr + let f: fn(&Type1) = std::hint::black_box(Type1::foo); + f(&Type1); + // The ReifyShim for foo in the vtable is transformed into ::foo, as + // it is created with ReifyReason::Vtable. + let x = &Type2 as &dyn Trait1; + // The virtual method call is transformed into ::foo + x.foo(); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-thread-locals.rs b/tests/ui/sanitizer/kcfi/works-with-thread-locals.rs new file mode 100644 index 0000000000000..48a8a6af14b7b --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-thread-locals.rs @@ -0,0 +1,21 @@ +// Verifies that thread locals can be accessed (i.e., through the +// compiler-generated accessors for them). +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +use std::cell::Cell; + +// The ThreadLocalShim for the accessor below is not transformed, as it does not implement any +// trait method and can not be called through a vtable. +thread_local! { + static COUNTER: Cell = const { Cell::new(0) }; +} + +fn main() { + COUNTER.with(|counter| counter.set(counter.get() + 1)); + assert_eq!(COUNTER.with(|counter| counter.get()), 1); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-trait-objects.rs b/tests/ui/sanitizer/kcfi/works-with-trait-objects.rs new file mode 100644 index 0000000000000..3314f52443cc2 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-trait-objects.rs @@ -0,0 +1,66 @@ +// Verifies that trait methods (i.e., both trait method implementations in impl +// blocks and provided (default) trait methods in trait blocks) can be called +// through trait objects. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + fn foo(&self) -> i32; + // ::bar is transformed into ::bar + fn bar(&self) -> i32 { + 2 + } + // ::baz is not transformed, as it can not be called through a vtable + fn baz(&self) -> i32 + where + Self: Sized, + { + 3 + } +} + +trait Trait2 { + fn qux(&self) -> i32; +} + +struct Type1; + +impl Trait1 for Type1 { + // ::foo is transformed into ::foo + fn foo(&self) -> i32 { + 1 + } +} + +impl Trait2 for Type1 { + // ::qux is not transformed, as Trait2 is not dyn compatible + fn qux(&self) -> i32 { + 4 + } +} + +fn main() { + // Trait methods, through a reference to a trait object + let x = &Type1 as &dyn Trait1; + // The virtual method call is transformed into ::foo + assert_eq!(x.foo(), 1); + // The virtual method call is transformed into ::bar + assert_eq!(x.bar(), 2); + + // Trait methods, through a boxed trait object + let x: Box = Box::new(Type1); + // The virtual method call is transformed into ::foo + assert_eq!(x.foo(), 1); + // The virtual method call is transformed into ::bar + assert_eq!(x.bar(), 2); + + // Trait methods that can not be called through a vtable + // ::baz is not transformed, as it can not be called through a vtable + assert_eq!(Type1.baz::(), 3); + // ::qux is not transformed, as Trait2 is not dyn compatible + assert_eq!(Type1.qux::(), 4); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-trait-types.rs b/tests/ui/sanitizer/kcfi/works-with-trait-types.rs new file mode 100644 index 0000000000000..4ae02540b2917 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-trait-types.rs @@ -0,0 +1,51 @@ +// Verifies that functions with trait types (i.e., trait objects) as argument +// types can be called through function pointers. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + fn foo(&self) -> i32; +} + +struct Type1; + +impl Trait1 for Type1 { + fn foo(&self) -> i32 { + 1 + } +} + +fn foo1(x: &dyn Trait1) -> i32 { + assert_eq!(x.foo(), 1); + 1 +} + +fn foo2(x: &mut dyn Trait1) -> i32 { + assert_eq!(x.foo(), 1); + 2 +} + +fn foo3(x: Box) -> i32 { + assert_eq!(x.foo(), 1); + 3 +} + +// A trait object without a principal trait, so its predicates are all auto traits +fn foo4(_: &dyn Send) -> i32 { + 4 +} + +fn main() { + let f: fn(&dyn Trait1) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(&Type1), 1); + let f: fn(&mut dyn Trait1) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(&mut Type1), 2); + let f: fn(Box) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(Box::new(Type1)), 3); + let f: fn(&dyn Send) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(&Type1), 4); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-user-defined-types.rs b/tests/ui/sanitizer/kcfi/works-with-user-defined-types.rs new file mode 100644 index 0000000000000..b235736e18bb8 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-user-defined-types.rs @@ -0,0 +1,84 @@ +// Verifies that functions with user-defined types (i.e., structs, enums, +// unions, and extern types) as argument types can be called through function +// pointers. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(extern_types)] + +struct Struct1(i32); + +enum Enum1 { + Variant1(i32), +} + +union Union1 { + f: i32, +} + +#[repr(C)] +struct Struct2 { + f: i32, +} + +struct Struct3(T); + +unsafe extern "C" { + type Type1; +} + +fn foo1(x: Struct1) -> i32 { + assert_eq!(x.0, 1); + 1 +} + +fn foo2(x: Enum1) -> i32 { + let Enum1::Variant1(y) = x; + assert_eq!(y, 2); + 2 +} + +fn foo3(x: Union1) -> i32 { + assert_eq!(unsafe { x.f }, 3); + 3 +} + +fn foo4(x: Struct2) -> i32 { + assert_eq!(x.f, 4); + 4 +} + +fn foo5(x: Struct3) -> i32 { + assert_eq!(x.0, 5); + 5 +} + +fn foo6(_: *const Type1) -> i32 { + 6 +} + +extern "C" fn foo7(x: Struct2) -> i32 { + assert_eq!(x.f, 7); + 7 +} + +fn main() { + let f: fn(Struct1) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(Struct1(1)), 1); + let f: fn(Enum1) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(Enum1::Variant1(2)), 2); + let f: fn(Union1) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(Union1 { f: 3 }), 3); + let f: fn(Struct2) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(Struct2 { f: 4 }), 4); + let f: fn(Struct3) -> i32 = std::hint::black_box(foo5); + assert_eq!(f(Struct3(5)), 5); + let f: fn(*const Type1) -> i32 = std::hint::black_box(foo6); + assert_eq!(f(&() as *const () as *const Type1), 6); + let f: extern "C" fn(Struct2) -> i32 = std::hint::black_box(foo7); + assert_eq!(f(Struct2 { f: 7 }), 7); +}