Skip to content

proc_macro: don't pass a client-side function pointer through the server. #97461

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 39 additions & 32 deletions compiler/rustc_builtin_macros/src/proc_macro_harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,50 +294,57 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P<ast::Item> {
// that we generate expressions. The position of each NodeId
// in the 'proc_macros' Vec corresponds to its position
// in the static array that will be generated
let decls = {
let local_path = |cx: &ExtCtxt<'_>, sp: Span, name| {
cx.expr_path(cx.path(sp.with_ctxt(span.ctxt()), vec![name]))
};
let proc_macro_ty_method_path = |cx: &ExtCtxt<'_>, method| {
cx.expr_path(cx.path(span, vec![proc_macro, bridge, client, proc_macro_ty, method]))
};
let attr_or_bang = |cx: &mut ExtCtxt<'_>, ca: &ProcMacroDef, ident| {
cx.resolver.declare_proc_macro(ca.id);
cx.expr_call(
span,
proc_macro_ty_method_path(cx, ident),
vec![
cx.expr_str(ca.span, ca.function_name.name),
local_path(cx, ca.span, ca.function_name),
],
)
};
macros
.iter()
.map(|m| match m {
let decls = macros
.iter()
.map(|m| {
let harness_span = span;
let span = match m {
ProcMacro::Derive(m) => m.span,
ProcMacro::Attr(m) | ProcMacro::Bang(m) => m.span,
};
let local_path = |cx: &ExtCtxt<'_>, name| cx.expr_path(cx.path(span, vec![name]));
let proc_macro_ty_method_path = |cx: &ExtCtxt<'_>, method| {
cx.expr_path(cx.path(
span.with_ctxt(harness_span.ctxt()),
vec![proc_macro, bridge, client, proc_macro_ty, method],
))
};
match m {
ProcMacro::Derive(cd) => {
cx.resolver.declare_proc_macro(cd.id);
cx.expr_call(
span,
proc_macro_ty_method_path(cx, custom_derive),
vec![
cx.expr_str(cd.span, cd.trait_name),
cx.expr_str(span, cd.trait_name),
cx.expr_vec_slice(
span,
cd.attrs
.iter()
.map(|&s| cx.expr_str(cd.span, s))
.collect::<Vec<_>>(),
cd.attrs.iter().map(|&s| cx.expr_str(span, s)).collect::<Vec<_>>(),
),
local_path(cx, cd.span, cd.function_name),
local_path(cx, cd.function_name),
],
)
}
ProcMacro::Attr(ca) => attr_or_bang(cx, &ca, attr),
ProcMacro::Bang(ca) => attr_or_bang(cx, &ca, bang),
})
.collect()
};
ProcMacro::Attr(ca) | ProcMacro::Bang(ca) => {
cx.resolver.declare_proc_macro(ca.id);
let ident = match m {
ProcMacro::Attr(_) => attr,
ProcMacro::Bang(_) => bang,
ProcMacro::Derive(_) => unreachable!(),
};

cx.expr_call(
span,
proc_macro_ty_method_path(cx, ident),
vec![
cx.expr_str(span, ca.function_name.name),
local_path(cx, ca.function_name),
],
)
}
}
})
.collect();

