summaryrefslogtreecommitdiffstats
path: root/js/src/vm/ArrayBufferViewObject.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/ArrayBufferViewObject.cpp')
-rw-r--r--js/src/vm/ArrayBufferViewObject.cpp175
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",