Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ private data class TryCatch(val body: List<Node>, val catchParam: String?, val c
private data class ThrowStmt(val expr: Node) : Node()

private class Parser(private val lex: Lexer) {
private var depth = 0
private val maxDepth = 25

fun parseProgram(): List<Node> {
val stmts = mutableListOf<Node>()
Expand Down Expand Up @@ -438,7 +440,9 @@ private class Parser(private val lex: Lexer) {
}

private fun parseAssign(): Node {
if (++depth > maxDepth) { depth--; return UndefinedLit }
val left = parseTernary()
depth--
val op = when (lex.peek().type) {
TT.EQ -> "="; TT.PLUSEQ -> "+="; TT.MINUSEQ -> "-="; TT.STAREQ -> "*="; TT.SLASHEQ -> "/="; TT.PERCENTEQ -> "%="
else -> return left
Expand Down Expand Up @@ -743,6 +747,8 @@ private class Scope(val parent: Scope? = null) {

private class JsInterpreter {
private val globalScope = Scope()
private var evalDepth = 0
private val maxEvalDepth = 200

init { installGlobals() }

Expand Down Expand Up @@ -844,7 +850,7 @@ private class JsInterpreter {
last
} catch (r: ReturnSignal) {
r.value
} catch (e: Throwable) {
} catch (e: Exception) {
logError(e)
Unit
}
Expand Down Expand Up @@ -929,7 +935,14 @@ private class JsInterpreter {
else -> evalExpr(node, scope)
}

private fun evalExpr(node: Node, scope: Scope): Any? = when (node) {
private fun evalExpr(node: Node, scope: Scope): Any? {
if (++evalDepth > maxEvalDepth) { evalDepth--; return Unit }
val result = evalExprInner(node, scope)
evalDepth--
return result
}

private fun evalExprInner(node: Node, scope: Scope): Any? = when (node) {
is NumLit -> node.v
is StrLit -> node.v
is BoolLit -> node.v
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1832,4 +1832,49 @@ class JsInterpreterTest {
fun bitwiseTruncationPattern() {
assertEquals(5.0, num("(11/2)|0"))
}

@Test
fun deeplyNestedParenthesesDoesNotStackOverflow() {
val open = "(".repeat(600)
val close = ")".repeat(600)
val result = evalJs("${open}42${close}")
assertTrue(result == Unit || result == 42.0)
}

@Test
fun extremelyDeeplyNestedParenthesesDoesNotStackOverflow() {
val open = "(".repeat(2000)
val close = ")".repeat(2000)
val result = evalJs("${open}1${close}")
assertTrue(result == Unit || result == 1.0)
}

@Test
fun deeplyRecursiveFunctionDoesNotStackOverflow() {
val code = """
function f(n) { return n <= 0 ? 0 : f(n-1) + 1; }
f(600)
""".trimIndent()
val result = evalJs(code)
assertTrue(result == Unit || result is Double)
}

@Test
fun infiniteRecursionDoesNotStackOverflow() {
val code = """
function inf() { return inf(); }
inf()
""".trimIndent()
val result = evalJs(code)
assertTrue(result == Unit || result is Double)
}

@Test
fun deeplyNestedFunctionCallsDoNotStackOverflow() {
val depth = 300
val decls = (1..depth).joinToString("\n") { "function f$it(x){return x+1;}" }
val call = (1 until depth).fold("f${depth}(0)") { acc, i -> "f$i($acc)" }
val result = evalJs("$decls\n$call")
assertTrue(result == Unit || result is Double)
}
}
Loading