mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-27 12:50:09 +00:00
Bug 1746515 - Implement Array Buffer Clone r=sfink
Add tests for ArrayBufferClone, as well as ArrayBufferCopyData. Adapt ArrayBufferCopyData to handle multiple compartments, and do runtime error checking. Differential Revision: https://phabricator.services.mozilla.com/D134080
This commit is contained in:
parent
7eb72ab403
commit
df1c982518
@ -268,16 +268,32 @@ extern JS_PUBLIC_API void SetLargeArrayBuffersEnabled(bool enable);
|
||||
/**
|
||||
* Copy data from one array buffer to another.
|
||||
*
|
||||
* Both fromBuffer and toBuffer must be ArrayBufferObjectMaybeShared.
|
||||
* Both fromBuffer and toBuffer must be (possibly wrapped)
|
||||
* ArrayBufferObjectMaybeShared.
|
||||
*
|
||||
* This method may throw if the sizes don't match, or if unwrapping fails.
|
||||
*
|
||||
* The API for this is modelled on CopyDataBlockBytes from the spec:
|
||||
* https://tc39.es/ecma262/#sec-copydatablockbytes
|
||||
*/
|
||||
extern JS_PUBLIC_API void ArrayBufferCopyData(JSContext* cx,
|
||||
Handle<JSObject*> toBlock,
|
||||
size_t toIndex,
|
||||
Handle<JSObject*> fromBlock,
|
||||
size_t fromIndex, size_t count);
|
||||
[[nodiscard]] extern JS_PUBLIC_API bool ArrayBufferCopyData(
|
||||
JSContext* cx, Handle<JSObject*> toBlock, size_t toIndex,
|
||||
Handle<JSObject*> fromBlock, size_t fromIndex, size_t count);
|
||||
|
||||
/**
|
||||
* Copy data from one array buffer to another.
|
||||
*
|
||||
* srcBuffer must be a (possibly wrapped) ArrayBufferObjectMaybeShared.
|
||||
*
|
||||
* This method may throw if unwrapping or allocation fails.
|
||||
*
|
||||
* The API for this is modelled on CloneArrayBuffer from the spec:
|
||||
* https://tc39.es/ecma262/#sec-clonearraybuffer
|
||||
*/
|
||||
extern JS_PUBLIC_API JSObject* ArrayBufferClone(JSContext* cx,
|
||||
Handle<JSObject*> srcBuffer,
|
||||
size_t srcByteOffset,
|
||||
size_t srcLength);
|
||||
|
||||
} // namespace JS
|
||||
|
||||
|
@ -674,6 +674,8 @@ MSG_DEF(JSMSG_SHORT_TYPED_ARRAY_RETURNED, 2, JSEXN_TYPEERR, "expected TypedArray
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_NOT_COMPATIBLE, 2, JSEXN_TYPEERR, "{0} elements are incompatible with {1}")
|
||||
MSG_DEF(JSMSG_ARRAYBUFFER_REQUIRED, 0, JSEXN_TYPEERR, "ArrayBuffer object required")
|
||||
|
||||
MSG_DEF(JSMSG_ARRAYBUFFER_COPY_RANGE, 0, JSEXN_RANGEERR, "ArrayBuffer range incorrect for copying")
|
||||
|
||||
// Shared array buffer
|
||||
MSG_DEF(JSMSG_SHARED_ARRAY_BAD_LENGTH, 0, JSEXN_RANGEERR, "length argument out of range")
|
||||
MSG_DEF(JSMSG_NON_SHARED_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected SharedArrayBuffer, but species constructor returned non-SharedArrayBuffer")
|
||||
|
@ -5,14 +5,18 @@
|
||||
#include "builtin/TestingFunctions.h"
|
||||
#include "js/Array.h" // JS::NewArrayObject
|
||||
#include "js/ArrayBuffer.h" // JS::{GetArrayBuffer{ByteLength,Data},IsArrayBufferObject,NewArrayBuffer{,WithContents},StealArrayBufferContents}
|
||||
#include "js/ArrayBufferMaybeShared.h"
|
||||
#include "js/CallAndConstruct.h"
|
||||
#include "js/Exception.h"
|
||||
#include "js/experimental/TypedData.h" // JS_New{Int32,Uint8}ArrayWithBuffer
|
||||
#include "js/friend/ErrorMessages.h" // JSMSG_*
|
||||
#include "js/MemoryFunctions.h"
|
||||
#include "js/PropertyAndElement.h" // JS_GetElement, JS_GetProperty, JS_SetElement
|
||||
#include "js/Realm.h"
|
||||
#include "jsapi-tests/tests.h"
|
||||
|
||||
#include "vm/Realm-inl.h"
|
||||
|
||||
BEGIN_TEST(testArrayBuffer_bug720949_steal) {
|
||||
static const unsigned NUM_TEST_BUFFERS = 2;
|
||||
static const unsigned MAGIC_VALUE_1 = 3;
|
||||
@ -301,3 +305,145 @@ BEGIN_TEST(testArrayBuffer_serializeExternal) {
|
||||
return true;
|
||||
}
|
||||
END_TEST(testArrayBuffer_serializeExternal)
|
||||
|
||||
BEGIN_TEST(testArrayBuffer_copyData) {
|
||||
ExternalData data1("One two three four");
|
||||
JS::RootedObject buffer1(cx, JS::NewExternalArrayBuffer(
|
||||
cx, data1.len(), data1.contents(), nullptr));
|
||||
|
||||
CHECK(buffer1);
|
||||
|
||||
ExternalData data2("Six");
|
||||
JS::RootedObject buffer2(cx, JS::NewExternalArrayBuffer(
|
||||
cx, data2.len(), data2.contents(), nullptr));
|
||||
|
||||
CHECK(buffer2);
|
||||
|
||||
// Check we can't copy from a larger to a smaller buffer.
|
||||
CHECK(!JS::ArrayBufferCopyData(cx, buffer2, 0, buffer1, 0, data1.len()));
|
||||
|
||||
// Verify expected exception is thrown.
|
||||
{
|
||||
JS::ExceptionStack exnStack(cx);
|
||||
CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
|
||||
|
||||
JS::ErrorReportBuilder report(cx);
|
||||
CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects));
|
||||
|
||||
CHECK_EQUAL(report.report()->errorNumber,
|
||||
static_cast<unsigned int>(JSMSG_ARRAYBUFFER_COPY_RANGE));
|
||||
}
|
||||
|
||||
CHECK(JS::ArrayBufferCopyData(
|
||||
cx, buffer1, 0, buffer2, 0,
|
||||
data2.len() - 1 /* don't copy null terminator */));
|
||||
|
||||
{
|
||||
size_t len;
|
||||
bool isShared;
|
||||
uint8_t* bufferData;
|
||||
JS::GetArrayBufferLengthAndData(buffer1, &len, &isShared, &bufferData);
|
||||
|
||||
ExternalData expected1("Six two three four");
|
||||
|
||||
fprintf(stderr, "expected %s actual %s\n", expected1.asString(),
|
||||
bufferData);
|
||||
|
||||
CHECK_EQUAL(len, expected1.len());
|
||||
CHECK_EQUAL(memcmp(expected1.contents(), bufferData, expected1.len()), 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
END_TEST(testArrayBuffer_copyData)
|
||||
|
||||
BEGIN_TEST(testArrayBuffer_copyDataAcrossGlobals) {
|
||||
JS::RootedObject otherGlobal(cx, createGlobal(nullptr));
|
||||
if (!otherGlobal) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ExternalData data1("One two three four");
|
||||
JS::RootedObject buffer1(cx);
|
||||
{
|
||||
js::AutoRealm realm(cx, otherGlobal);
|
||||
buffer1 =
|
||||
JS::NewExternalArrayBuffer(cx, data1.len(), data1.contents(), nullptr);
|
||||
}
|
||||
CHECK(buffer1);
|
||||
CHECK(JS_WrapObject(cx, &buffer1));
|
||||
|
||||
ExternalData data2("Six");
|
||||
JS::RootedObject buffer2(cx, JS::NewExternalArrayBuffer(
|
||||
cx, data2.len(), data2.contents(), nullptr));
|
||||
|
||||
CHECK(buffer2);
|
||||
|
||||
// Check we can't copy from a larger to a smaller buffer.
|
||||
CHECK(!JS::ArrayBufferCopyData(cx, buffer2, 0, buffer1, 0, data1.len()));
|
||||
|
||||
// Verify expected exception is thrown.
|
||||
{
|
||||
JS::ExceptionStack exnStack(cx);
|
||||
CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
|
||||
|
||||
JS::ErrorReportBuilder report(cx);
|
||||
CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects));
|
||||
|
||||
CHECK_EQUAL(report.report()->errorNumber,
|
||||
static_cast<unsigned int>(JSMSG_ARRAYBUFFER_COPY_RANGE));
|
||||
}
|
||||
|
||||
CHECK(JS::ArrayBufferCopyData(
|
||||
cx, buffer1, 0, buffer2, 0,
|
||||
data2.len() - 1 /* don't copy null terminator */));
|
||||
|
||||
{
|
||||
JS::RootedObject unwrappedBuffer1(
|
||||
cx, JS::UnwrapArrayBufferMaybeShared(buffer1));
|
||||
CHECK(unwrappedBuffer1);
|
||||
|
||||
size_t len;
|
||||
bool isShared;
|
||||
uint8_t* bufferData;
|
||||
JS::GetArrayBufferLengthAndData(unwrappedBuffer1, &len, &isShared,
|
||||
&bufferData);
|
||||
|
||||
ExternalData expected1("Six two three four");
|
||||
|
||||
fprintf(stderr, "expected %s actual %s\n", expected1.asString(),
|
||||
bufferData);
|
||||
|
||||
CHECK_EQUAL(len, expected1.len());
|
||||
CHECK_EQUAL(memcmp(expected1.contents(), bufferData, expected1.len()), 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
END_TEST(testArrayBuffer_copyDataAcrossGlobals)
|
||||
|
||||
BEGIN_TEST(testArrayBuffer_ArrayBufferClone) {
|
||||
ExternalData data("One two three four");
|
||||
JS::RootedObject externalBuffer(
|
||||
cx, JS::NewExternalArrayBuffer(cx, data.len(), data.contents(), nullptr));
|
||||
|
||||
CHECK(externalBuffer);
|
||||
|
||||
size_t lengthToCopy = 3;
|
||||
JS::RootedObject clonedBuffer(
|
||||
cx, JS::ArrayBufferClone(cx, externalBuffer, 4, lengthToCopy));
|
||||
CHECK(clonedBuffer);
|
||||
|
||||
size_t len;
|
||||
bool isShared;
|
||||
uint8_t* bufferData;
|
||||
JS::GetArrayBufferLengthAndData(clonedBuffer, &len, &isShared, &bufferData);
|
||||
|
||||
CHECK_EQUAL(len, lengthToCopy);
|
||||
|
||||
ExternalData expectedData("two");
|
||||
CHECK_EQUAL(memcmp(expectedData.contents(), bufferData, len), 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
END_TEST(testArrayBuffer_ArrayBufferClone)
|
||||
|
@ -2044,25 +2044,84 @@ JS::ArrayBuffer JS::ArrayBuffer::unwrap(JSObject* maybeWrapped) {
|
||||
return fromObject(ab);
|
||||
}
|
||||
|
||||
void JS::ArrayBufferCopyData(JSContext* cx, Handle<JSObject*> toBlock,
|
||||
bool JS::ArrayBufferCopyData(JSContext* cx, Handle<JSObject*> toBlock,
|
||||
size_t toIndex, Handle<JSObject*> fromBlock,
|
||||
size_t fromIndex, size_t count) {
|
||||
MOZ_ASSERT(toBlock->is<ArrayBufferObjectMaybeShared>());
|
||||
MOZ_ASSERT(fromBlock->is<ArrayBufferObjectMaybeShared>());
|
||||
Rooted<ArrayBufferObjectMaybeShared*> unwrappedToBlock(
|
||||
cx, toBlock->maybeUnwrapIf<ArrayBufferObjectMaybeShared>());
|
||||
if (!unwrappedToBlock) {
|
||||
ReportAccessDenied(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If both are array bufferrs, can use ArrayBufferCopyData
|
||||
if (toBlock->is<ArrayBufferObject>() && fromBlock->is<ArrayBufferObject>()) {
|
||||
Rooted<ArrayBufferObject*> toArray(cx, &toBlock->as<ArrayBufferObject>());
|
||||
Rooted<ArrayBufferObject*> fromArray(cx,
|
||||
&fromBlock->as<ArrayBufferObject>());
|
||||
Rooted<ArrayBufferObjectMaybeShared*> unwrappedFromBlock(
|
||||
cx, fromBlock->maybeUnwrapIf<ArrayBufferObjectMaybeShared>());
|
||||
if (!unwrappedFromBlock) {
|
||||
ReportAccessDenied(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify that lengths still make sense and throw otherwise.
|
||||
if (toIndex + count < toIndex || // size_t overflow
|
||||
fromIndex + count < fromIndex || // size_t overflow
|
||||
toIndex + count > unwrappedToBlock->byteLength() ||
|
||||
fromIndex + count > unwrappedFromBlock->byteLength()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_ARRAYBUFFER_COPY_RANGE);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If both are array buffers, can use ArrayBufferCopyData
|
||||
if (unwrappedToBlock->is<ArrayBufferObject>() &&
|
||||
unwrappedFromBlock->is<ArrayBufferObject>()) {
|
||||
Rooted<ArrayBufferObject*> toArray(
|
||||
cx, &unwrappedToBlock->as<ArrayBufferObject>());
|
||||
Rooted<ArrayBufferObject*> fromArray(
|
||||
cx, &unwrappedFromBlock->as<ArrayBufferObject>());
|
||||
ArrayBufferObject::copyData(toArray, toIndex, fromArray, fromIndex, count);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
Rooted<ArrayBufferObjectMaybeShared*> toArray(
|
||||
cx, &toBlock->as<ArrayBufferObjectMaybeShared>());
|
||||
cx, &unwrappedToBlock->as<ArrayBufferObjectMaybeShared>());
|
||||
Rooted<ArrayBufferObjectMaybeShared*> fromArray(
|
||||
cx, &toBlock->as<ArrayBufferObjectMaybeShared>());
|
||||
cx, &unwrappedFromBlock->as<ArrayBufferObjectMaybeShared>());
|
||||
SharedArrayBufferObject::copyData(toArray, toIndex, fromArray, fromIndex,
|
||||
count);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-clonearraybuffer
|
||||
// We only support the case where cloneConstructor is %ArrayBuffer%. Note,
|
||||
// this means that cloning a SharedArrayBuffer will produce an ArrayBuffer
|
||||
JSObject* JS::ArrayBufferClone(JSContext* cx, Handle<JSObject*> srcBuffer,
|
||||
size_t srcByteOffset, size_t srcLength) {
|
||||
MOZ_ASSERT(srcBuffer->is<ArrayBufferObjectMaybeShared>());
|
||||
|
||||
// 2. (reordered) If IsDetachedBuffer(srcBuffer) is true, throw a TypeError
|
||||
// exception.
|
||||
if (IsDetachedArrayBufferObject(srcBuffer)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 1. Let targetBuffer be ? AllocateArrayBuffer(cloneConstructor, srcLength).
|
||||
JS::RootedObject targetBuffer(cx, JS::NewArrayBuffer(cx, srcLength));
|
||||
if (!targetBuffer) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 3. Let srcBlock be srcBuffer.[[ArrayBufferData]].
|
||||
// 4. Let targetBlock be targetBuffer.[[ArrayBufferData]].
|
||||
// 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset,
|
||||
// srcLength).
|
||||
if (!ArrayBufferCopyData(cx, targetBuffer, 0, srcBuffer, srcByteOffset,
|
||||
srcLength)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 6. Return targetBuffer.
|
||||
return targetBuffer;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user