diff options
Diffstat (limited to 'js/src/vm/ArrayBufferViewObject.cpp')
-rw-r--r-- | js/src/vm/ArrayBufferViewObject.cpp | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/js/src/vm/ArrayBufferViewObject.cpp b/js/src/vm/ArrayBufferViewObject.cpp new file mode 100644 index 0000000000..4bcd7890c1 --- /dev/null +++ b/js/src/vm/ArrayBufferViewObject.cpp @@ -0,0 +1,447 @@ +/* -*- 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/ErrorReport.h" +#include "js/experimental/TypedData.h" // JS_GetArrayBufferView{Data,Buffer,Length,ByteOffset}, JS_GetObjectAsArrayBufferView, JS_IsArrayBufferViewObject +#include "js/SharedArrayBuffer.h" +#include "vm/Compartment.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. It +// updates the object's data pointer if it points to inline data in an object +// that was moved. +/* static */ +void ArrayBufferViewObject::trace(JSTracer* trc, JSObject* obj) { + ArrayBufferViewObject* view = &obj->as<ArrayBufferViewObject>(); + + // Update view's data pointer if it moved. + if (view->hasBuffer()) { + JSObject* bufferObj = &view->bufferValue().toObject(); + ArrayBufferObject* buffer = nullptr; + if (gc::MaybeForwardedObjectIs<FixedLengthArrayBufferObject>(bufferObj)) { + buffer = + &gc::MaybeForwardedObjectAs<FixedLengthArrayBufferObject>(bufferObj); + } else if (gc::MaybeForwardedObjectIs<ResizableArrayBufferObject>( + bufferObj)) { + buffer = + &gc::MaybeForwardedObjectAs<ResizableArrayBufferObject>(bufferObj); + } + if (buffer) { + size_t offset = view->byteOffset(); + MOZ_ASSERT_IF(!buffer->dataPointer(), offset == 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. + view->notifyBufferMoved( + static_cast<uint8_t*>(view->dataPointerEither_()) - offset, + buffer->dataPointer()); + } + } +} + +template <> +bool JSObject::is<js::ArrayBufferViewObject>() const { + return is<DataViewObject>() || is<TypedArrayObject>(); +} + +void ArrayBufferViewObject::notifyBufferDetached() { + MOZ_ASSERT(!isSharedMemory()); + MOZ_ASSERT(hasBuffer()); + MOZ_ASSERT(!bufferUnshared()->isLengthPinned()); + + setFixedSlot(LENGTH_SLOT, PrivateValue(size_t(0))); + setFixedSlot(BYTEOFFSET_SLOT, PrivateValue(size_t(0))); + setFixedSlot(DATA_SLOT, UndefinedValue()); +} + +void ArrayBufferViewObject::notifyBufferMoved(uint8_t* srcBufStart, + uint8_t* dstBufStart) { + MOZ_ASSERT(!isSharedMemory()); + MOZ_ASSERT(hasBuffer()); + + if (srcBufStart != dstBufStart) { + void* data = dstBufStart + byteOffset(); + getFixedSlotRef(DATA_SLOT).unbarrieredSet(PrivateValue(data)); + } +} + +/* static */ +bool ArrayBufferViewObject::ensureNonInline( + JSContext* cx, Handle<ArrayBufferViewObject*> view) { + MOZ_ASSERT(!view->isSharedMemory()); + // Create an ArrayBuffer for the data if it was in the view. + ArrayBufferObjectMaybeShared* buffer = ensureBufferObject(cx, view); + if (!buffer) { + return false; + } + Rooted<ArrayBufferObject*> unsharedBuffer(cx, + &buffer->as<ArrayBufferObject>()); + return ArrayBufferObject::ensureNonInline(cx, unsharedBuffer); +} + +/* static */ +ArrayBufferObjectMaybeShared* ArrayBufferViewObject::ensureBufferObject( + JSContext* cx, Handle<ArrayBufferViewObject*> thisObject) { + if (thisObject->is<TypedArrayObject>()) { + Rooted<TypedArrayObject*> typedArray(cx, + &thisObject->as<TypedArrayObject>()); + if (!TypedArrayObject::ensureHasBuffer(cx, typedArray)) { + return nullptr; + } + } + return thisObject->bufferEither(); +} + +bool ArrayBufferViewObject::init(JSContext* cx, + ArrayBufferObjectMaybeShared* buffer, + size_t byteOffset, size_t length, + uint32_t bytesPerElement) { + MOZ_ASSERT_IF(!buffer, byteOffset == 0); + MOZ_ASSERT_IF(buffer, !buffer->isDetached()); + + MOZ_ASSERT(byteOffset <= ArrayBufferObject::ByteLengthLimit); + MOZ_ASSERT(length <= ArrayBufferObject::ByteLengthLimit); + MOZ_ASSERT(byteOffset + length <= ArrayBufferObject::ByteLengthLimit); + + MOZ_ASSERT_IF(is<TypedArrayObject>(), + length <= TypedArrayObject::ByteLengthLimit / 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<SharedArrayBufferObject>()) { + setIsSharedMemory(); + } + + initFixedSlot(BYTEOFFSET_SLOT, PrivateValue(byteOffset)); + initFixedSlot(LENGTH_SLOT, PrivateValue(length)); + if (buffer) { + initFixedSlot(BUFFER_SLOT, ObjectValue(*buffer)); + } else { + MOZ_ASSERT(!isSharedMemory()); + initFixedSlot(BUFFER_SLOT, JS::FalseValue()); + } + + if (buffer) { + SharedMem<uint8_t*> ptr = buffer->dataPointerEither(); + initDataPointer(ptr + byteOffset); + + // 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() > 0, !cx->nursery().isInside(ptr)); + } else { + MOZ_ASSERT(is<FixedLengthTypedArrayObject>()); + MOZ_ASSERT(length * bytesPerElement <= + FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT); + void* data = fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START); + initReservedSlot(DATA_SLOT, PrivateValue(data)); + memset(data, 0, length * bytesPerElement); +#ifdef DEBUG + if (length == 0) { + uint8_t* elements = static_cast<uint8_t*>(data); + elements[0] = ZeroLengthArrayData; + } +#endif + } + +#ifdef DEBUG + if (buffer) { + size_t viewByteLength = length * bytesPerElement; + size_t viewByteOffset = byteOffset; + size_t bufferByteLength = buffer->byteLength(); + // Unwraps are safe: both are for the pointer value. + MOZ_ASSERT_IF(buffer->is<ArrayBufferObject>(), + buffer->dataPointerEither().unwrap(/*safe*/) <= + dataPointerEither().unwrap(/*safe*/)); + MOZ_ASSERT(bufferByteLength - viewByteOffset >= viewByteLength); + MOZ_ASSERT(viewByteOffset <= bufferByteLength); + } +#endif + + // ArrayBufferObjects track their views to support detaching. + if (buffer && buffer->is<ArrayBufferObject>()) { + if (!buffer->as<ArrayBufferObject>().addView(cx, this)) { + return false; + } + } + + return true; +} + +bool ArrayBufferViewObject::hasResizableBuffer() const { + if (auto* buffer = bufferEither()) { + return buffer->isResizable(); + } + return false; +} + +#if defined(DEBUG) || defined(JS_JITSPEW) +void ArrayBufferViewObject::dumpOwnFields(js::JSONPrinter& json) const { + json.formatProperty("length", "%zu", + size_t(getFixedSlot(LENGTH_SLOT).toPrivate())); + json.formatProperty("byteOffset", "%zu", + size_t(getFixedSlot(BYTEOFFSET_SLOT).toPrivate())); + void* data = dataPointerEither_(); + if (data) { + json.formatProperty("data", "0x%p", data); + } else { + json.nullProperty("data"); + } +} + +void ArrayBufferViewObject::dumpOwnStringContent( + js::GenericPrinter& out) const { + out.printf("length=%zu, byteOffset=%zu, ", + size_t(getFixedSlot(LENGTH_SLOT).toPrivate()), + size_t(getFixedSlot(BYTEOFFSET_SLOT).toPrivate())); + void* data = dataPointerEither_(); + if (data) { + out.printf("data=0x%p", data); + } else { + out.put("data=null"); + } +} +#endif + +/* JS Public API */ + +JS_PUBLIC_API bool JS_IsArrayBufferViewObject(JSObject* obj) { + return obj->canUnwrapAs<ArrayBufferViewObject>(); +} + +JS_PUBLIC_API JSObject* js::UnwrapArrayBufferView(JSObject* obj) { + return obj->maybeUnwrapIf<ArrayBufferViewObject>(); +} + +JS_PUBLIC_API void* JS_GetArrayBufferViewData(JSObject* obj, + bool* isSharedMemory, + const JS::AutoRequireNoGC&) { + ArrayBufferViewObject* view = obj->maybeUnwrapAs<ArrayBufferViewObject>(); + if (!view) { + return nullptr; + } + + *isSharedMemory = view->isSharedMemory(); + return view->dataPointerEither().unwrap( + /*safe - caller sees isSharedMemory flag*/); +} + +JS_PUBLIC_API uint8_t* JS_GetArrayBufferViewFixedData(JSObject* obj, + uint8_t* buffer, + size_t bufSize) { + ArrayBufferViewObject* view = obj->maybeUnwrapAs<ArrayBufferViewObject>(); + 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<FixedLengthTypedArrayObject>()) { + auto* ta = &view->as<FixedLengthTypedArrayObject>(); + if (ta->hasInlineElements()) { + size_t bytes = ta->byteLength(); + if (bytes > bufSize) { + return nullptr; // Does not fit. + } + memcpy(buffer, view->dataPointerUnshared(), bytes); + return buffer; + } + } + + return static_cast<uint8_t*>(view->dataPointerUnshared()); +} + +JS_PUBLIC_API JSObject* JS_GetArrayBufferViewBuffer(JSContext* cx, + HandleObject obj, + bool* isSharedMemory) { + AssertHeapIsIdle(); + CHECK_THREAD(cx); + cx->check(obj); + + Rooted<ArrayBufferViewObject*> unwrappedView( + cx, obj->maybeUnwrapAs<ArrayBufferViewObject>()); + if (!unwrappedView) { + ReportAccessDenied(cx); + return nullptr; + } + + ArrayBufferObjectMaybeShared* unwrappedBuffer; + { + AutoRealm ar(cx, unwrappedView); + unwrappedBuffer = + ArrayBufferViewObject::ensureBufferObject(cx, unwrappedView); + if (!unwrappedBuffer) { + return nullptr; + } + } + *isSharedMemory = unwrappedBuffer->is<SharedArrayBufferObject>(); + + RootedObject buffer(cx, unwrappedBuffer); + if (!cx->compartment()->wrap(cx, &buffer)) { + return nullptr; + } + + return buffer; +} + +JS_PUBLIC_API size_t JS_GetArrayBufferViewByteLength(JSObject* obj) { + obj = obj->maybeUnwrapAs<ArrayBufferViewObject>(); + if (!obj) { + return 0; + } + size_t length = obj->is<DataViewObject>() + ? obj->as<DataViewObject>().byteLength().valueOr(0) + : obj->as<TypedArrayObject>().byteLength().valueOr(0); + return length; +} + +bool JS::ArrayBufferView::isDetached() const { + MOZ_ASSERT(obj); + return obj->as<ArrayBufferViewObject>().hasDetachedBuffer(); +} + +bool JS::ArrayBufferView::isResizable() const { + MOZ_ASSERT(obj); + return obj->as<ArrayBufferViewObject>().hasResizableBuffer(); +} + +JS_PUBLIC_API size_t JS_GetArrayBufferViewByteOffset(JSObject* obj) { + obj = obj->maybeUnwrapAs<ArrayBufferViewObject>(); + if (!obj) { + return 0; + } + size_t offset = obj->is<DataViewObject>() + ? obj->as<DataViewObject>().byteOffset().valueOr(0) + : obj->as<TypedArrayObject>().byteOffset().valueOr(0); + return offset; +} + +JS_PUBLIC_API mozilla::Span<uint8_t> JS::ArrayBufferView::getData( + bool* isSharedMemory, const AutoRequireNoGC&) { + MOZ_ASSERT(obj->is<ArrayBufferViewObject>()); + size_t byteLength = obj->is<DataViewObject>() + ? obj->as<DataViewObject>().byteLength().valueOr(0) + : obj->as<TypedArrayObject>().byteLength().valueOr(0); + ArrayBufferViewObject& view = obj->as<ArrayBufferViewObject>(); + *isSharedMemory = view.isSharedMemory(); + return {static_cast<uint8_t*>(view.dataPointerEither().unwrap( + /*safe - caller sees isShared flag*/)), + byteLength}; +} + +JS_PUBLIC_API JSObject* JS_GetObjectAsArrayBufferView(JSObject* obj, + size_t* length, + bool* isSharedMemory, + uint8_t** data) { + obj = obj->maybeUnwrapIf<ArrayBufferViewObject>(); + if (!obj) { + return nullptr; + } + + js::GetArrayBufferViewLengthAndData(obj, length, isSharedMemory, data); + return obj; +} + +JS_PUBLIC_API void js::GetArrayBufferViewLengthAndData(JSObject* obj, + size_t* length, + bool* isSharedMemory, + uint8_t** data) { + JS::AutoAssertNoGC nogc; + auto span = + JS::ArrayBufferView::fromObject(obj).getData(isSharedMemory, nogc); + *data = span.data(); + *length = span.Length(); +} + +JS_PUBLIC_API bool JS::IsArrayBufferViewShared(JSObject* obj) { + ArrayBufferViewObject* view = obj->maybeUnwrapAs<ArrayBufferViewObject>(); + if (!view) { + return false; + } + return view->isSharedMemory(); +} + +JS_PUBLIC_API bool JS::IsLargeArrayBufferView(JSObject* obj) { +#ifdef JS_64BIT + obj = &obj->unwrapAs<ArrayBufferViewObject>(); + size_t len = obj->is<DataViewObject>() + ? obj->as<DataViewObject>().byteLength().valueOr(0) + : obj->as<TypedArrayObject>().byteLength().valueOr(0); + return len > ArrayBufferObject::ByteLengthLimitForSmallBuffer; +#else + // Large ArrayBuffers are not supported on 32-bit. + static_assert(ArrayBufferObject::ByteLengthLimit == + ArrayBufferObject::ByteLengthLimitForSmallBuffer); + return false; +#endif +} + +JS_PUBLIC_API bool JS::IsResizableArrayBufferView(JSObject* obj) { + auto* view = &obj->unwrapAs<ArrayBufferViewObject>(); + if (auto* buffer = view->bufferEither()) { + return buffer->isResizable(); + } + return false; +} + +JS_PUBLIC_API bool JS::PinArrayBufferOrViewLength(JSObject* obj, bool pin) { + ArrayBufferObjectMaybeShared* buffer = + obj->maybeUnwrapIf<ArrayBufferObjectMaybeShared>(); + if (buffer) { + return buffer->pinLength(pin); + } + + ArrayBufferViewObject* view = obj->maybeUnwrapAs<ArrayBufferViewObject>(); + if (view) { + return view->pinLength(pin); + } + + return false; +} + +JS_PUBLIC_API bool JS::EnsureNonInlineArrayBufferOrView(JSContext* cx, + JSObject* obj) { + if (obj->is<SharedArrayBufferObject>()) { + // Always locked and out of line. + return true; + } + + auto* buffer = obj->maybeUnwrapIf<ArrayBufferObject>(); + if (buffer) { + Rooted<ArrayBufferObject*> rootedBuffer(cx, buffer); + return ArrayBufferObject::ensureNonInline(cx, rootedBuffer); + } + + auto* view = obj->maybeUnwrapIf<ArrayBufferViewObject>(); + if (view) { + if (view->isSharedMemory()) { + // Always locked and out of line. + return true; + } + Rooted<ArrayBufferViewObject*> rootedView(cx, view); + return ArrayBufferViewObject::ensureNonInline(cx, rootedView); + } + + JS_ReportErrorASCII(cx, "unhandled type"); + return false; +} |