зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
a9205a2fc8
Коммит
75b3b72bb9
|
@ -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);
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче