Skip to content

Commit 70714e3

Browse files
committed
Auto merge of #123106 - maurer:cfi-closures, r=compiler-errors
CFI: Abstract Closures and Coroutines This will abstract coroutines in a moment, it's just abstracting closures for now to show `@rcvalle` This uses the same principal as the methods on traits - figure out the `dyn` type representing the fn trait, instantiate it, and attach that alias set. We're essentially just computing how we would be called in a dynamic context, and attaching that.
2 parents ef49365 + 8cc9a91 commit 70714e3

File tree

5 files changed

+240
-58
lines changed

5 files changed

+240
-58
lines changed

compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs

+94-35
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::{
@@ -641,9 +642,7 @@ fn encode_ty<'tcx>(
641642
}
642643

643644
// Function types
644-
ty::FnDef(def_id, args)
645-
| ty::Closure(def_id, args)
646-
| ty::CoroutineClosure(def_id, args) => {
645+
ty::FnDef(def_id, args) | ty::Closure(def_id, args) => {
647646
// u<length><name>[I<element-type1..element-typeN>E], where <element-type> is <subst>,
648647
// as vendor extended type.
649648
let mut s = String::new();
@@ -654,6 +653,18 @@ fn encode_ty<'tcx>(
654653
typeid.push_str(&s);
655654
}
656655

656+
ty::CoroutineClosure(def_id, args) => {
657+
// u<length><name>[I<element-type1..element-typeN>E], where <element-type> is <subst>,
658+
// as vendor extended type.
659+
let mut s = String::new();
660+
let name = encode_ty_name(tcx, *def_id);
661+
let _ = write!(s, "u{}{}", name.len(), &name);
662+
let parent_args = tcx.mk_args(args.as_coroutine_closure().parent_args());
663+
s.push_str(&encode_args(tcx, parent_args, dict, options));
664+
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
665+
typeid.push_str(&s);
666+
}
667+
657668
ty::Coroutine(def_id, args, ..) => {
658669
// u<length><name>[I<element-type1..element-typeN>E], where <element-type> is <subst>,
659670
// as vendor extended type.
@@ -1151,43 +1162,91 @@ pub fn typeid_for_instance<'tcx>(
11511162
};
11521163
let stripped_ty = strip_receiver_auto(tcx, upcast_ty);
11531164
instance.args = tcx.mk_args_trait(stripped_ty, instance.args.into_iter().skip(1));
1165+
} else if let ty::InstanceDef::VTableShim(def_id) = instance.def
1166+
&& let Some(trait_id) = tcx.trait_of_item(def_id)
1167+
{
1168+
// VTableShims may have a trait method, but a concrete Self. This is not suitable for a vtable,
1169+
// as the caller will not know the concrete Self.
1170+
let trait_ref = ty::TraitRef::new(tcx, trait_id, instance.args);
1171+
let invoke_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref));
1172+
instance.args = tcx.mk_args_trait(invoke_ty, trait_ref.args.into_iter().skip(1));
11541173
}
11551174

