123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- using System.Diagnostics.CodeAnalysis;
- using System.Runtime.CompilerServices;
- using Jint.Native;
- using Jint.Native.Function;
- using Jint.Native.Object;
- using Jint.Runtime.CallStack;
- using Jint.Runtime.Environments;
- using Environment = Jint.Runtime.Environments.Environment;
- namespace Jint.Runtime.Interpreter.Expressions
- {
- internal sealed class JintCallExpression : JintExpression
- {
- private CachedArgumentsHolder _cachedArguments = null!;
- private bool _cached;
- private JintExpression _calleeExpression = null!;
- private bool _hasSpreads;
- private bool _initialized;
- public JintCallExpression(CallExpression expression) : base(expression)
- {
- }
- private void Initialize(EvaluationContext context)
- {
- var expression = (CallExpression) _expression;
- ref readonly var expressionArguments = ref expression.Arguments;
- _calleeExpression = Build(expression.Callee);
- var cachedArgumentsHolder = new CachedArgumentsHolder
- {
- JintArguments = new JintExpression[expressionArguments.Count]
- };
- static bool CanSpread(Node? e)
- {
- if (e is null)
- {
- return false;
- }
- return e.Type == NodeType.SpreadElement || e is AssignmentExpression { Right.Type: NodeType.SpreadElement };
- }
- var cacheable = true;
- for (var i = 0; i < expressionArguments.Count; i++)
- {
- var expressionArgument = expressionArguments[i];
- cachedArgumentsHolder.JintArguments[i] = Build(expressionArgument);
- cacheable &= expressionArgument.Type == NodeType.Literal;
- _hasSpreads |= CanSpread(expressionArgument);
- if (expressionArgument is ArrayExpression ae)
- {
- ref readonly var elements = ref ae.Elements;
- for (var elementIndex = 0; elementIndex < elements.Count; elementIndex++)
- {
- _hasSpreads |= CanSpread(elements[elementIndex]);
- }
- }
- }
- if (cacheable)
- {
- _cached = true;
- var arguments = Array.Empty<JsValue>();
- if (cachedArgumentsHolder.JintArguments.Length > 0)
- {
- arguments = new JsValue[cachedArgumentsHolder.JintArguments.Length];
- BuildArguments(context, cachedArgumentsHolder.JintArguments, arguments);
- }
- cachedArgumentsHolder.CachedArguments = arguments;
- }
- _cachedArguments = cachedArgumentsHolder;
- }
- protected override object EvaluateInternal(EvaluationContext context)
- {
- if (!_initialized)
- {
- Initialize(context);
- _initialized = true;
- }
- if (!context.Engine._stackGuard.TryEnterOnCurrentStack())
- {
- return StackGuard.RunOnEmptyStack(EvaluateInternal, context);
- }
- if (_calleeExpression._expression.Type == NodeType.Super)
- {
- return SuperCall(context);
- }
- // https://tc39.es/ecma262/#sec-function-calls
- var reference = _calleeExpression.Evaluate(context);
- if (ReferenceEquals(reference, JsValue.Undefined))
- {
- return JsValue.Undefined;
- }
- var engine = context.Engine;
- var func = engine.GetValue(reference, false);
- if (func.IsNullOrUndefined() && _expression.IsOptional())
- {
- return JsValue.Undefined;
- }
- var referenceRecord = reference as Reference;
- if (ReferenceEquals(func, engine.Realm.Intrinsics.Eval)
- && referenceRecord != null
- && !referenceRecord.IsPropertyReference
- && CommonProperties.Eval.Equals(referenceRecord.ReferencedName))
- {
- return HandleEval(context, func, engine, referenceRecord);
- }
- var thisCall = (CallExpression) _expression;
- var tailCall = IsInTailPosition(thisCall);
- // https://tc39.es/ecma262/#sec-evaluatecall
- JsValue thisObject;
- if (referenceRecord is not null)
- {
- if (referenceRecord.IsPropertyReference)
- {
- thisObject = referenceRecord.ThisValue;
- }
- else
- {
- var baseValue = referenceRecord.Base;
- // deviation from the spec to support null-propagation helper
- if (baseValue.IsNullOrUndefined()
- && engine._referenceResolver.TryUnresolvableReference(engine, referenceRecord, out var value))
- {
- thisObject = value;
- }
- else
- {
- var refEnv = (Environment) baseValue;
- thisObject = refEnv.WithBaseObject();
- }
- }
- }
- else
- {
- thisObject = JsValue.Undefined;
- }
- var arguments = ArgumentListEvaluation(context);
- if (!func.IsObject() && !engine._referenceResolver.TryGetCallable(engine, reference, out func))
- {
- ThrowMemberIsNotFunction(referenceRecord, reference, engine);
- }
- var callable = func as ICallable;
- if (callable is null)
- {
- ThrowReferenceNotFunction(referenceRecord, reference, engine);
- }
- if (tailCall)
- {
- // TODO tail call
- // PrepareForTailCall();
- }
- // ensure logic is in sync between Call, Construct and JintCallExpression!
- JsValue result;
- if (callable is Function functionInstance)
- {
- var callStack = engine.CallStack;
- var recursionDepth = callStack.Push(functionInstance, _calleeExpression, engine.ExecutionContext);
- if (recursionDepth > engine.Options.Constraints.MaxRecursionDepth)
- {
- // automatically pops the current element as it was never reached
- ExceptionHelper.ThrowRecursionDepthOverflowException(callStack);
- }
- try
- {
- result = functionInstance.Call(thisObject, arguments);
- }
- finally
- {
- // if call stack was reset due to recursive call to engine or similar, we might not have it anymore
- if (callStack.Count > 0)
- {
- callStack.Pop();
- }
- }
- }
- else
- {
- result = callable.Call(thisObject, arguments);
- }
- if (!_cached && arguments.Length > 0)
- {
- engine._jsValueArrayPool.ReturnArray(arguments);
- }
- engine._referencePool.Return(referenceRecord);
- return result;
- }
- [DoesNotReturn]
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static void ThrowReferenceNotFunction(Reference? referenceRecord1, object reference, Engine engine)
- {
- var message = $"{referenceRecord1?.ReferencedName ?? reference} is not a function";
- ExceptionHelper.ThrowTypeError(engine.Realm, message);
- }
- [DoesNotReturn]
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static void ThrowMemberIsNotFunction(Reference? referenceRecord1, object reference, Engine engine)
- {
- var message = referenceRecord1 == null
- ? reference + " is not a function"
- : $"Property '{referenceRecord1.ReferencedName}' of object is not a function";
- ExceptionHelper.ThrowTypeError(engine.Realm, message);
- }
- private JsValue HandleEval(EvaluationContext context, JsValue func, Engine engine, Reference referenceRecord)
- {
- var argList = ArgumentListEvaluation(context);
- if (argList.Length == 0)
- {
- return JsValue.Undefined;
- }
- var evalFunctionInstance = (EvalFunction) func;
- var evalArg = argList[0];
- var strictCaller = StrictModeScope.IsStrictModeCode;
- var evalRealm = evalFunctionInstance._realm;
- var direct = !_expression.IsOptional();
- var value = evalFunctionInstance.PerformEval(evalArg, evalRealm, strictCaller, direct);
- engine._referencePool.Return(referenceRecord);
- return value;
- }
- private ObjectInstance SuperCall(EvaluationContext context)
- {
- var engine = context.Engine;
- var thisEnvironment = (FunctionEnvironment) engine.ExecutionContext.GetThisEnvironment();
- var newTarget = engine.GetNewTarget(thisEnvironment);
- var func = GetSuperConstructor(thisEnvironment);
- if (func is null || !func.IsConstructor)
- {
- ExceptionHelper.ThrowTypeError(engine.Realm, "Not a constructor");
- }
- var defaultSuperCall = ReferenceEquals(_expression, ClassDefinition._defaultSuperCall);
- var argList = defaultSuperCall ? DefaultSuperCallArgumentListEvaluation(context) : ArgumentListEvaluation(context);
- var result = ((IConstructor) func).Construct(argList, newTarget);
- var thisER = (FunctionEnvironment) engine.ExecutionContext.GetThisEnvironment();
- thisER.BindThisValue(result);
- var F = thisER._functionObject;
- result.InitializeInstanceElements((ScriptFunction) F);
- return result;
- }
- /// <summary>
- /// https://tc39.es/ecma262/#sec-getsuperconstructor
- /// </summary>
- private static ObjectInstance? GetSuperConstructor(FunctionEnvironment thisEnvironment)
- {
- var envRec = thisEnvironment;
- var activeFunction = envRec._functionObject;
- var superConstructor = activeFunction.GetPrototypeOf();
- return superConstructor;
- }
- /// <summary>
- /// https://tc39.es/ecma262/#sec-isintailposition
- /// </summary>
- private static bool IsInTailPosition(CallExpression call)
- {
- // TODO tail calls
- return false;
- }
- private JsValue[] ArgumentListEvaluation(EvaluationContext context)
- {
- var cachedArguments = _cachedArguments;
- var arguments = Array.Empty<JsValue>();
- if (_cached)
- {
- arguments = cachedArguments.CachedArguments;
- }
- else
- {
- if (cachedArguments.JintArguments.Length > 0)
- {
- if (_hasSpreads)
- {
- arguments = BuildArgumentsWithSpreads(context, cachedArguments.JintArguments);
- }
- else
- {
- arguments = context.Engine._jsValueArrayPool.RentArray(cachedArguments.JintArguments.Length);
- BuildArguments(context, cachedArguments.JintArguments, arguments);
- }
- }
- }
- return arguments;
- }
- private JsValue[] DefaultSuperCallArgumentListEvaluation(EvaluationContext context)
- {
- // This branch behaves similarly to constructor(...args) { super(...args); }.
- // The most notable distinction is that while the aforementioned ECMAScript source text observably calls
- // the @@iterator method on %Array.prototype%, this function does not.
- var spreadExpression = (JintSpreadExpression) _cachedArguments.JintArguments[0];
- var array = (JsArray) spreadExpression._argument.GetValue(context);
- var length = array.GetLength();
- var args = new List<JsValue>((int) length);
- for (uint j = 0; j < length; ++j)
- {
- array.TryGetValue(j, out var value);
- args.Add(value);
- }
- return args.ToArray();
- }
- private sealed class CachedArgumentsHolder
- {
- internal JintExpression[] JintArguments = Array.Empty<JintExpression>();
- internal JsValue[] CachedArguments = Array.Empty<JsValue>();
- }
- }
- }
|