You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The documentation for proc_macro::Delimiter::.None says:
∅ ... ∅ An invisible delimiter, that may, for example, appear around tokens coming from a “macro variable” $var. It is important to preserve operator priorities in cases like $var * 3 where $var is 1 + 2. Invisible delimiters might not survive roundtrip of a token stream through a string.
This implies that a None-delimited Group is parsed in an expression, the grouping overrides expression precedence. But this is not the case. It only appears to work because usually what looks like a None-delimited group in proc macro debug output, is really something else.
I conjecture that this has seemed to work for many people much of the time because of various optimisations in the proc_macro bridge, and because macro_rules! expansions don't actually produce these None-delimited groups internally.
CC @dtolnay (as the author of syn, whose users seem like they might be affected).
See also #124817, which is sort of the same bug, but with types. There, the outcome is a compile error rather than miscompilation.
To reproduce
proc macro
use proc_macro::{Group, TokenStream, TokenTree};
#[proc_macro]
pub fn dbg_dump(input: TokenStream) -> TokenStream {
dbg!(&input);
dbg!(input.to_string());
input
}
/// Reconstructs, identically, all the `Group`s in a `TokenStream`
///
/// (We don't bother adjusting spans.)
#[proc_macro]
pub fn reconstruct_groups(input: TokenStream) -> TokenStream {
fn recurse(input: TokenStream) -> TokenStream {
let mut output = TokenStream::new();
for tt in input {
let tt = match tt {
TokenTree::Group(g) => {
let delim = g.delimiter();
dbg!(&delim);
let stream = recurse(g.stream());
TokenTree::Group(Group::new(delim, stream))
},
other => other,
};
output.extend([tt]);
}
output
}
dbg!(&input);
let output = recurse(input);
dbg!(&output);
dbg!(&output.to_string());
output
}
2 * (3 + 4) should be 14
14 without proc_macro
14 dbg_dump
14 reconstruct
(plus debug dumps to stderr)
Actual output
2 * (3 + 4) should be 14
14 without proc_macro
14 dbg_dump
10 reconstruct
(plus debug dumps to stderr)
Additional inforamation, observations
Consequences
Some proc_macros could be generating expressions that are interpreted wrongly by the compiler.
It doesn't seem easy to rule out the possibility that this could result in very seriously buggy executables. However, many simpler cases seem to work as expected, so hopefully the problem is not very widespread.
One way the issue could be triggered is by composition of (a) a normal macro_rules macro with (b) a proc_macro which performs certain token stream reconstructions; these might even be in completely separate crates and only combined in a naive downstream crate.
It might be possible to use a specially instrumented compiler to detect programs (at least on crates.io) which are misinterpreted.
Semantic difference is not discernable even in Debug output
See the full output, below. The three Debug representations are identical, apart from spans:
The apparent TokenStream resulting from just the macro_rules! expansion
The pre-reconstructed input to reconstruct_groups
The reconstructed output from reconstruct_groups
But version 3 is parsed differently, when evaluated as an expression. It ignores the group and consequently gives the wrong answer.
The documentation for
proc_macro::Delimiter::.None
says:This implies that a
None
-delimitedGroup
is parsed in an expression, the grouping overrides expression precedence. But this is not the case. It only appears to work because usually what looks like a None-delimited group in proc macro debug output, is really something else.I conjecture that this has seemed to work for many people much of the time because of various optimisations in the proc_macro bridge, and because
macro_rules!
expansions don't actually produce theseNone
-delimited groups internally.CC @dtolnay (as the author of
syn
, whose users seem like they might be affected).See also #124817, which is sort of the same bug, but with types. There, the outcome is a compile error rather than miscompilation.
To reproduce
proc macro
main.rs
Expected output
(plus debug dumps to stderr)
Actual output
(plus debug dumps to stderr)
Additional inforamation, observations
Consequences
Some proc_macros could be generating expressions that are interpreted wrongly by the compiler.
It doesn't seem easy to rule out the possibility that this could result in very seriously buggy executables. However, many simpler cases seem to work as expected, so hopefully the problem is not very widespread.
One way the issue could be triggered is by composition of (a) a normal macro_rules macro with (b) a proc_macro which performs certain token stream reconstructions; these might even be in completely separate crates and only combined in a naive downstream crate.
It might be possible to use a specially instrumented compiler to detect programs (at least on crates.io) which are misinterpreted.
Semantic difference is not discernable even in Debug output
See the full output, below. The three
Debug
representations are identical, apart from spans:TokenStream
resulting from just themacro_rules!
expansionreconstruct_groups
reconstruct_groups
But version 3 is parsed differently, when evaluated as an expression. It ignores the group and consequently gives the wrong answer.
Rust version
I have reproduced with Rust 1.45, too (which was when proc macros in expression position were stabilised).
Full output
The text was updated successfully, but these errors were encountered: