From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- js/src/vm/ArrayBufferViewObject.cpp | 298 ++++++++++++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 js/src/vm/ArrayBufferViewObject.cpp (limited to 'js/src/vm/ArrayBufferViewObject.cpp') diff --git a/js/src/vm/ArrayBufferViewObject.cpp b/js/src/vm/ArrayBufferViewObject.cpp new file mode 100644 index 0000000000..07ce5c207a --- /dev/null +++ b/js/src/vm/ArrayBufferViewObject.cpp @@ -0,0 +1,298 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "vm/ArrayBufferViewObject.h" + +#include "builtin/DataViewObject.h" +#include "gc/Nursery.h" +#include "js/experimental/TypedData.h" // JS_GetArrayBufferView{Data,Buffer,Length,ByteOffset}, JS_GetObjectAsArrayBufferView, JS_IsArrayBufferViewObject +#include "js/SharedArrayBuffer.h" +#include "vm/JSContext.h" +#include "vm/TypedArrayObject.h" + +#include "gc/Nursery-inl.h" +#include "vm/ArrayBufferObject-inl.h" +#include "vm/NativeObject-inl.h" + +using namespace js; + +/* + * This method is used to trace TypedArrayObjects and DataViewObjects. We need + * a custom tracer to move the object's data pointer if its owner was moved and + * stores its data inline. + */ +/* static */ +void ArrayBufferViewObject::trace(JSTracer* trc, JSObject* objArg) { + ArrayBufferViewObject* obj = &objArg->as(); + HeapSlot& bufSlot = obj->getFixedSlotRef(BUFFER_SLOT); + TraceEdge(trc, &bufSlot, "ArrayBufferViewObject.buffer"); + + // Update obj's data pointer if it moved. + if (bufSlot.isObject()) { + if (gc::MaybeForwardedObjectIs(&bufSlot.toObject())) { + ArrayBufferObject& buf = + gc::MaybeForwardedObjectAs(&bufSlot.toObject()); + BufferSize offset = obj->byteOffset(); + + MOZ_ASSERT_IF(buf.dataPointer() == nullptr, offset.get() == 0); + + // 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. + size_t nfixed = obj->numFixedSlotsMaybeForwarded(); + obj->setPrivateUnbarriered(nfixed, buf.dataPointer() + offset.get()); + } + } +} + +template <> +bool JSObject::is() const { + return is() || is(); +} + +void ArrayBufferViewObject::notifyBufferDetached() { + MOZ_ASSERT(!isSharedMemory()); + MOZ_ASSERT(hasBuffer()); + + setFixedSlot(LENGTH_SLOT, PrivateValue(size_t(0))); + setFixedSlot(BYTEOFFSET_SLOT, PrivateValue(size_t(0))); + + setPrivate(nullptr); +} + +/* static */ +ArrayBufferObjectMaybeShared* ArrayBufferViewObject::bufferObject( + JSContext* cx, Handle thisObject) { + if (thisObject->is()) { + Rooted typedArray(cx, + &thisObject->as()); + if (!TypedArrayObject::ensureHasBuffer(cx, typedArray)) { + return nullptr; + } + } + return thisObject->bufferEither(); +} + +bool ArrayBufferViewObject::init(JSContext* cx, + ArrayBufferObjectMaybeShared* buffer, + BufferSize byteOffset, BufferSize length, + uint32_t bytesPerElement) { + MOZ_ASSERT_IF(!buffer, byteOffset.get() == 0); + MOZ_ASSERT_IF(buffer, !buffer->isDetached()); + + MOZ_ASSERT(byteOffset.get() <= ArrayBufferObject::maxBufferByteLength()); + MOZ_ASSERT(length.get() <= ArrayBufferObject::maxBufferByteLength()); + MOZ_ASSERT(byteOffset.get() + length.get() <= + ArrayBufferObject::maxBufferByteLength()); + + MOZ_ASSERT_IF( + is(), + length.get() < TypedArrayObject::maxByteLength() / bytesPerElement); + + // The isSharedMemory property is invariant. Self-hosting code that + // sets BUFFER_SLOT or the private slot (if it does) must maintain it by + // always setting those to reference shared memory. + if (buffer && buffer->is()) { + setIsSharedMemory(); + } + + initFixedSlot(BYTEOFFSET_SLOT, PrivateValue(byteOffset.get())); + initFixedSlot(LENGTH_SLOT, PrivateValue(length.get())); + initFixedSlot(BUFFER_SLOT, ObjectOrNullValue(buffer)); + + if (buffer) { + SharedMem ptr = buffer->dataPointerEither(); + initDataPointer(ptr + byteOffset.get()); + + // Only ArrayBuffers used for inline typed objects can have + // nursery-allocated data and we shouldn't see such buffers here. + MOZ_ASSERT_IF(buffer->byteLength().get() > 0, !cx->nursery().isInside(ptr)); + } else { + MOZ_ASSERT(is()); + MOZ_ASSERT(length.get() * bytesPerElement <= + TypedArrayObject::INLINE_BUFFER_LIMIT); + void* data = fixedData(TypedArrayObject::FIXED_DATA_START); + initPrivate(data); + memset(data, 0, length.get() * bytesPerElement); +#ifdef DEBUG + if (length.get() == 0) { + uint8_t* elements = static_cast(data); + elements[0] = ZeroLengthArrayData; + } +#endif + } + +#ifdef DEBUG + if (buffer) { + size_t viewByteLength = length.get() * bytesPerElement; + size_t viewByteOffset = byteOffset.get(); + size_t bufferByteLength = buffer->byteLength().get(); + // Unwraps are safe: both are for the pointer value. + MOZ_ASSERT_IF(IsArrayBuffer(buffer), + buffer->dataPointerEither().unwrap(/*safe*/) <= + dataPointerEither().unwrap(/*safe*/)); + MOZ_ASSERT(bufferByteLength - viewByteOffset >= viewByteLength); + MOZ_ASSERT(viewByteOffset <= bufferByteLength); + } + + // Verify that the private slot is at the expected place. + MOZ_ASSERT(numFixedSlots() == DATA_SLOT); +#endif + + // ArrayBufferObjects track their views to support detaching. + if (buffer && buffer->is()) { + if (!buffer->as().addView(cx, this)) { + return false; + } + } + + return true; +} + +/* JS Friend API */ + +JS_FRIEND_API bool JS_IsArrayBufferViewObject(JSObject* obj) { + return obj->canUnwrapAs(); +} + +JS_FRIEND_API JSObject* js::UnwrapArrayBufferView(JSObject* obj) { + return obj->maybeUnwrapIf(); +} + +JS_FRIEND_API void* JS_GetArrayBufferViewData(JSObject* obj, + bool* isSharedMemory, + const JS::AutoRequireNoGC&) { + ArrayBufferViewObject* view = obj->maybeUnwrapAs(); + if (!view) { + return nullptr; + } + + *isSharedMemory = view->isSharedMemory(); + return view->dataPointerEither().unwrap( + /*safe - caller sees isSharedMemory flag*/); +} + +JS_FRIEND_API uint8_t* JS_GetArrayBufferViewFixedData(JSObject* obj, + uint8_t* buffer, + size_t bufSize) { + ArrayBufferViewObject* view = obj->maybeUnwrapAs(); + if (!view) { + return nullptr; + } + + // Disallow shared memory until it is needed. + if (view->isSharedMemory()) { + return nullptr; + } + + // TypedArrays (but not DataViews) can have inline data, in which case we + // need to copy into the given buffer. + if (view->is()) { + TypedArrayObject* ta = &view->as(); + if (ta->hasInlineElements()) { + size_t bytes = ta->byteLength().get(); + if (bytes > bufSize) { + return nullptr; // Does not fit. + } + memcpy(buffer, view->dataPointerUnshared(), bytes); + return buffer; + } + } + + return static_cast(view->dataPointerUnshared()); +} + +JS_FRIEND_API JSObject* JS_GetArrayBufferViewBuffer(JSContext* cx, + HandleObject obj, + bool* isSharedMemory) { + AssertHeapIsIdle(); + CHECK_THREAD(cx); + cx->check(obj); + + Rooted unwrappedView( + cx, obj->maybeUnwrapAs()); + if (!unwrappedView) { + ReportAccessDenied(cx); + return nullptr; + } + + ArrayBufferObjectMaybeShared* unwrappedBuffer; + { + AutoRealm ar(cx, unwrappedView); + unwrappedBuffer = ArrayBufferViewObject::bufferObject(cx, unwrappedView); + if (!unwrappedBuffer) { + return nullptr; + } + } + *isSharedMemory = unwrappedBuffer->is(); + + RootedObject buffer(cx, unwrappedBuffer); + if (!cx->compartment()->wrap(cx, &buffer)) { + return nullptr; + } + + return buffer; +} + +JS_FRIEND_API uint32_t JS_GetArrayBufferViewByteLength(JSObject* obj) { + obj = obj->maybeUnwrapAs(); + if (!obj) { + return 0; + } + BufferSize length = obj->is() + ? obj->as().byteLength() + : obj->as().byteLength(); + return length.deprecatedGetUint32(); +} + +JS_FRIEND_API uint32_t JS_GetArrayBufferViewByteOffset(JSObject* obj) { + obj = obj->maybeUnwrapAs(); + if (!obj) { + return 0; + } + BufferSize offset = obj->is() + ? obj->as().byteOffset() + : obj->as().byteOffset(); + return offset.deprecatedGetUint32(); +} + +JS_FRIEND_API JSObject* JS_GetObjectAsArrayBufferView(JSObject* obj, + uint32_t* length, + bool* isSharedMemory, + uint8_t** data) { + obj = obj->maybeUnwrapIf(); + if (!obj) { + return nullptr; + } + + js::GetArrayBufferViewLengthAndData(obj, length, isSharedMemory, data); + return obj; +} + +JS_FRIEND_API void js::GetArrayBufferViewLengthAndData(JSObject* obj, + uint32_t* length, + bool* isSharedMemory, + uint8_t** data) { + MOZ_ASSERT(obj->is()); + + BufferSize byteLength = obj->is() + ? obj->as().byteLength() + : obj->as().byteLength(); + *length = byteLength.deprecatedGetUint32(); + + ArrayBufferViewObject& view = obj->as(); + *isSharedMemory = view.isSharedMemory(); + *data = static_cast( + view.dataPointerEither().unwrap(/*safe - caller sees isShared flag*/)); +} + +JS_PUBLIC_API bool JS::IsArrayBufferViewShared(JSObject* obj) { + ArrayBufferViewObject* view = obj->maybeUnwrapAs(); + if (!view) { + return false; + } + return view->isSharedMemory(); +} -- cgit v1.2.3