srctree

Robin Linden parent 72ac7d8a 161c5c0a
wasm: Split out parsing bits into ByteCodeParser

inlinesplit
wasm/wasm.cpp added: 181, removed: 169, total 12
@@ -2,9 +2,10 @@
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "wasm/wasm.h"
#include "wasm/byte_code_parser.h"
 
#include "wasm/leb128.h"
#include "wasm/wasm.h"
 
#include <tl/expected.hpp>
 
@@ -45,14 +46,56 @@ std::optional<std::uint32_t> parse(std::istream &is) {
return v ? std::optional{*v} : std::nullopt;
}
 
// https://webassembly.github.io/spec/core/binary/types.html
template<>
std::optional<ValueType> parse(std::istream &is) {
return ValueType::parse(is);
std::uint8_t byte{};
if (!is.read(reinterpret_cast<char *>(&byte), sizeof(byte))) {
return std::nullopt;
}
 
switch (byte) {
case 0x7f:
return ValueType{ValueType::Kind::Int32};
case 0x7e:
return ValueType{ValueType::Kind::Int64};
case 0x7d:
return ValueType{ValueType::Kind::Float32};
case 0x7c:
return ValueType{ValueType::Kind::Float64};
case 0x7b:
return ValueType{ValueType::Kind::Vector128};
case 0x70:
return ValueType{ValueType::Kind::FunctionReference};
case 0x6f:
return ValueType{ValueType::Kind::ExternReference};
default:
return std::nullopt;
}
}
 
template<>
std::optional<Limits> parse(std::istream &is) {
return Limits::parse(is);
std::uint8_t has_max{};
if (!is.read(reinterpret_cast<char *>(&has_max), sizeof(has_max)) || has_max > 1) {
return std::nullopt;
}
 
auto min = Leb128<std::uint32_t>::decode_from(is);
if (!min) {
return std::nullopt;
}
 
if (has_max == 0) {
return Limits{.min = *min};
}
 
auto max = Leb128<std::uint32_t>::decode_from(is);
if (!max) {
return std::nullopt;
}
 
return Limits{.min = *min, .max = *max};
}
 
