JsArguments.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. using System.Threading;
  2. using Jint.Native.Object;
  3. using Jint.Native.Symbol;
  4. using Jint.Runtime;
  5. using Jint.Runtime.Descriptors;
  6. using Jint.Runtime.Descriptors.Specialized;
  7. using Jint.Runtime.Environments;
  8. using Jint.Runtime.Interop;
  9. namespace Jint.Native
  10. {
  11. /// <summary>
  12. /// https://tc39.es/ecma262/#sec-arguments-exotic-objects
  13. /// </summary>
  14. public sealed class JsArguments : ObjectInstance
  15. {
  16. // cache property container for array iteration for less allocations
  17. private static readonly ThreadLocal<HashSet<Key>> _mappedNamed = new(() => []);
  18. private Function.Function _func = null!;
  19. private Key[] _names = null!;
  20. private JsValue[] _args = null!;
  21. private DeclarativeEnvironment _env = null!;
  22. private bool _canReturnToPool;
  23. private bool _hasRestParameter;
  24. private bool _materialized;
  25. internal JsArguments(Engine engine)
  26. : base(engine, ObjectClass.Arguments)
  27. {
  28. }
  29. internal void Prepare(
  30. Function.Function func,
  31. Key[] names,
  32. JsValue[] args,
  33. DeclarativeEnvironment env,
  34. bool hasRestParameter)
  35. {
  36. _func = func;
  37. _names = names;
  38. _args = args;
  39. _env = env;
  40. _hasRestParameter = hasRestParameter;
  41. _canReturnToPool = true;
  42. ClearProperties();
  43. }
  44. protected override void Initialize()
  45. {
  46. _canReturnToPool = false;
  47. var args = _args;
  48. DefinePropertyOrThrow(CommonProperties.Length, new PropertyDescriptor(_args.Length, PropertyFlag.NonEnumerable));
  49. if (_func is null)
  50. {
  51. // unmapped
  52. ParameterMap = null;
  53. for (uint i = 0; i < (uint) args.Length; i++)
  54. {
  55. var val = args[i];
  56. CreateDataProperty(JsString.Create(i), val);
  57. }
  58. DefinePropertyOrThrow(CommonProperties.Callee, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.None));
  59. }
  60. else
  61. {
  62. ObjectInstance? map = null;
  63. if (args.Length > 0)
  64. {
  65. var mappedNamed = _mappedNamed.Value!;
  66. mappedNamed.Clear();
  67. map = Engine.Realm.Intrinsics.Object.Construct(Arguments.Empty);
  68. for (uint i = 0; i < (uint) args.Length; i++)
  69. {
  70. SetOwnProperty(JsString.Create(i), new PropertyDescriptor(args[i], PropertyFlag.ConfigurableEnumerableWritable));
  71. if (i < _names.Length)
  72. {
  73. var name = _names[i];
  74. if (mappedNamed.Add(name))
  75. {
  76. map.SetOwnProperty(JsString.Create(i), new ClrAccessDescriptor(_env, Engine, name));
  77. }
  78. }
  79. }
  80. }
  81. ParameterMap = map;
  82. // step 13
  83. DefinePropertyOrThrow(CommonProperties.Callee, new PropertyDescriptor(_func, PropertyFlag.NonEnumerable));
  84. }
  85. var iteratorFunction = new ClrFunction(Engine, "iterator", _engine.Realm.Intrinsics.Array.PrototypeObject.Values, 0, PropertyFlag.Configurable);
  86. DefinePropertyOrThrow(GlobalSymbolRegistry.Iterator, new PropertyDescriptor(iteratorFunction, PropertyFlag.Writable | PropertyFlag.Configurable));
  87. }
  88. internal ObjectInstance? ParameterMap { get; set; }
  89. internal override bool IsArrayLike => true;
  90. internal override bool IsIntegerIndexedArray => true;
  91. public uint Length => (uint) _args.Length;
  92. public override PropertyDescriptor GetOwnProperty(JsValue property)
  93. {
  94. EnsureInitialized();
  95. if (ParameterMap is not null)
  96. {
  97. var desc = base.GetOwnProperty(property);
  98. if (desc == PropertyDescriptor.Undefined)
  99. {
  100. return desc;
  101. }
  102. if (ParameterMap.TryGetValue(property, out var jsValue) && !jsValue.IsUndefined())
  103. {
  104. desc.Value = jsValue;
  105. }
  106. return desc;
  107. }
  108. return base.GetOwnProperty(property);
  109. }
  110. /// Implementation from ObjectInstance official specs as the one
  111. /// in ObjectInstance is optimized for the general case and wouldn't work
  112. /// for arrays
  113. public override bool Set(JsValue property, JsValue value, JsValue receiver)
  114. {
  115. EnsureInitialized();
  116. if (!CanPut(property))
  117. {
  118. return false;
  119. }
  120. var ownDesc = GetOwnProperty(property);
  121. if (ownDesc.IsDataDescriptor())
  122. {
  123. var valueDesc = new PropertyDescriptor(value, PropertyFlag.None);
  124. return DefineOwnProperty(property, valueDesc);
  125. }
  126. // property is an accessor or inherited
  127. var desc = GetOwnProperty(property);
  128. if (desc.IsAccessorDescriptor())
  129. {
  130. if (desc.Set is not ICallable setter)
  131. {
  132. return false;
  133. }
  134. setter.Call(receiver, new[] {value});
  135. }
  136. else
  137. {
  138. var newDesc = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable);
  139. return DefineOwnProperty(property, newDesc);
  140. }
  141. return true;
  142. }
  143. public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc)
  144. {
  145. if (_hasRestParameter)
  146. {
  147. // immutable
  148. return true;
  149. }
  150. EnsureInitialized();
  151. if (ParameterMap is not null)
  152. {
  153. var map = ParameterMap;
  154. var isMapped = map.GetOwnProperty(property);
  155. var allowed = base.DefineOwnProperty(property, desc);
  156. if (!allowed)
  157. {
  158. return false;
  159. }
  160. if (isMapped != PropertyDescriptor.Undefined)
  161. {
  162. if (desc.IsAccessorDescriptor())
  163. {
  164. map.Delete(property);
  165. }
  166. else
  167. {
  168. var descValue = desc.Value;
  169. if (descValue is not null && !descValue.IsUndefined())
  170. {
  171. map.Set(property, descValue, false);
  172. }
  173. if (desc.WritableSet && !desc.Writable)
  174. {
  175. map.Delete(property);
  176. }
  177. }
  178. }
  179. return true;
  180. }
  181. return base.DefineOwnProperty(property, desc);
  182. }
  183. public override bool Delete(JsValue property)
  184. {
  185. EnsureInitialized();
  186. if (ParameterMap is not null)
  187. {
  188. var map = ParameterMap;
  189. var isMapped = map.GetOwnProperty(property);
  190. var result = base.Delete(property);
  191. if (result && isMapped != PropertyDescriptor.Undefined)
  192. {
  193. map.Delete(property);
  194. }
  195. return result;
  196. }
  197. return base.Delete(property);
  198. }
  199. internal void Materialize()
  200. {
  201. if (_materialized)
  202. {
  203. // already done
  204. return;
  205. }
  206. _materialized = true;
  207. EnsureInitialized();
  208. var args = _args;
  209. var copiedArgs = new JsValue[args.Length];
  210. System.Array.Copy(args, copiedArgs, args.Length);
  211. _args = copiedArgs;
  212. _canReturnToPool = false;
  213. }
  214. internal void FunctionWasCalled()
  215. {
  216. // should no longer expose arguments which is special name
  217. ParameterMap = null;
  218. if (_canReturnToPool)
  219. {
  220. _engine._argumentsInstancePool.Return(this);
  221. // prevent double-return
  222. _canReturnToPool = false;
  223. }
  224. }
  225. }
  226. }