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
This commit is contained in:
Steve Fink 2013-05-09 15:53:11 -07:00
parent bca7cd292f
commit b8c80b5536
4 changed files with 148 additions and 50 deletions

View File

@ -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.
*/

View File

@ -32,6 +32,8 @@
#include "mozilla/Endian.h"
#include "mozilla/FloatingPoint.h"
#include <algorithm>
#include "jsapi.h"
#include "jscntxt.h"
#include "jsdate.h"

View File

@ -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<ArrayBufferViewObject> views;
};
@ -329,17 +329,35 @@ GetViewListRef(ArrayBufferObject *obj)
return reinterpret_cast<OldObjectRepresentationHack*>(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<uint8_t*>(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<ArrayBufferObject*> 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<ArrayBufferObject>();
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<ObjectElements*>(buffer.getTransferableContents(cx, &own));
if (!header)
return false;
*contents = header;
*data = reinterpret_cast<uint8_t *>(header + 1);
ArrayBufferObject::setElementsHeader(newheader, length);
*contents = newheader;
*data = reinterpret_cast<uint8_t *>(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<ArrayBufferObject>();
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<ArrayBufferObject>();
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<uint8_t*>(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<uint8_t*>(header->elements());

View File

@ -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<DataViewObject*> obj,
CallArgs args, size_t typeSize, uint8_t **data);
template<typename NativeType>