From 2e92c2043e0cbf94238f16684a6c16df49d7d43c Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Thu, 12 May 2022 22:29:01 -0400 Subject: [PATCH] Improve `cfg_if!` parsing during module resolution 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. --- src/parse/macros/cfg_if.rs | 66 +++++++++++++++++++++------------- tests/source/issue-4442/a.rs | 2 ++ tests/source/issue-4442/b.rs | 2 ++ tests/source/issue-4442/c.rs | 2 ++ tests/source/issue-4442/d.rs | 2 ++ tests/source/issue-4442/e.rs | 2 ++ tests/source/issue-4442/lib.rs | 26 ++++++++++++++ tests/target/issue-4442/a.rs | 3 ++ tests/target/issue-4442/b.rs | 3 ++ tests/target/issue-4442/c.rs | 3 ++ tests/target/issue-4442/d.rs | 3 ++ tests/target/issue-4442/e.rs | 3 ++ tests/target/issue-4442/lib.rs | 26 ++++++++++++++ 13 files changed, 119 insertions(+), 24 deletions(-) create mode 100644 tests/source/issue-4442/a.rs create mode 100644 tests/source/issue-4442/b.rs create mode 100644 tests/source/issue-4442/c.rs create mode 100644 tests/source/issue-4442/d.rs create mode 100644 tests/source/issue-4442/e.rs create mode 100644 tests/source/issue-4442/lib.rs create mode 100644 tests/target/issue-4442/a.rs create mode 100644 tests/target/issue-4442/b.rs create mode 100644 tests/target/issue-4442/c.rs create mode 100644 tests/target/issue-4442/d.rs create mode 100644 tests/target/issue-4442/e.rs create mode 100644 tests/target/issue-4442/lib.rs diff --git a/src/parse/macros/cfg_if.rs b/src/parse/macros/cfg_if.rs index 306b6bb745e..7f892fcda1c 100644 --- a/src/parse/macros/cfg_if.rs +++ b/src/parse/macros/cfg_if.rs @@ -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; @@ -31,24 +31,7 @@ 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) @@ -56,7 +39,17 @@ fn parse_cfg_if_inner<'a>( { 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(); @@ -74,12 +67,15 @@ 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); @@ -87,3 +83,25 @@ fn parse_cfg_if_inner<'a>( 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(()) +} diff --git a/tests/source/issue-4442/a.rs b/tests/source/issue-4442/a.rs new file mode 100644 index 00000000000..4278f0f85b8 --- /dev/null +++ b/tests/source/issue-4442/a.rs @@ -0,0 +1,2 @@ +fn a () +{println!("mod a")} diff --git a/tests/source/issue-4442/b.rs b/tests/source/issue-4442/b.rs new file mode 100644 index 00000000000..da69971945b --- /dev/null +++ b/tests/source/issue-4442/b.rs @@ -0,0 +1,2 @@ +fn b () +{println!("mod b")} diff --git a/tests/source/issue-4442/c.rs b/tests/source/issue-4442/c.rs new file mode 100644 index 00000000000..43fadb4146d --- /dev/null +++ b/tests/source/issue-4442/c.rs @@ -0,0 +1,2 @@ +fn c () +{println!("mod c")} diff --git a/tests/source/issue-4442/d.rs b/tests/source/issue-4442/d.rs new file mode 100644 index 00000000000..99a9598f325 --- /dev/null +++ b/tests/source/issue-4442/d.rs @@ -0,0 +1,2 @@ +fn d () +{println!("mod d")} diff --git a/tests/source/issue-4442/e.rs b/tests/source/issue-4442/e.rs new file mode 100644 index 00000000000..dc28c824946 --- /dev/null +++ b/tests/source/issue-4442/e.rs @@ -0,0 +1,2 @@ +fn e () +{println!("mod e")} diff --git a/tests/source/issue-4442/lib.rs b/tests/source/issue-4442/lib.rs new file mode 100644 index 00000000000..4eddbfa28fa --- /dev/null +++ b/tests/source/issue-4442/lib.rs @@ -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; + } + } + } + } + } + } + } +} diff --git a/tests/target/issue-4442/a.rs b/tests/target/issue-4442/a.rs new file mode 100644 index 00000000000..6ee01582a2c --- /dev/null +++ b/tests/target/issue-4442/a.rs @@ -0,0 +1,3 @@ +fn a() { + println!("mod a") +} diff --git a/tests/target/issue-4442/b.rs b/tests/target/issue-4442/b.rs new file mode 100644 index 00000000000..c1a0a54d143 --- /dev/null +++ b/tests/target/issue-4442/b.rs @@ -0,0 +1,3 @@ +fn b() { + println!("mod b") +} diff --git a/tests/target/issue-4442/c.rs b/tests/target/issue-4442/c.rs new file mode 100644 index 00000000000..1e4661c5033 --- /dev/null +++ b/tests/target/issue-4442/c.rs @@ -0,0 +1,3 @@ +fn c() { + println!("mod c") +} diff --git a/tests/target/issue-4442/d.rs b/tests/target/issue-4442/d.rs new file mode 100644 index 00000000000..3679b181da8 --- /dev/null +++ b/tests/target/issue-4442/d.rs @@ -0,0 +1,3 @@ +fn d() { + println!("mod d") +} diff --git a/tests/target/issue-4442/e.rs b/tests/target/issue-4442/e.rs new file mode 100644 index 00000000000..5a21024b9ac --- /dev/null +++ b/tests/target/issue-4442/e.rs @@ -0,0 +1,3 @@ +fn e() { + println!("mod e") +} diff --git a/tests/target/issue-4442/lib.rs b/tests/target/issue-4442/lib.rs new file mode 100644 index 00000000000..4eddbfa28fa --- /dev/null +++ b/tests/target/issue-4442/lib.rs @@ -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; + } + } + } + } + } + } + } +}