Skip to content

Commit

Permalink
Rollup merge of rust-lang#100250 - cjgillot:recover-token-stream, r=A…
Browse files Browse the repository at this point in the history
…aron1011

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 rust-lang#96818.
Fixes rust-lang#80447.
Fixes rust-lang#81920.
Fixes rust-lang#91023.
  • Loading branch information
Dylan-DPC committed Sep 20, 2022
2 parents 99640b2 + cb5ea8d commit 0237e96
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 0237e96

Please sign in to comment.