mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 21:27:35 +00:00
LibJS: Partially implement resizable ArrayBuffer objects
This is (part of) a normative change in the ECMA-262 spec. See:
a9ae96e
This implements just support for resizing ArrayBuffer objects. This does
not implement the SharedArrayBuffer changes, as we do not have enough
support to do so.
This commit is contained in:
parent
a1e2f131c4
commit
29ac6e3689
13 changed files with 369 additions and 14 deletions
|
@ -139,23 +139,64 @@ void copy_data_block_bytes(ByteBuffer& to_block, u64 to_index, ByteBuffer const&
|
|||
}
|
||||
|
||||
// 25.1.3.1 AllocateArrayBuffer ( constructor, byteLength [ , maxByteLength ] ), https://tc39.es/ecma262/#sec-allocatearraybuffer
|
||||
ThrowCompletionOr<ArrayBuffer*> allocate_array_buffer(VM& vm, FunctionObject& constructor, size_t byte_length)
|
||||
ThrowCompletionOr<ArrayBuffer*> allocate_array_buffer(VM& vm, FunctionObject& constructor, size_t byte_length, Optional<size_t> const& max_byte_length)
|
||||
{
|
||||
// 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] »).
|
||||
// 1. Let slots be « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] ».
|
||||
|
||||
// 2. If maxByteLength is present and maxByteLength is not empty, let allocatingResizableBuffer be true; otherwise let allocatingResizableBuffer be false.
|
||||
auto allocating_resizable_buffer = max_byte_length.has_value();
|
||||
|
||||
// 3. If allocatingResizableBuffer is true, then
|
||||
if (allocating_resizable_buffer) {
|
||||
// a. If byteLength > maxByteLength, throw a RangeError exception.
|
||||
if (byte_length > *max_byte_length)
|
||||
return vm.throw_completion<RangeError>(ErrorType::ByteLengthExceedsMaxByteLength, byte_length, *max_byte_length);
|
||||
|
||||
// b. Append [[ArrayBufferMaxByteLength]] to slots.
|
||||
}
|
||||
|
||||
// 4. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", slots).
|
||||
auto obj = TRY(ordinary_create_from_constructor<ArrayBuffer>(vm, constructor, &Intrinsics::array_buffer_prototype, nullptr));
|
||||
|
||||
// 2. Let block be ? CreateByteDataBlock(byteLength).
|
||||
// 5. Let block be ? CreateByteDataBlock(byteLength).
|
||||
auto block = TRY(create_byte_data_block(vm, byte_length));
|
||||
|
||||
// 3. Set obj.[[ArrayBufferData]] to block.
|
||||
// 6. Set obj.[[ArrayBufferData]] to block.
|
||||
obj->set_data_block(move(block));
|
||||
|
||||
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
|
||||
// 7. Set obj.[[ArrayBufferByteLength]] to byteLength.
|
||||
|
||||
// 5. Return obj.
|
||||
// 8. If allocatingResizableBuffer is true, then
|
||||
if (allocating_resizable_buffer) {
|
||||
// a. If it is not possible to create a Data Block block consisting of maxByteLength bytes, throw a RangeError exception.
|
||||
// b. NOTE: Resizable ArrayBuffers are designed to be implementable with in-place growth. Implementations may throw if, for example, virtual memory cannot be reserved up front.
|
||||
if (auto result = obj->buffer().try_ensure_capacity(*max_byte_length); result.is_error())
|
||||
return vm.throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, *max_byte_length);
|
||||
|
||||
// c. Set obj.[[ArrayBufferMaxByteLength]] to maxByteLength.
|
||||
obj->set_max_byte_length(*max_byte_length);
|
||||
}
|
||||
|
||||
// 9. Return obj.
|
||||
return obj.ptr();
|
||||
}
|
||||
|
||||
// 25.1.3.2 ArrayBufferByteLength ( arrayBuffer, order ), https://tc39.es/ecma262/#sec-arraybufferbytelength
|
||||
size_t array_buffer_byte_length(ArrayBuffer const& array_buffer, ArrayBuffer::Order)
|
||||
{
|
||||
// FIXME: 1. If IsSharedArrayBuffer(arrayBuffer) is true and arrayBuffer has an [[ArrayBufferByteLengthData]] internal slot, then
|
||||
// FIXME: a. Let bufferByteLengthBlock be arrayBuffer.[[ArrayBufferByteLengthData]].
|
||||
// FIXME: b. Let rawLength be GetRawBytesFromSharedBlock(bufferByteLengthBlock, 0, biguint64, true, order).
|
||||
// FIXME: c. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
|
||||
// FIXME: d. Return ℝ(RawBytesToNumeric(biguint64, rawLength, isLittleEndian)).
|
||||
|
||||
// 2. Assert: IsDetachedBuffer(arrayBuffer) is false.
|
||||
VERIFY(!array_buffer.is_detached());
|
||||
|
||||
// 3. Return arrayBuffer.[[ArrayBufferByteLength]].
|
||||
return array_buffer.byte_length();
|
||||
}
|
||||
|
||||
// 25.1.3.4 DetachArrayBuffer ( arrayBuffer [ , key ] ), https://tc39.es/ecma262/#sec-detacharraybuffer
|
||||
ThrowCompletionOr<void> detach_array_buffer(VM& vm, ArrayBuffer& array_buffer, Optional<Value> key)
|
||||
{
|
||||
|
@ -202,6 +243,24 @@ ThrowCompletionOr<ArrayBuffer*> clone_array_buffer(VM& vm, ArrayBuffer& source_b
|
|||
return target_buffer;
|
||||
}
|
||||
|
||||
// 25.1.3.6 GetArrayBufferMaxByteLengthOption ( options ), https://tc39.es/ecma262/#sec-getarraybuffermaxbytelengthoption
|
||||
ThrowCompletionOr<Optional<size_t>> get_array_buffer_max_byte_length_option(VM& vm, Value options)
|
||||
{
|
||||
// 1. If options is not an Object, return empty.
|
||||
if (!options.is_object())
|
||||
return OptionalNone {};
|
||||
|
||||
// 2. Let maxByteLength be ? Get(options, "maxByteLength").
|
||||
auto max_byte_length = TRY(options.as_object().get(vm.names.maxByteLength));
|
||||
|
||||
// 3. If maxByteLength is undefined, return empty.
|
||||
if (max_byte_length.is_undefined())
|
||||
return OptionalNone {};
|
||||
|
||||
// 4. Return ? ToIndex(maxByteLength).
|
||||
return TRY(max_byte_length.to_index(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)
|
||||
{
|
||||
|
|
|
@ -69,6 +69,10 @@ public:
|
|||
ByteBuffer& buffer() { return m_data_block.buffer(); }
|
||||
ByteBuffer const& buffer() const { return m_data_block.buffer(); }
|
||||
|
||||
// [[ArrayBufferMaxByteLength]]
|
||||
size_t max_byte_length() const { return m_max_byte_length.value(); }
|
||||
void set_max_byte_length(size_t max_byte_length) { m_max_byte_length = max_byte_length; }
|
||||
|
||||
// Used by allocate_array_buffer() to attach the data block after construction
|
||||
void set_data_block(DataBlock block) { m_data_block = move(block); }
|
||||
|
||||
|
@ -87,6 +91,17 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
// 25.1.3.8 IsFixedLengthArrayBuffer ( arrayBuffer ), https://tc39.es/ecma262/#sec-isfixedlengtharraybuffer
|
||||
bool is_fixed_length() const
|
||||
{
|
||||
// 1. If arrayBuffer has an [[ArrayBufferMaxByteLength]] internal slot, return false.
|
||||
if (m_max_byte_length.has_value())
|
||||
return false;
|
||||
|
||||
// 2. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
// 25.2.2.2 IsSharedArrayBuffer ( obj ), https://tc39.es/ecma262/#sec-issharedarraybuffer
|
||||
bool is_shared_array_buffer() const
|
||||
{
|
||||
|
@ -121,6 +136,8 @@ private:
|
|||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
DataBlock m_data_block;
|
||||
Optional<size_t> m_max_byte_length;
|
||||
|
||||
// The various detach related members of ArrayBuffer are not used by any ECMA262 functionality,
|
||||
// but are required to be available for the use of various harnesses like the Test262 test runner.
|
||||
Value m_detach_key;
|
||||
|
@ -128,8 +145,10 @@ private:
|
|||
|
||||
ThrowCompletionOr<DataBlock> create_byte_data_block(VM& vm, size_t size);
|
||||
void copy_data_block_bytes(ByteBuffer& to_block, u64 to_index, ByteBuffer const& from_block, u64 from_index, u64 count);
|
||||
ThrowCompletionOr<ArrayBuffer*> allocate_array_buffer(VM&, FunctionObject& constructor, size_t byte_length);
|
||||
ThrowCompletionOr<ArrayBuffer*> allocate_array_buffer(VM&, FunctionObject& constructor, size_t byte_length, Optional<size_t> const& max_byte_length = {});
|
||||
size_t array_buffer_byte_length(ArrayBuffer const&, ArrayBuffer::Order);
|
||||
ThrowCompletionOr<void> detach_array_buffer(VM&, ArrayBuffer& array_buffer, Optional<Value> key = {});
|
||||
ThrowCompletionOr<Optional<size_t>> get_array_buffer_max_byte_length_option(VM&, Value options);
|
||||
ThrowCompletionOr<ArrayBuffer*> clone_array_buffer(VM&, ArrayBuffer& source_buffer, size_t source_byte_offset, size_t source_length);
|
||||
ThrowCompletionOr<ArrayBuffer*> array_buffer_copy_and_detach(VM&, ArrayBuffer& array_buffer, Value new_length, PreserveResizability preserve_resizability);
|
||||
ThrowCompletionOr<NonnullGCPtr<ArrayBuffer>> allocate_shared_array_buffer(VM&, FunctionObject& constructor, size_t byte_length);
|
||||
|
|
|
@ -52,8 +52,11 @@ ThrowCompletionOr<NonnullGCPtr<Object>> ArrayBufferConstructor::construct(Functi
|
|||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
auto length = vm.argument(0);
|
||||
auto options = vm.argument(1);
|
||||
|
||||
// 2. Let byteLength be ? ToIndex(length).
|
||||
auto byte_length_or_error = vm.argument(0).to_index(vm);
|
||||
auto byte_length_or_error = length.to_index(vm);
|
||||
|
||||
if (byte_length_or_error.is_error()) {
|
||||
auto error = byte_length_or_error.release_error();
|
||||
|
@ -64,8 +67,11 @@ ThrowCompletionOr<NonnullGCPtr<Object>> ArrayBufferConstructor::construct(Functi
|
|||
return error;
|
||||
}
|
||||
|
||||
// 3. Return ? AllocateArrayBuffer(NewTarget, byteLength).
|
||||
return *TRY(allocate_array_buffer(vm, new_target, byte_length_or_error.release_value()));
|
||||
// 3. Let requestedMaxByteLength be ? GetArrayBufferMaxByteLengthOption(options).
|
||||
auto requested_max_byte_length = TRY(get_array_buffer_max_byte_length_option(vm, options));
|
||||
|
||||
// 3. Return ? AllocateArrayBuffer(NewTarget, byteLength, requestedMaxByteLength).
|
||||
return *TRY(allocate_array_buffer(vm, new_target, byte_length_or_error.release_value(), requested_max_byte_length));
|
||||
}
|
||||
|
||||
// 25.1.5.1 ArrayBuffer.isView ( arg ), https://tc39.es/ecma262/#sec-arraybuffer.isview
|
||||
|
|
|
@ -27,6 +27,9 @@ void ArrayBufferPrototype::initialize(Realm& realm)
|
|||
Base::initialize(realm);
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_accessor(realm, vm.names.byteLength, byte_length_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.maxByteLength, max_byte_length, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.resizable, resizable, {}, Attribute::Configurable);
|
||||
define_native_function(realm, vm.names.resize, resize, 1, attr);
|
||||
define_native_function(realm, vm.names.slice, slice, 2, attr);
|
||||
define_native_accessor(realm, vm.names.detached, detached_getter, {}, Attribute::Configurable);
|
||||
define_native_function(realm, vm.names.transfer, transfer, 0, attr);
|
||||
|
@ -54,6 +57,110 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::byte_length_getter)
|
|||
return Value(array_buffer_object->byte_length());
|
||||
}
|
||||
|
||||
// 25.1.6.3 get ArrayBuffer.prototype.maxByteLength, https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.maxbytelength
|
||||
JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::max_byte_length)
|
||||
{
|
||||
// 1. Let O be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
||||
auto array_buffer_object = TRY(typed_this_value(vm));
|
||||
|
||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
||||
if (array_buffer_object->is_shared_array_buffer())
|
||||
return vm.throw_completion<TypeError>(ErrorType::ThisCannotBeSharedArrayBuffer);
|
||||
|
||||
// 4. If IsDetachedBuffer(O) is true, return +0𝔽.
|
||||
if (array_buffer_object->is_detached())
|
||||
return Value { 0 };
|
||||
|
||||
size_t length = 0;
|
||||
|
||||
// 5. If IsFixedLengthArrayBuffer(O) is true, then
|
||||
if (array_buffer_object->is_fixed_length()) {
|
||||
// a. Let length be O.[[ArrayBufferByteLength]].
|
||||
length = array_buffer_object->byte_length();
|
||||
}
|
||||
// 6. Else,
|
||||
else {
|
||||
// a. Let length be O.[[ArrayBufferMaxByteLength]].
|
||||
length = array_buffer_object->max_byte_length();
|
||||
}
|
||||
|
||||
// 7. Return 𝔽(length).
|
||||
return Value { length };
|
||||
}
|
||||
|
||||
// 25.1.6.4 get ArrayBuffer.prototype.resizable, https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.resizable
|
||||
JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::resizable)
|
||||
{
|
||||
// 1. Let O be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
||||
auto array_buffer_object = TRY(typed_this_value(vm));
|
||||
|
||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
||||
if (array_buffer_object->is_shared_array_buffer())
|
||||
return vm.throw_completion<TypeError>(ErrorType::ThisCannotBeSharedArrayBuffer);
|
||||
|
||||
// 4. If IsFixedLengthArrayBuffer(O) is false, return true; otherwise return false.
|
||||
return Value { !array_buffer_object->is_fixed_length() };
|
||||
}
|
||||
|
||||
// 25.1.6.5 ArrayBuffer.prototype.resize ( newLength ), https://tc39.es/ecma262/#sec-arraybuffer.prototype.resize
|
||||
JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::resize)
|
||||
{
|
||||
auto new_length = vm.argument(0);
|
||||
|
||||
// 1. Let O be the this value.
|
||||
auto array_buffer_object = TRY(typed_this_value(vm));
|
||||
|
||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferMaxByteLength]]).
|
||||
if (array_buffer_object->is_fixed_length())
|
||||
return vm.throw_completion<TypeError>(ErrorType::FixedArrayBuffer);
|
||||
|
||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
||||
if (array_buffer_object->is_shared_array_buffer())
|
||||
return vm.throw_completion<TypeError>(ErrorType::ThisCannotBeSharedArrayBuffer);
|
||||
|
||||
// 4. Let newByteLength be ? ToIndex(newLength).
|
||||
auto new_byte_length = TRY(new_length.to_index(vm));
|
||||
|
||||
// 5. If IsDetachedBuffer(O) is true, throw a TypeError exception.
|
||||
if (array_buffer_object->is_detached())
|
||||
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
|
||||
|
||||
// 6. If newByteLength > O.[[ArrayBufferMaxByteLength]], throw a RangeError exception.
|
||||
if (new_byte_length > array_buffer_object->max_byte_length())
|
||||
return vm.throw_completion<RangeError>(ErrorType::ByteLengthExceedsMaxByteLength, new_byte_length, array_buffer_object->max_byte_length());
|
||||
|
||||
// 7. Let hostHandled be ? HostResizeArrayBuffer(O, newByteLength).
|
||||
auto host_handled = TRY(vm.host_resize_array_buffer(array_buffer_object, new_byte_length));
|
||||
|
||||
// 8. If hostHandled is handled, return undefined.
|
||||
if (host_handled == HandledByHost::Handled)
|
||||
return js_undefined();
|
||||
|
||||
// 9. Let oldBlock be O.[[ArrayBufferData]].
|
||||
auto const& old_block = array_buffer_object->buffer();
|
||||
|
||||
// 10. Let newBlock be ? CreateByteDataBlock(newByteLength).
|
||||
auto new_block = TRY(create_byte_data_block(vm, new_byte_length));
|
||||
|
||||
// 11. Let copyLength be min(newByteLength, O.[[ArrayBufferByteLength]]).
|
||||
auto copy_length = min(new_byte_length, array_buffer_object->byte_length());
|
||||
|
||||
// 12. Perform CopyDataBlockBytes(newBlock, 0, oldBlock, 0, copyLength).
|
||||
copy_data_block_bytes(new_block.buffer(), 0, old_block, 0, copy_length);
|
||||
|
||||
// 13. NOTE: Neither creation of the new Data Block nor copying from the old Data Block are observable. Implementations may implement this method as in-place growth or shrinkage.
|
||||
|
||||
// 14. Set O.[[ArrayBufferData]] to newBlock.
|
||||
array_buffer_object->set_data_block(move(new_block));
|
||||
|
||||
// 15. Set O.[[ArrayBufferByteLength]] to newByteLength.
|
||||
|
||||
// 16. Return undefined.
|
||||
return js_undefined();
|
||||
}
|
||||
|
||||
// 25.1.6.6 ArrayBuffer.prototype.slice ( start, end ), https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice
|
||||
JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::slice)
|
||||
{
|
||||
|
@ -133,7 +240,8 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::slice)
|
|||
if (new_array_buffer_object->byte_length() < new_length)
|
||||
return vm.throw_completion<TypeError>(ErrorType::SpeciesConstructorReturned, "an ArrayBuffer smaller than requested");
|
||||
|
||||
// 22. NOTE: Side-effects of the above steps may have detached O.
|
||||
// 22. NOTE: Side-effects of the above steps may have detached or resized O.
|
||||
|
||||
// 23. If IsDetachedBuffer(O) is true, throw a TypeError exception.
|
||||
if (array_buffer_object->is_detached())
|
||||
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
|
||||
|
@ -144,10 +252,19 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::slice)
|
|||
// 25. Let toBuf be new.[[ArrayBufferData]].
|
||||
auto& to_buf = new_array_buffer_object->buffer();
|
||||
|
||||
// 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen).
|
||||
copy_data_block_bytes(to_buf, 0, from_buf, first, new_length);
|
||||
// 26. Let currentLen be O.[[ArrayBufferByteLength]].
|
||||
auto current_length = array_buffer_object->byte_length();
|
||||
|
||||
// 27. Return new.
|
||||
// 27. If first < currentLen, then
|
||||
if (first < current_length) {
|
||||
// a. Let count be min(newLen, currentLen - first).
|
||||
auto count = min(new_length, current_length - first);
|
||||
|
||||
// b. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, count).
|
||||
copy_data_block_bytes(to_buf, 0, from_buf, first, count);
|
||||
}
|
||||
|
||||
// 28. Return new.
|
||||
return new_array_buffer_object;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,9 @@ private:
|
|||
explicit ArrayBufferPrototype(Realm&);
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(byte_length_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(max_byte_length);
|
||||
JS_DECLARE_NATIVE_FUNCTION(resizable);
|
||||
JS_DECLARE_NATIVE_FUNCTION(resize);
|
||||
JS_DECLARE_NATIVE_FUNCTION(slice);
|
||||
JS_DECLARE_NATIVE_FUNCTION(detached_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(transfer);
|
||||
|
|
|
@ -351,6 +351,7 @@ namespace JS {
|
|||
P(leftContext) \
|
||||
P(map) \
|
||||
P(max) \
|
||||
P(maxByteLength) \
|
||||
P(maximize) \
|
||||
P(mergeFields) \
|
||||
P(message) \
|
||||
|
@ -426,6 +427,8 @@ namespace JS {
|
|||
P(reject) \
|
||||
P(relativeTo) \
|
||||
P(repeat) \
|
||||
P(resizable) \
|
||||
P(resize) \
|
||||
P(resolve) \
|
||||
P(resolvedOptions) \
|
||||
P(reverse) \
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
M(BigIntFromNonIntegral, "Cannot convert non-integral number to BigInt") \
|
||||
M(BigIntInvalidValue, "Invalid value for BigInt: {}") \
|
||||
M(BindingNotInitialized, "Binding {} is not initialized") \
|
||||
M(ByteLengthExceedsMaxByteLength, "ArrayBuffer byte length of {} exceeds the max byte length of {}") \
|
||||
M(CallStackSizeExceeded, "Call stack size limit exceeded") \
|
||||
M(CannotDeclareGlobalFunction, "Cannot declare global function of name '{}'") \
|
||||
M(CannotDeclareGlobalVariable, "Cannot declare global variable of name '{}'") \
|
||||
|
@ -37,6 +38,7 @@
|
|||
M(DivisionByZero, "Division by zero") \
|
||||
M(DynamicImportNotAllowed, "Dynamic Imports are not allowed") \
|
||||
M(FinalizationRegistrySameTargetAndValue, "Target and held value must not be the same") \
|
||||
M(FixedArrayBuffer, "ArrayBuffer is not resizable") \
|
||||
M(GetCapabilitiesExecutorCalledMultipleTimes, "GetCapabilitiesExecutor was called multiple times") \
|
||||
M(GeneratorAlreadyExecuting, "Generator is already executing") \
|
||||
M(GeneratorBrandMismatch, "Generator brand '{}' does not match generator brand '{}')") \
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <LibJS/JIT/NativeExecutable.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>
|
||||
|
@ -134,6 +135,25 @@ VM::VM(OwnPtr<CustomData> custom_data, ErrorMessages error_messages)
|
|||
// NOTE: Since LibJS has no way of knowing whether the current environment is a browser we always
|
||||
// call HostEnsureCanAddPrivateElement when needed.
|
||||
};
|
||||
|
||||
// 25.1.3.7 HostResizeArrayBuffer ( buffer, newByteLength ), https://tc39.es/ecma262/#sec-hostresizearraybuffer
|
||||
host_resize_array_buffer = [this](ArrayBuffer& buffer, size_t new_byte_length) -> ThrowCompletionOr<HandledByHost> {
|
||||
// The host-defined abstract operation HostResizeArrayBuffer takes arguments buffer (an ArrayBuffer) and
|
||||
// newByteLength (a non-negative integer) and returns either a normal completion containing either handled or
|
||||
// unhandled, or a throw completion. 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 behaviour.
|
||||
|
||||
// The implementation of HostResizeArrayBuffer must conform to the following requirements:
|
||||
// - 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 NormalCompletion(unhandled).
|
||||
|
||||
if (auto result = buffer.buffer().try_resize(new_byte_length); result.is_error())
|
||||
return throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, new_byte_length);
|
||||
|
||||
return HandledByHost::Handled;
|
||||
};
|
||||
}
|
||||
|
||||
VM::~VM() = default;
|
||||
|
|
|
@ -32,6 +32,11 @@ namespace JS {
|
|||
class Identifier;
|
||||
struct BindingPattern;
|
||||
|
||||
enum class HandledByHost {
|
||||
Handled,
|
||||
Unhandled,
|
||||
};
|
||||
|
||||
class VM : public RefCounted<VM> {
|
||||
public:
|
||||
struct CustomData {
|
||||
|
@ -252,6 +257,7 @@ public:
|
|||
Function<JobCallback(FunctionObject&)> host_make_job_callback;
|
||||
Function<ThrowCompletionOr<void>(Realm&)> host_ensure_can_compile_strings;
|
||||
Function<ThrowCompletionOr<void>(Object&)> host_ensure_can_add_private_element;
|
||||
Function<ThrowCompletionOr<HandledByHost>(ArrayBuffer&, size_t)> host_resize_array_buffer;
|
||||
|
||||
// Execute a specific AST node either in AST or BC interpreter, depending on which one is enabled by default.
|
||||
// NOTE: This is meant as a temporary stopgap until everything is bytecode.
|
||||
|
|
|
@ -17,3 +17,18 @@ test("ArrayBuffer size limit", () => {
|
|||
new ArrayBuffer(2 ** 53);
|
||||
}).toThrowWithMessage(RangeError, "Invalid array buffer length");
|
||||
});
|
||||
|
||||
test("invalid ArrayBuffer maximum size option", () => {
|
||||
expect(() => {
|
||||
new ArrayBuffer(10, { maxByteLength: -1 });
|
||||
}).toThrowWithMessage(RangeError, "Index must be a positive integer");
|
||||
});
|
||||
|
||||
test("ArrayBuffer size exceeds maximum size", () => {
|
||||
expect(() => {
|
||||
new ArrayBuffer(10, { maxByteLength: 5 });
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"ArrayBuffer byte length of 10 exceeds the max byte length of 5"
|
||||
);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
describe("errors", () => {
|
||||
test("called on non-ArrayBuffer object", () => {
|
||||
expect(() => {
|
||||
ArrayBuffer.prototype.maxByteLength;
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type ArrayBuffer");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normal behavior", () => {
|
||||
test("detached buffer", () => {
|
||||
let buffer = new ArrayBuffer(5);
|
||||
detachArrayBuffer(buffer);
|
||||
expect(buffer.maxByteLength).toBe(0);
|
||||
|
||||
buffer = new ArrayBuffer(5, { maxByteLength: 10 });
|
||||
detachArrayBuffer(buffer);
|
||||
expect(buffer.maxByteLength).toBe(0);
|
||||
});
|
||||
|
||||
test("fixed buffer", () => {
|
||||
let buffer = new ArrayBuffer(5);
|
||||
expect(buffer.maxByteLength).toBe(5);
|
||||
});
|
||||
|
||||
test("resizable buffer", () => {
|
||||
let buffer = new ArrayBuffer(5, { maxByteLength: 10 });
|
||||
expect(buffer.maxByteLength).toBe(10);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
describe("errors", () => {
|
||||
test("called on non-ArrayBuffer object", () => {
|
||||
expect(() => {
|
||||
ArrayBuffer.prototype.resizable;
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type ArrayBuffer");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normal behavior", () => {
|
||||
test("fixed buffer", () => {
|
||||
let buffer = new ArrayBuffer(5);
|
||||
expect(buffer.resizable).toBeFalse();
|
||||
});
|
||||
|
||||
test("resizable buffer", () => {
|
||||
let buffer = new ArrayBuffer(5, { maxByteLength: 10 });
|
||||
expect(buffer.resizable).toBeTrue();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
describe("errors", () => {
|
||||
test("called on non-ArrayBuffer object", () => {
|
||||
expect(() => {
|
||||
ArrayBuffer.prototype.resize(10);
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type ArrayBuffer");
|
||||
});
|
||||
|
||||
test("fixed buffer", () => {
|
||||
let buffer = new ArrayBuffer(5);
|
||||
detachArrayBuffer(buffer);
|
||||
|
||||
expect(() => {
|
||||
buffer.resize(10);
|
||||
}).toThrowWithMessage(TypeError, "ArrayBuffer is not resizable");
|
||||
});
|
||||
|
||||
test("detached buffer", () => {
|
||||
let buffer = new ArrayBuffer(5, { maxByteLength: 10 });
|
||||
detachArrayBuffer(buffer);
|
||||
|
||||
expect(() => {
|
||||
buffer.resize(10);
|
||||
}).toThrowWithMessage(TypeError, "ArrayBuffer is detached");
|
||||
});
|
||||
|
||||
test("invalid new byte length", () => {
|
||||
let buffer = new ArrayBuffer(5, { maxByteLength: 10 });
|
||||
|
||||
expect(() => {
|
||||
buffer.resize(-1);
|
||||
}).toThrowWithMessage(RangeError, "Index must be a positive integer");
|
||||
});
|
||||
|
||||
test("new byte length exceeds maximum size", () => {
|
||||
let buffer = new ArrayBuffer(5, { maxByteLength: 10 });
|
||||
|
||||
expect(() => {
|
||||
buffer.resize(11);
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"ArrayBuffer byte length of 11 exceeds the max byte length of 10"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("normal behavior", () => {
|
||||
test("resizable buffer", () => {
|
||||
let buffer = new ArrayBuffer(5, { maxByteLength: 10 });
|
||||
expect(buffer.byteLength).toBe(5);
|
||||
expect(buffer.maxByteLength).toBe(10);
|
||||
|
||||
for (let i = 0; i <= buffer.maxByteLength; ++i) {
|
||||
buffer.resize(i);
|
||||
expect(buffer.byteLength).toBe(i);
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue