diff --git a/Jint.Tests/Runtime/ArgumentsCacheBehaviorTests.cs b/Jint.Tests/Runtime/ArgumentsCacheBehaviorTests.cs new file mode 100644 index 0000000000..ded91fe4d7 --- /dev/null +++ b/Jint.Tests/Runtime/ArgumentsCacheBehaviorTests.cs @@ -0,0 +1,152 @@ +using Jint.Native; + +namespace Jint.Tests.Runtime; + +public class ArgumentsCacheBehaviorTests +{ + [Fact] + public void ArgsForGeneratorsAreNotReusedFromCache() + { + // Arrange + List 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 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 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 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); + } +} diff --git a/Jint.Tests/Runtime/Interpreter/JintFunctionDefinitionTest.cs b/Jint.Tests/Runtime/Interpreter/JintFunctionDefinitionTest.cs index 2a131a0f85..806b2ffda9 100644 --- a/Jint.Tests/Runtime/Interpreter/JintFunctionDefinitionTest.cs +++ b/Jint.Tests/Runtime/Interpreter/JintFunctionDefinitionTest.cs @@ -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); + } } diff --git a/Jint/Engine.cs b/Jint/Engine.cs index c2f0417ead..80c4ef6914 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -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; diff --git a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs index c97517562c..d2dcf6dcdb 100644 --- a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs +++ b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs @@ -192,6 +192,7 @@ internal sealed class State public bool IsSimpleParameterList; public bool HasParameterExpressions; public bool ArgumentsObjectNeeded; + public bool RequiresInputArgumentsOwnership; public List? VarNames; public LinkedList? FunctionsToInitialize; public readonly HashSet FunctionNames = new(); @@ -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();