JintCallExpression.cs 13 KB

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