1156-
if !options.contains(EncodeTyOptions::NO_SELF_TYPE_ERASURE)
1157-
&& let Some(impl_id) = tcx.impl_of_method(instance.def_id())
1158-
&& let Some(trait_ref) = tcx.impl_trait_ref(impl_id)
1159-
{
1160-
let impl_method = tcx.associated_item(instance.def_id());
1161-
let method_id = impl_method
1162-
.trait_item_def_id
1163-
.expect("Part of a trait implementation, but not linked to the def_id?");
1164-
let trait_method = tcx.associated_item(method_id);
1165-
let trait_id = trait_ref.skip_binder().def_id;
1166-
if traits::is_vtable_safe_method(tcx, trait_id, trait_method)
1167-
&& tcx.object_safety_violations(trait_id).is_empty()
1175+
if !options.contains(EncodeTyOptions::NO_SELF_TYPE_ERASURE) {
1176+
if let Some(impl_id) = tcx.impl_of_method(instance.def_id())
1177+
&& let Some(trait_ref) = tcx.impl_trait_ref(impl_id)
11681178
{
1169-
// Trait methods will have a Self polymorphic parameter, where the concreteized
1170-
// implementatation will not. We need to walk back to the more general trait method
1171-
let trait_ref = tcx.instantiate_and_normalize_erasing_regions(
1172-
instance.args,
1173-
ty::ParamEnv::reveal_all(),
1174-
trait_ref,
1175-
);
1179+
let impl_method = tcx.associated_item(instance.def_id());
1180+
let method_id = impl_method
1181+
.trait_item_def_id
1182+
.expect("Part of a trait implementation, but not linked to the def_id?");
1183+
let trait_method = tcx.associated_item(method_id);
1184+
let trait_id = trait_ref.skip_binder().def_id;
1185+
if traits::is_vtable_safe_method(tcx, trait_id, trait_method)
1186+
&& tcx.object_safety_violations(trait_id).is_empty()
1187+
{
1188+
// Trait methods will have a Self polymorphic parameter, where the concreteized
1189+
// implementatation will not. We need to walk back to the more general trait method
1190+
let trait_ref = tcx.instantiate_and_normalize_erasing_regions(
1191+
instance.args,
1192+
ty::ParamEnv::reveal_all(),
1193+
trait_ref,
1194+
);
1195+
let invoke_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref));
1196+
1197+
// At the call site, any call to this concrete function through a vtable will be
1198+
// `Virtual(method_id, idx)` with appropriate arguments for the method. Since we have the
1199+
// original method id, and we've recovered the trait arguments, we can make the callee
1200+
// instance we're computing the alias set for match the caller instance.
1201+
//
1202+
// Right now, our code ignores the vtable index everywhere, so we use 0 as a placeholder.
1203+
// If we ever *do* start encoding the vtable index, we will need to generate an alias set
1204+
// based on which vtables we are putting this method into, as there will be more than one
1205+
// index value when supertraits are involved.
1206+
instance.def = ty::InstanceDef::Virtual(method_id, 0);
1207+
let abstract_trait_args =
1208+
tcx.mk_args_trait(invoke_ty, trait_ref.args.into_iter().skip(1));
1209+
instance.args = instance.args.rebase_onto(tcx, impl_id, abstract_trait_args);
1210+
}
1211+
} else if tcx.is_closure_like(instance.def_id()) {
1212+
// We're either a closure or a coroutine. Our goal is to find the trait we're defined on,
1213+
// instantiate it, and take the type of its only method as our own.
1214+
let closure_ty = instance.ty(tcx, ty::ParamEnv::reveal_all());
1215+
let (trait_id, inputs) = match closure_ty.kind() {
1216+
ty::Closure(..) => {
1217+
let closure_args = instance.args.as_closure();
1218+
let trait_id = tcx.fn_trait_kind_to_def_id(closure_args.kind()).unwrap();
1219+
let tuple_args =
1220+
tcx.instantiate_bound_regions_with_erased(closure_args.sig()).inputs()[0];
1221+
(trait_id, tuple_args)
1222+
}
1223+
ty::Coroutine(..) => (
1224+
tcx.require_lang_item(LangItem::Coroutine, None),
1225+
instance.args.as_coroutine().resume_ty(),
1226+
),
1227+
ty::CoroutineClosure(..) => (
1228+
tcx.require_lang_item(LangItem::FnOnce, None),
1229+
tcx.instantiate_bound_regions_with_erased(
1230+
instance.args.as_coroutine_closure().coroutine_closure_sig(),
1231+
)
1232+
.tupled_inputs_ty,
1233+
),
1234+
x => bug!("Unexpected type kind for closure-like: {x:?}"),
1235+
};
1236+
let trait_ref = ty::TraitRef::new(tcx, trait_id, [closure_ty, inputs]);
11761237
let invoke_ty = trait_object_ty(tcx, ty::Binder::dummy(trait_ref));
1238+
let abstract_args = tcx.mk_args_trait(invoke_ty, trait_ref.args.into_iter().skip(1));
1239+
// There should be exactly one method on this trait, and it should be the one we're
1240+
// defining.
1241+
let call = tcx
1242+
.associated_items(trait_id)
1243+
.in_definition_order()
1244+
.find(|it| it.kind == ty::AssocKind::Fn)
1245+
.expect("No call-family function on closure-like Fn trait?")
1246+
.def_id;
11771247

1178-
// At the call site, any call to this concrete function through a vtable will be
1179-
// `Virtual(method_id, idx)` with appropriate arguments for the method. Since we have the
1180-
// original method id, and we've recovered the trait arguments, we can make the callee
1181-
// instance we're computing the alias set for match the caller instance.
1182-
//
1183-
// Right now, our code ignores the vtable index everywhere, so we use 0 as a placeholder.
1184-
// If we ever *do* start encoding the vtable index, we will need to generate an alias set
1185-
// based on which vtables we are putting this method into, as there will be more than one
1186-
// index value when supertraits are involved.
1187-
instance.def = ty::InstanceDef::Virtual(method_id, 0);
1188-
let abstract_trait_args =
1189-
tcx.mk_args_trait(invoke_ty, trait_ref.args.into_iter().skip(1));
1190-
instance.args = instance.args.rebase_onto(tcx, impl_id, abstract_trait_args);
1248+
instance.def = ty::InstanceDef::Virtual(call, 0);
1249+
instance.args = abstract_args;
11911250
}
11921251
}
11931252

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Check various forms of dynamic closure calls
2+
3+
//@ edition: 2021
4+
//@ revisions: cfi kcfi
5+
// FIXME(#122848) Remove only-linux once OSX CFI binaries work
6+
//@ only-linux
7+
//@ [cfi] needs-sanitizer-cfi
8+
//@ [kcfi] needs-sanitizer-kcfi
9+
//@ compile-flags: -C target-feature=-crt-static
10+
//@ [cfi] compile-flags: -C codegen-units=1 -C lto -C prefer-dynamic=off -C opt-level=0
11+
//@ [cfi] compile-flags: -Z sanitizer=cfi
12+
//@ [kcfi] compile-flags: -Z sanitizer=kcfi
13+
//@ [kcfi] compile-flags: -C panic=abort -Z panic-abort-tests -C prefer-dynamic=off
14+
//@ run-pass
15+
16+
#![feature(async_closure)]
17+
#![feature(async_fn_traits)]
18+
19+
use std::ops::AsyncFn;
20+
21+
#[inline(never)]
22+
fn identity<T>(x: T) -> T { x }
23+
24+
// We can't actually create a `dyn AsyncFn()`, because it's not object-safe, but we should check
25+
// that we don't bug out when we encounter one.
26+
27+
fn main() {
28+
let f = identity(async || ());
29+
let _ = f.async_call(());
30+
let _ = f();
31+
let g: Box<dyn FnOnce() -> _> = Box::new(f) as _;
32+
let _ = g();
33+
}

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

