diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsInterpreter.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsInterpreter.kt index 5bb5457f324..4cd96ae6489 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsInterpreter.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsInterpreter.kt @@ -269,6 +269,8 @@ private data class TryCatch(val body: List, 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 { val stmts = mutableListOf() @@ -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 @@ -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() } @@ -844,7 +850,7 @@ private class JsInterpreter { last } catch (r: ReturnSignal) { r.value - } catch (e: Throwable) { + } catch (e: Exception) { logError(e) Unit } @@ -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 diff --git a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt index 4d0f01b5a60..462a2810068 100644 --- a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt +++ b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt @@ -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) + } }