using System; using System.Collections.Generic; using Esprima.Ast; using Jint.Native; using Jint.Native.Function; using Jint.Runtime.Interpreter.Expressions; namespace Jint.Runtime.Interpreter { /// /// Works as memento for function execution. Optimization to cache things that don't change. /// internal sealed class JintFunctionDefinition { private readonly Engine _engine; private JintExpression _bodyExpression; private JintStatementList _bodyStatementList; public readonly string Name; public readonly bool Strict; public readonly IFunction Function; private State _state; public JintFunctionDefinition( Engine engine, IFunction function) { _engine = engine; Function = function; Name = !string.IsNullOrEmpty(function.Id?.Name) ? function.Id.Name : null; Strict = function.Strict; } public FunctionThisMode ThisMode => Strict || _engine._isStrict ? FunctionThisMode.Strict : FunctionThisMode.Global; internal Completion Execute(EvaluationContext context) { if (Function.Expression) { _bodyExpression ??= JintExpression.Build(_engine, (Expression) Function.Body); var jsValue = _bodyExpression?.GetValue(context).Value ?? Undefined.Instance; return new Completion(CompletionType.Return, jsValue, Function.Body.Location); } var blockStatement = (BlockStatement) Function.Body; _bodyStatementList ??= new JintStatementList(blockStatement, blockStatement.Body); return _bodyStatementList.Execute(context); } internal State Initialize(FunctionInstance functionInstance) { return _state ??= DoInitialize(functionInstance); } internal sealed class State { public bool HasRestParameter; public int Length; public Key[] ParameterNames; public bool HasDuplicates; public bool IsSimpleParameterList; public bool HasParameterExpressions; public bool ArgumentsObjectNeeded; public List VarNames; public LinkedList FunctionsToInitialize; public readonly HashSet FunctionNames = new HashSet(); public LexicalVariableDeclaration[] LexicalDeclarations = Array.Empty(); public HashSet ParameterBindings; public List VarsToInitialize; internal struct VariableValuePair { public Key Name; public JsValue InitialValue; } internal struct LexicalVariableDeclaration { public VariableDeclarationKind Kind; public List BoundNames; } } private State DoInitialize(FunctionInstance functionInstance) { var state = new State(); ProcessParameters(Function, state, out var hasArguments); var hoistingScope = HoistingScope.GetFunctionLevelDeclarations(Function, collectVarNames: true, collectLexicalNames: true); var functionDeclarations = hoistingScope._functionDeclarations; var lexicalNames = hoistingScope._lexicalNames; state.VarNames = hoistingScope._varNames; LinkedList functionsToInitialize = null; if (functionDeclarations != null) { functionsToInitialize = new LinkedList(); 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(new JintFunctionDefinition(_engine, d)); } } } state.FunctionsToInitialize = functionsToInitialize; const string ParameterNameArguments = "arguments"; state.ArgumentsObjectNeeded = true; if (functionInstance._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; } } var parameterBindings = new HashSet(state.ParameterNames); if (state.ArgumentsObjectNeeded) { parameterBindings.Add(KnownKeys.Arguments); } state.ParameterBindings = parameterBindings; var varsToInitialize = new List(); if (!state.HasParameterExpressions) { var instantiatedVarNames = state.VarNames != null ? new HashSet(state.ParameterBindings) : new HashSet(); 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(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(); d.GetBoundNames(boundNames); declarations[i] = new State.LexicalVariableDeclaration { Kind = d.Kind, BoundNames = boundNames }; } state.LexicalDeclarations = declarations; } return state; } private static void GetBoundNames( Expression parameter, List 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 |= identifier.Name == "arguments"; 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; ref readonly var functionDeclarationParams = ref function.Params; var count = functionDeclarationParams.Count; var parameterNames = new List(count); for (var i = 0; i < count; i++) { var parameter = functionDeclarationParams[i]; if (parameter is Identifier id) { state.HasDuplicates |= parameterNames.Contains(id.Name); hasArguments = id.Name == "arguments"; parameterNames.Add(id.Name); if (state.IsSimpleParameterList) { state.Length++; } } else if (parameter.Type != Nodes.Literal) { state.IsSimpleParameterList = false; GetBoundNames( parameter, parameterNames, checkDuplicates: true, ref state.HasRestParameter, ref state.HasParameterExpressions, ref state.HasDuplicates, ref hasArguments); } } state.ParameterNames = parameterNames.ToArray(); } } }