1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 14:27:35 +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:
Timothy Flynn 2023-12-28 08:55:38 -05:00 committed by Andreas Kling
parent 526a74f2f1
commit 3f3686cf7b
5 changed files with 201 additions and 8 deletions

View file

@ -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
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();
// 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
// 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())
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;
// 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,
else {
// a. Let newMaxByteLength be empty.
}
// 8. If arrayBuffer.[[ArrayBufferDetachKey]] is not undefined, throw a TypeError exception.
if (!array_buffer.detach_key().is_undefined())
return vm.throw_completion<TypeError>(ErrorType::DetachKeyMismatch, array_buffer.detach_key(), js_undefined());
// 9. Let newBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, newByteLength, FIXME: newMaxByteLength).
auto* new_buffer = TRY(allocate_array_buffer(vm, realm.intrinsics().array_buffer_constructor(), new_byte_length));
// 9. Let newBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, newByteLength, newMaxByteLength).
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]]).
auto copy_length = min(new_byte_length, array_buffer.byte_length());

View file

@ -278,7 +278,8 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::detached_getter)
auto array_buffer_object = TRY(typed_this_value(vm));
// 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).
return Value(array_buffer_object->is_detached());

View file

@ -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();
});
});

View file

@ -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]);
});
});

View file

@ -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]);
});
});