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