diff --git a/bin/pre-commit/clang_tidy_check.py b/bin/pre-commit/clang_tidy_check.py index 7fb51d1c460..f3c75033bb3 100755 --- a/bin/pre-commit/clang_tidy_check.py +++ b/bin/pre-commit/clang_tidy_check.py @@ -168,7 +168,19 @@ def main(): if not os.environ.get("TIDY"): return 0 - repo_root = Path(__file__).parent.parent + # Derive the repo root from git so this keeps working regardless of where + # under the tree this script lives. Fall back to the script location + # (bin/pre-commit/ is two levels below the repo root) if git is unavailable. + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, + text=True, + cwd=Path(__file__).parent, + ) + if result.returncode == 0: + repo_root = Path(result.stdout.strip()) + else: + repo_root = Path(__file__).parent.parent.parent files = staged_files(repo_root) if not files: return 0 diff --git a/src/libxrpl/tx/wasm/WasmiVM.cpp b/src/libxrpl/tx/wasm/WasmiVM.cpp index e22380741ba..42769a460be 100644 --- a/src/libxrpl/tx/wasm/WasmiVM.cpp +++ b/src/libxrpl/tx/wasm/WasmiVM.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -277,7 +278,7 @@ InstanceWrapper::setGas(std::int64_t gas) const ModulePtr ModuleWrapper::init(StorePtr& s, Bytes const& wasmBin, beast::Journal j) { - wasm_byte_vec_t const code{wasmBin.size(), (char*)(wasmBin.data())}; + wasm_byte_vec_t const code{.size = wasmBin.size(), .data = (char*)(wasmBin.data())}; ModulePtr m = ModulePtr(wasm_module_new(s.get(), &code), &wasm_module_delete); if (!m) throw std::runtime_error("can't create module"); @@ -711,8 +712,8 @@ WasmiResult WasmiEngine::call(FuncInfo const& f, std::vector& in) { WasmiResult ret(NR); - wasm_val_vec_t const inv = - in.empty() ? wasm_val_vec_t WASM_EMPTY_VEC : wasm_val_vec_t{in.size(), in.data()}; + wasm_val_vec_t const inv = in.empty() ? wasm_val_vec_t WASM_EMPTY_VEC + : wasm_val_vec_t{.size = in.size(), .data = in.data()}; #ifdef SHOW_CALL_TIME auto const start = usecs(); @@ -799,6 +800,131 @@ WasmiEngine::run( return Unexpected(tecFAILED_PROCESSING); } +namespace { + +struct CustomSection +{ + std::string_view name; + std::span payload; +}; + +struct Version +{ + std::string_view name; + std::string_view version; +}; + +uint32_t +readLEB128(Bytes const& wasmCode, size_t& offset) +{ + auto result = uint32_t{}; + auto shift = uint32_t{}; + while (offset < wasmCode.size()) + { + auto byte = wasmCode[offset++]; + result |= static_cast(byte & (shift < 28 ? 0x7Fu : 0x0Fu)) << shift; + if ((byte & 0x80) == 0) + { + break; + } + shift += 7; + if (shift >= 32) + { + // Drain the rest of the bytes for this leb. + while (offset < wasmCode.size()) + { + if ((wasmCode[offset++] & 0x80) == 0) + { + break; + } + } + break; + } + } + return result; +} + +template +void +filterCustomSections(Bytes const& wasmCode, Filter&& filter) +{ + auto offset = size_t{8}; // Skip Magic number and Version + + if (wasmCode.size() <= offset) + { + // There is nothing to parse. + return; + } + + while (offset < wasmCode.size()) + { + auto sectionId = wasmCode[offset++]; + auto sectionSize = readLEB128(wasmCode, offset); + auto nextSection = offset + sectionSize; + + if (nextSection > wasmCode.size()) + { + break; + } + + if (sectionId == 0) + { + // Wasm custom section marker. + auto customSection = CustomSection{}; + auto size = readLEB128(wasmCode, offset); + + if (offset + size > wasmCode.size() || offset + size > nextSection) + { + return; + } + + customSection.name = + std::string_view{reinterpret_cast(wasmCode.data()) + offset, size}; + offset += size; + + if (offset >= nextSection) + { + offset = nextSection; + continue; + } + + size = nextSection - offset; + customSection.payload = std::span{ + reinterpret_cast(wasmCode.data()) + offset, size}; + + if (filter(customSection)) + { + return; + } + } + offset = nextSection; + } +} + +std::vector +extractVersionInfo(Bytes const& wasmCode) +{ + static constexpr auto kCommonLib = "xrpl-common-stdlib"; + static constexpr auto kEscrowLib = "xrpl-escrow-stdlib"; + + auto versions = std::vector{}; + filterCustomSections(wasmCode, [&](auto const& section) { + if (section.name == kCommonLib || section.name == kEscrowLib) + { + versions.emplace_back( + Version{ + .name = section.name, + .version = std::string_view{section.payload.data(), section.payload.size()}}); + } + + // Just read until we have found all the information we are looking for. + return versions.size() == 2; + }); + return versions; +} + +} // namespace + Expected, TER> WasmiEngine::runHlp( Bytes const& wasmCode, @@ -818,6 +944,11 @@ WasmiEngine::runHlp( if (!hfs.checkSelf()) throw std::runtime_error("hfs isn't clean"); + for (auto const& version : extractVersionInfo(wasmCode)) + { + j_.debug() << "Module version: " << version.name << " " << version.version << "\n"; + } + // Create and instantiate the module. [[maybe_unused]] int const m = addModule(wasmCode, true, imports, gas); @@ -960,7 +1091,7 @@ wasm_trap_t* WasmiEngine::newTrap(std::string const& txt) { static char empty[1] = {0}; - wasm_message_t msg = {1, empty}; + wasm_message_t msg = {.size = 1, .data = empty}; if (!txt.empty()) wasm_name_new(&msg, txt.size() + 1, txt.c_str()); // include 0 diff --git a/src/test/app/Wasm_test.cpp b/src/test/app/Wasm_test.cpp index 537b8b2330e..07ee82c2c2b 100644 --- a/src/test/app/Wasm_test.cpp +++ b/src/test/app/Wasm_test.cpp @@ -1,8 +1,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -227,6 +229,34 @@ struct Wasm_test : public beast::unit_test::Suite checkResult(re, 6'912, 59); } + void + testVersion() + { + testcase("wasm module test"); + // wat2wasm --enable-annotations mymodule.wat -o mymodule.wasm + // xxd -p mymodule.wasm | tr -d '\n' + static auto const kWasmModule = hexToBytes( + "0061736d010000000105016000017f03020100040401700000070a010666696e69736800000a0601040041" + "010b0018127872706c2d657363726f772d7374646c6962342e352e360018127872706c2d636f6d6d6f6e2d" + "7374646c6962312e322e33"); + + StreamSink sink{beast::Severity::Debug}; + beast::Journal const journal{sink}; + + auto& vm = WasmEngine::instance(); + + auto hfs = HostFunctions{}; + auto imports = ImportVec{}; + WasmImpFunc(imports, "func-add", reinterpret_cast(&add), &hfs); + + [[maybe_unused]] auto result = + vm.run(kWasmModule, hfs, 10'000'000, "finish", wasmParams(), imports, journal); + + auto const logged = sink.messages().str(); + BEAST_EXPECT(logged.find("Module version: xrpl-escrow-stdlib 4.5.6") != std::string::npos); + BEAST_EXPECT(logged.find("Module version: xrpl-common-stdlib 1.2.3") != std::string::npos); + } + void testBadWasm() { @@ -1549,6 +1579,8 @@ struct Wasm_test : public beast::unit_test::Suite { using namespace test::jtx; + testVersion(); + testGetDataHelperFunctions(); testWasmLib(); testBadWasm(); diff --git a/src/test/app/wasm_fixtures/wat/custom_version.wat b/src/test/app/wasm_fixtures/wat/custom_version.wat new file mode 100644 index 00000000000..899a0b79589 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/custom_version.wat @@ -0,0 +1,13 @@ +(module + ;; Define a table with exactly 0 entries + (table 0 funcref) + + ;; Standard finish function + (func $finish (result i32) + i32.const 1 + ) + (export "finish" (func $finish)) + + (@custom "xrpl-escrow-stdlib" "4.5.6") + (@custom "xrpl-common-stdlib" "1.2.3") +)