summaryrefslogtreecommitdiffstats
path: root/js/src/vm/ArrayBufferViewObject.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/vm/ArrayBufferViewObject.cpp319
1 files changed, 319 insertions, 0 deletions
diff --git a/js/src/vm/ArrayBufferViewObject.cpp b/js/src/vm/ArrayBufferViewObject.cpp
new file mode 100644
index 0000000000..11e6d9fa72
--- /dev/null
+++ b/js/src/vm/ArrayBufferViewObject.cpp
@@ -0,0 +1,319 @@
+/* -*- 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/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();
+ if (gc::MaybeForwardedObjectIs<ArrayBufferObject>(bufferObj)) {
+ auto* buffer = &gc::MaybeForwardedObjectAs<ArrayBufferObject>(bufferObj);
+
+ 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.
+ void* oldData = view->dataPointerEither_();
+ void* data = buffer->dataPointer() + offset;
+ if (data != oldData) {
+ view->getFixedSlotRef(DATA_SLOT).unbarrieredSet(PrivateValue(data));
+ }
+ }
+ }
+}
+
+template <>
+bool JSObject::is<js::ArrayBufferViewObject>() const {
+ return is<DataViewObject>() || is<TypedArrayObject>();
+}
+
+void ArrayBufferViewObject::notifyBufferDetached() {
+ MOZ_ASSERT(!isSharedMemory());
+ MOZ_ASSERT(hasBuffer());
+
+ setFixedSlot(LENGTH_SLOT, PrivateValue(size_t(0)));
+ setFixedSlot(BYTEOFFSET_SLOT, PrivateValue(size_t(0)));
+ setFixedSlot(DATA_SLOT, UndefinedValue());
+}
+
+/* static */
+ArrayBufferObjectMaybeShared* ArrayBufferViewObject::bufferObject(
+ 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::MaxByteLength);
+ MOZ_ASSERT(length <= ArrayBufferObject::MaxByteLength);
+ MOZ_ASSERT(byteOffset + length <= ArrayBufferObject::MaxByteLength);
+
+ MOZ_ASSERT_IF(is<TypedArrayObject>(),
+ length <= 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<SharedArrayBufferObject>()) {
+ setIsSharedMemory();
+ }
+
+ initFixedSlot(BYTEOFFSET_SLOT, PrivateValue(byteOffset));
+ initFixedSlot(LENGTH_SLOT, PrivateValue(length));
+ initFixedSlot(BUFFER_SLOT, ObjectOrNullValue(buffer));
+
+ 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<TypedArrayObject>());
+ MOZ_ASSERT(length * bytesPerElement <=
+ TypedArrayObject::INLINE_BUFFER_LIMIT);
+ void* data = fixedData(TypedArrayObject::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;
+}
+
+/* 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<TypedArrayObject>()) {
+ TypedArrayObject* ta = &view->as<TypedArrayObject>();
+ 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::bufferObject(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()
+ : obj->as<TypedArrayObject>().byteLength();
+ return length;
+}
+
+bool JS::ArrayBufferView::isDetached() const {
+ MOZ_ASSERT(obj);
+ return obj->as<ArrayBufferViewObject>().hasDetachedBuffer();
+}
+
+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()
+ : obj->as<TypedArrayObject>().byteOffset();
+ return offset;
+}
+
+JS_PUBLIC_API uint8_t* JS::ArrayBufferView::getLengthAndData(
+ size_t* length, bool* isSharedMemory, const AutoRequireNoGC&) {
+ MOZ_ASSERT(obj->is<ArrayBufferViewObject>());
+ size_t byteLength = obj->is<DataViewObject>()
+ ? obj->as<DataViewObject>().byteLength()
+ : obj->as<TypedArrayObject>().byteLength();
+ *length = byteLength; // *Not* the number of elements in the array, if
+ // sizeof(elt) != 1.
+
+ ArrayBufferViewObject& view = obj->as<ArrayBufferViewObject>();
+ *isSharedMemory = view.isSharedMemory();
+ return static_cast<uint8_t*>(
+ view.dataPointerEither().unwrap(/*safe - caller sees isShared flag*/));
+}
+
+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;
+ *data = JS::ArrayBufferView::fromObject(obj).getLengthAndData(
+ length, isSharedMemory, nogc);
+}
+
+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()
+ : obj->as<TypedArrayObject>().byteLength();
+ return len > ArrayBufferObject::MaxByteLengthForSmallBuffer;
+#else
+ // Large ArrayBuffers are not supported on 32-bit.
+ static_assert(ArrayBufferObject::MaxByteLength ==
+ ArrayBufferObject::MaxByteLengthForSmallBuffer);
+ return false;
+#endif
+}