let decls_static = cx
.item_static(
Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_expand/src/proc_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use rustc_span::{Span, DUMMY_SP};
const EXEC_STRATEGY: pm::bridge::server::SameThread = pm::bridge::server::SameThread;

pub struct BangProcMacro {
pub client: pm::bridge::client::Client<fn(pm::TokenStream) -> pm::TokenStream>,
pub client: pm::bridge::client::Client<pm::TokenStream, pm::TokenStream>,
}

impl base::BangProcMacro for BangProcMacro {
Expand Down Expand Up @@ -42,7 +42,7 @@ impl base::BangProcMacro for BangProcMacro {
}

pub struct AttrProcMacro {
pub client: pm::bridge::client::Client<fn(pm::TokenStream, pm::TokenStream) -> pm::TokenStream>,
pub client: pm::bridge::client::Client<(pm::TokenStream, pm::TokenStream), pm::TokenStream>,
}

impl base::AttrProcMacro for AttrProcMacro {
Expand Down Expand Up @@ -73,7 +73,7 @@ impl base::AttrProcMacro for AttrProcMacro {
}

pub struct DeriveProcMacro {
pub client: pm::bridge::client::Client<fn(pm::TokenStream) -> pm::TokenStream>,
pub client: pm::bridge::client::Client<pm::TokenStream, pm::TokenStream>,
}

impl MultiItemModifier for DeriveProcMacro {
Expand Down
81 changes: 46 additions & 35 deletions library/proc_macro/src/bridge/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,22 +357,33 @@ impl Bridge<'_> {
}
}

/// A client-side "global object" (usually a function pointer),
/// which may be using a different `proc_macro` from the one
/// used by the server, but can be interacted with compatibly.
/// A client-side RPC entry-point, which may be using a different `proc_macro`
/// from the one used by the server, but can be invoked compatibly.
///
/// N.B., `F` must have FFI-friendly memory layout (e.g., a pointer).
/// The call ABI of function pointers used for `F` doesn't
/// need to match between server and client, since it's only
/// passed between them and (eventually) called by the client.
/// Note that the (phantom) `I` ("input") and `O` ("output") type parameters
/// decorate the `Client<I, O>` with the RPC "interface" of the entry-point, but
/// do not themselves participate in ABI, at all, only facilitate type-checking.
///
/// E.g. `Client<TokenStream, TokenStream>` is the common proc macro interface,
/// used for `#[proc_macro] fn foo(input: TokenStream) -> TokenStream`,
/// indicating that the RPC input and output will be serialized token streams,
/// and forcing the use of APIs that take/return `S::TokenStream`, server-side.
#[repr(C)]
#[derive(Copy, Clone)]
pub struct Client<F> {
pub struct Client<I, O> {
// FIXME(eddyb) use a reference to the `static COUNTERS`, instead of
// a wrapper `fn` pointer, once `const fn` can reference `static`s.
pub(super) get_handle_counters: extern "C" fn() -> &'static HandleCounters,
pub(super) run: extern "C" fn(Bridge<'_>, F) -> Buffer,
pub(super) f: F,

pub(super) run: extern "C" fn(Bridge<'_>) -> Buffer,

pub(super) _marker: PhantomData<fn(I) -> O>,
}

impl<I, O> Copy for Client<I, O> {}
impl<I, O> Clone for Client<I, O> {
fn clone(&self) -> Self {
*self
}
}

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

impl Client<fn(crate::TokenStream) -> crate::TokenStream> {
pub const fn expand1(f: fn(crate::TokenStream) -> crate::TokenStream) -> Self {
extern "C" fn run(
bridge: Bridge<'_>,
f: impl FnOnce(crate::TokenStream) -> crate::TokenStream,
) -> Buffer {
run_client(bridge, |input| f(crate::TokenStream(input)).0)
impl Client<crate::TokenStream, crate::TokenStream> {
pub const fn expand1(f: impl Fn(crate::TokenStream) -> crate::TokenStream + Copy) -> Self {
Client {
get_handle_counters: HandleCounters::get,
run: super::selfless_reify::reify_to_extern_c_fn_hrt_bridge(move |bridge| {
run_client(bridge, |input| f(crate::TokenStream(input)).0)
}),
_marker: PhantomData,
}
Client { get_handle_counters: HandleCounters::get, run, f }
}
}

impl Client<fn(crate::TokenStream, crate::TokenStream) -> crate::TokenStream> {
impl Client<(crate::TokenStream, crate::TokenStream), crate::TokenStream> {
pub const fn expand2(
f: fn(crate::TokenStream, crate::TokenStream) -> crate::TokenStream,
f: impl Fn(crate::TokenStream, crate::TokenStream) -> crate::TokenStream + Copy,
) -> Self {
extern "C" fn run(
bridge: Bridge<'_>,
f: impl FnOnce(crate::TokenStream, crate::TokenStream) -> crate::TokenStream,
) -> Buffer {
run_client(bridge, |(input, input2)| {
f(crate::TokenStream(input), crate::TokenStream(input2)).0
})
Client {
get_handle_counters: HandleCounters::get,
run: super::selfless_reify::reify_to_extern_c_fn_hrt_bridge(move |bridge| {
run_client(bridge, |(input, input2)| {
f(crate::TokenStream(input), crate::TokenStream(input2)).0
})
}),
_marker: PhantomData,
}
Client { get_handle_counters: HandleCounters::get, run, f }
}
}

Expand All @@ -453,17 +464,17 @@ pub enum ProcMacro {
CustomDerive {
trait_name: &'static str,
attributes: &'static [&'static str],
client: Client<fn(crate::TokenStream) -> crate::TokenStream>,
client: Client<crate::TokenStream, crate::TokenStream>,
},

Attr {
name: &'static str,
client: Client<fn(crate::TokenStream, crate::TokenStream) -> crate::TokenStream>,
client: Client<(crate::TokenStream, crate::TokenStream), crate::TokenStream>,
},

Bang {
name: &'static str,
client: Client<fn(crate::TokenStream) -> crate::TokenStream>,
client: Client<crate::TokenStream, crate::TokenStream>,
},
}

Expand All @@ -479,21 +490,21 @@ impl ProcMacro {
pub const fn custom_derive(
trait_name: &'static str,
attributes: &'static [&'static str],
expand: fn(crate::TokenStream) -> crate::TokenStream,
expand: impl Fn(crate::TokenStream) -> crate::TokenStream + Copy,
) -> Self {
ProcMacro::CustomDerive { trait_name, attributes, client: Client::expand1(expand) }
}

pub const fn attr(
name: &'static str,
expand: fn(crate::TokenStream, crate::TokenStream) -> crate::TokenStream,
expand: impl Fn(crate::TokenStream, crate::TokenStream) -> crate::TokenStream + Copy,
) -> Self {
ProcMacro::Attr { name, client: Client::expand2(expand) }
}

pub const fn bang(
name: &'static str,
expand: fn(crate::TokenStream) -> crate::TokenStream,
expand: impl Fn(crate::TokenStream) -> crate::TokenStream + Copy,
) -> Self {
ProcMacro::Bang { name, client: Client::expand1(expand) }
}
Expand Down
2 changes: 2 additions & 0 deletions library/proc_macro/src/bridge/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ mod handle;
mod rpc;
#[allow(unsafe_code)]
mod scoped_cell;
#[allow(unsafe_code)]
mod selfless_reify;
#[forbid(unsafe_code)]
pub mod server;

Expand Down
83 changes: 83 additions & 0 deletions library/proc_macro/src/bridge/selfless_reify.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//! Abstraction for creating `fn` pointers from any callable that *effectively*
//! has the equivalent of implementing `Default`, even if the compiler neither
//! provides `Default` nor allows reifying closures (i.e. creating `fn` pointers)
//! other than those with absolutely no captures.
//!
//! More specifically, for a closure-like type to be "effectively `Default`":
//! * it must be a ZST (zero-sized type): no information contained within, so
//! that `Default`'s return value (if it were implemented) is unambiguous
//! * it must be `Copy`: no captured "unique ZST tokens" or any other similar
//! types that would make duplicating values at will unsound
//! * combined with the ZST requirement, this confers a kind of "telecopy"
//! ability: similar to `Copy`, but without keeping the value around, and
//! instead "reconstructing" it (a noop given it's a ZST) when needed
//! * it must be *provably* inhabited: no captured uninhabited types or any
//! other types that cannot be constructed by the user of this abstraction
//! * the proof is a value of the closure-like type itself, in a sense the
//! "seed" for the "telecopy" process made possible by ZST + `Copy`
//! * this requirement is the only reason an abstraction limited to a specific
//! usecase is required: ZST + `Copy` can be checked with *at worst* a panic
//! at the "attempted `::default()` call" time, but that doesn't guarantee
//! that the value can be soundly created, and attempting to use the typical
//! "proof ZST token" approach leads yet again to having a ZST + `Copy` type
//! that is not proof of anything without a value (i.e. isomorphic to a
//! newtype of the type it's trying to prove the inhabitation of)
//!
//! A more flexible (and safer) solution to the general problem could exist once
//! `const`-generic parameters can have type parameters in their types:
//!
//! ```rust,ignore (needs future const-generics)
//! extern "C" fn ffi_wrapper<
//! A, R,
//! F: Fn(A) -> R,
//! const f: F, // <-- this `const`-generic is not yet allowed
//! >(arg: A) -> R {
//! f(arg)
//! }
//! ```

use std::mem;

// FIXME(eddyb) this could be `trait` impls except for the `const fn` requirement.
macro_rules! define_reify_functions {
($(
fn $name:ident $(<$($param:ident),*>)?
for $(extern $abi:tt)? fn($($arg:ident: $arg_ty:ty),*) -> $ret_ty:ty;
)+) => {
$(pub const fn $name<
$($($param,)*)?
F: Fn($($arg_ty),*) -> $ret_ty + Copy
>(f: F) -> $(extern $abi)? fn($($arg_ty),*) -> $ret_ty {
// FIXME(eddyb) describe the `F` type (e.g. via `type_name::<F>`) once panic
// formatting becomes possible in `const fn`.
assert!(mem::size_of::<F>() == 0, "selfless_reify: closure must be zero-sized");

$(extern $abi)? fn wrapper<
$($($param,)*)?
F: Fn($($arg_ty),*) -> $ret_ty + Copy
>($($arg: $arg_ty),*) -> $ret_ty {
let f = unsafe {
// SAFETY: `F` satisfies all criteria for "out of thin air"
// reconstructability (see module-level doc comment).
mem::MaybeUninit::<F>::uninit().assume_init()
};
f($($arg),*)
}
let _f_proof = f;
wrapper::<
$($($param,)*)?
F
>
})+
}
}

define_reify_functions! {
fn _reify_to_extern_c_fn_unary<A, R> for extern "C" fn(arg: A) -> R;

// HACK(eddyb) this abstraction is used with `for<'a> fn(Bridge<'a>) -> T`
// but that doesn't work with just `reify_to_extern_c_fn_unary` because of
// the `fn` pointer type being "higher-ranked" (i.e. the `for<'a>` binder).
// FIXME(eddyb) try to remove the lifetime from `Bridge`, that'd help.
fn reify_to_extern_c_fn_hrt_bridge<R> for extern "C" fn(bridge: super::Bridge<'_>) -> R;
}
Loading