Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion bin/pre-commit/clang_tidy_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If git is not in PATH, subprocess.run raises FileNotFoundError. Wrap in try/except and fall back:

["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
Expand Down
142 changes: 138 additions & 4 deletions src/libxrpl/tx/wasm/WasmiVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <limits>
#include <memory>
#include <mutex>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -711,8 +712,8 @@ WasmiResult
WasmiEngine::call(FuncInfo const& f, std::vector<wasm_val_t>& 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();
Expand Down Expand Up @@ -799,6 +800,134 @@ WasmiEngine::run(
return Unexpected<TER>(tecFAILED_PROCESSING);
}

namespace {

struct CustomSection
{
std::string_view name;
std::span<char const> payload;
};

struct Version
{
std::string_view name;
std::string_view version;

Version(std::string_view theName, std::string_view theVersion)
: name{theName}, version{theVersion}
{
}
};

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++];
Comment thread
depthfirst-app[bot] marked this conversation as resolved.
result |= static_cast<uint32_t>(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;
Comment thread
TimothyBanks marked this conversation as resolved.
}
}
return result;
}

template <typename Filter>
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;
Comment thread
depthfirst-app[bot] marked this conversation as resolved.

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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Malformed section causes early abort, skipping version info. Skip only this section and continue:

Suggested change
return;
offset = nextSection; continue;

}

customSection.name =
std::string_view{reinterpret_cast<char const*>(wasmCode.data()) + offset, size};
Comment thread
depthfirst-app[bot] marked this conversation as resolved.
offset += size;

if (offset >= nextSection)
{
offset = nextSection;
continue;
}

size = nextSection - offset;
customSection.payload = std::span<char const>{
reinterpret_cast<char const*>(wasmCode.data()) + offset, size};

if (filter(customSection))
{
return;
}
}
offset = nextSection;
}
}

std::vector<Version>
extractVersionInfo(Bytes const& wasmCode)
{
static constexpr auto kCommonLib = "xrpl-common-stdlib-version";
static constexpr auto kEscrowLib = "xrpl-escrow-stdlib-version";

auto versions = std::vector<Version>{};
filterCustomSections(wasmCode, [&](auto const& section) {
if (section.name == kCommonLib || section.name == kEscrowLib)
{
versions.emplace_back(
section.name, 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<WasmResult<int32_t>, TER>
WasmiEngine::runHlp(
Bytes const& wasmCode,
Expand All @@ -818,6 +947,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";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant "\n" — Beast Journal appends a newline on destruction, producing a blank line.

Suggested change
j_.debug() << "Module version: " << version.name << " " << version.version << "\n";
j_.debug() << "Module version: " << version.name << " " << version.version;

}

// Create and instantiate the module.
[[maybe_unused]] int const m = addModule(wasmCode, true, imports, gas);

Expand Down Expand Up @@ -960,7 +1094,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
Expand Down
34 changes: 34 additions & 0 deletions src/test/app/Wasm_test.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#include <test/app/wasm_fixtures/fixtures.h>
#include <test/jtx/Env.h>
#include <test/unit_test/SuiteJournal.h>

#include <xrpl/basics/Expected.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/wasm/HostFunc.h>
Expand Down Expand Up @@ -227,6 +229,36 @@ 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<Add_proto>(imports, "func-add", reinterpret_cast<void*>(&add), &hfs);
Comment thread
TimothyBanks marked this conversation as resolved.

[[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-version 4.5.6") != std::string::npos);
BEAST_EXPECT(
logged.find("Module version: xrpl-common-stdlib-version 1.2.3") != std::string::npos);
}

void
testBadWasm()
{
Expand Down Expand Up @@ -1549,6 +1581,8 @@ struct Wasm_test : public beast::unit_test::Suite
{
using namespace test::jtx;

testVersion();

testGetDataHelperFunctions();
testWasmLib();
testBadWasm();
Expand Down
13 changes: 13 additions & 0 deletions src/test/app/wasm_fixtures/wat/custom_version.wat
Original file line number Diff line number Diff line change
@@ -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-version" "4.5.6")
(@custom "xrpl-common-stdlib-version" "1.2.3")
)
Loading