Repository: intellij-rust/intellij-rust
Issue: StackOverflowError on infinitely recursive macros

## Environment

* **IntelliJ Rust plugin version:** 0.2.117.3001-201-nightly
* **Rust toolchain version:** 1.41.0 (5e1a79984 2020-01-27) x86_64-apple-darwin
* **IDE name and version:** IntelliJ IDEA 2020.1 EAP Ultimate Edition (IU-201.5616.10)
* **Operating system:** macOS 10.15.3

## Problem description
`java.lang.StackOverflowError` is thrown during building control flow graph of an infinitely recursive macro.

## Steps to reproduce
```Rust
macro_rules! infinite_macro {
    () => { infinite_macro!() };
}

fn main() {
    infinite_macro!();
}
```

Changed files:
src/test/kotlin/org/rust/lang/core/cfg/RsControlFlowGraphTest.kt, src/test/kotlin/org/rust/ide/inspections/borrowck/RsBorrowCheckerMovesTest.kt, src/main/kotlin/org/rust/lang/core/types/borrowck/ExprUseWalker.kt, src/main/kotlin/org/rust/lang/core/cfg/CFGBuilder.kt

diff --git a/src/main/kotlin/org/rust/lang/core/cfg/CFGBuilder.kt b/src/main/kotlin/org/rust/lang/core/cfg/CFGBuilder.kt
index 6c3a03692..d0451cb91 100644
--- a/src/main/kotlin/org/rust/lang/core/cfg/CFGBuilder.kt
+++ b/src/main/kotlin/org/rust/lang/core/cfg/CFGBuilder.kt
@@ -10,6 +10,7 @@ import org.rust.lang.core.cfg.CFGBuilder.ScopeCFKind.Break
 import org.rust.lang.core.cfg.CFGBuilder.ScopeCFKind.Continue
 import org.rust.lang.core.psi.*
 import org.rust.lang.core.psi.ext.*
+import org.rust.lang.core.resolve.DEFAULT_RECURSION_LIMIT
 import org.rust.lang.core.types.regions.Scope
 import org.rust.lang.core.types.regions.ScopeTree
 import org.rust.lang.core.types.ty.TyNever
@@ -43,6 +44,7 @@ class CFGBuilder(
     private val pred: CFGNode get() = preds.peek()
     private val loopScopes: Deque<LoopScope> = ArrayDeque()
     private val breakableBlockScopes: Deque<BlockScope> = ArrayDeque()
+    private var nestedMacroCallsCount: Int = 0
 
     private inline fun finishWith(callable: () -> CFGNode) {
         result = callable()
@@ -253,6 +255,12 @@ class CFGBuilder(
     }
 
     override fun visitMacroCall(macroCall: RsMacroCall) {
+        if (nestedMacroCallsCount > DEFAULT_RECURSION_LIMIT) {
+            finishWithUnreachableNode(addAstNode(macroCall, pred))
+            return
+        }
+        nestedMacroCallsCount++
+
         val subExprsExit = when (val argument = macroCall.macroArgumentElement) {
             is RsExprMacroArgument -> argument.expr?.let { process(it, pred) }
             is RsIncludeMacroArgument -> argument.expr?.let { process(it, pred) }
@@ -287,6 +295,8 @@ class CFGBuilder(
             else -> error("unreachable")
         }
 
+        nestedMacroCallsCount--
+
         val subElementsExit = subExprsExit ?: run {
             val subPathsIdents = PsiTreeUtil.findChildrenOfAnyType(
                 macroCall,
diff --git a/src/main/kotlin/org/rust/lang/core/types/borrowck/ExprUseWalker.kt b/src/main/kotlin/org/rust/lang/core/types/borrowck/ExprUseWalker.kt
index 82a7c8dff..b13bf90af 100644
--- a/src/main/kotlin/org/rust/lang/core/types/borrowck/ExprUseWalker.kt
+++ b/src/main/kotlin/org/rust/lang/core/types/borrowck/ExprUseWalker.kt
@@ -9,6 +9,7 @@ import org.rust.lang.core.psi.*
 import org.rust.lang.core.psi.ext.*
 import org.rust.lang.core.psi.ext.RsBindingModeKind.BindByReference
 import org.rust.lang.core.psi.ext.RsBindingModeKind.BindByValue
+import org.rust.lang.core.resolve.DEFAULT_RECURSION_LIMIT
 import org.rust.lang.core.resolve.VALUES
 import org.rust.lang.core.types.GatherLivenessContext
 import org.rust.lang.core.types.borrowck.ConsumeMode.Copy
@@ -110,6 +111,8 @@ enum class MutateMode {
 }
 
 class ExprUseWalker(private val delegate: Delegate, private val mc: MemoryCategorizationContext) {
+    private var nestedMacroCallsCount: Int = 0
+
     fun consumeBody(body: RsBlock) {
         val function = body.parent as? RsFunction ?: return
 
@@ -311,6 +314,11 @@ class ExprUseWalker(private val delegate: Delegate, private val mc: MemoryCatego
     }
 
     private fun walkMacroCall(macroCall: RsMacroCall) {
+        if (nestedMacroCallsCount > DEFAULT_RECURSION_LIMIT) {
+            return
+        }
+        nestedMacroCallsCount++
+
         when (val argument = macroCall.macroArgumentElement) {
             is RsExprMacroArgument -> argument.expr?.let(::walkExpr)
             is RsIncludeMacroArgument -> argument.expr?.let(::walkExpr)
@@ -347,6 +355,7 @@ class ExprUseWalker(private val delegate: Delegate, private val mc: MemoryCatego
 
             else -> error("unreachable")
         }
+        nestedMacroCallsCount--
     }
 
     private fun walkStmt(stmt: RsStmt) {
diff --git a/src/test/kotlin/org/rust/ide/inspections/borrowck/RsBorrowCheckerMovesTest.kt b/src/test/kotlin/org/rust/ide/inspections/borrowck/RsBorrowCheckerMovesTest.kt
index 23dfb8c87..ffd397b81 100644
--- a/src/test/kotlin/org/rust/ide/inspections/borrowck/RsBorrowCheckerMovesTest.kt
+++ b/src/test/kotlin/org/rust/ide/inspections/borrowck/RsBorrowCheckerMovesTest.kt
@@ -616,4 +616,17 @@ class RsBorrowCheckerMovesTest : RsInspectionsTestBase(RsBorrowCheckerInspection
             }
         }
     """, checkWarn = false)
+
+    fun `test infinitely recursive macro call`() = checkByText("""
+        macro_rules! infinite_macro {
+            ($ e:expr) => { infinite_macro!($ e) };
+        }
+        
+        struct S;
+        
+        fn main() {
+            let x = S;
+            infinite_macro!(x);
+        }
+    """, checkWarn = false)
 }
diff --git a/src/test/kotlin/org/rust/lang/core/cfg/RsControlFlowGraphTest.kt b/src/test/kotlin/org/rust/lang/core/cfg/RsControlFlowGraphTest.kt
index bc788d312..4cc8cf079 100644
--- a/src/test/kotlin/org/rust/lang/core/cfg/RsControlFlowGraphTest.kt
+++ b/src/test/kotlin/org/rust/lang/core/cfg/RsControlFlowGraphTest.kt
@@ -951,6 +951,23 @@ class RsControlFlowGraphTest : RsTestBase() {
         Exit
     """)
 
+    fun `test infinitely recursive macro call`() = testCFG("""
+        macro_rules! infinite_macro {
+            () => { infinite_macro!() };
+        }
+        fn foo() {
+            1;
+            infinite_macro!();
+            2;
+        }
+    """, """
+        Entry
+        1
+        1;
+        infinite_macro ! ( )
+        Termination
+    """)
+
 
     private fun testCFG(@Language("Rust") code: String, expectedIndented: String) {
         InlineFile(code)