123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- using System.Runtime.CompilerServices;
- using Jint.Native;
- using Jint.Native.Function;
- using Jint.Native.Generator;
- using Jint.Native.Promise;
- using Jint.Runtime.Environments;
- using Jint.Runtime.Interpreter.Expressions;
- namespace Jint.Runtime.Interpreter;
- /// <summary>
- /// Works as memento for function execution. Optimization to cache things that don't change.
- /// </summary>
- internal sealed class JintFunctionDefinition
- {
- private JintExpression? _bodyExpression;
- private JintStatementList? _bodyStatementList;
- public readonly string? Name;
- public readonly IFunction Function;
- public JintFunctionDefinition(IFunction function)
- {
- Function = function;
- Name = !string.IsNullOrEmpty(function.Id?.Name) ? function.Id!.Name : null;
- }
- public bool Strict => Function.Strict;
- public FunctionThisMode ThisMode => Function.Strict ? FunctionThisMode.Strict : FunctionThisMode.Global;
- /// <summary>
- /// https://tc39.es/ecma262/#sec-ordinarycallevaluatebody
- /// </summary>
- [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions) 512)]
- internal Completion EvaluateBody(EvaluationContext context, Function functionObject, JsValue[] argumentsList)
- {
- Completion result;
- JsArguments? argumentsInstance = null;
- if (Function.Expression)
- {
- // https://tc39.es/ecma262/#sec-runtime-semantics-evaluateconcisebody
- _bodyExpression ??= JintExpression.Build((Expression) Function.Body);
- if (Function.Async)
- {
- var promiseCapability = PromiseConstructor.NewPromiseCapability(context.Engine, context.Engine.Realm.Intrinsics.Promise);
- AsyncFunctionStart(context, promiseCapability, context =>
- {
- context.Engine.FunctionDeclarationInstantiation(functionObject, argumentsList);
- context.RunBeforeExecuteStatementChecks(Function.Body);
- var jsValue = _bodyExpression.GetValue(context).Clone();
- return new Completion(CompletionType.Return, jsValue, _bodyExpression._expression);
- });
- result = new Completion(CompletionType.Return, promiseCapability.PromiseInstance, Function.Body);
- }
- else
- {
- argumentsInstance = context.Engine.FunctionDeclarationInstantiation(functionObject, argumentsList);
- context.RunBeforeExecuteStatementChecks(Function.Body);
- var jsValue = _bodyExpression.GetValue(context).Clone();
- result = new Completion(CompletionType.Return, jsValue, Function.Body);
- }
- }
- else if (Function.Generator)
- {
- result = EvaluateGeneratorBody(context, functionObject, argumentsList);
- }
- else
- {
- if (Function.Async)
- {
- var promiseCapability = PromiseConstructor.NewPromiseCapability(context.Engine, context.Engine.Realm.Intrinsics.Promise);
- _bodyStatementList ??= new JintStatementList(Function);
- AsyncFunctionStart(context, promiseCapability, context =>
- {
- context.Engine.FunctionDeclarationInstantiation(functionObject, argumentsList);
- return _bodyStatementList.Execute(context);
- });
- result = new Completion(CompletionType.Return, promiseCapability.PromiseInstance, Function.Body);
- }
- else
- {
- // https://tc39.es/ecma262/#sec-runtime-semantics-evaluatefunctionbody
- argumentsInstance = context.Engine.FunctionDeclarationInstantiation(functionObject, argumentsList);
- _bodyStatementList ??= new JintStatementList(Function);
- result = _bodyStatementList.Execute(context);
- }
- }
- argumentsInstance?.FunctionWasCalled();
- return result;
- }
- /// <summary>
- /// https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start
- /// </summary>
- private static void AsyncFunctionStart(EvaluationContext context, PromiseCapability promiseCapability, Func<EvaluationContext, Completion> asyncFunctionBody)
- {
- var runningContext = context.Engine.ExecutionContext;
- var asyncContext = runningContext;
- AsyncBlockStart(context, promiseCapability, asyncFunctionBody, asyncContext);
- }
- /// <summary>
- /// https://tc39.es/ecma262/#sec-asyncblockstart
- /// </summary>
- private static void AsyncBlockStart(
- EvaluationContext context,
- PromiseCapability promiseCapability,
- Func<EvaluationContext, Completion> asyncBody,
- in ExecutionContext asyncContext)
- {
- var runningContext = context.Engine.ExecutionContext;
- // Set the code evaluation state of asyncContext such that when evaluation is resumed for that execution contxt the following steps will be performed:
- Completion result;
- try
- {
- result = asyncBody(context);
- }
- catch (JavaScriptException e)
- {
- promiseCapability.Reject.Call(JsValue.Undefined, new[] { e.Error });
- return;
- }
- if (result.Type == CompletionType.Normal)
- {
- promiseCapability.Resolve.Call(JsValue.Undefined, new[] { JsValue.Undefined });
- }
- else if (result.Type == CompletionType.Return)
- {
- promiseCapability.Resolve.Call(JsValue.Undefined, new[] { result.Value });
- }
- else
- {
- promiseCapability.Reject.Call(JsValue.Undefined, new[] { result.Value });
- }
- /*
- 4. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
- 5. Resume the suspended evaluation of asyncContext. Let result be the value returned by the resumed computation.
- 6. Assert: When we return here, asyncContext has already been removed from the execution context stack and runningContext is the currently running execution context.
- 7. Assert: result is a normal completion with a value of unused. The possible sources of this value are Await or, if the async function doesn't await anything, step 3.g above.
- 8. Return unused.
- */
- }
- /// <summary>
- /// https://tc39.es/ecma262/#sec-runtime-semantics-evaluategeneratorbody
- /// </summary>
- private Completion EvaluateGeneratorBody(
- EvaluationContext context,
- Function functionObject,
- JsValue[] argumentsList)
- {
- var engine = context.Engine;
- engine.FunctionDeclarationInstantiation(functionObject, argumentsList);
- var G = engine.Realm.Intrinsics.Function.OrdinaryCreateFromConstructor(
- functionObject,
- static intrinsics => intrinsics.GeneratorFunction.PrototypeObject.PrototypeObject,
- static (Engine engine , Realm _, object? _) => new GeneratorInstance(engine));
- _bodyStatementList ??= new JintStatementList(Function);
- _bodyStatementList.Reset();
- G.GeneratorStart(_bodyStatementList);
- return new Completion(CompletionType.Return, G, Function.Body);
- }
- internal State Initialize()
- {
- var node = (Node) Function;
- var state = (State) (node.AssociatedData ??= BuildState(Function));
- return state;
- }
- internal sealed class State
- {
- public bool HasRestParameter;
- public int Length;
- public Key[] ParameterNames = null!;
- public bool HasDuplicates;
- public bool IsSimpleParameterList;
- public bool HasParameterExpressions;
- public bool ArgumentsObjectNeeded;
- public List<Key>? VarNames;
- public LinkedList<FunctionDeclaration>? FunctionsToInitialize;
- public readonly HashSet<Key> FunctionNames = new();
- public LexicalVariableDeclaration[] LexicalDeclarations = Array.Empty<LexicalVariableDeclaration>();
- public HashSet<Key>? ParameterBindings;
- public List<VariableValuePair>? VarsToInitialize;
- internal struct VariableValuePair
- {
- public Key Name;
- public JsValue? InitialValue;
- }
- internal struct LexicalVariableDeclaration
- {
- public bool IsConstantDeclaration;
- public List<Key> BoundNames;
- }
- }
- internal static State BuildState(IFunction function)
- {
- var state = new State();
- ProcessParameters(function, state, out var hasArguments);
- var hoistingScope = HoistingScope.GetFunctionLevelDeclarations(function.Strict, function);
- var functionDeclarations = hoistingScope._functionDeclarations;
- var lexicalNames = hoistingScope._lexicalNames;
- state.VarNames = hoistingScope._varNames;
- LinkedList<FunctionDeclaration>? functionsToInitialize = null;
- if (functionDeclarations != null)
- {
- functionsToInitialize = new LinkedList<FunctionDeclaration>();
- for (var i = functionDeclarations.Count - 1; i >= 0; i--)
- {
- var d = functionDeclarations[i];
- var fn = d.Id!.Name;
- if (state.FunctionNames.Add(fn))
- {
- functionsToInitialize.AddFirst(d);
- }
- }
- }
- state.FunctionsToInitialize = functionsToInitialize;
- const string ParameterNameArguments = "arguments";
- state.ArgumentsObjectNeeded = true;
- var thisMode = function.Strict ? FunctionThisMode.Strict : FunctionThisMode.Global;
- if (function.Type == NodeType.ArrowFunctionExpression)
- {
- thisMode = FunctionThisMode.Lexical;
- }
- if (thisMode == FunctionThisMode.Lexical)
- {
- state.ArgumentsObjectNeeded = false;
- }
- else if (hasArguments)
- {
- state.ArgumentsObjectNeeded = false;
- }
- else if (!state.HasParameterExpressions)
- {
- if (state.FunctionNames.Contains(ParameterNameArguments)
- || lexicalNames?.Contains(ParameterNameArguments) == true)
- {
- state.ArgumentsObjectNeeded = false;
- }
- }
- if (state.ArgumentsObjectNeeded)
- {
- // just one extra check...
- state.ArgumentsObjectNeeded = ArgumentsUsageAstVisitor.HasArgumentsReference(function);
- }
- var parameterBindings = new HashSet<Key>(state.ParameterNames);
- if (state.ArgumentsObjectNeeded)
- {
- parameterBindings.Add(KnownKeys.Arguments);
- }
- state.ParameterBindings = parameterBindings;
- var varsToInitialize = new List<State.VariableValuePair>();
- if (!state.HasParameterExpressions)
- {
- var instantiatedVarNames = state.VarNames != null
- ? new HashSet<Key>(state.ParameterBindings)
- : new HashSet<Key>();
- for (var i = 0; i < state.VarNames?.Count; i++)
- {
- var n = state.VarNames[i];
- if (instantiatedVarNames.Add(n))
- {
- varsToInitialize.Add(new State.VariableValuePair
- {
- Name = n
- });
- }
- }
- }
- else
- {
- var instantiatedVarNames = state.VarNames != null
- ? new HashSet<Key>(state.ParameterBindings)
- : null;
- for (var i = 0; i < state.VarNames?.Count; i++)
- {
- var n = state.VarNames[i];
- if (instantiatedVarNames!.Add(n))
- {
- JsValue? initialValue = null;
- if (!state.ParameterBindings.Contains(n) || state.FunctionNames.Contains(n))
- {
- initialValue = JsValue.Undefined;
- }
- varsToInitialize.Add(new State.VariableValuePair
- {
- Name = n,
- InitialValue = initialValue
- });
- }
- }
- }
- state.VarsToInitialize = varsToInitialize;
- if (hoistingScope._lexicalDeclarations != null)
- {
- var _lexicalDeclarations = hoistingScope._lexicalDeclarations;
- var lexicalDeclarationsCount = _lexicalDeclarations.Count;
- var declarations = new State.LexicalVariableDeclaration[lexicalDeclarationsCount];
- for (var i = 0; i < lexicalDeclarationsCount; i++)
- {
- var d = _lexicalDeclarations[i];
- var boundNames = new List<Key>();
- d.GetBoundNames(boundNames);
- declarations[i] = new State.LexicalVariableDeclaration
- {
- IsConstantDeclaration = d.IsConstantDeclaration(),
- BoundNames = boundNames
- };
- }
- state.LexicalDeclarations = declarations;
- }
- return state;
- }
- private static void GetBoundNames(
- Node? parameter,
- List<Key> target,
- bool checkDuplicates,
- ref bool _hasRestParameter,
- ref bool _hasParameterExpressions,
- ref bool _hasDuplicates,
- ref bool hasArguments)
- {
- if (parameter is Identifier identifier)
- {
- _hasDuplicates |= checkDuplicates && target.Contains(identifier.Name);
- target.Add(identifier.Name);
- hasArguments |= string.Equals(identifier.Name, "arguments", StringComparison.Ordinal);
- return;
- }
- while (true)
- {
- if (parameter is RestElement restElement)
- {
- _hasRestParameter = true;
- _hasParameterExpressions = true;
- parameter = restElement.Argument;
- continue;
- }
- if (parameter is ArrayPattern arrayPattern)
- {
- _hasParameterExpressions = true;
- ref readonly var arrayPatternElements = ref arrayPattern.Elements;
- for (var i = 0; i < arrayPatternElements.Count; i++)
- {
- var expression = arrayPatternElements[i];
- GetBoundNames(
- expression,
- target,
- checkDuplicates,
- ref _hasRestParameter,
- ref _hasParameterExpressions,
- ref _hasDuplicates,
- ref hasArguments);
- }
- }
- else if (parameter is ObjectPattern objectPattern)
- {
- _hasParameterExpressions = true;
- ref readonly var objectPatternProperties = ref objectPattern.Properties;
- for (var i = 0; i < objectPatternProperties.Count; i++)
- {
- var property = objectPatternProperties[i];
- if (property is Property p)
- {
- GetBoundNames(
- p.Value,
- target,
- checkDuplicates,
- ref _hasRestParameter,
- ref _hasParameterExpressions,
- ref _hasDuplicates,
- ref hasArguments);
- }
- else
- {
- _hasRestParameter = true;
- _hasParameterExpressions = true;
- parameter = ((RestElement) property).Argument;
- continue;
- }
- }
- }
- else if (parameter is AssignmentPattern assignmentPattern)
- {
- _hasParameterExpressions = true;
- parameter = assignmentPattern.Left;
- continue;
- }
- break;
- }
- }
- private static void ProcessParameters(
- IFunction function,
- State state,
- out bool hasArguments)
- {
- hasArguments = false;
- state.IsSimpleParameterList = true;
- var countParameters = true;
- ref readonly var functionDeclarationParams = ref function.Params;
- var count = functionDeclarationParams.Count;
- var parameterNames = new List<Key>(count);
- for (var i = 0; i < count; i++)
- {
- var parameter = functionDeclarationParams[i];
- var type = parameter.Type;
- if (type == NodeType.Identifier)
- {
- var id = (Identifier) parameter;
- state.HasDuplicates |= parameterNames.Contains(id.Name);
- hasArguments = string.Equals(id.Name, "arguments", StringComparison.Ordinal);
- parameterNames.Add(id.Name);
- }
- else if (type != NodeType.Literal)
- {
- countParameters &= type != NodeType.AssignmentPattern;
- state.IsSimpleParameterList = false;
- GetBoundNames(
- parameter,
- parameterNames,
- checkDuplicates: true,
- ref state.HasRestParameter,
- ref state.HasParameterExpressions,
- ref state.HasDuplicates,
- ref hasArguments);
- }
- if (countParameters && type is NodeType.Identifier or NodeType.ObjectPattern or NodeType.ArrayPattern)
- {
- state.Length++;
- }
- }
- state.ParameterNames = parameterNames.ToArray();
- }
- private static class ArgumentsUsageAstVisitor
- {
- public static bool HasArgumentsReference(IFunction function)
- {
- if (HasArgumentsReference(function.Body))
- {
- return true;
- }
- ref readonly var parameters = ref function.Params;
- for (var i = 0; i < parameters.Count; ++i)
- {
- if (HasArgumentsReference(parameters[i]))
- {
- return true;
- }
- }
- return false;
- }
- private static bool HasArgumentsReference(Node node)
- {
- foreach (var childNode in node.ChildNodes)
- {
- var childType = childNode.Type;
- if (childType == NodeType.Identifier)
- {
- if (string.Equals(((Identifier) childNode).Name, "arguments", StringComparison.Ordinal))
- {
- return true;
- }
- }
- else if (childType != NodeType.FunctionDeclaration && !childNode.ChildNodes.IsEmpty())
- {
- if (HasArgumentsReference(childNode))
- {
- return true;
- }
- }
- }
- return false;
- }
- }
- }
|