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..d077341dcb 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 = 256; + 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 nesting exceeds maximum depth of {MaxDepth} levels.", 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..bad1f182af 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 property value: expected Expression but found {propertyValue.GetType().Name}."); + return; + } + valueExpressions[i] = propertyExpression; _canBuildFast &= !propertyValue.IsFunctionDefinition(); } else