JintCallExpression.cs 12 KB

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