From b499abbbba4bde7a3b0ce38198b0f9c523550e96 Mon Sep 17 00:00:00 2001 From: Ramon de C Valle Date: Tue, 4 Aug 2026 14:07:43 -0700 Subject: [PATCH] CFI: Reorganize the CFI and KCFI UI tests Reorganizes the CFI and KCFI UI tests under the cfi and kcfi directories similarly to how the CFI codegen tests are organized: each test now tests a type or a language construct or feature that is handled by the CFI and KCFI transform or encoding, with complete coverage of what can be exercised at run time, and is named works-with-, and tests that covered both CFI and KCFI using revisions are split into separate tests. It also adds tests for the types, language constructs, and features that were not covered (i.e., intrinsics, thread locals, builtin Clone and FnPtr implementations, pattern types, the never type, extern types, C variadics, and the generalize-pointers and normalize-integers options), removes the tests that became redundant, and changes the drop tests to run-pass, as they now pass at run time. Tests for flags/options and the regression tests are unchanged (moved and renamed in the previous commit). --- tests/ui/sanitizer/cfi/async-closures.rs | 33 ----- tests/ui/sanitizer/cfi/closures.rs | 91 ------------ tests/ui/sanitizer/cfi/complex-receiver.rs | 48 ------- tests/ui/sanitizer/cfi/const-generics.rs | 110 --------------- tests/ui/sanitizer/cfi/coroutine.rs | 68 --------- tests/ui/sanitizer/cfi/drop-in-place.rs | 22 --- tests/ui/sanitizer/cfi/drop-no-principal.rs | 22 --- tests/ui/sanitizer/cfi/fn-ptr.rs | 63 --------- tests/ui/sanitizer/cfi/fn-trait-objects.rs | 32 ----- tests/ui/sanitizer/cfi/self-ref.rs | 39 ------ tests/ui/sanitizer/cfi/sized-associated-ty.rs | 39 ------ tests/ui/sanitizer/cfi/supertraits.rs | 79 ----------- .../sanitizer/cfi/transparent-has-regions.rs | 19 --- tests/ui/sanitizer/cfi/virtual-auto.rs | 28 ---- .../cfi/works-with-associated-types.rs | 53 +++++++ .../cfi/works-with-async-closures.rs | 39 ++++++ .../sanitizer/cfi/works-with-auto-traits.rs | 31 +++++ .../ui/sanitizer/cfi/works-with-c-variadic.rs | 26 ++++ .../sanitizer/cfi/works-with-cfi-encoding.rs | 46 +++++++ tests/ui/sanitizer/cfi/works-with-clone.rs | 21 +++ tests/ui/sanitizer/cfi/works-with-closures.rs | 89 ++++++++++++ .../cfi/works-with-const-generics.rs | 129 ++++++++++++++++++ .../ui/sanitizer/cfi/works-with-coroutines.rs | 95 +++++++++++++ .../sanitizer/cfi/works-with-drop-in-place.rs | 27 ++++ .../sanitizer/cfi/works-with-fn-ptr-addr.rs | 23 ++++ .../sanitizer/cfi/works-with-fn-ptr-casts.rs | 70 ++++++++++ .../cfi/works-with-fn-trait-objects.rs | 42 ++++++ .../cfi/works-with-function-types.rs | 41 ++++++ .../cfi/works-with-generalized-pointers.rs | 53 +++++++ .../ui/sanitizer/cfi/works-with-intrinsics.rs | 29 ++++ .../ui/sanitizer/cfi/works-with-lifetimes.rs | 47 +++++++ .../cfi/works-with-normalized-integers.rs | 58 ++++++++ .../sanitizer/cfi/works-with-pattern-types.rs | 41 ++++++ .../sanitizer/cfi/works-with-pointer-types.rs | 51 +++++++ .../cfi/works-with-primitive-types.rs | 116 ++++++++++++++++ .../ui/sanitizer/cfi/works-with-receivers.rs | 58 ++++++++ .../cfi/works-with-repr-transparent-types.rs | 92 +++++++++++++ .../cfi/works-with-sequence-types.rs | 47 +++++++ .../sanitizer/cfi/works-with-supertraits.rs | 89 ++++++++++++ .../sanitizer/cfi/works-with-thread-locals.rs | 22 +++ .../sanitizer/cfi/works-with-trait-objects.rs | 67 +++++++++ .../sanitizer/cfi/works-with-trait-types.rs | 52 +++++++ .../cfi/works-with-user-defined-types.rs | 85 ++++++++++++ tests/ui/sanitizer/kcfi-c-variadic.rs | 18 --- tests/ui/sanitizer/kcfi-mangling.rs | 31 ----- tests/ui/sanitizer/kcfi/const-generics.rs | 109 --------------- tests/ui/sanitizer/kcfi/fn-trait-objects.rs | 32 ----- .../kcfi/works-with-associated-types.rs | 52 +++++++ .../kcfi/works-with-async-closures.rs | 38 ++++++ .../sanitizer/kcfi/works-with-auto-traits.rs | 30 ++++ .../sanitizer/kcfi/works-with-c-variadic.rs | 25 ++++ .../sanitizer/kcfi/works-with-cfi-encoding.rs | 45 ++++++ tests/ui/sanitizer/kcfi/works-with-clone.rs | 20 +++ .../ui/sanitizer/kcfi/works-with-closures.rs | 88 ++++++++++++ .../kcfi/works-with-const-generics.rs | 128 +++++++++++++++++ .../sanitizer/kcfi/works-with-coroutines.rs | 94 +++++++++++++ .../kcfi/works-with-drop-in-place.rs | 26 ++++ .../sanitizer/kcfi/works-with-fn-ptr-addr.rs | 22 +++ .../sanitizer/kcfi/works-with-fn-ptr-casts.rs | 69 ++++++++++ .../kcfi/works-with-fn-trait-objects.rs | 41 ++++++ .../kcfi/works-with-function-types.rs | 40 ++++++ .../kcfi/works-with-generalized-pointers.rs | 52 +++++++ .../sanitizer/kcfi/works-with-intrinsics.rs | 28 ++++ .../ui/sanitizer/kcfi/works-with-lifetimes.rs | 46 +++++++ .../kcfi/works-with-normalized-integers.rs | 57 ++++++++ .../kcfi/works-with-pattern-types.rs | 40 ++++++ .../kcfi/works-with-pointer-types.rs | 50 +++++++ .../kcfi/works-with-primitive-types.rs | 115 ++++++++++++++++ .../ui/sanitizer/kcfi/works-with-receivers.rs | 57 ++++++++ .../kcfi/works-with-repr-transparent-types.rs | 91 ++++++++++++ .../kcfi/works-with-sequence-types.rs | 46 +++++++ .../sanitizer/kcfi/works-with-supertraits.rs | 88 ++++++++++++ .../kcfi/works-with-symbol-mangling-v0.rs | 36 +++++ .../kcfi/works-with-thread-locals.rs | 21 +++ .../kcfi/works-with-trait-objects.rs | 66 +++++++++ .../sanitizer/kcfi/works-with-trait-types.rs | 51 +++++++ .../kcfi/works-with-user-defined-types.rs | 84 ++++++++++++ 77 files changed, 3285 insertions(+), 883 deletions(-) delete mode 100644 tests/ui/sanitizer/cfi/async-closures.rs delete mode 100644 tests/ui/sanitizer/cfi/closures.rs delete mode 100644 tests/ui/sanitizer/cfi/complex-receiver.rs delete mode 100644 tests/ui/sanitizer/cfi/const-generics.rs delete mode 100644 tests/ui/sanitizer/cfi/coroutine.rs delete mode 100644 tests/ui/sanitizer/cfi/drop-in-place.rs delete mode 100644 tests/ui/sanitizer/cfi/drop-no-principal.rs delete mode 100644 tests/ui/sanitizer/cfi/fn-ptr.rs delete mode 100644 tests/ui/sanitizer/cfi/fn-trait-objects.rs delete mode 100644 tests/ui/sanitizer/cfi/self-ref.rs delete mode 100644 tests/ui/sanitizer/cfi/sized-associated-ty.rs delete mode 100644 tests/ui/sanitizer/cfi/supertraits.rs delete mode 100644 tests/ui/sanitizer/cfi/transparent-has-regions.rs delete mode 100644 tests/ui/sanitizer/cfi/virtual-auto.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-associated-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-async-closures.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-auto-traits.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-c-variadic.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-cfi-encoding.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-clone.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-closures.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-const-generics.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-coroutines.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-drop-in-place.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-fn-ptr-addr.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-fn-ptr-casts.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-fn-trait-objects.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-function-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-generalized-pointers.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-intrinsics.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-lifetimes.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-normalized-integers.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-pattern-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-pointer-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-primitive-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-receivers.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-repr-transparent-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-sequence-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-supertraits.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-thread-locals.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-trait-objects.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-trait-types.rs create mode 100644 tests/ui/sanitizer/cfi/works-with-user-defined-types.rs delete mode 100644 tests/ui/sanitizer/kcfi-c-variadic.rs delete mode 100644 tests/ui/sanitizer/kcfi-mangling.rs delete mode 100644 tests/ui/sanitizer/kcfi/const-generics.rs delete mode 100644 tests/ui/sanitizer/kcfi/fn-trait-objects.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-associated-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-async-closures.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-auto-traits.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-c-variadic.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-cfi-encoding.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-clone.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-closures.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-const-generics.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-coroutines.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-drop-in-place.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-fn-ptr-addr.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-fn-ptr-casts.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-fn-trait-objects.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-function-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-generalized-pointers.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-intrinsics.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-lifetimes.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-normalized-integers.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-pattern-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-pointer-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-primitive-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-receivers.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-repr-transparent-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-sequence-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-supertraits.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-symbol-mangling-v0.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-thread-locals.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-trait-objects.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-trait-types.rs create mode 100644 tests/ui/sanitizer/kcfi/works-with-user-defined-types.rs diff --git a/tests/ui/sanitizer/cfi/async-closures.rs b/tests/ui/sanitizer/cfi/async-closures.rs deleted file mode 100644 index 621a0882c91b2..0000000000000 --- a/tests/ui/sanitizer/cfi/async-closures.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Check various forms of dynamic closure calls - -//@ edition: 2021 -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -Z panic-abort-tests -C prefer-dynamic=off -//@ run-pass - -#![feature(async_fn_traits)] - -use std::ops::AsyncFn; - -#[inline(never)] -fn identity(x: T) -> T { x } - -// We can't actually create a `dyn AsyncFn()`, because it's dyn-incompatible, but we should check -// that we don't bug out when we encounter one. - -fn main() { - let f = identity(async || ()); - let _ = f.async_call(()); - let _ = f(); - let g: Box _> = Box::new(f) as _; - let _ = g(); -} diff --git a/tests/ui/sanitizer/cfi/closures.rs b/tests/ui/sanitizer/cfi/closures.rs deleted file mode 100644 index 7493dba4928b0..0000000000000 --- a/tests/ui/sanitizer/cfi/closures.rs +++ /dev/null @@ -1,91 +0,0 @@ -// Check various forms of dynamic closure calls - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -Z panic-abort-tests -C prefer-dynamic=off -//@ compile-flags: --test -//@ run-pass - -#![feature(fn_traits)] -#![feature(unboxed_closures)] - -fn foo<'a, T>() -> Box &'a T> { - Box::new(|x| x) -} - -#[test] -fn dyn_fn_with_params() { - let x = 3; - let f = foo(); - f(&x); - // FIXME remove once drops are working. - std::mem::forget(f); -} - -#[test] -fn call_fn_trait() { - let f: &dyn Fn() = &(|| {}) as _; - f.call(()); -} - -#[test] -fn fn_ptr_cast() { - let f: &fn() = &((|| ()) as _); - f(); -} - -fn use_fnmut(mut f: F) { - f() -} - -#[test] -fn fn_to_fnmut() { - let f: &dyn Fn() = &(|| {}) as _; - use_fnmut(f); -} - -fn hrtb_helper(f: &dyn for<'a> Fn(&'a usize)) { - f(&10) -} - -#[test] -fn hrtb_fn() { - hrtb_helper((&|x: &usize| println!("{}", *x)) as _) -} - -#[test] -fn fnonce() { - let f: Box = Box::new(|| {}) as _; - f(); -} - -fn use_closure(call: extern "rust-call" fn(&C, ()) -> i32, f: &C) -> i32 { - call(f, ()) -} - -#[test] -fn closure_addr_taken() { - let x = 3i32; - let f = || x; - let call = Fn::<()>::call; - use_closure(call, &f); -} - -fn use_closure_once(call: extern "rust-call" fn(C, ()) -> i32, f: C) -> i32 { - call(f, ()) -} - -#[test] -fn closure_once_addr_taken() { - let g = || 3; - let call2 = FnOnce::<()>::call_once; - use_closure_once(call2, g); -} diff --git a/tests/ui/sanitizer/cfi/complex-receiver.rs b/tests/ui/sanitizer/cfi/complex-receiver.rs deleted file mode 100644 index adacc0d6c5df7..0000000000000 --- a/tests/ui/sanitizer/cfi/complex-receiver.rs +++ /dev/null @@ -1,48 +0,0 @@ -// Check that more complex receivers work: -// * Arc as for custom receivers -// * &dyn Bar for type constraints - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -C prefer-dynamic=off -//@ run-pass - -use std::sync::Arc; - -trait Foo { - fn foo(self: Arc); -} - -struct FooImpl; - -impl Foo for FooImpl { - fn foo(self: Arc) {} -} - -trait Bar { - type T; - fn bar(&self) -> Self::T; -} - -struct BarImpl; - -impl Bar for BarImpl { - type T = i32; - fn bar(&self) -> Self::T { 7 } -} - -fn main() { - let foo: Arc = Arc::new(FooImpl); - foo.foo(); - - let bar: &dyn Bar = &BarImpl; - assert_eq!(bar.bar(), 7); -} diff --git a/tests/ui/sanitizer/cfi/const-generics.rs b/tests/ui/sanitizer/cfi/const-generics.rs deleted file mode 100644 index 42fff233dd84b..0000000000000 --- a/tests/ui/sanitizer/cfi/const-generics.rs +++ /dev/null @@ -1,110 +0,0 @@ -// Verifies that functions with types with const generics as argument types can -// be called through function pointers. -// -//@ needs-sanitizer-cfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer -//@ run-pass - -#![feature(adt_const_params)] -#![feature(unsized_const_params)] -#![allow(incomplete_features)] - -use std::marker::ConstParamTy; - -#[derive(PartialEq, Eq, ConstParamTy)] -struct Struct2 { - x: u16, - y: u16, -} - -#[derive(PartialEq, Eq, ConstParamTy)] -enum Enum1 { - Variant1, - Variant2(u8), -} - -struct Struct1([i32; N]); -struct BoolHolder(bool); -struct IntHolder(i32); -struct CharHolder(char); -struct StrHolder(&'static str); -struct StructHolder(Struct2); -struct EnumHolder(Enum1); -struct ArrayHolder([u16; 2]); -struct TupleHolder((u16, bool)); - -fn foo1(x: Struct1<2>) { - assert_eq!(x.0, [1, 2]); -} - -fn foo2(x: &Struct1<4>) { - assert_eq!(x.0, [1, 2, 3, 4]); -} - -fn foo3(x: BoolHolder) { - assert!(x.0); -} - -fn foo4(x: IntHolder<-1>) { - assert_eq!(x.0, -1); -} - -fn foo5(x: CharHolder<'x'>) { - assert_eq!(x.0, 'x'); -} - -fn foo6(x: StrHolder<"hello">) { - assert_eq!(x.0, "hello"); -} - -fn foo7(x: StructHolder<{ Struct2 { x: 1, y: 2 } }>) { - assert_eq!(x.0.x, 1); - assert_eq!(x.0.y, 2); -} - -fn foo8(x: EnumHolder<{ Enum1::Variant1 }>) { - assert!(matches!(x.0, Enum1::Variant1)); -} - -fn foo9(x: EnumHolder<{ Enum1::Variant2(5) }>) { - match x.0 { - Enum1::Variant1 => unreachable!(), - Enum1::Variant2(v) => assert_eq!(v, 5), - } -} - -fn foo10(x: ArrayHolder<{ [3, 4] }>) { - assert_eq!(x.0, [3, 4]); -} - -fn foo11(x: TupleHolder<{ (6, true) }>) { - assert_eq!(x.0, (6, true)); -} - -fn main() { - let f: fn(Struct1<2>) = foo1; - f(Struct1([1, 2])); - let f: fn(&Struct1<4>) = foo2; - f(&Struct1([1, 2, 3, 4])); - let f: fn(BoolHolder) = foo3; - f(BoolHolder(true)); - let f: fn(IntHolder<-1>) = foo4; - f(IntHolder(-1)); - let f: fn(CharHolder<'x'>) = foo5; - f(CharHolder('x')); - let f: fn(StrHolder<"hello">) = foo6; - f(StrHolder("hello")); - let f: fn(StructHolder<{ Struct2 { x: 1, y: 2 } }>) = foo7; - f(StructHolder(Struct2 { x: 1, y: 2 })); - let f: fn(EnumHolder<{ Enum1::Variant1 }>) = foo8; - f(EnumHolder(Enum1::Variant1)); - let f: fn(EnumHolder<{ Enum1::Variant2(5) }>) = foo9; - f(EnumHolder(Enum1::Variant2(5))); - let f: fn(ArrayHolder<{ [3, 4] }>) = foo10; - f(ArrayHolder([3, 4])); - let f: fn(TupleHolder<{ (6, true) }>) = foo11; - f(TupleHolder((6, true))); -} diff --git a/tests/ui/sanitizer/cfi/coroutine.rs b/tests/ui/sanitizer/cfi/coroutine.rs deleted file mode 100644 index d85615b597de2..0000000000000 --- a/tests/ui/sanitizer/cfi/coroutine.rs +++ /dev/null @@ -1,68 +0,0 @@ -// Verifies that we can call dynamic coroutines - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ edition: 2024 -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -//@ compile-flags: -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -Z panic-abort-tests -C prefer-dynamic=off -//@ compile-flags: --test -//@ run-pass - -#![feature(coroutines, stmt_expr_attributes)] -#![feature(coroutine_trait)] -#![feature(gen_blocks)] -#![feature(async_iterator)] - -use std::ops::{Coroutine, CoroutineState}; -use std::pin::{pin, Pin}; -use std::task::{Context, Poll, Waker}; -use std::async_iter::AsyncIterator; - -#[test] -fn general_coroutine() { - let coro = #[coroutine] |x: i32| { - yield x; - "done" - }; - let mut abstract_coro: Pin<&mut dyn Coroutine> = pin!(coro); - assert_eq!(abstract_coro.as_mut().resume(2), CoroutineState::Yielded(2)); - assert_eq!(abstract_coro.as_mut().resume(0), CoroutineState::Complete("done")); -} - -async fn async_fn() {} - -#[test] -fn async_coroutine() { - let f: fn() -> Pin>> = || Box::pin(async_fn()); - let _ = async { f().await; }; - assert_eq!(f().as_mut().poll(&mut Context::from_waker(Waker::noop())), Poll::Ready(())); -} - -async gen fn async_gen_fn() -> u8 { - yield 5; -} - -#[test] -fn async_gen_coroutine() { - let f: fn() -> Pin>> = || Box::pin(async_gen_fn()); - assert_eq!(f().as_mut().poll_next(&mut Context::from_waker(Waker::noop())), - Poll::Ready(Some(5))); -} - -gen fn gen_fn() -> u8 { - yield 6; -} - -#[test] -fn gen_coroutine() { - let f: fn() -> Box> = || Box::new(gen_fn()); - assert_eq!(f().next(), Some(6)); -} diff --git a/tests/ui/sanitizer/cfi/drop-in-place.rs b/tests/ui/sanitizer/cfi/drop-in-place.rs deleted file mode 100644 index fe59d54631248..0000000000000 --- a/tests/ui/sanitizer/cfi/drop-in-place.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Verifies that drops can be called on arbitrary trait objects. -// -// FIXME(#122848): Remove only-linux when fixed. -//@ only-linux -//@ ignore-backends: gcc -//@ needs-sanitizer-cfi -//@ compile-flags: -Clto -Copt-level=0 -Cprefer-dynamic=off -Ctarget-feature=-crt-static -Zsanitizer=cfi -//@ compile-flags: -C unsafe-allow-abi-mismatch=sanitizer -//@ run-pass - -struct EmptyDrop; - -struct NonEmptyDrop; - -impl Drop for NonEmptyDrop { - fn drop(&mut self) {} -} - -fn main() { - let _ = Box::new(EmptyDrop) as Box; - let _ = Box::new(NonEmptyDrop) as Box; -} diff --git a/tests/ui/sanitizer/cfi/drop-no-principal.rs b/tests/ui/sanitizer/cfi/drop-no-principal.rs deleted file mode 100644 index 4fb905eb51d05..0000000000000 --- a/tests/ui/sanitizer/cfi/drop-no-principal.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Check that dropping a trait object without a principal trait succeeds - -//@ needs-sanitizer-cfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries works -//@ only-linux -//@ ignore-backends: gcc -//@ compile-flags: --crate-type=bin -Cprefer-dynamic=off -Clto -Zsanitizer=cfi -C unsafe-allow-abi-mismatch=sanitizer -//@ compile-flags: -C target-feature=-crt-static -C codegen-units=1 -C opt-level=0 -// FIXME(#118761) Should be run-pass once the labels on drop are compatible. -// This test is being landed ahead of that to test that the compiler doesn't ICE while labeling the -// callsite for a drop, but the vtable doesn't have the correct label yet. -//@ build-pass - -struct CustomDrop; - -impl Drop for CustomDrop { - fn drop(&mut self) {} -} - -fn main() { - let _ = Box::new(CustomDrop) as Box; -} diff --git a/tests/ui/sanitizer/cfi/fn-ptr.rs b/tests/ui/sanitizer/cfi/fn-ptr.rs deleted file mode 100644 index bdb8c7ceb328c..0000000000000 --- a/tests/ui/sanitizer/cfi/fn-ptr.rs +++ /dev/null @@ -1,63 +0,0 @@ -// Verifies that casting to a function pointer works. - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -//@ compile-flags: -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C opt-level=0 -C codegen-units=1 -C lto -//@ [cfi] compile-flags: -C prefer-dynamic=off -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -C prefer-dynamic=off -//@ run-pass - -trait Foo { - fn foo(&self); - fn bar(&self); -} - -struct S; - -impl Foo for S { - fn foo(&self) {} - #[track_caller] - fn bar(&self) {} -} - -struct S2 { - f: fn(&S) -} - -impl S2 { - fn foo(&self, s: &S) { - (self.f)(s) - } -} - -trait Trait1 { - fn foo(&self); -} - -struct Type1; - -impl Trait1 for Type1 { - fn foo(&self) {} -} - -fn foo(_: &T) {} - -fn main() { - let type1 = Type1 {}; - let f = ::foo; - f(&type1); - // Check again with different optimization barriers - S2 { f: ::foo }.foo(&S); - // Check mismatched #[track_caller] - S2 { f: ::bar }.foo(&S); - // Check non-method functions - S2 { f: foo }.foo(&S) -} diff --git a/tests/ui/sanitizer/cfi/fn-trait-objects.rs b/tests/ui/sanitizer/cfi/fn-trait-objects.rs deleted file mode 100644 index 977d4124fff0c..0000000000000 --- a/tests/ui/sanitizer/cfi/fn-trait-objects.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Verifies that types that implement the Fn, FnMut, or FnOnce traits can be -// called through their trait methods. -// -//@ needs-sanitizer-cfi -//@ only-linux -//@ ignore-backends: gcc -//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer --test -//@ run-pass - -#![feature(fn_traits)] -#![feature(unboxed_closures)] - -fn foo(_a: u32) {} - -#[test] -fn test_fn_trait() { - let f: Box = Box::new(foo); - Fn::call(&f, (0,)); -} - -#[test] -fn test_fnmut_trait() { - let mut a = 0; - let mut f: Box = Box::new(|x| a += x); - FnMut::call_mut(&mut f, (1,)); -} - -#[test] -fn test_fnonce_trait() { - let f: Box = Box::new(foo); - FnOnce::call_once(f, (2,)); -} diff --git a/tests/ui/sanitizer/cfi/self-ref.rs b/tests/ui/sanitizer/cfi/self-ref.rs deleted file mode 100644 index 827610a261064..0000000000000 --- a/tests/ui/sanitizer/cfi/self-ref.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Check that encoding self-referential types works with #[repr(transparent)] - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -C prefer-dynamic=off -//@ run-pass - -use std::marker::PhantomData; - -struct X { - _x: u8, - p: PhantomData, -} - -#[repr(transparent)] -struct Y(X); - -trait Fooable { - fn foo(&self, y: Y); -} - -struct Bar; - -impl Fooable for Bar { - fn foo(&self, _: Y) {} -} - -fn main() { - let x = &Bar as &dyn Fooable; - x.foo(Y(X {_x: 0, p: PhantomData})); -} diff --git a/tests/ui/sanitizer/cfi/sized-associated-ty.rs b/tests/ui/sanitizer/cfi/sized-associated-ty.rs deleted file mode 100644 index da8c385c6fc8b..0000000000000 --- a/tests/ui/sanitizer/cfi/sized-associated-ty.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Check that we only elaborate non-`Self: Sized` associated types when -// erasing the receiver from trait ref. - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -C prefer-dynamic=off -//@ run-pass - -trait Foo { - type Bar<'a> - where - Self: Sized; - - fn test(&self); -} - -impl Foo for () { - type Bar<'a> = () - where - Self: Sized; - - fn test(&self) {} -} - -fn test(x: &dyn Foo) { - x.test(); -} - -fn main() { - test(&()); -} diff --git a/tests/ui/sanitizer/cfi/supertraits.rs b/tests/ui/sanitizer/cfi/supertraits.rs deleted file mode 100644 index b2782dff5d555..0000000000000 --- a/tests/ui/sanitizer/cfi/supertraits.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Check that super-traits are callable. - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -C prefer-dynamic=off -//@ run-pass - -trait Parent1 { - type P1; - fn p1(&self) -> Self::P1; - fn d(&self) -> i32 { - 42 - } -} - -trait Parent2 { - type P2; - fn p2(&self) -> Self::P2; -} - -trait Child : Parent1 + Parent2 { - type C; - fn c(&self) -> Self::C; -} - -struct Foo; - -impl Parent1 for Foo { - type P1 = u16; - fn p1(&self) -> Self::P1 { - println!("p1"); - 1 - } -} - -impl Parent2 for Foo { - type P2 = u32; - fn p2(&self) -> Self::P2 { - println!("p2"); - 2 - } -} - -impl Child for Foo { - type C = u8; - fn c(&self) -> Self::C { - println!("c"); - 0 - } -} - -fn main() { - // Child can access its own methods and super methods. - let x = &Foo as &dyn Child; - x.c(); - x.p1(); - x.p2(); - x.d(); - // Parents can be created and access their methods. - let y = &Foo as &dyn Parent1; - y.p1(); - y.d(); - let z = &Foo as &dyn Parent2; - z.p2(); - // Trait upcasting works - let x1 = x as &dyn Parent1; - x1.p1(); - x1.d(); - let x2 = x as &dyn Parent2; - x2.p2(); -} diff --git a/tests/ui/sanitizer/cfi/transparent-has-regions.rs b/tests/ui/sanitizer/cfi/transparent-has-regions.rs deleted file mode 100644 index 3e9893df23c92..0000000000000 --- a/tests/ui/sanitizer/cfi/transparent-has-regions.rs +++ /dev/null @@ -1,19 +0,0 @@ -//@ needs-sanitizer-cfi -//@ compile-flags: -Ccodegen-units=1 -Clto -Ctarget-feature=-crt-static -Zsanitizer=cfi -C unsafe-allow-abi-mismatch=sanitizer -//@ no-prefer-dynamic -//@ only-x86_64-unknown-linux-gnu -//@ build-pass -//@ ignore-backends: gcc - -pub trait Trait {} - -impl Trait for i32 {} - -#[repr(transparent)] -struct BoxedTrait(Box); - -fn hello(x: BoxedTrait) {} - -fn main() { - hello(BoxedTrait(Box::new(1))); -} diff --git a/tests/ui/sanitizer/cfi/virtual-auto.rs b/tests/ui/sanitizer/cfi/virtual-auto.rs deleted file mode 100644 index d3a715c079aa6..0000000000000 --- a/tests/ui/sanitizer/cfi/virtual-auto.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Tests that calling a trait object method on a trait object with additional auto traits works. - -//@ revisions: cfi kcfi -// FIXME(#122848) Remove only-linux once OSX CFI binaries work -//@ only-linux -//@ ignore-backends: gcc -//@ [cfi] needs-sanitizer-cfi -//@ [kcfi] needs-sanitizer-kcfi -//@ compile-flags: -C target-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer -//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0 -//@ [cfi] compile-flags: -Z sanitizer=cfi -//@ [kcfi] compile-flags: -Z sanitizer=kcfi -//@ [kcfi] compile-flags: -C panic=abort -C prefer-dynamic=off -//@ run-pass - -trait Foo { - fn foo(&self); -} - -struct Bar; -impl Foo for Bar { - fn foo(&self) {} -} - -pub fn main() { - let x: &(dyn Foo + Send) = &Bar; - x.foo(); -} diff --git a/tests/ui/sanitizer/cfi/works-with-associated-types.rs b/tests/ui/sanitizer/cfi/works-with-associated-types.rs new file mode 100644 index 0000000000000..d2b0805a95977 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-associated-types.rs @@ -0,0 +1,53 @@ +// Verifies that trait methods can be called through trait objects with +// associated types. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + type Output; + fn foo(&self) -> Self::Output; +} + +struct Type1; + +impl Trait1 for Type1 { + type Output = i32; + // ::foo is transformed into as Trait1>::foo + fn foo(&self) -> Self::Output { + 1 + } +} + +trait Trait2 { + type Output<'a> + where + Self: Sized; + + fn bar(&self) -> i32; +} + +impl Trait2 for () { + type Output<'a> + = () + where + Self: Sized; + + // <() as Trait2>::bar is transformed into ::bar + fn bar(&self) -> i32 { + 2 + } +} + +fn main() { + let x: &dyn Trait1 = &Type1; + // The virtual method call is transformed into as Trait1>::foo + assert_eq!(x.foo(), 1); + let x: &dyn Trait2 = &(); + // The virtual method call is transformed into ::bar + assert_eq!(x.bar(), 2); +} diff --git a/tests/ui/sanitizer/cfi/works-with-async-closures.rs b/tests/ui/sanitizer/cfi/works-with-async-closures.rs new file mode 100644 index 0000000000000..4978dc9709ee2 --- /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..dd827e0615572 --- /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..1c5352693c65d --- /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 ::as_ptr FnPtrAsPtrShims 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..af8ad1471f8ae --- /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..c3c38cb0fba60 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-intrinsics.rs @@ -0,0 +1,29 @@ +// 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(link_llvm_intrinsics)] +#![allow(internal_features)] + +unsafe extern "llvm-intrinsic" { + #[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 lowered by codegen + // and does not have its own callable MIR, and its fallback body uses InstanceKind::Item, so + // InstanceKind::Intrinsic is not transformed. + assert_eq!(std::hint::black_box(1i32), 1); + + // LLVM intrinsics + // The bitreverse LLVM intrinsic (i.e., a fn item with extern "llvm-intrinsic") is lowered + // by codegen and does not have its own callable MIR, so it is not transformed. + 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..25fc881fef2f4 --- /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 can not be reified or +// called indirectly. +thread_local! { + static COUNTER: Cell = const { Cell::new(0) }; +} + +fn main() { + COUNTER.with(|counter| counter.set(counter.get() + 1)); + assert_eq!(COUNTER.with(|counter| counter.get()), 1); +} diff --git a/tests/ui/sanitizer/cfi/works-with-trait-objects.rs b/tests/ui/sanitizer/cfi/works-with-trait-objects.rs new file mode 100644 index 0000000000000..d6bbd8f825c85 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-trait-objects.rs @@ -0,0 +1,67 @@ +// Verifies that trait methods (i.e., both trait method implementations in impl +// blocks and provided (default) trait methods in trait blocks) can be called +// through trait objects. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + fn foo(&self) -> i32; + // ::bar is transformed into ::bar + fn bar(&self) -> i32 { + 2 + } + // ::baz is not transformed, as it can not be called through a vtable + fn baz(&self) -> i32 + where + Self: Sized, + { + 3 + } +} + +trait Trait2 { + fn qux(&self) -> i32; +} + +struct Type1; + +impl Trait1 for Type1 { + // ::foo is transformed into ::foo + fn foo(&self) -> i32 { + 1 + } +} + +impl Trait2 for Type1 { + // ::qux is not transformed, as Trait2 is not dyn compatible + fn qux(&self) -> i32 { + 4 + } +} + +fn main() { + // Trait methods, through a reference to a trait object + let x = &Type1 as &dyn Trait1; + // The virtual method call is transformed into ::foo + assert_eq!(x.foo(), 1); + // The virtual method call is transformed into ::bar + assert_eq!(x.bar(), 2); + + // Trait methods, through a boxed trait object + let x: Box = Box::new(Type1); + // The virtual method call is transformed into ::foo + assert_eq!(x.foo(), 1); + // The virtual method call is transformed into ::bar + assert_eq!(x.bar(), 2); + + // Trait methods that can not be called through a vtable + // ::baz is not transformed, as it can not be called through a vtable + assert_eq!(Type1.baz::(), 3); + // ::qux is not transformed, as Trait2 is not dyn compatible + assert_eq!(Type1.qux::(), 4); +} diff --git a/tests/ui/sanitizer/cfi/works-with-trait-types.rs b/tests/ui/sanitizer/cfi/works-with-trait-types.rs new file mode 100644 index 0000000000000..b0397dd40cb36 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-trait-types.rs @@ -0,0 +1,52 @@ +// Verifies that functions with trait types (i.e., trait objects) as argument +// types can be called through function pointers. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + fn foo(&self) -> i32; +} + +struct Type1; + +impl Trait1 for Type1 { + fn foo(&self) -> i32 { + 1 + } +} + +fn foo1(x: &dyn Trait1) -> i32 { + assert_eq!(x.foo(), 1); + 1 +} + +fn foo2(x: &mut dyn Trait1) -> i32 { + assert_eq!(x.foo(), 1); + 2 +} + +fn foo3(x: Box) -> i32 { + assert_eq!(x.foo(), 1); + 3 +} + +// A trait object without a principal trait, so its predicates are all auto traits +fn foo4(_: &dyn Send) -> i32 { + 4 +} + +fn main() { + let f: fn(&dyn Trait1) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(&Type1), 1); + let f: fn(&mut dyn Trait1) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(&mut Type1), 2); + let f: fn(Box) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(Box::new(Type1)), 3); + let f: fn(&dyn Send) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(&Type1), 4); +} diff --git a/tests/ui/sanitizer/cfi/works-with-user-defined-types.rs b/tests/ui/sanitizer/cfi/works-with-user-defined-types.rs new file mode 100644 index 0000000000000..bd6c5e7840fb9 --- /dev/null +++ b/tests/ui/sanitizer/cfi/works-with-user-defined-types.rs @@ -0,0 +1,85 @@ +// Verifies that functions with user-defined types (i.e., structs, enums, +// unions, and extern types) as argument types can be called through function +// pointers. +// +//@ needs-sanitizer-cfi +// FIXME(#122848) Remove only-linux once OSX CFI binaries work +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +#![feature(extern_types)] + +struct Struct1(i32); + +enum Enum1 { + Variant1(i32), +} + +union Union1 { + f: i32, +} + +#[repr(C)] +struct Struct2 { + f: i32, +} + +struct Struct3(T); + +unsafe extern "C" { + type Type1; +} + +fn foo1(x: Struct1) -> i32 { + assert_eq!(x.0, 1); + 1 +} + +fn foo2(x: Enum1) -> i32 { + let Enum1::Variant1(y) = x; + assert_eq!(y, 2); + 2 +} + +fn foo3(x: Union1) -> i32 { + assert_eq!(unsafe { x.f }, 3); + 3 +} + +fn foo4(x: Struct2) -> i32 { + assert_eq!(x.f, 4); + 4 +} + +fn foo5(x: Struct3) -> i32 { + assert_eq!(x.0, 5); + 5 +} + +fn foo6(_: *const Type1) -> i32 { + 6 +} + +extern "C" fn foo7(x: Struct2) -> i32 { + assert_eq!(x.f, 7); + 7 +} + +fn main() { + let f: fn(Struct1) -> i32 = std::hint::black_box(foo1); + assert_eq!(f(Struct1(1)), 1); + let f: fn(Enum1) -> i32 = std::hint::black_box(foo2); + assert_eq!(f(Enum1::Variant1(2)), 2); + let f: fn(Union1) -> i32 = std::hint::black_box(foo3); + assert_eq!(f(Union1 { f: 3 }), 3); + let f: fn(Struct2) -> i32 = std::hint::black_box(foo4); + assert_eq!(f(Struct2 { f: 4 }), 4); + let f: fn(Struct3) -> i32 = std::hint::black_box(foo5); + assert_eq!(f(Struct3(5)), 5); + let f: fn(*const Type1) -> i32 = std::hint::black_box(foo6); + assert_eq!(f(&() as *const () as *const Type1), 6); + let f: extern "C" fn(Struct2) -> i32 = std::hint::black_box(foo7); + assert_eq!(f(Struct2 { f: 7 }), 7); +} diff --git a/tests/ui/sanitizer/kcfi-c-variadic.rs b/tests/ui/sanitizer/kcfi-c-variadic.rs deleted file mode 100644 index 2f88ccfb1269c..0000000000000 --- a/tests/ui/sanitizer/kcfi-c-variadic.rs +++ /dev/null @@ -1,18 +0,0 @@ -//@ needs-sanitizer-kcfi -//@ no-prefer-dynamic -//@ compile-flags: -Zsanitizer=kcfi -Cpanic=abort -Cunsafe-allow-abi-mismatch=sanitizer -//@ ignore-backends: gcc -//@ run-pass - -trait Trait { - unsafe extern "C" fn foo(x: i32, y: i32, mut ap: ...) -> i32 { - x + y + ap.next_arg::() + ap.next_arg::() - } -} - -impl Trait for i32 {} - -fn main() { - let f = i32::foo as unsafe extern "C" fn(i32, i32, ...) -> i32; - assert_eq!(unsafe { f(1, 2, 3, 4) }, 1 + 2 + 3 + 4); -} diff --git a/tests/ui/sanitizer/kcfi-mangling.rs b/tests/ui/sanitizer/kcfi-mangling.rs deleted file mode 100644 index 371f34ba72af2..0000000000000 --- a/tests/ui/sanitizer/kcfi-mangling.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Check KCFI extra mangling works correctly on v0 - -//@ needs-sanitizer-kcfi -//@ no-prefer-dynamic -//@ compile-flags: -C panic=abort -Zsanitizer=kcfi -C symbol-mangling-version=v0 -C unsafe-allow-abi-mismatch=sanitizer -//@ build-pass -//@ ignore-backends: gcc - -trait Foo { - fn foo(&self); -} - -struct Bar; -impl Foo for Bar { - fn foo(&self) {} -} - -struct Baz; -impl Foo for Baz { - #[track_caller] - fn foo(&self) {} -} - -fn main() { - // Produces `ReifyShim(_, ReifyReason::FnPtr)` - let f: fn(&Bar) = Bar::foo; - f(&Bar); - // Produces `ReifyShim(_, ReifyReason::Vtable)` - let v: &dyn Foo = &Baz as _; - v.foo(); -} diff --git a/tests/ui/sanitizer/kcfi/const-generics.rs b/tests/ui/sanitizer/kcfi/const-generics.rs deleted file mode 100644 index 86f487bb9ea1e..0000000000000 --- a/tests/ui/sanitizer/kcfi/const-generics.rs +++ /dev/null @@ -1,109 +0,0 @@ -// Verifies that functions with types with const generics as argument types can -// be called through function pointers. -// -//@ needs-sanitizer-kcfi -//@ only-linux -//@ ignore-backends: gcc -//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer -//@ run-pass - -#![feature(adt_const_params)] -#![feature(unsized_const_params)] -#![allow(incomplete_features)] - -use std::marker::ConstParamTy; - -#[derive(PartialEq, Eq, ConstParamTy)] -struct Struct2 { - x: u16, - y: u16, -} - -#[derive(PartialEq, Eq, ConstParamTy)] -enum Enum1 { - Variant1, - Variant2(u8), -} - -struct Struct1([i32; N]); -struct BoolHolder(bool); -struct IntHolder(i32); -struct CharHolder(char); -struct StrHolder(&'static str); -struct StructHolder(Struct2); -struct EnumHolder(Enum1); -struct ArrayHolder([u16; 2]); -struct TupleHolder((u16, bool)); - -fn foo1(x: Struct1<2>) { - assert_eq!(x.0, [1, 2]); -} - -fn foo2(x: &Struct1<4>) { - assert_eq!(x.0, [1, 2, 3, 4]); -} - -fn foo3(x: BoolHolder) { - assert!(x.0); -} - -fn foo4(x: IntHolder<-1>) { - assert_eq!(x.0, -1); -} - -fn foo5(x: CharHolder<'x'>) { - assert_eq!(x.0, 'x'); -} - -fn foo6(x: StrHolder<"hello">) { - assert_eq!(x.0, "hello"); -} - -fn foo7(x: StructHolder<{ Struct2 { x: 1, y: 2 } }>) { - assert_eq!(x.0.x, 1); - assert_eq!(x.0.y, 2); -} - -fn foo8(x: EnumHolder<{ Enum1::Variant1 }>) { - assert!(matches!(x.0, Enum1::Variant1)); -} - -fn foo9(x: EnumHolder<{ Enum1::Variant2(5) }>) { - match x.0 { - Enum1::Variant1 => unreachable!(), - Enum1::Variant2(v) => assert_eq!(v, 5), - } -} - -fn foo10(x: ArrayHolder<{ [3, 4] }>) { - assert_eq!(x.0, [3, 4]); -} - -fn foo11(x: TupleHolder<{ (6, true) }>) { - assert_eq!(x.0, (6, true)); -} - -fn main() { - let f: fn(Struct1<2>) = foo1; - f(Struct1([1, 2])); - let f: fn(&Struct1<4>) = foo2; - f(&Struct1([1, 2, 3, 4])); - let f: fn(BoolHolder) = foo3; - f(BoolHolder(true)); - let f: fn(IntHolder<-1>) = foo4; - f(IntHolder(-1)); - let f: fn(CharHolder<'x'>) = foo5; - f(CharHolder('x')); - let f: fn(StrHolder<"hello">) = foo6; - f(StrHolder("hello")); - let f: fn(StructHolder<{ Struct2 { x: 1, y: 2 } }>) = foo7; - f(StructHolder(Struct2 { x: 1, y: 2 })); - let f: fn(EnumHolder<{ Enum1::Variant1 }>) = foo8; - f(EnumHolder(Enum1::Variant1)); - let f: fn(EnumHolder<{ Enum1::Variant2(5) }>) = foo9; - f(EnumHolder(Enum1::Variant2(5))); - let f: fn(ArrayHolder<{ [3, 4] }>) = foo10; - f(ArrayHolder([3, 4])); - let f: fn(TupleHolder<{ (6, true) }>) = foo11; - f(TupleHolder((6, true))); -} diff --git a/tests/ui/sanitizer/kcfi/fn-trait-objects.rs b/tests/ui/sanitizer/kcfi/fn-trait-objects.rs deleted file mode 100644 index 3f6b78545a0a1..0000000000000 --- a/tests/ui/sanitizer/kcfi/fn-trait-objects.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Verifies that types that implement the Fn, FnMut, or FnOnce traits can be -// called through their trait methods. -// -//@ needs-sanitizer-kcfi -//@ only-linux -//@ ignore-backends: gcc -//@ compile-flags: -Ctarget-feature=-crt-static -Zpanic_abort_tests -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer --test -//@ run-pass - -#![feature(fn_traits)] -#![feature(unboxed_closures)] - -fn foo(_a: u32) {} - -#[test] -fn test_fn_trait() { - let f: Box = Box::new(foo); - Fn::call(&f, (0,)); -} - -#[test] -fn test_fnmut_trait() { - let mut a = 0; - let mut f: Box = Box::new(|x| a += x); - FnMut::call_mut(&mut f, (1,)); -} - -#[test] -fn test_fnonce_trait() { - let f: Box = Box::new(foo); - FnOnce::call_once(f, (2,)); -} diff --git a/tests/ui/sanitizer/kcfi/works-with-associated-types.rs b/tests/ui/sanitizer/kcfi/works-with-associated-types.rs new file mode 100644 index 0000000000000..016b0c29568d1 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-associated-types.rs @@ -0,0 +1,52 @@ +// Verifies that trait methods can be called through trait objects with +// associated types. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer +//@ run-pass + +trait Trait1 { + type Output; + fn foo(&self) -> Self::Output; +} + +struct Type1; + +impl Trait1 for Type1 { + type Output = i32; + // ::foo is transformed into as Trait1>::foo + fn foo(&self) -> Self::Output { + 1 + } +} + +trait Trait2 { + type Output<'a> + where + Self: Sized; + + fn bar(&self) -> i32; +} + +impl Trait2 for () { + type Output<'a> + = () + where + Self: Sized; + + // <() as Trait2>::bar is transformed into ::bar + fn bar(&self) -> i32 { + 2 + } +} + +fn main() { + let x: &dyn Trait1 = &Type1; + // The virtual method call is transformed into as Trait1>::foo + assert_eq!(x.foo(), 1); + let x: &dyn Trait2 = &(); + // The virtual method call is transformed into ::bar + assert_eq!(x.bar(), 2); +} diff --git a/tests/ui/sanitizer/kcfi/works-with-async-closures.rs b/tests/ui/sanitizer/kcfi/works-with-async-closures.rs new file mode 100644 index 0000000000000..5cc24a4c11da9 --- /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..c23433cea2323 --- /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..f706bd0fd6150 --- /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 ::as_ptr FnPtrAsPtrShims 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..a63390111e5ae --- /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..e4ace9166cf08 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/works-with-intrinsics.rs @@ -0,0 +1,28 @@ +// 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(link_llvm_intrinsics)] +#![allow(internal_features)] + +unsafe extern "llvm-intrinsic" { + #[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 lowered by codegen + // and does not have its own callable MIR, and its fallback body uses InstanceKind::Item, so + // InstanceKind::Intrinsic is not transformed. + assert_eq!(std::hint::black_box(1i32), 1); + + // LLVM intrinsics + // The bitreverse LLVM intrinsic (i.e., a fn item with extern "llvm-intrinsic") is lowered + // by codegen and does not have its own callable MIR, so it is not transformed. + 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..e703202671fa3 --- /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 can not be reified or +// called indirectly. +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); +}