srctree

Robin Linden parent e3d34bb2 6f3620c6
wasm: Improve module parsing error reporting

Now we report what went wrong instead of just returning a std::nullopt.

inlinesplit
wasm/BUILD added: 30, removed: 18, total 12
@@ -13,6 +13,7 @@ cc_library(
hdrs = glob(["*.h"]),
copts = HASTUR_COPTS,
visibility = ["//visibility:public"],
deps = ["@expected"],
)
 
[cc_test(
 
wasm/wasm.cpp added: 30, removed: 18, total 12
@@ -209,21 +209,21 @@ std::optional<std::string> get_section_data(std::vector<Section> const &sections
 
} // namespace
 
std::optional<Module> Module::parse_from(std::istream &is) {
tl::expected<Module, ParseError> Module::parse_from(std::istream &is) {
std::string buf;
 
// https://webassembly.github.io/spec/core/bikeshed/#binary-magic
buf.resize(kMagicSize);
is.read(buf.data(), buf.size());
if (!is || buf != "\0asm"sv) {
return std::nullopt;
return tl::unexpected{ParseError::InvalidMagic};
}
 
// https://webassembly.github.io/spec/core/bikeshed/#binary-version
buf.resize(kVersionSize);
is.read(buf.data(), buf.size());
if (!is || buf != "\1\0\0\0"sv) {
return std::nullopt;
return tl::unexpected{ParseError::UnsupportedVersion};
}
 
Module module;
@@ -238,19 +238,20 @@ std::optional<Module> Module::parse_from(std::istream &is) {
}
 
if (!(id >= static_cast<int>(SectionId::Custom) && id <= static_cast<int>(SectionId::DataCount))) {
return std::nullopt;
return tl::unexpected{ParseError::InvalidSectionId};
}
 
// TODO(robinlinden): Propagate error from leb128-parsing.
auto size = Leb128<std::uint32_t>::decode_from(is);
if (!size) {
return std::nullopt;
return tl::unexpected{ParseError::Unknown};
}
 
std::vector<std::uint8_t> content;
content.resize(*size);
is.read(reinterpret_cast<char *>(content.data()), *size);
if (!is) {
return std::nullopt;
return tl::unexpected{ParseError::UnexpectedEof};
}
 
module.sections.push_back(Section{static_cast<SectionId>(id), std::move(content)});
 
wasm/wasm.h added: 30, removed: 18, total 12
@@ -11,6 +11,8 @@
#include <string>
#include <vector>
 
#include <tl/expected.hpp>
 
namespace wasm {
 
// https://webassembly.github.io/spec/core/binary/modules.html#indices
@@ -119,10 +121,18 @@ struct CodeSection {
[[nodiscard]] bool operator==(CodeSection const &) const = default;
};
 
enum class ParseError {
Unknown,
UnexpectedEof,
InvalidMagic,
UnsupportedVersion,
InvalidSectionId,
};
 
// https://webassembly.github.io/spec/core/bikeshed/#modules
struct Module {
static std::optional<Module> parse_from(std::istream &&is) { return parse_from(is); }
static std::optional<Module> parse_from(std::istream &);
static tl::expected<Module, ParseError> parse_from(std::istream &&is) { return parse_from(is); }
static tl::expected<Module, ParseError> parse_from(std::istream &);
 
std::vector<Section> sections{};
 
 
wasm/wasm_test.cpp added: 30, removed: 18, total 12
@@ -343,12 +343,12 @@ void code_section_tests() {
int main() {
etest::test("invalid magic", [] {
auto wasm_bytes = std::stringstream{"hello"};
expect_eq(wasm::Module::parse_from(wasm_bytes), std::nullopt);
expect_eq(wasm::Module::parse_from(wasm_bytes), tl::unexpected{wasm::ParseError::InvalidMagic});
});
 
etest::test("unsupported version", [] {
auto wasm_bytes = std::stringstream{"\0asm\2\0\0\0"s};
expect_eq(wasm::Module::parse_from(wasm_bytes), std::nullopt);
expect_eq(wasm::Module::parse_from(wasm_bytes), tl::unexpected{wasm::ParseError::UnsupportedVersion});
});
 
// https://webassembly.github.io/spec/core/bikeshed/#modules
@@ -360,22 +360,22 @@ int main() {
 
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)), std::nullopt);
expect_eq(wasm::Module::parse_from(std::move(wasm_bytes)), tl::unexpected{wasm::ParseError::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)), std::nullopt);
expect_eq(wasm::Module::parse_from(std::move(wasm_bytes)), tl::unexpected{wasm::ParseError::Unknown});
});
 
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)), std::nullopt);
expect_eq(wasm::Module::parse_from(std::move(wasm_bytes)), tl::unexpected{wasm::ParseError::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)), std::nullopt);
expect_eq(wasm::Module::parse_from(std::move(wasm_bytes)), tl::unexpected{wasm::ParseError::UnexpectedEof});
});
 
etest::test("one valid section", [] {