Skip to content

Commit

Permalink
Improve cfg_if! parsing during module resolution
Browse files Browse the repository at this point in the history
Fixes 4442

* Prevent Infinite loop when reaching tokens that can't be parsed as
  items. For example, nested `if #[..]` checks.
* Handle arbitrarily nested `if #[..]`, `else if #[..]`, `else` checks.
  • Loading branch information
ytmimi committed May 13, 2022
1 parent 17003ce commit 2e92c20
Show file tree
Hide file tree
Showing 13 changed files with 119 additions and 24 deletions.
66 changes: 42 additions & 24 deletions src/parse/macros/cfg_if.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::panic::{catch_unwind, AssertUnwindSafe};

use rustc_ast::ast;
use rustc_ast::token::{DelimToken, TokenKind};
use rustc_parse::parser::ForceCollect;
use rustc_parse::parser::{ForceCollect, Parser};
use rustc_span::symbol::kw;

use crate::parse::macros::build_stream_parser;
Expand Down Expand Up @@ -31,32 +31,25 @@ fn parse_cfg_if_inner<'a>(

while parser.token.kind != TokenKind::Eof {
if process_if_cfg {
if !parser.eat_keyword(kw::If) {
return Err("Expected `if`");
}
// Inner attributes are not actually syntactically permitted here, but we don't
// care about inner vs outer attributes in this position. Our purpose with this
// special case parsing of cfg_if macros is to ensure we can correctly resolve
// imported modules that may have a custom `path` defined.
//
// As such, we just need to advance the parser past the attribute and up to
// to the opening brace.
// See also https://github.com/rust-lang/rust/pull/79433
parser
.parse_attribute(rustc_parse::parser::attr::InnerAttrPolicy::Permitted)
.map_err(|_| "Failed to parse attributes")?;
}

if !parser.eat(&TokenKind::OpenDelim(DelimToken::Brace)) {
return Err("Expected an opening brace");
eat_if(&mut parser)?;
}

while parser.token != TokenKind::CloseDelim(DelimToken::Brace)
&& parser.token.kind != TokenKind::Eof
{
let item = match parser.parse_item(ForceCollect::No) {
Ok(Some(item_ptr)) => item_ptr.into_inner(),
Ok(None) => continue,
Ok(None) => {
if matches!(parser.token.kind, TokenKind::Ident(symbol, ..) if symbol == kw::If)
{
// eat a nested if
eat_if(&mut parser)?;
} else {
// Not sure what token we're on. To prevent infinite loops bump the parser
parser.bump();
}
continue;
}
Err(err) => {
err.cancel();
parser.sess.span_diagnostic.reset_err_count();
Expand All @@ -74,16 +67,41 @@ fn parse_cfg_if_inner<'a>(
return Err("Expected a closing brace");
}

if parser.eat(&TokenKind::Eof) {
break;
if matches!(parser.token.kind, TokenKind::Ident(symbol, ..) if symbol == kw::Else) {
// there might be an `else` after the `if`
parser.eat_keyword(kw::Else);
// there might be an opening brace after the `else`, but it might also be an `else if`
parser.eat(&TokenKind::OpenDelim(DelimToken::Brace));
}

if !parser.eat_keyword(kw::Else) {
return Err("Expected `else`");
if parser.eat(&TokenKind::Eof) {
break;
}

process_if_cfg = parser.token.is_keyword(kw::If);
}

Ok(items)
}

fn eat_if(parser: &mut Parser<'_>) -> Result<(), &'static str> {
if !parser.eat_keyword(kw::If) {
return Err("Expected `if`");
}
// Inner attributes are not actually syntactically permitted here, but we don't
// care about inner vs outer attributes in this position. Our purpose with this
// special case parsing of cfg_if macros is to ensure we can correctly resolve
// imported modules that may have a custom `path` defined.
//
// As such, we just need to advance the parser past the attribute and up to
// to the opening brace.
// See also https://github.com/rust-lang/rust/pull/79433
parser
.parse_attribute(rustc_parse::parser::attr::InnerAttrPolicy::Permitted)
.map_err(|_| "Failed to parse attributes")?;

if !parser.eat(&TokenKind::OpenDelim(DelimToken::Brace)) {
return Err("Expected an opening brace");
}
Ok(())
}
2 changes: 2 additions & 0 deletions tests/source/issue-4442/a.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fn a ()
{println!("mod a")}
2 changes: 2 additions & 0 deletions tests/source/issue-4442/b.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fn b ()
{println!("mod b")}
2 changes: 2 additions & 0 deletions tests/source/issue-4442/c.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fn c ()
{println!("mod c")}
2 changes: 2 additions & 0 deletions tests/source/issue-4442/d.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fn d ()
{println!("mod d")}
2 changes: 2 additions & 0 deletions tests/source/issue-4442/e.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fn e ()
{println!("mod e")}
26 changes: 26 additions & 0 deletions tests/source/issue-4442/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// main.rs
cfg_if::cfg_if! {
if #[cfg(not(feature = "client"))] {
mod a;
if #[cfg(feature = "server")] {
if #[cfg(not(feature = "client"))] {
if #[cfg(feature = "server")] {
if #[cfg(not(feature = "client"))] {
mod b;
} else {
mod c;
}
if #[cfg(feature = "server")] {
if #[cfg(not(feature = "client"))] {
if #[cfg(feature = "server")] {
mod d;
} else {
mod e;
}
}
}
}
}
}
}
}
3 changes: 3 additions & 0 deletions tests/target/issue-4442/a.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn a() {
println!("mod a")
}
3 changes: 3 additions & 0 deletions tests/target/issue-4442/b.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn b() {
println!("mod b")
}
3 changes: 3 additions & 0 deletions tests/target/issue-4442/c.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn c() {
println!("mod c")
}
3 changes: 3 additions & 0 deletions tests/target/issue-4442/d.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn d() {
println!("mod d")
}
3 changes: 3 additions & 0 deletions tests/target/issue-4442/e.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn e() {
println!("mod e")
}
26 changes: 26 additions & 0 deletions tests/target/issue-4442/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// main.rs
cfg_if::cfg_if! {
if #[cfg(not(feature = "client"))] {
mod a;
if #[cfg(feature = "server")] {
if #[cfg(not(feature = "client"))] {
if #[cfg(feature = "server")] {
if #[cfg(not(feature = "client"))] {
mod b;
} else {
mod c;
}
if #[cfg(feature = "server")] {
if #[cfg(not(feature = "client"))] {
if #[cfg(feature = "server")] {
mod d;
} else {
mod e;
}
}
}
}
}
}
}
}

0 comments on commit 2e92c20

Please sign in to comment.