From 05bd75ca4a8a2fb665d587638f38115c9262c033 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 30 Dec 2023 13:21:12 +0200 Subject: [PATCH 1/4] Implement resizable ArrayBuffer --- .../Test262Harness.settings.json | 1 - .../ArrayBuffer/ArrayBufferConstructor.cs | 150 ++++--- Jint/Native/ArrayBuffer/ArrayBufferOrder.cs | 2 +- .../ArrayBuffer/ArrayBufferPrototype.cs | 388 +++++++++++------- Jint/Native/ArrayBuffer/JsArrayBuffer.cs | 29 +- Jint/Native/DataView/DataViewConstructor.cs | 11 +- Jint/Native/DataView/DataViewPrototype.cs | 109 ++++- .../IntrinsicTypedArrayPrototype.cs | 101 ++++- Jint/Native/TypedArray/JsTypedArray.cs | 33 +- Jint/Native/TypedArray/TypeArrayHelper.cs | 16 +- .../TypedArray/TypedArrayConstructor.cs | 45 +- 11 files changed, 621 insertions(+), 264 deletions(-) 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..40351d3850 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 { diff --git a/Jint/Native/DataView/DataViewPrototype.cs b/Jint/Native/DataView/DataViewPrototype.cs index 342e539af7..9de6ea91f6 100644 --- a/Jint/Native/DataView/DataViewPrototype.cs +++ b/Jint/Native/DataView/DataViewPrototype.cs @@ -92,6 +92,12 @@ 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(); @@ -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/TypedArray/IntrinsicTypedArrayPrototype.cs b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs index b494970002..4c9f70d1f5 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.TypedArrayLength); } /// @@ -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,97 @@ 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); + } + } + } + + 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; } /// diff --git a/Jint/Native/TypedArray/JsTypedArray.cs b/Jint/Native/TypedArray/JsTypedArray.cs index 608b44feff..66c45a69bd 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; @@ -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..7399c86f94 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 JsTypedArray 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 null!; } - var buffer = typedArrayInstance._viewedArrayBuffer; - buffer.AssertNotDetached(); + var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(typedArray, order); + if (taRecord.IsTypedArrayOutOfBounds) + { + ExceptionHelper.ThrowTypeError(realm); + } - return typedArrayInstance; + return typedArray; } } diff --git a/Jint/Native/TypedArray/TypedArrayConstructor.cs b/Jint/Native/TypedArray/TypedArrayConstructor.cs index 0d1fb2060c..b08aaca7de 100644 --- a/Jint/Native/TypedArray/TypedArrayConstructor.cs +++ b/Jint/Native/TypedArray/TypedArrayConstructor.cs @@ -194,34 +194,51 @@ 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"); - } + // TODO AUTO + o._arrayLength = 0; + o._byteLength = 0; } else { - newByteLength = newLength * elementSize; - if (offset + newByteLength > bufferByteLength) + if (length.IsUndefined()) + { + 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 { - ExceptionHelper.ThrowRangeError(_realm, "Invalid buffer byte length"); + 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; } From e650596bef76b88b379c02af72fa0027e9cfc04b Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 30 Dec 2023 13:56:45 +0200 Subject: [PATCH 2/4] fixes --- .../IntrinsicTypedArrayConstructor.cs | 10 +- .../IntrinsicTypedArrayPrototype.cs | 162 +++++++++++------- Jint/Native/TypedArray/TypeArrayHelper.cs | 6 +- 3 files changed, 110 insertions(+), 68 deletions(-) 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 4c9f70d1f5..46d2d74cff 100644 --- a/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs +++ b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs @@ -244,14 +244,14 @@ internal static int ArrayBufferByteLength(JsArrayBuffer arrayBuffer, ArrayBuffer /// 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; @@ -345,7 +345,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); } @@ -354,8 +355,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) { @@ -387,7 +389,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); @@ -403,8 +407,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)) @@ -453,8 +455,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; @@ -513,8 +516,14 @@ 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; + + if (len == 0) + { + return new KeyValuePair(JsNumber.IntegerNegativeOne, Undefined); + } var predicate = GetCallable(arguments.At(0)); var thisArg = arguments.At(1); @@ -537,7 +546,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]; @@ -561,8 +570,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; @@ -584,8 +594,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) { @@ -641,8 +652,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; @@ -693,10 +706,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) @@ -734,7 +748,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); } @@ -745,8 +760,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; @@ -791,8 +808,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)); @@ -820,8 +838,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) { @@ -865,8 +884,9 @@ 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) { @@ -904,8 +924,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) @@ -1081,8 +1103,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; @@ -1112,8 +1135,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; @@ -1194,8 +1218,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); @@ -1227,25 +1253,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; } /// @@ -1325,8 +1353,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) { @@ -1373,7 +1403,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); } @@ -1392,14 +1423,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); } @@ -1408,13 +1440,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) @@ -1427,10 +1461,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; @@ -1440,7 +1476,7 @@ private ObjectInstance With(JsValue thisObject, JsValue[] arguments) } else { - actualIndex = (long) (length + relativeIndex); + actualIndex = (long) (len + relativeIndex); } value = o._contentType == TypedArrayContentType.BigInt @@ -1452,10 +1488,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/TypeArrayHelper.cs b/Jint/Native/TypedArray/TypeArrayHelper.cs index 7399c86f94..34b1f793b5 100644 --- a/Jint/Native/TypedArray/TypeArrayHelper.cs +++ b/Jint/Native/TypedArray/TypeArrayHelper.cs @@ -5,12 +5,12 @@ namespace Jint.Native.TypedArray; internal static class TypeArrayHelper { - internal static JsTypedArray ValidateTypedArray(this JsValue o, Realm realm, ArrayBufferOrder order = ArrayBufferOrder.Unordered) + internal static IntrinsicTypedArrayPrototype.TypedArrayWithBufferWitnessRecord ValidateTypedArray(this JsValue o, Realm realm, ArrayBufferOrder order = ArrayBufferOrder.Unordered) { if (o is not JsTypedArray typedArray) { ExceptionHelper.ThrowTypeError(realm); - return null!; + return default; } var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(typedArray, order); @@ -19,6 +19,6 @@ internal static JsTypedArray ValidateTypedArray(this JsValue o, Realm realm, Arr ExceptionHelper.ThrowTypeError(realm); } - return typedArray; + return taRecord; } } From ba335cc80afb5664abac1dc624403f67a34d62e2 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 30 Dec 2023 15:07:39 +0200 Subject: [PATCH 3/4] fixes --- Jint/Native/DataView/DataViewConstructor.cs | 14 ++++ Jint/Native/DataView/DataViewPrototype.cs | 2 +- Jint/Native/Object/ObjectInstance.cs | 6 +- .../IntrinsicTypedArrayPrototype.cs | 67 ++++++++++++++----- Jint/Native/TypedArray/JsTypedArray.cs | 2 +- .../TypedArray/TypedArrayConstructor.cs | 18 +++-- 6 files changed, 83 insertions(+), 26 deletions(-) diff --git a/Jint/Native/DataView/DataViewConstructor.cs b/Jint/Native/DataView/DataViewConstructor.cs index 40351d3850..e6b90b8001 100644 --- a/Jint/Native/DataView/DataViewConstructor.cs +++ b/Jint/Native/DataView/DataViewConstructor.cs @@ -90,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 9de6ea91f6..563caa28ac 100644 --- a/Jint/Native/DataView/DataViewPrototype.cs +++ b/Jint/Native/DataView/DataViewPrototype.cs @@ -101,7 +101,7 @@ private JsValue ByteLength(JsValue thisObject, JsValue[] arguments) var buffer = o._viewedArrayBuffer!; buffer.AssertNotDetached(); - return JsNumber.Create(o._byteLength); + return JsNumber.Create(viewRecord.ViewByteLength); } /// 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/IntrinsicTypedArrayPrototype.cs b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs index 46d2d74cff..4772373a00 100644 --- a/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs +++ b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs @@ -112,7 +112,7 @@ private JsValue ByteLength(JsValue thisObject, JsValue[] arguments) } var taRecord = MakeTypedArrayWithBufferWitnessRecord(o, ArrayBufferOrder.SeqCst); - return JsNumber.Create(taRecord.TypedArrayLength); + return JsNumber.Create(taRecord.TypedArrayByteLength); } /// @@ -211,6 +211,34 @@ public uint TypedArrayLength 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) @@ -520,14 +548,14 @@ private KeyValuePair DoFind(JsValue thisObject, JsValue[] argu 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 predicate = GetCallable(arguments.At(0)); - var thisArg = arguments.At(1); - var args = _engine._jsValueArrayPool.RentArray(3); args[2] = o; if (!fromEnd) @@ -893,7 +921,7 @@ private JsValue ReduceRight(JsValue thisObject, JsValue[] arguments) ExceptionHelper.ThrowTypeError(_realm); } - var k = len - 1; + var k = (long) len - 1; JsValue accumulator; if (arguments.Length > 1) { @@ -910,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); } @@ -984,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(); @@ -996,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)) @@ -1029,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; @@ -1047,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; } @@ -1057,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; } diff --git a/Jint/Native/TypedArray/JsTypedArray.cs b/Jint/Native/TypedArray/JsTypedArray.cs index 66c45a69bd..ebb7f90b61 100644 --- a/Jint/Native/TypedArray/JsTypedArray.cs +++ b/Jint/Native/TypedArray/JsTypedArray.cs @@ -50,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; diff --git a/Jint/Native/TypedArray/TypedArrayConstructor.cs b/Jint/Native/TypedArray/TypedArrayConstructor.cs index b08aaca7de..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--; @@ -206,9 +213,8 @@ private void InitializeTypedArrayFromArrayBuffer( ExceptionHelper.ThrowRangeError(_realm, "Invalid offset"); } - // TODO AUTO - o._arrayLength = 0; - o._byteLength = 0; + o._arrayLength = JsTypedArray.LengthAuto; + o._byteLength = JsTypedArray.LengthAuto; } else { From ab50bd467c890be37a0857289de8d0ccd588d8ef Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 30 Dec 2023 15:12:19 +0200 Subject: [PATCH 4/4] tick the box --- README.md | 1 + 1 file changed, 1 insertion(+) 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