From 1e01a85cdfd9f8016c0381d3008a8c0fb2787cdb Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Tue, 29 Mar 2022 20:36:22 +0100 Subject: [PATCH] LibJS: Import C++ sources from libjs-test262 :^) This commit upstreams most of the C++ bits of the LibJS test262 runner at https://github.com/linusg/libjs-test262/, specifically everything but the main.cpp file serving as the actual executable. Since all of these are just regular JS objects, I opted to put them in LibJS itself, in a new Contrib/ directory like many other projects have one. Other code that can end up there in the future is the runtime for esvu, which might even share some functionality with test262's $262 object. The code has been copied verbatim, and only a small number of changes have been made: - Putting everything into the JS::Test262 namespace - Removing now redundant JS namespace prefixes - Updating includes to use absolute paths - Updating the SPDX-License-Identifier comments from MIT to BSD-2-Clause I gained permission to change the license and upstream these changes from all the major contributors to this code: Ali, Andrew, David, Idan. The removal of the code from the source repository is here: https://github.com/linusg/libjs-test262/pull/54 This is only the first step, the goal is to eventually upstream the actual libjs-test262-runner executable and supporting Python scripts into SerenityOS as well. --- Userland/Libraries/LibJS/CMakeLists.txt | 4 + .../LibJS/Contrib/Test262/$262Object.cpp | 88 +++++++++++++++++++ .../LibJS/Contrib/Test262/$262Object.h | 36 ++++++++ .../LibJS/Contrib/Test262/AgentObject.cpp | 46 ++++++++++ .../LibJS/Contrib/Test262/AgentObject.h | 27 ++++++ .../LibJS/Contrib/Test262/GlobalObject.cpp | 42 +++++++++ .../LibJS/Contrib/Test262/GlobalObject.h | 32 +++++++ .../LibJS/Contrib/Test262/IsHTMLDDA.cpp | 40 +++++++++ .../LibJS/Contrib/Test262/IsHTMLDDA.h | 28 ++++++ 9 files changed, 343 insertions(+) create mode 100644 Userland/Libraries/LibJS/Contrib/Test262/$262Object.cpp create mode 100644 Userland/Libraries/LibJS/Contrib/Test262/$262Object.h create mode 100644 Userland/Libraries/LibJS/Contrib/Test262/AgentObject.cpp create mode 100644 Userland/Libraries/LibJS/Contrib/Test262/AgentObject.h create mode 100644 Userland/Libraries/LibJS/Contrib/Test262/GlobalObject.cpp create mode 100644 Userland/Libraries/LibJS/Contrib/Test262/GlobalObject.h create mode 100644 Userland/Libraries/LibJS/Contrib/Test262/IsHTMLDDA.cpp create mode 100644 Userland/Libraries/LibJS/Contrib/Test262/IsHTMLDDA.h diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index c90d83b957..b96734afba 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -15,6 +15,10 @@ set(SOURCES Bytecode/Pass/UnifySameBlocks.cpp Bytecode/StringTable.cpp Console.cpp + Contrib/Test262/$262Object.cpp + Contrib/Test262/AgentObject.cpp + Contrib/Test262/GlobalObject.cpp + Contrib/Test262/IsHTMLDDA.cpp CyclicModule.cpp Heap/BlockAllocator.cpp Heap/CellAllocator.cpp diff --git a/Userland/Libraries/LibJS/Contrib/Test262/$262Object.cpp b/Userland/Libraries/LibJS/Contrib/Test262/$262Object.cpp new file mode 100644 index 0000000000..a1be5b39df --- /dev/null +++ b/Userland/Libraries/LibJS/Contrib/Test262/$262Object.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021, Linus Groh + * Copyright (c) 2021, Idan Horowitz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace JS::Test262 { + +$262Object::$262Object(JS::GlobalObject& global_object) + : Object(Object::ConstructWithoutPrototypeTag::Tag, global_object) +{ +} + +void $262Object::initialize(JS::GlobalObject& global_object) +{ + Base::initialize(global_object); + + m_agent = vm().heap().allocate(global_object, global_object); + m_is_htmldda = vm().heap().allocate(global_object, global_object); + + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function("clearKeptObjects", clear_kept_objects, 0, attr); + define_native_function("createRealm", create_realm, 0, attr); + define_native_function("detachArrayBuffer", detach_array_buffer, 1, attr); + define_native_function("evalScript", eval_script, 1, attr); + + define_direct_property("agent", m_agent, attr); + define_direct_property("gc", global_object.get_without_side_effects("gc"), attr); + define_direct_property("global", &global_object, attr); + define_direct_property("IsHTMLDDA", m_is_htmldda, attr); +} + +void $262Object::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_agent); + visitor.visit(m_is_htmldda); +} + +JS_DEFINE_NATIVE_FUNCTION($262Object::clear_kept_objects) +{ + vm.finish_execution_generation(); + return js_undefined(); +} + +JS_DEFINE_NATIVE_FUNCTION($262Object::create_realm) +{ + auto realm = vm.heap().allocate_without_global_object(); + realm->initialize_global_object(); + return Value(realm->$262()); +} + +// 25.1.2.3 DetachArrayBuffer, https://tc39.es/ecma262/#sec-detacharraybuffer +JS_DEFINE_NATIVE_FUNCTION($262Object::detach_array_buffer) +{ + auto array_buffer = vm.argument(0); + if (!array_buffer.is_object() || !is(array_buffer.as_object())) + return vm.throw_completion(global_object); + auto& array_buffer_object = static_cast(array_buffer.as_object()); + if (!same_value(array_buffer_object.detach_key(), vm.argument(1))) + return vm.throw_completion(global_object); + array_buffer_object.detach_buffer(); + return js_null(); +} + +JS_DEFINE_NATIVE_FUNCTION($262Object::eval_script) +{ + auto source = TRY(vm.argument(0).to_string(global_object)); + auto script_or_error = Script::parse(source, *vm.current_realm()); + if (script_or_error.is_error()) + return vm.throw_completion(global_object, script_or_error.error()[0].to_string()); + TRY(vm.interpreter().run(script_or_error.value())); + return js_undefined(); +} + +} diff --git a/Userland/Libraries/LibJS/Contrib/Test262/$262Object.h b/Userland/Libraries/LibJS/Contrib/Test262/$262Object.h new file mode 100644 index 0000000000..08caddd6bb --- /dev/null +++ b/Userland/Libraries/LibJS/Contrib/Test262/$262Object.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace JS::Test262 { + +class $262Object final : public Object { + JS_OBJECT($262Object, Object); + +public: + $262Object(JS::GlobalObject&); + virtual void initialize(JS::GlobalObject&) override; + virtual ~$262Object() override = default; + +private: + virtual void visit_edges(Visitor&) override; + + AgentObject* m_agent { nullptr }; + IsHTMLDDA* m_is_htmldda { nullptr }; + + JS_DECLARE_NATIVE_FUNCTION(clear_kept_objects); + JS_DECLARE_NATIVE_FUNCTION(create_realm); + JS_DECLARE_NATIVE_FUNCTION(detach_array_buffer); + JS_DECLARE_NATIVE_FUNCTION(eval_script); +}; + +} diff --git a/Userland/Libraries/LibJS/Contrib/Test262/AgentObject.cpp b/Userland/Libraries/LibJS/Contrib/Test262/AgentObject.cpp new file mode 100644 index 0000000000..c801485b5b --- /dev/null +++ b/Userland/Libraries/LibJS/Contrib/Test262/AgentObject.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021-2022, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace JS::Test262 { + +AgentObject::AgentObject(JS::GlobalObject& global_object) + : Object(Object::ConstructWithoutPrototypeTag::Tag, global_object) +{ +} + +void AgentObject::initialize(JS::GlobalObject& global_object) +{ + Base::initialize(global_object); + + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function("monotonicNow", monotonic_now, 0, attr); + define_native_function("sleep", sleep, 1, attr); + // TODO: broadcast + // TODO: getReport + // TODO: start +} + +JS_DEFINE_NATIVE_FUNCTION(AgentObject::monotonic_now) +{ + auto time = Time::now_monotonic(); + auto milliseconds = time.to_milliseconds(); + return Value(static_cast(milliseconds)); +} + +JS_DEFINE_NATIVE_FUNCTION(AgentObject::sleep) +{ + auto milliseconds = TRY(vm.argument(0).to_i32(global_object)); + ::usleep(milliseconds * 1000); + return js_undefined(); +} + +} diff --git a/Userland/Libraries/LibJS/Contrib/Test262/AgentObject.h b/Userland/Libraries/LibJS/Contrib/Test262/AgentObject.h new file mode 100644 index 0000000000..2a961305d8 --- /dev/null +++ b/Userland/Libraries/LibJS/Contrib/Test262/AgentObject.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace JS::Test262 { + +class AgentObject final : public Object { + JS_OBJECT(AgentObject, Object); + +public: + AgentObject(JS::GlobalObject&); + virtual void initialize(JS::GlobalObject&) override; + virtual ~AgentObject() override = default; + +private: + JS_DECLARE_NATIVE_FUNCTION(monotonic_now); + JS_DECLARE_NATIVE_FUNCTION(sleep); +}; + +} diff --git a/Userland/Libraries/LibJS/Contrib/Test262/GlobalObject.cpp b/Userland/Libraries/LibJS/Contrib/Test262/GlobalObject.cpp new file mode 100644 index 0000000000..1a7bf5e212 --- /dev/null +++ b/Userland/Libraries/LibJS/Contrib/Test262/GlobalObject.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace JS::Test262 { + +void GlobalObject::initialize_global_object() +{ + Base::initialize_global_object(); + + m_$262 = vm().heap().allocate<$262Object>(*this, *this); + + // https://github.com/tc39/test262/blob/master/INTERPRETING.md#host-defined-functions + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function("print", print, 1, attr); + define_direct_property("$262", m_$262, attr); +} + +void GlobalObject::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_$262); +} + +JS_DEFINE_NATIVE_FUNCTION(GlobalObject::print) +{ + auto string = TRY(vm.argument(0).to_string(global_object)); + outln("{}", string); + return js_undefined(); +} + +} diff --git a/Userland/Libraries/LibJS/Contrib/Test262/GlobalObject.h b/Userland/Libraries/LibJS/Contrib/Test262/GlobalObject.h new file mode 100644 index 0000000000..5729f9021f --- /dev/null +++ b/Userland/Libraries/LibJS/Contrib/Test262/GlobalObject.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace JS::Test262 { + +class GlobalObject final : public JS::GlobalObject { + JS_OBJECT(GlobalObject, JS::GlobalObject); + +public: + GlobalObject() = default; + virtual void initialize_global_object() override; + virtual ~GlobalObject() override = default; + + $262Object* $262() const { return m_$262; } + +private: + virtual void visit_edges(Visitor&) override; + + $262Object* m_$262 { nullptr }; + + JS_DECLARE_NATIVE_FUNCTION(print); +}; + +} diff --git a/Userland/Libraries/LibJS/Contrib/Test262/IsHTMLDDA.cpp b/Userland/Libraries/LibJS/Contrib/Test262/IsHTMLDDA.cpp new file mode 100644 index 0000000000..01c142f2ef --- /dev/null +++ b/Userland/Libraries/LibJS/Contrib/Test262/IsHTMLDDA.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace JS::Test262 { + +IsHTMLDDA::IsHTMLDDA(JS::GlobalObject& global_object) + // NativeFunction without prototype is currently not possible (only due to the lack of a ctor that supports it) + : NativeFunction("IsHTMLDDA", *global_object.function_prototype()) +{ +} + +ThrowCompletionOr IsHTMLDDA::call() +{ + auto& vm = this->vm(); + if (vm.argument_count() == 0) + return js_null(); + if (vm.argument(0).is_string() && vm.argument(0).as_string().string().is_empty()) + return js_null(); + // Not sure if this really matters, INTERPRETING.md simply says: + // * IsHTMLDDA - (present only in implementations that can provide it) an object that: + // a. has an [[IsHTMLDDA]] internal slot, and + // b. when called with no arguments or with the first argument "" (an empty string) returns null. + return js_undefined(); +} + +ThrowCompletionOr IsHTMLDDA::construct(FunctionObject&) +{ + // Not sure if we need to support construction, but ¯\_(ツ)_/¯ + auto& vm = this->vm(); + auto& global_object = this->global_object(); + return vm.throw_completion(global_object, ErrorType::NotAConstructor, "IsHTMLDDA"); +} + +} diff --git a/Userland/Libraries/LibJS/Contrib/Test262/IsHTMLDDA.h b/Userland/Libraries/LibJS/Contrib/Test262/IsHTMLDDA.h new file mode 100644 index 0000000000..ab24c21391 --- /dev/null +++ b/Userland/Libraries/LibJS/Contrib/Test262/IsHTMLDDA.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Test262 { + +class IsHTMLDDA final : public NativeFunction { + JS_OBJECT(IsHTMLDDA, NativeFunction); + +public: + explicit IsHTMLDDA(JS::GlobalObject&); + virtual ~IsHTMLDDA() override = default; + + virtual ThrowCompletionOr call() override; + virtual ThrowCompletionOr construct(FunctionObject& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } + virtual bool is_htmldda() const override { return true; } +}; + +}