JintCallExpression.cs 13 KB

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