Skip to content

Commit 3e5b155

Browse files
committed
fix: avoid pathological macro expansions
Today, rust-analyzer (and rustc, and bat, and IntelliJ) fail badly on some kinds of maliciously constructed code, like a deep sequence of nested parenthesis. "Who writes 100k nested parenthesis" you'd ask? Well, in a language with macros, a run-away macro expansion might do that (see the added tests)! Such expansion can be broad, rather than deep, so it bypasses recursion check at the macro-expansion layer, but triggers deep recursion in parser. In the ideal world, the parser would just handle deeply nested structs gracefully. We'll get there some day, but at the moment, let's try to be simple, and just avoid expanding macros with unbalanced parenthesis in the first place. closes #9358
1 parent 9aa6be7 commit 3e5b155

File tree

6 files changed

+41
-24
lines changed

6 files changed

+41
-24
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/hir_def/src/nameres/tests/macros.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::*;
2+
23
use crate::nameres::proc_macro::{ProcMacroDef, ProcMacroKind};
34

45
#[test]
@@ -1021,3 +1022,20 @@ pub mod prelude {
10211022
"#]],
10221023
)
10231024
}
1025+
1026+
#[test]
1027+
fn issue9358_bad_macro_stack_overflow() {
1028+
cov_mark::check!(issue9358_bad_macro_stack_overflow);
1029+
check(
1030+
r#"
1031+
macro_rules! m {
1032+
($cond:expr) => { m!($cond, stringify!($cond)) };
1033+
($cond:expr, $($arg:tt)*) => { $cond };
1034+
}
1035+
m!(
1036+
"#,
1037+
expect![[r#"
1038+
crate
1039+
"#]],
1040+
)
1041+
}

crates/hir_expand/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ edition = "2018"
99
doctest = false
1010

1111
[dependencies]
12+
cov-mark = "2.0.0-pre.1"
1213
log = "0.4.8"
1314
either = "1.5.3"
1415
rustc-hash = "1.0.0"

crates/hir_expand/src/db.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::sync::Arc;
55
use base_db::{salsa, SourceDatabase};
66
use limit::Limit;
77
use mbe::{ExpandError, ExpandResult};
8-
use parser::FragmentKind;
8+
use parser::{FragmentKind, T};
99
use syntax::{
1010
algo::diff,
1111
ast::{self, NameOwner},
@@ -273,6 +273,23 @@ fn macro_arg_text(db: &dyn AstDatabase, id: MacroCallId) -> Option<GreenNode> {
273273
let loc = db.lookup_intern_macro(id);
274274
let arg = loc.kind.arg(db)?;
275275
let arg = process_macro_input(db, arg, id);
276+
if matches!(loc.kind, MacroCallKind::FnLike { .. }) {
277+
let first = arg.first_child_or_token().map_or(T![.], |it| it.kind());
278+
let last = arg.last_child_or_token().map_or(T![.], |it| it.kind());
279+
let well_formed_tt =
280+
matches!((first, last), (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']));
281+
if !well_formed_tt {
282+
// Don't expand malformed (unbalanced) macro invocations. This is
283+
// less than ideal, but trying to expand unbalanced macro calls
284+
// sometimes produces pathological, deeply nested code which breaks
285+
// all kinds of things.
286+
//
287+
// Some day, we'll have explicit recursion counters for all
288+
// recursive things, at which point this code might be removed.
289+
cov_mark::hit!(issue9358_bad_macro_stack_overflow);
290+
return None;
291+
}
292+
}
276293
Some(arg.green().into())
277294
}
278295

crates/ide_completion/src/tests/expression.rs

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -308,27 +308,7 @@ fn quux(x: i32) {
308308
m!(x$0
309309
}
310310
"#,
311-
expect![[r#"
312-
kw unsafe
313-
kw match
314-
kw while
315-
kw while let
316-
kw loop
317-
kw if
318-
kw if let
319-
kw for
320-
kw true
321-
kw false
322-
kw return
323-
kw self
324-
kw super
325-
kw crate
326-
lc y i32
327-
bt u32
328-
lc x i32
329-
fn quux(…) fn(i32)
330-
ma m!(…) macro_rules! m
331-
"#]],
311+
expect![[r#""#]],
332312
);
333313
}
334314

crates/ide_ssr/src/tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -921,13 +921,13 @@ fn preserves_whitespace_within_macro_expansion() {
921921
macro_rules! macro1 {
922922
($a:expr) => {$a}
923923
}
924-
fn f() {macro1!(1 * 2 + 3 + 4}
924+
fn f() {macro1!(1 * 2 + 3 + 4)}
925925
"#,
926926
expect![[r#"
927927
macro_rules! macro1 {
928928
($a:expr) => {$a}
929929
}
930-
fn f() {macro1!(4 - (3 - 1 * 2)}
930+
fn f() {macro1!(4 - (3 - 1 * 2))}
931931
"#]],
932932
)
933933
}

0 commit comments

Comments
 (0)