Skip to content

Commit 6bce455

Browse files
committed
CFI: Rewrite closure and coroutine instances to their trait method
Similar to methods on a trait object, the most common way to indirectly call a closure or coroutine is through the vtable on the appropriate trait. This uses the same approach as we use for trait methods, after backing out the trait arguments from the type.
1 parent 3e8187f commit 6bce455

File tree

5 files changed

+138
-22
lines changed

5 files changed

+138
-22
lines changed

compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs

+48
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use rustc_data_structures::base_n;
1111
use rustc_data_structures::fx::FxHashMap;
1212
use rustc_hir as hir;
13+
use rustc_hir::lang_items::LangItem;
1314
use rustc_middle::ty::layout::IntegerExt;
1415
use rustc_middle::ty::TypeVisitableExt;
1516
use rustc_middle::ty::{
@@ -1123,6 +1124,14 @@ pub fn typeid_for_instance<'tcx>(
11231124
) -> String {
11241125
if matches!(instance.def, ty::InstanceDef::Virtual(..)) {
11251126
instance.args = strip_receiver_auto(tcx, instance.args)
1127+
} else if let ty::InstanceDef::VTableShim(def_id) = instance.def
1128+
&& let Some(trait_id) = tcx.trait_of_item(def_id)
1129+
{
1130+
// VTableShims may have a trait method, but a concrete Self. This is not suitable for a vtable,
1131+
// as the caller will not know the concrete Self.
1132+
let trait_ref = ty::TraitRef::new(tcx, trait_id, instance.args);
1133+
let invoke_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref));
1134+
instance.args = tcx.mk_args_trait(invoke_ty, trait_ref.args.into_iter().skip(1));
11261135
}
11271136

11281137
if let Some(impl_id) = tcx.impl_of_method(instance.def_id())
@@ -1160,6 +1169,45 @@ pub fn typeid_for_instance<'tcx>(
11601169
tcx.mk_args_trait(invoke_ty, trait_ref.args.into_iter().skip(1));
11611170
instance.args = instance.args.rebase_onto(tcx, impl_id, abstract_trait_args);
11621171
}
1172+
} else if tcx.is_closure_like(instance.def_id()) {
1173+
// We're either a closure or a coroutine. Our goal is to find the trait we're defined on,
1174+
// instantiate it, and take the type of its only method as our own.
1175+
let closure_ty = instance.ty(tcx, ty::ParamEnv::reveal_all());
1176+
let (trait_id, inputs) = match closure_ty.kind() {
1177+
ty::Closure(..) => {
1178+
let closure_args = instance.args.as_closure();
1179+
let trait_id = tcx.fn_trait_kind_to_def_id(closure_args.kind()).unwrap();
1180+
let tuple_args =
1181+
tcx.instantiate_bound_regions_with_erased(closure_args.sig()).inputs()[0];
1182+
(trait_id, tuple_args)
1183+
}
1184+
ty::Coroutine(..) => (
1185+
tcx.require_lang_item(LangItem::Coroutine, None),
1186+
instance.args.as_coroutine().resume_ty(),
1187+
),
1188+
ty::CoroutineClosure(..) => (
1189+
tcx.require_lang_item(LangItem::FnOnce, None),
1190+
tcx.instantiate_bound_regions_with_erased(
1191+
instance.args.as_coroutine_closure().coroutine_closure_sig(),
1192+
)
1193+
.tupled_inputs_ty,
1194+
),
1195+
x => bug!("Unexpected type kind for closure-like: {x:?}"),
1196+
};
1197+
let trait_ref = ty::TraitRef::new(tcx, trait_id, [closure_ty, inputs]);
1198+
let invoke_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref));
1199+
let abstract_args = tcx.mk_args_trait(invoke_ty, trait_ref.args.into_iter().skip(1));
1200+
// There should be exactly one method on this trait, and it should be the one we're
1201+
// defining.
1202+
let call = tcx
1203+
.associated_items(trait_id)
1204+
.in_definition_order()
1205+
.find(|it| it.kind == ty::AssocKind::Fn)
1206+
.expect("No call-family function on closure-like Fn trait?")
1207+
.def_id;
1208+
1209+
instance.def = ty::InstanceDef::Virtual(call, 0);
1210+
instance.args = abstract_args;
11631211
}
11641212

11651213
let fn_abi = tcx

tests/ui/sanitizer/cfi-async-closures.rs

+2
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@ fn main() {
2727
let f = identity(async || ());
2828
let _ = f.async_call(());
2929
let _ = f();
30+
let g: Box<dyn FnOnce() -> _> = Box::new(f) as _;
31+
let _ = g();
3032
}

tests/ui/sanitizer/cfi-closure-fn-ptr-cast.rs

-22
This file was deleted.

tests/ui/sanitizer/cfi-closures.rs

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Check various forms of dynamic closure calls
2+
3+
//@ revisions: cfi kcfi
4+
// FIXME(#122848) Remove only-linux once OSX CFI binaries work
5+
//@ only-linux
6+
//@ [cfi] needs-sanitizer-cfi
7+
//@ [kcfi] needs-sanitizer-kcfi
8+
//@ compile-flags: -C target-feature=-crt-static
9+
//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0
10+
//@ [cfi] compile-flags: -Z sanitizer=cfi
11+
//@ [kcfi] compile-flags: -Z sanitizer=kcfi
12+
//@ compile-flags: --test
13+
//@ run-pass
14+
15+
#![feature(fn_traits)]
16+
17+
fn foo<'a, T>() -> Box<dyn Fn(&'a T) -> &'a T> {
18+
Box::new(|x| x)
19+
}
20+
21+
#[test]
22+
fn dyn_fn_with_params() {
23+
let x = 3;
24+
let f = foo();
25+
f(&x);
26+
// FIXME remove once drops are working.
27+
std::mem::forget(f);
28+
}
29+
30+
#[test]
31+
fn call_fn_trait() {
32+
let f: &(dyn Fn()) = &(|| {}) as _;
33+
f.call(());
34+
}
35+
36+
#[test]
37+
fn fn_ptr_cast() {
38+
let f: &fn() = &((|| ()) as _);
39+
f();
40+
}
41+
42+
fn use_fnmut<F: FnMut()>(mut f: F) {
43+
f()
44+
}
45+
46+
#[test]
47+
fn fn_to_fnmut() {
48+
let f: &(dyn Fn()) = &(|| {}) as _;
49+
use_fnmut(f);
50+
}
51+
52+
fn hrtb_helper(f: &dyn for<'a> Fn(&'a usize)) {
53+
f(&10)
54+
}
55+
56+
#[test]
57+
fn hrtb_fn() {
58+
hrtb_helper((&|x: &usize| println!("{}", *x)) as _)
59+
}
60+
61+
#[test]
62+
fn fnonce() {
63+
let f: Box<dyn FnOnce()> = Box::new(|| {}) as _;
64+
f();
65+
}

tests/ui/sanitizer/cfi-coroutine.rs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Verifies that we can call dynamic coroutines
2+
//
3+
// FIXME(#122848): Remove only-linux when fixed.
4+
//@ only-linux
5+
//@ needs-sanitizer-cfi
6+
//@ compile-flags: -Clto -Copt-level=0 -Cprefer-dynamic=off -Ctarget-feature=-crt-static -Zsanitizer=cfi
7+
//@ run-pass
8+
9+
#![feature(coroutines)]
10+
#![feature(coroutine_trait)]
11+
12+
use std::ops::{Coroutine, CoroutineState};
13+
use std::pin::{pin, Pin};
14+
15+
fn main() {
16+
let mut coro = |x: i32| {
17+
yield x;
18+
"done"
19+
};
20+
let mut abstract_coro: Pin<&mut dyn Coroutine<i32,Yield=i32,Return=&'static str>> = pin!(coro);
21+
assert_eq!(abstract_coro.as_mut().resume(2), CoroutineState::Yielded(2));
22+
assert_eq!(abstract_coro.as_mut().resume(0), CoroutineState::Complete("done"));
23+
}

0 commit comments

Comments
 (0)