Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 70714e3

Browse files
committed
Auto merge of rust-lang#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‎

Lines changed: 94 additions & 35 deletions
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

Lines changed: 33 additions & 0 deletions
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‎

Lines changed: 0 additions & 23 deletions
This file was deleted.

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

Lines changed: 83 additions & 0 deletions
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‎

Lines changed: 30 additions & 0 deletions
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)

AltStyle によって変換されたページ (->オリジナル) /