srctree

Robin Linden parent 7aef4883 ae4eaa2f
wasm: Parse module sections eagerly

This means fewer allocations, earlier failures if something's wrong,easier error-checking (since if a section fails to parse, the entiremodule will now fail to parse), and will allow the module parsing andvalidation to be single-pass.

inlinesplit
wasm/wasm.cpp added: 271, removed: 349, total 0
@@ -13,7 +13,6 @@
#include <cstdint>
#include <istream>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <utility>
@@ -192,13 +191,52 @@ std::optional<std::vector<T>> parse_vector(std::istream &&is) {
return parse_vector<T>(is);
}
 
std::optional<std::string> get_section_data(std::vector<Section> const &sections, SectionId id) {
auto section = std::ranges::find_if(sections, [&](auto const &s) { return s.id == id; });
if (section == end(sections)) {
return std::nullopt;
std::optional<TypeSection> parse_type_section(std::istream &is) {
if (auto maybe_types = parse_vector<FunctionType>(is)) {
return TypeSection{.types = *std::move(maybe_types)};
}
 
return std::string{reinterpret_cast<char const *>(section->content.data()), section->content.size()};
return std::nullopt;
}
 
std::optional<FunctionSection> parse_function_section(std::istream &is) {
if (auto maybe_type_indices = parse_vector<TypeIdx>(is)) {
return FunctionSection{.type_indices = *std::move(maybe_type_indices)};
}
 
return std::nullopt;
}
 
std::optional<TableSection> parse_table_section(std::istream &is) {
if (auto maybe_tables = parse_vector<TableType>(is)) {
return TableSection{*std::move(maybe_tables)};
}
 
return std::nullopt;
}
 
std::optional<ExportSection> parse_export_section(std::istream &is) {
if (auto maybe_exports = parse_vector<Export>(is)) {
return ExportSection{.exports = std::move(maybe_exports).value()};
}
 
return std::nullopt;
}
 
std::optional<StartSection> parse_start_section(std::istream &is) {
if (auto maybe_start = parse<FuncIdx>(is)) {
return StartSection{.start = *maybe_start};
}
 
return std::nullopt;
}
 
std::optional<CodeSection> parse_code_section(std::istream &is) {
if (auto code_entries = parse_vector<CodeEntry>(is)) {
return CodeSection{.entries = *std::move(code_entries)};
}
 
return std::nullopt;
}
 
} // namespace
@@ -230,85 +268,6 @@ std::optional<ValueType> ValueType::parse(std::istream &is) {
}
}
 
tl::expected<Module, ModuleParseError> Module::parse_from(std::istream &is) {
std::string buf;
 
// https://webassembly.github.io/spec/core/binary/modules.html#binary-magic
buf.resize(kMagicSize);
is.read(buf.data(), buf.size());
if (!is || buf != "\0asm"sv) {
return tl::unexpected{ModuleParseError::InvalidMagic};
}
 
// https://webassembly.github.io/spec/core/binary/modules.html#binary-version
buf.resize(kVersionSize);
is.read(buf.data(), buf.size());
if (!is || buf != "\1\0\0\0"sv) {
return tl::unexpected{ModuleParseError::UnsupportedVersion};
}
 
Module module;
 
// https://webassembly.github.io/spec/core/binary/modules.html#sections
while (true) {
std::uint8_t id{};
is.read(reinterpret_cast<char *>(&id), sizeof(id));
if (!is) {
// We've read 0 or more complete modules, so we're done.
break;
}
 
if (id < static_cast<int>(SectionId::Custom) || id > static_cast<int>(SectionId::DataCount)) {
return tl::unexpected{ModuleParseError::InvalidSectionId};
}
 
auto size = Leb128<std::uint32_t>::decode_from(is);
if (!size) {
if (size.error() == Leb128ParseError::UnexpectedEof) {
return tl::unexpected{ModuleParseError::UnexpectedEof};
}
return tl::unexpected{ModuleParseError::InvalidSize};
}
 
std::vector<std::uint8_t> content;
content.resize(*size);
is.read(reinterpret_cast<char *>(content.data()), *size);
if (!is) {
return tl::unexpected{ModuleParseError::UnexpectedEof};
}
 
module.sections.push_back(Section{static_cast<SectionId>(id), std::move(content)});
}
 
return module;
}
 
