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..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,13 +231,25 @@ impl<'tcx> TypeFolder> for TransformTy<'tcx> { } } +/// Returns whether a trait method may be called through a vtable. +fn may_be_called_through_vtable(tcx: TyCtxt<'_>, method_id: DefId) -> bool { + let trait_id = tcx.parent(method_id); + traits::is_vtable_safe_method(tcx, trait_id, tcx.associated_item(method_id)) + && tcx.is_dyn_compatible(trait_id) +} + +/// Returns the trait object type of a trait reference (i.e., a dyn Trait type with the trait as +/// its principal trait, and the associated types of the trait and its supertraits as its +/// projections), for self to be transformed into when performing type erasure. #[instrument(skip(tcx), ret)] fn trait_object_ty<'tcx>(tcx: TyCtxt<'tcx>, poly_trait_ref: ty::PolyTraitRef<'tcx>) -> Ty<'tcx> { - assert!(!poly_trait_ref.has_non_region_param()); - let principal_pred = poly_trait_ref.map_bound(|trait_ref| { + if poly_trait_ref.has_non_region_param() { + bug!("trait_object_ty: unexpected non-region param in `{:?}`", poly_trait_ref); + } + let principal_predicate = poly_trait_ref.map_bound(|trait_ref| { ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)) }); - let mut assoc_preds: Vec<_> = traits::supertraits(tcx, poly_trait_ref) + let mut assoc_predicates: Vec<_> = traits::supertraits(tcx, poly_trait_ref) .flat_map(|super_poly_trait_ref| { tcx.associated_items(super_poly_trait_ref.def_id()) .in_definition_order() @@ -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, @@ -268,11 +276,182 @@ 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), + ); + Ty::new_dynamic(tcx, predicates, tcx.lifetimes.re_erased) +} + +/// Performs type erasure for closure-likes (i.e., instances identified by the def id of a +/// closure, coroutine, or coroutine-closure) by transforming self into a trait object of the Fn, +/// FnMut, FnOnce, Coroutine, Future, Iterator, or AsyncIterator trait that defines the call +/// method they are called through, and the instance into a virtual call to that method, to match +/// the type erasure performed during code generation at call sites (see transform_virtual_call). +/// Returns None if the instance is not a closure-like. +/// +/// E.g.: +/// +/// ```ignore (illustrative) +/// // The closure is transformed into >::call. +/// let f: Box = Box::new(|_x| {}); +/// f(0); +/// ``` +fn transform_closure_like<'tcx>( + tcx: TyCtxt<'tcx>, + mut instance: Instance<'tcx>, +) -> Option> { + if !tcx.is_closure_like(instance.def_id()) { + return None; + } + let closure_like_ty = instance.ty(tcx, ty::TypingEnv::fully_monomorphized()); + let (trait_id, inputs) = match closure_like_ty.kind() { + ty::Closure(_, args) => { + let closure_args = args.as_closure(); + let closure_kind = closure_args.kind(); + let trait_id = tcx.fn_trait_kind_to_def_id(closure_kind).unwrap_or_else(|| { + bug!( + "transform_closure_like: couldn't get trait of closure kind `{:?}`", + closure_kind + ) + }); + let tuple_args = + tcx.instantiate_bound_regions_with_erased(closure_args.sig()).inputs()[0]; + (trait_id, Some(tuple_args)) + } + 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(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(_, args) => ( + tcx.require_lang_item(LangItem::FnOnce, DUMMY_SP), + Some( + tcx.instantiate_bound_regions_with_erased( + args.as_coroutine_closure().coroutine_closure_sig(), + ) + .tupled_inputs_ty, + ), + ), + _ => bug!("transform_closure_like: unexpected `{:?}`", closure_like_ty.kind()), + }; + let 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 being defined. + let call_method_id = tcx + .associated_items(trait_id) + .in_definition_order() + .find(|item| item.is_fn()) + .unwrap_or_else(|| { + bug!("transform_closure_like: couldn't get call method of `{:?}`", trait_id) + }) + .def_id; + + instance.def = ty::InstanceKind::Virtual(call_method_id, 0); + instance.args = abstract_args; + Some(instance) +} + +/// Adjusts the type ids of DropGlues to a synthesized Drop trait object. +/// +/// DropGlues may have indirect calls to one or more given types drop function. Rust allows for +/// types to be erased to any trait object and retains the drop function for the original type, +/// which means at the indirect call sites in DropGlues, when typeid_for_fnabi is called a second +/// time, it only has information after type erasure and it could be a call on any arbitrary trait +/// object. They are normalized to a synthesized Drop trait object, both on declaration/definition, +/// and during code generation at call sites so they have the same type id and match. +/// +/// E.g.: +/// +/// ```ignore (illustrative) +/// struct Type1; +/// +/// impl Drop for Type1 { +/// fn drop(&mut self) {} +/// } +/// +/// let x: Box = Box::new(Type1); +/// // Dropping x calls the drop glue of Type1 through the vtable of the dyn Send trait object. +/// // Both the drop glue and the virtual drop call to it are transformed into +/// // drop_in_place::. +/// ``` +/// +/// FIXME(rcvalle): This allows a drop call on any trait object to call the drop function of any +/// other type. +/// +fn transform_drop_glue<'tcx>(tcx: TyCtxt<'tcx>, mut instance: Instance<'tcx>) -> Instance<'tcx> { + let trait_id = tcx + .lang_items() + .drop_trait() + .unwrap_or_else(|| bug!("transform_drop_glue: couldn't get drop_trait lang item")); + let predicate = ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::new_from_args( + tcx, + trait_id, + ty::List::empty(), + )); + let predicates = tcx.mk_poly_existential_predicates(&[ty::Binder::dummy(predicate)]); + let self_ty = Ty::new_dynamic(tcx, predicates, tcx.lifetimes.re_erased); + instance.args = tcx.mk_args_trait(self_ty, List::empty()); + instance +} + +/// Performs type erasure for trait method implementations in impl blocks by transforming self +/// into a trait object of the trait that defines the method, and the instance into a virtual call +/// to the trait method definition it implements, to match the type erasure performed during code +/// generation at call sites (see transform_virtual_call). Returns None if the instance is not a +/// trait method implementation in an impl block that may be called through a vtable. +/// +/// E.g.: +/// +/// ```ignore (illustrative) +/// trait Trait1 { +/// fn foo(&self); +/// } +/// +/// struct Type1; +/// +/// impl Trait1 for Type1 { +/// fn foo(&self) {} // ::foo is transformed into ::foo. +/// } +/// +/// let x: &dyn Trait1 = &Type1; +/// x.foo(); +/// ``` +fn transform_impl_method<'tcx>( + tcx: TyCtxt<'tcx>, + mut instance: Instance<'tcx>, +) -> Option> { + let assoc = tcx.opt_associated_item(instance.def_id())?; + let AssocContainer::TraitImpl(Ok(method_id)) = assoc.container else { + return None; + }; + if !may_be_called_through_vtable(tcx, method_id) { + return None; + } + let impl_id = tcx.parent(instance.def_id()); + let trait_ref = tcx.instantiate_and_normalize_erasing_regions( + instance.args, + ty::TypingEnv::fully_monomorphized(), + tcx.impl_trait_ref(impl_id), ); - Ty::new_dynamic(tcx, preds, tcx.lifetimes.re_erased) + let self_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); + instance.def = ty::InstanceKind::Virtual(method_id, 0); + let abstract_args = tcx.mk_args_trait(self_ty, trait_ref.args.into_iter().skip(1)); + instance.args = instance.args.rebase_onto(tcx, impl_id, abstract_args); + Some(instance) } /// Transforms an instance for LLVM CFI and cross-language LLVM CFI support using Itanium C++ ABI @@ -293,228 +472,301 @@ fn trait_object_ty<'tcx>(tcx: TyCtxt<'tcx>, poly_trait_ref: ty::PolyTraitRef<'tc /// most as much information that would be available in the second call (i.e., during code /// generation at call sites); otherwise, the type ids would not match. /// -/// For this, it: +/// For this, it covers each InstanceKind (and ShimKind) explicitly, and either: /// -/// * Adjust the type ids of DropGlues (see below). -/// * Adjusts the type ids of VTableShims to the type id expected in the call sites for the -/// entry in the vtable (i.e., by using the signature of the closure passed as an argument to the -/// shim, or by just removing self). +/// * Performs type erasure for closures and coroutines by transforming self into a trait object +/// of the Fn, FnMut, FnOnce, Coroutine, Future, Iterator, or AsyncIterator trait that defines +/// the call method they are called through (see transform_closure_like). /// * Performs type erasure for calls on trait objects by transforming self into a trait object of -/// the trait that defines the method. -/// * Performs type erasure for closures call methods by transforming self into a trait object of -/// the Fn trait that defines the method (for being attached as a secondary type id). +/// the trait that defines the method, both on declaration/definition (see +/// transform_impl_method and transform_provided_method) and during code generation at call +/// sites (see transform_virtual_call). +/// * Adjusts the type ids of VTableShims to the type id expected in the call sites for the +/// entry in the vtable by transforming self into a trait object of the trait that defines the +/// method (see transform_vtable_shim). +/// * Adjusts the type ids of DropGlues to a synthesized Drop trait object (see +/// transform_drop_glue). +/// * Does not transform the instance (i.e., encodes type ids for the instance as is). /// #[instrument(level = "trace", skip(tcx))] pub(crate) fn transform_instance<'tcx>( tcx: TyCtxt<'tcx>, - mut instance: Instance<'tcx>, + instance: Instance<'tcx>, options: TransformTyOptions, ) -> Instance<'tcx> { - // FIXME: account for async-drop-glue - if (matches!(instance.def, ty::InstanceKind::Virtual(..)) - && tcx.is_lang_item(instance.def_id(), LangItem::DropGlue)) - || matches!(instance.def, ty::InstanceKind::Shim(ty::ShimKind::DropGlue(..))) - { - // Adjust the type ids of DropGlues + // If the USE_CONCRETE_SELF option is set, type erasure is not performed for the instances + // that may also be called directly (i.e., type ids are encoded for them as is). The + // USE_CONCRETE_SELF option is set for encoding methods as concrete types for being attached + // as secondary type ids (see rustc_codegen_llvm::declare::declare_fn), and for ReifyShims + // created for function pointers (i.e., ReifyReason::FnPtr) when KCFI is enabled (see + // kcfi::typeid_for_instance). Note that DropGlues, virtual method calls, and VTableShims are + // transformed regardless of this option (see below). + let erase_self = !options.contains(TransformTyOptions::USE_CONCRETE_SELF); + match instance.def { + // User-defined callable items (i.e., fn items, closures, and coroutines): // - // 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. + // * Closures and coroutines are called through the call methods of the Fn, FnMut, FnOnce, + // Coroutine, Future, Iterator, or AsyncIterator traits they implement, either on the + // concrete type or through a vtable, so type erasure is performed for them (see + // transform_closure_like). + // * Fn items that implement a trait method may be called through a vtable, so type + // erasure is also performed for them, both for trait method implementations in impl + // blocks (see transform_impl_method) and for provided (default) trait methods in trait + // blocks (see transform_provided_method). + // * Other fn items (i.e., free functions, inherent methods, and trait methods that can + // not be called through a vtable) are not transformed (i.e., type ids are encoded for + // them as is). + ty::InstanceKind::Item(..) => { + if erase_self { + transform_closure_like(tcx, instance) + .or_else(|| transform_impl_method(tcx, instance)) + .or_else(|| transform_provided_method(tcx, instance)) + .unwrap_or(instance) + } else { + instance + } + } + + // Intrinsic fn items (i.e., fn items with #[rustc_intrinsic]) and LLVM intrinsic fn items + // (i.e., fn items with extern "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): // - // FIXME(rcvalle): This allows a drop call on any trait object to call the drop function of - // any other type. + // * Virtual drop glue calls (i.e., the drop function entry in vtables) are normalized to + // a synthesized Drop trait object to match the DropGlues (see transform_drop_glue). + // * Other virtual method calls have self transformed into a trait object of the trait + // that defines the method to match the type erasure performed on + // declaration/definition (see transform_virtual_call). + ty::InstanceKind::Virtual(def_id, _) => { + if tcx.is_lang_item(def_id, LangItem::DropGlue) { + transform_drop_glue(tcx, instance) + } else { + transform_virtual_call(tcx, instance) + } + } + + // VTableShims (i.e., shims for trait methods that receive an unsizeable `self: Self`): + // have their type ids adjusted to the type id expected in the call sites for the entry in + // the vtable (see transform_vtable_shim). + ty::InstanceKind::Shim(ty::ShimKind::VTable(..)) => transform_vtable_shim(tcx, instance), + + // ReifyShims (i.e., fn pointers created for methods that can not be directly reified, + // such as virtual methods and methods with #[track_caller]): // - let def_id = tcx - .lang_items() - .drop_trait() - .unwrap_or_else(|| bug!("typeid_for_instance: couldn't get drop_trait lang item")); - let predicate = ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::new_from_args( - tcx, - def_id, - ty::List::empty(), - )); - let predicates = tcx.mk_poly_existential_predicates(&[ty::Binder::dummy(predicate)]); - let self_ty = Ty::new_dynamic(tcx, predicates, tcx.lifetimes.re_erased); - instance.args = tcx.mk_args_trait(self_ty, List::empty()); - } else if let ty::InstanceKind::Virtual(def_id, _) = instance.def { - // Transform self into a trait object of the trait that defines the method for virtual - // functions to match the type erasure done below. - let upcast_ty = match tcx.trait_of_assoc(def_id) { - Some(trait_id) => trait_object_ty( - tcx, - ty::Binder::dummy(ty::TraitRef::from_assoc(tcx, trait_id, instance.args)), - ), - // drop_in_place won't have a defining trait, skip the upcast - None => instance.args.type_at(0), - }; - let ty::Dynamic(preds, lifetime) = upcast_ty.kind() else { - bug!("Tried to remove autotraits from non-dynamic type {upcast_ty}"); - }; - let self_ty = if preds.principal().is_some() { - let filtered_preds = - tcx.mk_poly_existential_predicates_from_iter(preds.into_iter().filter(|pred| { - !matches!(pred.skip_binder(), ty::ExistentialPredicate::AutoTrait(..)) - })); - Ty::new_dynamic(tcx, filtered_preds, *lifetime) - } else { - // If there's no principal type, re-encode it as a unit, since we don't know anything - // about it. This technically discards the knowledge that it was a type that was made - // into a trait object at some point, but that's not a lot. - tcx.types.unit - }; - instance.args = tcx.mk_args_trait(self_ty, instance.args.into_iter().skip(1)); - } else if let ty::InstanceKind::Shim(ty::ShimKind::VTable(def_id)) = instance.def - && let Some(trait_id) = tcx.trait_of_assoc(def_id) - { - // Adjust the type ids of VTableShims to the type id expected in the call sites for the - // entry in the vtable (i.e., by using the signature of the closure passed as an argument - // to the shim, or by just removing self). - let trait_ref = ty::TraitRef::new_from_args(tcx, trait_id, instance.args); - let invoke_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); - instance.args = tcx.mk_args_trait(invoke_ty, trait_ref.args.into_iter().skip(1)); - } + // * 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 + } + } - if !options.contains(TransformTyOptions::USE_CONCRETE_SELF) { - // Perform type erasure for calls on trait objects by transforming self into a trait object - // of the trait that defines the method. - if let Some((trait_ref, method_id, ancestor)) = implemented_method(tcx, instance) { - // Trait methods will have a Self polymorphic parameter, where the concreteized - // implementation will not. We need to walk back to the more general trait method - let trait_ref = tcx.instantiate_and_normalize_erasing_regions( - instance.args, - ty::TypingEnv::fully_monomorphized(), - trait_ref, - ); - let invoke_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); - - // At the call site, any call to this concrete function through a vtable will be - // `Virtual(method_id, idx)` with appropriate arguments for the method. Since we have the - // original method id, and we've recovered the trait arguments, we can make the callee - // instance we're computing the alias set for match the caller instance. - // - // Right now, our code ignores the vtable index everywhere, so we use 0 as a placeholder. - // If we ever *do* start encoding the vtable index, we will need to generate an alias set - // based on which vtables we are putting this method into, as there will be more than one - // index value when supertraits are involved. - instance.def = ty::InstanceKind::Virtual(method_id, 0); - let abstract_trait_args = - tcx.mk_args_trait(invoke_ty, trait_ref.args.into_iter().skip(1)); - instance.args = instance.args.rebase_onto(tcx, ancestor, abstract_trait_args); - } else if tcx.is_closure_like(instance.def_id()) { - // We're either a closure or a coroutine. Our goal is to find the trait we're defined on, - // instantiate it, and take the type of its only method as our own. - let closure_ty = instance.ty(tcx, ty::TypingEnv::fully_monomorphized()); - let (trait_id, inputs) = match closure_ty.kind() { - ty::Closure(..) => { - let closure_args = instance.args.as_closure(); - let trait_id = tcx.fn_trait_kind_to_def_id(closure_args.kind()).unwrap(); - let tuple_args = - tcx.instantiate_bound_regions_with_erased(closure_args.sig()).inputs()[0]; - (trait_id, Some(tuple_args)) - } - ty::Coroutine(..) => match tcx.coroutine_kind(instance.def_id()).unwrap() { - hir::CoroutineKind::Coroutine(..) => ( - tcx.require_lang_item(LangItem::Coroutine, DUMMY_SP), - Some(instance.args.as_coroutine().resume_ty()), - ), - hir::CoroutineKind::Desugared(desugaring, _) => { - let lang_item = match desugaring { - hir::CoroutineDesugaring::Async => LangItem::Future, - hir::CoroutineDesugaring::AsyncGen => LangItem::AsyncIterator, - hir::CoroutineDesugaring::Gen => LangItem::Iterator, - }; - (tcx.require_lang_item(lang_item, DUMMY_SP), None) - } - }, - ty::CoroutineClosure(..) => ( - tcx.require_lang_item(LangItem::FnOnce, DUMMY_SP), - Some( - tcx.instantiate_bound_regions_with_erased( - instance.args.as_coroutine_closure().coroutine_closure_sig(), - ) - .tupled_inputs_ty, - ), - ), - x => bug!("Unexpected type kind for closure-like: {x:?}"), - }; - let concrete_args = tcx.mk_args_trait(closure_ty, inputs.map(Into::into)); - let trait_ref = ty::TraitRef::new_from_args(tcx, trait_id, concrete_args); - let invoke_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); - let abstract_args = tcx.mk_args_trait(invoke_ty, trait_ref.args.into_iter().skip(1)); - // There should be exactly one method on this trait, and it should be the one we're - // defining. - let call = tcx - .associated_items(trait_id) - .in_definition_order() - .find(|it| it.is_fn()) - .expect("No call-family function on closure-like Fn trait?") - .def_id; + // FnPtrShims (i.e., `::call_*`, the generated Fn, FnMut, and FnOnce + // trait implementations for fn pointers): may be called through a vtable (e.g., a + // `dyn Fn` trait object created from a fn pointer or fn item), so type erasure is + // performed for them (see transform_impl_method and transform_provided_method). + ty::InstanceKind::Shim(ty::ShimKind::FnPtr(..)) => { + if erase_self { + transform_impl_method(tcx, instance) + .or_else(|| transform_provided_method(tcx, instance)) + .unwrap_or(instance) + } else { + instance + } + } - instance.def = ty::InstanceKind::Virtual(call, 0); - instance.args = abstract_args; + // ClosureOnceShims (i.e., `<[FnMut/Fn closure] as FnOnce>::call_once`): + // `FnOnce::call_once` receives an unsizeable `self: Self`, so when it is called through a + // `dyn FnOnce` trait object, the entry in the vtable is a VTableShim (handled above), + // which calls the ClosureOnceShim directly. ClosureOnceShims can not be called through a + // vtable, so they are not transformed (i.e., type ids are encoded for them as is). + ty::InstanceKind::Shim(ty::ShimKind::ClosureOnce { .. }) => instance, + + // ConstructCoroutineInClosureShims (i.e., `<[FnMut/Fn coroutine-closure] as + // FnOnce>::call_once`, identified by the def id of the coroutine-closure): may be called + // through a vtable (e.g., a `dyn FnOnce` trait object created from a coroutine-closure), + // so type erasure is performed for them (see transform_closure_like). + ty::InstanceKind::Shim(ty::ShimKind::ConstructCoroutineInClosure { .. }) => { + if erase_self { + transform_closure_like(tcx, instance).unwrap_or(instance) + } else { + instance + } } - } - instance -} + // ThreadLocalShims (i.e., compiler-generated accessors for thread locals): 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, -fn default_or_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> Option { - match instance.def { - ty::InstanceKind::Item(def_id) | ty::InstanceKind::Shim(ty::ShimKind::FnPtr(def_id, _)) => { - tcx.opt_associated_item(def_id).map(|item| item.def_id) + // FutureDropPollShims (i.e., proxy poll functions for async drop of futures) and + // AsyncDropGlueShims (i.e., poll functions of the `async_drop_in_place::::{closure}` + // coroutines): identified by the def id of the `async_drop_in_place::::{closure}` + // coroutine, so type erasure is performed for them like other coroutines (i.e., self is + // transformed into a Future trait object) (see transform_closure_like). + // + // FIXME: account for async-drop-glue: similarly to DropGlues (see transform_drop_glue), + // at the indirect call sites in async drop glue the receiver may have been erased to + // any trait object, so async drop glue may need to be normalized to a synthesized + // trait object instead. + ty::InstanceKind::Shim( + ty::ShimKind::FutureDropPoll(..) | ty::ShimKind::AsyncDropGlue(..), + ) => { + if erase_self { + transform_closure_like(tcx, instance).unwrap_or(instance) + } else { + instance + } } - _ => 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. +/// } /// -/// This function handles two main cases: +/// struct Type1; /// -/// * **Implementation in an `impl` block**: When the instance represents a concrete implementation -/// of a trait method in an `impl` block, it extracts the trait reference, method ID, and trait -/// ID from the implementation. The method ID is obtained from the `trait_item_def_id` field of -/// the associated item, which points to the original trait method definition. +/// impl Trait1 for Type1 {} /// -/// * **Provided method in a `trait` block or synthetic `shim`**: When the instance represents a -/// default implementation provided in the trait definition itself or a synthetic shim, it uses -/// the instance's own `def_id` as the method ID and determines the trait ID from the associated -/// item. +/// let x: &dyn Trait1 = &Type1; +/// x.foo(); +/// ``` /// -fn implemented_method<'tcx>( +/// And for synthetic FnPtrShims: +/// +/// ```ignore (illustrative) +/// fn foo(_: i32) {} +/// +/// let f: Box = Box::new(foo); +/// f(0); +/// // The >::call FnPtrShim is transformed into +/// // >::call. +/// ``` +fn transform_provided_method<'tcx>( tcx: TyCtxt<'tcx>, - instance: Instance<'tcx>, -) -> Option<(ty::EarlyBinder<'tcx, TraitRef<'tcx>>, DefId, DefId)> { - let trait_ref; - let method_id; - let trait_id; - let trait_method; + mut instance: Instance<'tcx>, +) -> Option> { let assoc = tcx.opt_associated_item(instance.def_id())?; - let ancestor = if let AssocContainer::TraitImpl(Ok(trait_method_id)) = assoc.container { - let impl_id = tcx.parent(instance.def_id()); - trait_ref = tcx.impl_trait_ref(impl_id); - method_id = trait_method_id; - trait_method = tcx.associated_item(method_id); - trait_id = trait_ref.skip_binder().def_id; - impl_id - } else if let AssocContainer::Trait = assoc.container - && let Some(trait_method_def_id) = default_or_shim(tcx, instance) - { - // Provided method in a `trait` block or a synthetic `shim` - trait_method = assoc; - method_id = trait_method_def_id; - trait_id = tcx.parent(method_id); - trait_ref = ty::EarlyBinder::bind(tcx, TraitRef::from_assoc(tcx, trait_id, instance.args)); - trait_id - } else { + let AssocContainer::Trait = assoc.container else { return None; }; - let vtable_possible = traits::is_vtable_safe_method(tcx, trait_id, trait_method) - && tcx.is_dyn_compatible(trait_id); - vtable_possible.then_some((trait_ref, method_id, ancestor)) + let method_id = assoc.def_id; + if !may_be_called_through_vtable(tcx, method_id) { + return None; + } + let trait_id = tcx.parent(method_id); + let trait_ref = tcx.normalize_erasing_regions( + ty::TypingEnv::fully_monomorphized(), + Unnormalized::new_wip(TraitRef::from_assoc(tcx, trait_id, instance.args)), + ); + let self_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); + instance.def = ty::InstanceKind::Virtual(method_id, 0); + let abstract_args = tcx.mk_args_trait(self_ty, trait_ref.args.into_iter().skip(1)); + instance.args = instance.args.rebase_onto(tcx, trait_id, abstract_args); + Some(instance) +} + +/// Performs type erasure for virtual method calls (i.e., calls to methods through trait objects) +/// by transforming self into a trait object of the trait that defines the method, to match the +/// type erasure performed on declaration/definition (see transform_impl_method, +/// transform_provided_method, and transform_closure_like). +/// +/// E.g.: +/// +/// ```ignore (illustrative) +/// trait Trait1 { +/// fn foo(&self); +/// } +/// +/// struct Type1; +/// +/// impl Trait1 for Type1 { +/// fn foo(&self) {} +/// } +/// +/// let x: &dyn Trait1 = &Type1; +/// x.foo(); // The virtual method call is transformed into ::foo. +/// ``` +fn transform_virtual_call<'tcx>(tcx: TyCtxt<'tcx>, mut instance: Instance<'tcx>) -> Instance<'tcx> { + // Virtual method calls are either drop glue calls (handled above) or calls to trait methods, + // so they always have a defining trait. + let trait_id = tcx.trait_of_assoc(instance.def_id()).unwrap_or_else(|| { + bug!("transform_virtual_call: couldn't get defining trait of `{:?}`", instance.def_id()) + }); + let trait_ref = ty::TraitRef::from_assoc(tcx, trait_id, instance.args); + let self_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); + instance.args = tcx.mk_args_trait(self_ty, instance.args.into_iter().skip(1)); + instance +} + +/// Adjusts the type ids of VTableShims to the type id expected in the call sites for the entry in +/// the vtable by transforming self into a trait object of the trait that defines the method, to +/// match the type erasure performed during code generation at call sites (see +/// transform_virtual_call). +/// +/// E.g.: +/// +/// ```ignore (illustrative) +/// let f: Box = Box::new(|| {}); +/// f(); +/// // >::call_once receives an unsizeable `self: Self`, so the +/// // VTableShim for it in the vtable is transformed into >::call_once. +/// ``` +fn transform_vtable_shim<'tcx>(tcx: TyCtxt<'tcx>, mut instance: Instance<'tcx>) -> Instance<'tcx> { + // VTableShims are only created for trait methods (see Instance::expect_resolve_for_vtable), + // so they always have a defining trait. + let trait_id = tcx.trait_of_assoc(instance.def_id()).unwrap_or_else(|| { + bug!("transform_vtable_shim: couldn't get defining trait of `{:?}`", instance.def_id()) + }); + let trait_ref = ty::TraitRef::new_from_args(tcx, trait_id, instance.args); + let self_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref)); + instance.args = tcx.mk_args_trait(self_ty, trait_ref.args.into_iter().skip(1)); + instance } 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/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/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/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/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/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/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/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/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/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-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 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); +}