From e4d267d4fbd0d12ee88d395f4c17e2e73a88b264 Mon Sep 17 00:00:00 2001 From: Idan Horowitz Date: Mon, 14 Jun 2021 01:47:08 +0300 Subject: [PATCH] LibJS: Add the DataView built-in object --- Userland/Libraries/LibJS/CMakeLists.txt | 3 + Userland/Libraries/LibJS/Forward.h | 1 + Userland/Libraries/LibJS/Runtime/DataView.cpp | 34 +++++++ Userland/Libraries/LibJS/Runtime/DataView.h | 36 ++++++++ .../LibJS/Runtime/DataViewConstructor.cpp | 90 +++++++++++++++++++ .../LibJS/Runtime/DataViewConstructor.h | 28 ++++++ .../LibJS/Runtime/DataViewPrototype.cpp | 78 ++++++++++++++++ .../LibJS/Runtime/DataViewPrototype.h | 27 ++++++ Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 2 + .../Libraries/LibJS/Runtime/GlobalObject.cpp | 3 + .../LibJS/Tests/builtins/DataView/DataView.js | 15 ++++ .../DataView/DataView.prototype.buffer.js | 4 + .../DataView/DataView.prototype.byteLength.js | 7 ++ .../DataView/DataView.prototype.byteOffset.js | 7 ++ Userland/Utilities/js.cpp | 16 ++++ 15 files changed, 351 insertions(+) create mode 100644 Userland/Libraries/LibJS/Runtime/DataView.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/DataView.h create mode 100644 Userland/Libraries/LibJS/Runtime/DataViewConstructor.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/DataViewConstructor.h create mode 100644 Userland/Libraries/LibJS/Runtime/DataViewPrototype.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/DataViewPrototype.h create mode 100644 Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.js create mode 100644 Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.buffer.js create mode 100644 Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteLength.js create mode 100644 Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteOffset.js diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 0e2bb053eb..7838999228 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -37,6 +37,9 @@ set(SOURCES Runtime/BooleanPrototype.cpp Runtime/BoundFunction.cpp Runtime/ConsoleObject.cpp + Runtime/DataView.cpp + Runtime/DataViewConstructor.cpp + Runtime/DataViewPrototype.cpp Runtime/DateConstructor.cpp Runtime/Date.cpp Runtime/DatePrototype.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 5329370c15..20efeb8549 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -31,6 +31,7 @@ __JS_ENUMERATE(ArrayBuffer, array_buffer, ArrayBufferPrototype, ArrayBufferConstructor, void) \ __JS_ENUMERATE(BigIntObject, bigint, BigIntPrototype, BigIntConstructor, void) \ __JS_ENUMERATE(BooleanObject, boolean, BooleanPrototype, BooleanConstructor, void) \ + __JS_ENUMERATE(DataView, data_view, DataViewPrototype, DataViewConstructor, void) \ __JS_ENUMERATE(Date, date, DatePrototype, DateConstructor, void) \ __JS_ENUMERATE(Error, error, ErrorPrototype, ErrorConstructor, void) \ __JS_ENUMERATE(Function, function, FunctionPrototype, FunctionConstructor, void) \ diff --git a/Userland/Libraries/LibJS/Runtime/DataView.cpp b/Userland/Libraries/LibJS/Runtime/DataView.cpp new file mode 100644 index 0000000000..836f8ccf26 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/DataView.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace JS { + +DataView* DataView::create(GlobalObject& global_object, ArrayBuffer* viewed_buffer, size_t byte_length, size_t byte_offset) +{ + return global_object.heap().allocate(global_object, *global_object.data_view_prototype(), viewed_buffer, byte_length, byte_offset); +} + +DataView::DataView(Object& prototype, ArrayBuffer* viewed_buffer, size_t byte_length, size_t byte_offset) + : Object(prototype) + , m_viewed_array_buffer(viewed_buffer) + , m_byte_length(byte_length) + , m_byte_offset(byte_offset) +{ +} + +DataView::~DataView() +{ +} + +void DataView::visit_edges(Visitor& visitor) +{ + Object::visit_edges(visitor); + visitor.visit(m_viewed_array_buffer); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/DataView.h b/Userland/Libraries/LibJS/Runtime/DataView.h new file mode 100644 index 0000000000..b4fc44bb9a --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/DataView.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace JS { + +class DataView : public Object { + JS_OBJECT(DataView, Object); + +public: + static DataView* create(GlobalObject&, ArrayBuffer*, size_t byte_length, size_t byte_offset); + + explicit DataView(Object& prototype, ArrayBuffer*, size_t byte_length, size_t byte_offset); + virtual ~DataView() override; + + ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; } + size_t byte_length() const { return m_byte_length; } + size_t byte_offset() const { return m_byte_offset; } + +private: + virtual void visit_edges(Visitor& visitor) override; + + ArrayBuffer* m_viewed_array_buffer { nullptr }; + size_t m_byte_length { 0 }; + size_t m_byte_offset { 0 }; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/DataViewConstructor.cpp b/Userland/Libraries/LibJS/Runtime/DataViewConstructor.cpp new file mode 100644 index 0000000000..05667876a9 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/DataViewConstructor.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace JS { + +DataViewConstructor::DataViewConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.DataView, *global_object.function_prototype()) +{ +} + +void DataViewConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + + // 25.3.3.1 DataView.prototype, https://tc39.es/ecma262/#sec-dataview.prototype + define_property(vm.names.prototype, global_object.data_view_prototype(), 0); + + define_property(vm.names.length, Value(1), Attribute::Configurable); +} + +DataViewConstructor::~DataViewConstructor() +{ +} + +// 25.3.2.1 DataView ( buffer [ , byteOffset [ , byteLength ] ] ), https://tc39.es/ecma262/#sec-dataview-buffer-byteoffset-bytelength +Value DataViewConstructor::call() +{ + auto& vm = this->vm(); + vm.throw_exception(global_object(), ErrorType::ConstructorWithoutNew, vm.names.DataView); + return {}; +} + +// 25.3.2.1 DataView ( buffer [ , byteOffset [ , byteLength ] ] ), https://tc39.es/ecma262/#sec-dataview-buffer-byteoffset-bytelength +Value DataViewConstructor::construct(Function&) +{ + auto& vm = this->vm(); + auto buffer = vm.argument(0); + if (!buffer.is_object() || !is(buffer.as_object())) { + vm.throw_exception(global_object(), ErrorType::IsNotAn, buffer.to_string_without_side_effects(), vm.names.ArrayBuffer); + return {}; + } + auto& array_buffer = static_cast(buffer.as_object()); + + auto offset = vm.argument(1).to_index(global_object()); + if (vm.exception()) + return {}; + + if (array_buffer.is_detached()) { + vm.throw_exception(global_object(), ErrorType::DetachedArrayBuffer); + return {}; + } + + auto buffer_byte_length = array_buffer.byte_length(); + if (offset > buffer_byte_length) { + vm.throw_exception(global_object(), ErrorType::DataViewOutOfRangeByteOffset, offset, buffer_byte_length); + return {}; + } + + size_t view_byte_length; + if (vm.argument(2).is_undefined()) { + view_byte_length = buffer_byte_length - offset; + } else { + view_byte_length = vm.argument(2).to_index(global_object()); + if (vm.exception()) + return {}; + if (offset + view_byte_length > buffer_byte_length) { + vm.throw_exception(global_object(), ErrorType::InvalidLength, vm.names.DataView); + return {}; + } + } + + // FIXME: Use OrdinaryCreateFromConstructor(newTarget, "%DataView.prototype%") + if (array_buffer.is_detached()) { + vm.throw_exception(global_object(), ErrorType::DetachedArrayBuffer); + return {}; + } + + return DataView::create(global_object(), &array_buffer, view_byte_length, offset); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/DataViewConstructor.h b/Userland/Libraries/LibJS/Runtime/DataViewConstructor.h new file mode 100644 index 0000000000..c1a5852490 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/DataViewConstructor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS { + +class DataViewConstructor final : public NativeFunction { + JS_OBJECT(DataViewConstructor, NativeFunction); + +public: + explicit DataViewConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~DataViewConstructor() override; + + virtual Value call() override; + virtual Value construct(Function&) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/DataViewPrototype.cpp b/Userland/Libraries/LibJS/Runtime/DataViewPrototype.cpp new file mode 100644 index 0000000000..731f8053aa --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/DataViewPrototype.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace JS { + +DataViewPrototype::DataViewPrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void DataViewPrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + + define_native_accessor(vm.names.buffer, buffer_getter, {}, Attribute::Configurable); + define_native_accessor(vm.names.byteLength, byte_length_getter, {}, Attribute::Configurable); + define_native_accessor(vm.names.byteOffset, byte_offset_getter, {}, Attribute::Configurable); + + // 25.3.4.25 DataView.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-dataview.prototype-@@tostringtag + define_property(vm.well_known_symbol_to_string_tag(), js_string(global_object.heap(), vm.names.DataView.as_string()), Attribute::Configurable); +} + +DataViewPrototype::~DataViewPrototype() +{ +} + +static DataView* typed_this(VM& vm, GlobalObject& global_object) +{ + auto this_value = vm.this_value(global_object); + if (!this_value.is_object() || !is(this_value.as_object())) { + vm.throw_exception(global_object, ErrorType::NotA, vm.names.DataView); + return nullptr; + } + return static_cast(&this_value.as_object()); +} + +// 25.3.4.1 get DataView.prototype.buffer, https://tc39.es/ecma262/#sec-get-dataview.prototype.buffer +JS_DEFINE_NATIVE_GETTER(DataViewPrototype::buffer_getter) +{ + auto* data_view = typed_this(vm, global_object); + if (!data_view) + return {}; + return data_view->viewed_array_buffer(); +} + +// 25.3.4.2 get DataView.prototype.byteLength, https://tc39.es/ecma262/#sec-get-dataview.prototype.bytelength +JS_DEFINE_NATIVE_GETTER(DataViewPrototype::byte_length_getter) +{ + auto* data_view = typed_this(vm, global_object); + if (!data_view) + return {}; + if (data_view->viewed_array_buffer()->is_detached()) { + vm.throw_exception(global_object, ErrorType::DetachedArrayBuffer); + return {}; + } + return Value(data_view->byte_length()); +} + +// 25.3.4.3 get DataView.prototype.byteOffset, https://tc39.es/ecma262/#sec-get-dataview.prototype.byteoffset +JS_DEFINE_NATIVE_GETTER(DataViewPrototype::byte_offset_getter) +{ + auto* data_view = typed_this(vm, global_object); + if (!data_view) + return {}; + if (data_view->viewed_array_buffer()->is_detached()) { + vm.throw_exception(global_object, ErrorType::DetachedArrayBuffer); + return {}; + } + return Value(data_view->byte_offset()); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/DataViewPrototype.h b/Userland/Libraries/LibJS/Runtime/DataViewPrototype.h new file mode 100644 index 0000000000..604e3c17c3 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/DataViewPrototype.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS { + +class DataViewPrototype final : public Object { + JS_OBJECT(DataViewPrototype, Object); + +public: + DataViewPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~DataViewPrototype() override; + +private: + JS_DECLARE_NATIVE_GETTER(buffer_getter); + JS_DECLARE_NATIVE_GETTER(byte_length_getter); + JS_DECLARE_NATIVE_GETTER(byte_offset_getter); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index a1fe56b288..0a6b716aa7 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -21,6 +21,7 @@ M(ClassIsAbstract, "Abstract class {} cannot be constructed directly") \ M(ConstructorWithoutNew, "{} constructor must be called with 'new'") \ M(Convert, "Cannot convert {} to {}") \ + M(DataViewOutOfRangeByteOffset, "Data view byte offset {} is out of range for buffer with length {}") \ M(DescChangeNonConfigurable, "Cannot change attributes of non-configurable property '{}'") \ M(DescWriteNonWritable, "Cannot write to non-writable property '{}'") \ M(DetachedArrayBuffer, "ArrayBuffer is detached") \ @@ -36,6 +37,7 @@ M(InvalidTimeValue, "Invalid time value") \ M(InvalidRadix, "Radix must be an integer no less than 2, and no greater than 36") \ M(IsNotA, "{} is not a {}") \ + M(IsNotAn, "{} is not an {}") \ M(IsNotAEvaluatedFrom, "{} is not a {} (evaluated from '{}')") \ M(IterableNextBadReturn, "iterator.next() returned a non-object value") \ M(IterableNextNotAFunction, "'next' property on returned object from Symbol.iterator method is " \ diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index 7761f171e3..ce79290fe5 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -150,6 +152,7 @@ void GlobalObject::initialize_global_object() add_constructor(vm.names.ArrayBuffer, m_array_buffer_constructor, m_array_buffer_prototype); add_constructor(vm.names.BigInt, m_bigint_constructor, m_bigint_prototype); add_constructor(vm.names.Boolean, m_boolean_constructor, m_boolean_prototype); + add_constructor(vm.names.DataView, m_data_view_constructor, m_data_view_prototype); add_constructor(vm.names.Date, m_date_constructor, m_date_prototype); add_constructor(vm.names.Error, m_error_constructor, m_error_prototype); add_constructor(vm.names.Function, m_function_constructor, m_function_prototype); diff --git a/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.js b/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.js new file mode 100644 index 0000000000..fc2a82184b --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.js @@ -0,0 +1,15 @@ +test("basic functionality", () => { + expect(DataView).toHaveLength(1); + expect(DataView.name).toBe("DataView"); + expect(DataView.prototype.constructor).toBe(DataView); + + const buffer = new ArrayBuffer(); + expect(new DataView(buffer)).toBeInstanceOf(DataView); + expect(typeof new DataView(buffer)).toBe("object"); +}); + +test("DataView constructor must be invoked with 'new'", () => { + expect(() => { + DataView(); + }).toThrowWithMessage(TypeError, "DataView constructor must be called with 'new'"); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.buffer.js b/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.buffer.js new file mode 100644 index 0000000000..04f450ac6b --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.buffer.js @@ -0,0 +1,4 @@ +test("basic functionality", () => { + const buffer = new ArrayBuffer(); + expect(new DataView(buffer).buffer).toBe(buffer); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteLength.js b/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteLength.js new file mode 100644 index 0000000000..b544badb84 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteLength.js @@ -0,0 +1,7 @@ +test("basic functionality", () => { + const buffer = new ArrayBuffer(124); + expect(new DataView(buffer).byteLength).toBe(124); + expect(new DataView(buffer, 0, 1).byteLength).toBe(1); + expect(new DataView(buffer, 0, 64).byteLength).toBe(64); + expect(new DataView(buffer, 0, 123).byteLength).toBe(123); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteOffset.js b/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteOffset.js new file mode 100644 index 0000000000..68c5d84716 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteOffset.js @@ -0,0 +1,7 @@ +test("basic functionality", () => { + const buffer = new ArrayBuffer(124); + expect(new DataView(buffer).byteOffset).toBe(0); + expect(new DataView(buffer, 1).byteOffset).toBe(1); + expect(new DataView(buffer, 64).byteOffset).toBe(64); + expect(new DataView(buffer, 123).byteOffset).toBe(123); +}); diff --git a/Userland/Utilities/js.cpp b/Userland/Utilities/js.cpp index 971ebf4cbb..1ad0d7f852 100644 --- a/Userland/Utilities/js.cpp +++ b/Userland/Utilities/js.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -397,6 +398,19 @@ static void print_typed_array(const JS::Object& object, HashTable& VERIFY_NOT_REACHED(); } +static void print_data_view(const JS::Object& object, HashTable& seen_objects) +{ + auto& data_view = static_cast(object); + print_type("DataView"); + out("\n byteLength: "); + print_value(JS::Value(data_view.byte_length()), seen_objects); + out("\n byteOffset: "); + print_value(JS::Value(data_view.byte_offset()), seen_objects); + out("\n buffer: "); + print_type("ArrayBuffer"); + out(" @ {:p}", data_view.viewed_array_buffer()); +} + static void print_primitive_wrapper_object(const FlyString& name, const JS::Object& object, HashTable& seen_objects) { // BooleanObject, NumberObject, StringObject @@ -438,6 +452,8 @@ static void print_value(JS::Value value, HashTable& seen_objects) return print_map(object, seen_objects); if (is(object)) return print_set(object, seen_objects); + if (is(object)) + return print_data_view(object, seen_objects); if (is(object)) return print_proxy_object(object, seen_objects); if (is(object))