JintCallExpression.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Runtime.CompilerServices;
  3. using Jint.Native;
  4. using Jint.Native.Function;
  5. using Jint.Native.Object;
  6. using Jint.Runtime.CallStack;
  7. using Jint.Runtime.Environments;
  8. using Environment = Jint.Runtime.Environments.Environment;
  9. namespace Jint.Runtime.Interpreter.Expressions;
  10. internal sealed class JintCallExpression : JintExpression
  11. {
  12. private CachedArgumentsHolder _cachedArguments = null!;
  13. private bool _cached;
  14. private JintExpression _calleeExpression = null!;
  15. private bool _hasSpreads;
  16. private bool _initialized;
  17. public JintCallExpression(CallExpression expression) : base(expression)
  18. {
  19. }
  20. private void Initialize(EvaluationContext context)
  21. {
  22. var expression = (CallExpression) _expression;
  23. ref readonly var expressionArguments = ref expression.Arguments;
  24. _calleeExpression = Build(expression.Callee);
  25. var cachedArgumentsHolder = new CachedArgumentsHolder
  26. {
  27. JintArguments = new JintExpression[expressionArguments.Count]
  28. };
  29. static bool CanSpread(Node? e)
  30. {
  31. if (e is null)
  32. {
  33. return false;
  34. }
  35. return e.Type == NodeType.SpreadElement || e is AssignmentExpression { Right.Type: NodeType.SpreadElement };
  36. }
  37. var cacheable = true;
  38. for (var i = 0; i < expressionArguments.Count; i++)
  39. {
  40. var expressionArgument = expressionArguments[i];
  41. cachedArgumentsHolder.JintArguments[i] = Build(expressionArgument);
  42. cacheable &= expressionArgument.Type == NodeType.Literal;
  43. _hasSpreads |= CanSpread(expressionArgument);
  44. if (expressionArgument is ArrayExpression ae)
  45. {
  46. ref readonly var elements = ref ae.Elements;
  47. for (var elementIndex = 0; elementIndex < elements.Count; elementIndex++)
  48. {
  49. _hasSpreads |= CanSpread(elements[elementIndex]);
  50. }
  51. }
  52. }
  53. if (cacheable)
  54. {
  55. _cached = true;
  56. var arguments = Array.Empty<JsValue>();
  57. if (cachedArgumentsHolder.JintArguments.Length > 0)
  58. {
  59. arguments = new JsValue[cachedArgumentsHolder.JintArguments.Length];
  60. BuildArguments(context, cachedArgumentsHolder.JintArguments, arguments);
  61. }
  62. cachedArgumentsHolder.CachedArguments = arguments;
  63. }
  64. _cachedArguments = cachedArgumentsHolder;
  65. }
  66. protected override object EvaluateInternal(EvaluationContext context)
  67. {
  68. if (!_initialized)
  69. {
  70. Initialize(context);
  71. _initialized = true;
  72. }
  73. if (!context.Engine._stackGuard.TryEnterOnCurrentStack())
  74. {
  75. return StackGuard.RunOnEmptyStack(EvaluateInternal, context);
  76. }
  77. if (_calleeExpression._expression.Type == NodeType.Super)
  78. {
  79. return SuperCall(context);
  80. }
  81. // https://tc39.es/ecma262/#sec-function-calls
  82. var reference = _calleeExpression.Evaluate(context);
  83. if (ReferenceEquals(reference, JsValue.Undefined))
  84. {
  85. return JsValue.Undefined;
  86. }
  87. var engine = context.Engine;
  88. var func = engine.GetValue(reference, false);
  89. if (func.IsNullOrUndefined() && _expression.IsOptional())
  90. {
  91. return JsValue.Undefined;
  92. }
  93. var referenceRecord = reference as Reference;
  94. if (ReferenceEquals(func, engine.Realm.Intrinsics.Eval)
  95. && referenceRecord != null
  96. && !referenceRecord.IsPropertyReference
  97. && CommonProperties.Eval.Equals(referenceRecord.ReferencedName))
  98. {
  99. return HandleEval(context, func, engine, referenceRecord);
  100. }
  101. var thisCall = (CallExpression) _expression;
  102. var tailCall = IsInTailPosition(thisCall);
  103. // https://tc39.es/ecma262/#sec-evaluatecall
  104. JsValue thisObject;
  105. if (referenceRecord is not null)
  106. {
  107. if (referenceRecord.IsPropertyReference)
  108. {
  109. thisObject = referenceRecord.ThisValue;
  110. }
  111. else
  112. {
  113. var baseValue = referenceRecord.Base;
  114. // deviation from the spec to support null-propagation helper
  115. if (baseValue.IsNullOrUndefined()
  116. && engine._referenceResolver.TryUnresolvableReference(engine, referenceRecord, out var value))
  117. {
  118. thisObject = value;
  119. }
  120. else
  121. {
  122. var refEnv = (Environment) baseValue;
  123. thisObject = refEnv.WithBaseObject();
  124. }
  125. }
  126. }
  127. else
  128. {
  129. thisObject = JsValue.Undefined;
  130. }
  131. var arguments = ArgumentListEvaluation(context);
  132. if (!func.IsObject() && !engine._referenceResolver.TryGetCallable(engine, reference, out func))
  133. {
  134. ThrowMemberIsNotFunction(referenceRecord, reference, engine);
  135. }
  136. var callable = func as ICallable;
  137. if (callable is null)
  138. {
  139. ThrowReferenceNotFunction(referenceRecord, reference, engine);
  140. }
  141. if (tailCall)
  142. {
  143. // TODO tail call
  144. // PrepareForTailCall();
  145. }
  146. // ensure logic is in sync between Call, Construct and JintCallExpression!
  147. JsValue result;
  148. if (callable is Function functionInstance)
  149. {
  150. var callStack = engine.CallStack;
  151. var recursionDepth = callStack.Push(functionInstance, _calleeExpression, engine.ExecutionContext);
  152. if (recursionDepth > engine.Options.Constraints.MaxRecursionDepth)
  153. {
  154. // automatically pops the current element as it was never reached
  155. ExceptionHelper.ThrowRecursionDepthOverflowException(callStack);
  156. }
  157. try
  158. {
  159. result = functionInstance.Call(thisObject, arguments);
  160. }
  161. finally
  162. {
  163. // if call stack was reset due to recursive call to engine or similar, we might not have it anymore
  164. if (callStack.Count > 0)
  165. {
  166. callStack.Pop();
  167. }
  168. }
  169. }
  170. else
  171. {
  172. result = callable.Call(thisObject, arguments);
  173. }
  174. if (!_cached && arguments.Length > 0)
  175. {
  176. engine._jsValueArrayPool.ReturnArray(arguments);
  177. }
  178. engine._referencePool.Return(referenceRecord);
  179. return result;
  180. }
  181. [DoesNotReturn]
  182. [MethodImpl(MethodImplOptions.NoInlining)]
  183. private static void ThrowReferenceNotFunction(Reference? referenceRecord1, object reference, Engine engine)
  184. {
  185. var message = $"{referenceRecord1?.ReferencedName ?? reference} is not a function";
  186. ExceptionHelper.ThrowTypeError(engine.Realm, message);
  187. }
  188. [DoesNotReturn]
  189. [MethodImpl(MethodImplOptions.NoInlining)]
  190. private static void ThrowMemberIsNotFunction(Reference? referenceRecord1, object reference, Engine engine)
  191. {
  192. var message = referenceRecord1 == null
  193. ? reference + " is not a function"
  194. : $"Property '{referenceRecord1.ReferencedName}' of object is not a function";
  195. ExceptionHelper.ThrowTypeError(engine.Realm, message);
  196. }
  197. private JsValue HandleEval(EvaluationContext context, JsValue func, Engine engine, Reference referenceRecord)
  198. {
  199. var argList = ArgumentListEvaluation(context);
  200. if (argList.Length == 0)
  201. {
  202. return JsValue.Undefined;
  203. }
  204. var evalFunctionInstance = (EvalFunction) func;
  205. var evalArg = argList[0];
  206. var strictCaller = StrictModeScope.IsStrictModeCode;
  207. var evalRealm = evalFunctionInstance._realm;
  208. var direct = !_expression.IsOptional();
  209. var value = evalFunctionInstance.PerformEval(evalArg, evalRealm, strictCaller, direct);
  210. engine._referencePool.Return(referenceRecord);
  211. return value;
  212. }
  213. private ObjectInstance SuperCall(EvaluationContext context)
  214. {
  215. var engine = context.Engine;
  216. var thisEnvironment = (FunctionEnvironment) engine.ExecutionContext.GetThisEnvironment();
  217. var newTarget = engine.GetNewTarget(thisEnvironment);
  218. var func = GetSuperConstructor(thisEnvironment);
  219. if (func is null || !func.IsConstructor)
  220. {
  221. ExceptionHelper.ThrowTypeError(engine.Realm, "Not a constructor");
  222. }
  223. var defaultSuperCall = ReferenceEquals(_expression, ClassDefinition._defaultSuperCall);
  224. var argList = defaultSuperCall ? DefaultSuperCallArgumentListEvaluation(context) : ArgumentListEvaluation(context);
  225. var result = ((IConstructor) func).Construct(argList, newTarget);
  226. var thisER = (FunctionEnvironment) engine.ExecutionContext.GetThisEnvironment();
  227. thisER.BindThisValue(result);
  228. var F = thisER._functionObject;
  229. result.InitializeInstanceElements((ScriptFunction) F);
  230. return result;
  231. }
  232. /// <summary>
  233. /// https://tc39.es/ecma262/#sec-getsuperconstructor
  234. /// </summary>
  235. private static ObjectInstance? GetSuperConstructor(FunctionEnvironment thisEnvironment)
  236. {
  237. var envRec = thisEnvironment;
  238. var activeFunction = envRec._functionObject;
  239. var superConstructor = activeFunction.GetPrototypeOf();
  240. return superConstructor;
  241. }
  242. /// <summary>
  243. /// https://tc39.es/ecma262/#sec-isintailposition
  244. /// </summary>
  245. private static bool IsInTailPosition(CallExpression call)
  246. {
  247. // TODO tail calls
  248. return false;
  249. }
  250. private JsValue[] ArgumentListEvaluation(EvaluationContext context)
  251. {
  252. var cachedArguments = _cachedArguments;
  253. var arguments = Array.Empty<JsValue>();
  254. if (_cached)
  255. {
  256. arguments = cachedArguments.CachedArguments;
  257. }
  258. else
  259. {
  260. if (cachedArguments.JintArguments.Length > 0)
  261. {
  262. if (_hasSpreads)
  263. {
  264. arguments = BuildArgumentsWithSpreads(context, cachedArguments.JintArguments);
  265. }
  266. else
  267. {
  268. arguments = context.Engine._jsValueArrayPool.RentArray(cachedArguments.JintArguments.Length);
  269. BuildArguments(context, cachedArguments.JintArguments, arguments);
  270. }
  271. }
  272. }
  273. return arguments;
  274. }
  275. private JsValue[] DefaultSuperCallArgumentListEvaluation(EvaluationContext context)
  276. {
  277. // This branch behaves similarly to constructor(...args) { super(...args); }.
  278. // The most notable distinction is that while the aforementioned ECMAScript source text observably calls
  279. // the @@iterator method on %Array.prototype%, this function does not.
  280. var spreadExpression = (JintSpreadExpression) _cachedArguments.JintArguments[0];
  281. var array = (JsArray) spreadExpression._argument.GetValue(context);
  282. var length = array.GetLength();
  283. var args = new List<JsValue>((int) length);
  284. for (uint j = 0; j < length; ++j)
  285. {
  286. array.TryGetValue(j, out var value);
  287. args.Add(value);
  288. }
  289. return args.ToArray();
  290. }
  291. private sealed class CachedArgumentsHolder
  292. {
  293. internal JintExpression[] JintArguments = Array.Empty<JintExpression>();
  294. internal JsValue[] CachedArguments = Array.Empty<JsValue>();
  295. }
  296. }