-23
This file was deleted.

tests/ui/sanitizer/cfi-closures.rs

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
//@ [kcfi] compile-flags: -C panic=abort -Z panic-abort-tests -C prefer-dynamic=off
13+
//@ compile-flags: --test
14+
//@ run-pass
15+
16+
#![feature(fn_traits)]
17+
#![feature(unboxed_closures)]
18+
#![feature(cfg_sanitize)]
19+
20+
fn foo<'a, T>() -> Box<dyn Fn(&'a T) -> &'a T> {
21+
Box::new(|x| x)
22+
}
23+
24+
#[test]
25+
fn dyn_fn_with_params() {
26+
let x = 3;
27+
let f = foo();
28+
f(&x);
29+
// FIXME remove once drops are working.
30+
std::mem::forget(f);
31+
}
32+
33+
#[test]
34+
fn call_fn_trait() {
35+
let f: &(dyn Fn()) = &(|| {}) as _;
36+
f.call(());
37+
}
38+
39+
#[test]
40+
fn fn_ptr_cast() {
41+
let f: &fn() = &((|| ()) as _);
42+
f();
43+
}
44+
45+
fn use_fnmut<F: FnMut()>(mut f: F) {
46+
f()
47+
}
48+
49+
#[test]
50+
fn fn_to_fnmut() {
51+
let f: &(dyn Fn()) = &(|| {}) as _;
52+
use_fnmut(f);
53+
}
54+
55+
fn hrtb_helper(f: &dyn for<'a> Fn(&'a usize)) {
56+
f(&10)
57+
}
58+
59+
#[test]
60+
fn hrtb_fn() {
61+
hrtb_helper((&|x: &usize| println!("{}", *x)) as _)
62+
}
63+
64+
#[test]
65+
fn fnonce() {
66+
let f: Box<dyn FnOnce()> = Box::new(|| {}) as _;
67+
f();
68+
}
69+
70+
fn use_closure<C>(call: extern "rust-call" fn(&C, ()) -> i32, f: &C) -> i32 {
71+
call(f, ())
72+
}
73+
74+
#[test]
75+
// FIXME after KCFI reify support is added, remove this
76+
// It will appear to work if you test locally, set -C opt-level=0 to see it fail.
77+
#[cfg_attr(sanitize = "kcfi", ignore)]
78+
fn closure_addr_taken() {
79+
let x = 3i32;
80+
let f = || x;
81+
let call = Fn::<()>::call;
82+
use_closure(call, &f);
83+
}

tests/ui/sanitizer/cfi-coroutine.rs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Verifies that we can call dynamic coroutines
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+
//@ [kcfi] compile-flags: -C panic=abort -Z panic-abort-tests -C prefer-dynamic=off
13+
//@ compile-flags: --test
14+
//@ run-pass
15+
16+
#![feature(coroutines)]
17+
#![feature(coroutine_trait)]
18+
19+
use std::ops::{Coroutine, CoroutineState};
20+
use std::pin::{pin, Pin};
21+
22+
fn main() {
23+
let mut coro = |x: i32| {
24+
yield x;
25+
"done"
26+
};
27+
let mut abstract_coro: Pin<&mut dyn Coroutine<i32,Yield=i32,Return=&'static str>> = pin!(coro);
28+
assert_eq!(abstract_coro.as_mut().resume(2), CoroutineState::Yielded(2));
29+
assert_eq!(abstract_coro.as_mut().resume(0), CoroutineState::Complete("done"));
30+
}

0 commit comments

Comments
 (0)