ArrayInstance.cs 42 KB

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