From 85960ee7f37b79efff4422b51dce04596f41fbea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 05:29:44 +0000 Subject: [PATCH 1/4] Initial plan From e45dfa068d1eafc86ddeeb8b99f0e20b96f38cec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 05:49:30 +0000 Subject: [PATCH 2/4] fix: prevent StackOverflow, IOOB, and InvalidCast crashes in Engine.Execute Co-authored-by: lahma <171892+lahma@users.noreply.github.com> --- Jint.Tests/Runtime/EngineTests.cs | 13 ++++++++++++ .../Runtime/ExecutionConstraintTests.cs | 15 ++++++++++++++ Jint.Tests/Runtime/JsonTests.cs | 2 ++ Jint/HoistingScope.cs | 20 +++++++++++++++++++ Jint/Native/Json/JsonParser.cs | 2 +- .../Expressions/JintObjectExpression.cs | 7 ++++++- 6 files changed, 57 insertions(+), 2 deletions(-) diff --git a/Jint.Tests/Runtime/EngineTests.cs b/Jint.Tests/Runtime/EngineTests.cs index 62e1cb5672..da2e4a7e73 100644 --- a/Jint.Tests/Runtime/EngineTests.cs +++ b/Jint.Tests/Runtime/EngineTests.cs @@ -1918,6 +1918,19 @@ function recurse() { } + [Fact] + public void ShouldThrowJavaScriptExceptionForObjectExpressionWithAssignmentPattern() + { + // Before the fix, JintObjectExpression.Initialize crashed with InvalidCastException + // because an AssignmentPattern node was being cast to Expression without a type check. + // After the fix, it throws a catchable JavaScriptException (SyntaxError). + var engine = new Engine(); + Assert.ThrowsAny(() => + engine.Execute("a=[1,3];aaa={}={}.aap+=[1,3];aaa={}={a=-[]<= [](() => new Engine().Execute(sb.ToString())); + } + private static Engine CreateEngine() { Engine engine = new(options => options.LimitRecursion(5)); diff --git a/Jint.Tests/Runtime/JsonTests.cs b/Jint.Tests/Runtime/JsonTests.cs index 38b003acf9..2d90f10534 100644 --- a/Jint.Tests/Runtime/JsonTests.cs +++ b/Jint.Tests/Runtime/JsonTests.cs @@ -70,6 +70,8 @@ public void ShouldParseEscapedCharactersCorrectly(string json, string expectedCh [InlineData("[,]", "Unexpected token ',' in JSON at position 1")] [InlineData("{\"key\": ()}", "Unexpected token '(' in JSON at position 8")] [InlineData(".1", "Unexpected token '.' in JSON at position 0")] + [InlineData("\"\\u", "Expected hexadecimal digit in JSON at position 3")] // truncated \u escape at end of input + [InlineData("\"\\u1\"", "Expected hexadecimal digit in JSON at position 4")] // \u with only 1 hex digit public void ShouldReportHelpfulSyntaxErrorForInvalidJson(string json, string expectedMessage) { var engine = new Engine(); diff --git a/Jint/HoistingScope.cs b/Jint/HoistingScope.cs index 0825d4fd4a..48b69e885b 100644 --- a/Jint/HoistingScope.cs +++ b/Jint/HoistingScope.cs @@ -178,6 +178,9 @@ private sealed class ScriptWalker /// internal List? _annexBFunctions; + private int _depth; + private const int MaxDepth = 2_000; + public ScriptWalker(bool collectVarNames, bool collectLexicalNames) { _collectVarNames = collectVarNames; @@ -185,6 +188,23 @@ public ScriptWalker(bool collectVarNames, bool collectLexicalNames) } public void Visit(Node node, Node? parent, HashSet? enclosingLexicalNames = null) + { + if (++_depth > MaxDepth) + { + _depth--; + throw new ScriptPreparationException("Script is too deeply nested.", null); + } + try + { + VisitCore(node, parent, enclosingLexicalNames); + } + finally + { + _depth--; + } + } + + private void VisitCore(Node node, Node? parent, HashSet? enclosingLexicalNames) { // Collect lexical names from this scope level for AnnexB conflict checking. // These are let/const/class declarations in non-root scopes (blocks, for headers, etc.) diff --git a/Jint/Native/Json/JsonParser.cs b/Jint/Native/Json/JsonParser.cs index 8ecff0bd02..7091021d53 100644 --- a/Jint/Native/Json/JsonParser.cs +++ b/Jint/Native/Json/JsonParser.cs @@ -129,7 +129,7 @@ private char ScanHexEscape() for (int i = 0; i < 4; ++i) { - if (_index < _length + 1 && IsHexDigit(_source[_index])) + if (_index < _length && IsHexDigit(_source[_index])) { char ch = char.ToLower(_source[_index++], CultureInfo.InvariantCulture); code = code * 16 + "0123456789abcdef".IndexOf(ch); diff --git a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs index 4c42c5430d..178c545a7e 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs @@ -92,7 +92,12 @@ private void Initialize(EvaluationContext context) if (p.Kind is PropertyKind.Init) { var propertyValue = p.Value; - valueExpressions[i] = (Expression) propertyValue; + if (propertyValue is not Expression propertyExpression) + { + Throw.SyntaxError(context.Engine.Realm, "Invalid object property value"); + return; + } + valueExpressions[i] = propertyExpression; _canBuildFast &= !propertyValue.IsFunctionDefinition(); } else From 5b0b9c45082804c5c55611b4a2ae8467c4490723 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 05:53:36 +0000 Subject: [PATCH 3/4] fix: improve error messages for depth limit and invalid object property Co-authored-by: lahma <171892+lahma@users.noreply.github.com> --- Jint/HoistingScope.cs | 2 +- Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Jint/HoistingScope.cs b/Jint/HoistingScope.cs index 48b69e885b..c0950c2f13 100644 --- a/Jint/HoistingScope.cs +++ b/Jint/HoistingScope.cs @@ -192,7 +192,7 @@ public void Visit(Node node, Node? parent, HashSet? enclosingLexicalName if (++_depth > MaxDepth) { _depth--; - throw new ScriptPreparationException("Script is too deeply nested.", null); + throw new ScriptPreparationException($"Script nesting exceeds maximum depth of {MaxDepth} levels.", null); } try { diff --git a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs index 178c545a7e..bad1f182af 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs @@ -94,7 +94,7 @@ private void Initialize(EvaluationContext context) var propertyValue = p.Value; if (propertyValue is not Expression propertyExpression) { - Throw.SyntaxError(context.Engine.Realm, "Invalid object property value"); + Throw.SyntaxError(context.Engine.Realm, $"Invalid property value: expected Expression but found {propertyValue.GetType().Name}."); return; } valueExpressions[i] = propertyExpression; From 86d886d71c0e06f7f2a77a4845d2b5601553f5b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 06:18:46 +0000 Subject: [PATCH 4/4] fix: lower ScriptWalker MaxDepth to 256 and fix test for cross-platform compatibility Co-authored-by: lahma <171892+lahma@users.noreply.github.com> --- Jint.Tests/Runtime/ExecutionConstraintTests.cs | 7 ++++--- Jint/HoistingScope.cs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Jint.Tests/Runtime/ExecutionConstraintTests.cs b/Jint.Tests/Runtime/ExecutionConstraintTests.cs index cfac605294..d6fd04a8b6 100644 --- a/Jint.Tests/Runtime/ExecutionConstraintTests.cs +++ b/Jint.Tests/Runtime/ExecutionConstraintTests.cs @@ -408,10 +408,11 @@ public void ResetConstraints() [Fact] public void ShouldThrowScriptPreparationExceptionForDeeplyNestedScript() { - // Generate a script with more than MaxDepth (2000) levels of AST nesting. + // Generate a script with more than MaxDepth (256) levels of AST nesting. // Each if-block pair adds 2 depth levels: IfStatement + BlockStatement. - // 1100 pairs → depth up to 2202, well above the 2000 limit. - const int nestingDepth = 1100; + // 150 pairs → depth up to ~302, well above the 256 limit, and safe for the + // Acornima parser on all platforms (Windows 1MB stack, macOS, Linux). + const int nestingDepth = 150; var sb = new System.Text.StringBuilder(); for (int i = 0; i < nestingDepth; i++) sb.Append("if(true){"); sb.Append("1"); diff --git a/Jint/HoistingScope.cs b/Jint/HoistingScope.cs index c0950c2f13..d077341dcb 100644 --- a/Jint/HoistingScope.cs +++ b/Jint/HoistingScope.cs @@ -179,7 +179,7 @@ private sealed class ScriptWalker internal List? _annexBFunctions; private int _depth; - private const int MaxDepth = 2_000; + private const int MaxDepth = 256; public ScriptWalker(bool collectVarNames, bool collectLexicalNames) {