Add import / export sections test (#6497)

This commit is contained in:
Olek
2026-03-19 12:46:58 -04:00
committed by GitHub
parent 9c25d18851
commit 27468ddbcf
3 changed files with 155 additions and 19 deletions

View File

@@ -32,6 +32,86 @@ hexToBytes(std::string const& hex)
return Bytes(ws.begin(), ws.end());
}
template <class IT, class T>
unsigned
uleb128(IT& it, T val)
{
unsigned count = 0;
do
{
std::uint8_t byte = val & 0x7f;
val >>= 7;
if (val)
byte |= 0x80;
*it++ = byte;
++count;
} while (val != 0);
return count;
}
template <class IT>
std::pair<std::uint64_t, unsigned>
uleb128(IT&& it)
{
static_assert(sizeof(*it) == 1, "invalid iterator type");
std::uint64_t val = 0;
std::uint64_t byte = 0;
unsigned shift = 0;
unsigned count = 0;
do
{
if (shift > sizeof(std::uint64_t) * 8 - 7)
return {0, 0};
byte = *it++;
val |= (byte & 0x7F) << shift;
shift += 7;
++count;
} while (byte >= 0x80);
return {val, count};
}
std::pair<unsigned, unsigned>
getSection(Bytes const& module, std::uint8_t n)
{
static std::uint8_t const hdr[] = {0x00, 0x61, 0x73, 0x6D};
static std::uint8_t const ver[] = {0x01, 0x00, 0x00, 0x00};
static std::uint8_t const lastSec = 12;
// sections:
// 0: "Custom", 1: "Type", 2: "Import", 3: "Function", 4: "Table", 5: "Memory", 6: "Global",
// 7: "Export", 8: "Start", 9: "Element", 10: "Code", 11: "Data", 12: "DataCount"
if (module.size() < sizeof(hdr) + sizeof(ver) + 2)
return {0, 0};
if (memcmp(module.data(), hdr, sizeof(hdr)) != 0)
return {0, 0};
if (memcmp(module.data() + sizeof(hdr), ver, sizeof(ver)) != 0)
return {0, 0};
unsigned pos = sizeof(hdr) + sizeof(ver); // sections start
for (; pos < module.size();)
{
auto const start = pos;
std::uint8_t const byte = module[pos++];
if (byte > lastSec)
return {0, 0};
auto [sz, cnt] = uleb128(module.cbegin() + pos);
if (!cnt)
return {0, 0};
if (pos + cnt + sz > module.size())
return {0, 0};
pos += cnt + sz;
if (byte == n)
return {start, pos};
}
return {0, 0};
}
std::optional<int32_t>
runFinishFunction(std::string const& code)
{
@@ -196,6 +276,71 @@ struct Wasm_test : public beast::unit_test::suite
env.close();
}
void
testImpExp()
{
testcase("Wasm import/export functions");
auto impExpWasm = hexToBytes(impExpHex);
using namespace test::jtx;
Env env{*this};
TestLedgerDataProvider hfs(env);
ImportVec imports;
WASM_IMPORT_FUNC2(imports, getLedgerSqn, "get_ledger_sqn", &hfs, 33);
WASM_IMPORT_FUNC2(imports, getParentLedgerHash, "get_parent_ledger_hash", &hfs, 60);
auto& engine = WasmEngine::instance();
// Test exp_func1() - should return 1
auto re = engine.run(impExpWasm, hfs, 1'000'000, "exp_func1", {}, imports, env.journal);
checkResult(re, 1, 30);
// Test exp_func2(5) - should return 2 * 5 = 10
re = engine.run(
impExpWasm, hfs, 1'000'000, "exp_func2", wasmParams(5), imports, env.journal);
checkResult(re, 10, 52);
// Test test_imports() - should call get_ledger_sqn and get_parent_ledger_hash
re = engine.run(impExpWasm, hfs, 1'000'000, "test_imports", {}, imports, env.journal);
// Should return the ledger sequence number (3 by default in test env)
checkResult(re, 3, 294);
// Test corrupted import/export sections - invert each byte and expect failure
testcase("Wasm import/export section corruption");
{
// Import section(#2): bytes [26, 79) - 53 bytes
// Export section(#7): bytes [90, 141) - 51 bytes
auto [importStart, importEnd] = getSection(impExpWasm, 2);
auto [exportStart, exportEnd] = getSection(impExpWasm, 7);
BEAST_EXPECTS(importStart == 26, std::to_string(importStart));
BEAST_EXPECTS(importEnd == 79, std::to_string(importEnd));
BEAST_EXPECTS(exportStart == 90, std::to_string(exportStart));
BEAST_EXPECTS(exportEnd == 141, std::to_string(exportEnd));
auto testInv = [&](unsigned i) {
auto corruptedWasm = impExpWasm;
corruptedWasm[i] = ~corruptedWasm[i]; // Invert byte
// Try to run any function - should fail due to corruption
auto result = engine.run(
corruptedWasm, hfs, 1'000'000, "exp_func1", {}, imports, env.journal);
BEAST_EXPECT(!result);
};
// Test each byte in import section
for (unsigned i = importStart; i < importEnd; ++i)
testInv(i);
// Test each byte in export section
for (unsigned i = exportStart; i < exportEnd; ++i)
testInv(i);
}
env.close();
}
void
testWasmFib()
{
@@ -723,8 +868,7 @@ struct Wasm_test : public beast::unit_test::suite
// i32.const 1001))
auto const wasmHex =
"0061736d01000000010c0260017f017f60027f7f017f03030200010503010001071a03066d656d6f727902"
"0005746573743100"
"0005746573743200010a0d02050041e8070b050041e9070b";
"00057465737431000005746573743200010a0d02050041e8070b050041e9070b";
auto const wasm = hexToBytes(wasmHex);
// good params, module is working properly
@@ -864,23 +1008,6 @@ struct Wasm_test : public beast::unit_test::suite
env.close();
}
template <class IT>
std::size_t
uleb128(IT& it, std::uint16_t val)
{
std::size_t count = 0;
do
{
std::uint8_t byte = val & 0x7f;
val >>= 7;
if (val)
byte |= 0x80;
*it++ = byte;
++count;
} while (val != 0);
return count;
}
void
testOpcodes()
{
@@ -1394,6 +1521,7 @@ struct Wasm_test : public beast::unit_test::suite
testWasmLib();
testBadWasm();
testWasmLedgerSqn();
testImpExp();
testWasmFib();

View File

@@ -8978,3 +8978,9 @@ extern std::string const opcReservedHex =
"01010101010101010101010101010101010101010101010101010101010101010101010101010101010101410b0b0b"
"0a010041000b04746573"
"74";
extern std::string const impExpHex =
"0061736d0100000001100360027f7f017f6000017f60017f017f02330203656e760e6765745f6c65646765725f7371"
"6e000003656e76166765745f706172656e745f6c65646765725f686173680000030403010201050301000107310406"
"6d656d6f72790200096578705f66756e63310002096578705f66756e633200030c746573745f696d706f7274730004"
"0a2b03040041010b0700200041026c0b1c01027f4120410410001a41202802002100410041201001210120000b";

View File

@@ -79,3 +79,5 @@ extern std::string const locals10kHex;
extern std::string const functions5kHex;
extern std::string const opcReservedHex;
extern std::string const impExpHex;