mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 14:07:46 +00:00
LibJS: Implement missing steps from the ArrayBuffer transfer proposal
We can now implement steps related to resizable ArrayBuffer objects. We can also implement a couple of missing SharedArrayBuffer checks. The original implementation of this proposal did not have any tests, so tests are added here for the whole implementation.
This commit is contained in:
parent
526a74f2f1
commit
3f3686cf7b
5 changed files with 201 additions and 8 deletions
|
@ -262,13 +262,15 @@ ThrowCompletionOr<Optional<size_t>> get_array_buffer_max_byte_length_option(VM&
|
||||||
}
|
}
|
||||||
|
|
||||||
// 25.1.2.14 ArrayBufferCopyAndDetach ( arrayBuffer, newLength, preserveResizability ), https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfertofixedlength
|
// 25.1.2.14 ArrayBufferCopyAndDetach ( arrayBuffer, newLength, preserveResizability ), https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfertofixedlength
|
||||||
ThrowCompletionOr<ArrayBuffer*> array_buffer_copy_and_detach(VM& vm, ArrayBuffer& array_buffer, Value new_length, PreserveResizability)
|
ThrowCompletionOr<ArrayBuffer*> array_buffer_copy_and_detach(VM& vm, ArrayBuffer& array_buffer, Value new_length, PreserveResizability preserve_resizability)
|
||||||
{
|
{
|
||||||
auto& realm = *vm.current_realm();
|
auto& realm = *vm.current_realm();
|
||||||
|
|
||||||
// 1. Perform ? RequireInternalSlot(arrayBuffer, [[ArrayBufferData]]).
|
// 1. Perform ? RequireInternalSlot(arrayBuffer, [[ArrayBufferData]]).
|
||||||
|
|
||||||
// FIXME: 2. If IsSharedArrayBuffer(arrayBuffer) is true, throw a TypeError exception.
|
// 2. If IsSharedArrayBuffer(arrayBuffer) is true, throw a TypeError exception.
|
||||||
|
if (array_buffer.is_shared_array_buffer())
|
||||||
|
return vm.throw_completion<TypeError>(ErrorType::SharedArrayBuffer);
|
||||||
|
|
||||||
// 3. If newLength is undefined, then
|
// 3. If newLength is undefined, then
|
||||||
// a. Let newByteLength be arrayBuffer.[[ArrayBufferByteLength]].
|
// a. Let newByteLength be arrayBuffer.[[ArrayBufferByteLength]].
|
||||||
|
@ -280,17 +282,25 @@ ThrowCompletionOr<ArrayBuffer*> array_buffer_copy_and_detach(VM& vm, ArrayBuffer
|
||||||
if (array_buffer.is_detached())
|
if (array_buffer.is_detached())
|
||||||
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
|
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
|
||||||
|
|
||||||
// FIXME: 6. If preserveResizability is preserve-resizability and IsResizableArrayBuffer(arrayBuffer) is true, then
|
Optional<size_t> new_max_byte_length;
|
||||||
// a. Let newMaxByteLength be arrayBuffer.[[ArrayBufferMaxByteLength]].
|
|
||||||
|
// 6. If preserveResizability is preserve-resizability and IsResizableArrayBuffer(arrayBuffer) is true, then
|
||||||
|
// FIXME: The ArrayBuffer transfer spec is a bit out-of-date. IsResizableArrayBuffer no longer exists, we now have IsFixedLengthArrayBuffer.
|
||||||
|
if (preserve_resizability == PreserveResizability::PreserveResizability && !array_buffer.is_fixed_length()) {
|
||||||
|
// a. Let newMaxByteLength be arrayBuffer.[[ArrayBufferMaxByteLength]].
|
||||||
|
new_max_byte_length = array_buffer.max_byte_length();
|
||||||
|
}
|
||||||
// 7. Else,
|
// 7. Else,
|
||||||
// a. Let newMaxByteLength be empty.
|
else {
|
||||||
|
// a. Let newMaxByteLength be empty.
|
||||||
|
}
|
||||||
|
|
||||||
// 8. If arrayBuffer.[[ArrayBufferDetachKey]] is not undefined, throw a TypeError exception.
|
// 8. If arrayBuffer.[[ArrayBufferDetachKey]] is not undefined, throw a TypeError exception.
|
||||||
if (!array_buffer.detach_key().is_undefined())
|
if (!array_buffer.detach_key().is_undefined())
|
||||||
return vm.throw_completion<TypeError>(ErrorType::DetachKeyMismatch, array_buffer.detach_key(), js_undefined());
|
return vm.throw_completion<TypeError>(ErrorType::DetachKeyMismatch, array_buffer.detach_key(), js_undefined());
|
||||||
|
|
||||||
// 9. Let newBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, newByteLength, FIXME: newMaxByteLength).
|
// 9. Let newBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, newByteLength, newMaxByteLength).
|
||||||
auto* new_buffer = TRY(allocate_array_buffer(vm, realm.intrinsics().array_buffer_constructor(), new_byte_length));
|
auto* new_buffer = TRY(allocate_array_buffer(vm, realm.intrinsics().array_buffer_constructor(), new_byte_length, new_max_byte_length));
|
||||||
|
|
||||||
// 10. Let copyLength be min(newByteLength, arrayBuffer.[[ArrayBufferByteLength]]).
|
// 10. Let copyLength be min(newByteLength, arrayBuffer.[[ArrayBufferByteLength]]).
|
||||||
auto copy_length = min(new_byte_length, array_buffer.byte_length());
|
auto copy_length = min(new_byte_length, array_buffer.byte_length());
|
||||||
|
|
|
@ -278,7 +278,8 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::detached_getter)
|
||||||
auto array_buffer_object = TRY(typed_this_value(vm));
|
auto array_buffer_object = TRY(typed_this_value(vm));
|
||||||
|
|
||||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
||||||
// FIXME: Check for shared buffer
|
if (array_buffer_object->is_shared_array_buffer())
|
||||||
|
return vm.throw_completion<TypeError>(ErrorType::SharedArrayBuffer);
|
||||||
|
|
||||||
// 4. Return IsDetachedBuffer(O).
|
// 4. Return IsDetachedBuffer(O).
|
||||||
return Value(array_buffer_object->is_detached());
|
return Value(array_buffer_object->is_detached());
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
describe("errors", () => {
|
||||||
|
test("called on non-ArrayBuffer object", () => {
|
||||||
|
expect(() => {
|
||||||
|
ArrayBuffer.prototype.detached;
|
||||||
|
}).toThrowWithMessage(TypeError, "Not an object of type ArrayBuffer");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("called on SharedArrayBuffer object", () => {
|
||||||
|
let detached = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "detached");
|
||||||
|
let getter = detached.get;
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
getter.call(new SharedArrayBuffer());
|
||||||
|
}).toThrowWithMessage(TypeError, "The array buffer object cannot be a SharedArrayBuffer");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("normal behavior", () => {
|
||||||
|
test("not detached", () => {
|
||||||
|
let buffer = new ArrayBuffer();
|
||||||
|
expect(buffer.detached).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("detached", () => {
|
||||||
|
let buffer = new ArrayBuffer();
|
||||||
|
detachArrayBuffer(buffer);
|
||||||
|
|
||||||
|
expect(buffer.detached).toBeTrue();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,76 @@
|
||||||
|
describe("errors", () => {
|
||||||
|
test("called on non-ArrayBuffer object", () => {
|
||||||
|
expect(() => {
|
||||||
|
ArrayBuffer.prototype.transfer(Symbol.hasInstance);
|
||||||
|
}).toThrowWithMessage(TypeError, "Not an object of type ArrayBuffer");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("called on SharedArrayBuffer object", () => {
|
||||||
|
expect(() => {
|
||||||
|
ArrayBuffer.prototype.transfer.call(new SharedArrayBuffer());
|
||||||
|
}).toThrowWithMessage(TypeError, "The array buffer object cannot be a SharedArrayBuffer");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("detached buffer", () => {
|
||||||
|
let buffer = new ArrayBuffer(5);
|
||||||
|
detachArrayBuffer(buffer);
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
buffer.transfer();
|
||||||
|
}).toThrowWithMessage(TypeError, "ArrayBuffer is detached");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const readBuffer = buffer => {
|
||||||
|
let array = new Uint8Array(buffer, 0, buffer.byteLength / Uint8Array.BYTES_PER_ELEMENT);
|
||||||
|
let values = [];
|
||||||
|
|
||||||
|
for (let value of array) {
|
||||||
|
values.push(Number(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeBuffer = (buffer, values) => {
|
||||||
|
let array = new Uint8Array(buffer, 0, buffer.byteLength / Uint8Array.BYTES_PER_ELEMENT);
|
||||||
|
array.set(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("normal behavior", () => {
|
||||||
|
test("old buffer is detached", () => {
|
||||||
|
let buffer = new ArrayBuffer(5);
|
||||||
|
let newBuffer = buffer.transfer();
|
||||||
|
|
||||||
|
expect(buffer.detached).toBeTrue();
|
||||||
|
expect(newBuffer.detached).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("resizability is preserved", () => {
|
||||||
|
let buffer = new ArrayBuffer(5, { maxByteLength: 10 });
|
||||||
|
let newBuffer = buffer.transfer();
|
||||||
|
|
||||||
|
expect(buffer.resizable).toBeTrue();
|
||||||
|
expect(newBuffer.resizable).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("data is transferred", () => {
|
||||||
|
let buffer = new ArrayBuffer(5);
|
||||||
|
writeBuffer(buffer, [1, 2, 3, 4, 5]);
|
||||||
|
|
||||||
|
let newBuffer = buffer.transfer();
|
||||||
|
const values = readBuffer(newBuffer);
|
||||||
|
|
||||||
|
expect(values).toEqual([1, 2, 3, 4, 5]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("length may be limited", () => {
|
||||||
|
let buffer = new ArrayBuffer(5);
|
||||||
|
writeBuffer(buffer, [1, 2, 3, 4, 5]);
|
||||||
|
|
||||||
|
let newBuffer = buffer.transfer(3);
|
||||||
|
const values = readBuffer(newBuffer);
|
||||||
|
|
||||||
|
expect(values).toEqual([1, 2, 3]);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,76 @@
|
||||||
|
describe("errors", () => {
|
||||||
|
test("called on non-ArrayBuffer object", () => {
|
||||||
|
expect(() => {
|
||||||
|
ArrayBuffer.prototype.transferToFixedLength(Symbol.hasInstance);
|
||||||
|
}).toThrowWithMessage(TypeError, "Not an object of type ArrayBuffer");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("called on SharedArrayBuffer object", () => {
|
||||||
|
expect(() => {
|
||||||
|
ArrayBuffer.prototype.transferToFixedLength.call(new SharedArrayBuffer());
|
||||||
|
}).toThrowWithMessage(TypeError, "The array buffer object cannot be a SharedArrayBuffer");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("detached buffer", () => {
|
||||||
|
let buffer = new ArrayBuffer(5);
|
||||||
|
detachArrayBuffer(buffer);
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
buffer.transferToFixedLength();
|
||||||
|
}).toThrowWithMessage(TypeError, "ArrayBuffer is detached");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const readBuffer = buffer => {
|
||||||
|
let array = new Uint8Array(buffer, 0, buffer.byteLength / Uint8Array.BYTES_PER_ELEMENT);
|
||||||
|
let values = [];
|
||||||
|
|
||||||
|
for (let value of array) {
|
||||||
|
values.push(Number(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeBuffer = (buffer, values) => {
|
||||||
|
let array = new Uint8Array(buffer, 0, buffer.byteLength / Uint8Array.BYTES_PER_ELEMENT);
|
||||||
|
array.set(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("normal behavior", () => {
|
||||||
|
test("old buffer is detached", () => {
|
||||||
|
let buffer = new ArrayBuffer(5);
|
||||||
|
let newBuffer = buffer.transferToFixedLength();
|
||||||
|
|
||||||
|
expect(buffer.detached).toBeTrue();
|
||||||
|
expect(newBuffer.detached).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("resizability is not preserved", () => {
|
||||||
|
let buffer = new ArrayBuffer(5, { maxByteLength: 10 });
|
||||||
|
let newBuffer = buffer.transferToFixedLength();
|
||||||
|
|
||||||
|
expect(buffer.resizable).toBeTrue();
|
||||||
|
expect(newBuffer.resizable).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("data is transferred", () => {
|
||||||
|
let buffer = new ArrayBuffer(5);
|
||||||
|
writeBuffer(buffer, [1, 2, 3, 4, 5]);
|
||||||
|
|
||||||
|
let newBuffer = buffer.transferToFixedLength();
|
||||||
|
const values = readBuffer(newBuffer);
|
||||||
|
|
||||||
|
expect(values).toEqual([1, 2, 3, 4, 5]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("length may be limited", () => {
|
||||||
|
let buffer = new ArrayBuffer(5);
|
||||||
|
writeBuffer(buffer, [1, 2, 3, 4, 5]);
|
||||||
|
|
||||||
|
let newBuffer = buffer.transferToFixedLength(3);
|
||||||
|
const values = readBuffer(newBuffer);
|
||||||
|
|
||||||
|
expect(values).toEqual([1, 2, 3]);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue