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
152 changes: 152 additions & 0 deletions Jint.Tests/Runtime/ArgumentsCacheBehaviorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using Jint.Native;

namespace Jint.Tests.Runtime;

public class ArgumentsCacheBehaviorTests
{
[Fact]
public void ArgsForGeneratorsAreNotReusedFromCache()
{
// Arrange
List<JsValue> logValues = new();
var engine = new Engine();
engine.SetValue("log", logValues.Add);

// Act
engine.Evaluate(
"""
function *method() {
log(arguments[0]);
log(arguments[1]);
}

function *other() {
log(arguments[0]);
log(arguments[1]);
}

var generator1 = method(42, undefined);
var generator2 = other(10, undefined);
generator1.next();
generator2.next();
""");

// Assert
Assert.Equal([
JsNumber.Create(42),
JsValue.Undefined,
JsNumber.Create(10),
JsValue.Undefined,
], logValues);
}

[Fact]
public void NamedArgsForGeneratorsAreNotReusedFromCache()
{
// Arrange
List<JsValue> logValues = new();
var engine = new Engine();
engine.SetValue("log", logValues.Add);

// Act
engine.Evaluate(
"""
function *method(a, b) {
log(a);
log(b);
}

function *other(a, b) {
log(a);
log(b);
}

var generator1 = method(42, undefined);
var generator2 = other(10, undefined);
generator1.next();
generator2.next();
""");

// Assert
Assert.Equal([
JsNumber.Create(42),
JsValue.Undefined,
JsNumber.Create(10),
JsValue.Undefined,
], logValues);
}

[Fact]
public void ArgsForGeneratorsWithBindAreNotReusedFromCache()
{
// Arrange
List<JsValue> logValues = new();
var engine = new Engine();
engine.SetValue("log", logValues.Add);

// Act
engine.Evaluate(
"""
function *method() {
log(arguments[0]);
log(arguments[1]);
}

function *other() {
log(arguments[0]);
log(arguments[1]);
}

var methodWithBind = method.bind({});
var otherWithBind = other.bind({});

var generator1 = methodWithBind(42, undefined);
var generator2 = otherWithBind(10, undefined);
generator1.next();
generator2.next();
""");

// Assert
Assert.Equal([
JsNumber.Create(42),
JsValue.Undefined,
JsNumber.Create(10),
JsValue.Undefined,
], logValues);
}

[Fact]
public void ArgsForAsyncFunctionsAreNotReused()
{
// Arrange
List<JsValue> logValues = new();
var engine = new Engine(new Options(){ ExperimentalFeatures = ExperimentalFeature.All });
engine.SetValue("log", logValues.Add);

// Act
engine.Execute(
"""
async function method() {
log(arguments[0]);
log(arguments[1]);
}

async function other() {
log(arguments[0]);
log(arguments[1]);
}

method(42, undefined);
other(10, undefined);
""");
engine.RunAvailableContinuations();

// Assert
Assert.Equal([
JsNumber.Create(42),
JsValue.Undefined,
JsNumber.Create(10),
JsValue.Undefined,
], logValues);
}
}
31 changes: 31 additions & 0 deletions Jint.Tests/Runtime/Interpreter/JintFunctionDefinitionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,35 @@ public void ShouldDetectParameterExpression(string functionCode, bool hasExpress
var state = JintFunctionDefinition.BuildState(function);
state.HasParameterExpressions.Should().Be(hasExpressions);
}

[Theory]
[InlineData("function g() { }", false)]
[InlineData("function* g() { }", false)]
[InlineData("async function g() { }", false)]
[InlineData("() => { }", false)]
[InlineData("async () => { }", false)]
[InlineData("function g(a) { }", false)]
[InlineData("function* g(a) { }", false)]
[InlineData("async function g(a) { }", false)]
[InlineData("(a) => { }", false)]
[InlineData("async (a) => { }", false)]
[InlineData("function g(a) { _ = arguments[0] }", false)]
[InlineData("function* g(a) { _ = arguments[0] }", true)]
[InlineData("async function g(a) { _ = arguments[0] }", true)]
[InlineData("(a) => { _ = arguments[0] }", false)]
[InlineData("async (a) => { _ = arguments[0] }", true)]
public void ShouldIndicateArgumentsOwnershipIfNeeded(string functionCode, bool requiresOwnership)
{
var parser = new Parser();
var script = parser.ParseScript(functionCode);
Node statement = script.Body.First();
var function = (IFunction) (
statement is ExpressionStatement expr
? expr.Expression
: statement
);

var state = JintFunctionDefinition.BuildState(function);
state.RequiresInputArgumentsOwnership.Should().Be(requiresOwnership);
}
}
4 changes: 4 additions & 0 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,10 @@ private void GlobalDeclarationInstantiation(
var hasDuplicates = configuration.HasDuplicates;
var simpleParameterList = configuration.IsSimpleParameterList;
var hasParameterExpressions = configuration.HasParameterExpressions;
if (configuration.RequiresInputArgumentsOwnership)
{
argumentsList = [.. argumentsList];
}

var canInitializeParametersOnDeclaration = simpleParameterList && !configuration.HasDuplicates;
var arguments = canInitializeParametersOnDeclaration ? argumentsList : null;
Expand Down
12 changes: 12 additions & 0 deletions Jint/Runtime/Interpreter/JintFunctionDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ internal sealed class State
public bool IsSimpleParameterList;
public bool HasParameterExpressions;
public bool ArgumentsObjectNeeded;
public bool RequiresInputArgumentsOwnership;
public List<Key>? VarNames;
public LinkedList<FunctionDeclaration>? FunctionsToInitialize;
public readonly HashSet<Key> FunctionNames = new();
Expand Down Expand Up @@ -271,6 +272,17 @@ internal static State BuildState(IFunction function)
parameterBindings.Add(KnownKeys.Arguments);
}

if (function.Type == NodeType.ArrowFunctionExpression)
{
state.RequiresInputArgumentsOwnership = state.ArgumentsObjectNeeded ||
(function.Async && ArgumentsUsageAstVisitor.HasArgumentsReference(function));
}
else
{
state.RequiresInputArgumentsOwnership = state.ArgumentsObjectNeeded &&
(function.Async || function.Generator);
}

state.ParameterBindings = parameterBindings;

var varsToInitialize = new List<State.VariableValuePair>();
Expand Down