diff --git a/library/proc_macro/src/bridge/server.rs b/library/proc_macro/src/bridge/server.rs index e47a77f6c13..8202c40d631 100644 --- a/library/proc_macro/src/bridge/server.rs +++ b/library/proc_macro/src/bridge/server.rs @@ -2,6 +2,7 @@ use super::*; +use std::cell::Cell; use std::marker::PhantomData; // FIXME(eddyb) generate the definition of `HandleStore` in `server.rs`. @@ -143,6 +144,38 @@ pub trait ExecutionStrategy { ) -> Buffer; } +thread_local! { + /// While running a proc-macro with the same-thread executor, this flag will + /// be set, forcing nested proc-macro invocations (e.g. due to + /// `TokenStream::expand_expr`) to be run using a cross-thread executor. + /// + /// This is required as the thread-local state in the proc_macro client does + /// not handle being re-entered, and will invalidate all `Symbol`s when + /// entering a nested macro. + static ALREADY_RUNNING_SAME_THREAD: Cell = Cell::new(false); +} + +/// Keep `ALREADY_RUNNING_SAME_THREAD` (see also its documentation) +/// set to `true`, preventing same-thread reentrance. +struct RunningSameThreadGuard(()); + +impl RunningSameThreadGuard { + fn new() -> Self { + let already_running = ALREADY_RUNNING_SAME_THREAD.replace(true); + assert!( + !already_running, + "same-thread nesting (\"reentrance\") of proc macro executions is not supported" + ); + RunningSameThreadGuard(()) + } +} + +impl Drop for RunningSameThreadGuard { + fn drop(&mut self) { + ALREADY_RUNNING_SAME_THREAD.set(false); + } +} + pub struct MaybeCrossThread

{ cross_thread: bool, marker: PhantomData

, @@ -165,7 +198,7 @@ where run_client: extern "C" fn(BridgeConfig<'_>) -> Buffer, force_show_panics: bool, ) -> Buffer { - if self.cross_thread { + if self.cross_thread || ALREADY_RUNNING_SAME_THREAD.get() { >::new().run_bridge_and_client( dispatcher, input, @@ -188,6 +221,8 @@ impl ExecutionStrategy for SameThread { run_client: extern "C" fn(BridgeConfig<'_>) -> Buffer, force_show_panics: bool, ) -> Buffer { + let _guard = RunningSameThreadGuard::new(); + let mut dispatch = |buf| dispatcher.dispatch(buf); run_client(BridgeConfig { diff --git a/src/test/ui/proc-macro/auxiliary/expand-expr.rs b/src/test/ui/proc-macro/auxiliary/expand-expr.rs index 5463e79d74e..1d6ef8a1361 100644 --- a/src/test/ui/proc-macro/auxiliary/expand-expr.rs +++ b/src/test/ui/proc-macro/auxiliary/expand-expr.rs @@ -80,13 +80,21 @@ fn assert_ts_eq(lhs: &TokenStream, rhs: &TokenStream) { pub fn expand_expr_is(input: TokenStream) -> TokenStream { let mut iter = input.into_iter(); let mut expected_tts = Vec::new(); - loop { + let comma = loop { match iter.next() { - Some(TokenTree::Punct(ref p)) if p.as_char() == ',' => break, + Some(TokenTree::Punct(p)) if p.as_char() == ',' => break p, Some(tt) => expected_tts.push(tt), None => panic!("expected comma"), } - } + }; + + // Make sure that `Ident` and `Literal` objects from this proc-macro's + // environment are not invalidated when `expand_expr` recursively invokes + // another macro by taking a local copy, and checking it after the fact. + let pre_expand_span = comma.span(); + let pre_expand_ident = Ident::new("ident", comma.span()); + let pre_expand_literal = Literal::string("literal"); + let pre_expand_call_site = Span::call_site(); let expected = expected_tts.into_iter().collect::(); let expanded = iter.collect::().expand_expr().expect("expand_expr failed"); @@ -100,6 +108,15 @@ pub fn expand_expr_is(input: TokenStream) -> TokenStream { // Also compare the raw tts to make sure they line up. assert_ts_eq(&expected, &expanded); + assert!(comma.span().eq(&pre_expand_span), "pre-expansion span is still equal"); + assert_eq!(pre_expand_ident.to_string(), "ident", "pre-expansion identifier is still valid"); + assert_eq!( + pre_expand_literal.to_string(), + "\"literal\"", + "pre-expansion literal is still valid" + ); + assert!(Span::call_site().eq(&pre_expand_call_site), "pre-expansion call-site is still equal"); + TokenStream::new() }