Procházet zdrojové kódy

LibJS: Add the DataView built-in object

Idan Horowitz před 4 roky
rodič
revize
e4d267d4fb

+ 3 - 0
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

+ 1 - 0
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)                          \

+ 34 - 0
Userland/Libraries/LibJS/Runtime/DataView.cpp

@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/DataView.h>
+
+namespace JS {
+
+DataView* DataView::create(GlobalObject& global_object, ArrayBuffer* viewed_buffer, size_t byte_length, size_t byte_offset)
+{
+    return global_object.heap().allocate<DataView>(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);
+}
+
+}

+ 36 - 0
Userland/Libraries/LibJS/Runtime/DataView.h

@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/ArrayBuffer.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Object.h>
+
+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 };
+};
+
+}

+ 90 - 0
Userland/Libraries/LibJS/Runtime/DataViewConstructor.cpp

@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/DataView.h>
+#include <LibJS/Runtime/DataViewConstructor.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+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<TypeError>(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<ArrayBuffer>(buffer.as_object())) {
+        vm.throw_exception<TypeError>(global_object(), ErrorType::IsNotAn, buffer.to_string_without_side_effects(), vm.names.ArrayBuffer);
+        return {};
+    }
+    auto& array_buffer = static_cast<ArrayBuffer&>(buffer.as_object());
+
+    auto offset = vm.argument(1).to_index(global_object());
+    if (vm.exception())
+        return {};
+
+    if (array_buffer.is_detached()) {
+        vm.throw_exception<TypeError>(global_object(), ErrorType::DetachedArrayBuffer);
+        return {};
+    }
+
+    auto buffer_byte_length = array_buffer.byte_length();
+    if (offset > buffer_byte_length) {
+        vm.throw_exception<RangeError>(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<RangeError>(global_object(), ErrorType::InvalidLength, vm.names.DataView);
+            return {};
+        }
+    }
+
+    // FIXME: Use OrdinaryCreateFromConstructor(newTarget, "%DataView.prototype%")
+    if (array_buffer.is_detached()) {
+        vm.throw_exception<TypeError>(global_object(), ErrorType::DetachedArrayBuffer);
+        return {};
+    }
+
+    return DataView::create(global_object(), &array_buffer, view_byte_length, offset);
+}
+
+}

+ 28 - 0
Userland/Libraries/LibJS/Runtime/DataViewConstructor.h

@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+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; }
+};
+
+}

+ 78 - 0
Userland/Libraries/LibJS/Runtime/DataViewPrototype.cpp

@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/DataViewPrototype.h>
+
+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<DataView>(this_value.as_object())) {
+        vm.throw_exception<TypeError>(global_object, ErrorType::NotA, vm.names.DataView);
+        return nullptr;
+    }
+    return static_cast<DataView*>(&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<TypeError>(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<TypeError>(global_object, ErrorType::DetachedArrayBuffer);
+        return {};
+    }
+    return Value(data_view->byte_offset());
+}
+
+}

+ 27 - 0
Userland/Libraries/LibJS/Runtime/DataViewPrototype.h

@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/DataView.h>
+
+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);
+};
+
+}

+ 2 - 0
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 "                                    \

+ 3 - 0
Userland/Libraries/LibJS/Runtime/GlobalObject.cpp

@@ -27,6 +27,8 @@
 #include <LibJS/Runtime/BooleanConstructor.h>
 #include <LibJS/Runtime/BooleanPrototype.h>
 #include <LibJS/Runtime/ConsoleObject.h>
+#include <LibJS/Runtime/DataViewConstructor.h>
+#include <LibJS/Runtime/DataViewPrototype.h>
 #include <LibJS/Runtime/DateConstructor.h>
 #include <LibJS/Runtime/DatePrototype.h>
 #include <LibJS/Runtime/ErrorConstructor.h>
@@ -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);

+ 15 - 0
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'");
+});

+ 4 - 0
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);
+});

+ 7 - 0
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);
+});

+ 7 - 0
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);
+});

+ 16 - 0
Userland/Utilities/js.cpp

@@ -23,6 +23,7 @@
 #include <LibJS/Runtime/Array.h>
 #include <LibJS/Runtime/ArrayBuffer.h>
 #include <LibJS/Runtime/BooleanObject.h>
+#include <LibJS/Runtime/DataView.h>
 #include <LibJS/Runtime/Date.h>
 #include <LibJS/Runtime/Error.h>
 #include <LibJS/Runtime/Function.h>
@@ -397,6 +398,19 @@ static void print_typed_array(const JS::Object& object, HashTable<JS::Object*>&
     VERIFY_NOT_REACHED();
 }
 
+static void print_data_view(const JS::Object& object, HashTable<JS::Object*>& seen_objects)
+{
+    auto& data_view = static_cast<const JS::DataView&>(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<JS::Object*>& seen_objects)
 {
     // BooleanObject, NumberObject, StringObject
@@ -438,6 +452,8 @@ static void print_value(JS::Value value, HashTable<JS::Object*>& seen_objects)
             return print_map(object, seen_objects);
         if (is<JS::Set>(object))
             return print_set(object, seen_objects);
+        if (is<JS::DataView>(object))
+            return print_data_view(object, seen_objects);
         if (is<JS::ProxyObject>(object))
             return print_proxy_object(object, seen_objects);
         if (is<JS::Promise>(object))