diff --git a/Cargo.lock b/Cargo.lock index 4d42c1c7be..61180fcb9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -508,6 +508,7 @@ version = "0.0.0" dependencies = [ "base_db", "cfg", + "cov-mark", "either", "expect-test", "la-arena", diff --git a/crates/hir_def/src/nameres/tests/macros.rs b/crates/hir_def/src/nameres/tests/macros.rs index 45448ba81b..763dff5a21 100644 --- a/crates/hir_def/src/nameres/tests/macros.rs +++ b/crates/hir_def/src/nameres/tests/macros.rs @@ -1,4 +1,5 @@ use super::*; + use crate::nameres::proc_macro::{ProcMacroDef, ProcMacroKind}; #[test] @@ -1021,3 +1022,20 @@ pub mod prelude { "#]], ) } + +#[test] +fn issue9358_bad_macro_stack_overflow() { + cov_mark::check!(issue9358_bad_macro_stack_overflow); + check( + r#" +macro_rules! m { + ($cond:expr) => { m!($cond, stringify!($cond)) }; + ($cond:expr, $($arg:tt)*) => { $cond }; +} +m!( +"#, + expect![[r#" + crate + "#]], + ) +} diff --git a/crates/hir_expand/Cargo.toml b/crates/hir_expand/Cargo.toml index 92d6c3e96a..4645970266 100644 --- a/crates/hir_expand/Cargo.toml +++ b/crates/hir_expand/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" doctest = false [dependencies] +cov-mark = "2.0.0-pre.1" log = "0.4.8" either = "1.5.3" rustc-hash = "1.0.0" diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs index fa693e74ee..a9bf6db476 100644 --- a/crates/hir_expand/src/db.rs +++ b/crates/hir_expand/src/db.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use base_db::{salsa, SourceDatabase}; use limit::Limit; use mbe::{ExpandError, ExpandResult}; -use parser::FragmentKind; +use parser::{FragmentKind, T}; use syntax::{ algo::diff, ast::{self, NameOwner}, @@ -273,6 +273,23 @@ fn macro_arg_text(db: &dyn AstDatabase, id: MacroCallId) -> Option { let loc = db.lookup_intern_macro(id); let arg = loc.kind.arg(db)?; let arg = process_macro_input(db, arg, id); + if matches!(loc.kind, MacroCallKind::FnLike { .. }) { + let first = arg.first_child_or_token().map_or(T![.], |it| it.kind()); + let last = arg.last_child_or_token().map_or(T![.], |it| it.kind()); + let well_formed_tt = + matches!((first, last), (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}'])); + if !well_formed_tt { + // Don't expand malformed (unbalanced) macro invocations. This is + // less than ideal, but trying to expand unbalanced macro calls + // sometimes produces pathological, deeply nested code which breaks + // all kinds of things. + // + // Some day, we'll have explicit recursion counters for all + // recursive things, at which point this code might be removed. + cov_mark::hit!(issue9358_bad_macro_stack_overflow); + return None; + } + } Some(arg.green().into()) } diff --git a/crates/ide_completion/src/tests/expression.rs b/crates/ide_completion/src/tests/expression.rs index 95aaff01ac..9a42dd7b27 100644 --- a/crates/ide_completion/src/tests/expression.rs +++ b/crates/ide_completion/src/tests/expression.rs @@ -308,27 +308,7 @@ fn quux(x: i32) { m!(x$0 } "#, - expect![[r#" - kw unsafe - kw match - kw while - kw while let - kw loop - kw if - kw if let - kw for - kw true - kw false - kw return - kw self - kw super - kw crate - lc y i32 - bt u32 - lc x i32 - fn quux(…) fn(i32) - ma m!(…) macro_rules! m - "#]], + expect![[r#""#]], ); } diff --git a/crates/ide_ssr/src/tests.rs b/crates/ide_ssr/src/tests.rs index 444c6b0afd..0b0c1111c4 100644 --- a/crates/ide_ssr/src/tests.rs +++ b/crates/ide_ssr/src/tests.rs @@ -921,13 +921,13 @@ fn preserves_whitespace_within_macro_expansion() { macro_rules! macro1 { ($a:expr) => {$a} } - fn f() {macro1!(1 * 2 + 3 + 4} + fn f() {macro1!(1 * 2 + 3 + 4)} "#, expect![[r#" macro_rules! macro1 { ($a:expr) => {$a} } - fn f() {macro1!(4 - (3 - 1 * 2)} + fn f() {macro1!(4 - (3 - 1 * 2))} "#]], ) }