Skip to content

Commit 6601a5e

Browse files
committed
Avoid exponential retries on inlining overflow
In some situations Typer tries several alternatives around an inlined call. If overflow is detected at depth 32 then this could cause 2^32 different retries which leads to OOM. Prevent this with a switch that stops further inline calls as long as an overflowing call stack is not completely unwound. Fixes #12116
1 parent 865895d commit 6601a5e

File tree

4 files changed

+39
-0
lines changed

4 files changed

+39
-0
lines changed

compiler/src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,9 @@ object Contexts {
922922

923923
private[core] var denotTransformers: Array[DenotTransformer] = _
924924

925+
/** Flag to suppress inlining, set after overflow */
926+
private[dotc] var stopInlining: Boolean = false
927+
925928
// Reporters state
926929
private[dotc] var indent: Int = 0
927930

compiler/src/dotty/tools/dotc/typer/Inliner.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ object Inliner {
7070
|| (ctx.phase == Phases.typerPhase && needsTransparentInlining(tree))
7171
)
7272
&& !ctx.typer.hasInliningErrors
73+
&& !ctx.base.stopInlining
7374
}
7475

7576
private def needsTransparentInlining(tree: Tree)(using Context): Boolean =
@@ -140,6 +141,7 @@ object Inliner {
140141
val body = bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors
141142
new Inliner(tree, body).inlined(tree.srcPos)
142143
else
144+
ctx.base.stopInlining = true
143145
val (reason, setting) =
144146
if reachedInlinedTreesLimit then ("inlined trees", ctx.settings.XmaxInlinedTrees)
145147
else ("successive inlines", ctx.settings.XmaxInlines)
@@ -150,6 +152,10 @@ object Inliner {
150152
|You can use ${setting.name} to change the limit.""",
151153
(tree :: enclosingInlineds).last.srcPos
152154
)
155+
if ctx.base.stopInlining && enclosingInlineds.isEmpty then
156+
ctx.base.stopInlining = false
157+
// we have completely backed out of the call that overflowed;
158+
// reset so that further inline calls can be expanded
153159
tree2
154160
}
155161

tests/neg/i12116.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import compiletime.erasedValue
2+
3+
object test1:
4+
5+
transparent inline def length[T]: Int =
6+
erasedValue[T] match
7+
case _: (h *: t) => 1 + length[t]
8+
case _: EmptyTuple => 0
9+
10+
transparent inline def foo(): Int = 1 + foo()
11+
12+
val y = length[(1, 2, 3)] // error
13+
val x = foo() // error
14+
15+

tests/neg/i12116a.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import compiletime.erasedValue
2+
3+
object test1:
4+
5+
inline def length[T]: Int =
6+
erasedValue[T] match
7+
case _: (h *: t) => 1 + length[t]
8+
case _: EmptyTuple => 0
9+
10+
inline def foo(): Int = 1 + foo()
11+
12+
val y = length[(1, 2, 3)] // error
13+
val x = foo() // error
14+
15+

0 commit comments

Comments
 (0)