Bug 1633598 - Add JSAPI to create an ArrayBuffer with contents and length copied from an existing one. r=sfink

Differential Revision: https://phabricator.services.mozilla.com/D72909
This commit is contained in:
Jeff Walden 2020-05-07 23:46:22 +00:00
Родитель a9205a2fc8
Коммит 75b3b72bb9
3 изменённых файлов: 151 добавлений и 18 удалений

Просмотреть файл

@ -35,11 +35,38 @@ extern JS_PUBLIC_API JSObject* NewArrayBuffer(JSContext* cx, uint32_t nbytes);
*
* If and only if an ArrayBuffer is successfully created and returned,
* ownership of |contents| is transferred to the new ArrayBuffer.
*
* Care must be taken that |nbytes| bytes of |content| remain valid for the
* duration of this call. In particular, passing the length/pointer of existing
* typed array or ArrayBuffer data is generally unsafe: if a GC occurs during a
* call to this function, it could move those contents to a different location
* and invalidate the provided pointer.
*/
extern JS_PUBLIC_API JSObject* NewArrayBufferWithContents(JSContext* cx,
size_t nbytes,
void* contents);
/**
* Create a new ArrayBuffer, whose bytes are set to the values of the bytes in
* the provided ArrayBuffer.
*
* |maybeArrayBuffer| is asserted to be non-null. An error is thrown if
* |maybeArrayBuffer| would fail the |IsArrayBufferObject| test given further
* below or if |maybeArrayBuffer| is detached.
*
* |maybeArrayBuffer| may store its contents in any fashion (i.e. it doesn't
* matter whether |maybeArrayBuffer| was allocated using |JS::NewArrayBuffer|,
* |JS::NewExternalArrayBuffer|, or any other ArrayBuffer-allocating function).
*
* The newly-created ArrayBuffer is effectively creatd as if by
* |JS::NewArrayBufferWithContents| passing in |maybeArrayBuffer|'s internal
* data pointer and length, in a manner safe against |maybeArrayBuffer|'s data
* being moved around by the GC. In particular, the new ArrayBuffer will not
* behave like one created for WASM or asm.js, so it *can* be detached.
*/
extern JS_PUBLIC_API JSObject* CopyArrayBuffer(
JSContext* cx, JS::Handle<JSObject*> maybeArrayBuffer);
using BufferContentsFreeFunc = void (*)(void* contents, void* userData);
/**

Просмотреть файл

@ -18,11 +18,13 @@
#include "mozilla/ScopeExit.h"
#include "mozilla/TaggedAnonymousMemory.h"
#include <algorithm>
#include <algorithm> // std::max, std::min
#include <memory> // std::uninitialized_copy_n
#include <string.h>
#ifndef XP_WIN
# include <sys/mman.h>
#endif
#include <tuple> // std::tuple
#ifdef MOZ_VALGRIND
# include <valgrind/memcheck.h>
#endif
@ -413,6 +415,24 @@ bool ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc,
using ArrayBufferContents = UniquePtr<uint8_t[], JS::FreePolicy>;
static ArrayBufferContents AllocateUninitializedArrayBufferContents(
JSContext* cx, uint32_t nbytes) {
// First attempt a normal allocation.
uint8_t* p =
cx->maybe_pod_arena_malloc<uint8_t>(js::ArrayBufferContentsArena, nbytes);
if (MOZ_UNLIKELY(!p)) {
// Otherwise attempt a large allocation, calling the
// large-allocation-failure callback if necessary.
p = static_cast<uint8_t*>(cx->runtime()->onOutOfMemoryCanGC(
js::AllocFunction::Malloc, js::ArrayBufferContentsArena, nbytes));
if (!p) {
ReportOutOfMemory(cx);
}
}
return ArrayBufferContents(p);
}
static ArrayBufferContents AllocateArrayBufferContents(JSContext* cx,
uint32_t nbytes) {
// First attempt a normal allocation.
@ -434,7 +454,7 @@ static ArrayBufferContents AllocateArrayBufferContents(JSContext* cx,
static ArrayBufferContents NewCopiedBufferContents(
JSContext* cx, Handle<ArrayBufferObject*> buffer) {
ArrayBufferContents dataCopy =
AllocateArrayBufferContents(cx, buffer->byteLength());
AllocateUninitializedArrayBufferContents(cx, buffer->byteLength());
if (dataCopy) {
if (auto count = buffer->byteLength()) {
memcpy(dataCopy.get(), buffer->dataPointer(), count);
@ -1168,12 +1188,13 @@ ArrayBufferObject* ArrayBufferObject::createForContents(
return buffer;
}
ArrayBufferObject* ArrayBufferObject::createZeroed(
JSContext* cx, uint32_t nbytes, HandleObject proto /* = nullptr */) {
// 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
if (!CheckArrayBufferTooLarge(cx, nbytes)) {
return nullptr;
}
template <ArrayBufferObject::FillContents FillType>
/* static */ std::tuple<ArrayBufferObject*, uint8_t*>
ArrayBufferObject::createBufferAndData(
JSContext* cx, uint32_t nbytes, AutoSetNewObjectMetadata&,
JS::Handle<JSObject*> proto /* = nullptr */) {
MOZ_ASSERT(nbytes <= ArrayBufferObject::MaxBufferByteLength,
"caller must validate the byte count it passes");
// Try fitting the data inline with the object by repurposing fixed-slot
// storage. Add extra fixed slots if necessary to accomplish this, but don't
@ -1186,35 +1207,75 @@ ArrayBufferObject* ArrayBufferObject::createZeroed(
nslots += newSlots;
} else {
data = AllocateArrayBufferContents(cx, nbytes);
data = (FillType == FillContents::Uninitialized
? AllocateUninitializedArrayBufferContents
: AllocateArrayBufferContents)(cx, nbytes);
if (!data) {
return nullptr;
return {nullptr, nullptr};
}
}
MOZ_ASSERT(!(class_.flags & JSCLASS_HAS_PRIVATE));
gc::AllocKind allocKind = GetArrayBufferGCObjectKind(nslots);
AutoSetNewObjectMetadata metadata(cx);
Rooted<ArrayBufferObject*> buffer(
cx, NewObjectWithClassProto<ArrayBufferObject>(cx, proto, allocKind,
GenericObject));
ArrayBufferObject* buffer = NewObjectWithClassProto<ArrayBufferObject>(
cx, proto, allocKind, GenericObject);
if (!buffer) {
return nullptr;
return {nullptr, nullptr};
}
MOZ_ASSERT(!gc::IsInsideNursery(buffer),
"ArrayBufferObject has a finalizer that must be called to not "
"leak in some cases, so it can't be nursery-allocated");
uint8_t* toFill;
if (data) {
buffer->initialize(nbytes, BufferContents::createMalloced(data.release()));
toFill = data.release();
buffer->initialize(nbytes, BufferContents::createMalloced(toFill));
AddCellMemory(buffer, nbytes, MemoryUse::ArrayBufferContents);
} else {
void* inlineData = buffer->initializeToInlineData(nbytes);
memset(inlineData, 0, nbytes);
toFill = static_cast<uint8_t*>(buffer->initializeToInlineData(nbytes));
if constexpr (FillType == FillContents::Zero) {
memset(toFill, 0, nbytes);
}
}
return {buffer, toFill};
}
/* static */ ArrayBufferObject* ArrayBufferObject::copy(
JSContext* cx, JS::Handle<ArrayBufferObject*> unwrappedArrayBuffer) {
if (unwrappedArrayBuffer->isDetached()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_DETACHED);
return nullptr;
}
uint32_t nbytes = unwrappedArrayBuffer->byteLength();
AutoSetNewObjectMetadata metadata(cx);
auto [buffer, toFill] = createBufferAndData<FillContents::Uninitialized>(
cx, nbytes, metadata, nullptr);
if (!buffer) {
return nullptr;
}
std::uninitialized_copy_n(unwrappedArrayBuffer->dataPointer(), nbytes,
toFill);
return buffer;
}
ArrayBufferObject* ArrayBufferObject::createZeroed(
JSContext* cx, uint32_t nbytes, HandleObject proto /* = nullptr */) {
// 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
if (!CheckArrayBufferTooLarge(cx, nbytes)) {
return nullptr;
}
AutoSetNewObjectMetadata metadata(cx);
auto [buffer, toFill] =
createBufferAndData<FillContents::Zero>(cx, nbytes, metadata, proto);
Unused << toFill;
return buffer;
}
@ -1674,6 +1735,39 @@ JS_PUBLIC_API JSObject* JS::NewArrayBufferWithContents(JSContext* cx,
return ArrayBufferObject::createForContents(cx, nbytes, contents);
}
static ArrayBufferObject* UnwrapArrayBuffer(
JSContext* cx, JS::Handle<JSObject*> maybeArrayBuffer) {
JSObject* obj = CheckedUnwrapStatic(maybeArrayBuffer);
if (!obj) {
ReportAccessDenied(cx);
return nullptr;
}
if (!obj->is<ArrayBufferObject>()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_BAD_ARGS);
return nullptr;
}
return &obj->as<ArrayBufferObject>();
}
JS_PUBLIC_API JSObject* JS::CopyArrayBuffer(JSContext* cx,
Handle<JSObject*> arrayBuffer) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
MOZ_ASSERT(arrayBuffer != nullptr);
Rooted<ArrayBufferObject*> unwrappedSource(
cx, UnwrapArrayBuffer(cx, arrayBuffer));
if (!unwrappedSource) {
return nullptr;
}
return ArrayBufferObject::copy(cx, unwrappedSource);
}
JS_PUBLIC_API JSObject* JS::NewExternalArrayBuffer(
JSContext* cx, size_t nbytes, void* data,
JS::BufferContentsFreeFunc freeFunc, void* freeUserData) {

Просмотреть файл

@ -9,6 +9,8 @@
#include "mozilla/Maybe.h"
#include <tuple> // std::tuple
#include "builtin/TypedObjectConstants.h"
#include "gc/Memory.h"
#include "gc/ZoneAllocator.h"
@ -232,6 +234,13 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared {
"self-hosted code with burned-in constants must use the "
"correct DETACHED bit value");
enum class FillContents { Zero, Uninitialized };
template <FillContents FillType>
static std::tuple<ArrayBufferObject*, uint8_t*> createBufferAndData(
JSContext* cx, uint32_t nbytes, AutoSetNewObjectMetadata&,
JS::Handle<JSObject*> proto = nullptr);
public:
class BufferContents {
uint8_t* data_;
@ -319,6 +328,9 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared {
static ArrayBufferObject* createForContents(JSContext* cx, uint32_t nbytes,
BufferContents contents);
static ArrayBufferObject* copy(
JSContext* cx, JS::Handle<ArrayBufferObject*> unwrappedArrayBuffer);
static ArrayBufferObject* createZeroed(JSContext* cx, uint32_t nbytes,
HandleObject proto = nullptr);