Skip to content

Commit

Permalink
Rollup merge of #100250 - cjgillot:recover-token-stream, r=Aaron1011
Browse files Browse the repository at this point in the history
Manually cleanup token stream when macro expansion aborts.

In case of syntax error in macro expansion, the expansion code can decide to stop processing anything. In that case, the token stream is malformed. This makes downstream users, like derive macros, ICE.

In this case, this PR manually cleans up the token stream by closing all currently open delimiters.

Fixes #96818.
Fixes #80447.
Fixes #81920.
Fixes #91023.
  • Loading branch information
notriddle authored Sep 20, 2022
2 parents 4136b59 + cb5ea8d commit 14b27cf
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 29 deletions.
64 changes: 36 additions & 28 deletions compiler/rustc_builtin_macros/src/cfg_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use rustc_ast::visit::Visitor;
use rustc_ast::NodeId;
use rustc_ast::{mut_visit, visit};
use rustc_ast::{Attribute, HasAttrs, HasTokens};
use rustc_errors::PResult;
use rustc_expand::base::{Annotatable, ExtCtxt};
use rustc_expand::config::StripUnconfigured;
use rustc_expand::configure;
Expand Down Expand Up @@ -144,33 +145,34 @@ impl CfgEval<'_, '_> {
// the location of `#[cfg]` and `#[cfg_attr]` in the token stream. The tokenization
// process is lossless, so this process is invisible to proc-macros.

let parse_annotatable_with: fn(&mut Parser<'_>) -> _ = match annotatable {
Annotatable::Item(_) => {
|parser| Annotatable::Item(parser.parse_item(ForceCollect::Yes).unwrap().unwrap())
}
Annotatable::TraitItem(_) => |parser| {
Annotatable::TraitItem(
parser.parse_trait_item(ForceCollect::Yes).unwrap().unwrap().unwrap(),
)
},
Annotatable::ImplItem(_) => |parser| {
Annotatable::ImplItem(
parser.parse_impl_item(ForceCollect::Yes).unwrap().unwrap().unwrap(),
)
},
Annotatable::ForeignItem(_) => |parser| {
Annotatable::ForeignItem(
parser.parse_foreign_item(ForceCollect::Yes).unwrap().unwrap().unwrap(),
)
},
Annotatable::Stmt(_) => |parser| {
Annotatable::Stmt(P(parser.parse_stmt(ForceCollect::Yes).unwrap().unwrap()))
},
Annotatable::Expr(_) => {
|parser| Annotatable::Expr(parser.parse_expr_force_collect().unwrap())
}
_ => unreachable!(),
};
let parse_annotatable_with: for<'a> fn(&mut Parser<'a>) -> PResult<'a, _> =
match annotatable {
Annotatable::Item(_) => {
|parser| Ok(Annotatable::Item(parser.parse_item(ForceCollect::Yes)?.unwrap()))
}
Annotatable::TraitItem(_) => |parser| {
Ok(Annotatable::TraitItem(
parser.parse_trait_item(ForceCollect::Yes)?.unwrap().unwrap(),
))
},
Annotatable::ImplItem(_) => |parser| {
Ok(Annotatable::ImplItem(
parser.parse_impl_item(ForceCollect::Yes)?.unwrap().unwrap(),
))
},
Annotatable::ForeignItem(_) => |parser| {
Ok(Annotatable::ForeignItem(
parser.parse_foreign_item(ForceCollect::Yes)?.unwrap().unwrap(),
))
},
Annotatable::Stmt(_) => |parser| {
Ok(Annotatable::Stmt(P(parser.parse_stmt(ForceCollect::Yes)?.unwrap())))
},
Annotatable::Expr(_) => {
|parser| Ok(Annotatable::Expr(parser.parse_expr_force_collect()?))
}
_ => unreachable!(),
};

// 'Flatten' all nonterminals (i.e. `TokenKind::Interpolated`)
// to `None`-delimited groups containing the corresponding tokens. This
Expand All @@ -193,7 +195,13 @@ impl CfgEval<'_, '_> {
let mut parser =
rustc_parse::stream_to_parser(&self.cfg.sess.parse_sess, orig_tokens, None);
parser.capture_cfg = true;
annotatable = parse_annotatable_with(&mut parser);
match parse_annotatable_with(&mut parser) {
Ok(a) => annotatable = a,
Err(mut err) => {
err.emit();
return Some(annotatable);
}
}

// Now that we have our re-parsed `AttrTokenStream`, recursively configuring
// our attribute target will correctly the tokens as well.
Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_parse/src/parser/attr_wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,5 @@ fn make_token_stream(
panic!("Unexpected last token {:?}", last_token)
}
}
assert!(stack.is_empty(), "Stack should be empty: final_buf={:?} stack={:?}", final_buf, stack);
AttrTokenStream::new(final_buf.inner)
}
18 changes: 18 additions & 0 deletions src/test/ui/macros/syntax-error-recovery.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
macro_rules! values {
($($token:ident($value:literal) $(as $inner:ty)? => $attr:meta,)*) => {
#[derive(Debug)]
pub enum TokenKind {
$(
#[$attr]
$token $($inner)? = $value,
)*
}
};
}
//~^^^^^ ERROR expected one of `(`, `,`, `=`, `{`, or `}`, found `(String)`
//~| ERROR macro expansion ignores token `(String)` and any following

values!(STRING(1) as (String) => cfg(test),);
//~^ ERROR expected one of `!` or `::`, found `<eof>`

fn main() {}
30 changes: 30 additions & 0 deletions src/test/ui/macros/syntax-error-recovery.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
error: expected one of `(`, `,`, `=`, `{`, or `}`, found `(String)`
--> $DIR/syntax-error-recovery.rs:7:26
|
LL | $token $($inner)? = $value,
| ^^^^^^ expected one of `(`, `,`, `=`, `{`, or `}`
...
LL | values!(STRING(1) as (String) => cfg(test),);
| -------------------------------------------- in this macro invocation
|
= note: this error originates in the macro `values` (in Nightly builds, run with -Z macro-backtrace for more info)

error: macro expansion ignores token `(String)` and any following
--> $DIR/syntax-error-recovery.rs:7:26
|
LL | $token $($inner)? = $value,
| ^^^^^^
...
LL | values!(STRING(1) as (String) => cfg(test),);
| -------------------------------------------- caused by the macro expansion here
|
= note: the usage of `values!` is likely invalid in item context

error: expected one of `!` or `::`, found `<eof>`
--> $DIR/syntax-error-recovery.rs:15:9
|
LL | values!(STRING(1) as (String) => cfg(test),);
| ^^^^^^ expected one of `!` or `::`

error: aborting due to 3 previous errors

0 comments on commit 14b27cf

Please sign in to comment.