1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 08:07:44 +00:00

LibJS: Implement and test ArrayBuffer.prototype.resize

This commit is contained in:
ForLoveOfCats 2022-03-02 11:24:46 -05:00 committed by Linus Groh
parent b29e19c52a
commit f350c153e8
7 changed files with 118 additions and 0 deletions

View file

@ -26,6 +26,7 @@ void ArrayBufferPrototype::initialize(GlobalObject& global_object)
Object::initialize(global_object);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(vm.names.slice, slice, 2, attr);
define_native_function(vm.names.resize, resize, 1, attr);
define_native_accessor(vm.names.byteLength, byte_length_getter, {}, Attribute::Configurable);
define_native_accessor(vm.names.maxByteLength, max_byte_length_getter, {}, Attribute::Configurable);
define_native_accessor(vm.names.resizable, resizable_getter, {}, Attribute::Configurable);
@ -127,6 +128,60 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::slice)
return new_array_buffer_object;
}
// 1.3.5 ArrayBuffer.prototype.resize ( newLength ), https://tc39.es/proposal-resizablearraybuffer/#sec-arraybuffer.prototype.resize
JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::resize)
{
// 1. Let O be the this value.
auto* array_buffer_object = TRY(typed_this_value(global_object));
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferMaxByteLength]]).
if (!array_buffer_object->is_resizable_array_buffer())
return vm.throw_completion<TypeError>(global_object, ErrorType::NotAResizableArrayBuffer);
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
// FIXME: Check for shared buffer
// 4. If IsDetachedBuffer(O) is true, throw a TypeError exception.
if (array_buffer_object->is_detached())
return vm.throw_completion<TypeError>(global_object, ErrorType::DetachedArrayBuffer);
// 5. Let newByteLength be ? ToIntegerOrInfinity(newLength).
auto new_byte_length = TRY(vm.argument(0).to_integer_or_infinity(global_object));
// 6. If newByteLength < 0 or newByteLength > O.[[ArrayBufferMaxByteLength]], throw a RangeError exception.
if (new_byte_length < 0 || new_byte_length > array_buffer_object->max_byte_length())
return vm.throw_completion<RangeError>(global_object, ErrorType::NewByteLengthOutOfRange);
// 7. Let hostHandled be ? HostResizeArrayBuffer(O, newByteLength).
auto host_handled = TRY(vm.host_resize_array_buffer(global_object, (size_t)new_byte_length));
// 8. If hostHandled is handled, return undefined.
if (host_handled == VM::HostResizeArrayBufferResult::Handled)
return js_undefined();
// 9. Let oldBlock be O.[[ArrayBufferData]].
// 10. Let newBlock be ? CreateByteDataBlock(newByteLength).
// 11. Let copyLength be min(newByteLength, O.[[ArrayBufferByteLength]]).
// 12. Perform CopyDataBlockBytes(newBlock, 0, oldBlock, 0, copyLength).
// 13. NOTE: Neither creation of the new Data Block nor copying from the old Data Block are observable. Implementations reserve the right to implement this method as in-place growth or shrinkage.
// 14. Set O.[[ArrayBufferData]] to newBlock.
auto old_byte_length = array_buffer_object->byte_length();
if (array_buffer_object->buffer().try_resize((size_t)new_byte_length).is_error())
return global_object.vm().throw_completion<RangeError>(global_object, ErrorType::NotEnoughMemoryToAllocate, new_byte_length);
// Resizing an `AK::ByteBuffer` does not zero initialize any new capacity, but we want it to be anyway
if (new_byte_length > old_byte_length) {
// This performs bounds checking whereas a raw `memset` call would not be
array_buffer_object->buffer().bytes().slice(old_byte_length).fill(0);
}
// 15. Set O.[[ArrayBufferByteLength]] to newLength.
// This value is managed by the `AK::ByteBuffer` internally, it already has its new value
// 16. Return undefined.
return js_undefined();
}
// 25.1.5.1 get ArrayBuffer.prototype.byteLength, https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.bytelength
JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::byte_length_getter)
{

View file

@ -21,6 +21,7 @@ public:
private:
JS_DECLARE_NATIVE_FUNCTION(slice);
JS_DECLARE_NATIVE_FUNCTION(resize);
JS_DECLARE_NATIVE_FUNCTION(byte_length_getter);
JS_DECLARE_NATIVE_FUNCTION(max_byte_length_getter);
JS_DECLARE_NATIVE_FUNCTION(resizable_getter);

View file

@ -380,6 +380,7 @@ namespace JS {
P(reject) \
P(relativeTo) \
P(repeat) \
P(resize) \
P(resizable) \
P(resolve) \
P(resolvedOptions) \

View file

@ -75,6 +75,7 @@
M(ModuleNotFound, "Cannot find/open module: '{}'") \
M(ModuleNotFoundNoReferencingScript, "Cannot resolve module {} without any active script or module") \
M(NegativeExponent, "Exponent must be positive") \
M(NewByteLengthOutOfRange, "New byte length outside range supported by ArrayBuffer instance") \
M(NonExtensibleDefine, "Cannot define property {} on non-extensible object") \
M(NotAConstructor, "{} is not a constructor") \
M(NotAFunction, "{} is not a function") \
@ -83,6 +84,7 @@
M(NotAnObjectOfType, "Not an object of type {}") \
M(NotAnObjectOrNull, "{} is neither an object nor null") \
M(NotAnObjectOrString, "{} is neither an object nor a string") \
M(NotAResizableArrayBuffer, "ArrayBuffer instance not resizable") \
M(NotAString, "{} is not a string") \
M(NotASymbol, "{} is not a symbol") \
M(NotImplemented, "TODO({} is not implemented in LibJS)") \

View file

@ -14,6 +14,7 @@
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/BoundFunction.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
@ -113,6 +114,23 @@ VM::VM(OwnPtr<CustomData> custom_data)
return Vector<String> { "type" };
};
// 1.1.7 HostResizeArrayBuffer ( buffer, newByteLength ), https://tc39.es/proposal-resizablearraybuffer/#sec-hostresizearraybuffer
host_resize_array_buffer = [](GlobalObject& global_object, size_t new_byte_length) {
// The host-defined abstract operation HostResizeArrayBuffer takes arguments buffer (an ArrayBuffer) and newByteLength (a non-negative integer).
// The host-defined abstract operation HostResizeArrayBuffer takes arguments buffer (an ArrayBuffer object) and newByteLength.
// It gives the host an opportunity to perform implementation-defined resizing of buffer. If the host chooses not to handle resizing of buffer, it may return unhandled for the default behavior.
// The implementation of HostResizeArrayBuffer must conform to the following requirements:
// * The abstract operation must return either NormalCompletion(handled), NormalCompletion(unhandled), or an abrupt throw completion.
// * The abstract operation does not detach buffer.
// * If the abstract operation completes normally with handled, buffer.[[ArrayBufferByteLength]] is newByteLength.
// The default implementation of HostResizeArrayBuffer is to return unhandled.
(void)global_object;
(void)new_byte_length;
return HostResizeArrayBufferResult::Unhandled;
};
#define __JS_ENUMERATE(SymbolName, snake_name) \
m_well_known_symbol_##snake_name = js_symbol(*this, "Symbol." #SymbolName, false);
JS_ENUMERATE_WELL_KNOWN_SYMBOLS

View file

@ -39,6 +39,11 @@ public:
static NonnullRefPtr<VM> create(OwnPtr<CustomData> = {});
~VM();
enum class HostResizeArrayBufferResult {
Unhandled,
Handled,
};
Heap& heap() { return m_heap; }
const Heap& heap() const { return m_heap; }
@ -223,6 +228,7 @@ public:
Function<void(FinalizationRegistry&)> host_enqueue_finalization_registry_cleanup_job;
Function<void(Function<ThrowCompletionOr<Value>()>, Realm*)> host_enqueue_promise_job;
Function<JobCallback(FunctionObject&)> host_make_job_callback;
Function<ThrowCompletionOr<HostResizeArrayBufferResult>(GlobalObject&, size_t)> host_resize_array_buffer;
private:
explicit VM(OwnPtr<CustomData>);

View file

@ -0,0 +1,35 @@
test("length is 1", () => {
expect(ArrayBuffer.prototype.resize).toHaveLength(1);
});
test("resize up to max", () => {
let a = new ArrayBuffer(0, { maxByteLength: 10 });
a.resize(10);
expect(a.byteLength).toEqual(10);
});
test("resize less than max", () => {
let a = new ArrayBuffer(10, { maxByteLength: 10 });
a.resize(5);
expect(a.byteLength).toEqual(5);
});
test("resize with negative length", () => {
let a = new ArrayBuffer(10, { maxByteLength: 10 });
expect(() => {
a.resize(-1);
}).toThrowWithMessage(
RangeError,
"New byte length outside range supported by ArrayBuffer instance"
);
});
test("resize past max length", () => {
let a = new ArrayBuffer(10, { maxByteLength: 10 });
expect(() => {
a.resize(11);
}).toThrowWithMessage(
RangeError,
"New byte length outside range supported by ArrayBuffer instance"
);
});