ArrayInstance.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using System.Runtime.CompilerServices;
  4. using Jint.Native.Object;
  5. using Jint.Native.Symbol;
  6. using Jint.Runtime;
  7. using Jint.Runtime.Descriptors;
  8. namespace Jint.Native.Array
  9. {
  10. public class ArrayInstance : ObjectInstance, IEnumerable<JsValue>
  11. {
  12. internal PropertyDescriptor _length;
  13. private const int MaxDenseArrayLength = 10_000_000;
  14. // we have dense and sparse, we usually can start with dense and fall back to sparse when necessary
  15. internal PropertyDescriptor[] _dense;
  16. private Dictionary<uint, PropertyDescriptor> _sparse;
  17. private ObjectChangeFlags _objectChangeFlags;
  18. public ArrayInstance(Engine engine, uint capacity = 0) : base(engine, ObjectClass.Array)
  19. {
  20. if (capacity > engine.Options.Constraints.MaxArraySize)
  21. {
  22. ThrowMaximumArraySizeReachedException(engine, capacity);
  23. }
  24. if (capacity < MaxDenseArrayLength)
  25. {
  26. _dense = capacity > 0 ? new PropertyDescriptor[capacity] : System.Array.Empty<PropertyDescriptor>();
  27. }
  28. else
  29. {
  30. _sparse = new Dictionary<uint, PropertyDescriptor>((int) (capacity <= 1024 ? capacity : 1024));
  31. }
  32. }
  33. /// <summary>
  34. /// Possibility to construct valid array fast, requires that supplied array does not have holes.
  35. /// </summary>
  36. public ArrayInstance(Engine engine, PropertyDescriptor[] items) : base(engine, ObjectClass.Array)
  37. {
  38. int length = 0;
  39. if (items == null || items.Length == 0)
  40. {
  41. _dense = System.Array.Empty<PropertyDescriptor>();
  42. length = 0;
  43. }
  44. else
  45. {
  46. _dense = items;
  47. length = items.Length;
  48. }
  49. _length = new PropertyDescriptor(length, PropertyFlag.OnlyWritable);
  50. }
  51. public ArrayInstance(Engine engine, Dictionary<uint, PropertyDescriptor> items) : base(engine, ObjectClass.Array)
  52. {
  53. _sparse = items;
  54. var length = items?.Count ?? 0;
  55. _length = new PropertyDescriptor(length, PropertyFlag.OnlyWritable);
  56. }
  57. public sealed override bool IsArrayLike => true;
  58. public sealed override bool IsArray() => true;
  59. internal sealed override bool HasOriginalIterator
  60. => ReferenceEquals(Get(GlobalSymbolRegistry.Iterator), _engine.Realm.Intrinsics.Array.PrototypeObject._originalIteratorFunction);
  61. /// <summary>
  62. /// Checks whether there have been changes to object prototype chain which could render fast access patterns impossible.
  63. /// </summary>
  64. internal bool CanUseFastAccess
  65. {
  66. get
  67. {
  68. if ((_objectChangeFlags & ObjectChangeFlags.NonDefaultDataDescriptorUsage) != 0)
  69. {
  70. // could be a mutating property for example, length might change, not safe anymore
  71. return false;
  72. }
  73. if (_prototype is not ArrayPrototype arrayPrototype
  74. || !ReferenceEquals(_prototype, _engine.Realm.Intrinsics.Array.PrototypeObject))
  75. {
  76. // somebody has switched prototype
  77. return false;
  78. }
  79. if ((arrayPrototype._objectChangeFlags & ObjectChangeFlags.ArrayIndex) != 0)
  80. {
  81. // maybe somebody moved integer property to prototype? not safe anymore
  82. return false;
  83. }
  84. if (arrayPrototype.Prototype is not ObjectPrototype arrayPrototypePrototype
  85. || !ReferenceEquals(arrayPrototypePrototype, _engine.Realm.Intrinsics.Array.PrototypeObject.Prototype))
  86. {
  87. return false;
  88. }
  89. return (arrayPrototypePrototype._objectChangeFlags & ObjectChangeFlags.ArrayIndex) == 0;
  90. }
  91. }
  92. public sealed override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc)
  93. {
  94. var isArrayIndex = IsArrayIndex(property, out var index);
  95. TrackChanges(property, desc, isArrayIndex);
  96. if (isArrayIndex)
  97. {
  98. return DefineOwnProperty(index, desc);
  99. }
  100. if (property == CommonProperties.Length)
  101. {
  102. var value = desc.Value;
  103. if (ReferenceEquals(value, null))
  104. {
  105. return base.DefineOwnProperty(CommonProperties.Length, desc);
  106. }
  107. var newLenDesc = new PropertyDescriptor(desc);
  108. uint newLen = TypeConverter.ToUint32(value);
  109. if (newLen != TypeConverter.ToNumber(value))
  110. {
  111. ExceptionHelper.ThrowRangeError(_engine.Realm);
  112. }
  113. var oldLenDesc = _length;
  114. var oldLen = (uint) TypeConverter.ToNumber(oldLenDesc.Value);
  115. newLenDesc.Value = newLen;
  116. if (newLen >= oldLen)
  117. {
  118. return base.DefineOwnProperty(CommonProperties.Length, newLenDesc);
  119. }
  120. if (!oldLenDesc.Writable)
  121. {
  122. return false;
  123. }
  124. bool newWritable;
  125. if (!newLenDesc.WritableSet || newLenDesc.Writable)
  126. {
  127. newWritable = true;
  128. }
  129. else
  130. {
  131. newWritable = false;
  132. newLenDesc.Writable = true;
  133. }
  134. var succeeded = base.DefineOwnProperty(CommonProperties.Length, newLenDesc);
  135. if (!succeeded)
  136. {
  137. return false;
  138. }
  139. var count = _dense?.Length ?? _sparse.Count;
  140. if (count < oldLen - newLen)
  141. {
  142. if (_dense != null)
  143. {
  144. for (uint keyIndex = 0; keyIndex < _dense.Length; ++keyIndex)
  145. {
  146. if (_dense[keyIndex] == null)
  147. {
  148. continue;
  149. }
  150. // is it the index of the array
  151. if (keyIndex >= newLen && keyIndex < oldLen)
  152. {
  153. var deleteSucceeded = Delete(keyIndex);
  154. if (!deleteSucceeded)
  155. {
  156. newLenDesc.Value = keyIndex + 1;
  157. if (!newWritable)
  158. {
  159. newLenDesc.Writable = false;
  160. }
  161. base.DefineOwnProperty(CommonProperties.Length, newLenDesc);
  162. return false;
  163. }
  164. }
  165. }
  166. }
  167. else
  168. {
  169. // in the case of sparse arrays, treat each concrete element instead of
  170. // iterating over all indexes
  171. var keys = new List<uint>(_sparse.Keys);
  172. var keysCount = keys.Count;
  173. for (var i = 0; i < keysCount; i++)
  174. {
  175. var keyIndex = keys[i];
  176. // is it the index of the array
  177. if (keyIndex >= newLen && keyIndex < oldLen)
  178. {
  179. var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex));
  180. if (!deleteSucceeded)
  181. {
  182. newLenDesc.Value = JsNumber.Create(keyIndex + 1);
  183. if (!newWritable)
  184. {
  185. newLenDesc.Writable = false;
  186. }
  187. base.DefineOwnProperty(CommonProperties.Length, newLenDesc);
  188. return false;
  189. }
  190. }
  191. }
  192. }
  193. }
  194. else
  195. {
  196. while (newLen < oldLen)
  197. {
  198. // algorithm as per the spec
  199. oldLen--;
  200. var deleteSucceeded = Delete(oldLen);
  201. if (!deleteSucceeded)
  202. {
  203. newLenDesc.Value = oldLen + 1;
  204. if (!newWritable)
  205. {
  206. newLenDesc.Writable = false;
  207. }
  208. base.DefineOwnProperty(CommonProperties.Length, newLenDesc);
  209. return false;
  210. }
  211. }
  212. }
  213. if (!newWritable)
  214. {
  215. base.DefineOwnProperty(CommonProperties.Length, new PropertyDescriptor(value: null, PropertyFlag.WritableSet));
  216. }
  217. return true;
  218. }
  219. return base.DefineOwnProperty(property, desc);
  220. }
  221. private bool DefineOwnProperty(uint index, PropertyDescriptor desc)
  222. {
  223. var oldLenDesc = _length;
  224. var oldLen = (uint) TypeConverter.ToNumber(oldLenDesc.Value);
  225. if (index >= oldLen && !oldLenDesc.Writable)
  226. {
  227. return false;
  228. }
  229. var succeeded = base.DefineOwnProperty(index, desc);
  230. if (!succeeded)
  231. {
  232. return false;
  233. }
  234. if (index >= oldLen)
  235. {
  236. oldLenDesc.Value = index + 1;
  237. base.DefineOwnProperty(CommonProperties.Length, oldLenDesc);
  238. }
  239. return true;
  240. }
  241. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  242. internal uint GetLength()
  243. {
  244. if (_length is null)
  245. {
  246. return 0;
  247. }
  248. return (uint) ((JsNumber) _length._value)._value;
  249. }
  250. protected sealed override void AddProperty(JsValue property, PropertyDescriptor descriptor)
  251. {
  252. if (property == CommonProperties.Length)
  253. {
  254. _length = descriptor;
  255. return;
  256. }
  257. base.AddProperty(property, descriptor);
  258. }
  259. protected sealed override bool TryGetProperty(JsValue property, out PropertyDescriptor descriptor)
  260. {
  261. if (property == CommonProperties.Length)
  262. {
  263. descriptor = _length;
  264. return _length != null;
  265. }
  266. return base.TryGetProperty(property, out descriptor);
  267. }
  268. public sealed override List<JsValue> GetOwnPropertyKeys(Types types = Types.None | Types.String | Types.Symbol)
  269. {
  270. if ((types & Types.String) == 0)
  271. {
  272. return base.GetOwnPropertyKeys(types);
  273. }
  274. var properties = new List<JsValue>(_dense?.Length ?? 0 + 1);
  275. if (_dense != null)
  276. {
  277. var length = System.Math.Min(_dense.Length, GetLength());
  278. for (var i = 0; i < length; i++)
  279. {
  280. if (_dense[i] != null)
  281. {
  282. properties.Add(JsString.Create(i));
  283. }
  284. }
  285. }
  286. else
  287. {
  288. foreach (var entry in _sparse)
  289. {
  290. properties.Add(JsString.Create(entry.Key));
  291. }
  292. }
  293. if (_length != null)
  294. {
  295. properties.Add(CommonProperties.Length);
  296. }
  297. properties.AddRange(base.GetOwnPropertyKeys(types));
  298. return properties;
  299. }
  300. public sealed override IEnumerable<KeyValuePair<JsValue, PropertyDescriptor>> GetOwnProperties()
  301. {
  302. if (_dense != null)
  303. {
  304. var length = System.Math.Min(_dense.Length, GetLength());
  305. for (var i = 0; i < length; i++)
  306. {
  307. if (_dense[i] != null)
  308. {
  309. yield return new KeyValuePair<JsValue, PropertyDescriptor>(TypeConverter.ToString(i), _dense[i]);
  310. }
  311. }
  312. }
  313. else
  314. {
  315. foreach (var entry in _sparse)
  316. {
  317. yield return new KeyValuePair<JsValue, PropertyDescriptor>(TypeConverter.ToString(entry.Key), entry.Value);
  318. }
  319. }
  320. if (_length != null)
  321. {
  322. yield return new KeyValuePair<JsValue, PropertyDescriptor>(CommonProperties.Length, _length);
  323. }
  324. foreach (var entry in base.GetOwnProperties())
  325. {
  326. yield return entry;
  327. }
  328. }
  329. public sealed override PropertyDescriptor GetOwnProperty(JsValue property)
  330. {
  331. if (property == CommonProperties.Length)
  332. {
  333. return _length ?? PropertyDescriptor.Undefined;
  334. }
  335. if (IsArrayIndex(property, out var index))
  336. {
  337. if (TryGetDescriptor(index, out var result))
  338. {
  339. return result;
  340. }
  341. return PropertyDescriptor.Undefined;
  342. }
  343. return base.GetOwnProperty(property);
  344. }
  345. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  346. private PropertyDescriptor GetOwnProperty(uint index)
  347. {
  348. return TryGetDescriptor(index, out var result)
  349. ? result
  350. : PropertyDescriptor.Undefined;
  351. }
  352. internal JsValue Get(uint index)
  353. {
  354. var prop = GetOwnProperty(index);
  355. if (prop == PropertyDescriptor.Undefined)
  356. {
  357. prop = Prototype?.GetProperty(JsString.Create(index)) ?? PropertyDescriptor.Undefined;
  358. }
  359. return UnwrapJsValue(prop);
  360. }
  361. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  362. private PropertyDescriptor GetProperty(uint index)
  363. {
  364. var prop = GetOwnProperty(index);
  365. if (prop != PropertyDescriptor.Undefined)
  366. {
  367. return prop;
  368. }
  369. return Prototype?.GetProperty(JsString.Create(index)) ?? PropertyDescriptor.Undefined;
  370. }
  371. public sealed override bool Set(JsValue property, JsValue value, JsValue receiver)
  372. {
  373. if (ReferenceEquals(receiver, this) && Extensible && IsArrayIndex(property, out var index))
  374. {
  375. if (TryGetDescriptor(index, out var descriptor))
  376. {
  377. if (descriptor.IsDefaultArrayValueDescriptor())
  378. {
  379. // fast path with direct write without allocations
  380. descriptor.Value = value;
  381. return true;
  382. }
  383. }
  384. else if (CanUseFastAccess)
  385. {
  386. // we know it's to be written to own array backing field as new value
  387. WriteArrayValue(index, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable));
  388. EnsureCorrectLength(index);
  389. return true;
  390. }
  391. }
  392. // slow path
  393. return base.Set(property, value, receiver);
  394. }
  395. protected internal sealed override void SetOwnProperty(JsValue property, PropertyDescriptor desc)
  396. {
  397. var isArrayIndex = IsArrayIndex(property, out var index);
  398. TrackChanges(property, desc, isArrayIndex);
  399. if (isArrayIndex)
  400. {
  401. WriteArrayValue(index, desc);
  402. }
  403. else if (property == CommonProperties.Length)
  404. {
  405. _length = desc;
  406. }
  407. else
  408. {
  409. base.SetOwnProperty(property, desc);
  410. }
  411. }
  412. private void TrackChanges(JsValue property, PropertyDescriptor desc, bool isArrayIndex)
  413. {
  414. EnsureInitialized();
  415. if (!desc.IsDefaultArrayValueDescriptor())
  416. {
  417. _objectChangeFlags |= ObjectChangeFlags.NonDefaultDataDescriptorUsage;
  418. }
  419. if (isArrayIndex)
  420. {
  421. _objectChangeFlags |= ObjectChangeFlags.ArrayIndex;
  422. }
  423. else
  424. {
  425. _objectChangeFlags |= property.IsSymbol() ? ObjectChangeFlags.Symbol : ObjectChangeFlags.Property;
  426. }
  427. }
  428. public sealed override bool HasOwnProperty(JsValue p)
  429. {
  430. if (IsArrayIndex(p, out var index))
  431. {
  432. return index < GetLength()
  433. && (_sparse == null || _sparse.ContainsKey(index))
  434. && (_dense == null || (index < (uint) _dense.Length && _dense[index] != null));
  435. }
  436. if (p == CommonProperties.Length)
  437. {
  438. return _length != null;
  439. }
  440. return base.HasOwnProperty(p);
  441. }
  442. public sealed override void RemoveOwnProperty(JsValue p)
  443. {
  444. if (IsArrayIndex(p, out var index))
  445. {
  446. Delete(index);
  447. }
  448. if (p == CommonProperties.Length)
  449. {
  450. _length = null;
  451. }
  452. base.RemoveOwnProperty(p);
  453. }
  454. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  455. internal static bool IsArrayIndex(JsValue p, out uint index)
  456. {
  457. if (p is JsNumber number)
  458. {
  459. var value = number._value;
  460. var intValue = (uint) value;
  461. index = intValue;
  462. return value == intValue && intValue != uint.MaxValue;
  463. }
  464. index = ParseArrayIndex(p.ToString());
  465. return index != uint.MaxValue;
  466. // 15.4 - Use an optimized version of the specification
  467. // return TypeConverter.ToString(index) == TypeConverter.ToString(p) && index != uint.MaxValue;
  468. }
  469. private static uint ParseArrayIndex(string p)
  470. {
  471. if (p.Length == 0)
  472. {
  473. return uint.MaxValue;
  474. }
  475. int d = p[0] - '0';
  476. if (d < 0 || d > 9)
  477. {
  478. return uint.MaxValue;
  479. }
  480. if (d == 0 && p.Length > 1)
  481. {
  482. // If p is a number that start with '0' and is not '0' then
  483. // its ToString representation can't be the same a p. This is
  484. // not a valid array index. '01' !== ToString(ToUInt32('01'))
  485. // http://www.ecma-international.org/ecma-262/5.1/#sec-15.4
  486. return uint.MaxValue;
  487. }
  488. if (p.Length > 1)
  489. {
  490. return StringAsIndex(d, p);
  491. }
  492. return (uint) d;
  493. }
  494. private static uint StringAsIndex(int d, string p)
  495. {
  496. ulong result = (uint) d;
  497. for (int i = 1; i < p.Length; i++)
  498. {
  499. d = p[i] - '0';
  500. if (d < 0 || d > 9)
  501. {
  502. return uint.MaxValue;
  503. }
  504. result = result * 10 + (uint) d;
  505. if (result >= uint.MaxValue)
  506. {
  507. return uint.MaxValue;
  508. }
  509. }
  510. return (uint) result;
  511. }
  512. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  513. internal void SetIndexValue(uint index, JsValue value, bool updateLength)
  514. {
  515. if (updateLength)
  516. {
  517. EnsureCorrectLength(index);
  518. }
  519. WriteArrayValue(index, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable));
  520. }
  521. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  522. private void EnsureCorrectLength(uint index)
  523. {
  524. var length = GetLength();
  525. if (index >= length)
  526. {
  527. SetLength(index + 1);
  528. }
  529. }
  530. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  531. internal void SetLength(uint length)
  532. {
  533. _length.Value = length;
  534. }
  535. internal uint GetSmallestIndex()
  536. {
  537. if (_dense != null)
  538. {
  539. return 0;
  540. }
  541. uint smallest = 0;
  542. // only try to help if collection reasonable small
  543. if (_sparse.Count > 0 && _sparse.Count < 100 && !_sparse.ContainsKey(0))
  544. {
  545. smallest = uint.MaxValue;
  546. foreach (var key in _sparse.Keys)
  547. {
  548. smallest = System.Math.Min(key, smallest);
  549. }
  550. }
  551. return smallest;
  552. }
  553. public bool TryGetValue(uint index, out JsValue value)
  554. {
  555. value = Undefined;
  556. if (!TryGetDescriptor(index, out var desc))
  557. {
  558. desc = GetProperty(JsString.Create(index));
  559. }
  560. return desc.TryGetValue(this, out value);
  561. }
  562. internal bool DeletePropertyOrThrow(uint index)
  563. {
  564. if (!Delete(index))
  565. {
  566. ExceptionHelper.ThrowTypeError(_engine.Realm);
  567. }
  568. return true;
  569. }
  570. internal bool Delete(uint index)
  571. {
  572. var desc = GetOwnProperty(index);
  573. if (desc == PropertyDescriptor.Undefined)
  574. {
  575. return true;
  576. }
  577. if (desc.Configurable)
  578. {
  579. DeleteAt(index);
  580. return true;
  581. }
  582. return false;
  583. }
  584. internal bool DeleteAt(uint index)
  585. {
  586. var temp = _dense;
  587. if (temp != null)
  588. {
  589. if (index < (uint) temp.Length)
  590. {
  591. temp[index] = null;
  592. return true;
  593. }
  594. }
  595. else
  596. {
  597. return _sparse.Remove(index);
  598. }
  599. return false;
  600. }
  601. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  602. private bool TryGetDescriptor(uint index, out PropertyDescriptor descriptor)
  603. {
  604. var temp = _dense;
  605. if (temp != null)
  606. {
  607. descriptor = null;
  608. if (index < (uint) temp.Length)
  609. {
  610. descriptor = temp[index];
  611. }
  612. return descriptor != null;
  613. }
  614. return _sparse.TryGetValue(index, out descriptor);
  615. }
  616. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  617. internal void WriteArrayValue(uint index, PropertyDescriptor desc)
  618. {
  619. // calculate eagerly so we know if we outgrow
  620. var newSize = _dense != null && index >= (uint) _dense.Length
  621. ? System.Math.Max(index, System.Math.Max(_dense.Length, 2)) * 2
  622. : 0;
  623. bool canUseDense = _dense != null
  624. && index < MaxDenseArrayLength
  625. && newSize < MaxDenseArrayLength
  626. && index < _dense.Length + 50; // looks sparse
  627. if (canUseDense)
  628. {
  629. if (index >= (uint) _dense.Length)
  630. {
  631. EnsureCapacity((uint) newSize);
  632. }
  633. _dense[index] = desc;
  634. }
  635. else
  636. {
  637. if (_dense != null)
  638. {
  639. ConvertToSparse();
  640. }
  641. _sparse[index] = desc;
  642. }
  643. }
  644. private void ConvertToSparse()
  645. {
  646. _sparse = new Dictionary<uint, PropertyDescriptor>(_dense.Length <= 1024 ? _dense.Length : 0);
  647. // need to move data
  648. for (uint i = 0; i < (uint) _dense.Length; ++i)
  649. {
  650. if (_dense[i] != null)
  651. {
  652. _sparse[i] = _dense[i];
  653. }
  654. }
  655. _dense = null;
  656. }
  657. internal void EnsureCapacity(uint capacity)
  658. {
  659. if (capacity > MaxDenseArrayLength || _dense is null || capacity <= (uint) _dense.Length)
  660. {
  661. return;
  662. }
  663. if (capacity > _engine.Options.Constraints.MaxArraySize)
  664. {
  665. ThrowMaximumArraySizeReachedException(_engine, capacity);
  666. }
  667. // need to grow
  668. var newArray = new PropertyDescriptor[capacity];
  669. System.Array.Copy(_dense, newArray, _dense.Length);
  670. _dense = newArray;
  671. }
  672. public IEnumerator<JsValue> GetEnumerator()
  673. {
  674. var length = GetLength();
  675. for (uint i = 0; i < length; i++)
  676. {
  677. TryGetValue(i, out var outValue);
  678. yield return outValue;
  679. }
  680. }
  681. IEnumerator IEnumerable.GetEnumerator()
  682. {
  683. return GetEnumerator();
  684. }
  685. internal uint Push(JsValue[] arguments)
  686. {
  687. var initialLength = GetLength();
  688. var newLength = initialLength + arguments.Length;
  689. // if we see that we are bringing more than normal growth algorithm handles, ensure capacity eagerly
  690. if (_dense != null
  691. && initialLength != 0
  692. && arguments.Length > initialLength * 2
  693. && newLength <= MaxDenseArrayLength)
  694. {
  695. EnsureCapacity((uint) newLength);
  696. }
  697. var canUseDirectIndexSet = _dense != null && newLength <= _dense.Length;
  698. double n = initialLength;
  699. foreach (var argument in arguments)
  700. {
  701. var desc = new PropertyDescriptor(argument, PropertyFlag.ConfigurableEnumerableWritable);
  702. if (canUseDirectIndexSet)
  703. {
  704. _dense[(uint) n] = desc;
  705. }
  706. else
  707. {
  708. WriteValueSlow(n, desc);
  709. }
  710. n++;
  711. }
  712. // check if we can set length fast without breaking ECMA specification
  713. if (n < uint.MaxValue && CanSetLength())
  714. {
  715. _length.Value = (uint) n;
  716. }
  717. else
  718. {
  719. if (!Set(CommonProperties.Length, newLength))
  720. {
  721. ExceptionHelper.ThrowTypeError(_engine.Realm);
  722. }
  723. }
  724. return (uint) n;
  725. }
  726. private bool CanSetLength()
  727. {
  728. if (!_length.IsAccessorDescriptor())
  729. {
  730. return _length.Writable;
  731. }
  732. var set = _length.Set;
  733. return set is not null && !set.IsUndefined();
  734. }
  735. [MethodImpl(MethodImplOptions.NoInlining)]
  736. private void WriteValueSlow(double n, PropertyDescriptor desc)
  737. {
  738. if (n < uint.MaxValue)
  739. {
  740. WriteArrayValue((uint) n, desc);
  741. }
  742. else
  743. {
  744. DefinePropertyOrThrow((uint) n, desc);
  745. }
  746. }
  747. internal ArrayInstance Map(JsValue[] arguments)
  748. {
  749. var callbackfn = arguments.At(0);
  750. var thisArg = arguments.At(1);
  751. var len = GetLength();
  752. var callable = GetCallable(callbackfn);
  753. var a = Engine.Realm.Intrinsics.Array.ArrayCreate(len);
  754. var args = _engine._jsValueArrayPool.RentArray(3);
  755. args[2] = this;
  756. for (uint k = 0; k < len; k++)
  757. {
  758. if (TryGetValue(k, out var kvalue))
  759. {
  760. args[0] = kvalue;
  761. args[1] = k;
  762. var mappedValue = callable.Call(thisArg, args);
  763. var desc = new PropertyDescriptor(mappedValue, PropertyFlag.ConfigurableEnumerableWritable);
  764. if (a._dense != null && k < (uint) a._dense.Length)
  765. {
  766. a._dense[k] = desc;
  767. }
  768. else
  769. {
  770. a.WriteArrayValue(k, desc);
  771. }
  772. }
  773. }
  774. _engine._jsValueArrayPool.ReturnArray(args);
  775. return a;
  776. }
  777. /// <inheritdoc />
  778. internal sealed override bool FindWithCallback(
  779. JsValue[] arguments,
  780. out uint index,
  781. out JsValue value,
  782. bool visitUnassigned,
  783. bool fromEnd = false)
  784. {
  785. var thisArg = arguments.At(1);
  786. var callbackfn = arguments.At(0);
  787. var callable = GetCallable(callbackfn);
  788. var len = GetLength();
  789. if (len == 0)
  790. {
  791. index = 0;
  792. value = Undefined;
  793. return false;
  794. }
  795. var args = _engine._jsValueArrayPool.RentArray(3);
  796. args[2] = this;
  797. if (!fromEnd)
  798. {
  799. for (uint k = 0; k < len; k++)
  800. {
  801. if (TryGetValue(k, out var kvalue) || visitUnassigned)
  802. {
  803. args[0] = kvalue;
  804. args[1] = k;
  805. var testResult = callable.Call(thisArg, args);
  806. if (TypeConverter.ToBoolean(testResult))
  807. {
  808. index = k;
  809. value = kvalue;
  810. return true;
  811. }
  812. }
  813. }
  814. }
  815. else
  816. {
  817. for (long k = len - 1; k >= 0; k--)
  818. {
  819. var idx = (uint) k;
  820. if (TryGetValue(idx, out var kvalue) || visitUnassigned)
  821. {
  822. args[0] = kvalue;
  823. args[1] = idx;
  824. var testResult = callable.Call(thisArg, args);
  825. if (TypeConverter.ToBoolean(testResult))
  826. {
  827. index = idx;
  828. value = kvalue;
  829. return true;
  830. }
  831. }
  832. }
  833. }
  834. _engine._jsValueArrayPool.ReturnArray(args);
  835. index = 0;
  836. value = Undefined;
  837. return false;
  838. }
  839. public sealed override uint Length => GetLength();
  840. internal sealed override bool IsIntegerIndexedArray => true;
  841. public JsValue this[uint index]
  842. {
  843. get
  844. {
  845. TryGetValue(index, out var kValue);
  846. return kValue;
  847. }
  848. }
  849. /// <summary>
  850. /// Fast path for concatenating sane-sized arrays, we assume size has been calculated.
  851. /// </summary>
  852. internal void CopyValues(ArrayInstance source, uint sourceStartIndex, uint targetStartIndex, uint length)
  853. {
  854. if (length == 0)
  855. {
  856. return;
  857. }
  858. var sourceDense = source._dense;
  859. if (sourceDense is not null)
  860. {
  861. EnsureCapacity((uint) sourceDense.LongLength);
  862. }
  863. var dense = _dense;
  864. if (dense != null && sourceDense != null
  865. && (uint) dense.Length >= targetStartIndex + length
  866. && dense[targetStartIndex] is null)
  867. {
  868. uint j = 0;
  869. for (uint i = sourceStartIndex; i < sourceStartIndex + length; ++i, j++)
  870. {
  871. var sourcePropertyDescriptor = i < (uint) sourceDense.Length && sourceDense[i] != null
  872. ? sourceDense[i]
  873. : source.GetProperty(i);
  874. dense[targetStartIndex + j] = sourcePropertyDescriptor?._value is not null
  875. ? new PropertyDescriptor(sourcePropertyDescriptor._value, PropertyFlag.ConfigurableEnumerableWritable)
  876. : null;
  877. }
  878. }
  879. else
  880. {
  881. // slower version
  882. for (uint k = sourceStartIndex; k < length; k++)
  883. {
  884. if (source.TryGetValue(k, out var subElement))
  885. {
  886. SetIndexValue(targetStartIndex, subElement, updateLength: false);
  887. }
  888. targetStartIndex++;
  889. }
  890. }
  891. }
  892. public sealed override string ToString()
  893. {
  894. // debugger can make things hard when evaluates computed values
  895. return "(" + (_length?._value.AsNumber() ?? 0) + ")[]";
  896. }
  897. private static void ThrowMaximumArraySizeReachedException(Engine engine, uint capacity)
  898. {
  899. ExceptionHelper.ThrowMemoryLimitExceededException(
  900. $"The array size {capacity} is larger than maximum allowed ({engine.Options.Constraints.MaxArraySize})"
  901. );
  902. }
  903. }
  904. internal static class ArrayPropertyDescriptorExtensions
  905. {
  906. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  907. internal static bool IsDefaultArrayValueDescriptor(this PropertyDescriptor propertyDescriptor)
  908. => propertyDescriptor.Flags == PropertyFlag.ConfigurableEnumerableWritable && propertyDescriptor.IsDataDescriptor();
  909. }
  910. }