From e8416b603fafa59caf87c5b73ceda54a33d2bb7b Mon Sep 17 00:00:00 2001 From: ForLoveOfCats Date: Wed, 2 Mar 2022 11:44:32 -0500 Subject: [PATCH] LibJS: Accept ArrayBuffer constructor options argument Test262 seems to test the changes in the "Resizable ArrayBuffer and growable SharedArrayBuffer" proposal. Begin implementing this proposal by accepting the new options object argument to the ArrayBuffer constructor. https://tc39.es/proposal-resizablearraybuffer https://github.com/tc39/test262/blob/main/test/built-ins/ArrayBuffer/options-maxbytelength-diminuitive.js --- .../Libraries/LibJS/Runtime/ArrayBuffer.cpp | 5 +- .../Libraries/LibJS/Runtime/ArrayBuffer.h | 2 +- .../LibJS/Runtime/ArrayBufferConstructor.cpp | 50 +++++++++++++++++-- .../LibJS/Runtime/CommonPropertyNames.h | 1 + Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 1 + 5 files changed, 54 insertions(+), 5 deletions(-) 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 '{}'") \