/* * Copyright (c) 2021, Ali Mohammad Pur * Copyright (c) 2023, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Web::WebAssembly { GC_DEFINE_ALLOCATOR(Table); static Wasm::ValueType table_kind_to_value_type(Bindings::TableKind kind) { switch (kind) { case Bindings::TableKind::Externref: return Wasm::ValueType { Wasm::ValueType::ExternReference }; case Bindings::TableKind::Anyfunc: return Wasm::ValueType { Wasm::ValueType::FunctionReference }; } VERIFY_NOT_REACHED(); } // https://webassembly.github.io/spec/js-api/#addressvaluetou64 static WebIDL::ExceptionOr address_value_to_u64(JS::VM& vm, JS::Value value, Wasm::AddressType address_type) { if (address_type == Wasm::AddressType::I64) { // 1. If addrtype is i64, then: // 1.1. Let n be ? ToBigInt(value). auto bigint = TRY(value.to_bigint(vm)); // 1.2. If n < 0 or n >= 2^64, throw a TypeError exception. if (bigint->big_integer().is_negative()) return vm.throw_completion("Table size must be non-negative"sv); auto string = TRY_OR_THROW_OOM(vm, bigint->big_integer().to_base(10)); auto number = string.to_number(); if (!number.has_value()) return vm.throw_completion("Table size is too large"sv); // 1.3. Return n. return *number; } // 2. Otherwise: // 2.1. Assert: addrtype is i32. // 2.2. Let n be ? Web IDL's convert a JavaScript value to an IDL value of type [EnforceRange] unsigned long, given value. auto n = TRY(WebIDL::convert_to_int(vm, value, WebIDL::EnforceRange::Yes)); // 2.3. Return n. return n; } static JS::Value table_limit_to_js_value(JS::VM& vm, u64 value, Wasm::AddressType address_type) { if (address_type == Wasm::AddressType::I64) return JS::BigInt::create(vm, ::Crypto::SignedBigInteger { ::Crypto::UnsignedBigInteger { value } }); return JS::Value { static_cast(value) }; } WebIDL::ExceptionOr> Table::construct_impl(JS::Realm& realm, Bindings::TableDescriptor& descriptor, Optional value) { auto& vm = realm.vm(); // 1. Let elementtype be ToValueType(descriptor["element"]). auto reference_type = table_kind_to_value_type(descriptor.element); // 2. If elementtype is not a reftype, throw a TypeError exception. // NOTE: The TableKind IDL enum only accepts reference types. // 3. If descriptor["address"] exists, let addrtype be descriptor["address"]; otherwise, let addrtype be "i32". auto address_type = descriptor.address == Bindings::AddressType::I64 ? Wasm::AddressType::I64 : Wasm::AddressType::I32; // 4. Let initial be ? AddressValueToU64(descriptor["initial"], addrtype). auto initial = TRY(address_value_to_u64(vm, descriptor.initial, address_type)); // 5. If descriptor["maximum"] exists, let maximum be ? AddressValueToU64(descriptor["maximum"], addrtype); otherwise, let maximum be empty. auto maximum = descriptor.maximum.has_value() ? Optional { TRY(address_value_to_u64(vm, descriptor.maximum.value(), address_type)) } : Optional {}; // 6. Let type be the table type addrtype { min initial, max maximum } elementtype. Wasm::Limits limits { address_type, initial, maximum }; Wasm::TableType table_type { reference_type, move(limits) }; // 7. If type is not valid, throw a RangeError exception. if (maximum.has_value() && maximum.value() < initial) return vm.throw_completion("Maximum should not be less than initial in table type"sv); // 8. If value is missing, let ref be DefaultValue(elementtype). // 9. Otherwise, let ref be ? ToWebAssemblyValue(value, elementtype). auto reference_value = !value.has_value() ? Detail::default_webassembly_value(vm, reference_type) : TRY(Detail::to_webassembly_value(vm, *value, reference_type)); // 10. Let store be the surrounding agent's associated store. auto& cache = Detail::get_cache(realm); // 11. Let (store, tableaddr) be table_alloc(store, type, ref). If allocation fails, throw a RangeError exception. auto address = cache.abstract_machine().store().allocate(table_type); if (!address.has_value()) return vm.throw_completion("Wasm Table allocation failed"sv); auto const& reference = reference_value.to(); auto& table = *cache.abstract_machine().store().get(*address); for (auto& element : table.elements()) element = reference; // 12. Set the surrounding agent's associated store to store. // NOTE: The store is updated in-place. // 13. Initialize this from tableaddr. return realm.create(realm, *address); } Table::Table(JS::Realm& realm, Wasm::TableAddress address) : Bindings::PlatformObject(realm) , m_address(address) { } void Table::initialize(JS::Realm& realm) { WEB_SET_PROTOTYPE_FOR_INTERFACE_WITH_CUSTOM_NAME(Table, WebAssembly.Table); Base::initialize(realm); // https://webassembly.github.io/spec/js-api/#initialize-a-table-object // 1. Let map be the surrounding agent's associated Table object cache. auto& cache = Detail::get_cache(realm); // 2. Assert: map[tableaddr] doesn't exist. auto exists = cache.table_instances().contains(m_address); VERIFY(!exists); // 3. Set table.[[Table]] to tableaddr. // NOTE: This is already set by the Table constructor. // 4. Set map[tableaddr] to table. cache.add_table_instance(m_address, *this); } // https://webassembly.github.io/spec/js-api/#dom-table-grow WebIDL::ExceptionOr Table::grow(JS::Value delta_value, Optional value) { auto& vm = this->vm(); // 1. Let tableaddr be this.[[Table]]. // 2. Let store be the surrounding agent's associated store. auto& cache = Detail::get_cache(realm()); auto* table = cache.abstract_machine().store().get(address()); if (!table) return vm.throw_completion("Could not find the memory table to grow"sv); // 3. Let initialSize be table_size(store, tableaddr). auto initial_size = table->elements().size(); // 4. Let (addrtype, limits, elementtype) be table_type(store, tableaddr). auto address_type = table->type().limits().address_type(); auto element_type = table->type().element_type(); // 5. Let delta64 be ? AddressValueToU64(delta, addrtype). auto delta = TRY(address_value_to_u64(vm, delta_value, address_type)); // 6. If value is missing, let ref be DefaultValue(elementtype). // 7. Otherwise, let ref be ? ToWebAssemblyValue(value, elementtype). auto reference_value = !value.has_value() ? Detail::default_webassembly_value(vm, element_type) : TRY(Detail::to_webassembly_value(vm, *value, element_type)); auto const& reference = reference_value.to(); // 8. Let result be table_grow(store, tableaddr, delta64, ref). // 9. If result is error, throw a RangeError exception. if (!table->grow(delta, reference)) return vm.throw_completion("Failed to grow table"sv); // 10. Set the surrounding agent's associated store to result. // NOTE: The store is updated in-place. // 11. Return U64ToAddressValue(initialSize, addrtype). return table_limit_to_js_value(vm, initial_size, address_type); } // https://webassembly.github.io/spec/js-api/#dom-table-get WebIDL::ExceptionOr Table::get(JS::Value index_value) const { auto& vm = this->vm(); // 1. Let tableaddr be this.[[Table]]. // 2. Let store be the surrounding agent's associated store. auto& cache = Detail::get_cache(realm()); auto* table = cache.abstract_machine().store().get(address()); if (!table) return vm.throw_completion("Could not find the memory table"sv); // 3. Let (addrtype, limits, elementtype) be table_type(store, tableaddr). auto address_type = table->type().limits().address_type(); auto element_type = table->type().element_type(); // 4. If elementtype matches exnref, throw a TypeError exception. if (element_type.kind() == Wasm::ValueType::ExceptionReference) return vm.throw_completion("Cannot get an exnref table element"sv); // 5. Let index64 be ? AddressValueToU64(index, addrtype). auto index = TRY(address_value_to_u64(vm, index_value, address_type)); // 6. Let result be table_read(store, tableaddr, index64). // 7. If result is error, throw a RangeError exception. if (table->elements().size() <= index) return vm.throw_completion("Table element index out of range"sv); auto& ref = table->elements()[index]; // 8. Return ! ToJSValue(result). Wasm::Value wasm_value { ref }; return Detail::to_js_value(vm, wasm_value, element_type); } // https://webassembly.github.io/spec/js-api/#dom-table-set WebIDL::ExceptionOr Table::set(JS::Value index_value, Optional value) { auto& vm = this->vm(); // 1. Let tableaddr be this.[[Table]]. // 2. Let store be the surrounding agent's associated store. auto& cache = Detail::get_cache(realm()); auto* table = cache.abstract_machine().store().get(address()); if (!table) return vm.throw_completion("Could not find the memory table"sv); // 3. Let (addrtype, limits, elementtype) be table_type(store, tableaddr). auto address_type = table->type().limits().address_type(); auto element_type = table->type().element_type(); // 4. If elementtype matches exnref, throw a TypeError exception. if (element_type.kind() == Wasm::ValueType::ExceptionReference) return vm.throw_completion("Cannot set an exnref table element"sv); // 5. Let index64 be ? AddressValueToU64(index, addrtype). auto index = TRY(address_value_to_u64(vm, index_value, address_type)); if (table->elements().size() <= index) return vm.throw_completion("Table element index out of range"sv); // 6. If value is missing, let ref be DefaultValue(elementtype). // 7. Otherwise, let ref be ? ToWebAssemblyValue(value, elementtype). auto reference_value = !value.has_value() ? Detail::default_webassembly_value(vm, element_type) : TRY(Detail::to_webassembly_value(vm, *value, element_type)); auto const& reference = reference_value.to(); // 8. Let store be table_write(store, tableaddr, index64, ref). // 9. If store is error, throw a RangeError exception. table->elements()[index] = reference; // 10. Set the surrounding agent's associated store to store. // NOTE: The store is updated in-place. return {}; } // https://webassembly.github.io/spec/js-api/#dom-table-length WebIDL::ExceptionOr Table::length() const { auto& vm = this->vm(); // 1. Let tableaddr be this.[[Table]]. // 2. Let store be the surrounding agent's associated store. auto& cache = Detail::get_cache(realm()); auto* table = cache.abstract_machine().store().get(address()); if (!table) return vm.throw_completion("Could not find the memory table"sv); // 3. Let addrtype be the address type in table_type(store, tableaddr). auto address_type = table->type().limits().address_type(); // 4. Let length64 be table_size(store, tableaddr). auto length = table->elements().size(); // 5. Return U64ToAddressValue(length64, addrtype). return table_limit_to_js_value(vm, length, address_type); } }