ArrayInstance.cs 39 KB

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