Skip to content
Merged
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
13 changes: 13 additions & 0 deletions Jint.Tests/Runtime/EngineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<JavaScriptException>(() =>
engine.Execute("a=[1,3];aaa={}={}.aap+=[1,3];aaa={}={a=-[]<= []<a.m}.aap+=[,2,3111-1]")
);
}


[Fact]
public void LocaleNumberShouldUseLocalCulture()
{
Expand Down
16 changes: 16 additions & 0 deletions Jint.Tests/Runtime/ExecutionConstraintTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,22 @@ public void ResetConstraints()
Assert.Equal(expected, RunLoop(e4, InvokeAction));
}

[Fact]
public void ShouldThrowScriptPreparationExceptionForDeeplyNestedScript()
{
// Generate a script with more than MaxDepth (256) levels of AST nesting.
// Each if-block pair adds 2 depth levels: IfStatement + BlockStatement.
// 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");
for (int i = 0; i < nestingDepth; i++) sb.Append("}");

Assert.Throws<ScriptPreparationException>(() => new Engine().Execute(sb.ToString()));
}

private static Engine CreateEngine()
{
Engine engine = new(options => options.LimitRecursion(5));
Expand Down
2 changes: 2 additions & 0 deletions Jint.Tests/Runtime/JsonTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
20 changes: 20 additions & 0 deletions Jint/HoistingScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,33 @@ private sealed class ScriptWalker
/// </summary>
internal List<FunctionDeclaration>? _annexBFunctions;

private int _depth;
private const int MaxDepth = 256;

public ScriptWalker(bool collectVarNames, bool collectLexicalNames)
{
_collectVarNames = collectVarNames;
_collectLexicalNames = collectLexicalNames;
}

public void Visit(Node node, Node? parent, HashSet<string>? 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<string>? 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.)
Expand Down
2 changes: 1 addition & 1 deletion Jint/Native/Json/JsonParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 6 additions & 1 deletion Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down