mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 07:15:46 +00:00
Bug 1287298 - Add an API to give up ownership of ArrayBuffer data; r=Waldo
This is similar to stealing the buffer, except that the ArrayBuffer won't be detached. The caller is still responsible for freeing the buffer.
This commit is contained in:
parent
652779a674
commit
556af42028
@ -158,3 +158,70 @@ bool hasDetachedBuffer(JS::HandleObject obj) {
|
||||
}
|
||||
|
||||
END_TEST(testArrayBuffer_bug720949_viewList)
|
||||
|
||||
BEGIN_TEST(testArrayBuffer_externalize)
|
||||
{
|
||||
if (!testWithSize(cx, 2)) // ArrayBuffer data stored inline in the object.
|
||||
return false;
|
||||
if (!testWithSize(cx, 2000)) // ArrayBuffer data stored out-of-line in a separate heap allocation.
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool testWithSize(JSContext* cx, size_t n)
|
||||
{
|
||||
JS::RootedObject buffer(cx, JS_NewArrayBuffer(cx, n));
|
||||
CHECK(buffer != nullptr);
|
||||
|
||||
JS::RootedObject view(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 0, -1));
|
||||
CHECK(view != nullptr);
|
||||
|
||||
void* contents = JS_ExternalizeArrayBufferContents(cx, buffer);
|
||||
CHECK(contents != nullptr);
|
||||
uint32_t actualLength;
|
||||
CHECK(hasExpectedLength(cx, view, &actualLength));
|
||||
CHECK(actualLength == n);
|
||||
CHECK(!JS_IsDetachedArrayBufferObject(buffer));
|
||||
CHECK(JS_GetArrayBufferByteLength(buffer) == uint32_t(n));
|
||||
|
||||
uint8_t* uint8Contents = static_cast<uint8_t*>(contents);
|
||||
CHECK(*uint8Contents == 0);
|
||||
uint8_t randomByte(rand() % UINT8_MAX);
|
||||
*uint8Contents = randomByte;
|
||||
|
||||
JS::RootedValue v(cx);
|
||||
CHECK(JS_GetElement(cx, view, 0, &v));
|
||||
CHECK(v.toInt32() == randomByte);
|
||||
|
||||
view = nullptr;
|
||||
GC(cx);
|
||||
|
||||
CHECK(JS_DetachArrayBuffer(cx, buffer));
|
||||
GC(cx);
|
||||
CHECK(*uint8Contents == randomByte);
|
||||
JS_free(cx, contents);
|
||||
GC(cx);
|
||||
buffer = nullptr;
|
||||
GC(cx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void GC(JSContext* cx)
|
||||
{
|
||||
JS_GC(cx);
|
||||
JS_GC(cx); // Trigger another to wait for background finalization to end
|
||||
}
|
||||
|
||||
static bool
|
||||
hasExpectedLength(JSContext* cx, JS::HandleObject obj, uint32_t* len)
|
||||
{
|
||||
JS::RootedValue v(cx);
|
||||
if (!JS_GetProperty(cx, obj, "byteLength", &v))
|
||||
return false;
|
||||
*len = v.toInt32();
|
||||
return true;
|
||||
}
|
||||
|
||||
END_TEST(testArrayBuffer_externalize)
|
||||
|
@ -3446,6 +3446,25 @@ JS_NewArrayBufferWithExternalContents(JSContext* cx, size_t nbytes, void* conten
|
||||
extern JS_PUBLIC_API(void*)
|
||||
JS_StealArrayBufferContents(JSContext* cx, JS::HandleObject obj);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the ArrayBuffer |obj|'s data. |obj| and its views will store and expose
|
||||
* the data in the returned pointer: assigning into the returned pointer will affect values exposed
|
||||
* by views of |obj| and vice versa.
|
||||
*
|
||||
* The caller must ultimately deallocate the returned pointer to avoid leaking. The memory is
|
||||
* *not* garbage-collected with |obj|. These steps must be followed to deallocate:
|
||||
*
|
||||
* 1. The ArrayBuffer |obj| must be detached using JS_DetachArrayBuffer.
|
||||
* 2. The returned pointer must be freed using JS_free.
|
||||
*
|
||||
* To perform step 1, callers *must* hold a reference to |obj| until they finish using the returned
|
||||
* pointer. They *must not* attempt to let |obj| be GC'd, then JS_free the pointer.
|
||||
*
|
||||
* If |obj| isn't an ArrayBuffer, this function returns null and reports an error.
|
||||
*/
|
||||
extern JS_PUBLIC_API(void*)
|
||||
JS_ExternalizeArrayBufferContents(JSContext* cx, JS::HandleObject obj);
|
||||
|
||||
/**
|
||||
* Create a new mapped array buffer with the given memory mapped contents. It
|
||||
* must be legal to free the contents pointer by unmapping it. On success,
|
||||
|
@ -316,21 +316,21 @@ ArrayBufferObject::detach(JSContext* cx, Handle<ArrayBufferObject*> buffer,
|
||||
}
|
||||
|
||||
if (newContents.data() != buffer->dataPointer())
|
||||
buffer->setNewOwnedData(cx->runtime()->defaultFreeOp(), newContents);
|
||||
buffer->setNewData(cx->runtime()->defaultFreeOp(), newContents, OwnsData);
|
||||
|
||||
buffer->setByteLength(0);
|
||||
buffer->setIsDetached();
|
||||
}
|
||||
|
||||
void
|
||||
ArrayBufferObject::setNewOwnedData(FreeOp* fop, BufferContents newContents)
|
||||
ArrayBufferObject::setNewData(FreeOp* fop, BufferContents newContents, OwnsState ownsState)
|
||||
{
|
||||
if (ownsData()) {
|
||||
MOZ_ASSERT(newContents.data() != dataPointer());
|
||||
releaseData(fop);
|
||||
}
|
||||
|
||||
setDataPointer(newContents, OwnsData);
|
||||
setDataPointer(newContents, ownsState);
|
||||
}
|
||||
|
||||
// This is called *only* from changeContents(), below.
|
||||
@ -361,14 +361,15 @@ ArrayBufferObject::changeViewContents(JSContext* cx, ArrayBufferViewObject* view
|
||||
// BufferContents is specific to ArrayBuffer, hence it will not represent shared memory.
|
||||
|
||||
void
|
||||
ArrayBufferObject::changeContents(JSContext* cx, BufferContents newContents)
|
||||
ArrayBufferObject::changeContents(JSContext* cx, BufferContents newContents,
|
||||
OwnsState ownsState)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(!isWasm());
|
||||
MOZ_ASSERT(!forInlineTypedObject());
|
||||
|
||||
// Change buffer contents.
|
||||
uint8_t* oldDataPointer = dataPointer();
|
||||
setNewOwnedData(cx->runtime()->defaultFreeOp(), newContents);
|
||||
setNewData(cx->runtime()->defaultFreeOp(), newContents, ownsState);
|
||||
|
||||
// Update all views.
|
||||
auto& innerViews = cx->compartment()->innerViews;
|
||||
@ -741,7 +742,7 @@ ArrayBufferObject::prepareForAsmJS(JSContext* cx, Handle<ArrayBufferObject*> buf
|
||||
|
||||
// Swap the new elements into the ArrayBufferObject. Mark the
|
||||
// ArrayBufferObject so we don't do this again.
|
||||
buffer->changeContents(cx, BufferContents::create<WASM>(data));
|
||||
buffer->changeContents(cx, BufferContents::create<WASM>(data), OwnsData);
|
||||
buffer->setIsPreparedForAsmJS();
|
||||
MOZ_ASSERT(data == buffer->dataPointer());
|
||||
cx->zone()->updateMallocCounter(wasmBuf->mappedSize());
|
||||
@ -760,7 +761,7 @@ ArrayBufferObject::prepareForAsmJS(JSContext* cx, Handle<ArrayBufferObject*> buf
|
||||
if (!contents)
|
||||
return false;
|
||||
memcpy(contents.data(), buffer->dataPointer(), buffer->byteLength());
|
||||
buffer->changeContents(cx, contents);
|
||||
buffer->changeContents(cx, contents, OwnsData);
|
||||
}
|
||||
|
||||
buffer->setIsPreparedForAsmJS();
|
||||
@ -1096,6 +1097,32 @@ ArrayBufferObject::createDataViewForThis(JSContext* cx, unsigned argc, Value* vp
|
||||
return CallNonGenericMethod<IsArrayBuffer, createDataViewForThisImpl>(cx, args);
|
||||
}
|
||||
|
||||
/* static */ ArrayBufferObject::BufferContents
|
||||
ArrayBufferObject::externalizeContents(JSContext* cx, Handle<ArrayBufferObject*> buffer,
|
||||
bool hasStealableContents)
|
||||
{
|
||||
MOZ_ASSERT(buffer->isPlain(), "Only support doing this on plain ABOs");
|
||||
MOZ_ASSERT(!buffer->isDetached(), "must have contents to externalize");
|
||||
MOZ_ASSERT_IF(hasStealableContents, buffer->hasStealableContents());
|
||||
|
||||
BufferContents contents(buffer->dataPointer(), buffer->bufferKind());
|
||||
|
||||
if (hasStealableContents) {
|
||||
buffer->setOwnsData(DoesntOwnData);
|
||||
return contents;
|
||||
}
|
||||
|
||||
// Create a new chunk of memory to return since we cannot steal the
|
||||
// existing contents away from the buffer.
|
||||
BufferContents newContents = AllocateArrayBufferContents(cx, buffer->byteLength());
|
||||
if (!newContents)
|
||||
return BufferContents::createPlain(nullptr);
|
||||
memcpy(newContents.data(), contents.data(), buffer->byteLength());
|
||||
buffer->changeContents(cx, newContents, DoesntOwnData);
|
||||
|
||||
return newContents;
|
||||
}
|
||||
|
||||
/* static */ ArrayBufferObject::BufferContents
|
||||
ArrayBufferObject::stealContents(JSContext* cx, Handle<ArrayBufferObject*> buffer,
|
||||
bool hasStealableContents)
|
||||
@ -1655,6 +1682,38 @@ js::UnwrapSharedArrayBuffer(JSObject* obj)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void*)
|
||||
JS_ExternalizeArrayBufferContents(JSContext* cx, HandleObject obj)
|
||||
{
|
||||
AssertHeapIsIdle(cx);
|
||||
CHECK_REQUEST(cx);
|
||||
assertSameCompartment(cx, obj);
|
||||
|
||||
if (!obj->is<ArrayBufferObject>()) {
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Handle<ArrayBufferObject*> buffer = obj.as<ArrayBufferObject>();
|
||||
if (!buffer->isPlain()) {
|
||||
// This operation isn't supported on mapped or wsm ArrayBufferObjects.
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
|
||||
return nullptr;
|
||||
}
|
||||
if (buffer->isDetached()) {
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The caller assumes that a plain malloc'd buffer is returned.
|
||||
// hasStealableContents is true for mapped buffers, so we must additionally
|
||||
// require that the buffer is plain. In the future, we could consider
|
||||
// returning something that handles releasing the memory.
|
||||
bool hasStealableContents = buffer->hasStealableContents();
|
||||
|
||||
return ArrayBufferObject::externalizeContents(cx, buffer, hasStealableContents).data();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void*)
|
||||
JS_StealArrayBufferContents(JSContext* cx, HandleObject objArg)
|
||||
{
|
||||
|
@ -276,6 +276,9 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
static void trace(JSTracer* trc, JSObject* obj);
|
||||
static void objectMoved(JSObject* obj, const JSObject* old);
|
||||
|
||||
static BufferContents externalizeContents(JSContext* cx,
|
||||
Handle<ArrayBufferObject*> buffer,
|
||||
bool hasStealableContents);
|
||||
static BufferContents stealContents(JSContext* cx,
|
||||
Handle<ArrayBufferObject*> buffer,
|
||||
bool hasStealableContents);
|
||||
@ -297,8 +300,8 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
|
||||
bool addView(JSContext* cx, JSObject* view);
|
||||
|
||||
void setNewOwnedData(FreeOp* fop, BufferContents newContents);
|
||||
void changeContents(JSContext* cx, BufferContents newContents);
|
||||
void setNewData(FreeOp* fop, BufferContents newContents, OwnsState ownsState);
|
||||
void changeContents(JSContext* cx, BufferContents newContents, OwnsState ownsState);
|
||||
|
||||
// Detach this buffer from its original memory. (This necessarily makes
|
||||
// views of this buffer unusable for modifying that original memory.)
|
||||
|
Loading…
Reference in New Issue
Block a user