Skip to content

Commit b222013

Browse files
authored
Rollup merge of rust-lang#146865 - folkertdev:kcfi-only-reify-dyn-compatible, r=rcvalle
kcfi: only reify trait methods when dyn-compatible fixes rust-lang#146853 Only generate a `ReifyShim` for trait method calls if the trait is dyn-compatible. Until now kcfi would generate a `ReifyShim` whenever a trait method was cast to a function pointer. But technically the shim is only needed for dyn-compatible traits (where the method might end up in a vtable). Up to this point that was only slightly inefficient, but in combination with c-variadic trait methods it is wrong. For c-variadic trait methods the generated shim is incorrect, and that is why c-variadic methods make a trait no longer dyn-compatible: we should simply never generate a `ReifyShim` that is c-variadic. With this change the documentation on `ReifyReason` is now actually correct: > If KCFI is enabled, creating a function pointer from a method on a dyn-compatible trait. This includes the case of converting `::call`-like methods on closure-likes to function pointers. cc ``@maurer`` ``@workingjubilee`` r? ``@rcvalle``
2 parents 1fb7f3d + f51fb91 commit b222013

File tree

4 files changed

+100
-5
lines changed

4 files changed

+100
-5
lines changed

compiler/rustc_middle/src/ty/instance.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -618,11 +618,11 @@ impl<'tcx> Instance<'tcx> {
618618
// be directly reified because it's closure-like. The reify can handle the
619619
// unresolved instance.
620620
resolved = Instance { def: InstanceKind::ReifyShim(def_id, reason), args }
621-
// Reify `Trait::method` implementations
622-
// FIXME(maurer) only reify it if it is a vtable-safe function
621+
// Reify `Trait::method` implementations if the trait is dyn-compatible.
623622
} else if let Some(assoc) = tcx.opt_associated_item(def_id)
624623
&& let AssocContainer::Trait | AssocContainer::TraitImpl(Ok(_)) =
625624
assoc.container
625+
&& tcx.is_dyn_compatible(assoc.container_id(tcx))
626626
{
627627
// If this function could also go in a vtable, we need to `ReifyShim` it with
628628
// KCFI because it can only attach one type per function.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//@ add-core-stubs
2+
//@ revisions: aarch64 x86_64
3+
//@ [aarch64] compile-flags: --target aarch64-unknown-none
4+
//@ [aarch64] needs-llvm-components: aarch64
5+
//@ [x86_64] compile-flags: --target x86_64-unknown-none
6+
//@ [x86_64] needs-llvm-components: x86
7+
//@ compile-flags: -Ctarget-feature=-crt-static -Zsanitizer=kcfi -Cno-prepopulate-passes -Copt-level=0
8+
9+
#![feature(no_core, lang_items)]
10+
#![crate_type = "lib"]
11+
#![no_core]
12+
13+
// A `ReifyShim` should only be created when the trait is dyn-compatible.
14+
15+
extern crate minicore;
16+
use minicore::*;
17+
18+
trait DynCompatible {
19+
fn dyn_name(&self) -> &'static str;
20+
21+
fn dyn_name_default(&self) -> &'static str {
22+
let _ = self;
23+
"dyn_default"
24+
}
25+
}
26+
27+
// Not dyn-compatible because the `Self: Sized` bound is missing.
28+
trait NotDynCompatible {
29+
fn not_dyn_name() -> &'static str;
30+
31+
fn not_dyn_name_default() -> &'static str {
32+
"not_dyn_default"
33+
}
34+
}
35+
36+
struct S;
37+
38+
impl DynCompatible for S {
39+
fn dyn_name(&self) -> &'static str {
40+
"dyn_compatible"
41+
}
42+
}
43+
44+
impl NotDynCompatible for S {
45+
fn not_dyn_name() -> &'static str {
46+
"not_dyn_compatible"
47+
}
48+
}
49+
50+
#[no_mangle]
51+
pub fn main() {
52+
let s = S;
53+
54+
// `DynCompatible` is indeed dyn-compatible.
55+
let _: &dyn DynCompatible = &s;
56+
57+
// CHECK: call <fn_ptr_reify_shim::S as fn_ptr_reify_shim::DynCompatible>::dyn_name{{.*}}reify.shim.fnptr
58+
let dyn_name = S::dyn_name as fn(&S) -> &str;
59+
let _unused = dyn_name(&s);
60+
61+
// CHECK: call fn_ptr_reify_shim::DynCompatible::dyn_name_default{{.*}}reify.shim.fnptr
62+
let dyn_name_default = S::dyn_name_default as fn(&S) -> &str;
63+
let _unused = dyn_name_default(&s);
64+
65+
// Check using $ (end-of-line) that these calls do not contain `reify.shim.fnptr`.
66+
67+
// CHECK: call <fn_ptr_reify_shim::S as fn_ptr_reify_shim::NotDynCompatible>::not_dyn_name{{$}}
68+
let not_dyn_name = S::not_dyn_name as fn() -> &'static str;
69+
let _unused = not_dyn_name();
70+
71+
// CHECK: call fn_ptr_reify_shim::NotDynCompatible::not_dyn_name_default{{$}}
72+
let not_dyn_name_default = S::not_dyn_name_default as fn() -> &'static str;
73+
let _unused = not_dyn_name_default();
74+
}

tests/codegen-llvm/sanitizer/kcfi/naked-function.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ use minicore::*;
1515

1616
struct Thing;
1717
trait MyTrait {
18+
// NOTE: this test assumes that this trait is dyn-compatible.
1819
#[unsafe(naked)]
19-
extern "C" fn my_naked_function() {
20+
extern "C" fn my_naked_function(&self) {
2021
// the real function is defined
2122
// CHECK: .globl
2223
// CHECK-SAME: my_naked_function
@@ -34,13 +35,13 @@ impl MyTrait for Thing {}
3435
#[unsafe(no_mangle)]
3536
pub fn main() {
3637
// Trick the compiler into generating an indirect call.
37-
const F: extern "C" fn() = Thing::my_naked_function;
38+
const F: extern "C" fn(&Thing) = Thing::my_naked_function;
3839

3940
// main calls the shim function
4041
// CHECK: call void
4142
// CHECK-SAME: my_naked_function
4243
// CHECK-SAME: reify.shim.fnptr
43-
(F)();
44+
(F)(&Thing);
4445
}
4546

4647
// CHECK: declare !kcfi_type
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//@ needs-sanitizer-kcfi
2+
//@ no-prefer-dynamic
3+
//@ compile-flags: -Zsanitizer=kcfi -Cpanic=abort -Cunsafe-allow-abi-mismatch=sanitizer
4+
//@ ignore-backends: gcc
5+
//@ run-pass
6+
7+
#![feature(c_variadic)]
8+
9+
trait Trait {
10+
unsafe extern "C" fn foo(x: i32, y: i32, mut ap: ...) -> i32 {
11+
x + y + ap.arg::<i32>() + ap.arg::<i32>()
12+
}
13+
}
14+
15+
impl Trait for i32 {}
16+
17+
fn main() {
18+
let f = i32::foo as unsafe extern "C" fn(i32, i32, ...) -> i32;
19+
assert_eq!(unsafe { f(1, 2, 3, 4) }, 1 + 2 + 3 + 4);
20+
}

0 commit comments

Comments
 (0)