Browse Source

Implement resizable ArrayBuffer (#1707)

Marko Lahma 1 year ago
parent
commit
331c1b7676

+ 0 - 1
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",

+ 91 - 59
Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs

@@ -8,83 +8,115 @@ using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
 
-namespace Jint.Native.ArrayBuffer
+namespace Jint.Native.ArrayBuffer;
+
+/// <summary>
+/// https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-constructor
+/// </summary>
+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);
+    }
+
     /// <summary>
-    /// https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-constructor
+    /// https://tc39.es/ecma262/#sec-arraybuffer.isview
     /// </summary>
-    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; }
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-get-arraybuffer-@@species
+    /// </summary>
+    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);
         }
 
-        /// <summary>
-        /// https://tc39.es/ecma262/#sec-arraybuffer.isview
-        /// </summary>
-        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);
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-getarraybuffermaxbytelengthoption
+    /// </summary>
+    private uint? GetArrayBufferMaxByteLengthOption(JsValue options)
+    {
+        if (options is not ObjectInstance)
         {
-            var arg = arguments.At(0);
-            return arg is JsDataView or JsTypedArray;
+            return null;
         }
 
-        /// <summary>
-        /// https://tc39.es/ecma262/#sec-get-arraybuffer-@@species
-        /// </summary>
-        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);
-        }
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-allocatearraybuffer
+    /// </summary>
+    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<ulong, uint?>(byteLength, maxByteLength));
+
+        return obj;
     }
 }

+ 1 - 1
Jint/Native/ArrayBuffer/ArrayBufferOrder.cs

@@ -3,6 +3,6 @@ namespace Jint.Native.ArrayBuffer;
 internal enum ArrayBufferOrder
 {
     Init,
-    SecCst,
+    SeqCst,
     Unordered
 }

+ 231 - 157
Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs

@@ -7,168 +7,242 @@ using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
 
-namespace Jint.Native.ArrayBuffer
+namespace Jint.Native.ArrayBuffer;
+
+/// <summary>
+/// https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-prototype-object
+/// </summary>
+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;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.maxbytelength
+    /// </summary>
+    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;
+    }
+
     /// <summary>
-    /// https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-prototype-object
+    /// https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.resizable
     /// </summary>
-    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;
-        }
-
-        /// <summary>
-        /// https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.bytelength
-        /// </summary>
-        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);
-        }
-
-        /// <summary>
-        /// https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice
-        /// </summary>
-        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;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-arraybuffer.prototype.resize
+    /// </summary>
+    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;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.bytelength
+    /// </summary>
+    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);
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice
+    /// </summary>
+    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;
     }
 }

+ 28 - 1
Jint/Native/ArrayBuffer/JsArrayBuffer.cs

@@ -13,12 +13,16 @@ namespace Jint.Native.ArrayBuffer
         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<byte>();
             _arrayBufferData = block;
         }
@@ -37,6 +41,9 @@ namespace Jint.Native.ArrayBuffer
         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 @@ namespace Jint.Native.ArrayBuffer
             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<byte>();
+            var newBlock = CreateByteDataBlock(newByteLength);
+            var copyLength = System.Math.Min(newByteLength, ArrayBufferByteLength);
+
+            System.Array.Copy(oldBlock, newBlock, copyLength);
+            _arrayBufferData = newBlock;
+        }
+
         internal void AssertNotDetached()
         {
             if (IsDetachedBuffer)

+ 24 - 1
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 @@ namespace Jint.Native.DataView
                 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 @@ namespace Jint.Native.DataView
                 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;

+ 105 - 6
Jint/Native/DataView/DataViewPrototype.cs

@@ -92,10 +92,16 @@ namespace Jint.Native.DataView
                 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);
         }
 
         /// <summary>
@@ -109,6 +115,12 @@ namespace Jint.Native.DataView
                 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 @@ namespace Jint.Native.DataView
             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 @@ namespace Jint.Native.DataView
             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 @@ namespace Jint.Native.DataView
             }
 
             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)
+        {
+            /// <summary>
+            /// https://tc39.es/ecma262/#sec-isviewoutofbounds
+            /// </summary>
+            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;
+                }
+            }
+
+            /// <summary>
+            /// https://tc39.es/ecma262/#sec-getviewbytelength
+            /// </summary>
+            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;
+                }
+            }
+        }
+
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-makedataviewwithbufferwitnessrecord
+        /// </summary>
+        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);
         }
 
         /// <summary>
@@ -281,7 +374,13 @@ namespace Jint.Native.DataView
             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)
             {

+ 3 - 3
Jint/Native/Object/ObjectInstance.cs

@@ -1177,9 +1177,9 @@ namespace Jint.Native.Object
             }
             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 @@ namespace Jint.Native.Object
                         var testResult = callable.Call(thisArg, args);
                         if (TypeConverter.ToBoolean(testResult))
                         {
-                            index = k;
+                            index = (ulong) k;
                             value = kvalue;
                             return true;
                         }

+ 8 - 2
Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs

@@ -167,16 +167,22 @@ namespace Jint.Native.TypedArray
         /// </summary>
         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)

+ 238 - 84
Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs

@@ -111,12 +111,8 @@ namespace Jint.Native.TypedArray
                 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);
         }
 
         /// <summary>
@@ -130,7 +126,8 @@ namespace Jint.Native.TypedArray
                 ExceptionHelper.ThrowTypeError(_realm);
             }
 
-            if (o._viewedArrayBuffer.IsDetachedBuffer)
+            var taRecord = MakeTypedArrayWithBufferWitnessRecord(o, ArrayBufferOrder.SeqCst);
+            if (taRecord.IsTypedArrayOutOfBounds)
             {
                 return JsNumber.PositiveZero;
             }
@@ -149,13 +146,125 @@ namespace Jint.Native.TypedArray
                 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)
+        {
+            /// <summary>
+            /// https://tc39.es/ecma262/#sec-istypedarrayoutofbounds
+            /// </summary>
+            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;
+                }
+            }
+
+            /// <summary>
+            /// https://tc39.es/ecma262/#sec-typedarraylength
+            /// </summary>
+            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);
+                }
+            }
+
+            /// <summary>
+            /// https://tc39.es/ecma262/#sec-typedarraybytelength
+            /// </summary>
+            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);
+        }
+
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-arraybufferbytelength
+        /// </summary>
+        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;
         }
 
         /// <summary>
@@ -163,14 +272,14 @@ namespace Jint.Native.TypedArray
         /// </summary>
         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 @@ namespace Jint.Native.TypedArray
         /// </summary>
         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 @@ namespace Jint.Native.TypedArray
         /// </summary>
         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 @@ namespace Jint.Native.TypedArray
         /// </summary>
         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 @@ namespace Jint.Native.TypedArray
                 value = JsNumber.Create(jsValue);
             }
 
-            var len = o.Length;
-
             int k;
             var relativeStart = TypeConverter.ToIntegerOrInfinity(start);
             if (double.IsNegativeInfinity(relativeStart))
@@ -372,8 +483,9 @@ namespace Jint.Native.TypedArray
             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<JsValue>();
             var captured = 0;
@@ -432,12 +544,18 @@ namespace Jint.Native.TypedArray
 
         private KeyValuePair<JsValue, JsValue> 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<JsValue, JsValue>(JsNumber.IntegerNegativeOne, Undefined);
+            }
+
             var args = _engine._jsValueArrayPool.RentArray(3);
             args[2] = o;
             if (!fromEnd)
@@ -456,7 +574,7 @@ namespace Jint.Native.TypedArray
             }
             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 @@ namespace Jint.Native.TypedArray
             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 @@ namespace Jint.Native.TypedArray
         /// </summary>
         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 @@ namespace Jint.Native.TypedArray
             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 @@ namespace Jint.Native.TypedArray
         /// </summary>
         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 @@ namespace Jint.Native.TypedArray
         /// </summary>
         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 @@ namespace Jint.Native.TypedArray
         {
             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 @@ namespace Jint.Native.TypedArray
         /// </summary>
         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 @@ namespace Jint.Native.TypedArray
             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 @@ namespace Jint.Native.TypedArray
             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 @@ namespace Jint.Native.TypedArray
             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 @@ namespace Jint.Native.TypedArray
         /// </summary>
         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 @@ namespace Jint.Native.TypedArray
         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 @@ namespace Jint.Native.TypedArray
 
             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 @@ namespace Jint.Native.TypedArray
             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 @@ namespace Jint.Native.TypedArray
                 // 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 @@ namespace Jint.Native.TypedArray
             {
                 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 @@ namespace Jint.Native.TypedArray
         {
             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 @@ namespace Jint.Native.TypedArray
             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 @@ namespace Jint.Native.TypedArray
         /// </summary>
         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 @@ namespace Jint.Native.TypedArray
              * 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;
         }
 
         /// <summary>
@@ -1244,8 +1390,10 @@ namespace Jint.Native.TypedArray
              * 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 @@ namespace Jint.Native.TypedArray
         /// </summary>
         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 @@ namespace Jint.Native.TypedArray
 
         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 @@ namespace Jint.Native.TypedArray
 
         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 @@ namespace Jint.Native.TypedArray
 
         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 @@ namespace Jint.Native.TypedArray
             }
             else
             {
-                actualIndex = (long) (length + relativeIndex);
+                actualIndex = (long) (len + relativeIndex);
             }
 
             value = o._contentType == TypedArrayContentType.BigInt
@@ -1371,10 +1525,10 @@ namespace Jint.Native.TypedArray
                 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++;

+ 25 - 10
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 @@ namespace Jint.Native.TypedArray
             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 @@ namespace Jint.Native.TypedArray
         /// </summary>
         public override List<JsValue> GetOwnPropertyKeys(Types types = Types.Empty | Types.String | Types.Symbol)
         {
+            var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(this, ArrayBufferOrder.SeqCst);
             var keys = new List<JsValue>();
-            if (!_viewedArrayBuffer.IsDetachedBuffer)
+            if (!taRecord.IsTypedArrayOutOfBounds)
             {
                 var length = Length;
                 for (uint i = 0; i < length; ++i)
@@ -262,7 +265,7 @@ namespace Jint.Native.TypedArray
             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 @@ namespace Jint.Native.TypedArray
                 return false;
             }
 
-            if (index < 0 || index >= _arrayLength)
-            {
-                return false;
-            }
-
-            return true;
+            return IsValidIntegerIndex((int) index);
         }
 
         /// <summary>
@@ -362,7 +360,24 @@ namespace Jint.Native.TypedArray
         [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<T>()

+ 10 - 6
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;
     }
 }

+ 40 - 17
Jint/Native/TypedArray/TypedArrayConstructor.cs

@@ -130,10 +130,17 @@ namespace Jint.Native.TypedArray
             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 @@ namespace Jint.Native.TypedArray
                 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 @@ namespace Jint.Native.TypedArray
                 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;
         }
 

+ 1 - 0
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