|
@@ -1,23 +1,33 @@
|
|
|
using System.Collections.Generic;
|
|
|
-using System.Threading;
|
|
|
+using System.Runtime.CompilerServices;
|
|
|
using Jint.Native.Object;
|
|
|
using Jint.Runtime;
|
|
|
-using Jint.Runtime.Descriptors;
|
|
|
+using PropertyDescriptor = Jint.Runtime.Descriptors.PropertyDescriptor;
|
|
|
+using TypeConverter = Jint.Runtime.TypeConverter;
|
|
|
|
|
|
namespace Jint.Native.Array
|
|
|
{
|
|
|
public class ArrayInstance : ObjectInstance
|
|
|
{
|
|
|
- // cache key container for array iteration for less allocations
|
|
|
- private static readonly ThreadLocal<List<uint>> keyCache = new ThreadLocal<List<uint>>(() => new List<uint>());
|
|
|
-
|
|
|
private readonly Engine _engine;
|
|
|
- private readonly Dictionary<uint, PropertyDescriptor> _array = new Dictionary<uint, PropertyDescriptor>();
|
|
|
- private PropertyDescriptor _length;
|
|
|
|
|
|
- public ArrayInstance(Engine engine) : base(engine)
|
|
|
+ private const int MaxDenseArrayLength = 1024 * 10;
|
|
|
+
|
|
|
+ // we have dense and sparse, we usually can start with dense and fall back to sparse when necessary
|
|
|
+ private PropertyDescriptor[] _dense;
|
|
|
+ private Dictionary<uint, PropertyDescriptor> _sparse;
|
|
|
+
|
|
|
+ public ArrayInstance(Engine engine, uint capacity = 0) : base(engine)
|
|
|
{
|
|
|
_engine = engine;
|
|
|
+ if (capacity < MaxDenseArrayLength)
|
|
|
+ {
|
|
|
+ _dense = capacity > 0 ? new PropertyDescriptor[capacity] : System.Array.Empty<PropertyDescriptor>();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ _sparse = new Dictionary<uint, PropertyDescriptor>((int) (capacity <= 1024 ? capacity : 1024));
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public override string Class => "Array";
|
|
@@ -64,8 +74,7 @@ namespace Jint.Native.Array
|
|
|
public override bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError)
|
|
|
{
|
|
|
var oldLenDesc = GetOwnProperty("length");
|
|
|
- var oldLen = (uint)TypeConverter.ToNumber(oldLenDesc.Value);
|
|
|
- uint index;
|
|
|
+ var oldLen = (uint) TypeConverter.ToNumber(oldLenDesc.Value);
|
|
|
|
|
|
if (propertyName == "length")
|
|
|
{
|
|
@@ -84,8 +93,9 @@ namespace Jint.Native.Array
|
|
|
newLenDesc.Value = newLen;
|
|
|
if (newLen >= oldLen)
|
|
|
{
|
|
|
- return base.DefineOwnProperty("length", _length = newLenDesc, throwOnError);
|
|
|
+ return base.DefineOwnProperty("length", newLenDesc, throwOnError);
|
|
|
}
|
|
|
+
|
|
|
if (!oldLenDesc.Writable.Value)
|
|
|
{
|
|
|
if (throwOnError)
|
|
@@ -95,6 +105,7 @@ namespace Jint.Native.Array
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
+
|
|
|
bool newWritable;
|
|
|
if (!newLenDesc.Writable.HasValue || newLenDesc.Writable.Value)
|
|
|
{
|
|
@@ -106,41 +117,94 @@ namespace Jint.Native.Array
|
|
|
newLenDesc.Writable = true;
|
|
|
}
|
|
|
|
|
|
- var succeeded = base.DefineOwnProperty("length", _length = newLenDesc, throwOnError);
|
|
|
+ var succeeded = base.DefineOwnProperty("length", newLenDesc, throwOnError);
|
|
|
if (!succeeded)
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- // in the case of sparse arrays, treat each concrete element instead of
|
|
|
- // iterating over all indexes
|
|
|
+ int count = 0;
|
|
|
+ if (_dense != null)
|
|
|
+ {
|
|
|
+ for (int i = 0; i < _dense.Length; ++i)
|
|
|
+ {
|
|
|
+ if (_dense[i] != null)
|
|
|
+ {
|
|
|
+ count++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ count = _sparse.Count;
|
|
|
+ }
|
|
|
|
|
|
- if (_array.Count < oldLen - newLen)
|
|
|
+ if (count < oldLen - newLen)
|
|
|
{
|
|
|
- var keys = keyCache.Value;
|
|
|
- keys.Clear();
|
|
|
- keys.AddRange(_array.Keys);
|
|
|
- foreach (var keyIndex in keys)
|
|
|
+ if (_dense != null)
|
|
|
{
|
|
|
- // is it the index of the array
|
|
|
- if (keyIndex >= newLen && keyIndex < oldLen)
|
|
|
+ for (uint keyIndex = 0; keyIndex < _dense.Length; ++keyIndex)
|
|
|
{
|
|
|
- var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex), false);
|
|
|
- if (!deleteSucceeded)
|
|
|
+ if (_dense[keyIndex] == null)
|
|
|
{
|
|
|
- newLenDesc.Value = JsValue.FromInt(keyIndex + 1);
|
|
|
- if (!newWritable)
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // is it the index of the array
|
|
|
+ if (keyIndex >= newLen && keyIndex < oldLen)
|
|
|
+ {
|
|
|
+ var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex), false);
|
|
|
+ if (!deleteSucceeded)
|
|
|
{
|
|
|
- newLenDesc.Writable = false;
|
|
|
+ newLenDesc.Value = new JsValue(keyIndex + 1);
|
|
|
+ if (!newWritable)
|
|
|
+ {
|
|
|
+ newLenDesc.Writable = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ base.DefineOwnProperty("length", newLenDesc, false);
|
|
|
+
|
|
|
+ if (throwOnError)
|
|
|
+ {
|
|
|
+ throw new JavaScriptException(_engine.TypeError);
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
}
|
|
|
- base.DefineOwnProperty("length", _length = newLenDesc, false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // in the case of sparse arrays, treat each concrete element instead of
|
|
|
+ // iterating over all indexes
|
|
|
|
|
|
- if (throwOnError)
|
|
|
+ var keys = ArrayExecutionContext.Current.KeyCache;
|
|
|
+ keys.Clear();
|
|
|
+ keys.AddRange(_sparse.Keys);
|
|
|
+ foreach (var keyIndex in keys)
|
|
|
+ {
|
|
|
+ // is it the index of the array
|
|
|
+ if (keyIndex >= newLen && keyIndex < oldLen)
|
|
|
+ {
|
|
|
+ var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex), false);
|
|
|
+ if (!deleteSucceeded)
|
|
|
{
|
|
|
- throw new JavaScriptException(_engine.TypeError);
|
|
|
- }
|
|
|
+ newLenDesc.Value = JsValue.FromInt(keyIndex + 1);
|
|
|
+ if (!newWritable)
|
|
|
+ {
|
|
|
+ newLenDesc.Writable = false;
|
|
|
+ }
|
|
|
|
|
|
- return false;
|
|
|
+ base.DefineOwnProperty("length", newLenDesc, false);
|
|
|
+
|
|
|
+ if (throwOnError)
|
|
|
+ {
|
|
|
+ throw new JavaScriptException(_engine.TypeError);
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -159,7 +223,8 @@ namespace Jint.Native.Array
|
|
|
{
|
|
|
newLenDesc.Writable = false;
|
|
|
}
|
|
|
- base.DefineOwnProperty("length", _length = newLenDesc, false);
|
|
|
+
|
|
|
+ base.DefineOwnProperty("length", newLenDesc, false);
|
|
|
|
|
|
if (throwOnError)
|
|
|
{
|
|
@@ -170,13 +235,15 @@ namespace Jint.Native.Array
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
if (!newWritable)
|
|
|
{
|
|
|
DefineOwnProperty("length", new PropertyDescriptor(value: null, writable: false, enumerable: null, configurable: null), false);
|
|
|
}
|
|
|
+
|
|
|
return true;
|
|
|
}
|
|
|
- else if (IsArrayIndex(propertyName, out index))
|
|
|
+ else if (IsArrayIndex(propertyName, out var index))
|
|
|
{
|
|
|
if (index >= oldLen && !oldLenDesc.Writable.Value)
|
|
|
{
|
|
@@ -187,6 +254,7 @@ namespace Jint.Native.Array
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
+
|
|
|
var succeeded = base.DefineOwnProperty(propertyName, desc, false);
|
|
|
if (!succeeded)
|
|
|
{
|
|
@@ -197,11 +265,13 @@ namespace Jint.Native.Array
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
+
|
|
|
if (index >= oldLen)
|
|
|
{
|
|
|
oldLenDesc.Value = index + 1;
|
|
|
- base.DefineOwnProperty("length", _length = oldLenDesc, false);
|
|
|
+ base.DefineOwnProperty("length", oldLenDesc, false);
|
|
|
}
|
|
|
+
|
|
|
return true;
|
|
|
}
|
|
|
|
|
@@ -210,17 +280,30 @@ namespace Jint.Native.Array
|
|
|
|
|
|
public uint GetLength()
|
|
|
{
|
|
|
- return TypeConverter.ToUint32(_length.Value);
|
|
|
+ return GetLengthValue();
|
|
|
}
|
|
|
|
|
|
public override IEnumerable<KeyValuePair<string, PropertyDescriptor>> GetOwnProperties()
|
|
|
{
|
|
|
- foreach(var entry in _array)
|
|
|
+ if (_dense != null)
|
|
|
{
|
|
|
- yield return new KeyValuePair<string, PropertyDescriptor>(TypeConverter.ToString(entry.Key), entry.Value);
|
|
|
+ for (var i = 0; i < _dense.Length; i++)
|
|
|
+ {
|
|
|
+ if (_dense[i] != null)
|
|
|
+ {
|
|
|
+ yield return new KeyValuePair<string, PropertyDescriptor>(TypeConverter.ToString(i), _dense[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ foreach (var entry in _sparse)
|
|
|
+ {
|
|
|
+ yield return new KeyValuePair<string, PropertyDescriptor>(TypeConverter.ToString(entry.Key), entry.Value);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- foreach(var entry in base.GetOwnProperties())
|
|
|
+ foreach (var entry in base.GetOwnProperties())
|
|
|
{
|
|
|
yield return entry;
|
|
|
}
|
|
@@ -228,18 +311,14 @@ namespace Jint.Native.Array
|
|
|
|
|
|
public override PropertyDescriptor GetOwnProperty(string propertyName)
|
|
|
{
|
|
|
- uint index;
|
|
|
- if (IsArrayIndex(propertyName, out index))
|
|
|
+ if (IsArrayIndex(propertyName, out var index))
|
|
|
{
|
|
|
- PropertyDescriptor result;
|
|
|
- if (_array.TryGetValue(index, out result))
|
|
|
+ if (TryGetDescriptor(index, out var result))
|
|
|
{
|
|
|
return result;
|
|
|
}
|
|
|
- else
|
|
|
- {
|
|
|
- return PropertyDescriptor.Undefined;
|
|
|
- }
|
|
|
+
|
|
|
+ return PropertyDescriptor.Undefined;
|
|
|
}
|
|
|
|
|
|
return base.GetOwnProperty(propertyName);
|
|
@@ -247,28 +326,23 @@ namespace Jint.Native.Array
|
|
|
|
|
|
protected override void SetOwnProperty(string propertyName, PropertyDescriptor desc)
|
|
|
{
|
|
|
- uint index;
|
|
|
- if (IsArrayIndex(propertyName, out index))
|
|
|
+ if (IsArrayIndex(propertyName, out var index))
|
|
|
{
|
|
|
- _array[index] = desc;
|
|
|
+ WriteArrayValue(index, desc);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- if(propertyName == "length")
|
|
|
- {
|
|
|
- _length = desc;
|
|
|
- }
|
|
|
-
|
|
|
base.SetOwnProperty(propertyName, desc);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public override bool HasOwnProperty(string p)
|
|
|
{
|
|
|
- uint index;
|
|
|
- if (IsArrayIndex(p, out index))
|
|
|
+ if (IsArrayIndex(p, out var index))
|
|
|
{
|
|
|
- return index < GetLength() && _array.ContainsKey(index);
|
|
|
+ return index < GetLengthValue()
|
|
|
+ && (_sparse == null || _sparse.ContainsKey(index))
|
|
|
+ && (_dense == null || _dense[index] != null);
|
|
|
}
|
|
|
|
|
|
return base.HasOwnProperty(p);
|
|
@@ -277,14 +351,15 @@ namespace Jint.Native.Array
|
|
|
public override void RemoveOwnProperty(string p)
|
|
|
{
|
|
|
uint index;
|
|
|
- if(IsArrayIndex(p, out index))
|
|
|
+ if (IsArrayIndex(p, out index))
|
|
|
{
|
|
|
- _array.Remove(index);
|
|
|
+ DeleteAt(index);
|
|
|
}
|
|
|
|
|
|
base.RemoveOwnProperty(p);
|
|
|
}
|
|
|
|
|
|
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
private static bool IsArrayIndex(string p, out uint index)
|
|
|
{
|
|
|
index = ParseArrayIndex(p);
|
|
@@ -294,7 +369,8 @@ namespace Jint.Native.Array
|
|
|
// return TypeConverter.ToString(index) == TypeConverter.ToString(p) && index != uint.MaxValue;
|
|
|
}
|
|
|
|
|
|
- internal static uint ParseArrayIndex(string p)
|
|
|
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
+ private static uint ParseArrayIndex(string p)
|
|
|
{
|
|
|
int d = p[0] - '0';
|
|
|
|
|
@@ -303,7 +379,7 @@ namespace Jint.Native.Array
|
|
|
return uint.MaxValue;
|
|
|
}
|
|
|
|
|
|
- if(d == 0 && p.Length > 1)
|
|
|
+ if (d == 0 && p.Length > 1)
|
|
|
{
|
|
|
// If p is a number that start with '0' and is not '0' then
|
|
|
// its ToString representation can't be the same a p. This is
|
|
@@ -313,7 +389,7 @@ namespace Jint.Native.Array
|
|
|
return uint.MaxValue;
|
|
|
}
|
|
|
|
|
|
- ulong result = (uint)d;
|
|
|
+ ulong result = (uint) d;
|
|
|
|
|
|
for (int i = 1; i < p.Length; i++)
|
|
|
{
|
|
@@ -324,7 +400,7 @@ namespace Jint.Native.Array
|
|
|
return uint.MaxValue;
|
|
|
}
|
|
|
|
|
|
- result = result * 10 + (uint)d;
|
|
|
+ result = result * 10 + (uint) d;
|
|
|
|
|
|
if (result >= uint.MaxValue)
|
|
|
{
|
|
@@ -332,7 +408,146 @@ namespace Jint.Native.Array
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return (uint)result;
|
|
|
+ return (uint) result;
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void SetIndexValue(uint index, JsValue value, bool throwOnError)
|
|
|
+ {
|
|
|
+ var length = GetLengthValue();
|
|
|
+ if (index >= length)
|
|
|
+ {
|
|
|
+ var p = base.GetOwnProperty("length");
|
|
|
+ p.Value = index + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ WriteArrayValue(index, new PropertyDescriptor(value, true, true, true));
|
|
|
+ }
|
|
|
+
|
|
|
+ internal uint GetSmallestIndex()
|
|
|
+ {
|
|
|
+ if (_dense != null)
|
|
|
+ {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ uint smallest = 0;
|
|
|
+ // only try to help if collection reasonable small
|
|
|
+ if (_sparse.Count > 0 && _sparse.Count < 100 && !_sparse.ContainsKey(0))
|
|
|
+ {
|
|
|
+ smallest = uint.MaxValue;
|
|
|
+ foreach (var key in _sparse.Keys)
|
|
|
+ {
|
|
|
+ smallest = System.Math.Min(key, smallest);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return smallest;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool TryGetValue(uint index, out JsValue value)
|
|
|
+ {
|
|
|
+ value = JsValue.Undefined;
|
|
|
+
|
|
|
+ if (!TryGetDescriptor(index, out var desc)
|
|
|
+ || desc == null
|
|
|
+ || desc == PropertyDescriptor.Undefined
|
|
|
+ || (desc.Value == null && desc.Get == null))
|
|
|
+ {
|
|
|
+ desc = GetProperty(TypeConverter.ToString(index));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (desc != null && desc != PropertyDescriptor.Undefined)
|
|
|
+ {
|
|
|
+ bool success = desc.TryGetValue(this, out value);
|
|
|
+ return success;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void DeleteAt(uint index)
|
|
|
+ {
|
|
|
+ if (_dense != null)
|
|
|
+ {
|
|
|
+ if (index < _dense.Length)
|
|
|
+ {
|
|
|
+ _dense[index] = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ _sparse.Remove(index);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool TryGetDescriptor(uint index, out PropertyDescriptor descriptor)
|
|
|
+ {
|
|
|
+ if (_dense != null)
|
|
|
+ {
|
|
|
+ if (index >= _dense.Length)
|
|
|
+ {
|
|
|
+ descriptor = null;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ descriptor = _dense[index];
|
|
|
+ return descriptor != null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return _sparse.TryGetValue(index, out descriptor);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void WriteArrayValue(uint index, PropertyDescriptor desc)
|
|
|
+ {
|
|
|
+ // calculate eagerly so we know if we outgrow
|
|
|
+ var newSize = _dense != null && index >= _dense.Length
|
|
|
+ ? System.Math.Max(index, System.Math.Max(_dense.Length, 2)) * 2
|
|
|
+ : 0;
|
|
|
+
|
|
|
+ bool canUseDense = _dense != null
|
|
|
+ && index < MaxDenseArrayLength
|
|
|
+ && newSize < MaxDenseArrayLength
|
|
|
+ && index < _dense.Length + 50; // looks sparse
|
|
|
+
|
|
|
+ if (canUseDense)
|
|
|
+ {
|
|
|
+ if (index >= _dense.Length)
|
|
|
+ {
|
|
|
+ EnsureCapacity((uint) newSize);
|
|
|
+ }
|
|
|
+
|
|
|
+ _dense[index] = desc;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (_dense != null)
|
|
|
+ {
|
|
|
+ _sparse = new Dictionary<uint, PropertyDescriptor>(_dense.Length <= 1024 ? _dense.Length : 0);
|
|
|
+ // need to move data
|
|
|
+ for (uint i = 0; i < _dense.Length; ++i)
|
|
|
+ {
|
|
|
+ if (_dense[i] != null)
|
|
|
+ {
|
|
|
+ _sparse[i] = _dense[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ _dense = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ _sparse[index] = desc;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void EnsureCapacity(uint capacity)
|
|
|
+ {
|
|
|
+ if (capacity > _dense.Length)
|
|
|
+ {
|
|
|
+ // need to grow
|
|
|
+ var newArray = new PropertyDescriptor[capacity];
|
|
|
+ System.Array.Copy(_dense, newArray, _dense.Length);
|
|
|
+ _dense = newArray;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
-}
|
|
|
+}
|