diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 8da4cbb79e8..86826b85dc4 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -782,6 +782,7 @@ set(SOURCES WebAssembly/Module.cpp WebAssembly/Table.cpp WebAssembly/WebAssembly.cpp + WebAssembly/Error.cpp WebAudio/AudioBuffer.cpp WebAudio/AudioBufferSourceNode.cpp WebAudio/AudioContext.cpp diff --git a/Libraries/LibWeb/WebAssembly/Error.cpp b/Libraries/LibWeb/WebAssembly/Error.cpp new file mode 100644 index 00000000000..11a26fa4b94 --- /dev/null +++ b/Libraries/LibWeb/WebAssembly/Error.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024, Pavel Shliak + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "Error.h" +#include + +namespace Web::WebAssembly { + +GC_DEFINE_ALLOCATOR(CompileError); + +GC::Ref CompileError::create(JS::Realm& realm) +{ + return realm.heap().allocate(realm.intrinsics().error_prototype()); +} + +GC::Ref CompileError::create(JS::Realm& realm, StringView message) +{ + auto& vm = realm.vm(); + auto error = CompileError::create(realm); + u8 attr = JS::Attribute::Writable | JS::Attribute::Configurable; + error->define_direct_property(vm.names.message, JS::PrimitiveString::create(vm, message), attr); + return error; +} + +CompileError::CompileError(JS::Object& prototype) + : JS::Error(prototype) +{ +} + +GC_DEFINE_ALLOCATOR(LinkError); + +GC::Ref LinkError::create(JS::Realm& realm) +{ + return realm.heap().allocate(realm.intrinsics().error_prototype()); +} + +GC::Ref LinkError::create(JS::Realm& realm, StringView message) +{ + auto& vm = realm.vm(); + auto error = LinkError::create(realm); + u8 attr = JS::Attribute::Writable | JS::Attribute::Configurable; + error->define_direct_property(vm.names.message, JS::PrimitiveString::create(vm, message), attr); + return error; +} + +LinkError::LinkError(JS::Object& prototype) + : JS::Error(prototype) +{ +} + +GC_DEFINE_ALLOCATOR(RuntimeError); + +GC::Ref RuntimeError::create(JS::Realm& realm) +{ + return realm.heap().allocate(realm.intrinsics().error_prototype()); +} + +GC::Ref RuntimeError::create(JS::Realm& realm, StringView message) +{ + auto& vm = realm.vm(); + auto error = RuntimeError::create(realm); + u8 attr = JS::Attribute::Writable | JS::Attribute::Configurable; + error->define_direct_property(vm.names.message, JS::PrimitiveString::create(vm, message), attr); + return error; +} + +RuntimeError::RuntimeError(JS::Object& prototype) + : JS::Error(prototype) +{ +} + +} // namespace Web::WebAssembly diff --git a/Libraries/LibWeb/WebAssembly/Error.h b/Libraries/LibWeb/WebAssembly/Error.h new file mode 100644 index 00000000000..c25fa7ea732 --- /dev/null +++ b/Libraries/LibWeb/WebAssembly/Error.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, Pavel Shliak + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::WebAssembly { + +class CompileError final : public JS::Error { + JS_OBJECT(CompileError, JS::Error); + GC_DECLARE_ALLOCATOR(CompileError); + +public: + static GC::Ref create(JS::Realm&); + static GC::Ref create(JS::Realm&, StringView message); + virtual ~CompileError() override = default; + +private: + explicit CompileError(JS::Object& prototype); +}; + +class LinkError final : public JS::Error { + JS_OBJECT(LinkError, JS::Error); + GC_DECLARE_ALLOCATOR(LinkError); + +public: + static GC::Ref create(JS::Realm&); + static GC::Ref create(JS::Realm&, StringView message); + virtual ~LinkError() override = default; + +private: + explicit LinkError(JS::Object& prototype); +}; + +class RuntimeError final : public JS::Error { + JS_OBJECT(RuntimeError, JS::Error); + GC_DECLARE_ALLOCATOR(RuntimeError); + +public: + static GC::Ref create(JS::Realm&); + static GC::Ref create(JS::Realm&, StringView message); + virtual ~RuntimeError() override = default; + +private: + explicit RuntimeError(JS::Object& prototype); +}; + +} // namespace Web::WebAssembly diff --git a/Libraries/LibWeb/WebAssembly/Module.cpp b/Libraries/LibWeb/WebAssembly/Module.cpp index 77750e51b82..b9a960e83ad 100644 --- a/Libraries/LibWeb/WebAssembly/Module.cpp +++ b/Libraries/LibWeb/WebAssembly/Module.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -25,7 +26,7 @@ WebIDL::ExceptionOr> Module::construct_impl(JS::Realm& realm, GC auto stable_bytes_or_error = WebIDL::get_buffer_source_copy(bytes->raw_object()); if (stable_bytes_or_error.is_error()) { VERIFY(stable_bytes_or_error.error().code() == ENOMEM); - return vm.throw_completion(vm.error_message(JS::VM::ErrorMessage::OutOfMemory)); + return vm.throw_completion(vm.error_message(JS::VM::ErrorMessage::OutOfMemory)); } auto stable_bytes = stable_bytes_or_error.release_value(); diff --git a/Libraries/LibWeb/WebAssembly/Table.cpp b/Libraries/LibWeb/WebAssembly/Table.cpp index 357525fa1c0..5c54e711f6d 100644 --- a/Libraries/LibWeb/WebAssembly/Table.cpp +++ b/Libraries/LibWeb/WebAssembly/Table.cpp @@ -29,11 +29,15 @@ static Wasm::ValueType table_kind_to_value_type(Bindings::TableKind kind) VERIFY_NOT_REACHED(); } +// https://webassembly.github.io/spec/js-api/#tables WebIDL::ExceptionOr> Table::construct_impl(JS::Realm& realm, TableDescriptor& descriptor, JS::Value value) { auto& vm = realm.vm(); auto reference_type = table_kind_to_value_type(descriptor.element); + // If elementType is not a reftype, throw a TypeError exception. + if (!reference_type.is_reference()) + return vm.throw_completion("elementType is not a reftype"_string); auto reference_value = vm.argument_count() == 1 ? Detail::default_webassembly_value(vm, reference_type) : TRY(Detail::to_webassembly_value(vm, value, reference_type)); diff --git a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp index 847ed351602..31d3d8b587d 100644 --- a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp +++ b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -17,10 +18,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -157,6 +160,11 @@ namespace Detail { JS::ThrowCompletionOr> instantiate_module(JS::VM& vm, Wasm::Module const& module, GC::Ptr import_object) { + // 1. If module.imports is not empty, and importObject is undefined, throw a TypeError exception. + if (!module.import_section().imports().is_empty() && !import_object) { + return vm.throw_completion("Import object must be defined when module has imports"sv); + } + Wasm::Linker linker { module }; auto& cache = get_cache(*vm.current_realm()); // https://webassembly.github.io/spec/js-api/index.html#read-the-imports @@ -173,27 +181,29 @@ JS::ThrowCompletionOr> instantiate_module(JS dbgln_if(LIBWEB_WASM_DEBUG, "Trying to resolve {}::{}", import_name.module, import_name.name); // 3.1. Let o be ? Get(importObject, moduleName). auto value_or_error = import_object->get(import_name.module); - if (value_or_error.is_error()) - break; + if (!value_or_error.is_error() && !value_or_error.release_value().is_object()) { + return vm.throw_completion("Module name must resolve to an object"sv); + } auto value = value_or_error.release_value(); // 3.2. If o is not an Object, throw a TypeError exception. auto object_or_error = value.to_object(vm); if (object_or_error.is_error()) - break; + return vm.throw_completion("Module name must resolve to an object"sv); auto object = object_or_error.release_value(); // 3.3. Let v be ? Get(o, componentName). auto import_or_error = object->get(import_name.name); if (import_or_error.is_error()) - break; + return vm.throw_completion("Cannot get component name from module"sv); auto import_ = import_or_error.release_value(); TRY(import_name.type.visit( // 3.4. If externtype is of the form func functype, [&](Wasm::TypeIndex index) -> JS::ThrowCompletionOr { dbgln_if(LIBWEB_WASM_DEBUG, "Trying to resolve a function {}::{}, type index {}", import_name.module, import_name.name, index.value()); auto& type = module.type_section().types()[index.value()]; - // FIXME: 3.4.1. If IsCallable(v) is false, throw a LinkError exception. - if (!import_.is_function()) - return {}; + // FIXME: IsCallable() + if (!import_.is_function()) { + return vm.throw_completion("Imported value is not a callable function"sv); + } auto& function = import_.as_function(); // 3.4.2. If v has a [[FunctionAddress]] internal slot, and therefore is an Exported Function, Optional address; @@ -263,32 +273,26 @@ JS::ThrowCompletionOr> instantiate_module(JS if (import_.is_number() || import_.is_bigint()) { // 3.5.1.1. If valtype is i64 and v is a Number, if (import_.is_number() && type.type().kind() == Wasm::ValueType::I64) { - // FIXME: 3.5.1.1.1. Throw a LinkError exception. - return vm.throw_completion("LinkError: Import resolution attempted to cast a Number to a BigInteger"sv); + return vm.throw_completion("Import resolution attempted to cast a Number to a BigInteger"sv); } // 3.5.1.2. If valtype is not i64 and v is a BigInt, if (import_.is_bigint() && type.type().kind() != Wasm::ValueType::I64) { - // FIXME: 3.5.1.2.1. Throw a LinkError exception. - return vm.throw_completion("LinkError: Import resolution attempted to cast a BigInteger to a Number"sv); + return vm.throw_completion("Import resolution attempted to cast a BigInteger to a Number"sv); } - // 3.5.1.3. If valtype is v128, if (type.type().kind() == Wasm::ValueType::V128) { - // FIXME: 3.5.1.3.1. Throw a LinkError exception. - return vm.throw_completion("LinkError: Import resolution attempted to cast a Number or BigInt to a V128"sv); + return vm.throw_completion("Cannot import v128 type for global"sv); } - // 3.5.1.4. Let value be ToWebAssemblyValue(v, valtype). auto cast_value = TRY(to_webassembly_value(vm, import_, type.type())); // 3.5.1.5. Let store be the surrounding agent's associated store. // 3.5.1.6. Let (store, globaladdr) be global_alloc(store, const valtype, value). // 3.5.1.7. Set the surrounding agent's associated store to store. address = cache.abstract_machine().store().allocate({ type.type(), false }, cast_value); - } - // FIXME: 3.5.2. Otherwise, if v implements Global, - // FIXME: 3.5.2.1. Let globaladdr be v.[[Global]]. - // 3.5.3. Otherwise, - else { - // FIXME: 3.5.3.1. Throw a LinkError exception. - return vm.throw_completion("LinkError: Invalid value for global type"sv); + } else { + // FIXME: https://webassembly.github.io/spec/js-api/#read-the-imports step 5.2 + // if v implements Global + // let globaladdr be v.[[Global]] + + return vm.throw_completion("Invalid value for global type"sv); } // 3.5.4. Let externglobal be global globaladdr. @@ -300,8 +304,7 @@ JS::ThrowCompletionOr> instantiate_module(JS [&](Wasm::MemoryType const&) -> JS::ThrowCompletionOr { // 3.6.1. If v does not implement Memory, throw a LinkError exception. if (!import_.is_object() || !is(import_.as_object())) { - // FIXME: Throw a LinkError instead - return vm.throw_completion("LinkError: Expected an instance of WebAssembly.Memory for a memory import"sv); + return vm.throw_completion("Expected an instance of WebAssembly.Memory for a memory import"sv); } // 3.6.2. Let externmem be the external value mem v.[[Memory]]. auto address = static_cast(import_.as_object()).address(); @@ -313,8 +316,7 @@ JS::ThrowCompletionOr> instantiate_module(JS [&](Wasm::TableType const&) -> JS::ThrowCompletionOr { // 3.7.1. If v does not implement Table, throw a LinkError exception. if (!import_.is_object() || !is(import_.as_object())) { - // FIXME: Throw a LinkError instead - return vm.throw_completion("LinkError: Expected an instance of WebAssembly.Table for a table import"sv); + return vm.throw_completion("Expected an instance of WebAssembly.Table for a table import"sv); } // 3.7.2. Let tableaddr be v.[[Table]]. // 3.7.3. Let externtable be the external value table tableaddr. @@ -324,8 +326,9 @@ JS::ThrowCompletionOr> instantiate_module(JS return {}; }, [&](auto const&) -> JS::ThrowCompletionOr { - // (noop) - return {}; + // FIXME: Implement these. + dbgln("Unimplemented import of non-function attempted"); + return vm.throw_completion("Not Implemented"sv); })); } } @@ -334,17 +337,15 @@ JS::ThrowCompletionOr> instantiate_module(JS linker.link(resolved_imports); auto link_result = linker.finish(); if (link_result.is_error()) { - // FIXME: Throw a LinkError. StringBuilder builder; - builder.append("LinkError: Missing "sv); + builder.append("Missing "sv); builder.join(' ', link_result.error().missing_imports); - return vm.throw_completion(MUST(builder.to_string())); + return vm.throw_completion(MUST(builder.to_string())); } auto instance_result = cache.abstract_machine().instantiate(module, link_result.release_value()); if (instance_result.is_error()) { - // FIXME: Throw a LinkError instead. - return vm.throw_completion(instance_result.error().error); + return vm.throw_completion(String::from_byte_string(instance_result.error().error).release_value_but_fixme_should_propagate_errors()); } return instance_result.release_value(); @@ -356,14 +357,12 @@ JS::ThrowCompletionOr> compile_a_webass FixedMemoryStream stream { data.bytes() }; auto module_result = Wasm::Module::parse(stream); if (module_result.is_error()) { - // FIXME: Throw CompileError instead. - return vm.throw_completion(Wasm::parse_error_to_byte_string(module_result.error())); + return vm.throw_completion(String::from_byte_string(Wasm::parse_error_to_byte_string(module_result.error())).release_value_but_fixme_should_propagate_errors()); } auto& cache = get_cache(*vm.current_realm()); if (auto validation_result = cache.abstract_machine().validate(module_result.value()); validation_result.is_error()) { - // FIXME: Throw CompileError instead. - return vm.throw_completion(validation_result.error().error_string); + return vm.throw_completion(String::from_byte_string(validation_result.error().error_string).release_value_but_fixme_should_propagate_errors()); } auto compiled_module = make_ref_counted(module_result.release_value()); cache.add_compiled_module(compiled_module);