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 readonly ExpressionCache _arguments = new(); private JintExpression _calleeExpression = null!; private bool _initialized; public JintCallExpression(CallExpression expression) : base(expression) { } private void Initialize(EvaluationContext context) { var expression = (CallExpression) _expression; _arguments.Initialize(context, expression.Arguments.AsSpan()); _calleeExpression = Build(expression.Callee); } 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 = this._arguments.ArgumentListEvaluation(context, out var rented); 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 Throw.RecursionDepthOverflowException(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 (rented) { 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"; Throw.TypeError(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"; Throw.TypeError(engine.Realm, message); } private JsValue HandleEval(EvaluationContext context, JsValue func, Engine engine, Reference referenceRecord) { var argList = _arguments.ArgumentListEvaluation(context, out var rented); 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); if (rented) { engine._jsValueArrayPool.ReturnArray(argList); } 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) { Throw.TypeError(engine.Realm, "Not a constructor"); } var rented = false; var defaultSuperCall = ReferenceEquals(_expression, ClassDefinition._defaultSuperCall); var argList = defaultSuperCall ? _arguments.DefaultSuperCallArgumentListEvaluation(context) : _arguments.ArgumentListEvaluation(context, out rented); var result = ((IConstructor) func).Construct(argList, newTarget); var thisER = (FunctionEnvironment) engine.ExecutionContext.GetThisEnvironment(); thisER.BindThisValue(result); var F = thisER._functionObject; result.InitializeInstanceElements((ScriptFunction) F); if (rented) { engine._jsValueArrayPool.ReturnArray(argList); } return result; } /// /// https://tc39.es/ecma262/#sec-getsuperconstructor /// private static ObjectInstance? GetSuperConstructor(FunctionEnvironment thisEnvironment) { var envRec = thisEnvironment; var activeFunction = envRec._functionObject; var superConstructor = activeFunction.GetPrototypeOf(); return superConstructor; } /// /// https://tc39.es/ecma262/#sec-isintailposition /// private static bool IsInTailPosition(CallExpression call) { // TODO tail calls return false; } }