From 2e2d399564ff18938ae7f9cc1acd412da8f3a6c1 Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Fri, 15 May 2026 19:40:53 -0400 Subject: [PATCH] Implement resizable buffer view semantics --- .../non262/ArrayBuffer/resizable-views.js | 70 ++++++++++ js/src/vm/ArrayBufferObject.cpp | 64 ++++++--- js/src/vm/ArrayBufferObject.h | 3 +- js/src/vm/TypedArrayObject.cpp | 47 +++++-- js/src/vm/TypedArrayObject.h | 131 +++++++++++++++--- 5 files changed, 267 insertions(+), 48 deletions(-) create mode 100644 js/src/tests/non262/ArrayBuffer/resizable-views.js diff --git a/js/src/tests/non262/ArrayBuffer/resizable-views.js b/js/src/tests/non262/ArrayBuffer/resizable-views.js new file mode 100644 index 0000000000..32d2e0e342 --- /dev/null +++ b/js/src/tests/non262/ArrayBuffer/resizable-views.js @@ -0,0 +1,70 @@ +// |reftest| skip-if(!this.SharedArrayBuffer) + +var rab = new ArrayBuffer(4, { maxByteLength: 16 }); +var tracking = new Uint8Array(rab); +var fixed = new Uint8Array(rab, 1, 2); +var bytes = new Uint8Array(rab); + +bytes[1] = 11; +bytes[2] = 22; + +assertEq(tracking.length, 4); +assertEq(tracking.byteLength, 4); +assertEq(tracking.byteOffset, 0); +assertEq(fixed.length, 2); +assertEq(fixed.byteLength, 2); +assertEq(fixed.byteOffset, 1); + +rab.resize(2); +assertEq(tracking.length, 2); +assertEq(tracking.byteLength, 2); +assertEq(fixed.length, 0); +assertEq(fixed.byteLength, 0); +assertEq(fixed.byteOffset, 0); +assertEq(fixed[0], undefined); + +rab.resize(8); +assertEq(tracking.length, 8); +assertEq(tracking.byteLength, 8); +assertEq(fixed.length, 2); +assertEq(fixed.byteLength, 2); +assertEq(fixed.byteOffset, 1); +assertEq(fixed[0], 11); +assertEq(fixed[1], 0); +tracking[6] = 66; +assertEq(new Uint8Array(rab)[6], 66); + +var dv = new DataView(rab, 4); +assertEq(dv.byteOffset, 4); +assertEq(dv.byteLength, 4); +dv.setUint8(0, 44); +assertEq(tracking[4], 44); + +rab.resize(3); +assertEq(dv.byteOffset, 0); +assertEq(dv.byteLength, 0); +assertThrowsInstanceOf(() => dv.getUint8(0), RangeError); + +rab.resize(6); +assertEq(dv.byteOffset, 4); +assertEq(dv.byteLength, 2); +assertEq(dv.getUint8(0), 0); + +var fixedDv = new DataView(rab, 4, 2); +rab.resize(5); +assertEq(fixedDv.byteOffset, 0); +assertEq(fixedDv.byteLength, 0); +rab.resize(6); +assertEq(fixedDv.byteOffset, 4); +assertEq(fixedDv.byteLength, 2); + +var gsab = new SharedArrayBuffer(4, { maxByteLength: 16 }); +var sharedTracking = new Uint8Array(gsab); +assertEq(sharedTracking.length, 4); +gsab.grow(8); +assertEq(sharedTracking.length, 8); +sharedTracking[6] = 33; +assertEq(new Uint8Array(gsab)[6], 33); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index 739bd0de33..904a1b3842 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -432,8 +432,12 @@ ArrayBufferViewFits(ArrayBufferViewObject* view, uint32_t newByteLength) { if (view->is()) { DataViewObject& dataView = view->as(); - uint32_t byteOffset = dataView.byteOffset(); - uint32_t byteLength = dataView.byteLength(); + uint32_t byteOffset = dataView.byteOffsetMaybeOutOfBounds(); + if (byteOffset > newByteLength) + return false; + uint32_t byteLength = dataView.isLengthTracking() + ? newByteLength - byteOffset + : dataView.fixedByteLengthMaybeOutOfBounds(); return byteOffset <= newByteLength && byteLength <= newByteLength - byteOffset; } @@ -441,8 +445,13 @@ ArrayBufferViewFits(ArrayBufferViewObject* view, uint32_t newByteLength) TypedArrayObject& typedArray = view->as(); if (typedArray.isSharedMemory()) return true; - uint32_t byteOffset = typedArray.byteOffset(); - uint32_t byteLength = typedArray.byteLength(); + uint32_t byteOffset = typedArray.byteOffsetMaybeOutOfBounds(); + if (byteOffset > newByteLength) + return false; + uint32_t byteLength = typedArray.isLengthTracking() + ? newByteLength - byteOffset + : typedArray.fixedLengthMaybeOutOfBounds() * + typedArray.bytesPerElement(); return byteOffset <= newByteLength && byteLength <= newByteLength - byteOffset; } @@ -532,7 +541,8 @@ ArrayBufferObject::setNewData(FreeOp* fop, BufferContents newContents, OwnsState void ArrayBufferObject::changeViewContents(JSContext* cx, ArrayBufferViewObject* view, - uint8_t* oldDataPointer, BufferContents newContents) + uint8_t* oldDataPointer, BufferContents newContents, + uint32_t newByteLength) { MOZ_ASSERT(!view->isSharedMemory()); @@ -543,7 +553,18 @@ ArrayBufferObject::changeViewContents(JSContext* cx, ArrayBufferViewObject* view uint8_t* viewDataPointer = view->dataPointerUnshared(nogc); if (viewDataPointer) { MOZ_ASSERT(newContents); - ptrdiff_t offset = viewDataPointer - oldDataPointer; + uint32_t offset; + if (view->is()) { + offset = view->as().byteOffsetMaybeOutOfBounds(); + } else if (view->is()) { + offset = view->as().byteOffsetMaybeOutOfBounds(); + } else { + ptrdiff_t oldOffset = viewDataPointer - oldDataPointer; + MOZ_ASSERT(oldOffset >= 0); + offset = uint32_t(oldOffset); + } + if (offset > newByteLength) + offset = 0; viewDataPointer = static_cast(newContents.data()) + offset; view->setDataPointerUnshared(viewDataPointer); } @@ -569,10 +590,10 @@ ArrayBufferObject::changeContents(JSContext* cx, BufferContents newContents, auto& innerViews = cx->compartment()->innerViews.get(); if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(this)) { for (size_t i = 0; i < views->length(); i++) - changeViewContents(cx, (*views)[i], oldDataPointer, newContents); + changeViewContents(cx, (*views)[i], oldDataPointer, newContents, byteLength()); } if (firstView()) - changeViewContents(cx, firstView(), oldDataPointer, newContents); + changeViewContents(cx, firstView(), oldDataPointer, newContents, byteLength()); } void @@ -584,26 +605,29 @@ ArrayBufferObject::changeContentsForResize(JSContext* cx, BufferContents newCont uint8_t* oldDataPointer = dataPointer(); setNewData(cx->runtime()->defaultFreeOp(), newContents, ownsState); + setByteLength(newByteLength); auto& innerViews = cx->compartment()->innerViews.get(); if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(this)) { for (size_t i = 0; i < views->length(); i++) { ArrayBufferViewObject* view = (*views)[i]; - if (ArrayBufferViewFits(view, newByteLength)) - changeViewContents(cx, view, oldDataPointer, newContents); + if (view->is() || view->is()) + changeViewContents(cx, view, oldDataPointer, newContents, newByteLength); + else if (ArrayBufferViewFits(view, newByteLength)) + changeViewContents(cx, view, oldDataPointer, newContents, newByteLength); else NoteViewBufferWasDetached(view, newContents, cx); } } if (firstView()) { - if (ArrayBufferViewFits(firstView(), newByteLength)) - changeViewContents(cx, firstView(), oldDataPointer, newContents); + if (firstView()->is() || firstView()->is()) + changeViewContents(cx, firstView(), oldDataPointer, newContents, newByteLength); + else if (ArrayBufferViewFits(firstView(), newByteLength)) + changeViewContents(cx, firstView(), oldDataPointer, newContents, newByteLength); else NoteViewBufferWasDetached(firstView(), newContents, cx); } - - setByteLength(newByteLength); } static bool @@ -1455,20 +1479,22 @@ ArrayBufferObject::createDataViewForThisImpl(JSContext* cx, const CallArgs& args /* * This method is only called for |DataView(alienBuf, ...)| which calls * this as |createDataViewForThis.call(alienBuf, byteOffset, byteLength, - * DataView.prototype)|, - * ergo there must be exactly 3 arguments. + * DataView.prototype, lengthTracking)|, + * ergo there must be exactly 4 arguments. */ - MOZ_ASSERT(args.length() == 3); + MOZ_ASSERT(args.length() == 4); uint32_t byteOffset = args[0].toPrivateUint32(); uint32_t byteLength = args[1].toPrivateUint32(); + bool lengthTracking = args[3].toBoolean(); Rooted buffer(cx, &args.thisv().toObject().as()); /* * Pop off the passed-along prototype and delegate to normal DataViewObject * construction. */ - JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, &args[2].toObject()); + JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, + &args[2].toObject(), lengthTracking); if (!obj) return false; args.rval().setObject(*obj); @@ -1848,6 +1874,8 @@ ArrayBufferViewObject::trace(JSTracer* trc, JSObject* objArg) // The data may or may not be inline with the buffer. The buffer // can only move during a compacting GC, in which case its // objectMoved hook has already updated the buffer's data pointer. + if (offset > buf.byteLength()) + offset = 0; obj->initPrivate(buf.dataPointer() + offset); } } diff --git a/js/src/vm/ArrayBufferObject.h b/js/src/vm/ArrayBufferObject.h index d0d83c431d..c9dc80a598 100644 --- a/js/src/vm/ArrayBufferObject.h +++ b/js/src/vm/ArrayBufferObject.h @@ -323,7 +323,8 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared private: void changeViewContents(JSContext* cx, ArrayBufferViewObject* view, - uint8_t* oldDataPointer, BufferContents newContents); + uint8_t* oldDataPointer, BufferContents newContents, + uint32_t newByteLength); void setFirstView(ArrayBufferViewObject* view); uint8_t* inlineDataPointer() const; diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index faf3dc40df..7750122ac4 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -484,8 +484,9 @@ class TypedArrayObjectTemplate : public TypedArrayObject } static TypedArrayObject* - makeInstance(JSContext* cx, Handle buffer, uint32_t byteOffset, uint32_t len, - HandleObject proto) + makeInstance(JSContext* cx, Handle buffer, + uint32_t byteOffset, uint32_t len, HandleObject proto, + bool lengthTracking = false) { MOZ_ASSERT_IF(!buffer, byteOffset == 0); @@ -549,7 +550,9 @@ class TypedArrayObjectTemplate : public TypedArrayObject #endif } - obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(len)); + obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT, + Int32Value(lengthTracking ? TypedArrayObject::LENGTH_TRACKING + : int32_t(len))); obj->setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(byteOffset)); #ifdef DEBUG @@ -898,6 +901,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject return nullptr; // invalid byteOffset } + bool lengthTracking = false; uint32_t len; if (lengthInt == -1) { len = (buffer->byteLength() - byteOffset) / sizeof(NativeType); @@ -906,6 +910,12 @@ class TypedArrayObjectTemplate : public TypedArrayObject JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS); return nullptr; // given byte array doesn't map exactly to sizeof(NativeType) * N } + if ((buffer->is() && buffer->as().isResizable()) || + (buffer->is() && + buffer->as().isGrowable())) + { + lengthTracking = true; + } } else { len = uint32_t(lengthInt); } @@ -924,7 +934,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject return nullptr; // byteOffset + len is too big for the arraybuffer } - return makeInstance(cx, buffer, byteOffset, len, proto); + return makeInstance(cx, buffer, byteOffset, len, proto, lengthTracking); } static bool @@ -1859,7 +1869,8 @@ DataViewNewObjectKind(JSContext* cx, uint32_t byteLength, JSObject* proto) DataViewObject* DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength, - Handle arrayBuffer, JSObject* protoArg) + Handle arrayBuffer, JSObject* protoArg, + bool lengthTracking) { if (arrayBuffer->isDetached()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); @@ -1901,7 +1912,9 @@ DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength, DataViewObject& dvobj = obj->as(); dvobj.setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(byteOffset)); - dvobj.setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(byteLength)); + dvobj.setFixedSlot(TypedArrayObject::LENGTH_SLOT, + Int32Value(lengthTracking ? TypedArrayObject::LENGTH_TRACKING + : int32_t(byteLength))); dvobj.setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectValue(*arrayBuffer)); dvobj.initPrivate(arrayBuffer->dataPointer() + byteOffset); @@ -1921,7 +1934,8 @@ DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength, bool DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, const CallArgs& args, - uint32_t* byteOffsetPtr, uint32_t* byteLengthPtr) + uint32_t* byteOffsetPtr, uint32_t* byteLengthPtr, + bool* lengthTrackingPtr) { if (!IsArrayBuffer(bufobj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, @@ -1932,6 +1946,7 @@ DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, cons Rooted buffer(cx, &AsArrayBuffer(bufobj)); uint32_t byteOffset = 0; uint32_t byteLength = buffer->byteLength(); + bool lengthTracking = buffer->isResizable() && !args.hasDefined(2); if (args.length() > 1) { if (!ToUint32(cx, args[1], &byteOffset)) @@ -1957,9 +1972,11 @@ DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, cons if (args.get(2).isUndefined()) { byteLength -= byteOffset; + lengthTracking = buffer->isResizable(); } else { if (!ToUint32(cx, args[2], &byteLength)) return false; + lengthTracking = false; if (byteLength > INT32_MAX) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, "2"); @@ -1983,6 +2000,7 @@ DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, cons *byteOffsetPtr = byteOffset; *byteLengthPtr = byteLength; + *lengthTrackingPtr = lengthTracking; return true; } @@ -1994,7 +2012,8 @@ DataViewObject::constructSameCompartment(JSContext* cx, HandleObject bufobj, con assertSameCompartment(cx, bufobj); uint32_t byteOffset, byteLength; - if (!getAndCheckConstructorArgs(cx, bufobj, args, &byteOffset, &byteLength)) + bool lengthTracking; + if (!getAndCheckConstructorArgs(cx, bufobj, args, &byteOffset, &byteLength, &lengthTracking)) return false; RootedObject proto(cx); @@ -2003,7 +2022,8 @@ DataViewObject::constructSameCompartment(JSContext* cx, HandleObject bufobj, con return false; Rooted buffer(cx, &AsArrayBuffer(bufobj)); - JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, proto); + JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, proto, + lengthTracking); if (!obj) return false; args.rval().setObject(*obj); @@ -2043,8 +2063,12 @@ DataViewObject::constructWrapped(JSContext* cx, HandleObject bufobj, const CallA // NB: This entails the IsArrayBuffer check uint32_t byteOffset, byteLength; - if (!getAndCheckConstructorArgs(cx, unwrapped, args, &byteOffset, &byteLength)) + bool lengthTracking; + if (!getAndCheckConstructorArgs(cx, unwrapped, args, &byteOffset, &byteLength, + &lengthTracking)) + { return false; + } // Make sure to get the [[Prototype]] for the created view from this // compartment. @@ -2060,11 +2084,12 @@ DataViewObject::constructWrapped(JSContext* cx, HandleObject bufobj, const CallA return false; } - FixedInvokeArgs<3> args2(cx); + FixedInvokeArgs<4> args2(cx); args2[0].set(PrivateUint32Value(byteOffset)); args2[1].set(PrivateUint32Value(byteLength)); args2[2].setObject(*proto); + args2[3].setBoolean(lengthTracking); RootedValue fval(cx, global->createDataViewForThis()); RootedValue thisv(cx, ObjectValue(*bufobj)); diff --git a/js/src/vm/TypedArrayObject.h b/js/src/vm/TypedArrayObject.h index 8b4b0b5092..9d7f667979 100644 --- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -52,6 +52,8 @@ class TypedArrayObject : public NativeObject "right buffer slot"); // Slot containing length of the view in number of typed elements. + // Length-tracking views on resizable/growable buffers store + // LENGTH_TRACKING here and compute their visible length from the buffer. static const size_t LENGTH_SLOT = 1; static_assert(LENGTH_SLOT == JS_TYPEDARRAYLAYOUT_LENGTH_SLOT, "self-hosted code with burned-in constants must get the " @@ -65,6 +67,8 @@ class TypedArrayObject : public NativeObject static const size_t RESERVED_SLOTS = 3; + static const int32_t LENGTH_TRACKING = -1; + #ifdef DEBUG static const uint8_t ZeroLengthArrayData = 0x4A; #endif @@ -139,15 +143,13 @@ class TypedArrayObject : public NativeObject return tarr->getFixedSlot(BUFFER_SLOT); } static Value byteOffsetValue(TypedArrayObject* tarr) { - Value v = tarr->getFixedSlot(BYTEOFFSET_SLOT); - MOZ_ASSERT(v.toInt32() >= 0); - return v; + return Int32Value(tarr->byteOffset()); } static Value byteLengthValue(TypedArrayObject* tarr) { - return Int32Value(tarr->getFixedSlot(LENGTH_SLOT).toInt32() * tarr->bytesPerElement()); + return Int32Value(tarr->byteLength()); } static Value lengthValue(TypedArrayObject* tarr) { - return tarr->getFixedSlot(LENGTH_SLOT); + return Int32Value(tarr->length()); } static bool @@ -159,14 +161,70 @@ class TypedArrayObject : public NativeObject JSObject* bufferObject() const { return bufferValue(const_cast(this)).toObjectOrNull(); } + bool isLengthTracking() const { + return getFixedSlot(LENGTH_SLOT).toInt32() == LENGTH_TRACKING; + } + bool hasResizableOrGrowableBuffer() const { + if (!hasBuffer()) + return false; + if (isSharedMemory()) + return bufferShared()->isGrowable(); + return bufferUnshared()->isResizable(); + } + uint32_t byteOffsetMaybeOutOfBounds() const { + Value v = getFixedSlot(BYTEOFFSET_SLOT); + MOZ_ASSERT(v.toInt32() >= 0); + return v.toInt32(); + } + uint32_t fixedLengthMaybeOutOfBounds() const { + int32_t length = getFixedSlot(LENGTH_SLOT).toInt32(); + MOZ_ASSERT(length >= 0); + return length; + } + uint32_t bufferByteLength() const { + MOZ_ASSERT(hasBuffer()); + if (isSharedMemory()) + return bufferShared()->byteLength(); + return bufferUnshared()->byteLength(); + } + bool isOutOfBounds() const { + if (!hasBuffer()) + return false; + if (hasDetachedBuffer()) + return true; + + uint32_t bufferByteLength = this->bufferByteLength(); + uint32_t offset = byteOffsetMaybeOutOfBounds(); + if (offset > bufferByteLength) + return true; + + if (isLengthTracking()) + return false; + + uint32_t byteLength = fixedLengthMaybeOutOfBounds() * bytesPerElement(); + return byteLength > bufferByteLength - offset; + } uint32_t byteOffset() const { - return byteOffsetValue(const_cast(this)).toInt32(); + if (isOutOfBounds()) + return 0; + return byteOffsetMaybeOutOfBounds(); } uint32_t byteLength() const { - return byteLengthValue(const_cast(this)).toInt32(); + return length() * bytesPerElement(); } uint32_t length() const { - return lengthValue(const_cast(this)).toInt32(); + if (!isLengthTracking()) { + if (isOutOfBounds()) + return 0; + return fixedLengthMaybeOutOfBounds(); + } + + if (isOutOfBounds()) + return 0; + + uint32_t bufferByteLength = this->bufferByteLength(); + uint32_t offset = byteOffsetMaybeOutOfBounds(); + return (bufferByteLength - offset) / bytesPerElement(); } bool hasInlineElements() const; @@ -466,28 +524,26 @@ class DataViewObject : public NativeObject defineGetter(JSContext* cx, PropertyName* name, HandleNativeObject proto); static bool getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, const CallArgs& args, - uint32_t *byteOffset, uint32_t* byteLength); + uint32_t *byteOffset, uint32_t* byteLength, + bool* lengthTracking); static bool constructSameCompartment(JSContext* cx, HandleObject bufobj, const CallArgs& args); static bool constructWrapped(JSContext* cx, HandleObject bufobj, const CallArgs& args); friend bool ArrayBufferObject::createDataViewForThisImpl(JSContext* cx, const CallArgs& args); static DataViewObject* create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength, - Handle arrayBuffer, JSObject* proto); + Handle arrayBuffer, JSObject* proto, + bool lengthTracking = false); public: static const Class class_; static Value byteOffsetValue(DataViewObject* view) { - Value v = view->getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT); - MOZ_ASSERT(v.toInt32() >= 0); - return v; + return Int32Value(view->byteOffset()); } static Value byteLengthValue(DataViewObject* view) { - Value v = view->getFixedSlot(TypedArrayObject::LENGTH_SLOT); - MOZ_ASSERT(v.toInt32() >= 0); - return v; + return Int32Value(view->byteLength()); } static Value bufferValue(DataViewObject* view) { @@ -495,11 +551,17 @@ class DataViewObject : public NativeObject } uint32_t byteOffset() const { - return byteOffsetValue(const_cast(this)).toInt32(); + if (isOutOfBounds()) + return 0; + return byteOffsetMaybeOutOfBounds(); } uint32_t byteLength() const { - return byteLengthValue(const_cast(this)).toInt32(); + if (isOutOfBounds()) + return 0; + if (isLengthTracking()) + return arrayBuffer().byteLength() - byteOffsetMaybeOutOfBounds(); + return fixedByteLengthMaybeOutOfBounds(); } ArrayBufferObject& arrayBuffer() const { @@ -510,6 +572,39 @@ class DataViewObject : public NativeObject return getPrivate(); } + bool isLengthTracking() const { + return getFixedSlot(TypedArrayObject::LENGTH_SLOT).toInt32() == + TypedArrayObject::LENGTH_TRACKING; + } + + uint32_t byteOffsetMaybeOutOfBounds() const { + Value v = getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT); + MOZ_ASSERT(v.toInt32() >= 0); + return v.toInt32(); + } + + uint32_t fixedByteLengthMaybeOutOfBounds() const { + int32_t length = getFixedSlot(TypedArrayObject::LENGTH_SLOT).toInt32(); + MOZ_ASSERT(length >= 0); + return length; + } + + bool isOutOfBounds() const { + const ArrayBufferObject& buffer = arrayBuffer(); + if (buffer.isDetached()) + return true; + + uint32_t bufferByteLength = buffer.byteLength(); + uint32_t offset = byteOffsetMaybeOutOfBounds(); + if (offset > bufferByteLength) + return true; + + if (isLengthTracking()) + return false; + + return fixedByteLengthMaybeOutOfBounds() > bufferByteLength - offset; + } + static bool class_constructor(JSContext* cx, unsigned argc, Value* vp); static bool getInt8Impl(JSContext* cx, const CallArgs& args);