Implement resizable buffer view semantics

This commit is contained in:
Basilisk-Dev 2026-05-15 19:40:53 -04:00 committed by wuggy
commit 2e2d399564
5 changed files with 267 additions and 48 deletions

View file

@ -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);

View file

@ -432,8 +432,12 @@ ArrayBufferViewFits(ArrayBufferViewObject* view, uint32_t newByteLength)
{
if (view->is<DataViewObject>()) {
DataViewObject& dataView = view->as<DataViewObject>();
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<TypedArrayObject>();
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<DataViewObject>()) {
offset = view->as<DataViewObject>().byteOffsetMaybeOutOfBounds();
} else if (view->is<TypedArrayObject>()) {
offset = view->as<TypedArrayObject>().byteOffsetMaybeOutOfBounds();
} else {
ptrdiff_t oldOffset = viewDataPointer - oldDataPointer;
MOZ_ASSERT(oldOffset >= 0);
offset = uint32_t(oldOffset);
}
if (offset > newByteLength)
offset = 0;
viewDataPointer = static_cast<uint8_t*>(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<DataViewObject>() || view->is<TypedArrayObject>())
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<DataViewObject>() || firstView()->is<TypedArrayObject>())
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<ArrayBufferObject*> buffer(cx, &args.thisv().toObject().as<ArrayBufferObject>());
/*
* 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);
}
}

View file

@ -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;

View file

@ -484,8 +484,9 @@ class TypedArrayObjectTemplate : public TypedArrayObject
}
static TypedArrayObject*
makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, uint32_t byteOffset, uint32_t len,
HandleObject proto)
makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> 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<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isResizable()) ||
(buffer->is<SharedArrayBufferObject>() &&
buffer->as<SharedArrayBufferObject>().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<ArrayBufferObject*> arrayBuffer, JSObject* protoArg)
Handle<ArrayBufferObject*> 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<DataViewObject>();
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<ArrayBufferObject*> 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<ArrayBufferObject*> 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));

View file

@ -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<TypedArrayObject*>(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<TypedArrayObject*>(this)).toInt32();
if (isOutOfBounds())
return 0;
return byteOffsetMaybeOutOfBounds();
}
uint32_t byteLength() const {
return byteLengthValue(const_cast<TypedArrayObject*>(this)).toInt32();
return length() * bytesPerElement();
}
uint32_t length() const {
return lengthValue(const_cast<TypedArrayObject*>(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<ArrayBufferObject*> arrayBuffer, JSObject* proto);
Handle<ArrayBufferObject*> 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<DataViewObject*>(this)).toInt32();
if (isOutOfBounds())
return 0;
return byteOffsetMaybeOutOfBounds();
}
uint32_t byteLength() const {
return byteLengthValue(const_cast<DataViewObject*>(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);