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:
parent
b29e19c52a
commit
f350c153e8
7 changed files with 118 additions and 0 deletions
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -380,6 +380,7 @@ namespace JS {
|
|||
P(reject) \
|
||||
P(relativeTo) \
|
||||
P(repeat) \
|
||||
P(resize) \
|
||||
P(resizable) \
|
||||
P(resolve) \
|
||||
P(resolvedOptions) \
|
||||
|
|
|
@ -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)") \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>);
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue