diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp index c0b10ec221..57cb232bcf 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp +++ b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020-2021, Linus Groh + * Copyright (c) 2022, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ @@ -54,8 +55,10 @@ void ArrayBuffer::visit_edges(Cell::Visitor& visitor) } // 25.1.2.1 AllocateArrayBuffer ( constructor, byteLength ), https://tc39.es/ecma262/#sec-allocatearraybuffer -ThrowCompletionOr allocate_array_buffer(GlobalObject& global_object, FunctionObject& constructor, size_t byte_length) +// 1.1.2 AllocateArrayBuffer ( constructor, byteLength [, maxByteLength ] ), https://tc39.es/proposal-resizablearraybuffer/#sec-allocatearraybuffer +ThrowCompletionOr allocate_array_buffer(GlobalObject& global_object, FunctionObject& constructor, size_t byte_length, Optional max_byte_length) { + (void)max_byte_length; // 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] »). auto* obj = TRY(ordinary_create_from_constructor(global_object, constructor, &GlobalObject::array_buffer_prototype, nullptr)); diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h index 58693e49b7..642eb900d3 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h +++ b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h @@ -75,7 +75,7 @@ private: Value m_detach_key; }; -ThrowCompletionOr allocate_array_buffer(GlobalObject&, FunctionObject& constructor, size_t byte_length); +ThrowCompletionOr allocate_array_buffer(GlobalObject&, FunctionObject& constructor, size_t byte_length, Optional max_byte_length = {}); ThrowCompletionOr clone_array_buffer(GlobalObject&, ArrayBuffer& source_buffer, size_t source_byte_offset, size_t source_length, FunctionObject& clone_constructor); // 25.1.2.9 RawBytesToNumeric ( type, rawBytes, isLittleEndian ), https://tc39.es/ecma262/#sec-rawbytestonumeric diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.cpp index 43318a0328..db1a394e66 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Linus Groh + * Copyright (c) 2022, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ @@ -46,20 +47,63 @@ ThrowCompletionOr ArrayBufferConstructor::call() return vm.throw_completion(global_object(), ErrorType::ConstructorWithoutNew, vm.names.ArrayBuffer); } +// 1.1.6 GetArrayBufferMaxByteLengthOption ( options ), https://tc39.es/proposal-resizablearraybuffer/#sec-getarraybuffermaxbytelengthoption +static ThrowCompletionOr> get_array_buffer_max_byte_length_option(VM& vm, GlobalObject& global_object, Value options) +{ + // 1. If Type(options) is not Object, return empty. + if (!options.is_object()) + return Optional(); + + // 2. Let maxByteLength be ? Get(options, "maxByteLength"). + auto max_byte_length = TRY(options.get(global_object, vm.names.maxByteLength)); + + // 3. If maxByteLength is undefined, return empty. + if (max_byte_length.is_undefined()) + return Optional(); + + // 4. Return ? ToIndex(maxByteLength). + return TRY(max_byte_length.to_index(global_object)); +} + // 25.1.3.1 ArrayBuffer ( length ), https://tc39.es/ecma262/#sec-arraybuffer-length +// 1.2.1 ArrayBuffer ( length [, options ] ), https://tc39.es/proposal-resizablearraybuffer/#sec-arraybuffer-constructor ThrowCompletionOr ArrayBufferConstructor::construct(FunctionObject& new_target) { + auto& global_object = new_target.realm()->global_object(); auto& vm = this->vm(); - auto byte_length_or_error = vm.argument(0).to_index(global_object()); + + // 1. If NewTarget is undefined, throw a TypeError exception. + // NOTE: See `ArrayBufferConstructor::call()` + + // 2. Let byteLength be ? ToIndex(length). + auto byte_length_or_error = vm.argument(0).to_index(global_object); if (byte_length_or_error.is_error()) { auto error = byte_length_or_error.release_error(); if (error.value()->is_object() && is(error.value()->as_object())) { // Re-throw more specific RangeError - return vm.throw_completion(global_object(), ErrorType::InvalidLength, "array buffer"); + return vm.throw_completion(global_object, ErrorType::InvalidLength, "array buffer"); } return error; } - return TRY(allocate_array_buffer(global_object(), new_target, byte_length_or_error.release_value())); + auto byte_length = byte_length_or_error.release_value(); + + // 3. Let requestedMaxByteLength be ? GetArrayBufferMaxByteLengthOption(options). + auto options = vm.argument(1); + auto requested_max_byte_length_value = TRY(get_array_buffer_max_byte_length_option(vm, global_object, options)); + + // 4. If requestedMaxByteLength is empty, then + if (!requested_max_byte_length_value.has_value()) { + // a. Return ? AllocateArrayBuffer(NewTarget, byteLength). + return TRY(allocate_array_buffer(global_object, new_target, byte_length)); + } + auto requested_max_byte_length = requested_max_byte_length_value.release_value(); + + // 5. If byteLength > requestedMaxByteLength, throw a RangeError exception. + if (byte_length > requested_max_byte_length) + return vm.throw_completion(global_object, ErrorType::ByteLengthBeyondRequestedMax); + + // 6. Return ? AllocateArrayBuffer(NewTarget, byteLength, requestedMaxByteLength). + return TRY(allocate_array_buffer(global_object, new_target, byte_length, requested_max_byte_length)); } // 25.1.4.1 ArrayBuffer.isView ( arg ), https://tc39.es/ecma262/#sec-arraybuffer.isview diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 02672074af..7444be9ddc 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -314,6 +314,7 @@ namespace JS { P(log10) \ P(map) \ P(max) \ + P(maxByteLength) \ P(maximize) \ P(mergeFields) \ P(message) \ diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index ac68590376..3ac7f5fc9a 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -15,6 +15,7 @@ M(BigIntFromNonIntegral, "Cannot convert non-integral number to BigInt") \ M(BigIntInvalidValue, "Invalid value for BigInt: {}") \ M(BindingNotInitialized, "Binding {} is not initialized") \ + M(ByteLengthBeyondRequestedMax, "Byte length exceeds maxByteLength option") \ M(CallStackSizeExceeded, "Call stack size limit exceeded") \ M(CannotDeclareGlobalFunction, "Cannot declare global function of name '{}'") \ M(CannotDeclareGlobalVariable, "Cannot declare global variable of name '{}'") \