// https://webassembly.github.io/spec/core/binary/types.html#function-types
@@ -81,7 +124,19 @@ std::optional<FunctionType> parse(std::istream &is) {
 
template<>
std::optional<TableType> parse(std::istream &is) {
return TableType::parse(is);
auto element_type = parse<ValueType>(is);
if (!element_type
|| (element_type->kind != ValueType::Kind::FunctionReference
&& element_type->kind != ValueType::Kind::ExternReference)) {
return std::nullopt;
}
 
auto limits = parse<Limits>(is);
if (!limits) {
return std::nullopt;
}
 
return TableType{.element_type = *element_type, .limits = *limits};
}
 
// https://webassembly.github.io/spec/core/binary/modules.html#binary-exportsec
@@ -129,7 +184,7 @@ std::optional<CodeEntry::Local> parse(std::istream &is) {
return std::nullopt;
}
 
auto type = ValueType::parse(is);
auto type = parse<ValueType>(is);
if (!type) {
return std::nullopt;
}
@@ -255,73 +310,7 @@ std::optional<CodeSection> parse_code_section(std::istream &is) {
 
} // namespace
 
// https://webassembly.github.io/spec/core/binary/types.html
std::optional<ValueType> ValueType::parse(std::istream &is) {
std::uint8_t byte{};
if (!is.read(reinterpret_cast<char *>(&byte), sizeof(byte))) {
return std::nullopt;
}
 
switch (byte) {
case 0x7f:
return ValueType{Kind::Int32};
case 0x7e:
return ValueType{Kind::Int64};
case 0x7d:
return ValueType{Kind::Float32};
case 0x7c:
return ValueType{Kind::Float64};
case 0x7b:
return ValueType{Kind::Vector128};
case 0x70:
return ValueType{Kind::FunctionReference};
case 0x6f:
return ValueType{Kind::ExternReference};
default:
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) {
return std::nullopt;
}
 
auto min = Leb128<std::uint32_t>::decode_from(is);
if (!min) {
return std::nullopt;
}
 
if (has_max == 0) {
return Limits{.min = *min};
}
 
auto max = Leb128<std::uint32_t>::decode_from(is);
if (!max) {
return std::nullopt;
}
 
return Limits{.min = *min, .max = *max};
}
 
std::optional<TableType> TableType::parse(std::istream &is) {
auto element_type = ValueType::parse(is);
if (!element_type
|| (element_type->kind != ValueType::Kind::FunctionReference
&& element_type->kind != ValueType::Kind::ExternReference)) {
return std::nullopt;
}
 
auto limits = Limits::parse(is);
if (!limits) {
return std::nullopt;
}
 
return TableType{.element_type = *element_type, .limits = *limits};
}
 
tl::expected<Module, ModuleParseError> Module::parse_from(std::istream &is) {
tl::expected<Module, ModuleParseError> ByteCodeParser::parse_module(std::istream &is) {
// https://webassembly.github.io/spec/core/binary/modules.html#sections
enum class SectionId {
Custom = 0,
@@ -434,4 +423,8 @@ tl::expected<Module, ModuleParseError> Module::parse_from(std::istream &is) {
return module;
}
 
std::optional<ValueType> ByteCodeParser::parse_value_type(std::istream &is) {
return parse<ValueType>(is);
}
 
} // namespace wasm
 
filename was Deleted added: 181, removed: 169, total 12
@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2023-2024 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "wasm/wasm.h"
 
#include <tl/expected.hpp>
 
#include <iosfwd>
#include <optional>
 
namespace wasm {
 
enum class ModuleParseError {
UnexpectedEof,
InvalidMagic,
UnsupportedVersion,
InvalidSectionId,
InvalidSize,
InvalidTypeSection,
InvalidFunctionSection,
InvalidTableSection,
InvalidMemorySection,
InvalidExportSection,
InvalidStartSection,
InvalidCodeSection,
UnhandledSection,
};
 
class ByteCodeParser {
public:
static tl::expected<Module, ModuleParseError> parse_module(std::istream &);
static tl::expected<Module, ModuleParseError> parse_module(std::istream &&is) { return parse_module(is); }
 
// TODO(robinlinden): Make private once instructions are parsed eagerly.
static std::optional<ValueType> parse_value_type(std::istream &);
};
 
} // namespace wasm
 
wasm/wasm_test.cpp added: 181, removed: 169, total 12
@@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "wasm/byte_code_parser.h"
 
#include "wasm/wasm.h"
 
#include "etest/etest.h"
@@ -21,6 +23,7 @@
using namespace std::literals;
 
using etest::expect_eq;
using wasm::ByteCodeParser;
 
namespace {
 
@@ -53,24 +56,24 @@ std::stringstream make_module_bytes(SectionId id, std::vector<std::uint8_t> cons
 
void export_section_tests() {
etest::test("export section, missing export count", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Export, {}));
auto module = ByteCodeParser::parse_module(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(SectionId::Export, {1}));
auto module = ByteCodeParser::parse_module(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(SectionId::Export, {0})).value();
auto module = ByteCodeParser::parse_module(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(SectionId::Export, content)).value();
auto module = ByteCodeParser::parse_module(make_module_bytes(SectionId::Export, content)).value();
expect_eq(module.export_section,
wasm::ExportSection{.exports{wasm::Export{"hi", wasm::Export::Type::Function, 5}}});
});
@@ -91,7 +94,7 @@ void export_section_tests() {
2,
};
 
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Export, content)).value();
auto module = ByteCodeParser::parse_module(make_module_bytes(SectionId::Export, content)).value();
expect_eq(module.export_section,
wasm::ExportSection{.exports{
wasm::Export{"hi", wasm::Export::Type::Function, 5},
@@ -100,74 +103,74 @@ void export_section_tests() {
});
 
etest::test("export section, missing name", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Export, {1, 2}));
auto module = ByteCodeParser::parse_module(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(SectionId::Export, {1, 1, 'a'}));
auto module = ByteCodeParser::parse_module(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(SectionId::Export, {1, 1, 'a', 1}));
auto module = ByteCodeParser::parse_module(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, missing start", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Start, {}));
auto module = ByteCodeParser::parse_module(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(SectionId::Start, {42})).value();
auto module = ByteCodeParser::parse_module(make_module_bytes(SectionId::Start, {42})).value();
expect_eq(module.start_section, wasm::StartSection{.start = 42});
});
}
 
void function_section_tests() {
etest::test("function section, missing data", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Function, {}));
auto module = ByteCodeParser::parse_module(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(SectionId::Function, {0})).value();
auto module = ByteCodeParser::parse_module(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(SectionId::Function, {1}));
auto module = ByteCodeParser::parse_module(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(SectionId::Function, {2, 9, 13})).value();
auto module = ByteCodeParser::parse_module(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, missing data", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Table, {}));
auto module = ByteCodeParser::parse_module(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(SectionId::Table, {0})).value();
auto module = ByteCodeParser::parse_module(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(SectionId::Table, {1}));
auto module = ByteCodeParser::parse_module(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(SectionId::Table, {1, kInt32Type}));
auto module = ByteCodeParser::parse_module(make_module_bytes(SectionId::Table, {1, kInt32Type}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidTableSection});
});
 
@@ -175,22 +178,23 @@ void table_section_tests() {
static constexpr std::uint8_t kExtRefType = 0x6f;
 
etest::test("table section, missing limits", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Table, {1, kFuncRefType}));
auto module = ByteCodeParser::parse_module(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(SectionId::Table, {1, kFuncRefType, 4}));
auto module = ByteCodeParser::parse_module(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(SectionId::Table, {1, kFuncRefType, 0}));
auto module = ByteCodeParser::parse_module(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(SectionId::Table, {1, kFuncRefType, 0, 42})).value();
auto module =
ByteCodeParser::parse_module(make_module_bytes(SectionId::Table, {1, kFuncRefType, 0, 42})).value();
expect_eq(module.table_section,
wasm::TableSection{.tables{
wasm::TableType{
@@ -201,13 +205,13 @@ void table_section_tests() {
});
 
etest::test("table section, missing max in limits", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Table, {1, kExtRefType, 1, 42}));
auto module = ByteCodeParser::parse_module(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(SectionId::Table, {1, kExtRefType, 1, 42, 42})).value();
ByteCodeParser::parse_module(make_module_bytes(SectionId::Table, {1, kExtRefType, 1, 42, 42})).value();
expect_eq(module.table_section,
wasm::TableSection{.tables{
wasm::TableType{
@@ -220,32 +224,32 @@ void table_section_tests() {
 
void memory_section_tests() {
etest::test("memory section, missing data", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Memory, {}));
auto module = ByteCodeParser::parse_module(make_module_bytes(SectionId::Memory, {}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidMemorySection});
});
 
etest::test("memory section, empty", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Memory, {0})).value();
auto module = ByteCodeParser::parse_module(make_module_bytes(SectionId::Memory, {0})).value();
expect_eq(module.memory_section, wasm::MemorySection{});
});
 
etest::test("memory section, missing limits", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Memory, {1}));
auto module = ByteCodeParser::parse_module(make_module_bytes(SectionId::Memory, {1}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidMemorySection});
});
 
etest::test("memory section, invalid has_max in limits", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Memory, {1, 4}));
auto module = ByteCodeParser::parse_module(make_module_bytes(SectionId::Memory, {1, 4}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidMemorySection});
});
 
etest::test("memory section, missing min in limits", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Memory, {1, 0}));
auto module = ByteCodeParser::parse_module(make_module_bytes(SectionId::Memory, {1, 0}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidMemorySection});
});
 
etest::test("memory section, only min", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Memory, {1, 0, 42})).value();
auto module = ByteCodeParser::parse_module(make_module_bytes(SectionId::Memory, {1, 0, 42})).value();
expect_eq(module.memory_section,
wasm::MemorySection{.memories{
wasm::MemType{.min = 42},
@@ -253,12 +257,12 @@ void memory_section_tests() {
});
 
etest::test("memory section, missing max in limits", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Memory, {1, 1, 42}));
auto module = ByteCodeParser::parse_module(make_module_bytes(SectionId::Memory, {1, 1, 42}));
expect_eq(module, tl::unexpected{wasm::ModuleParseError::InvalidMemorySection});
});
 
