FunctionEnvironmentRecord.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. using System.Runtime.CompilerServices;
  2. using Esprima.Ast;
  3. using Jint.Native;
  4. using Jint.Native.Array;
  5. using Jint.Native.Function;
  6. using Jint.Native.Iterator;
  7. using Jint.Native.Object;
  8. using Jint.Runtime.Interpreter;
  9. using Jint.Runtime.Interpreter.Expressions;
  10. namespace Jint.Runtime.Environments
  11. {
  12. /// <summary>
  13. /// https://tc39.es/ecma262/#sec-function-environment-records
  14. /// </summary>
  15. internal sealed class FunctionEnvironmentRecord : DeclarativeEnvironmentRecord
  16. {
  17. private enum ThisBindingStatus
  18. {
  19. Lexical,
  20. Initialized,
  21. Uninitialized
  22. }
  23. private JsValue _thisValue;
  24. private ThisBindingStatus _thisBindingStatus;
  25. internal readonly FunctionInstance _functionObject;
  26. public FunctionEnvironmentRecord(
  27. Engine engine,
  28. FunctionInstance functionObject,
  29. JsValue newTarget) : base(engine)
  30. {
  31. _functionObject = functionObject;
  32. NewTarget = newTarget;
  33. if (functionObject._functionDefinition?.Function is ArrowFunctionExpression)
  34. {
  35. _thisBindingStatus = ThisBindingStatus.Lexical;
  36. }
  37. else
  38. {
  39. _thisBindingStatus = ThisBindingStatus.Uninitialized;
  40. }
  41. }
  42. public override bool HasThisBinding() => _thisBindingStatus != ThisBindingStatus.Lexical;
  43. public override bool HasSuperBinding() =>
  44. _thisBindingStatus != ThisBindingStatus.Lexical && !_functionObject._homeObject.IsUndefined();
  45. public JsValue BindThisValue(JsValue value)
  46. {
  47. if (_thisBindingStatus == ThisBindingStatus.Initialized)
  48. {
  49. ExceptionHelper.ThrowReferenceError(_functionObject._realm, "'this' has already been bound");
  50. }
  51. _thisValue = value;
  52. _thisBindingStatus = ThisBindingStatus.Initialized;
  53. return value;
  54. }
  55. public override JsValue GetThisBinding()
  56. {
  57. if (_thisBindingStatus != ThisBindingStatus.Uninitialized)
  58. {
  59. return _thisValue;
  60. }
  61. var message = "Cannot access uninitialized 'this'";
  62. if (NewTarget is ScriptFunctionInstance { _isClassConstructor: true, _constructorKind: ConstructorKind.Derived })
  63. {
  64. // help with better error message
  65. message = "Must call super constructor in derived class before accessing 'this' or returning from derived constructor";
  66. }
  67. ExceptionHelper.ThrowReferenceError(_engine.ExecutionContext.Realm, message);
  68. return null;
  69. }
  70. public JsValue GetSuperBase()
  71. {
  72. var home = _functionObject._homeObject;
  73. return home.IsUndefined()
  74. ? Undefined
  75. : ((ObjectInstance) home).GetPrototypeOf() ?? Null;
  76. }
  77. // optimization to have logic near record internal structures.
  78. internal void InitializeParameters(
  79. Key[] parameterNames,
  80. bool hasDuplicates,
  81. JsValue[] arguments)
  82. {
  83. var value = hasDuplicates ? Undefined : null;
  84. var directSet = !hasDuplicates && _dictionary.Count == 0;
  85. for (var i = 0; (uint) i < (uint) parameterNames.Length; i++)
  86. {
  87. var paramName = parameterNames[i];
  88. if (directSet || !_dictionary.ContainsKey(paramName))
  89. {
  90. var parameterValue = value;
  91. if (arguments != null)
  92. {
  93. parameterValue = (uint) i < (uint) arguments.Length ? arguments[i] : Undefined;
  94. }
  95. _dictionary[paramName] = new Binding(parameterValue, canBeDeleted: false, mutable: true, strict: false);
  96. }
  97. }
  98. }
  99. internal void AddFunctionParameters(EvaluationContext context, IFunction functionDeclaration, JsValue[] arguments)
  100. {
  101. bool empty = _dictionary.Count == 0;
  102. ref readonly var parameters = ref functionDeclaration.Params;
  103. var count = parameters.Count;
  104. for (var i = 0; i < count; i++)
  105. {
  106. SetFunctionParameter(context, parameters[i], arguments, i, empty);
  107. }
  108. }
  109. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  110. private void SetFunctionParameter(
  111. EvaluationContext context,
  112. Node parameter,
  113. JsValue[] arguments,
  114. int index,
  115. bool initiallyEmpty)
  116. {
  117. if (parameter is Identifier identifier)
  118. {
  119. var argument = (uint) index < (uint) arguments.Length ? arguments[index] : Undefined;
  120. SetItemSafely(identifier.Name, argument, initiallyEmpty);
  121. }
  122. else
  123. {
  124. SetFunctionParameterUnlikely(context, parameter, arguments, index, initiallyEmpty);
  125. }
  126. }
  127. private void SetFunctionParameterUnlikely(
  128. EvaluationContext context,
  129. Node parameter,
  130. JsValue[] arguments,
  131. int index,
  132. bool initiallyEmpty)
  133. {
  134. var argument = arguments.Length > index ? arguments[index] : Undefined;
  135. if (parameter is RestElement restElement)
  136. {
  137. HandleRestElementArray(context, restElement, arguments, index, initiallyEmpty);
  138. }
  139. else if (parameter is ArrayPattern arrayPattern)
  140. {
  141. HandleArrayPattern(context, initiallyEmpty, argument, arrayPattern);
  142. }
  143. else if (parameter is ObjectPattern objectPattern)
  144. {
  145. HandleObjectPattern(context, initiallyEmpty, argument, objectPattern);
  146. }
  147. else if (parameter is AssignmentPattern assignmentPattern)
  148. {
  149. HandleAssignmentPatternOrExpression(context, assignmentPattern.Left, assignmentPattern.Right, argument, initiallyEmpty);
  150. }
  151. else if (parameter is AssignmentExpression assignmentExpression)
  152. {
  153. HandleAssignmentPatternOrExpression(context, assignmentExpression.Left, assignmentExpression.Right, argument, initiallyEmpty);
  154. }
  155. }
  156. private void HandleObjectPattern(EvaluationContext context, bool initiallyEmpty, JsValue argument, ObjectPattern objectPattern)
  157. {
  158. if (argument.IsNullOrUndefined())
  159. {
  160. ExceptionHelper.ThrowTypeError(_functionObject._realm, "Destructed parameter is null or undefined");
  161. }
  162. if (!argument.IsObject())
  163. {
  164. return;
  165. }
  166. var argumentObject = argument.AsObject();
  167. var processedProperties = objectPattern.Properties.Count > 0 &&
  168. objectPattern.Properties[objectPattern.Properties.Count - 1] is RestElement
  169. ? new HashSet<JsValue>()
  170. : null;
  171. var jsValues = _engine._jsValueArrayPool.RentArray(1);
  172. foreach (var property in objectPattern.Properties)
  173. {
  174. var oldEnv = _engine.ExecutionContext.LexicalEnvironment;
  175. var paramVarEnv = JintEnvironment.NewDeclarativeEnvironment(_engine, oldEnv);
  176. PrivateEnvironmentRecord privateEnvironment = null; // TODO PRIVATE check when implemented
  177. _engine.EnterExecutionContext(paramVarEnv, paramVarEnv, _engine.ExecutionContext.Realm, privateEnvironment);
  178. try
  179. {
  180. if (property is Property p)
  181. {
  182. JsString propertyName = JsString.Empty;
  183. if (p.Key is Identifier propertyIdentifier)
  184. {
  185. propertyName = JsString.Create(propertyIdentifier.Name);
  186. }
  187. else if (p.Key is Literal propertyLiteral)
  188. {
  189. propertyName = JsString.Create(propertyLiteral.Raw);
  190. }
  191. else if (p.Key is CallExpression callExpression)
  192. {
  193. var jintCallExpression = new JintCallExpression(callExpression);
  194. var jsValue = jintCallExpression.GetValue(context).Value;
  195. propertyName = TypeConverter.ToJsString(jsValue);
  196. }
  197. else
  198. {
  199. ExceptionHelper.ThrowArgumentOutOfRangeException("property", "unknown object pattern property type");
  200. }
  201. processedProperties?.Add(propertyName.ToString());
  202. jsValues[0] = argumentObject.Get(propertyName);
  203. SetFunctionParameter(context, p.Value, jsValues, 0, initiallyEmpty);
  204. }
  205. else
  206. {
  207. if (((RestElement) property).Argument is Identifier restIdentifier)
  208. {
  209. var rest = _engine.Realm.Intrinsics.Object.Construct(argumentObject.Properties.Count - processedProperties.Count);
  210. argumentObject.CopyDataProperties(rest, processedProperties);
  211. SetItemSafely(restIdentifier.Name, rest, initiallyEmpty);
  212. }
  213. else
  214. {
  215. ExceptionHelper.ThrowSyntaxError(_functionObject._realm, "Object rest parameter can only be objects");
  216. }
  217. }
  218. }
  219. finally
  220. {
  221. _engine.LeaveExecutionContext();
  222. }
  223. }
  224. _engine._jsValueArrayPool.ReturnArray(jsValues);
  225. }
  226. private void HandleArrayPattern(EvaluationContext context, bool initiallyEmpty, JsValue argument, ArrayPattern arrayPattern)
  227. {
  228. if (argument.IsNull())
  229. {
  230. ExceptionHelper.ThrowTypeError(_functionObject._realm, "Destructed parameter is null");
  231. }
  232. ArrayInstance array = null;
  233. var arrayContents = System.Array.Empty<JsValue>();
  234. if (argument.IsArray())
  235. {
  236. array = argument.AsArray();
  237. }
  238. else if (argument.IsObject() && argument.TryGetIterator(_functionObject._realm, out var iterator))
  239. {
  240. array = _engine.Realm.Intrinsics.Array.ArrayCreate(0);
  241. var max = arrayPattern.Elements.Count;
  242. if (max > 0 && arrayPattern.Elements[max - 1]?.Type == Nodes.RestElement)
  243. {
  244. // need to consume all
  245. max = int.MaxValue;
  246. }
  247. var protocol = new ArrayPatternProtocol(_engine, array, iterator, max);
  248. protocol.Execute();
  249. }
  250. if (!ReferenceEquals(array, null))
  251. {
  252. arrayContents = new JsValue[array.GetLength()];
  253. for (uint i = 0; i < (uint) arrayContents.Length; i++)
  254. {
  255. arrayContents[i] = array.Get(i);
  256. }
  257. }
  258. for (var i = 0; i < arrayPattern.Elements.Count; i++)
  259. {
  260. SetFunctionParameter(context, arrayPattern.Elements[i], arrayContents, i, initiallyEmpty);
  261. }
  262. }
  263. private void HandleRestElementArray(
  264. EvaluationContext context,
  265. RestElement restElement,
  266. JsValue[] arguments,
  267. int index,
  268. bool initiallyEmpty)
  269. {
  270. // index + 1 == parameters.count because rest is last
  271. int restCount = arguments.Length - (index + 1) + 1;
  272. uint count = restCount > 0 ? (uint) restCount : 0;
  273. var rest = _engine.Realm.Intrinsics.Array.ArrayCreate(count);
  274. uint targetIndex = 0;
  275. for (var argIndex = index; argIndex < arguments.Length; ++argIndex)
  276. {
  277. rest.SetIndexValue(targetIndex++, arguments[argIndex], updateLength: false);
  278. }
  279. if (restElement.Argument is Identifier restIdentifier)
  280. {
  281. SetItemSafely(restIdentifier.Name, rest, initiallyEmpty);
  282. }
  283. else if (restElement.Argument is BindingPattern bindingPattern)
  284. {
  285. SetFunctionParameter(context, bindingPattern, new JsValue[]
  286. {
  287. rest
  288. }, index, initiallyEmpty);
  289. }
  290. else
  291. {
  292. ExceptionHelper.ThrowSyntaxError(_functionObject._realm, "Rest parameters can only be identifiers or arrays");
  293. }
  294. }
  295. private void HandleAssignmentPatternOrExpression(
  296. EvaluationContext context,
  297. Node left,
  298. Node right,
  299. JsValue argument,
  300. bool initiallyEmpty)
  301. {
  302. var idLeft = left as Identifier;
  303. if (idLeft != null
  304. && right is Identifier idRight
  305. && idLeft.Name == idRight.Name)
  306. {
  307. ExceptionHelper.ThrowReferenceNameError(_functionObject._realm, idRight.Name);
  308. }
  309. if (argument.IsUndefined())
  310. {
  311. var expression = right.As<Expression>();
  312. var jintExpression = JintExpression.Build(_engine, expression);
  313. var oldEnv = _engine.ExecutionContext.LexicalEnvironment;
  314. var paramVarEnv = JintEnvironment.NewDeclarativeEnvironment(_engine, oldEnv);
  315. _engine.EnterExecutionContext(new ExecutionContext(null, paramVarEnv, paramVarEnv, null, _engine.Realm, null));
  316. try
  317. {
  318. argument = jintExpression.GetValue(context).Value;
  319. }
  320. finally
  321. {
  322. _engine.LeaveExecutionContext();
  323. }
  324. if (idLeft != null && right.IsFunctionDefinition())
  325. {
  326. ((FunctionInstance) argument).SetFunctionName(idLeft.Name);
  327. }
  328. }
  329. SetFunctionParameter(context, left, new[]
  330. {
  331. argument
  332. }, 0, initiallyEmpty);
  333. }
  334. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  335. private void SetItemSafely(Key name, JsValue argument, bool initiallyEmpty)
  336. {
  337. if (initiallyEmpty)
  338. {
  339. _dictionary[name] = new Binding(argument, canBeDeleted: false, mutable: true, strict: false);
  340. }
  341. else
  342. {
  343. SetItemCheckExisting(name, argument);
  344. }
  345. }
  346. private void SetItemCheckExisting(Key name, JsValue argument)
  347. {
  348. if (!_dictionary.TryGetValue(name, out var existing))
  349. {
  350. _dictionary[name] = new Binding(argument, canBeDeleted: false, mutable: true, strict: false);
  351. }
  352. else
  353. {
  354. if (existing.Mutable)
  355. {
  356. _dictionary[name] = existing.ChangeValue(argument);
  357. }
  358. else
  359. {
  360. ExceptionHelper.ThrowTypeError(_functionObject._realm, "Can't update the value of an immutable binding.");
  361. }
  362. }
  363. }
  364. private sealed class ArrayPatternProtocol : IteratorProtocol
  365. {
  366. private readonly ArrayInstance _instance;
  367. private readonly int _max;
  368. private long _index = 0;
  369. public ArrayPatternProtocol(
  370. Engine engine,
  371. ArrayInstance instance,
  372. IteratorInstance iterator,
  373. int max) : base(engine, iterator, 0)
  374. {
  375. _instance = instance;
  376. _max = max;
  377. }
  378. protected override void ProcessItem(JsValue[] args, JsValue currentValue)
  379. {
  380. _index++;
  381. _instance.SetIndexValue((uint) _index, currentValue, updateLength: false);
  382. }
  383. protected override bool ShouldContinue => _index < _max;
  384. protected override void IterationEnd()
  385. {
  386. if (_index > 0)
  387. {
  388. _instance.SetLength((uint) _index);
  389. IteratorClose(CompletionType.Normal);
  390. }
  391. }
  392. }
  393. }
  394. }