From 333ba93d491d1945066e51977eeff4de921c8e1e Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Sat, 3 May 2025 13:22:44 +0330 Subject: [PATCH] LibWeb/WebAssembly: Define the hacky 'native' errors given in the spec These cannot be implemented "correctly" as the set of native errors as defined by ecma262 is closed, but we can get pretty close. --- Libraries/LibWeb/WebAssembly/WebAssembly.cpp | 170 +++++++++++++++++++ Libraries/LibWeb/WebAssembly/WebAssembly.h | 67 ++++++++ Libraries/LibWeb/WebAssembly/WebAssembly.idl | 2 +- 3 files changed, 238 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp index c68b7b549c5..41caabc096c 100644 --- a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp +++ b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp @@ -12,12 +12,15 @@ #include #include #include +#include +#include #include #include #include #include #include #include +#include #include #include #include @@ -72,6 +75,35 @@ void finalize(JS::Object& object) Detail::s_caches.remove(global_object); } +// https://webassembly.github.io/spec/js-api/#error-objects +void initialize(JS::Object& self, JS::Realm& realm) +{ + // 1. Let namespaceObject be the namespace object. + auto& namespace_object = self; + + // 2. For each error of « "CompileError", "LinkError", "RuntimeError" », + // 2.1. Let constructor be a new object, implementing the NativeError Object Structure, with NativeError set to error. + // 2.2. ! DefineMethodProperty(namespaceObject, error, constructor, false). + + // 2..2.2 for error=CompileError: + auto descriptor = JS::PropertyDescriptor { + .writable = true, + .enumerable = false, + .configurable = true, + }; + + descriptor.value = &Bindings::ensure_web_constructor(realm, "CompileError"_fly_string); + MUST(namespace_object.define_property_or_throw("CompileError"_string, descriptor)); + + // 2..2.2 for error=LinkError: + descriptor.value = &Bindings::ensure_web_constructor(realm, "LinkError"_fly_string); + MUST(namespace_object.define_property_or_throw("LinkError"_string, descriptor)); + + // 2..2.2 for error=RuntimeError: + descriptor.value = &Bindings::ensure_web_constructor(realm, "RuntimeError"_fly_string); + MUST(namespace_object.define_property_or_throw("RuntimeError"_string, descriptor)); +} + // https://webassembly.github.io/spec/js-api/#dom-webassembly-validate bool validate(JS::VM& vm, GC::Root& bytes) { @@ -851,4 +883,142 @@ GC::Ref compile_potential_webassembly_response(JS::VM& vm, GC:: return return_value; } +#define DEFINE_WASM_NATIVE_ERROR(ClassName, snake_name, PrototypeName, ConstructorName) \ + GC_DEFINE_ALLOCATOR(ClassName); \ + GC::Ref ClassName::create(JS::Realm& realm) \ + { \ + return realm.create(Bindings::ensure_web_prototype(realm, #ClassName##_fly_string)); \ + } \ + \ + GC::Ref ClassName::create(JS::Realm& realm, String message) \ + { \ + auto& vm = realm.vm(); \ + auto error = ClassName::create(realm); \ + u8 const attr = JS::Attribute::Writable | JS::Attribute::Configurable; \ + error->define_direct_property(vm.names.message, JS::PrimitiveString::create(vm, move(message)), attr); \ + return error; \ + } \ + \ + GC::Ref ClassName::create(JS::Realm& realm, StringView message) \ + { \ + return create(realm, MUST(String::from_utf8(message))); \ + } \ + \ + ClassName::ClassName(JS::Object& prototype) \ + : Error(prototype) \ + { \ + } + +DEFINE_WASM_NATIVE_ERROR(CompileError, compile_error, CompileErrorPrototype, CompileErrorConstructor) +DEFINE_WASM_NATIVE_ERROR(LinkError, link_error, LinkErrorPrototype, LinkErrorConstructor) +DEFINE_WASM_NATIVE_ERROR(RuntimeError, runtime_error, RuntimeErrorPrototype, RuntimeErrorConstructor) + +#undef DEFINE_WASM_NATIVE_ERROR + +#define DEFINE_WASM_NATIVE_ERROR_CONSTRUCTOR(ClassName, snake_name, PrototypeName, ConstructorName) \ + GC_DEFINE_ALLOCATOR(ConstructorName); \ + ConstructorName::ConstructorName(JS::Realm& realm) \ + : NativeFunction(#ClassName##_string, *realm.intrinsics().error_constructor()) \ + { \ + } \ + \ + void ConstructorName::initialize(JS::Realm& realm) \ + { \ + auto& vm = this->vm(); \ + Base::initialize(realm); \ + \ + /* 20.5.6.2.1 NativeError.prototype, https://tc39.es/ecma262/#sec-nativeerror.prototype */ \ + define_direct_property(vm.names.prototype, &Bindings::ensure_web_prototype(realm, #ClassName##_fly_string), 0); \ + \ + define_direct_property(vm.names.length, JS::Value(1), JS::Attribute::Configurable); \ + } \ + \ + ConstructorName::~ConstructorName() = default; \ + \ + /* 20.5.6.1.1 NativeError ( message [ , options ] ), https://tc39.es/ecma262/#sec-nativeerror */ \ + JS::ThrowCompletionOr ConstructorName::call() \ + { \ + /* 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. */ \ + return TRY(construct(*this)); \ + } \ + \ + /* 20.5.6.1.1 NativeError ( message [ , options ] ), https://tc39.es/ecma262/#sec-nativeerror */ \ + JS::ThrowCompletionOr> ConstructorName::construct(JS::FunctionObject& new_target) \ + { \ + auto& vm = this->vm(); \ + \ + auto message = vm.argument(0); \ + auto options = vm.argument(1); \ + \ + /* 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). */ \ + auto error = TRY(ordinary_create_from_constructor(vm, new_target, &JS::Intrinsics::error_prototype)); \ + \ + /* 3. If message is not undefined, then */ \ + if (!message.is_undefined()) { \ + /* a. Let msg be ? ToString(message). */ \ + auto msg = TRY(message.to_string(vm)); \ + \ + /* b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). */ \ + error->create_non_enumerable_data_property_or_throw(vm.names.message, JS::PrimitiveString::create(vm, move(msg))); \ + } \ + \ + /* 4. Perform ? InstallErrorCause(O, options). */ \ + TRY(error->install_error_cause(options)); \ + \ + /* 5. Return O. */ \ + return error; \ + } + +DEFINE_WASM_NATIVE_ERROR_CONSTRUCTOR(CompileError, compile_error, CompileErrorPrototype, CompileErrorConstructor) +DEFINE_WASM_NATIVE_ERROR_CONSTRUCTOR(LinkError, link_error, LinkErrorPrototype, LinkErrorConstructor) +DEFINE_WASM_NATIVE_ERROR_CONSTRUCTOR(RuntimeError, runtime_error, RuntimeErrorPrototype, RuntimeErrorConstructor) + +#undef DEFINE_WASM_NATIVE_ERROR_CONSTRUCTOR + +#define DEFINE_WASM_NATIVE_ERROR_PROTOTYPE(ClassName, snake_name, PrototypeName, ConstructorName) \ + GC_DEFINE_ALLOCATOR(PrototypeName); \ + \ + PrototypeName::PrototypeName(JS::Realm& realm) \ + : PrototypeObject(realm.intrinsics().error_prototype()) \ + { \ + } \ + \ + void PrototypeName::initialize(JS::Realm& realm) \ + { \ + auto& vm = this->vm(); \ + Base::initialize(realm); \ + u8 const attr = JS::Attribute::Writable | JS::Attribute::Configurable; \ + define_direct_property(vm.names.name, JS::PrimitiveString::create(vm, #ClassName##_string), attr); \ + define_direct_property(vm.names.message, JS::PrimitiveString::create(vm, String {}), attr); \ + } + +DEFINE_WASM_NATIVE_ERROR_PROTOTYPE(CompileError, compile_error, CompileErrorPrototype, CompileErrorConstructor) +DEFINE_WASM_NATIVE_ERROR_PROTOTYPE(LinkError, link_error, LinkErrorPrototype, LinkErrorConstructor) +DEFINE_WASM_NATIVE_ERROR_PROTOTYPE(RuntimeError, runtime_error, RuntimeErrorPrototype, RuntimeErrorConstructor) + +#undef DEFINE_WASM_NATIVE_ERROR_PROTOTYPE + } + +#define DEFINE_WASM_ERROR_PROTOTYPE_AND_CONSTRUCTOR_WEB_INTRINSIC(ClassName, snake_name, PrototypeName, ConstructorName) \ + template<> \ + void Intrinsics::create_web_prototype_and_constructor(JS::Realm & realm) \ + { \ + auto& vm = realm.vm(); \ + auto prototype = heap().allocate(realm); \ + m_prototypes.set(#ClassName##_string, prototype); \ + \ + auto constructor = heap().allocate(realm); \ + m_constructors.set(#ClassName##_string, constructor); \ + \ + prototype->define_direct_property(vm.names.constructor, constructor.ptr(), JS::Attribute::Writable | JS::Attribute::Configurable); \ + constructor->define_direct_property(vm.names.name, JS::PrimitiveString::create(vm, #ClassName##_string), JS::Attribute::Configurable); \ + } + +namespace Web::Bindings { +DEFINE_WASM_ERROR_PROTOTYPE_AND_CONSTRUCTOR_WEB_INTRINSIC(CompileError, compile_error, CompileErrorPrototype, CompileErrorConstructor) +DEFINE_WASM_ERROR_PROTOTYPE_AND_CONSTRUCTOR_WEB_INTRINSIC(LinkError, link_error, LinkErrorPrototype, LinkErrorConstructor) +DEFINE_WASM_ERROR_PROTOTYPE_AND_CONSTRUCTOR_WEB_INTRINSIC(RuntimeError, runtime_error, RuntimeErrorPrototype, RuntimeErrorConstructor) +} + +#undef DEFINE_WASM_ERROR_PROTOTYPE_AND_CONSTRUCTOR_WEB_INTRINSIC diff --git a/Libraries/LibWeb/WebAssembly/WebAssembly.h b/Libraries/LibWeb/WebAssembly/WebAssembly.h index 1689e7b42c9..ffd8b4ffff8 100644 --- a/Libraries/LibWeb/WebAssembly/WebAssembly.h +++ b/Libraries/LibWeb/WebAssembly/WebAssembly.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,7 @@ namespace Web::WebAssembly { void visit_edges(JS::Object&, JS::Cell::Visitor&); void finalize(JS::Object&); +void initialize(JS::Object&, JS::Realm&); bool validate(JS::VM&, GC::Root& bytes); WebIDL::ExceptionOr> compile(JS::VM&, GC::Root& bytes); @@ -96,4 +98,69 @@ extern HashMap, WebAssemblyCache> s_caches; } +// NOTE: This is technically not allowed by ECMA262, as the set of native errors is closed +// our implementation uses this fact in places, but for the purposes of wasm returning +// *some* kind of error, named e.g. 'WebAssembly.RuntimeError', this is sufficient. +#define DECLARE_WASM_NATIVE_ERROR(ClassName, snake_name, PrototypeName, ConstructorName) \ + class ClassName final : public JS::Error { \ + JS_OBJECT(ClassName, Error); \ + GC_DECLARE_ALLOCATOR(ClassName); \ + \ + public: \ + static GC::Ref create(JS::Realm&); \ + static GC::Ref create(JS::Realm&, String message); \ + static GC::Ref create(JS::Realm&, StringView message); \ + \ + explicit ClassName(Object& prototype); \ + virtual ~ClassName() override = default; \ + }; + +#define DECLARE_WASM_NATIVE_ERROR_CONSTRUCTOR(ClassName, snake_name, PrototypeName, ConstructorName) \ + class ConstructorName final : public JS::NativeFunction { \ + JS_OBJECT(ConstructorName, NativeFunction); \ + GC_DECLARE_ALLOCATOR(ConstructorName); \ + \ + public: \ + virtual void initialize(JS::Realm&) override; \ + virtual ~ConstructorName() override; \ + virtual JS::ThrowCompletionOr call() override; \ + virtual JS::ThrowCompletionOr> construct(JS::FunctionObject& new_target) override; \ + \ + private: \ + explicit ConstructorName(JS::Realm&); \ + \ + virtual bool has_constructor() const override \ + { \ + return true; \ + } \ + }; + +#define DECLARE_WASM_NATIVE_ERROR_PROTOTYPE(ClassName, snake_name, PrototypeName, ConstructorName) \ + class PrototypeName final : public JS::PrototypeObject { \ + JS_PROTOTYPE_OBJECT(PrototypeName, ClassName, ClassName); \ + GC_DECLARE_ALLOCATOR(PrototypeName); \ + \ + public: \ + virtual void initialize(JS::Realm&) override; \ + virtual ~PrototypeName() override = default; \ + \ + private: \ + explicit PrototypeName(JS::Realm&); \ + }; + +DECLARE_WASM_NATIVE_ERROR_CONSTRUCTOR(CompilError, compile_error, CompileErrorPrototype, CompileErrorConstructor) +DECLARE_WASM_NATIVE_ERROR_CONSTRUCTOR(LinkError, link_error, LinkErrorPrototype, LinkErrorConstructor) +DECLARE_WASM_NATIVE_ERROR_CONSTRUCTOR(RuntimeError, runtime_error, RuntimeErrorPrototype, RuntimeErrorConstructor) + +DECLARE_WASM_NATIVE_ERROR(CompileError, compile_error, CompileErrorPrototype, CompileErrorConstructor) +DECLARE_WASM_NATIVE_ERROR(LinkError, link_error, LinkErrorPrototype, LinkErrorConstructor) +DECLARE_WASM_NATIVE_ERROR(RuntimeError, runtime_error, LinkErrorPrototype, LinkErrorConstructor) + +DECLARE_WASM_NATIVE_ERROR_PROTOTYPE(CompileError, compile_error, CompileErrorPrototype, CompileErrorConstructor) +DECLARE_WASM_NATIVE_ERROR_PROTOTYPE(LinkError, link_error, LinkErrorPrototype, LinkErrorConstructor) +DECLARE_WASM_NATIVE_ERROR_PROTOTYPE(RuntimeError, runtime_error, RuntimeErrorPrototype, LinkErrorConstructor) + +#undef DECLARE_WASM_NATIVE_ERROR +#undef DECLARE_WASM_NATIVE_ERROR_PROTOTYPE +#undef DECLARE_WASM_NATIVE_ERROR_CONSTRUCTOR } diff --git a/Libraries/LibWeb/WebAssembly/WebAssembly.idl b/Libraries/LibWeb/WebAssembly/WebAssembly.idl index ab45651ffad..c44e77759b8 100644 --- a/Libraries/LibWeb/WebAssembly/WebAssembly.idl +++ b/Libraries/LibWeb/WebAssembly/WebAssembly.idl @@ -9,7 +9,7 @@ dictionary WebAssemblyInstantiatedSource { // https://webassembly.github.io/spec/js-api/#webassembly-namespace // https://webassembly.github.io/spec/web-api/index.html#streaming-modules -[Exposed=*, WithGCVisitor, WithFinalizer] +[Exposed=*, WithGCVisitor, WithFinalizer, WithInitializer] namespace WebAssembly { // FIXME: Streaming APIs are supposed to be only exposed to Window, Worker