etest::test("memory section, min and max", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Memory, {1, 1, 42, 42})).value();
auto module = ByteCodeParser::parse_module(make_module_bytes(SectionId::Memory, {1, 1, 42, 42})).value();
expect_eq(module.memory_section,
wasm::MemorySection{.memories{
wasm::Limits{.min = 42, .max = 42},
@@ -266,7 +270,8 @@ void memory_section_tests() {
});
 
etest::test("memory section, two memories", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Memory, {2, 1, 4, 51, 1, 19, 84})).value();
auto module =
ByteCodeParser::parse_module(make_module_bytes(SectionId::Memory, {2, 1, 4, 51, 1, 19, 84})).value();
expect_eq(module.memory_section,
wasm::MemorySection{.memories{
wasm::Limits{.min = 4, .max = 51},
@@ -277,44 +282,44 @@ void memory_section_tests() {
 
void type_section_tests() {
etest::test("type section, missing type data", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Type, {}));
auto module = ByteCodeParser::parse_module(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(SectionId::Type, {0})).value();
auto module = ByteCodeParser::parse_module(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(SectionId::Type, {1}));
auto module = ByteCodeParser::parse_module(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(SectionId::Type, {1, 0x59}));
auto module = ByteCodeParser::parse_module(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(SectionId::Type, {1, 0x60, 0, 0})).value();
auto module = ByteCodeParser::parse_module(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(SectionId::Type, {1, 0x60, 1}));
auto module = ByteCodeParser::parse_module(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(SectionId::Type, {1, 0x60, 0, 1}));
auto module = ByteCodeParser::parse_module(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(
auto module = ByteCodeParser::parse_module(
make_module_bytes(
SectionId::Type, {2, 0x60, 0, 1, kInt32Byte, 0x60, 2, kInt32Byte, kInt32Byte, 1, kFloat64Byte}))
.value();
@@ -333,7 +338,7 @@ void type_section_tests() {
});
 
etest::test("type section, all types", [] {
auto module = wasm::Module::parse_from(
auto module = ByteCodeParser::parse_module(
make_module_bytes(SectionId::Type, {1, 0x60, 7, 0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x70, 0x6f, 0}))
.value();
 
@@ -356,44 +361,45 @@ void type_section_tests() {
});
 
etest::test("type section, invalid value type", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Type, {1, 0x60, 0, 1, 0x10}));
auto module = ByteCodeParser::parse_module(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, missing type data", [] {
auto module = wasm::Module::parse_from(make_module_bytes(SectionId::Code, {}));
auto module = ByteCodeParser::parse_module(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(SectionId::Code, {0})).value();
auto module = ByteCodeParser::parse_module(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(SectionId::Code, {1}));
auto module = ByteCodeParser::parse_module(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(SectionId::Code, {1, 1, 1}));
auto module = ByteCodeParser::parse_module(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(SectionId::Code, {1, 1, 1, 1}));
auto module = ByteCodeParser::parse_module(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(SectionId::Code, {1, 6, 1, 1, 0x7f, 4, 4}));
auto module = ByteCodeParser::parse_module(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(SectionId::Code, {1, 6, 1, 1, 0x7f, 4, 4, 4})).value();
auto module =
ByteCodeParser::parse_module(make_module_bytes(SectionId::Code, {1, 6, 1, 1, 0x7f, 4, 4, 4})).value();
 
wasm::CodeSection expected{.entries{
wasm::CodeEntry{
@@ -405,7 +411,7 @@ void code_section_tests() {
});
 
etest::test("code section, two entries", [] {
auto module = wasm::Module::parse_from(
auto module = ByteCodeParser::parse_module(
make_module_bytes(SectionId::Code, {2, 6, 1, 1, 0x7f, 4, 4, 4, 9, 2, 5, 0x7e, 6, 0x7d, 7, 8, 9, 10}))
.value();
 
@@ -429,40 +435,41 @@ void code_section_tests() {
int main() {
etest::test("invalid magic", [] {
auto wasm_bytes = std::stringstream{"hello"};
expect_eq(wasm::Module::parse_from(wasm_bytes), tl::unexpected{wasm::ModuleParseError::InvalidMagic});
expect_eq(ByteCodeParser::parse_module(wasm_bytes), tl::unexpected{wasm::ModuleParseError::InvalidMagic});
});
 
etest::test("unsupported version", [] {
auto wasm_bytes = std::stringstream{"\0asm\2\0\0\0"s};
expect_eq(wasm::Module::parse_from(wasm_bytes), tl::unexpected{wasm::ModuleParseError::UnsupportedVersion});
expect_eq(ByteCodeParser::parse_module(wasm_bytes), tl::unexpected{wasm::ModuleParseError::UnsupportedVersion});
});
 
// https://webassembly.github.io/spec/core/syntax/modules.html
// Each of the vectors – and thus the entire module – may be empty
etest::test("empty module", [] {
auto wasm_bytes = std::stringstream{"\0asm\1\0\0\0"s};
expect_eq(wasm::Module::parse_from(std::move(wasm_bytes)), wasm::Module{});
expect_eq(ByteCodeParser::parse_module(std::move(wasm_bytes)), wasm::Module{});
});
 
etest::test("invalid section id", [] {
auto wasm_bytes = std::stringstream{"\0asm\1\0\0\0\x0d"s};
expect_eq(wasm::Module::parse_from(std::move(wasm_bytes)),
expect_eq(ByteCodeParser::parse_module(std::move(wasm_bytes)),
tl::unexpected{wasm::ModuleParseError::InvalidSectionId});
});
 
etest::test("missing size", [] {
auto wasm_bytes = std::stringstream{"\0asm\1\0\0\0\0"s};
expect_eq(
wasm::Module::parse_from(std::move(wasm_bytes)), tl::unexpected{wasm::ModuleParseError::UnexpectedEof});
expect_eq(ByteCodeParser::parse_module(std::move(wasm_bytes)),
tl::unexpected{wasm::ModuleParseError::UnexpectedEof});
});
 
etest::test("invalid size", [] {
auto wasm_bytes = std::stringstream{"\0asm\1\0\0\0\0\x80\x80\x80\x80\x80\x80"s};
expect_eq(wasm::Module::parse_from(std::move(wasm_bytes)), tl::unexpected{wasm::ModuleParseError::InvalidSize});
expect_eq(ByteCodeParser::parse_module(std::move(wasm_bytes)),
tl::unexpected{wasm::ModuleParseError::InvalidSize});
});
 
etest::test("unhandled section", [] {
expect_eq(wasm::Module::parse_from(make_module_bytes(SectionId::Custom, {})),
expect_eq(ByteCodeParser::parse_module(make_module_bytes(SectionId::Custom, {})),
tl::unexpected{wasm::ModuleParseError::UnhandledSection});
});
 
 
wasm/instructions.cpp added: 181, removed: 169, total 12
@@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: 2023 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2023-2024 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2024 David Zero <zero-one@zer0-one.net>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "wasm/instructions.h"
 
#include "wasm/byte_code_parser.h"
#include "wasm/leb128.h"
#include "wasm/wasm.h"
 
#include <cstdint>
#include <iomanip>
@@ -44,7 +44,7 @@ std::optional<BlockType> BlockType::parse(std::istream &is) {
}
 
std::stringstream ss{std::string{static_cast<char>(type)}};
auto value_type = ValueType::parse(ss);
auto value_type = ByteCodeParser::parse_value_type(ss);
if (value_type) {
return BlockType{{*std::move(value_type)}};
}
 
wasm/wasm.h added: 181, removed: 169, total 12
@@ -6,13 +6,10 @@
#define WASM_WASM_H_
 
#include <cstdint>
#include <iosfwd>
#include <optional>
#include <string>
#include <vector>
 
#include <tl/expected.hpp>
 
namespace wasm {
 
// https://webassembly.github.io/spec/core/binary/modules.html#indices
@@ -36,8 +33,6 @@ struct ValueType {
ExternReference,
};
 
static std::optional<ValueType> parse(std::istream &);
 
Kind kind{};
 
[[nodiscard]] bool operator==(ValueType const &) const = default;
@@ -70,8 +65,6 @@ struct FunctionSection {
 
// https://webassembly.github.io/spec/core/binary/types.html#limits
struct Limits {
static std::optional<Limits> parse(std::istream &);
 
std::uint32_t min{};
std::optional<std::uint32_t> max{};
 
@@ -80,8 +73,6 @@ struct Limits {
 
// https://webassembly.github.io/spec/core/binary/types.html#table-types
struct TableType {
static std::optional<TableType> parse(std::istream &);
 
ValueType element_type{};
Limits limits{};
 
@@ -149,27 +140,8 @@ struct CodeSection {
[[nodiscard]] bool operator==(CodeSection const &) const = default;
};
 
enum class ModuleParseError {
UnexpectedEof,
InvalidMagic,
UnsupportedVersion,
InvalidSectionId,
InvalidSize,
InvalidTypeSection,
InvalidFunctionSection,
InvalidTableSection,
InvalidMemorySection,
InvalidExportSection,
InvalidStartSection,
InvalidCodeSection,
UnhandledSection,
};
 
// https://webassembly.github.io/spec/core/syntax/modules.html
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 &);
 
// TODO(robinlinden): custom_sections
std::optional<TypeSection> type_section{};
// TODO(robinlinden): import_section
 
wasm/wasm_example.cpp added: 181, removed: 169, total 12
@@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "wasm/byte_code_parser.h"
#include "wasm/instructions.h"
#include "wasm/wasm.h"
 
@@ -58,7 +59,7 @@ int main(int argc, char **argv) {
return 1;
}
 
auto module = wasm::Module::parse_from(fs);
auto module = wasm::ByteCodeParser::parse_module(fs);
if (!module) {
std::cerr << "Unable to parse " << argv[1] << " as a wasm module: "
<< static_cast<std::underlying_type_t<wasm::ModuleParseError>>(module.error()) << '\n';