diff --git a/.cspell.config.yaml b/.cspell.config.yaml index 4e4869260d..51dc955be4 100644 --- a/.cspell.config.yaml +++ b/.cspell.config.yaml @@ -218,6 +218,7 @@ words: - Nyffenegger - onlatest - ostr + - oxalica - pargs - partitioner - paychan @@ -343,6 +344,7 @@ words: - unsquelch - unsquelched - unsquelching + - unsuffixed - unvalidated - unveto - unvetoed diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f1f7aa18f7..95d75c04b4 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,10 +1,10 @@ @@ -15,7 +15,7 @@ https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your- Please include a summary of the changes. This may be a direct input to the release notes. If too broad, please consider splitting into multiple PRs. -If a relevant task or issue, please link it here. +If there is a relevant task or issue, please link it here. --> ### Context of Change @@ -65,5 +65,5 @@ This section may not be needed if your change includes thoroughly commented unit diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index 7edbf96ef6..992f314686 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -1,5 +1,5 @@ { - "image_tag": "sha-2e25435", + "image_tag": "sha-3122de8", "configs": { "ubuntu": [ { diff --git a/.github/workflows/build-nix-images.yml b/.github/workflows/build-nix-images.yml index da28b8db49..8574182a7e 100644 --- a/.github/workflows/build-nix-images.yml +++ b/.github/workflows/build-nix-images.yml @@ -8,6 +8,7 @@ on: - ".github/workflows/build-nix-images.yml" - "flake.nix" - "flake.lock" + - "rust-toolchain.toml" - "nix/**" - "!nix/docker/README.md" - "!nix/devshell.nix" @@ -18,6 +19,7 @@ on: - ".github/workflows/build-nix-images.yml" - "flake.nix" - "flake.lock" + - "rust-toolchain.toml" - "nix/**" - "!nix/docker/README.md" - "!nix/devshell.nix" @@ -54,7 +56,7 @@ jobs: base_image: debian:bookworm - name: rhel base_image: registry.access.redhat.com/ubi9/ubi:latest - uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@ee03d31bcc4501d7599dc1b1ecd7a34af582ad1c + uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32 with: image_name: xrpld/nix-${{ matrix.distro.name }} dockerfile: nix/docker/Dockerfile diff --git a/.github/workflows/build-packaging-images.yml b/.github/workflows/build-packaging-images.yml index e72ea876a7..43b276bdf1 100644 --- a/.github/workflows/build-packaging-images.yml +++ b/.github/workflows/build-packaging-images.yml @@ -38,7 +38,7 @@ jobs: base_image: debian:bookworm - name: rhel base_image: registry.access.redhat.com/ubi9/ubi:latest - uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@ee03d31bcc4501d7599dc1b1ecd7a34af582ad1c + uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32 with: image_name: xrpld/packaging-${{ matrix.distro.name }} dockerfile: package/Dockerfile diff --git a/.github/workflows/build-pre-commit-image.yml b/.github/workflows/build-pre-commit-image.yml index 1d0a479846..d0eba6b495 100644 --- a/.github/workflows/build-pre-commit-image.yml +++ b/.github/workflows/build-pre-commit-image.yml @@ -7,10 +7,12 @@ on: paths: - ".github/workflows/build-pre-commit-image.yml" - "bin/pre-commit/Dockerfile" + - "rust-toolchain.toml" pull_request: paths: - ".github/workflows/build-pre-commit-image.yml" - "bin/pre-commit/Dockerfile" + - "rust-toolchain.toml" workflow_dispatch: concurrency: @@ -28,7 +30,7 @@ jobs: permissions: contents: read packages: write - uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@ee03d31bcc4501d7599dc1b1ecd7a34af582ad1c + uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32 with: image_name: xrpld/pre-commit dockerfile: bin/pre-commit/Dockerfile diff --git a/.github/workflows/check-pr-title.yml b/.github/workflows/check-pr-title.yml index 4b5f679df1..cc80982440 100644 --- a/.github/workflows/check-pr-title.yml +++ b/.github/workflows/check-pr-title.yml @@ -20,4 +20,4 @@ on: jobs: check_title: if: ${{ github.event.pull_request.draft != true }} - uses: XRPLF/actions/.github/workflows/check-pr-title.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81 + uses: XRPLF/actions/.github/workflows/check-pr-title.yml@d7c65e49225a38f6d8010eacf017bb5a98d7476c diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 37207c41ef..ac5fe46722 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -14,7 +14,7 @@ on: jobs: # Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks. run-hooks: - uses: XRPLF/actions/.github/workflows/pre-commit.yml@1bde119a1ab71305ba5d3716e7a82cea1c7bdede + uses: XRPLF/actions/.github/workflows/pre-commit.yml@3ba08d6ddf114092891d48491fc2e26c3ba15552 with: runs_on: ubuntu-latest - container: '{ "image": "ghcr.io/xrplf/xrpld/pre-commit:sha-4c869ee" }' + container: '{ "image": "ghcr.io/xrplf/xrpld/pre-commit:sha-f56b79f" }' diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 90182e7cbb..19c73f93d6 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -41,13 +41,13 @@ env: jobs: build: runs-on: ubuntu-latest - container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-2e25435 + container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-3122de8 steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@ad188deb3dae79dc39816e16ddfdad1e06c6fab2 + uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606 with: enable_ccache: false diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index d4135207fe..6372bb6328 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -113,7 +113,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@ad188deb3dae79dc39816e16ddfdad1e06c6fab2 + uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606 with: enable_ccache: ${{ inputs.ccache_enabled }} @@ -223,11 +223,13 @@ jobs: BUILD_TYPE: ${{ inputs.build_type }} CMAKE_TARGET: ${{ inputs.cmake_target }} run: | + set -o pipefail cmake \ --build . \ --config "${BUILD_TYPE}" \ --parallel "${BUILD_NPROC}" \ - --target "${CMAKE_TARGET}" + --target "${CMAKE_TARGET}" \ + 2>&1 | tee "${GITHUB_WORKSPACE}/build.log" - name: Show ccache statistics if: ${{ inputs.ccache_enabled }} @@ -322,7 +324,7 @@ jobs: PRELOAD="" fi - LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log + LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee "${GITHUB_WORKSPACE}/unittest.log" # Smoke-run every benchmark module with a single repetition to confirm the # benchmarks still build and execute. This is a correctness check, not a @@ -341,25 +343,27 @@ jobs: done < <(find src/benchmarks -type f -perm -u+x -name 'xrpl.bench.*') exit "${rc}" - - name: Show test failure summary - if: ${{ failure() && !inputs.build_only }} - env: - WORKING_DIR: ${{ runner.os == 'Windows' && format('{0}\{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }} + - name: Show build/test failure summary + if: ${{ failure() }} run: | - if [ ! -d "${WORKING_DIR}" ]; then - echo "Working directory '${WORKING_DIR}' does not exist." - exit 0 - fi + cd "${GITHUB_WORKSPACE}" - cd "${WORKING_DIR}" - - if [ ! -f unittest.log ]; then - echo "unittest.log not found; embedded tests may not have run." - exit 0 - fi - - if ! grep -E "failed" unittest.log; then - echo "Log present but no failure lines found in unittest.log." + if [ -f unittest.log ]; then + if ! grep -E "failed" unittest.log | grep -vE "^I[0-9]|^[0-9]+> (ERR:|FTL:)"; then + echo "unittest.log present but no failure lines found." + fi + elif [ -f build.log ]; then + # GCC/Clang emit "error:" (covers "fatal error:"); MSVC emits + # "error C####:", "error LNK####:", and "fatal error LNK####:". + # -A6 prints the lines that follow each match (source line, caret, + # notes, and the "N errors generated" tally) to capture the whole + # diagnostic block. + if ! grep -E -A6 "error:|error C[0-9]{4}|error LNK[0-9]{4}|fatal error" build.log; then + echo "build.log present but no compile errors found." + fi + else + echo "unittest.log/build.log not found; something went wrong." + exit 1 fi - name: Debug failure (Linux) if: ${{ failure() && runner.os == 'Linux' && !inputs.build_only }} diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index 4a10e4b0f7..e81bbea367 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -34,7 +34,7 @@ jobs: needs: [determine-files] if: ${{ needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.need_full_run == 'true' }} runs-on: ["self-hosted", "Linux", "X64", "heavy"] - container: "ghcr.io/xrplf/xrpld/nix-debian:sha-2e25435" + container: "ghcr.io/xrplf/xrpld/nix-debian:sha-3122de8" permissions: contents: read issues: write @@ -43,7 +43,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@ad188deb3dae79dc39816e16ddfdad1e06c6fab2 + uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606 with: enable_ccache: false diff --git a/.github/workflows/reusable-package.yml b/.github/workflows/reusable-package.yml index 6feecbfb75..55bc20dc5c 100644 --- a/.github/workflows/reusable-package.yml +++ b/.github/workflows/reusable-package.yml @@ -30,7 +30,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.13" diff --git a/.github/workflows/reusable-strategy-matrix.yml b/.github/workflows/reusable-strategy-matrix.yml index b6091b99d9..de8d9cfc8e 100644 --- a/.github/workflows/reusable-strategy-matrix.yml +++ b/.github/workflows/reusable-strategy-matrix.yml @@ -26,7 +26,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.13" diff --git a/.github/workflows/reusable-upload-recipe.yml b/.github/workflows/reusable-upload-recipe.yml index 07077163ed..df4a2d9516 100644 --- a/.github/workflows/reusable-upload-recipe.yml +++ b/.github/workflows/reusable-upload-recipe.yml @@ -40,7 +40,7 @@ defaults: jobs: upload: runs-on: ubuntu-latest - container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-2e25435 + container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-3122de8 env: REMOTE_NAME: ${{ inputs.remote_name }} CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }} diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index abc0867b15..8a02c4c2db 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -68,7 +68,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@ad188deb3dae79dc39816e16ddfdad1e06c6fab2 + uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606 with: enable_ccache: false diff --git a/.gitignore b/.gitignore index 6bd34ece04..13b59a7e2c 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,9 @@ DerivedData # Python __pycache__ +# Rust build artifacts. +target/ + # Direnv's directory /.direnv diff --git a/bin/pre-commit/Dockerfile b/bin/pre-commit/Dockerfile index fcd07146c3..a96f3e4c25 100644 --- a/bin/pre-commit/Dockerfile +++ b/bin/pre-commit/Dockerfile @@ -10,6 +10,7 @@ ARG DEBIAN_FRONTEND=noninteractive RUN <::type; - if constexpr (std::is_pointer_v) - { - e.params.push_back(WasmTypes::WtI32); - } - else if constexpr (std::is_same_v) + if constexpr (std::is_pointer_v || std::is_same_v) { e.params.push_back(WasmTypes::WtI32); } @@ -73,11 +69,7 @@ template void WasmImpRet(WasmImportFunc& e) { - if constexpr (std::is_pointer_v) - { - e.result = WasmTypes::WtI32; - } - else if constexpr (std::is_same_v) + if constexpr (std::is_pointer_v || std::is_same_v) { e.result = WasmTypes::WtI32; } diff --git a/include/xrpl/tx/wasm/WasmiVM.h b/include/xrpl/tx/wasm/WasmiVM.h index d4a026ff54..5a72cd35f6 100644 --- a/include/xrpl/tx/wasm/WasmiVM.h +++ b/include/xrpl/tx/wasm/WasmiVM.h @@ -264,7 +264,7 @@ public: return instanceWrap_.getFunc(funcName, exportTypes_); } - wasm_functype_t* + wasm_functype_t const* getFuncType(std::string_view funcName) const; Wmem diff --git a/nix/ci-env.nix b/nix/ci-env.nix index 9b754af97d..63bddb46d8 100644 --- a/nix/ci-env.nix +++ b/nix/ci-env.nix @@ -7,8 +7,10 @@ let inherit (import ./packages.nix { inherit pkgs; }) commonPackages gccPackage + gccVersion llvmPackages llvmVersion + mkVersionedToolLinks ; # Underlying compiler toolchains to wrap (versions pinned in packages.nix). @@ -127,6 +129,25 @@ in customGcov customClangForCiEnv customBinutils + (mkVersionedToolLinks { + name = "gcc"; + package = customGcc; + version = gccVersion; + tools = [ + "gcc" + "g++" + "cpp" + ]; + }) + (mkVersionedToolLinks { + name = "clang"; + package = customClang; + version = llvmVersion; + tools = [ + "clang" + "clang++" + ]; + }) # CA certificate bundle so HTTPS clients (git, curl, conan) can verify # TLS connections without ca-certificates being installed in the system. pkgs.cacert diff --git a/nix/devshell.nix b/nix/devshell.nix index 34f173ef08..1316fe4234 100644 --- a/nix/devshell.nix +++ b/nix/devshell.nix @@ -3,7 +3,9 @@ let inherit (import ./packages.nix { inherit pkgs; }) commonPackages gccVersion + llvmVersion llvmPackages + mkVersionedToolLinks ; # Plain nixpkgs stdenvs — no custom glibc, unlike ci-env.nix. @@ -15,6 +17,8 @@ let { stdenv, compilerName, + version ? null, + versionedTools ? [ ], }: let compilerVersion = @@ -25,9 +29,15 @@ let echo "Compiler: " ${compilerName} --version ''; + versionedLinks = pkgs.lib.optional (version != null) (mkVersionedToolLinks { + name = compilerName; + package = stdenv.cc; + inherit version; + tools = versionedTools; + }); in (pkgs.mkShell.override { inherit stdenv; }) { - packages = commonPackages; + packages = commonPackages ++ versionedLinks; shellHook = '' echo "Welcome to xrpld development shell"; ${compilerVersion} @@ -41,11 +51,22 @@ rec { gcc = makeShell { stdenv = gccStdenv; compilerName = "gcc"; + version = gccVersion; + versionedTools = [ + "gcc" + "g++" + "cpp" + ]; }; clang = makeShell { stdenv = clangStdenv; compilerName = "clang"; + version = llvmVersion; + versionedTools = [ + "clang" + "clang++" + ]; }; # Nix provides no compiler; use the one from your system (e.g. Apple Clang). diff --git a/nix/docker/Dockerfile b/nix/docker/Dockerfile index 7222cc8fa8..8b851fd9e0 100644 --- a/nix/docker/Dockerfile +++ b/nix/docker/Dockerfile @@ -12,6 +12,7 @@ COPY nix/packages.nix /tmp/build/nix/packages.nix COPY nix/utils.nix /tmp/build/nix/utils.nix COPY flake.nix /tmp/build/ COPY flake.lock /tmp/build/ +COPY rust-toolchain.toml /tmp/build/ WORKDIR /tmp/build FROM builder-source AS builder diff --git a/nix/docker/README.md b/nix/docker/README.md index 23ad05049e..7b4d4b9387 100644 --- a/nix/docker/README.md +++ b/nix/docker/README.md @@ -47,7 +47,9 @@ work without `ca-certificates` being installed in the base image. [`test_files/cpp/sources/`](./test_files/cpp/sources) with both `g++` and `clang++`, and sanitizers, and - compiles the Rust test programs in - [`test_files/rust/sources/`](./test_files/rust/sources) with `rustc`. + [`test_files/rust/sources/`](./test_files/rust/sources) with `rustc`, and + builds the [`test_files/rust/proc_macro/`](./test_files/rust/proc_macro) + workspace with `cargo` to exercise proc-macro dylib loading. 3. **`tester`** — Start again from a clean `BASE_IMAGE` (no Nix toolchain), install only the sanitizer runtime libraries ([`install-sanitizer-libs.sh`](./install-sanitizer-libs.sh)), and run the @@ -76,20 +78,23 @@ toolchain being present at runtime. Two pieces make that work: [`loader-path.sh`](./loader-path.sh) reports the expected loader path for the current architecture, so we can patch the binaries to use the correct loader. -The build then verifies all of this end to end: the C++ test programs in -`test_files/cpp/sources/` (a regular binary plus ASan/TSan/UBSan variants) and -the Rust test programs in `test_files/rust/sources/` (a hello binary plus panic -and overflow-check variants) are compiled in `final`, their `PT_INTERP` is -patched to the target loader, and they are run in the clean `tester` stage to -confirm each emits the expected diagnostic on a stock base image. +The build then verifies all of this end to end, and the C++ and Rust programs +go through the same pipeline: each is compiled in `final`, has its `PT_INTERP` +patched to the target loader, and is then run in the clean `tester` stage to +confirm it emits the expected diagnostic on a stock base image. The C++ programs +are in `test_files/cpp/sources/` (a regular binary plus ASan/TSan/UBSan +variants); the Rust programs are in `test_files/rust/sources/` (a hello binary +plus panic and overflow-check variants), plus the `test_files/rust/proc_macro/` +workspace — a crate whose compilation additionally loads a proc-macro dylib, and +whose resulting binary is patched and run like the others. ## Files -| File | Purpose | -| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- | -| [`./Dockerfile`](./Dockerfile) | Multi-stage build described above. | -| [`./loader-path.sh`](./loader-path.sh) | Print the dynamic-linker (`PT_INTERP`) path for the current architecture. | -| [`./test_files/cpp/`](./test_files/cpp) | C++ sanitizer smoke test: sources + compile/run scripts. | -| [`./test_files/rust/`](./test_files/rust) | Rust rustc smoke test: sources + compile/run scripts. | -| [`/bin/check-tools.sh`](../../bin/check-tools.sh) | Verify every expected tools are present and runnable. | -| [`/bin/install-sanitizer-libs.sh`](../../bin/install-sanitizer-libs.sh) | Install `libasan`/`libtsan`/`libubsan` runtimes on the supported base images. | +| File | Purpose | +| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| [`./Dockerfile`](./Dockerfile) | Multi-stage build described above. | +| [`./loader-path.sh`](./loader-path.sh) | Print the dynamic-linker (`PT_INTERP`) path for the current architecture. | +| [`./test_files/cpp/`](./test_files/cpp) | C++ sanitizer smoke test: sources + compile/run scripts. | +| [`./test_files/rust/`](./test_files/rust) | Rust smoke test: rustc sources + a cargo proc-macro workspace + compile/run scripts. | +| [`/bin/check-tools.sh`](../../bin/check-tools.sh) | Verify every expected tools are present and runnable. | +| [`/bin/install-sanitizer-libs.sh`](../../bin/install-sanitizer-libs.sh) | Install `libasan`/`libtsan`/`libubsan` runtimes on the supported base images. | diff --git a/nix/docker/test_files/rust/compile-sources.sh b/nix/docker/test_files/rust/compile-sources.sh index 5e5ecc0dcb..106855a3ab 100755 --- a/nix/docker/test_files/rust/compile-sources.sh +++ b/nix/docker/test_files/rust/compile-sources.sh @@ -40,6 +40,29 @@ compile hello compile panic compile overflow "-C overflow-checks=on" +function compile_proc_macro() { + local proj="${src_dir}/../proc_macro" + + echo "=== Building proc-macro workspace (cargo) ===" + cargo build --manifest-path "${proj}/Cargo.toml" --offline + + local built="${proj}/target/debug/test_macro" + if [ ! -f "${built}" ]; then + echo "ERROR: built test_macro binary not found at ${built}" >&2 + exit 1 + fi + + local binary="${dst_dir}/proc_macro" + cp "${built}" "${binary}" + + echo "=== Patching ${binary} to use ${loader} as PT_INTERP ===" + patchelf --set-interpreter "${loader}" --remove-rpath "${binary}" + + rm -rf "${proj}/target" +} + +compile_proc_macro + echo "=== All binaries compiled ===" ls -la "${dst_dir}" diff --git a/nix/docker/test_files/rust/proc_macro/Cargo.lock b/nix/docker/test_files/rust/proc_macro/Cargo.lock new file mode 100644 index 0000000000..acab3fa0b9 --- /dev/null +++ b/nix/docker/test_files/rust/proc_macro/Cargo.lock @@ -0,0 +1,14 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "echo_macro" +version = "0.0.0" + +[[package]] +name = "test_macro" +version = "0.0.0" +dependencies = [ + "echo_macro", +] diff --git a/nix/docker/test_files/rust/proc_macro/Cargo.toml b/nix/docker/test_files/rust/proc_macro/Cargo.toml new file mode 100644 index 0000000000..d54955de3d --- /dev/null +++ b/nix/docker/test_files/rust/proc_macro/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = ["echo_macro", "test_macro"] diff --git a/nix/docker/test_files/rust/proc_macro/echo_macro/Cargo.toml b/nix/docker/test_files/rust/proc_macro/echo_macro/Cargo.toml new file mode 100644 index 0000000000..b0f92ce75a --- /dev/null +++ b/nix/docker/test_files/rust/proc_macro/echo_macro/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "echo_macro" +version = "0.0.0" +edition = "2024" +publish = false + +[lib] +proc-macro = true diff --git a/nix/docker/test_files/rust/proc_macro/echo_macro/src/lib.rs b/nix/docker/test_files/rust/proc_macro/echo_macro/src/lib.rs new file mode 100644 index 0000000000..e4b90d58fe --- /dev/null +++ b/nix/docker/test_files/rust/proc_macro/echo_macro/src/lib.rs @@ -0,0 +1,6 @@ +use proc_macro::TokenStream; + +#[proc_macro] +pub fn define_echo(item: TokenStream) -> TokenStream { + format!("fn echo() -> u32 {{ {item} }}").parse().unwrap() +} diff --git a/nix/docker/test_files/rust/proc_macro/test_macro/Cargo.toml b/nix/docker/test_files/rust/proc_macro/test_macro/Cargo.toml new file mode 100644 index 0000000000..25b6ba8e45 --- /dev/null +++ b/nix/docker/test_files/rust/proc_macro/test_macro/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "test_macro" +version = "0.0.0" +edition = "2024" +publish = false + +[dependencies] +echo_macro = { path = "../echo_macro" } diff --git a/nix/docker/test_files/rust/proc_macro/test_macro/src/main.rs b/nix/docker/test_files/rust/proc_macro/test_macro/src/main.rs new file mode 100644 index 0000000000..77718b9d75 --- /dev/null +++ b/nix/docker/test_files/rust/proc_macro/test_macro/src/main.rs @@ -0,0 +1,9 @@ +use echo_macro::define_echo; + +define_echo!(42); + +fn main() { + let a = echo(); + println!("proc-macro answer = {a}"); + assert_eq!(a, 42, "proc-macro expansion produced the wrong value"); +} diff --git a/nix/docker/test_files/rust/run-binaries.sh b/nix/docker/test_files/rust/run-binaries.sh index b627c12609..4cafee00ec 100755 --- a/nix/docker/test_files/rust/run-binaries.sh +++ b/nix/docker/test_files/rust/run-binaries.sh @@ -1,7 +1,7 @@ #!/bin/bash # Run pre-compiled Rust binaries and confirm each emits its expected diagnostic. # Binaries must already exist in as for name in -# {hello,panic,overflow}. +# {hello,panic,overflow,proc_macro}. set -eo pipefail @@ -54,12 +54,13 @@ declare -A expect=( [hello]="Hello from main thread" [panic]="explicit panic from test" [overflow]="attempt to add with overflow" + [proc_macro]="proc-macro answer = 42" ) -for name in hello panic overflow; do +for name in hello panic overflow proc_macro; do binary="${bins_dir}/${name}" - if [ "${name}" = "hello" ]; then + if [ "${name}" = "hello" ] || [ "${name}" = "proc_macro" ]; then expected_rc=0 else expected_rc=nonzero diff --git a/nix/packages.nix b/nix/packages.nix index 41d7e97328..1d9b6cd2f8 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -15,6 +15,55 @@ let runClangTidy = pkgs.writeShellScriptBin "run-clang-tidy" '' exec ${pkgs.python3}/bin/python3 ${llvmPackages.clang-unwrapped}/bin/run-clang-tidy "$@" ''; + + rustToolchain = pkgs.rust-bin.fromRustupToolchainFile ../rust-toolchain.toml; + + # Nix wraps its toolchain so that binaries are exposed only under unsuffixed + # names (gcc, g++, clang-tidy, ...). Several tools probe for a + # version-suffixed name first and fall back to a system binary on the PATH + # when Nix doesn't provide it: + # - Conan's Boost recipe looks up `g++-` before plain `g++`. + # - bin/pre-commit/clang_tidy_check.py looks up `run-clang-tidy-` and + # `clang-apply-replacements-` before the unsuffixed names. + # On a host that also has the matching system binary (e.g. Ubuntu's + # `/usr/bin/g++-15` or `clang-tidy-22`) the probe escapes Nix and mixes a + # system tool into the Nix environment. Generate version-suffixed symlinks + # next to a package's tools so those probes resolve to the Nix ones. + # + # Compiler links must point at whichever compiler is active in a given + # environment (the plain stdenv compiler in the dev shell, the custom-glibc + # wrappers in ci-env.nix), so those callers pass their own `package`; the + # clang tooling is environment-independent and is linked in commonPackages. + mkVersionedToolLinks = + { + name, + package, + version, + tools, + }: + pkgs.linkFarm "${name}-${toString version}-versioned-links" ( + map (tool: { + name = "bin/${tool}-${toString version}"; + path = "${package}/bin/${tool}"; + }) tools + ); + + clangToolLinks = mkVersionedToolLinks { + name = "clang-tools"; + package = clangTools; + version = llvmVersion; + tools = [ + "clang-tidy" + "clang-apply-replacements" + "clang-format" + ]; + }; + runClangTidyLink = mkVersionedToolLinks { + name = "run-clang-tidy"; + package = runClangTidy; + version = llvmVersion; + tools = [ "run-clang-tidy" ]; + }; in { inherit @@ -22,9 +71,12 @@ in llvmVersion gccPackage llvmPackages + mkVersionedToolLinks ; commonPackages = with pkgs; [ + clangToolLinks + runClangTidyLink ccache clangbuildanalyzer clangTools @@ -63,14 +115,10 @@ in vim zip # Rust packages - cargo cargo-audit cargo-llvm-cov cargo-nextest - clippy corrosion - rust-analyzer - rustc - rustfmt + rustToolchain ]; } diff --git a/nix/utils.nix b/nix/utils.nix index d83e612c16..0b70183ef3 100644 --- a/nix/utils.nix +++ b/nix/utils.nix @@ -1,4 +1,8 @@ -{ nixpkgs, nixpkgs-custom-glibc }: +{ + nixpkgs, + nixpkgs-custom-glibc, + rust-overlay, +}: function: nixpkgs.lib.genAttrs [ @@ -10,7 +14,12 @@ nixpkgs.lib.genAttrs ( system: function { - pkgs = import nixpkgs { inherit system; }; + # rust-overlay adds `pkgs.rust-bin`, from which we build the pinned Rust + # toolchain (see packages.nix). Consumed by both the CI image and dev shell. + pkgs = import nixpkgs { + inherit system; + overlays = [ (import rust-overlay) ]; + }; # glibc 2.31 — matches the system libc on Ubuntu 20.04 LTS. Sourced # from the nixpkgs snapshot pinned via the `nixpkgs-custom-glibc` # flake input, so the build uses the compiler from that snapshot diff --git a/rust-toolchain.toml b/rust-toolchain.toml index dbc9e74c5d..3206cf59c0 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,8 +1,4 @@ -# Rust toolchain pin for rustup-based CI runners and local development. -# rustup reads this file and installs the pinned toolchain (see the -# prepare-runner action in XRPLF/actions, which runs `rustup toolchain install`). -# NOTE: the Nix CI image and development shell ignore this file; its rustc comes from flake.lock. [toolchain] channel = "1.95" -components = ["rustfmt", "clippy"] +components = ["rustfmt", "clippy", "rust-analyzer"] profile = "minimal" diff --git a/src/libxrpl/tx/wasm/WasmiVM.cpp b/src/libxrpl/tx/wasm/WasmiVM.cpp index a174124208..cfe54fccc2 100644 --- a/src/libxrpl/tx/wasm/WasmiVM.cpp +++ b/src/libxrpl/tx/wasm/WasmiVM.cpp @@ -311,7 +311,10 @@ InstanceWrapper::setTransferLimit(std::int64_t x) ModulePtr ModuleWrapper::init(StorePtr& s, Bytes const& wasmBin, beast::Journal j) { - wasm_byte_vec_t const code{.size = wasmBin.size(), .data = (char*)(wasmBin.data())}; + wasm_byte_vec_t const code{ + .size = wasmBin.size(), + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + .data = const_cast(reinterpret_cast(wasmBin.data()))}; ModulePtr m = ModulePtr(wasm_module_new(s.get(), &code), &wasm_module_delete); if (!m) throw std::runtime_error("can't create module"); @@ -482,7 +485,7 @@ ModuleWrapper::buildImports(StorePtr& s, ImportVec const& imports) const return wimports; } -wasm_functype_t* +wasm_functype_t const* ModuleWrapper::getFuncType(std::string_view funcName) const { for (size_t i = 0; i < exportTypes_.size(); i++) @@ -493,7 +496,7 @@ ModuleWrapper::getFuncType(std::string_view funcName) const if (wasm_externtype_kind(exnType) == WASM_EXTERN_FUNC && funcName == std::string_view(name->data, name->size)) { - return wasm_externtype_as_functype(const_cast(exnType)); + return wasm_externtype_as_functype_const(exnType); } } diff --git a/src/test/app/HostFuncImpl_test.cpp b/src/test/app/HostFuncImpl_test.cpp index 378d6a0947..8311aae638 100644 --- a/src/test/app/HostFuncImpl_test.cpp +++ b/src/test/app/HostFuncImpl_test.cpp @@ -3452,7 +3452,7 @@ struct HostFuncImpl_test : public beast::unit_test::Suite BEAST_EXPECT(result[0].of.i32 == 0)) { auto const messages = sink.messages().str(); - BEAST_EXPECT(messages.find(msg) != std::string::npos); + BEAST_EXPECT(messages.contains(msg)); } } @@ -3471,8 +3471,8 @@ struct HostFuncImpl_test : public beast::unit_test::Suite std::string hex; hex.reserve(data.size() * 2); boost::algorithm::hex(data.begin(), data.end(), std::back_inserter(hex)); - BEAST_EXPECT(messages.find(msg) != std::string::npos); - BEAST_EXPECT(messages.find(hex) != std::string::npos); + BEAST_EXPECT(messages.contains(msg)); + BEAST_EXPECT(messages.contains(hex)); } } } @@ -3542,8 +3542,8 @@ struct HostFuncImpl_test : public beast::unit_test::Suite BEAST_EXPECT(result[0].of.i32 == 0)) { auto const messages = sink.messages().str(); - BEAST_EXPECT(messages.find(msg) != std::string::npos); - BEAST_EXPECT(messages.find(std::to_string(num)) != std::string::npos); + BEAST_EXPECT(messages.contains(msg)); + BEAST_EXPECT(messages.contains(std::to_string(num))); } } @@ -3611,8 +3611,8 @@ struct HostFuncImpl_test : public beast::unit_test::Suite BEAST_EXPECT(result[0].of.i32 == 0)) { auto const messages = sink.messages().str(); - BEAST_EXPECT(messages.find(msg) != std::string::npos); - BEAST_EXPECT(messages.find(env.master.human()) != std::string::npos); + BEAST_EXPECT(messages.contains(msg)); + BEAST_EXPECT(messages.contains(env.master.human())); } } @@ -3689,8 +3689,8 @@ struct HostFuncImpl_test : public beast::unit_test::Suite BEAST_EXPECT(result[0].of.i32 == 0)) { auto const messages = sink.messages().str(); - BEAST_EXPECT(messages.find(msg) != std::string::npos); - BEAST_EXPECT(messages.find(amount.getFullText()) != std::string::npos); + BEAST_EXPECT(messages.contains(msg)); + BEAST_EXPECT(messages.contains(amount.getFullText())); } } @@ -6176,7 +6176,10 @@ struct HostFuncImpl_test : public beast::unit_test::Suite // trace() uses getDataString() -> getDataSlice() which does NOT check transfer limit std::string testMsg = "This message is longer than 10 bytes to prove slices don't count"; vrt.setBytes(0, testMsg.data(), testMsg.size()); - vrt.setBytes(100, (uint8_t const*)"dummy", 5); // Empty data slice for trace + vrt.setBytes( + 100, + reinterpret_cast("dummy"), + 5); // Empty data slice for trace { WasmValVec params(5), result(1); // trace(msg_ptr, msg_len, data_ptr, data_len, asHex) diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 076e39a42b..eaf1f2704c 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -101,6 +102,12 @@ class Invariants_test : public beast::unit_test::Suite return xrpl::test::jtx::testableAmendments() | fixCleanup3_1_3 | fixCleanup3_2_0; } + test::jtx::Env + makeEnv(FeatureBitset features) + { + return {*this, test::jtx::envconfig(), features, nullptr, beast::Severity::Disabled}; + } + /** * Run a specific test case to put the ledger into a state that will be * detected by an invariant. Simulates the actions of a transaction that @@ -130,7 +137,7 @@ class Invariants_test : public beast::unit_test::Suite TxAccount setTxAccount = TxAccount::None) { doInvariantCheck( - test::jtx::Env(*this, defaultAmendments()), + makeEnv(defaultAmendments()), expectLogs, precheck, fee, @@ -1494,7 +1501,7 @@ class Invariants_test : public beast::unit_test::Suite testcase << "PermissionedDomain" + std::string(fixEnabled ? " fix" : ""); doInvariantCheck( - Env(*this, features), + makeEnv(features), {{"permissioned domain with no rules."}}, [](Account const& a1, Account const& a2, ApplyContext& ac) { return createPermissionedDomain(ac, a1, a2, 0).get(); @@ -1507,7 +1514,7 @@ class Invariants_test : public beast::unit_test::Suite static constexpr auto kTooBig = kMaxPermissionedDomainCredentialsArraySize + 1; doInvariantCheck( - Env(*this, features), + makeEnv(features), {{"permissioned domain bad credentials size " + std::to_string(kTooBig)}}, [](Account const& a1, Account const& a2, ApplyContext& ac) { return !!createPermissionedDomain(ac, a1, a2, kTooBig); @@ -1518,7 +1525,7 @@ class Invariants_test : public beast::unit_test::Suite testcase << "PermissionedDomain 3"; doInvariantCheck( - Env(*this, features), + makeEnv(features), {{"permissioned domain credentials aren't sorted"}}, [](Account const& a1, Account const& a2, ApplyContext& ac) { auto slePd = createPermissionedDomain(ac, a1, a2, 0); @@ -1542,7 +1549,7 @@ class Invariants_test : public beast::unit_test::Suite testcase << "PermissionedDomain 4"; doInvariantCheck( - Env(*this, features), + makeEnv(features), {{"permissioned domain credentials aren't unique"}}, [](Account const& a1, Account const& a2, ApplyContext& ac) { auto slePd = createPermissionedDomain(ac, a1, a2, 0); @@ -1565,7 +1572,7 @@ class Invariants_test : public beast::unit_test::Suite testcase << "PermissionedDomain Set 1"; doInvariantCheck( - Env(*this, features), + makeEnv(features), {{"permissioned domain with no rules."}}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { // create PD @@ -1586,7 +1593,7 @@ class Invariants_test : public beast::unit_test::Suite testcase << "PermissionedDomain Set 2"; doInvariantCheck( - Env(*this, features), + makeEnv(features), {{"permissioned domain bad credentials size " + std::to_string(kTooBig)}}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { // create PD @@ -1617,7 +1624,7 @@ class Invariants_test : public beast::unit_test::Suite testcase << "PermissionedDomain Set 3"; doInvariantCheck( - Env(*this, features), + makeEnv(features), {{"permissioned domain credentials aren't sorted"}}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { // create PD @@ -1647,7 +1654,7 @@ class Invariants_test : public beast::unit_test::Suite testcase << "PermissionedDomain Set 4"; doInvariantCheck( - Env(*this, features), + makeEnv(features), {{"permissioned domain credentials aren't unique"}}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { // create PD @@ -1688,7 +1695,7 @@ class Invariants_test : public beast::unit_test::Suite { testcase << "PermissionedDomain set 2 domains "; doInvariantCheck( - Env(*this, features), + makeEnv(features), fixEnabled ? badMoreThan1 : emptyV, [](Account const& a1, Account const& a2, ApplyContext& ac) { createPermissionedDomain(ac, a1, a2); @@ -1734,7 +1741,7 @@ class Invariants_test : public beast::unit_test::Suite { testcase << "PermissionedDomain set 0 domains "; doInvariantCheck( - Env(*this, features), + makeEnv(features), fixEnabled ? badNoDomains : emptyV, [](Account const&, Account const&, ApplyContext&) { return true; }, XRPAmount{}, @@ -1757,7 +1764,7 @@ class Invariants_test : public beast::unit_test::Suite env1.close(); doInvariantCheck( - Env(*this, features), + makeEnv(features), a1, a2, fixEnabled ? badNoDomains : emptyV, @@ -1798,7 +1805,7 @@ class Invariants_test : public beast::unit_test::Suite { testcase << "PermissionedDomain del, create domain "; doInvariantCheck( - Env(*this, features), + makeEnv(features), fixEnabled ? badNotDeleted : emptyV, [](Account const& a1, Account const& a2, ApplyContext& ac) { createPermissionedDomain(ac, a1, a2); @@ -1995,7 +2002,7 @@ class Invariants_test : public beast::unit_test::Suite testcase << "PermissionedDEX" + std::string(fixEnabled ? " fix" : ""); doInvariantCheck( - Env(*this, features), + makeEnv(features), {{"domain doesn't exist"}}, [](Account const& a1, Account const&, ApplyContext& ac) { Keylet const offerKey = keylet::offer(a1.id(), 10); @@ -2022,7 +2029,7 @@ class Invariants_test : public beast::unit_test::Suite // missing domain ID in offer object doInvariantCheck( - Env(*this, features), + makeEnv(features), {{"hybrid offer is malformed"}}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { Keylet const offerKey = keylet::offer(a2.id(), 10); @@ -4342,7 +4349,7 @@ class Invariants_test : public beast::unit_test::Suite }; doInvariantCheck( - Env{*this, defaultAmendments() - fixCleanup3_2_0}, + makeEnv(defaultAmendments() - fixCleanup3_2_0), {}, [](Account const&, Account const&, ApplyContext&) { return true; }, XRPAmount{}, @@ -5468,7 +5475,7 @@ class Invariants_test : public beast::unit_test::Suite // sfHighLimit issue, not the keylet currency). testcase << "overwrite: NoXRPTrustLines" + std::string(fixEnabled ? " fix" : ""); doInvariantCheck( - Env(*this, features), + makeEnv(features), fixEnabled ? std::vector{{"an XRP trust line was created"}} : std::vector{}, [&insertOrderedTrustLinePair](Account const& a1, Account const& a2, ApplyContext& ac) { @@ -5496,7 +5503,7 @@ class Invariants_test : public beast::unit_test::Suite // Regression: bad deep-freeze trust line followed by a valid one. testcase << "overwrite: NoDeepFreeze" + std::string(fixEnabled ? " fix" : ""); doInvariantCheck( - Env(*this, features), + makeEnv(features), fixEnabled ? std::vector{{"a trust line with deep freeze flag without " "normal freeze was created"}} : std::vector{}, @@ -5530,7 +5537,7 @@ class Invariants_test : public beast::unit_test::Suite // still fires ("a MPT issuance was created"). testcase << "overwrite: NoZeroEscrow MPT" + std::string(fixEnabled ? " fix" : ""); doInvariantCheck( - Env(*this, features), + makeEnv(features), fixEnabled ? std::vector{{"escrow specifies invalid amount"}} : std::vector{{"a MPT issuance was created"}}, [](Account const& a1, Account const&, ApplyContext& ac) {