diff options
Diffstat (limited to 'js/src/vm/ArrayBufferViewObject.cpp')
-rw-r--r-- | js/src/vm/ArrayBufferViewObject.cpp | 175 |
1 files changed, 173 insertions, 2 deletions
diff --git a/js/src/vm/ArrayBufferViewObject.cpp b/js/src/vm/ArrayBufferViewObject.cpp index 4bcd7890c1..27004f3e2a 100644 --- a/js/src/vm/ArrayBufferViewObject.cpp +++ b/js/src/vm/ArrayBufferViewObject.cpp @@ -41,7 +41,7 @@ void ArrayBufferViewObject::trace(JSTracer* trc, JSObject* obj) { &gc::MaybeForwardedObjectAs<ResizableArrayBufferObject>(bufferObj); } if (buffer) { - size_t offset = view->byteOffset(); + size_t offset = view->dataPointerOffset(); MOZ_ASSERT_IF(!buffer->dataPointer(), offset == 0); // The data may or may not be inline with the buffer. The buffer can only @@ -69,13 +69,22 @@ void ArrayBufferViewObject::notifyBufferDetached() { setFixedSlot(DATA_SLOT, UndefinedValue()); } +void ArrayBufferViewObject::notifyBufferResized() { + MOZ_ASSERT(!isSharedMemory()); + MOZ_ASSERT(hasBuffer()); + MOZ_ASSERT(!bufferUnshared()->isLengthPinned()); + MOZ_ASSERT(bufferUnshared()->isResizable()); + + computeResizableLengthAndByteOffset(bytesPerElement()); +} + void ArrayBufferViewObject::notifyBufferMoved(uint8_t* srcBufStart, uint8_t* dstBufStart) { MOZ_ASSERT(!isSharedMemory()); MOZ_ASSERT(hasBuffer()); if (srcBufStart != dstBufStart) { - void* data = dstBufStart + byteOffset(); + void* data = dstBufStart + dataPointerOffset(); getFixedSlotRef(DATA_SLOT).unbarrieredSet(PrivateValue(data)); } } @@ -183,6 +192,76 @@ bool ArrayBufferViewObject::init(JSContext* cx, return true; } +bool ArrayBufferViewObject::initResizable(JSContext* cx, + ArrayBufferObjectMaybeShared* buffer, + size_t byteOffset, size_t length, + uint32_t bytesPerElement, + AutoLength autoLength) { + MOZ_ASSERT(buffer->isResizable()); + + if (!init(cx, buffer, byteOffset, length, bytesPerElement)) { + return false; + } + + initFixedSlot(AUTO_LENGTH_SLOT, BooleanValue(static_cast<bool>(autoLength))); + initFixedSlot(INITIAL_LENGTH_SLOT, PrivateValue(length)); + initFixedSlot(INITIAL_BYTE_OFFSET_SLOT, PrivateValue(byteOffset)); + + // Compute the actual byteLength and byteOffset for non-shared buffers. + if (!isSharedMemory()) { + computeResizableLengthAndByteOffset(bytesPerElement); + } + + MOZ_ASSERT(!isOutOfBounds(), "can't create out-of-bounds views"); + + return true; +} + +void ArrayBufferViewObject::computeResizableLengthAndByteOffset( + size_t bytesPerElement) { + MOZ_ASSERT(!isSharedMemory()); + MOZ_ASSERT(hasBuffer()); + MOZ_ASSERT(!bufferUnshared()->isLengthPinned()); + MOZ_ASSERT(bufferUnshared()->isResizable()); + + size_t byteOffsetStart = initialByteOffset(); + size_t bufferByteLength = bufferUnshared()->byteLength(); + + // Out-of-bounds if the byteOffset exceeds the buffer length. + if (byteOffsetStart > bufferByteLength) { + setFixedSlot(LENGTH_SLOT, PrivateValue(size_t(0))); + setFixedSlot(BYTEOFFSET_SLOT, PrivateValue(size_t(0))); + return; + } + + size_t length; + if (isAutoLength()) { + length = (bufferByteLength - byteOffsetStart) / bytesPerElement; + } else { + length = initialLength(); + + // Out-of-bounds if the byteOffset end index exceeds the buffer length. + size_t byteOffsetEnd = byteOffsetStart + length * bytesPerElement; + if (byteOffsetEnd > bufferByteLength) { + setFixedSlot(LENGTH_SLOT, PrivateValue(size_t(0))); + setFixedSlot(BYTEOFFSET_SLOT, PrivateValue(size_t(0))); + return; + } + } + + setFixedSlot(LENGTH_SLOT, PrivateValue(length)); + setFixedSlot(BYTEOFFSET_SLOT, PrivateValue(byteOffsetStart)); +} + +size_t ArrayBufferViewObject::bytesPerElement() const { + if (is<TypedArrayObject>()) { + return as<TypedArrayObject>().bytesPerElement(); + } + + MOZ_ASSERT(is<DataViewObject>()); + return 1; +} + bool ArrayBufferViewObject::hasResizableBuffer() const { if (auto* buffer = bufferEither()) { return buffer->isResizable(); @@ -190,6 +269,98 @@ bool ArrayBufferViewObject::hasResizableBuffer() const { return false; } +size_t ArrayBufferViewObject::dataPointerOffset() const { + // Views without a buffer have a zero offset. + if (!hasBuffer()) { + MOZ_ASSERT(byteOffsetSlotValue() == 0); + return 0; + } + + // Views on shared buffers store the offset in |byteOffset|. + if (isSharedMemory()) { + return byteOffsetSlotValue(); + } + + // Can be called during tracing, so the buffer is possibly forwarded. + const auto* bufferObj = gc::MaybeForwarded(&bufferValue().toObject()); + + // Two distinct classes are used for non-shared buffers. + MOZ_ASSERT( + gc::MaybeForwardedObjectIs<FixedLengthArrayBufferObject>(bufferObj) || + gc::MaybeForwardedObjectIs<ResizableArrayBufferObject>(bufferObj)); + + // Ensure these two classes can be casted to ArrayBufferObject. + static_assert( + std::is_base_of_v<ArrayBufferObject, FixedLengthArrayBufferObject>); + static_assert( + std::is_base_of_v<ArrayBufferObject, ResizableArrayBufferObject>); + + // Manual cast necessary because the buffer is possibly forwarded. + const auto* buffer = static_cast<const ArrayBufferObject*>(bufferObj); + + // Views on resizable buffers store the offset in |initialByteOffset|. + if (buffer->isResizable() && !buffer->isDetached()) { + return initialByteOffsetValue(); + } + + // Callers expect that this method returns zero for detached buffers. + MOZ_ASSERT_IF(buffer->isDetached(), byteOffsetSlotValue() == 0); + + // Views on fixed-length buffers store the offset in |byteOffset|. + return byteOffsetSlotValue(); +} + +mozilla::Maybe<size_t> ArrayBufferViewObject::byteOffset() const { + // |byteOffset| is set to zero for detached or out-of-bounds views, so a + // non-zero value indicates the view is in-bounds. + size_t byteOffset = byteOffsetSlotValue(); + if (byteOffset > 0) { + MOZ_ASSERT(!hasDetachedBuffer()); + MOZ_ASSERT_IF(hasResizableBuffer(), !isOutOfBounds()); + return mozilla::Some(byteOffset); + } + if (hasDetachedBufferOrIsOutOfBounds()) { + return mozilla::Nothing{}; + } + return mozilla::Some(0); +} + +mozilla::Maybe<size_t> ArrayBufferViewObject::length() const { + // |length| is set to zero for detached or out-of-bounds views, so a non-zero + // value indicates the view is in-bounds. + size_t length = lengthSlotValue(); + if (MOZ_LIKELY(length > 0)) { + MOZ_ASSERT(!hasDetachedBuffer()); + MOZ_ASSERT_IF(hasResizableBuffer(), !isOutOfBounds()); + MOZ_ASSERT(!isSharedMemory() || !hasResizableBuffer() || !isAutoLength(), + "length is zero for auto-length growable shared buffers"); + return mozilla::Some(length); + } + + if (hasDetachedBufferOrIsOutOfBounds()) { + return mozilla::Nothing{}; + } + + if (isSharedMemory()) { + auto* buffer = bufferShared(); + MOZ_ASSERT(buffer, "shared memory doesn't use inline data"); + + // Views backed by a growable SharedArrayBuffer can never get out-of-bounds, + // but we have to dynamically compute the length when the auto-length flag + // is set. + if (buffer->isGrowable() && isAutoLength()) { + size_t bufferByteLength = buffer->byteLength(); + size_t byteOffset = byteOffsetSlotValue(); + MOZ_ASSERT(byteOffset <= bufferByteLength); + MOZ_ASSERT(byteOffset == initialByteOffset(), + "views on growable shared buffers can't get out-of-bounds"); + + return mozilla::Some((bufferByteLength - byteOffset) / bytesPerElement()); + } + } + return mozilla::Some(0); +} + #if defined(DEBUG) || defined(JS_JITSPEW) void ArrayBufferViewObject::dumpOwnFields(js::JSONPrinter& json) const { json.formatProperty("length", "%zu", |