From b8c80b5536ec8f76d178cd57ab22df8d30f773ac Mon Sep 17 00:00:00 2001 From: Steve Fink Date: Thu, 9 May 2013 15:53:11 -0700 Subject: [PATCH] Bug 861925 - Allow grabbing data from ArrayBuffers and neutering them independently (in addition to Steal, which does both at the same time). r=Waldo --HG-- extra : rebase_source : 86a233d67fbd734b59ce78556b3a808e4d10faa2 --- js/src/jsfriendapi.h | 6 ++ js/src/vm/StructuredClone.cpp | 2 + js/src/vm/TypedArrayObject.cpp | 151 +++++++++++++++++++++++---------- js/src/vm/TypedArrayObject.h | 39 ++++++++- 4 files changed, 148 insertions(+), 50 deletions(-) diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 446be8bf703..41db6a21651 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -1240,6 +1240,12 @@ JS_GetArrayBufferViewData(JSObject *obj); extern JS_FRIEND_API(JSObject *) JS_GetArrayBufferViewBuffer(JSObject *obj); +/* + * Set an ArrayBuffer's length to 0 and neuter all of its views. + */ +extern JS_FRIEND_API(void) +JS_NeuterArrayBuffer(JSObject *obj, JSContext *cx); + /* * Check whether obj supports JS_GetDataView* APIs. */ diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index d1cccd7ce4b..1661d4a2b61 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -32,6 +32,8 @@ #include "mozilla/Endian.h" #include "mozilla/FloatingPoint.h" +#include + #include "jsapi.h" #include "jscntxt.h" #include "jsdate.h" diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 5a8ab682616..59b67f1552d 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -244,7 +244,7 @@ AllocateArrayBufferContents(JSContext *maybecx, uint32_t nbytes, uint8_t *initda memcpy(newheader->elements(), initdata, nbytes); // we rely on this being correct - ArrayBufferObject::setElementsHeader(newheader, nbytes); + ArrayBufferObject::updateElementsHeader(newheader, nbytes); return newheader; } @@ -274,7 +274,7 @@ ArrayBufferObject::allocateSlots(JSContext *maybecx, uint32_t bytes, uint8_t *co memset(elements, 0, bytes); } - setElementsHeader(getElementsHeader(), bytes); + initElementsHeader(getElementsHeader(), bytes); return true; } @@ -297,7 +297,7 @@ PostBarrierTypedArrayObject(JSObject *obj) // JS_USE_NEW_OBJECT_REPRESENTATION pending, since it will solve this much // more cleanly. struct OldObjectRepresentationHack { - uint32_t capacity; + uint32_t flags; uint32_t initializedLength; EncapsulatedPtr views; }; @@ -329,17 +329,35 @@ GetViewListRef(ArrayBufferObject *obj) return reinterpret_cast(obj->getElementsHeader())->views; } +void +ArrayBufferObject::neuterViews(JSContext *maybecx) +{ + ArrayBufferViewObject *view; + for (view = GetViewList(this); view; view = view->nextView()) { + view->neuter(); + + // Notify compiled jit code that the base pointer has moved. + if (maybecx) + MarkObjectStateChange(maybecx, view); + } + + // neuterAsmJSArrayBuffer adjusts state specific to the ArrayBuffer data + // itself, but it only affects the behavior of views + if (isAsmJSArrayBuffer()) + ArrayBufferObject::neuterAsmJSArrayBuffer(*this); +} + void ArrayBufferObject::changeContents(JSContext *maybecx, ObjectElements *newHeader) { // Grab out data before invalidating it. uint32_t byteLengthCopy = byteLength(); uintptr_t oldDataPointer = uintptr_t(dataPointer()); - ArrayBufferViewObject *viewListHead = GetViewList(this); + ArrayBufferViewObject *viewListHead = GetViewList(this); // Update all views. uintptr_t newDataPointer = uintptr_t(newHeader->elements()); - for (ArrayBufferViewObject *view = viewListHead; view; view = view->nextView()) { + for (ArrayBufferViewObject *view = viewListHead; view; view = view->nextView()) { uintptr_t newDataPtr = uintptr_t(view->getPrivate()) - oldDataPointer + newDataPointer; view->setPrivate(reinterpret_cast(newDataPtr)); @@ -348,20 +366,35 @@ ArrayBufferObject::changeContents(JSContext *maybecx, ObjectElements *newHeader) MarkObjectStateChange(maybecx, view); } - // Change to the new header (now, so we can use SetViewList). + // The list of views in the old header is reachable if the contents are + // being transferred, so NULL it out + SetViewList(this, NULL); + elements = newHeader->elements(); - // Initialize 'newHeader'. - ArrayBufferObject::setElementsHeader(newHeader, byteLengthCopy); - SetViewList(this, viewListHead); + initElementsHeader(newHeader, byteLengthCopy); + InitViewList(this, viewListHead); +} + +void +ArrayBufferObject::neuter(JSContext *cx) +{ + JS_ASSERT(cx); + if (hasDynamicElements() && !isAsmJSArrayBuffer()) { + ObjectElements *oldHeader = getElementsHeader(); + changeContents(cx, ObjectElements::fromElements(fixedElements())); + + FreeOp fop(cx->runtime(), false); + fop.free_(oldHeader); + } + + uint32_t byteLen = 0; + updateElementsHeader(getElementsHeader(), byteLen); } bool -ArrayBufferObject::uninlineData(JSContext *maybecx) +ArrayBufferObject::copyData(JSContext *maybecx) { - if (hasDynamicElements()) - return true; - ObjectElements *newHeader = AllocateArrayBufferContents(maybecx, byteLength(), dataPointer()); if (!newHeader) return false; @@ -370,6 +403,36 @@ ArrayBufferObject::uninlineData(JSContext *maybecx) return true; } +bool +ArrayBufferObject::ensureNonInline(JSContext *maybecx) +{ + if (hasDynamicElements()) + return true; + return copyData(maybecx); +} + +// If the ArrayBuffer already contains dynamic contents, hand them back. +// Otherwise, allocate some new contents and copy the data over, but in no case +// modify the original ArrayBuffer. (Also, any allocated contents will have no +// views linked to in its header.) +ObjectElements * +ArrayBufferObject::getTransferableContents(JSContext *maybecx, bool *callerOwns) +{ + if (hasDynamicElements() && !isAsmJSArrayBuffer()) { + *callerOwns = false; + return getElementsHeader(); + } + + uint32_t byteLen = byteLength(); + ObjectElements *newheader = AllocateArrayBufferContents(maybecx, byteLen, dataPointer()); + if (!newheader) + return NULL; + + initElementsHeader(newheader, byteLen); + *callerOwns = true; + return newheader; +} + #if defined(JS_ION) && defined(JS_CPU_X64) // To avoid dynamically checking bounds on each load/store, asm.js code relies // on the SIGSEGV handler in AsmJSSignalHandlers.cpp. However, this only works @@ -479,8 +542,9 @@ ArrayBufferObject::neuterAsmJSArrayBuffer(ArrayBufferObject &buffer) bool ArrayBufferObject::prepareForAsmJS(JSContext *cx, Handle buffer) { - if (!buffer->uninlineData(cx)) + if (!buffer->copyData(cx)) return false; + JS_ASSERT(buffer->hasDynamicElements()); buffer->getElementsHeader()->setIsAsmJSArrayBuffer(); return true; @@ -597,41 +661,28 @@ ArrayBufferObject::createDataViewForThis(JSContext *cx, unsigned argc, Value *vp } bool -ArrayBufferObject::stealContents(JSContext *cx, JSObject *obj, void **contents, - uint8_t **data) +ArrayBufferObject::stealContents(JSContext *cx, JSObject *obj, void **contents, uint8_t **data) { ArrayBufferObject &buffer = obj->as(); - ArrayBufferViewObject *views = GetViewList(&buffer); - js::ObjectElements *header = js::ObjectElements::fromElements((js::HeapSlot*)buffer.dataPointer()); - if (buffer.hasDynamicElements() && !buffer.isAsmJSArrayBuffer()) { - SetViewList(&buffer, nullptr); - *contents = header; - *data = buffer.dataPointer(); - buffer.setFixedElements(); - header = js::ObjectElements::fromElements((js::HeapSlot*)buffer.dataPointer()); - } else { - uint32_t length = buffer.byteLength(); - js::ObjectElements *newheader = - AllocateArrayBufferContents(cx, length, buffer.dataPointer()); - if (!newheader) { - js_ReportOutOfMemory(cx); - return false; - } + // Make the data stealable + bool own; + ObjectElements *header = reinterpret_cast(buffer.getTransferableContents(cx, &own)); + if (!header) + return false; + *contents = header; + *data = reinterpret_cast(header + 1); - ArrayBufferObject::setElementsHeader(newheader, length); - *contents = newheader; - *data = reinterpret_cast(newheader + 1); + // Neuter the views, which may also mprotect(PROT_NONE) the buffer. So do + // it after copying out the data. + buffer.neuterViews(cx); - if (buffer.isAsmJSArrayBuffer()) - ArrayBufferObject::neuterAsmJSArrayBuffer(buffer); + if (!own) { + // If header has dynamically allocated elements, revert it back to + // fixed-element storage before neutering it. + buffer.changeContents(cx, ObjectElements::fromElements(buffer.fixedElements())); } - - // Neuter the donor ArrayBufferObject and all views of it - ArrayBufferObject::setElementsHeader(header, 0); - InitViewList(&buffer, views); - for (ArrayBufferViewObject *view = views; view; view = view->nextView()) - view->neuter(); + buffer.neuter(cx); return true; } @@ -3966,11 +4017,19 @@ JS_GetArrayBufferData(JSObject *obj) if (!obj) return nullptr; ArrayBufferObject &buffer = obj->as(); - if (!buffer.uninlineData(nullptr)) + if (!buffer.ensureNonInline(NULL)) return nullptr; return buffer.dataPointer(); } +JS_FRIEND_API(void) +JS_NeuterArrayBuffer(JSObject *obj, JSContext *cx) +{ + ArrayBufferObject &buffer = obj->as(); + buffer.neuterViews(cx); + buffer.neuter(cx); +} + JS_FRIEND_API(JSObject *) JS_NewArrayBuffer(JSContext *cx, uint32_t nbytes) { @@ -3997,7 +4056,7 @@ JS_AllocateArrayBufferContents(JSContext *cx, uint32_t nbytes, void **contents, if (!header) return false; - ArrayBufferObject::setElementsHeader(header, nbytes); + ArrayBufferObject::updateElementsHeader(header, nbytes); *contents = header; *data = reinterpret_cast(header->elements()); @@ -4011,7 +4070,7 @@ JS_ReallocateArrayBufferContents(JSContext *cx, uint32_t nbytes, void **contents if (!header) return false; - ArrayBufferObject::setElementsHeader(header, nbytes); + ArrayBufferObject::initElementsHeader(header, nbytes); *contents = header; *data = reinterpret_cast(header->elements()); diff --git a/js/src/vm/TypedArrayObject.h b/js/src/vm/TypedArrayObject.h index ce13ea413f4..dc1fdab3c57 100644 --- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -146,8 +146,7 @@ class ArrayBufferObject : public JSObject static bool stealContents(JSContext *cx, JSObject *obj, void **contents, uint8_t **data); - static void setElementsHeader(js::ObjectElements *header, uint32_t bytes) { - header->flags = 0; + static void updateElementsHeader(js::ObjectElements *header, uint32_t bytes) { header->initializedLength = bytes; // NB: one or both of these fields is clobbered by GetViewList to store @@ -157,6 +156,11 @@ class ArrayBufferObject : public JSObject header->capacity = 0; } + static void initElementsHeader(js::ObjectElements *header, uint32_t bytes) { + header->flags = 0; + updateElementsHeader(header, bytes); + } + static uint32_t headerInitializedLength(const js::ObjectElements *header) { return header->initializedLength; } @@ -164,22 +168,48 @@ class ArrayBufferObject : public JSObject void addView(ArrayBufferViewObject *view); bool allocateSlots(JSContext *cx, uint32_t size, uint8_t *contents = nullptr); + void changeContents(JSContext *cx, ObjectElements *newHeader); /* - * Ensure that the data is not stored inline. Used when handing back a + * Copy the data into freshly-allocated memory. Used when un-inlining or + * when converting an ArrayBuffer to an AsmJS (MMU-assisted) ArrayBuffer. + */ + bool copyData(JSContext *maybecx); + + /* + * Ensure data is not stored inline in the object. Used when handing back a * GC-safe pointer. */ - bool uninlineData(JSContext *cx); + bool ensureNonInline(JSContext *maybecx); uint32_t byteLength() const { return getElementsHeader()->initializedLength; } + /* + * Return the contents of an ArrayBuffer without modifying the ArrayBuffer + * itself. Set *callerOwns to true if the caller has the only pointer to + * the returned contents (which is the case for inline or asm.js buffers), + * and false if the ArrayBuffer still owns the pointer. + */ + ObjectElements *getTransferableContents(JSContext *maybecx, bool *callerOwns); + + /* + * Neuter all views of an ArrayBuffer. + */ + void neuterViews(JSContext *maybecx); + inline uint8_t * dataPointer() const { return (uint8_t *) elements; } + /* + * Discard the ArrayBuffer contents. For asm.js buffers, at least, should + * be called after neuterViews(). + */ + void neuter(JSContext *maybecx); + /* * Check if the arrayBuffer contains any data. This will return false for * ArrayBuffer.prototype and neutered ArrayBuffers. @@ -521,6 +551,7 @@ class DataViewObject : public ArrayBufferViewObject static bool fun_setFloat64(JSContext *cx, unsigned argc, Value *vp); static JSObject *initClass(JSContext *cx); + static void neuter(JSObject *view); static bool getDataPointer(JSContext *cx, Handle obj, CallArgs args, size_t typeSize, uint8_t **data); template