Skip to content

Commit a1fa46e

Browse files
committed
proc_macro: don't pass a client-side function pointer through the server.
1 parent eaf5b65 commit a1fa46e

File tree

4 files changed

+163
-90
lines changed

4 files changed

+163
-90
lines changed

proc_macro/src/bridge/client.rs

+46-35
Original file line numberDiff line numberDiff line change
@@ -357,22 +357,33 @@ impl Bridge<'_> {
357357
}
358358
}
359359

360-
/// A client-side "global object" (usually a function pointer),
361-
/// which may be using a different `proc_macro` from the one
362-
/// used by the server, but can be interacted with compatibly.
360+
/// A client-side RPC entry-point, which may be using a different `proc_macro`
361+
/// from the one used by the server, but can be invoked compatibly.
363362
///
364-
/// N.B., `F` must have FFI-friendly memory layout (e.g., a pointer).
365-
/// The call ABI of function pointers used for `F` doesn't
366-
/// need to match between server and client, since it's only
367-
/// passed between them and (eventually) called by the client.
363+
/// Note that the (phantom) `I` ("input") and `O` ("output") type parameters
364+
/// decorate the `Client<I, O>` with the RPC "interface" of the entry-point, but
365+
/// do not themselves participate in ABI, at all, only facilitate type-checking.
366+
///
367+
/// E.g. `Client<TokenStream, TokenStream>` is the common proc macro interface,
368+
/// used for `#[proc_macro] fn foo(input: TokenStream) -> TokenStream`,
369+
/// indicating that the RPC input and output will be serialized token streams,
370+
/// and forcing the use of APIs that take/return `S::TokenStream`, server-side.
368371
#[repr(C)]
369-
#[derive(Copy, Clone)]
370-
pub struct Client<F> {
372+
pub struct Client<I, O> {
371373
// FIXME(eddyb) use a reference to the `static COUNTERS`, instead of
372374
// a wrapper `fn` pointer, once `const fn` can reference `static`s.
373375
pub(super) get_handle_counters: extern "C" fn() -> &'static HandleCounters,
374-
pub(super) run: extern "C" fn(Bridge<'_>, F) -> Buffer,
375-
pub(super) f: F,
376+
377+
pub(super) run: extern "C" fn(Bridge<'_>) -> Buffer,
378+
379+
pub(super) _marker: PhantomData<fn(I) -> O>,
380+
}
381+
382+
impl<I, O> Copy for Client<I, O> {}
383+
impl<I, O> Clone for Client<I, O> {
384+
fn clone(&self) -> Self {
385+
*self
386+
}
376387
}
377388

378389
/// Client-side helper for handling client panics, entering the bridge,
@@ -419,31 +430,31 @@ fn run_client<A: for<'a, 's> DecodeMut<'a, 's, ()>, R: Encode<()>>(
419430
buf
420431
}
421432

422-
impl Client<fn(crate::TokenStream) -> crate::TokenStream> {
423-
pub const fn expand1(f: fn(crate::TokenStream) -> crate::TokenStream) -> Self {
424-
extern "C" fn run(
425-
bridge: Bridge<'_>,
426-
f: impl FnOnce(crate::TokenStream) -> crate::TokenStream,
427-
) -> Buffer {
428-
run_client(bridge, |input| f(crate::TokenStream(input)).0)
433+
impl Client<crate::TokenStream, crate::TokenStream> {
434+
pub const fn expand1(f: impl Fn(crate::TokenStream) -> crate::TokenStream + Copy) -> Self {
435+
Client {
436+
get_handle_counters: HandleCounters::get,
437+
run: super::selfless_reify::reify_to_extern_c_fn_hrt_bridge(move |bridge| {
438+
run_client(bridge, |input| f(crate::TokenStream(input)).0)
439+
}),
440+
_marker: PhantomData,
429441
}
430-
Client { get_handle_counters: HandleCounters::get, run, f }
431442
}
432443
}
433444

434-
impl Client<fn(crate::TokenStream, crate::TokenStream) -> crate::TokenStream> {
445+
impl Client<(crate::TokenStream, crate::TokenStream), crate::TokenStream> {
435446
pub const fn expand2(
436-
f: fn(crate::TokenStream, crate::TokenStream) -> crate::TokenStream,
447+
f: impl Fn(crate::TokenStream, crate::TokenStream) -> crate::TokenStream + Copy,
437448
) -> Self {
438-
extern "C" fn run(
439-
bridge: Bridge<'_>,
440-
f: impl FnOnce(crate::TokenStream, crate::TokenStream) -> crate::TokenStream,
441-
) -> Buffer {
442-
run_client(bridge, |(input, input2)| {
443-
f(crate::TokenStream(input), crate::TokenStream(input2)).0
444-
})
449+
Client {
450+
get_handle_counters: HandleCounters::get,
451+
run: super::selfless_reify::reify_to_extern_c_fn_hrt_bridge(move |bridge| {
452+
run_client(bridge, |(input, input2)| {
453+
f(crate::TokenStream(input), crate::TokenStream(input2)).0
454+
})
455+
}),
456+
_marker: PhantomData,
445457
}
446-
Client { get_handle_counters: HandleCounters::get, run, f }
447458
}
448459
}
449460

@@ -453,17 +464,17 @@ pub enum ProcMacro {
453464
CustomDerive {
454465
trait_name: &'static str,
455466
attributes: &'static [&'static str],
456-
client: Client<fn(crate::TokenStream) -> crate::TokenStream>,
467+
client: Client<crate::TokenStream, crate::TokenStream>,
457468
},
458469

459470
Attr {
460471
name: &'static str,
461-
client: Client<fn(crate::TokenStream, crate::TokenStream) -> crate::TokenStream>,
472+
client: Client<(crate::TokenStream, crate::TokenStream), crate::TokenStream>,
462473
},
463474

464475
Bang {
465476
name: &'static str,
466-
client: Client<fn(crate::TokenStream) -> crate::TokenStream>,
477+
client: Client<crate::TokenStream, crate::TokenStream>,
467478
},
468479
}
469480

@@ -479,21 +490,21 @@ impl ProcMacro {
479490
pub const fn custom_derive(
480491
trait_name: &'static str,
481492
attributes: &'static [&'static str],
482-
expand: fn(crate::TokenStream) -> crate::TokenStream,
493+
expand: impl Fn(crate::TokenStream) -> crate::TokenStream + Copy,
483494
) -> Self {
484495
ProcMacro::CustomDerive { trait_name, attributes, client: Client::expand1(expand) }
485496
}
486497

487498
pub const fn attr(
488499
name: &'static str,
489-
expand: fn(crate::TokenStream, crate::TokenStream) -> crate::TokenStream,
500+
expand: impl Fn(crate::TokenStream, crate::TokenStream) -> crate::TokenStream + Copy,
490501
) -> Self {
491502
ProcMacro::Attr { name, client: Client::expand2(expand) }
492503
}
493504

494505
pub const fn bang(
495506
name: &'static str,
496-
expand: fn(crate::TokenStream) -> crate::TokenStream,
507+
expand: impl Fn(crate::TokenStream) -> crate::TokenStream + Copy,
497508
) -> Self {
498509
ProcMacro::Bang { name, client: Client::expand1(expand) }
499510
}

proc_macro/src/bridge/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ mod handle;
208208
mod rpc;
209209
#[allow(unsafe_code)]
210210
mod scoped_cell;
211+
#[allow(unsafe_code)]
212+
mod selfless_reify;
211213
#[forbid(unsafe_code)]
212214
pub mod server;
213215

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//! Abstraction for creating `fn` pointers from any callable that *effectively*
2+
//! has the equivalent of implementing `Default`, even if the compiler neither
3+
//! provides `Default` nor allows reifying closures (i.e. creating `fn` pointers)
4+
//! other than those with absolutely no captures.
5+
//!
6+
//! More specifically, for a closure-like type to be "effectively `Default`":
7+
//! * it must be a ZST (zero-sized type): no information contained within, so
8+
//! that `Default`'s return value (if it were implemented) is unambiguous
9+
//! * it must be `Copy`: no captured "unique ZST tokens" or any other similar
10+
//! types that would make duplicating values at will unsound
11+
//! * combined with the ZST requirement, this confers a kind of "telecopy"
12+
//! ability: similar to `Copy`, but without keeping the value around, and
13+
//! instead "reconstructing" it (a noop given it's a ZST) when needed
14+
//! * it must be *provably* inhabited: no captured uninhabited types or any
15+
//! other types that cannot be constructed by the user of this abstraction
16+
//! * the proof is a value of the closure-like type itself, in a sense the
17+
//! "seed" for the "telecopy" process made possible by ZST + `Copy`
18+
//! * this requirement is the only reason an abstraction limited to a specific
19+
//! usecase is required: ZST + `Copy` can be checked with *at worst* a panic
20+
//! at the "attempted `::default()` call" time, but that doesn't guarantee
21+
//! that the value can be soundly created, and attempting to use the typical
22+
//! "proof ZST token" approach leads yet again to having a ZST + `Copy` type
23+
//! that is not proof of anything without a value (i.e. isomorphic to a
24+
//! newtype of the type it's trying to prove the inhabitation of)
25+
//!
26+
//! A more flexible (and safer) solution to the general problem could exist once
27+
//! `const`-generic parameters can have type parameters in their types:
28+
//!
29+
//! ```rust,ignore (needs future const-generics)
30+
//! extern "C" fn ffi_wrapper<
31+
//! A, R,
32+
//! F: Fn(A) -> R,
33+
//! const f: F, // <-- this `const`-generic is not yet allowed
34+
//! >(arg: A) -> R {
35+
//! f(arg)
36+
//! }
37+
//! ```
38+
39+
use std::mem;
40+
41+
// FIXME(eddyb) this could be `trait` impls except for the `const fn` requirement.
42+
macro_rules! define_reify_functions {
43+
($(
44+
fn $name:ident $(<$($param:ident),*>)?
45+
for $(extern $abi:tt)? fn($($arg:ident: $arg_ty:ty),*) -> $ret_ty:ty;
46+
)+) => {
47+
$(pub const fn $name<
48+
$($($param,)*)?
49+
F: Fn($($arg_ty),*) -> $ret_ty + Copy
50+
>(f: F) -> $(extern $abi)? fn($($arg_ty),*) -> $ret_ty {
51+
// FIXME(eddyb) describe the `F` type (e.g. via `type_name::<F>`) once panic
52+
// formatting becomes possible in `const fn`.
53+
assert!(mem::size_of::<F>() == 0, "selfless_reify: closure must be zero-sized");
54+
55+
$(extern $abi)? fn wrapper<
56+
$($($param,)*)?
57+
F: Fn($($arg_ty),*) -> $ret_ty + Copy
58+
>($($arg: $arg_ty),*) -> $ret_ty {
59+
let f = unsafe {
60+
// SAFETY: `F` satisfies all criteria for "out of thin air"
61+
// reconstructability (see module-level doc comment).
62+
mem::MaybeUninit::<F>::uninit().assume_init()
63+
};
64+
f($($arg),*)
65+
}
66+
let _f_proof = f;
67+
wrapper::<
68+
$($($param,)*)?
69+
F
70+
>
71+
})+
72+
}
73+
}
74+
75+
define_reify_functions! {
76+
fn _reify_to_extern_c_fn_unary<A, R> for extern "C" fn(arg: A) -> R;
77+
78+
// HACK(eddyb) this abstraction is used with `for<'a> fn(Bridge<'a>) -> T`
79+
// but that doesn't work with just `reify_to_extern_c_fn_unary` because of
80+
// the `fn` pointer type being "higher-ranked" (i.e. the `for<'a>` binder).
81+
// FIXME(eddyb) try to remove the lifetime from `Bridge`, that'd help.
82+
fn reify_to_extern_c_fn_hrt_bridge<R> for extern "C" fn(bridge: super::Bridge<'_>) -> R;
83+
}

0 commit comments

Comments
 (0)