std::optional<TypeSection> Module::type_section() const {
auto content = get_section_data(sections, SectionId::Type);
if (!content) {
return std::nullopt;
}
 
if (auto maybe_types = parse_vector<FunctionType>(std::stringstream{*std::move(content)})) {
return TypeSection{.types = *std::move(maybe_types)};
}
 
return std::nullopt;
}
 
std::optional<FunctionSection> Module::function_section() const {
auto content = get_section_data(sections, SectionId::Function);
if (!content) {
return std::nullopt;
}
 
if (auto maybe_type_indices = parse_vector<TypeIdx>(std::stringstream{*std::move(content)})) {
return FunctionSection{.type_indices = *std::move(maybe_type_indices)};
}
 
return std::nullopt;
}
 
std::optional<Limits> Limits::parse(std::istream &is) {
std::uint8_t has_max{};
if (!is.read(reinterpret_cast<char *>(&has_max), sizeof(has_max)) || has_max > 1) {
@@ -348,56 +307,110 @@ std::optional<TableType> TableType::parse(std::istream &is) {
return TableType{.element_type = *element_type, .limits = *limits};
}
 
std::optional<TableSection> Module::table_section() const {
auto content = get_section_data(sections, SectionId::Table);
if (!content) {
return std::nullopt;
tl::expected<Module, ModuleParseError> Module::parse_from(std::istream &is) {
// https://webassembly.github.io/spec/core/binary/modules.html#sections
enum class SectionId {
Custom = 0,
Type = 1,
Import = 2,
Function = 3,
Table = 4,
Memory = 5,
Global = 6,
Export = 7,
Start = 8,
Element = 9,
Code = 10,
Data = 11,
DataCount = 12,
};
 
std::string buf;
 
// https://webassembly.github.io/spec/core/binary/modules.html#binary-magic
buf.resize(kMagicSize);
is.read(buf.data(), buf.size());
if (!is || buf != "\0asm"sv) {
return tl::unexpected{ModuleParseError::InvalidMagic};
}
 
if (auto maybe_tables = parse_vector<TableType>(std::stringstream{*std::move(content)})) {
return TableSection{*std::move(maybe_tables)};
// https://webassembly.github.io/spec/core/binary/modules.html#binary-version
buf.resize(kVersionSize);
is.read(buf.data(), buf.size());
if (!is || buf != "\1\0\0\0"sv) {
return tl::unexpected{ModuleParseError::UnsupportedVersion};
}
 
return std::nullopt;
}
Module module;
 
std::optional<ExportSection> Module::export_section() const {
auto content = get_section_data(sections, SectionId::Export);
if (!content) {
return std::nullopt;
// https://webassembly.github.io/spec/core/binary/modules.html#sections
while (true) {
std::uint8_t id_byte{};
is.read(reinterpret_cast<char *>(&id_byte), sizeof(id_byte));
if (!is) {
// We've read 0 or more complete modules, so we're done.
break;
}
 
if (id_byte < static_cast<int>(SectionId::Custom) || id_byte > static_cast<int>(SectionId::DataCount)) {
return tl::unexpected{ModuleParseError::InvalidSectionId};
}
 
auto size = Leb128<std::uint32_t>::decode_from(is);
if (!size) {
if (size.error() == Leb128ParseError::UnexpectedEof) {
return tl::unexpected{ModuleParseError::UnexpectedEof};
}
return tl::unexpected{ModuleParseError::InvalidSize};
}
 
auto id = static_cast<SectionId>(id_byte);
switch (id) {
case SectionId::Type:
module.type_section = parse_type_section(is);
if (!module.type_section) {
return tl::unexpected{ModuleParseError::InvalidTypeSection};
}
break;
case SectionId::Function:
module.function_section = parse_function_section(is);
if (!module.function_section) {
return tl::unexpected{ModuleParseError::InvalidFunctionSection};
}
break;
case SectionId::Table:
module.table_section = parse_table_section(is);
if (!module.table_section) {
return tl::unexpected{ModuleParseError::InvalidTableSection};
}
break;
case SectionId::Export:
module.export_section = parse_export_section(is);
if (!module.export_section) {
return tl::unexpected{ModuleParseError::InvalidExportSection};
}
break;
case SectionId::Start:
module.start_section = parse_start_section(is);
if (!module.start_section) {
return tl::unexpected{ModuleParseError::InvalidStartSection};
}
break;
case SectionId::Code:
module.code_section = parse_code_section(is);
if (!module.code_section) {
return tl::unexpected{ModuleParseError::InvalidCodeSection};
}
break;
default:
// Uncomment if you want to skip past unhandled sections for e.g. debugging.
// is.seekg(*size, std::ios::cur);
// break;
return tl::unexpected{ModuleParseError::UnhandledSection};
}
}
 
if (auto maybe_exports = parse_vector<Export>(std::stringstream{*std::move(content)})) {
return ExportSection{.exports = std::move(maybe_exports).value()};
}
 
return std::nullopt;
}
 
std::optional<StartSection> Module::start_section() const {
auto content = get_section_data(sections, SectionId::Start);
if (!content) {
return std::nullopt;
}
 
if (auto maybe_start = parse<FuncIdx>(std::stringstream(std::move(*content)))) {
return StartSection{.start = *maybe_start};
}
 
return std::nullopt;
}
 
std::optional<CodeSection> Module::code_section() const {
auto content = get_section_data(sections, SectionId::Code);
if (!content) {
return std::nullopt;
}
 
if (auto code_entries = parse_vector<CodeEntry>(std::stringstream{*std::move(content)})) {
return CodeSection{.entries = *std::move(code_entries)};
}
 
return std::nullopt;
return module;
}
 
} // namespace wasm
 
wasm/wasm.h added: 271, removed: 349, total 0
@@ -19,30 +19,6 @@ namespace wasm {
using TypeIdx = std::uint32_t;
using FuncIdx = std::uint32_t;
 
// https://webassembly.github.io/spec/core/binary/modules.html#sections
enum class SectionId {
Custom = 0,
Type = 1,
Import = 2,
Function = 3,
Table = 4,
Memory = 5,
Global = 6,
Export = 7,
Start = 8,
Element = 9,
Code = 10,
Data = 11,
DataCount = 12,
};
 
struct Section {
SectionId id{};
std::vector<std::uint8_t> content{};
 
[[nodiscard]] bool operator==(Section const &) const = default;
};
 
// https://webassembly.github.io/spec/core/syntax/types.html
struct ValueType {
enum Kind : std::uint8_t {
@@ -169,6 +145,13 @@ enum class ModuleParseError {
UnsupportedVersion,
InvalidSectionId,
InvalidSize,
InvalidTypeSection,
InvalidFunctionSection,
InvalidTableSection,
InvalidExportSection,
InvalidStartSection,
InvalidCodeSection,
UnhandledSection,
};
 
// https://webassembly.github.io/spec/core/syntax/modules.html
@@ -176,19 +159,17 @@ struct Module {
static tl::expected<Module, ModuleParseError> parse_from(std::istream &&is) { return parse_from(is); }
static tl::expected<Module, ModuleParseError> parse_from(std::istream &);
 
std::vector<Section> sections{};
 
// TODO(robinlinden): custom_sections
std::optional<TypeSection> type_section() const;
std::optional<TypeSection> type_section{};
// TODO(robinlinden): import_section
std::optional<FunctionSection> function_section() const;
std::optional<TableSection> table_section() const;
std::optional<FunctionSection> function_section{};
std::optional<TableSection> table_section{};
// TODO(robinlinden): memory_section
// TODO(robinlinden): global_section
std::optional<ExportSection> export_section() const;
std::optional<StartSection> start_section() const;
std::optional<ExportSection> export_section{};
std::optional<StartSection> start_section{};
// TODO(robinlinden): element_section
std::optional<CodeSection> code_section() const;
std::optional<CodeSection> code_section{};
// TODO(robinlinden): data_section
// TODO(robinlinden): data_count_section
 
 
wasm/wasm_example.cpp added: 271, removed: 349, total 0
@@ -65,17 +65,7 @@ int main(int argc, char **argv) {
return 1;
}
 
if (module->sections.empty()) {
std::cout << "No sections in module\n";
return 0;
}
 
std::cout << "# Sections\n";
for (auto const &section : module->sections) {
std::cout << static_cast<int>(section.id) << ": " << section.content.size() << '\n';
}
 
if (auto const &type_section = module->type_section()) {
if (auto const &type_section = module->type_section) {
std::cout << "\n# Types\n";
// Prints a list of wasm::ValueType separated by commas.
// https://en.cppreference.com/w/cpp/experimental/ostream_joiner soon, I hope.
@@ -95,21 +85,21 @@ int main(int argc, char **argv) {
}
}
 
if (auto const &function_section = module->function_section()) {
if (auto const &function_section = module->function_section) {
std::cout << "\n# Function idx -> type idx\n";
for (std::size_t i = 0; i < function_section->type_indices.size(); ++i) {
std::cout << i << " -> " << function_section->type_indices[i] << '\n';
}
}
 
if (auto const &export_section = module->export_section()) {
if (auto const &export_section = module->export_section) {
std::cout << "\n# Exports\n";
for (auto const &e : export_section->exports) {
std::cout << e.name << ": " << static_cast<int>(e.type) << ':' << e.index << '\n';
}
}
 
if (auto const &s = module->code_section()) {
if (auto const &s = module->code_section) {
std::cout << "\n# Code\n";
for (auto const &e : s->entries) {
std::cout << e.code.size() << "B code, " << e.locals.size() << " locals";
 
wasm/wasm_test.cpp added: 271, removed: 349, total 0
@@ -12,7 +12,6 @@
#include <cassert>
#include <cstdint>
#include <iterator>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
@@ -25,7 +24,24 @@ using etest::expect_eq;
 
namespace {
 
std::stringstream make_module_bytes(wasm::SectionId id, std::vector<std::uint8_t> const &section_content) {
// https://webassembly.github.io/spec/core/binary/modules.html#sections
enum class SectionId {
Custom = 0,
Type = 1,
Import = 2,
Function = 3,
Table = 4,
Memory = 5,
Global = 6,
Export = 7,
Start = 8,
Element = 9,
Code = 10,
Data = 11,
DataCount = 12,
};
 
std::stringstream make_module_bytes(SectionId id, std::vector<std::uint8_t> const &section_content) {
std::stringstream wasm_bytes;
wasm_bytes << "\0asm\1\0\0\0"sv;
wasm_bytes << static_cast<std::uint8_t>(id);
@@ -36,31 +52,26 @@ std::stringstream make_module_bytes(wasm::SectionId id, std::vector<std::uint8_t
}
 
void export_section_tests() {
etest::test("export section, non-existent", [] {
auto module = wasm::Module{};
expect_eq(module.export_section(), std::nullopt);
});
 
etest::test("export section, missing export count", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Export, {})).value();
expect_eq(module.export_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Export, {}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidExportSection});
});
 
etest::test("export section, missing export after count", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Export, {1})).value();
expect_eq(module.export_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Export, {1}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidExportSection});
});
 
etest::test("export section, empty", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Export, {0})).value();
expect_eq(module.export_section(), wasm::ExportSection{});
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Export, {0})).value();
expect_eq(module.export_section, wasm::ExportSection{});
});
 
etest::test("export section, one", [] {
std::vector<std::uint8_t> content{1, 2, 'h', 'i', static_cast<std::uint8_t>(wasm::Export::Type::Function), 5};
 
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Export, content)).value();
expect_eq(module.export_section(),
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Export, content)).value();
expect_eq(module.export_section,
wasm::ExportSection{.exports{wasm::Export{"hi", wasm::Export::Type::Function, 5}}});
});
 
@@ -80,8 +91,8 @@ void export_section_tests() {
2,
};
 
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Export, content)).value();
expect_eq(module.export_section(),
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Export, content)).value();
expect_eq(module.export_section,
wasm::ExportSection{.exports{
wasm::Export{"hi", wasm::Export::Type::Function, 5},
wasm::Export{"lol", wasm::Export::Type::Global, 2},
@@ -89,114 +100,98 @@ void export_section_tests() {
});
 
etest::test("export section, missing name", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Export, {1, 2})).value();
expect_eq(module.export_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Export, {1, 2}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidExportSection});
});
 
etest::test("export section, missing type", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Export, {1, 1, 'a'})).value();
expect_eq(module.export_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Export, {1, 1, 'a'}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidExportSection});
});
 
etest::test("export section, missing index", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Export, {1, 1, 'a', 1})).value();
expect_eq(module.export_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Export, {1, 1, 'a', 1}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidExportSection});
});
}
 
void start_section_tests() {
etest::test("start section, non-existent", [] {
auto module = wasm::Module{};
expect_eq(module.start_section(), std::nullopt);
});
 
etest::test("start section, missing start", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Start, {})).value();
expect_eq(module.start_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Start, {}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidStartSection});
});
 
etest::test("start section, excellent", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Start, {42})).value();
expect_eq(module.start_section(), wasm::StartSection{.start = 42});
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Start, {42})).value();
expect_eq(module.start_section, wasm::StartSection{.start = 42});
});
}
 
void function_section_tests() {
etest::test("function section, non-existent", [] {
auto module = wasm::Module{};
expect_eq(module.function_section(), std::nullopt);
});
 
etest::test("function section, missing data", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Function, {})).value();
expect_eq(module.function_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Function, {}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidFunctionSection});
});
 
etest::test("function section, empty", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Function, {0})).value();
expect_eq(module.function_section(), wasm::FunctionSection{});
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Function, {0})).value();
expect_eq(module.function_section, wasm::FunctionSection{});
});
 
etest::test("function section, missing type indices after count", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Function, {1})).value();
expect_eq(module.function_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Function, {1}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidFunctionSection});
});
 
etest::test("function section, good one", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Function, {2, 9, 13})).value();
expect_eq(module.function_section(), wasm::FunctionSection{.type_indices{9, 13}});
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Function, {2, 9, 13})).value();
expect_eq(module.function_section, wasm::FunctionSection{.type_indices{9, 13}});
});
}
 
void table_section_tests() {
etest::test("table section, non-existent", [] {
auto module = wasm::Module{};
expect_eq(module.table_section(), std::nullopt);
});
 
etest::test("table section, missing data", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Table, {})).value();
expect_eq(module.table_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Table, {}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidTableSection});
});
 
etest::test("table section, empty", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Table, {0})).value();
expect_eq(module.table_section(), wasm::TableSection{});
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Table, {0})).value();
expect_eq(module.table_section, wasm::TableSection{});
});
 
etest::test("table section, no element type", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Table, {1})).value();
expect_eq(module.table_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Table, {1}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidTableSection});
});
 
etest::test("table section, invalid element type", [] {
constexpr std::uint8_t kInt32Type = 0x7f;
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Table, {1, kInt32Type})).value();
expect_eq(module.table_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Table, {1, kInt32Type}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidTableSection});
});
 
static constexpr std::uint8_t kFuncRefType = 0x70;
static constexpr std::uint8_t kExtRefType = 0x6f;
 
etest::test("table section, missing limits", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Table, {1, kFuncRefType})).value();
expect_eq(module.table_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Table, {1, kFuncRefType}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidTableSection});
});
 
etest::test("table section, invalid has_max in limits", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Table, {1, kFuncRefType, 4})).value();
expect_eq(module.table_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Table, {1, kFuncRefType, 4}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidTableSection});
});
 
etest::test("table section, missing min in limits", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Table, {1, kFuncRefType, 0})).value();
expect_eq(module.table_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Table, {1, kFuncRefType, 0}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidTableSection});
});
 
etest::test("table section, only min", [] {
auto module =
wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Table, {1, kFuncRefType, 0, 42})).value();
expect_eq(module.table_section(),
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Table, {1, kFuncRefType, 0, 42})).value();
expect_eq(module.table_section,
wasm::TableSection{.tables{
wasm::TableType{
wasm::ValueType{wasm::ValueType::FunctionReference},
@@ -206,15 +201,14 @@ void table_section_tests() {
});
 
etest::test("table section, missing max in limits", [] {
auto module =
wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Table, {1, kExtRefType, 1, 42})).value();
expect_eq(module.table_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Table, {1, kExtRefType, 1, 42}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidTableSection});
});
 
etest::test("table section, min and max", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Table, {1, kExtRefType, 1, 42, 42}))
.value();
expect_eq(module.table_section(),
auto module =
wasm::Module::parse_from(make_module_bytes(SectionId::Table, {1, kExtRefType, 1, 42, 42})).value();
expect_eq(module.table_section,
wasm::TableSection{.tables{
wasm::TableType{
wasm::ValueType{wasm::ValueType::ExternReference},
@@ -225,61 +219,50 @@ void table_section_tests() {
}
 
void type_section_tests() {
etest::test("type section, non-existent", [] {
auto module = wasm::Module{};
expect_eq(module.type_section(), std::nullopt);
});
 
etest::test("type section, missing type data", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Type, {})).value();
 
expect_eq(module.type_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Type, {}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidTypeSection});
});
 
etest::test("type section, empty", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Type, {0})).value();
expect_eq(module.type_section(), wasm::TypeSection{});
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Type, {0})).value();
expect_eq(module.type_section, wasm::TypeSection{});
});
 
etest::test("type section, missing type after count", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Type, {1})).value();
 
expect_eq(module.type_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Type, {1}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidTypeSection});
});
 
etest::test("type section, bad magic in function type", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Type, {1, 0x59})).value();
 
expect_eq(module.type_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Type, {1, 0x59}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidTypeSection});
});
 
etest::test("type section, one type with no parameters and no results", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Type, {1, 0x60, 0, 0})).value();
 
expect_eq(module.type_section(), wasm::TypeSection{.types{wasm::FunctionType{}}});
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Type, {1, 0x60, 0, 0})).value();
expect_eq(module.type_section, wasm::TypeSection{.types{wasm::FunctionType{}}});
});
 
etest::test("type section, eof in parameter parsing", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Type, {1, 0x60, 1})).value();
 
expect_eq(module.type_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Type, {1, 0x60, 1}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidTypeSection});
});
 
etest::test("type section, eof in result parsing", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Type, {1, 0x60, 0, 1})).value();
 
expect_eq(module.type_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Type, {1, 0x60, 0, 1}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidTypeSection});
});
 
etest::test("type section, two types", [] {
constexpr std::uint8_t kInt32Byte = 0x7f;
constexpr std::uint8_t kFloat64Byte = 0x7c;
auto module = wasm::Module::parse_from(
make_module_bytes(wasm::SectionId::Type,
{2, 0x60, 0, 1, kInt32Byte, 0x60, 2, kInt32Byte, kInt32Byte, 1, kFloat64Byte}))
make_module_bytes(
SectionId::Type, {2, 0x60, 0, 1, kInt32Byte, 0x60, 2, kInt32Byte, kInt32Byte, 1, kFloat64Byte}))
.value();
 
expect_eq(module.type_section(),
expect_eq(module.type_section,
wasm::TypeSection{
.types{
wasm::FunctionType{.results{wasm::ValueType{wasm::ValueType::Int32}}},
@@ -294,10 +277,10 @@ void type_section_tests() {
 
etest::test("type section, all types", [] {
auto module = wasm::Module::parse_from(
make_module_bytes(wasm::SectionId::Type, {1, 0x60, 7, 0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x70, 0x6f, 0}))
make_module_bytes(SectionId::Type, {1, 0x60, 7, 0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x70, 0x6f, 0}))
.value();
 
expect_eq(module.type_section(),
expect_eq(module.type_section,
wasm::TypeSection{
.types{
wasm::FunctionType{
@@ -316,51 +299,44 @@ void type_section_tests() {
});
 
etest::test("type section, invalid value type", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Type, {1, 0x60, 0, 1, 0x10})).value();
expect_eq(module.type_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Type, {1, 0x60, 0, 1, 0x10}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidTypeSection});
});
}
 
void code_section_tests() {
etest::test("code section, non-existent", [] {
auto module = wasm::Module{};
expect_eq(module.code_section(), std::nullopt);
});
 
etest::test("code section, missing type data", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Code, {})).value();
expect_eq(module.code_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Code, {}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidCodeSection});
});
 
etest::test("code section, empty", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Code, {0})).value();
expect_eq(module.code_section(), wasm::CodeSection{});
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Code, {0})).value();
expect_eq(module.code_section, wasm::CodeSection{});
});
 
