diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json
index d710ab61bc..b5c25a499e 100644
--- a/Jint.Tests.Test262/Test262Harness.settings.json
+++ b/Jint.Tests.Test262/Test262Harness.settings.json
@@ -17,7 +17,6 @@
"regexp-lookbehind",
"regexp-unicode-property-escapes",
"regexp-v-flag",
- "resizable-arraybuffer",
"SharedArrayBuffer",
"tail-call-optimization",
"Temporal",
diff --git a/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs b/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs
index e2c49877f1..15333563b0 100644
--- a/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs
+++ b/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs
@@ -8,83 +8,115 @@
using Jint.Runtime.Descriptors;
using Jint.Runtime.Interop;
-namespace Jint.Native.ArrayBuffer
+namespace Jint.Native.ArrayBuffer;
+
+///
+/// https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-constructor
+///
+internal sealed class ArrayBufferConstructor : Constructor
{
+ private static readonly JsString _functionName = new("ArrayBuffer");
+
+ internal ArrayBufferConstructor(
+ Engine engine,
+ Realm realm,
+ FunctionPrototype functionPrototype,
+ ObjectPrototype objectPrototype)
+ : base(engine, realm, _functionName)
+ {
+ _prototype = functionPrototype;
+ PrototypeObject = new ArrayBufferPrototype(engine, this, objectPrototype);
+ _length = new PropertyDescriptor(1, PropertyFlag.Configurable);
+ _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
+ }
+
+ private ArrayBufferPrototype PrototypeObject { get; }
+
+ protected override void Initialize()
+ {
+ const PropertyFlag lengthFlags = PropertyFlag.Configurable;
+ var properties = new PropertyDictionary(1, checkExistingKeys: false)
+ {
+ ["isView"] = new PropertyDescriptor(new PropertyDescriptor(new ClrFunctionInstance(Engine, "isView", IsView, 1, lengthFlags), PropertyFlag.Configurable | PropertyFlag.Writable)),
+ };
+ SetProperties(properties);
+
+ var symbols = new SymbolDictionary(1)
+ {
+ [GlobalSymbolRegistry.Species] = new GetSetPropertyDescriptor(get: new ClrFunctionInstance(Engine, "get [Symbol.species]", Species, 0, lengthFlags), set: Undefined,PropertyFlag.Configurable),
+ };
+ SetSymbols(symbols);
+ }
+
///
- /// https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-constructor
+ /// https://tc39.es/ecma262/#sec-arraybuffer.isview
///
- internal sealed class ArrayBufferConstructor : Constructor
+ private static JsValue IsView(JsValue thisObject, JsValue[] arguments)
{
- private static readonly JsString _functionName = new("ArrayBuffer");
-
- internal ArrayBufferConstructor(
- Engine engine,
- Realm realm,
- FunctionPrototype functionPrototype,
- ObjectPrototype objectPrototype)
- : base(engine, realm, _functionName)
- {
- _prototype = functionPrototype;
- PrototypeObject = new ArrayBufferPrototype(engine, this, objectPrototype);
- _length = new PropertyDescriptor(1, PropertyFlag.Configurable);
- _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
- }
+ var arg = arguments.At(0);
+ return arg is JsDataView or JsTypedArray;
+ }
- private ArrayBufferPrototype PrototypeObject { get; }
+ ///
+ /// https://tc39.es/ecma262/#sec-get-arraybuffer-@@species
+ ///
+ private static JsValue Species(JsValue thisObject, JsValue[] arguments)
+ {
+ return thisObject;
+ }
- protected override void Initialize()
+ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
+ {
+ if (newTarget.IsUndefined())
{
- const PropertyFlag lengthFlags = PropertyFlag.Configurable;
- var properties = new PropertyDictionary(1, checkExistingKeys: false)
- {
- ["isView"] = new PropertyDescriptor(new PropertyDescriptor(new ClrFunctionInstance(Engine, "isView", IsView, 1, lengthFlags), PropertyFlag.Configurable | PropertyFlag.Writable)),
- };
- SetProperties(properties);
-
- var symbols = new SymbolDictionary(1)
- {
- [GlobalSymbolRegistry.Species] = new GetSetPropertyDescriptor(get: new ClrFunctionInstance(Engine, "get [Symbol.species]", Species, 0, lengthFlags), set: Undefined,PropertyFlag.Configurable),
- };
- SetSymbols(symbols);
+ ExceptionHelper.ThrowTypeError(_realm);
}
- ///
- /// https://tc39.es/ecma262/#sec-arraybuffer.isview
- ///
- private static JsValue IsView(JsValue thisObject, JsValue[] arguments)
+ var length = arguments.At(0);
+ var options = arguments.At(1);
+
+ var byteLength = TypeConverter.ToIndex(_realm, length);
+ var requestedMaxByteLength = GetArrayBufferMaxByteLengthOption(options);
+ return AllocateArrayBuffer(newTarget, byteLength, requestedMaxByteLength);
+ }
+
+ ///
+ /// https://tc39.es/ecma262/#sec-getarraybuffermaxbytelengthoption
+ ///
+ private uint? GetArrayBufferMaxByteLengthOption(JsValue options)
+ {
+ if (options is not ObjectInstance)
{
- var arg = arguments.At(0);
- return arg is JsDataView or JsTypedArray;
+ return null;
}
- ///
- /// https://tc39.es/ecma262/#sec-get-arraybuffer-@@species
- ///
- private static JsValue Species(JsValue thisObject, JsValue[] arguments)
+ var maxByteLength = options.Get("maxByteLength");
+ if (maxByteLength.IsUndefined())
{
- return thisObject;
+ return null;
}
- public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
- {
- if (newTarget.IsUndefined())
- {
- ExceptionHelper.ThrowTypeError(_realm);
- }
+ return TypeConverter.ToIndex(_realm, maxByteLength);
+ }
- var byteLength = TypeConverter.ToIndex(_realm, arguments.At(0));
- return AllocateArrayBuffer(newTarget, byteLength);
- }
+ ///
+ /// https://tc39.es/ecma262/#sec-allocatearraybuffer
+ ///
+ internal JsArrayBuffer AllocateArrayBuffer(JsValue constructor, ulong byteLength, uint? maxByteLength = null)
+ {
+ var allocatingResizableBuffer = maxByteLength != null;
- internal JsArrayBuffer AllocateArrayBuffer(JsValue constructor, ulong byteLength)
+ if (allocatingResizableBuffer && byteLength > maxByteLength)
{
- var obj = OrdinaryCreateFromConstructor(
- constructor,
- static intrinsics => intrinsics.ArrayBuffer.PrototypeObject,
- static (engine, realm, state) => new JsArrayBuffer(engine, (ulong) state!._value),
- JsNumber.Create(byteLength));
-
- return obj;
+ ExceptionHelper.ThrowRangeError(_realm);
}
+
+ var obj = OrdinaryCreateFromConstructor(
+ constructor,
+ static intrinsics => intrinsics.ArrayBuffer.PrototypeObject,
+ static (engine, _, state) => new JsArrayBuffer(engine, state!.Item1, state.Item2),
+ new Tuple(byteLength, maxByteLength));
+
+ return obj;
}
}
diff --git a/Jint/Native/ArrayBuffer/ArrayBufferOrder.cs b/Jint/Native/ArrayBuffer/ArrayBufferOrder.cs
index 76a95ee328..799cfd1fef 100644
--- a/Jint/Native/ArrayBuffer/ArrayBufferOrder.cs
+++ b/Jint/Native/ArrayBuffer/ArrayBufferOrder.cs
@@ -3,6 +3,6 @@ namespace Jint.Native.ArrayBuffer;
internal enum ArrayBufferOrder
{
Init,
- SecCst,
+ SeqCst,
Unordered
}
diff --git a/Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs b/Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs
index d89f20738e..b99d7c6a87 100644
--- a/Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs
+++ b/Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs
@@ -7,168 +7,242 @@
using Jint.Runtime.Descriptors;
using Jint.Runtime.Interop;
-namespace Jint.Native.ArrayBuffer
+namespace Jint.Native.ArrayBuffer;
+
+///
+/// https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-prototype-object
+///
+internal sealed class ArrayBufferPrototype : Prototype
{
+ private readonly ArrayBufferConstructor _constructor;
+
+ internal ArrayBufferPrototype(
+ Engine engine,
+ ArrayBufferConstructor constructor,
+ ObjectPrototype objectPrototype) : base(engine, engine.Realm)
+ {
+ _prototype = objectPrototype;
+ _constructor = constructor;
+ }
+
+ protected override void Initialize()
+ {
+ const PropertyFlag lengthFlags = PropertyFlag.Configurable;
+ var properties = new PropertyDictionary(4, checkExistingKeys: false)
+ {
+ ["byteLength"] = new GetSetPropertyDescriptor(new ClrFunctionInstance(_engine, "get byteLength", ByteLength, 0, lengthFlags), Undefined, PropertyFlag.Configurable),
+ [KnownKeys.Constructor] = new PropertyDescriptor(_constructor, PropertyFlag.NonEnumerable),
+ ["detached"] = new GetSetPropertyDescriptor(new ClrFunctionInstance(_engine, "get detached", Detached, 0, lengthFlags), Undefined, PropertyFlag.Configurable),
+ ["maxByteLength"] = new GetSetPropertyDescriptor(new ClrFunctionInstance(_engine, "get maxByteLength", MaxByteLength, 0, lengthFlags), Undefined, PropertyFlag.Configurable),
+ ["resizable"] = new GetSetPropertyDescriptor(new ClrFunctionInstance(_engine, "get resizable", Resizable, 0, lengthFlags), Undefined, PropertyFlag.Configurable),
+ ["resize"] = new PropertyDescriptor(new ClrFunctionInstance(_engine, "resize", Resize, 1, lengthFlags), PropertyFlag.NonEnumerable),
+ ["slice"] = new PropertyDescriptor(new ClrFunctionInstance(_engine, "slice", Slice, 2, lengthFlags), PropertyFlag.NonEnumerable)
+ };
+ SetProperties(properties);
+
+ var symbols = new SymbolDictionary(1) { [GlobalSymbolRegistry.ToStringTag] = new PropertyDescriptor("ArrayBuffer", PropertyFlag.Configurable) };
+ SetSymbols(symbols);
+ }
+
+ private JsValue Detached(JsValue thisObject, JsValue[] arguments)
+ {
+ var o = thisObject as JsArrayBuffer;
+ if (o is null)
+ {
+ ExceptionHelper.ThrowTypeError(_realm, "Method ArrayBuffer.prototype.detached called on incompatible receiver " + thisObject);
+ }
+
+ if (o.IsSharedArrayBuffer)
+ {
+ ExceptionHelper.ThrowTypeError(_realm);
+ }
+
+ return o.IsDetachedBuffer;
+ }
+
+ ///
+ /// https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.maxbytelength
+ ///
+ private JsValue MaxByteLength(JsValue thisObject, JsValue[] arguments)
+ {
+ var o = thisObject as JsArrayBuffer;
+ if (o is null)
+ {
+ ExceptionHelper.ThrowTypeError(_realm, "Method ArrayBuffer.prototype.maxByteLength called on incompatible receiver " + thisObject);
+ }
+
+ if (o.IsSharedArrayBuffer)
+ {
+ ExceptionHelper.ThrowTypeError(_realm);
+ }
+
+ if (o.IsDetachedBuffer)
+ {
+ return JsNumber.PositiveZero;
+ }
+
+ long length = o.IsFixedLengthArrayBuffer
+ ? o.ArrayBufferByteLength
+ : o._arrayBufferMaxByteLength.GetValueOrDefault();
+
+ return length;
+ }
+
///
- /// https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-prototype-object
+ /// https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.resizable
///
- internal sealed class ArrayBufferPrototype : Prototype
+ private JsValue Resizable(JsValue thisObject, JsValue[] arguments)
{
- private readonly ArrayBufferConstructor _constructor;
+ var o = thisObject as JsArrayBuffer;
+ if (o is null)
+ {
+ ExceptionHelper.ThrowTypeError(_realm, "Method ArrayBuffer.prototype.resizable called on incompatible receiver " + thisObject);
+ }
- internal ArrayBufferPrototype(
- Engine engine,
- ArrayBufferConstructor constructor,
- ObjectPrototype objectPrototype) : base(engine, engine.Realm)
- {
- _prototype = objectPrototype;
- _constructor = constructor;
- }
-
- protected override void Initialize()
- {
- const PropertyFlag lengthFlags = PropertyFlag.Configurable;
- var properties = new PropertyDictionary(4, checkExistingKeys: false)
- {
- ["byteLength"] = new GetSetPropertyDescriptor(new ClrFunctionInstance(_engine, "get byteLength", ByteLength, 0, lengthFlags), Undefined, PropertyFlag.Configurable),
- ["constructor"] = new PropertyDescriptor(_constructor, PropertyFlag.NonEnumerable),
- ["detached"] = new GetSetPropertyDescriptor(new ClrFunctionInstance(_engine, "get detached", Detached, 0, lengthFlags), Undefined, PropertyFlag.Configurable),
- ["slice"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "slice", Slice, 2, lengthFlags), PropertyFlag.Configurable | PropertyFlag.Writable)
- };
- SetProperties(properties);
-
- var symbols = new SymbolDictionary(1)
- {
- [GlobalSymbolRegistry.ToStringTag] = new PropertyDescriptor("ArrayBuffer", PropertyFlag.Configurable)
- };
- SetSymbols(symbols);
- }
-
- private JsValue Detached(JsValue thisObject, JsValue[] arguments)
- {
- var o = thisObject as JsArrayBuffer;
- if (o is null)
- {
- ExceptionHelper.ThrowTypeError(_realm, "Method ArrayBuffer.prototype.detached called on incompatible receiver " + thisObject);
- }
-
- if (o.IsSharedArrayBuffer)
- {
- ExceptionHelper.ThrowTypeError(_realm);
- }
-
- return o.IsDetachedBuffer;
- }
-
- ///
- /// https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.bytelength
- ///
- private JsValue ByteLength(JsValue thisObject, JsValue[] arguments)
- {
- var o = thisObject as JsArrayBuffer;
- if (o is null)
- {
- ExceptionHelper.ThrowTypeError(_realm, "Method ArrayBuffer.prototype.byteLength called on incompatible receiver " + thisObject);
- }
-
- if (o.IsSharedArrayBuffer)
- {
- ExceptionHelper.ThrowTypeError(_realm);
- }
-
- if (o.IsDetachedBuffer)
- {
- return JsNumber.PositiveZero;
- }
-
- return JsNumber.Create(o.ArrayBufferByteLength);
- }
-
- ///
- /// https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice
- ///
- private JsValue Slice(JsValue thisObject, JsValue[] arguments)
- {
- var o = thisObject as JsArrayBuffer;
- if (o is null)
- {
- ExceptionHelper.ThrowTypeError(_realm, "Method ArrayBuffer.prototype.slice called on incompatible receiver " + thisObject);
- }
-
- if (o.IsSharedArrayBuffer)
- {
- ExceptionHelper.ThrowTypeError(_realm);
- }
-
- o.AssertNotDetached();
-
- var start = arguments.At(0);
- var end = arguments.At(1);
-
- var len = o.ArrayBufferByteLength;
- var relativeStart = TypeConverter.ToIntegerOrInfinity(start);
- var first = relativeStart switch
- {
- double.NegativeInfinity => 0,
- < 0 => (int) System.Math.Max(len + relativeStart, 0),
- _ => (int) System.Math.Min(relativeStart, len)
- };
-
- double relativeEnd;
- if (end.IsUndefined())
- {
- relativeEnd = len;
- }
- else
- {
- relativeEnd = TypeConverter.ToIntegerOrInfinity(end);
- }
-
- var final = relativeEnd switch
- {
- double.NegativeInfinity => 0,
- < 0 => (int) System.Math.Max(len + relativeEnd, 0),
- _ => (int) System.Math.Min(relativeEnd, len)
- };
-
- var newLen = System.Math.Max(final - first, 0);
- var ctor = SpeciesConstructor(o, _realm.Intrinsics.ArrayBuffer);
- var bufferInstance = Construct(ctor, new JsValue[] { JsNumber.Create(newLen) }) as JsArrayBuffer;
-
- if (bufferInstance is null)
- {
- ExceptionHelper.ThrowTypeError(_realm);
- }
- if (bufferInstance.IsSharedArrayBuffer)
- {
- ExceptionHelper.ThrowTypeError(_realm);
- }
- if (bufferInstance.IsDetachedBuffer)
- {
- ExceptionHelper.ThrowTypeError(_realm);
- }
-
- if (ReferenceEquals(bufferInstance, o))
- {
- ExceptionHelper.ThrowTypeError(_realm);
- }
-
- if (bufferInstance.ArrayBufferByteLength < newLen)
- {
- ExceptionHelper.ThrowTypeError(_realm);
- }
-
- // NOTE: Side-effects of the above steps may have detached O.
-
- if (bufferInstance.IsDetachedBuffer)
- {
- ExceptionHelper.ThrowTypeError(_realm);
- }
-
- var fromBuf = o.ArrayBufferData;
- var toBuf = bufferInstance.ArrayBufferData;
- System.Array.Copy(fromBuf!, first, toBuf!, 0, newLen);
- return bufferInstance;
+ if (o.IsSharedArrayBuffer)
+ {
+ ExceptionHelper.ThrowTypeError(_realm);
}
+
+ return !o.IsFixedLengthArrayBuffer;
+ }
+
+ ///
+ /// https://tc39.es/ecma262/#sec-arraybuffer.prototype.resize
+ ///
+ private JsValue Resize(JsValue thisObject, JsValue[] arguments)
+ {
+ var o = thisObject as JsArrayBuffer;
+ if (o is null)
+ {
+ ExceptionHelper.ThrowTypeError(_realm, "Method ArrayBuffer.prototype.resize called on incompatible receiver " + thisObject);
+ }
+
+ if (o.IsSharedArrayBuffer)
+ {
+ ExceptionHelper.ThrowTypeError(_realm);
+ }
+
+ var newLength = arguments.At(0);
+ var newByteLength = TypeConverter.ToIndex(_realm, newLength);
+
+ o.AssertNotDetached();
+
+ o.Resize(newByteLength);
+
+ return Undefined;
+ }
+
+ ///
+ /// https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.bytelength
+ ///
+ private JsValue ByteLength(JsValue thisObject, JsValue[] arguments)
+ {
+ var o = thisObject as JsArrayBuffer;
+ if (o is null)
+ {
+ ExceptionHelper.ThrowTypeError(_realm, "Method ArrayBuffer.prototype.byteLength called on incompatible receiver " + thisObject);
+ }
+
+ if (o.IsSharedArrayBuffer)
+ {
+ ExceptionHelper.ThrowTypeError(_realm);
+ }
+
+ if (o.IsDetachedBuffer)
+ {
+ return JsNumber.PositiveZero;
+ }
+
+ return JsNumber.Create(o.ArrayBufferByteLength);
+ }
+
+ ///
+ /// https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice
+ ///
+ private JsValue Slice(JsValue thisObject, JsValue[] arguments)
+ {
+ var o = thisObject as JsArrayBuffer;
+ if (o is null)
+ {
+ ExceptionHelper.ThrowTypeError(_realm, "Method ArrayBuffer.prototype.slice called on incompatible receiver " + thisObject);
+ }
+
+ if (o.IsSharedArrayBuffer)
+ {
+ ExceptionHelper.ThrowTypeError(_realm);
+ }
+
+ o.AssertNotDetached();
+
+ var start = arguments.At(0);
+ var end = arguments.At(1);
+
+ var len = o.ArrayBufferByteLength;
+ var relativeStart = TypeConverter.ToIntegerOrInfinity(start);
+ var first = relativeStart switch
+ {
+ double.NegativeInfinity => 0,
+ < 0 => (int) System.Math.Max(len + relativeStart, 0),
+ _ => (int) System.Math.Min(relativeStart, len)
+ };
+
+ double relativeEnd;
+ if (end.IsUndefined())
+ {
+ relativeEnd = len;
+ }
+ else
+ {
+ relativeEnd = TypeConverter.ToIntegerOrInfinity(end);
+ }
+
+ var final = relativeEnd switch
+ {
+ double.NegativeInfinity => 0,
+ < 0 => (int) System.Math.Max(len + relativeEnd, 0),
+ _ => (int) System.Math.Min(relativeEnd, len)
+ };
+
+ var newLen = System.Math.Max(final - first, 0);
+ var ctor = SpeciesConstructor(o, _realm.Intrinsics.ArrayBuffer);
+ var bufferInstance = Construct(ctor, new JsValue[] { JsNumber.Create(newLen) }) as JsArrayBuffer;
+
+ if (bufferInstance is null)
+ {
+ ExceptionHelper.ThrowTypeError(_realm);
+ }
+
+ if (bufferInstance.IsSharedArrayBuffer)
+ {
+ ExceptionHelper.ThrowTypeError(_realm);
+ }
+
+ if (bufferInstance.IsDetachedBuffer)
+ {
+ ExceptionHelper.ThrowTypeError(_realm);
+ }
+
+ if (ReferenceEquals(bufferInstance, o))
+ {
+ ExceptionHelper.ThrowTypeError(_realm);
+ }
+
+ if (bufferInstance.ArrayBufferByteLength < newLen)
+ {
+ ExceptionHelper.ThrowTypeError(_realm);
+ }
+
+ // NOTE: Side-effects of the above steps may have detached O.
+
+ if (bufferInstance.IsDetachedBuffer)
+ {
+ ExceptionHelper.ThrowTypeError(_realm);
+ }
+
+ var fromBuf = o.ArrayBufferData;
+ var toBuf = bufferInstance.ArrayBufferData;
+ System.Array.Copy(fromBuf!, first, toBuf!, 0, newLen);
+ return bufferInstance;
}
}
diff --git a/Jint/Native/ArrayBuffer/JsArrayBuffer.cs b/Jint/Native/ArrayBuffer/JsArrayBuffer.cs
index 1c84164be5..d6ad752cdd 100644
--- a/Jint/Native/ArrayBuffer/JsArrayBuffer.cs
+++ b/Jint/Native/ArrayBuffer/JsArrayBuffer.cs
@@ -13,12 +13,16 @@ public sealed class JsArrayBuffer : ObjectInstance
private readonly byte[] _workBuffer = new byte[8];
private byte[]? _arrayBufferData;
+ internal readonly uint? _arrayBufferMaxByteLength;
+
private readonly JsValue _arrayBufferDetachKey = Undefined;
internal JsArrayBuffer(
Engine engine,
- ulong byteLength) : base(engine)
+ ulong byteLength,
+ uint? arrayBufferMaxByteLength = null) : base(engine)
{
+ _arrayBufferMaxByteLength = arrayBufferMaxByteLength;
var block = byteLength > 0 ? CreateByteDataBlock(byteLength) : System.Array.Empty();
_arrayBufferData = block;
}
@@ -37,6 +41,9 @@ private byte[] CreateByteDataBlock(ulong byteLength)
internal byte[]? ArrayBufferData => _arrayBufferData;
internal bool IsDetachedBuffer => _arrayBufferData is null;
+
+ internal bool IsFixedLengthArrayBuffer => _arrayBufferMaxByteLength is null;
+
#pragma warning disable CA1822
internal bool IsSharedArrayBuffer => false; // TODO SharedArrayBuffer
#pragma warning restore CA1822
@@ -308,6 +315,26 @@ private byte[] NumericToRawBytes(TypedArrayElementType type, TypedArrayValue val
return rawBytes;
}
+ internal void Resize(uint newByteLength)
+ {
+ if (_arrayBufferMaxByteLength is null)
+ {
+ ExceptionHelper.ThrowTypeError(_engine.Realm);
+ }
+
+ if (newByteLength > _arrayBufferMaxByteLength)
+ {
+ ExceptionHelper.ThrowRangeError(_engine.Realm);
+ }
+
+ var oldBlock = _arrayBufferData ?? System.Array.Empty();
+ var newBlock = CreateByteDataBlock(newByteLength);
+ var copyLength = System.Math.Min(newByteLength, ArrayBufferByteLength);
+
+ System.Array.Copy(oldBlock, newBlock, copyLength);
+ _arrayBufferData = newBlock;
+ }
+
internal void AssertNotDetached()
{
if (IsDetachedBuffer)
diff --git a/Jint/Native/DataView/DataViewConstructor.cs b/Jint/Native/DataView/DataViewConstructor.cs
index 5c2863b1df..e6b90b8001 100644
--- a/Jint/Native/DataView/DataViewConstructor.cs
+++ b/Jint/Native/DataView/DataViewConstructor.cs
@@ -1,6 +1,7 @@
using Jint.Native.ArrayBuffer;
using Jint.Native.Function;
using Jint.Native.Object;
+using Jint.Native.TypedArray;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
@@ -57,10 +58,18 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
ExceptionHelper.ThrowRangeError(_realm, "Start offset " + offset + " is outside the bounds of the buffer");
}
+ var bufferIsFixedLength = buffer.IsFixedLengthArrayBuffer;
uint viewByteLength;
if (byteLength.IsUndefined())
{
- viewByteLength = bufferByteLength - offset;
+ if (bufferIsFixedLength)
+ {
+ viewByteLength = bufferByteLength - offset;
+ }
+ else
+ {
+ viewByteLength = JsTypedArray.LengthAuto;
+ }
}
else
{
@@ -81,6 +90,20 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
ExceptionHelper.ThrowTypeError(_realm);
}
+ bufferByteLength = (uint) buffer.ArrayBufferByteLength;
+ if (offset > bufferByteLength)
+ {
+ ExceptionHelper.ThrowRangeError(_realm, "Invalid DataView offset");
+ }
+
+ if (!byteLength.IsUndefined())
+ {
+ if (offset + viewByteLength > bufferByteLength)
+ {
+ ExceptionHelper.ThrowRangeError(_realm, "Invalid DataView length");
+ }
+ }
+
o._viewedArrayBuffer = buffer;
o._byteLength = viewByteLength;
o._byteOffset = offset;
diff --git a/Jint/Native/DataView/DataViewPrototype.cs b/Jint/Native/DataView/DataViewPrototype.cs
index 342e539af7..563caa28ac 100644
--- a/Jint/Native/DataView/DataViewPrototype.cs
+++ b/Jint/Native/DataView/DataViewPrototype.cs
@@ -92,10 +92,16 @@ private JsValue ByteLength(JsValue thisObject, JsValue[] arguments)
ExceptionHelper.ThrowTypeError(_realm, "Method get DataView.prototype.byteLength called on incompatible receiver " + thisObject);
}
+ var viewRecord = MakeDataViewWithBufferWitnessRecord(o, ArrayBufferOrder.SeqCst);
+ if (viewRecord.IsViewOutOfBounds)
+ {
+ ExceptionHelper.ThrowTypeError(_realm, "Offset is outside the bounds of the DataView");
+ }
+
var buffer = o._viewedArrayBuffer!;
buffer.AssertNotDetached();
- return JsNumber.Create(o._byteLength);
+ return JsNumber.Create(viewRecord.ViewByteLength);
}
///
@@ -109,6 +115,12 @@ private JsValue ByteOffset(JsValue thisObject, JsValue[] arguments)
ExceptionHelper.ThrowTypeError(_realm, "Method get DataView.prototype.byteOffset called on incompatible receiver " + thisObject);
}
+ var viewRecord = MakeDataViewWithBufferWitnessRecord(o, ArrayBufferOrder.SeqCst);
+ if (viewRecord.IsViewOutOfBounds)
+ {
+ ExceptionHelper.ThrowTypeError(_realm, "Offset is outside the bounds of the DataView");
+ }
+
var buffer = o._viewedArrayBuffer!;
buffer.AssertNotDetached();
@@ -224,10 +236,10 @@ private JsValue GetViewValue(
JsValue isLittleEndian,
TypedArrayElementType type)
{
- var dataView = view as JsDataView;
- if (dataView is null)
+ if (view is not JsDataView dataView)
{
ExceptionHelper.ThrowTypeError(_realm, "Method called on incompatible receiver " + view);
+ return Undefined;
}
var getIndex = (int) TypeConverter.ToIndex(_realm, requestIndex);
@@ -237,7 +249,13 @@ private JsValue GetViewValue(
buffer.AssertNotDetached();
var viewOffset = dataView._byteOffset;
- var viewSize = dataView._byteLength;
+ var viewRecord = MakeDataViewWithBufferWitnessRecord(dataView, ArrayBufferOrder.Unordered);
+ if (viewRecord.IsViewOutOfBounds)
+ {
+ ExceptionHelper.ThrowTypeError(_realm, "Offset is outside the bounds of the DataView");
+ }
+
+ var viewSize = viewRecord.ViewByteLength;
var elementSize = type.GetElementSize();
if (getIndex + elementSize > viewSize)
{
@@ -245,7 +263,82 @@ private JsValue GetViewValue(
}
var bufferIndex = (int) (getIndex + viewOffset);
- return buffer.GetValueFromBuffer(bufferIndex, type, false, ArrayBufferOrder.Unordered, isLittleEndianBoolean).ToJsValue();
+ return buffer.GetValueFromBuffer(bufferIndex, type, isTypedArray: false, ArrayBufferOrder.Unordered, isLittleEndianBoolean).ToJsValue();
+ }
+
+ internal readonly record struct DataViewWithBufferWitnessRecord(JsDataView Object, int CachedBufferByteLength)
+ {
+ ///
+ /// https://tc39.es/ecma262/#sec-isviewoutofbounds
+ ///
+ public bool IsViewOutOfBounds
+ {
+ get
+ {
+ var view = Object;
+ var bufferByteLength = CachedBufferByteLength;
+ if (bufferByteLength == -1)
+ {
+ return true;
+ }
+
+ var byteOffsetStart = view._byteOffset;
+ long byteOffsetEnd;
+ if (view._byteLength == JsTypedArray.LengthAuto)
+ {
+ byteOffsetEnd = bufferByteLength;
+ }
+ else
+ {
+ byteOffsetEnd = byteOffsetStart + view._byteLength;
+ }
+
+ if (byteOffsetStart > bufferByteLength || byteOffsetEnd > bufferByteLength)
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ ///
+ /// https://tc39.es/ecma262/#sec-getviewbytelength
+ ///
+ public long ViewByteLength
+ {
+ get
+ {
+ var view = Object;
+ if (view._byteLength != JsTypedArray.LengthAuto)
+ {
+ return view._byteLength;
+ }
+
+ var byteOffset = view._byteOffset;
+ var byteLength = CachedBufferByteLength;
+ return byteLength - byteOffset;
+ }
+ }
+ }
+
+ ///
+ /// https://tc39.es/ecma262/#sec-makedataviewwithbufferwitnessrecord
+ ///
+ private static DataViewWithBufferWitnessRecord MakeDataViewWithBufferWitnessRecord(JsDataView obj, ArrayBufferOrder order)
+ {
+ var buffer = obj._viewedArrayBuffer;
+ int byteLength;
+ if (buffer?.IsDetachedBuffer == true)
+ {
+ byteLength = -1;
+ }
+ else
+ {
+ byteLength = IntrinsicTypedArrayPrototype.ArrayBufferByteLength(buffer!, order);
+ }
+
+ return new DataViewWithBufferWitnessRecord(obj, byteLength);
}
///
@@ -281,7 +374,13 @@ private JsValue SetViewValue(
buffer.AssertNotDetached();
var viewOffset = dataView._byteOffset;
- var viewSize = dataView._byteLength;
+ var viewRecord = MakeDataViewWithBufferWitnessRecord(dataView, ArrayBufferOrder.Unordered);
+ if (viewRecord.IsViewOutOfBounds)
+ {
+ ExceptionHelper.ThrowTypeError(_realm, "Offset is outside the bounds of the DataView");
+ }
+
+ var viewSize = viewRecord.ViewByteLength;
var elementSize = type.GetElementSize();
if (getIndex + elementSize > viewSize)
{
diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs
index d0e375c388..4db81ff18f 100644
--- a/Jint/Native/Object/ObjectInstance.cs
+++ b/Jint/Native/Object/ObjectInstance.cs
@@ -1177,9 +1177,9 @@ bool TryGetValue(ulong idx, out JsValue jsValue)
}
else
{
- for (ulong k = length - 1; k >= 0; k--)
+ for (var k = (long) (length - 1); k >= 0; k--)
{
- if (TryGetValue(k, out var kvalue) || visitUnassigned)
+ if (TryGetValue((ulong) k, out var kvalue) || visitUnassigned)
{
kvalue ??= Undefined;
args[0] = kvalue;
@@ -1187,7 +1187,7 @@ bool TryGetValue(ulong idx, out JsValue jsValue)
var testResult = callable.Call(thisArg, args);
if (TypeConverter.ToBoolean(testResult))
{
- index = k;
+ index = (ulong) k;
value = kvalue;
return true;
}
diff --git a/Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs b/Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs
index 7695d36029..f37b1fefd3 100644
--- a/Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs
+++ b/Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs
@@ -167,16 +167,22 @@ internal JsTypedArray TypedArraySpeciesCreate(JsTypedArray exemplar, JsValue[] a
///
internal static JsTypedArray TypedArrayCreate(Realm realm, IConstructor constructor, JsValue[] argumentList)
{
- var newTypedArray = Construct(constructor, argumentList).ValidateTypedArray(realm);
+ var newTypedArray = Construct(constructor, argumentList);
+ var taRecord = newTypedArray.ValidateTypedArray(realm);
+
if (argumentList.Length == 1 && argumentList[0] is JsNumber number)
{
+ if (taRecord.IsTypedArrayOutOfBounds)
+ {
+ ExceptionHelper.ThrowTypeError(realm);
+ }
if (newTypedArray.Length < number._value)
{
ExceptionHelper.ThrowTypeError(realm);
}
}
- return newTypedArray;
+ return taRecord.Object;
}
private static JsValue Species(JsValue thisObject, JsValue[] arguments)
diff --git a/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs
index b494970002..4772373a00 100644
--- a/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs
+++ b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs
@@ -111,12 +111,8 @@ private JsValue ByteLength(JsValue thisObject, JsValue[] arguments)
ExceptionHelper.ThrowTypeError(_realm);
}
- if (o._viewedArrayBuffer.IsDetachedBuffer)
- {
- return JsNumber.PositiveZero;
- }
-
- return JsNumber.Create(o._byteLength);
+ var taRecord = MakeTypedArrayWithBufferWitnessRecord(o, ArrayBufferOrder.SeqCst);
+ return JsNumber.Create(taRecord.TypedArrayByteLength);
}
///
@@ -130,7 +126,8 @@ private JsValue ByteOffset(JsValue thisObject, JsValue[] arguments)
ExceptionHelper.ThrowTypeError(_realm);
}
- if (o._viewedArrayBuffer.IsDetachedBuffer)
+ var taRecord = MakeTypedArrayWithBufferWitnessRecord(o, ArrayBufferOrder.SeqCst);
+ if (taRecord.IsTypedArrayOutOfBounds)
{
return JsNumber.PositiveZero;
}
@@ -149,13 +146,125 @@ private JsValue GetLength(JsValue thisObject, JsValue[] arguments)
ExceptionHelper.ThrowTypeError(_realm);
}
- var buffer = o._viewedArrayBuffer;
- if (buffer.IsDetachedBuffer)
+ var taRecord = MakeTypedArrayWithBufferWitnessRecord(o, ArrayBufferOrder.SeqCst);
+ if (taRecord.IsTypedArrayOutOfBounds)
{
return JsNumber.PositiveZero;
}
- return JsNumber.Create(o.Length);
+ return JsNumber.Create(taRecord.TypedArrayLength);
+ }
+
+ internal readonly record struct TypedArrayWithBufferWitnessRecord(JsTypedArray Object, int CachedBufferByteLength)
+ {
+ ///
+ /// https://tc39.es/ecma262/#sec-istypedarrayoutofbounds
+ ///
+ public bool IsTypedArrayOutOfBounds
+ {
+ get
+ {
+ var o = Object;
+ var bufferByteLength = CachedBufferByteLength;
+ if (bufferByteLength == -1)
+ {
+ return true;
+ }
+
+ var byteOffsetStart = o._byteOffset;
+ long byteOffsetEnd;
+ if (o._arrayLength == JsTypedArray.LengthAuto)
+ {
+ byteOffsetEnd = bufferByteLength;
+ }
+ else
+ {
+ var elementSize = o._arrayElementType.GetElementSize();
+ byteOffsetEnd = byteOffsetStart + o._arrayLength * elementSize;
+ }
+
+ if (byteOffsetStart > bufferByteLength || byteOffsetEnd > bufferByteLength)
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ ///
+ /// https://tc39.es/ecma262/#sec-typedarraylength
+ ///
+ public uint TypedArrayLength
+ {
+ get
+ {
+ var o = Object;
+ if (o._arrayLength != JsTypedArray.LengthAuto)
+ {
+ return o._arrayLength;
+ }
+
+ var byteOffset = o._byteOffset;
+ var elementSize = o._arrayElementType.GetElementSize();
+ var byteLength = (double) CachedBufferByteLength;
+ return (uint) System.Math.Floor((byteLength - byteOffset) / elementSize);
+ }
+ }
+
+ ///
+ /// https://tc39.es/ecma262/#sec-typedarraybytelength
+ ///
+ public uint TypedArrayByteLength
+ {
+ get
+ {
+ if (IsTypedArrayOutOfBounds)
+ {
+ return 0;
+ }
+
+ var length = TypedArrayLength;
+ if (length == 0)
+ {
+ return 0;
+ }
+
+ var o = Object;
+ if (o._byteLength != JsTypedArray.LengthAuto)
+ {
+ return o._byteLength;
+ }
+
+ return length * o._arrayElementType.GetElementSize();
+ }
+ }
+ }
+
+ internal static TypedArrayWithBufferWitnessRecord MakeTypedArrayWithBufferWitnessRecord(JsTypedArray obj, ArrayBufferOrder order)
+ {
+ var buffer = obj._viewedArrayBuffer;
+ var byteLength = buffer.IsDetachedBuffer
+ ? -1
+ : ArrayBufferByteLength(buffer, order);
+
+ return new TypedArrayWithBufferWitnessRecord(obj, byteLength);
+ }
+
+ ///
+ /// https://tc39.es/ecma262/#sec-arraybufferbytelength
+ ///
+ internal static int ArrayBufferByteLength(JsArrayBuffer arrayBuffer, ArrayBufferOrder order)
+ {
+ if (arrayBuffer.IsSharedArrayBuffer && arrayBuffer.ArrayBufferByteLength > 0)
+ {
+ // a. Let bufferByteLengthBlock be arrayBuffer.[[ArrayBufferByteLengthData]].
+ // b. Let rawLength be GetRawBytesFromSharedBlock(bufferByteLengthBlock, 0, BIGUINT64, true, order).
+ // c. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
+ // d. Return ℝ(RawBytesToNumeric(BIGUINT64, rawLength, isLittleEndian)).
+ }
+
+ return arrayBuffer.ArrayBufferByteLength;
}
///
@@ -163,14 +272,14 @@ private JsValue GetLength(JsValue thisObject, JsValue[] arguments)
///
private JsValue CopyWithin(JsValue thisObject, JsValue[] arguments)
{
- var o = thisObject.ValidateTypedArray(_realm);
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
var target = arguments.At(0);
var start = arguments.At(1);
var end = arguments.At(2);
- long len = o.Length;
-
var relativeTarget = TypeConverter.ToIntegerOrInfinity(target);
long to;
@@ -264,7 +373,8 @@ private JsValue CopyWithin(JsValue thisObject, JsValue[] arguments)
///
private JsValue Entries(JsValue thisObject, JsValue[] arguments)
{
- var o = thisObject.ValidateTypedArray(_realm);
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
return _realm.Intrinsics.ArrayIteratorPrototype.Construct(o, ArrayIteratorType.KeyAndValue);
}
@@ -273,8 +383,9 @@ private JsValue Entries(JsValue thisObject, JsValue[] arguments)
///
private JsValue Every(JsValue thisObject, JsValue[] arguments)
{
- var o = thisObject.ValidateTypedArray(_realm);
- var len = o.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
if (len == 0)
{
@@ -306,7 +417,9 @@ private JsValue Every(JsValue thisObject, JsValue[] arguments)
///
private JsValue Fill(JsValue thisObject, JsValue[] arguments)
{
- var o = thisObject.ValidateTypedArray(_realm);
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
var jsValue = arguments.At(0);
var start = arguments.At(1);
@@ -322,8 +435,6 @@ private JsValue Fill(JsValue thisObject, JsValue[] arguments)
value = JsNumber.Create(jsValue);
}
- var len = o.Length;
-
int k;
var relativeStart = TypeConverter.ToIntegerOrInfinity(start);
if (double.IsNegativeInfinity(relativeStart))
@@ -372,8 +483,9 @@ private JsValue Filter(JsValue thisObject, JsValue[] arguments)
var callbackfn = GetCallable(arguments.At(0));
var thisArg = arguments.At(1);
- var o = thisObject.ValidateTypedArray(_realm);
- var len = o.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
var kept = new List();
var captured = 0;
@@ -432,12 +544,18 @@ private JsValue FindLastIndex(JsValue thisObject, JsValue[] arguments)
private KeyValuePair DoFind(JsValue thisObject, JsValue[] arguments, bool fromEnd = false)
{
- var o = thisObject.ValidateTypedArray(_realm);
- var len = (int) o.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
var predicate = GetCallable(arguments.At(0));
var thisArg = arguments.At(1);
+ if (len == 0)
+ {
+ return new KeyValuePair(JsNumber.IntegerNegativeOne, Undefined);
+ }
+
var args = _engine._jsValueArrayPool.RentArray(3);
args[2] = o;
if (!fromEnd)
@@ -456,7 +574,7 @@ private KeyValuePair DoFind(JsValue thisObject, JsValue[] argu
}
else
{
- for (var k = len - 1; k >= 0; k--)
+ for (var k = (int) (len - 1); k >= 0; k--)
{
var kNumber = JsNumber.Create(k);
var kValue = o[k];
@@ -480,8 +598,9 @@ private JsValue ForEach(JsValue thisObject, JsValue[] arguments)
var callbackfn = GetCallable(arguments.At(0));
var thisArg = arguments.At(1);
- var o = thisObject.ValidateTypedArray(_realm);
- var len = o.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
var args = _engine._jsValueArrayPool.RentArray(3);
args[2] = o;
@@ -503,8 +622,9 @@ private JsValue ForEach(JsValue thisObject, JsValue[] arguments)
///
private JsValue Includes(JsValue thisObject, JsValue[] arguments)
{
- var o = thisObject.ValidateTypedArray(_realm);
- var len = o.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
if (len == 0)
{
@@ -560,8 +680,10 @@ private JsValue IndexOf(JsValue thisObject, JsValue[] arguments)
var searchElement = arguments.At(0);
var fromIndex = arguments.At(1);
- var o = thisObject.ValidateTypedArray(_realm);
- var len = o.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
+
if (len == 0)
{
return JsNumber.IntegerNegativeOne;
@@ -612,10 +734,11 @@ private JsValue IndexOf(JsValue thisObject, JsValue[] arguments)
///
private JsValue Join(JsValue thisObject, JsValue[] arguments)
{
- var o = thisObject.ValidateTypedArray(_realm);
-
var separator = arguments.At(0);
- var len = o.Length;
+
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
var sep = TypeConverter.ToString(separator.IsUndefined() ? JsString.CommaString : separator);
// as per the spec, this has to be called after ToString(separator)
@@ -653,7 +776,8 @@ static string StringFromJsValue(JsValue value)
///
private JsValue Keys(JsValue thisObject, JsValue[] arguments)
{
- var o = thisObject.ValidateTypedArray(_realm);
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
return _realm.Intrinsics.ArrayIteratorPrototype.Construct(o, ArrayIteratorType.Key);
}
@@ -664,8 +788,10 @@ private JsValue LastIndexOf(JsValue thisObject, JsValue[] arguments)
{
var searchElement = arguments.At(0);
- var o = thisObject.ValidateTypedArray(_realm);
- var len = o.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
+
if (len == 0)
{
return JsNumber.IntegerNegativeOne;
@@ -710,8 +836,9 @@ private JsValue LastIndexOf(JsValue thisObject, JsValue[] arguments)
///
private ObjectInstance Map(JsValue thisObject, JsValue[] arguments)
{
- var o = thisObject.ValidateTypedArray(_realm);
- var len = o.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
var thisArg = arguments.At(1);
var callable = GetCallable(arguments.At(0));
@@ -739,8 +866,9 @@ private JsValue Reduce(JsValue thisObject, JsValue[] arguments)
var callbackfn = GetCallable(arguments.At(0));
var initialValue = arguments.At(1);
- var o = thisObject.ValidateTypedArray(_realm);
- var len = o.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
if (len == 0 && arguments.Length < 2)
{
@@ -784,15 +912,16 @@ private JsValue ReduceRight(JsValue thisObject, JsValue[] arguments)
var callbackfn = GetCallable(arguments.At(0));
var initialValue = arguments.At(1);
- var o = thisObject.ValidateTypedArray(_realm);
- var len = (int) o.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
if (len == 0 && arguments.Length < 2)
{
ExceptionHelper.ThrowTypeError(_realm);
}
- var k = len - 1;
+ var k = (long) len - 1;
JsValue accumulator;
if (arguments.Length > 1)
{
@@ -809,7 +938,7 @@ private JsValue ReduceRight(JsValue thisObject, JsValue[] arguments)
for (; k >= 0; k--)
{
jsValues[0] = accumulator;
- jsValues[1] = o[k];
+ jsValues[1] = o[(int) k];
jsValues[2] = k;
accumulator = callbackfn.Call(Undefined, jsValues);
}
@@ -823,8 +952,10 @@ private JsValue ReduceRight(JsValue thisObject, JsValue[] arguments)
///
private ObjectInstance Reverse(JsValue thisObject, JsValue[] arguments)
{
- var o = thisObject.ValidateTypedArray(_realm);
- var len = (int) o.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
+
var middle = (int) System.Math.Floor(len / 2.0);
var lower = 0;
while (lower != middle)
@@ -881,11 +1012,20 @@ private JsValue Set(JsValue thisObject, JsValue[] arguments)
private void SetTypedArrayFromTypedArray(JsTypedArray target, double targetOffset, JsTypedArray source)
{
var targetBuffer = target._viewedArrayBuffer;
- targetBuffer.AssertNotDetached();
+ var targetRecord = MakeTypedArrayWithBufferWitnessRecord(target, ArrayBufferOrder.SeqCst);
+ if (targetRecord.IsTypedArrayOutOfBounds)
+ {
+ ExceptionHelper.ThrowTypeError(_realm);
+ }
+
+ var targetLength = targetRecord.TypedArrayLength;
- var targetLength = target._arrayLength;
var srcBuffer = source._viewedArrayBuffer;
- srcBuffer.AssertNotDetached();
+ var srcRecord = MakeTypedArrayWithBufferWitnessRecord(source, ArrayBufferOrder.SeqCst);
+ if (srcRecord.IsTypedArrayOutOfBounds)
+ {
+ ExceptionHelper.ThrowTypeError(_realm);
+ }
var targetType = target._arrayElementType;
var targetElementSize = targetType.GetElementSize();
@@ -893,7 +1033,7 @@ private void SetTypedArrayFromTypedArray(JsTypedArray target, double targetOffse
var srcType = source._arrayElementType;
var srcElementSize = srcType.GetElementSize();
- var srcLength = source._arrayLength;
+ var srcLength = srcRecord.TypedArrayLength;
var srcByteOffset = source._byteOffset;
if (double.IsNegativeInfinity(targetOffset))
@@ -926,7 +1066,7 @@ private void SetTypedArrayFromTypedArray(JsTypedArray target, double targetOffse
int srcByteIndex;
if (same)
{
- var srcByteLength = source._byteLength;
+ var srcByteLength = srcRecord.TypedArrayByteLength;
srcBuffer = srcBuffer.CloneArrayBuffer(_realm.Intrinsics.ArrayBuffer, srcByteOffset, srcByteLength);
// %ArrayBuffer% is used to clone srcBuffer because is it known to not have any observable side-effects.
srcByteIndex = 0;
@@ -944,8 +1084,8 @@ private void SetTypedArrayFromTypedArray(JsTypedArray target, double targetOffse
// NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data.
while (targetByteIndex < limit)
{
- var value = srcBuffer.GetValueFromBuffer(srcByteIndex, TypedArrayElementType.Uint8, true, ArrayBufferOrder.Unordered);
- targetBuffer.SetValueInBuffer(targetByteIndex, TypedArrayElementType.Uint8, value, true, ArrayBufferOrder.Unordered);
+ var value = srcBuffer.GetValueFromBuffer(srcByteIndex, TypedArrayElementType.Uint8, isTypedArray: true, ArrayBufferOrder.Unordered);
+ targetBuffer.SetValueInBuffer(targetByteIndex, TypedArrayElementType.Uint8, value, isTypedArray: true, ArrayBufferOrder.Unordered);
srcByteIndex += 1;
targetByteIndex += 1;
}
@@ -954,8 +1094,8 @@ private void SetTypedArrayFromTypedArray(JsTypedArray target, double targetOffse
{
while (targetByteIndex < limit)
{
- var value = srcBuffer.GetValueFromBuffer(srcByteIndex, srcType, true, ArrayBufferOrder.Unordered);
- targetBuffer.SetValueInBuffer(targetByteIndex, targetType, value, true, ArrayBufferOrder.Unordered);
+ var value = srcBuffer.GetValueFromBuffer(srcByteIndex, srcType, isTypedArray: true, ArrayBufferOrder.Unordered);
+ targetBuffer.SetValueInBuffer(targetByteIndex, targetType, value, isTypedArray: true, ArrayBufferOrder.Unordered);
srcByteIndex += srcElementSize;
targetByteIndex += targetElementSize;
}
@@ -1000,8 +1140,9 @@ private JsValue At(JsValue thisObject, JsValue[] arguments)
{
var start = arguments.At(0);
- var o = thisObject.ValidateTypedArray(_realm);
- long len = o.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
var relativeStart = TypeConverter.ToInteger(start);
int k;
@@ -1031,8 +1172,9 @@ private JsValue Slice(JsValue thisObject, JsValue[] arguments)
var start = arguments.At(0);
var end = arguments.At(1);
- var o = thisObject.ValidateTypedArray(_realm);
- long len = o.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
var relativeStart = TypeConverter.ToIntegerOrInfinity(start);
int k;
@@ -1113,8 +1255,10 @@ private JsValue Slice(JsValue thisObject, JsValue[] arguments)
///
private JsValue Some(JsValue thisObject, JsValue[] arguments)
{
- var o = thisObject.ValidateTypedArray(_realm);
- var len = o.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
+
var callbackfn = GetCallable(arguments.At(0));
var thisArg = arguments.At(1);
@@ -1146,25 +1290,27 @@ private JsValue Sort(JsValue thisObject, JsValue[] arguments)
* an object that has a fixed length and whose integer-indexed properties are not sparse.
*/
- var obj = thisObject.ValidateTypedArray(_realm);
- var buffer = obj._viewedArrayBuffer;
- var len = obj.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
+
+ var buffer = o._viewedArrayBuffer;
var compareFn = GetCompareFunction(arguments.At(0));
if (len <= 1)
{
- return obj;
+ return o;
}
- var array = SortArray(buffer, compareFn, obj);
+ var array = SortArray(buffer, compareFn, o);
for (var i = 0; i < (uint) array.Length; ++i)
{
- obj[i] = array[i];
+ o[i] = array[i];
}
- return obj;
+ return o;
}
///
@@ -1244,8 +1390,10 @@ private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments)
* any observable changes in the specified behaviour of the algorithm.
*/
- var array = thisObject.ValidateTypedArray(_realm);
- var len = array.Length;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var array = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
+
const string separator = ",";
if (len == 0)
{
@@ -1292,7 +1440,8 @@ private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments)
///
private JsValue Values(JsValue thisObject, JsValue[] arguments)
{
- var o = thisObject.ValidateTypedArray(_realm);
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
return _realm.Intrinsics.ArrayIteratorPrototype.Construct(o, ArrayIteratorType.Value);
}
@@ -1311,14 +1460,15 @@ private static JsValue ToStringTag(JsValue thisObject, JsValue[] arguments)
private JsValue ToReversed(JsValue thisObject, JsValue[] arguments)
{
- var o = thisObject.ValidateTypedArray(_realm);
- var length = o._arrayLength;
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
- var a = TypedArrayCreateSameType(o, new [] { JsNumber.Create(length) });
+ var a = TypedArrayCreateSameType(o, new [] { JsNumber.Create(len) });
uint k = 0;
- while (k < length)
+ while (k < len)
{
- var from = length - k - 1;
+ var from = len - k - 1;
a[k++] = o.Get(from);
}
@@ -1327,13 +1477,15 @@ private JsValue ToReversed(JsValue thisObject, JsValue[] arguments)
private JsValue ToSorted(JsValue thisObject, JsValue[] arguments)
{
- var o = thisObject.ValidateTypedArray(_realm);
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
+
var compareFn = GetCompareFunction(arguments.At(0));
var buffer = o._viewedArrayBuffer;
- var length = o.Length;
- var a = TypedArrayCreateSameType(o, new [] { JsNumber.Create(length) });
+ var a = TypedArrayCreateSameType(o, new [] { JsNumber.Create(len) });
var array = SortArray(buffer, compareFn, o);
for (var i = 0; (uint) i < (uint) array.Length; ++i)
@@ -1346,10 +1498,12 @@ private JsValue ToSorted(JsValue thisObject, JsValue[] arguments)
private ObjectInstance With(JsValue thisObject, JsValue[] arguments)
{
- var o = thisObject.ValidateTypedArray(_realm);
+ var taRecord = thisObject.ValidateTypedArray(_realm, ArrayBufferOrder.SeqCst);
+ var o = taRecord.Object;
+ var len = taRecord.TypedArrayLength;
+
var value = arguments.At(1);
- var length = o._arrayLength;
var relativeIndex = TypeConverter.ToIntegerOrInfinity(arguments.At(0));
long actualIndex;
@@ -1359,7 +1513,7 @@ private ObjectInstance With(JsValue thisObject, JsValue[] arguments)
}
else
{
- actualIndex = (long) (length + relativeIndex);
+ actualIndex = (long) (len + relativeIndex);
}
value = o._contentType == TypedArrayContentType.BigInt
@@ -1371,10 +1525,10 @@ private ObjectInstance With(JsValue thisObject, JsValue[] arguments)
ExceptionHelper.ThrowRangeError(_realm, "Invalid start index");
}
- var a = TypedArrayCreateSameType(o, new [] { JsNumber.Create(length) });
+ var a = TypedArrayCreateSameType(o, new [] { JsNumber.Create(len) });
var k = 0;
- while (k < length)
+ while (k < len)
{
a[k] = k == (int) actualIndex ? value : o.Get(k);
k++;
diff --git a/Jint/Native/TypedArray/JsTypedArray.cs b/Jint/Native/TypedArray/JsTypedArray.cs
index 608b44feff..ebb7f90b61 100644
--- a/Jint/Native/TypedArray/JsTypedArray.cs
+++ b/Jint/Native/TypedArray/JsTypedArray.cs
@@ -11,6 +11,8 @@ namespace Jint.Native.TypedArray
{
public sealed class JsTypedArray : ObjectInstance
{
+ internal const uint LengthAuto = uint.MaxValue;
+
internal readonly TypedArrayContentType _contentType;
internal readonly TypedArrayElementType _arrayElementType;
internal JsArrayBuffer _viewedArrayBuffer;
@@ -48,7 +50,7 @@ public JsValue this[uint index]
set => IntegerIndexedElementSet(index, value);
}
- public override uint Length => _viewedArrayBuffer.IsDetachedBuffer ? 0 : _arrayLength;
+ public override uint Length => IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(this, ArrayBufferOrder.Unordered).TypedArrayLength;
internal override bool IsIntegerIndexedArray => true;
@@ -187,8 +189,9 @@ public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc
///
public override List GetOwnPropertyKeys(Types types = Types.Empty | Types.String | Types.Symbol)
{
+ var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(this, ArrayBufferOrder.SeqCst);
var keys = new List();
- if (!_viewedArrayBuffer.IsDetachedBuffer)
+ if (!taRecord.IsTypedArrayOutOfBounds)
{
var length = Length;
for (uint i = 0; i < length; ++i)
@@ -262,7 +265,7 @@ private JsValue DoIntegerIndexedElementGet(int index)
var elementType = _arrayElementType;
var elementSize = elementType.GetElementSize();
var indexedPosition = index * elementSize + offset;
- var value = _viewedArrayBuffer.GetValueFromBuffer(indexedPosition, elementType, true, ArrayBufferOrder.Unordered);
+ var value = _viewedArrayBuffer.GetValueFromBuffer(indexedPosition, elementType, isTypedArray: true, ArrayBufferOrder.Unordered);
if (value.Type == Types.Number)
{
return _arrayElementType.FitsInt32()
@@ -348,12 +351,7 @@ internal bool IsValidIntegerIndex(double index)
return false;
}
- if (index < 0 || index >= _arrayLength)
- {
- return false;
- }
-
- return true;
+ return IsValidIntegerIndex((int) index);
}
///
@@ -362,7 +360,24 @@ internal bool IsValidIntegerIndex(double index)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool IsValidIntegerIndex(int index)
{
- return !_viewedArrayBuffer.IsDetachedBuffer && (uint) index < _arrayLength;
+ if (_viewedArrayBuffer.IsDetachedBuffer)
+ {
+ return false;
+ }
+
+ var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(this, ArrayBufferOrder.Unordered);
+ if (taRecord.IsTypedArrayOutOfBounds)
+ {
+ return false;
+ }
+
+ var length = taRecord.TypedArrayLength;
+ if (index < 0 || index >= length)
+ {
+ return false;
+ }
+
+ return true;
}
internal T[] ToNativeArray()
diff --git a/Jint/Native/TypedArray/TypeArrayHelper.cs b/Jint/Native/TypedArray/TypeArrayHelper.cs
index c9f49b1ba5..34b1f793b5 100644
--- a/Jint/Native/TypedArray/TypeArrayHelper.cs
+++ b/Jint/Native/TypedArray/TypeArrayHelper.cs
@@ -1,20 +1,24 @@
+using Jint.Native.ArrayBuffer;
using Jint.Runtime;
namespace Jint.Native.TypedArray;
internal static class TypeArrayHelper
{
- internal static JsTypedArray ValidateTypedArray(this JsValue o, Realm realm)
+ internal static IntrinsicTypedArrayPrototype.TypedArrayWithBufferWitnessRecord ValidateTypedArray(this JsValue o, Realm realm, ArrayBufferOrder order = ArrayBufferOrder.Unordered)
{
- var typedArrayInstance = o as JsTypedArray;
- if (typedArrayInstance is null)
+ if (o is not JsTypedArray typedArray)
{
ExceptionHelper.ThrowTypeError(realm);
+ return default;
}
- var buffer = typedArrayInstance._viewedArrayBuffer;
- buffer.AssertNotDetached();
+ var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(typedArray, order);
+ if (taRecord.IsTypedArrayOutOfBounds)
+ {
+ ExceptionHelper.ThrowTypeError(realm);
+ }
- return typedArrayInstance;
+ return taRecord;
}
}
diff --git a/Jint/Native/TypedArray/TypedArrayConstructor.cs b/Jint/Native/TypedArray/TypedArrayConstructor.cs
index 0d1fb2060c..09ab267443 100644
--- a/Jint/Native/TypedArray/TypedArrayConstructor.cs
+++ b/Jint/Native/TypedArray/TypedArrayConstructor.cs
@@ -130,10 +130,17 @@ private void InitializeTypedArrayFromTypedArray(JsTypedArray o, JsTypedArray src
srcData.AssertNotDetached();
var elementType = o._arrayElementType;
- var elementLength = srcArray._arrayLength;
var srcType = srcArray._arrayElementType;
var srcElementSize = srcType.GetElementSize();
var srcByteOffset = srcArray._byteOffset;
+
+ var srcRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(srcArray, ArrayBufferOrder.SeqCst);
+ if (srcRecord.IsTypedArrayOutOfBounds)
+ {
+ ExceptionHelper.ThrowTypeError(_realm);
+ }
+
+ var elementLength = srcRecord.TypedArrayLength;
var elementSize = elementType.GetElementSize();
var byteLength = elementSize * elementLength;
@@ -157,8 +164,8 @@ private void InitializeTypedArrayFromTypedArray(JsTypedArray o, JsTypedArray src
var count = elementLength;
while (count > 0)
{
- var value = srcData.GetValueFromBuffer(srcByteIndex, srcType, true, ArrayBufferOrder.Unordered);
- data.SetValueInBuffer(targetByteIndex, elementType, value, true, ArrayBufferOrder.Unordered);
+ var value = srcData.GetValueFromBuffer(srcByteIndex, srcType, isTypedArray: true, ArrayBufferOrder.Unordered);
+ data.SetValueInBuffer(targetByteIndex, elementType, value, isTypedArray: true, ArrayBufferOrder.Unordered);
srcByteIndex += srcElementSize;
targetByteIndex += elementSize;
count--;
@@ -194,34 +201,50 @@ private void InitializeTypedArrayFromArrayBuffer(
newLength = (int) TypeConverter.ToIndex(_realm, length);
}
+ var bufferIsFixedLength = buffer.IsFixedLengthArrayBuffer;
+
buffer.AssertNotDetached();
- var bufferByteLength = buffer.ArrayBufferByteLength;
- if (length.IsUndefined())
+ var bufferByteLength = IntrinsicTypedArrayPrototype.ArrayBufferByteLength(buffer, ArrayBufferOrder.SeqCst);
+ if (length.IsUndefined() && !bufferIsFixedLength)
{
- if (bufferByteLength % elementSize != 0)
+ if (offset > bufferByteLength)
{
- ExceptionHelper.ThrowRangeError(_realm, "Invalid buffer byte length");
+ ExceptionHelper.ThrowRangeError(_realm, "Invalid offset");
}
- newByteLength = bufferByteLength - offset;
- if (newByteLength < 0)
- {
- ExceptionHelper.ThrowRangeError(_realm, "Invalid buffer byte length");
- }
+ o._arrayLength = JsTypedArray.LengthAuto;
+ o._byteLength = JsTypedArray.LengthAuto;
}
else
{
- newByteLength = newLength * elementSize;
- if (offset + newByteLength > bufferByteLength)
+ if (length.IsUndefined())
{
- ExceptionHelper.ThrowRangeError(_realm, "Invalid buffer byte length");
+ if (bufferByteLength % elementSize != 0)
+ {
+ ExceptionHelper.ThrowRangeError(_realm, "Invalid buffer byte length");
+ }
+
+ newByteLength = bufferByteLength - offset;
+ if (newByteLength < 0)
+ {
+ ExceptionHelper.ThrowRangeError(_realm, "Invalid buffer byte length");
+ }
}
+ else
+ {
+ newByteLength = newLength * elementSize;
+ if (offset + newByteLength > bufferByteLength)
+ {
+ ExceptionHelper.ThrowRangeError(_realm, "Invalid buffer byte length");
+ }
+ }
+
+ o._arrayLength = (uint) (newByteLength / elementSize);
+ o._byteLength = (uint) newByteLength;
}
o._viewedArrayBuffer = buffer;
- o._arrayLength = (uint) (newByteLength / elementSize);
- o._byteLength = (uint) newByteLength;
o._byteOffset = offset;
}
diff --git a/README.md b/README.md
index 93113e6778..7abf078bab 100644
--- a/README.md
+++ b/README.md
@@ -110,6 +110,7 @@ Following features are supported in version 3.x.
- ✔ Array Grouping - `Object.groupBy` and `Map.groupBy`
- ✔ Promise.withResolvers
+- ✔ Resizable and growable ArrayBuffers
- ✔ ShadowRealm
#### Other