etest::test("code section, missing data after count", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Code, {1})).value();
expect_eq(module.code_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Code, {1}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidCodeSection});
});
 
etest::test("code section, missing local count", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Code, {1, 1, 1})).value();
expect_eq(module.code_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Code, {1, 1, 1}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidCodeSection});
});
 
etest::test("code section, missing local type", [] {
auto module = wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Code, {1, 1, 1, 1})).value();
expect_eq(module.code_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Code, {1, 1, 1, 1}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidCodeSection});
});
 
etest::test("code section, not enough data", [] {
auto module =
wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Code, {1, 6, 1, 1, 0x7f, 4, 4})).value();
expect_eq(module.code_section(), std::nullopt);
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Code, {1, 6, 1, 1, 0x7f, 4, 4}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidCodeSection});
});
 
etest::test("code section, one entry", [] {
auto module =
wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Code, {1, 6, 1, 1, 0x7f, 4, 4, 4})).value();
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Code, {1, 6, 1, 1, 0x7f, 4, 4, 4})).value();
 
wasm::CodeSection expected{.entries{
wasm::CodeEntry{
@@ -368,14 +344,13 @@ void code_section_tests() {
.locals{{1, wasm::ValueType{wasm::ValueType::Int32}}},
},
}};
expect_eq(module.code_section(), expected);
expect_eq(module.code_section, expected);
});
 
etest::test("code section, two entries", [] {
auto module =
wasm::Module::parse_from(make_module_bytes(wasm::SectionId::Code,
{2, 6, 1, 1, 0x7f, 4, 4, 4, 9, 2, 5, 0x7e, 6, 0x7d, 7, 8, 9, 10}))
.value();
auto module = wasm::Module::parse_from(
make_module_bytes(SectionId::Code, {2, 6, 1, 1, 0x7f, 4, 4, 4, 9, 2, 5, 0x7e, 6, 0x7d, 7, 8, 9, 10}))
.value();
 
wasm::CodeSection expected{.entries{
wasm::CodeEntry{
@@ -388,7 +363,7 @@ void code_section_tests() {
{6, wasm::ValueType{wasm::ValueType::Float32}}},
},
}};
expect_eq(module.code_section(), expected);
expect_eq(module.code_section, expected);
});
}
 
@@ -429,46 +404,9 @@ int main() {
expect_eq(wasm::Module::parse_from(std::move(wasm_bytes)), tl::unexpected{wasm::ModuleParseError::InvalidSize});
});
 
etest::test("missing content", [] {
auto wasm_bytes = std::stringstream{"\0asm\1\0\0\0\0\4"s};
expect_eq(
wasm::Module::parse_from(std::move(wasm_bytes)), tl::unexpected{wasm::ModuleParseError::UnexpectedEof});
});
 
etest::test("not enough content", [] {
auto wasm_bytes = std::stringstream{"\0asm\1\0\0\0\0\4\0\0\0"s};
expect_eq(
wasm::Module::parse_from(std::move(wasm_bytes)), tl::unexpected{wasm::ModuleParseError::UnexpectedEof});
});
 
etest::test("one valid section", [] {
auto wasm_bytes = std::stringstream{"\0asm\1\0\0\0\x0b\4\1\2\3\4"s};
expect_eq(wasm::Module::parse_from(std::move(wasm_bytes)),
wasm::Module{
.sections{
wasm::Section{
.id = wasm::SectionId::Data,
.content{1, 2, 3, 4},
},
},
});
});
 
etest::test("two valid sections", [] {
auto wasm_bytes = std::stringstream{"\0asm\1\0\0\0\x09\4\1\2\3\4\x0a\2\5\6"s};
expect_eq(wasm::Module::parse_from(std::move(wasm_bytes)),
wasm::Module{
.sections{
wasm::Section{
.id = wasm::SectionId::Element,
.content{1, 2, 3, 4},
},
wasm::Section{
.id = wasm::SectionId::Code,
.content{5, 6},
},
},
});
etest::test("unhandled section", [] {
expect_eq(wasm::Module::parse_from(make_module_bytes(SectionId::Custom, {})),
tl::unexpected{wasm::ModuleParseError::UnhandledSection});
});
 
type_section_tests();