├── .cargo └── config.toml ├── .clippy.toml ├── .github └── workflows │ ├── beta.yaml │ ├── ci.yaml │ ├── deploy.yml │ ├── msrv.yaml │ ├── release.yaml │ └── style.yaml ├── .gitignore ├── .mailmap ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── src ├── driver.rs ├── main.rs ├── output.rs ├── panic.rs └── valgrind │ ├── mod.rs │ └── xml │ ├── memory-leaks.xml │ ├── mod.rs │ └── tests.rs ├── suppressions ├── README.md ├── rust-1.83 └── rust-1.86 └── tests ├── a-default-cargo-project.rs ├── cli.rs ├── corpus ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── issue-13.rs ├── issue-20.rs ├── issue-70.rs └── issue-74.rs ├── default-new-project ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src │ └── lib.rs ├── ffi-bug ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── examples │ └── no-leak.rs └── src │ └── main.rs └── regression.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [resolver] 2 | incompatible-rust-versions = "fallback" 3 | -------------------------------------------------------------------------------- /.clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.58.1" 2 | -------------------------------------------------------------------------------- /.github/workflows/beta.yaml: -------------------------------------------------------------------------------- 1 | # It can happen, that new compiler versions introduce new behavior to the Rust 2 | # standard library, which might impact this tool (see rust-lang/rust#133574 for 3 | # an example of such an issue). Therefore this job builds the code with the 4 | # current beta compiler to detect potential issues before they reach the stable 5 | # compiler/standard library. The jobs runs periodically. 6 | name: Check against Rust Beta versions 7 | on: 8 | schedule: 9 | - cron: '0 9 * * SUN' # run at every Sunday morning 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | issues: write 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Install valgrind 18 | run: sudo apt-get update && sudo apt-get install valgrind 19 | - run: rustup update beta && rustup default beta 20 | - name: Compile the code with the beta compiler 21 | id: build-beta 22 | run: | 23 | set -uex 24 | if ! cargo test > error.log 2>&1; then 25 | printf 'Hello, I have detected, that this repository does not work with the current beta compiler.\n\nIt looks like, there were changes introduced, that broke this repository. The error was:\n```console\n%s\n```\nPlease take actions to fix this before the behavior reaches stable.' "$(< error.log)" | gh issue create --title 'The compilation fails with current beta compiler' --body-file - 26 | fi 27 | env: 28 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | GH_REPO: ${{ github.repository }} 30 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Continuos Integration 2 | on: 3 | push: 4 | pull_request: 5 | env: 6 | CARGO_TERM_COLOR: always 7 | jobs: 8 | # build the crate on Linux, Windows and MacOS (can only be tested on Linux) 9 | build: 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, windows-latest, macos-latest] 13 | rust: [stable, nightly] 14 | runs-on: ${{ matrix.os }} 15 | steps: 16 | - uses: actions/checkout@v4 17 | - run: rustup update --no-self-update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} 18 | - name: Build 19 | run: cargo build 20 | 21 | # execute all tests (requires an installed valgrind) 22 | test: 23 | runs-on: ubuntu-latest 24 | needs: build 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Install valgrind 28 | run: sudo apt-get update && sudo apt-get install valgrind 29 | - run: rustup update --no-self-update 1.82 && rustup default 1.82 30 | - name: Run tests 31 | run: cargo test 32 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*.*.*' 7 | 8 | jobs: 9 | 10 | create-windows-binaries: 11 | 12 | runs-on: windows-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Install stable 18 | uses: dtolnay/rust-toolchain@stable 19 | with: 20 | toolchain: stable 21 | 22 | - name: Build cargo-valgrind 23 | run: | 24 | cargo build --release 25 | 26 | - name: Get the version 27 | shell: bash 28 | id: tagName 29 | run: | 30 | VERSION=$(cargo pkgid | cut -d# -f2) 31 | echo "tag=$VERSION" >> $GITHUB_OUTPUT 32 | 33 | - name: Build package 34 | id: package 35 | shell: bash 36 | run: | 37 | ARCHIVE_TARGET="x86_64-pc-windows-msvc" 38 | ARCHIVE_NAME="cargo-valgrind-${{ steps.tagName.outputs.tag }}-$ARCHIVE_TARGET" 39 | ARCHIVE_FILE="${ARCHIVE_NAME}.zip" 40 | 7z a ${ARCHIVE_FILE} ./target/release/cargo-valgrind.exe 41 | echo "file=$ARCHIVE_FILE" >> $GITHUB_OUTPUT 42 | echo "name=$ARCHIVE_NAME.zip" >> $GITHUB_OUTPUT 43 | 44 | - name: Upload artifacts 45 | uses: actions/upload-artifact@v4 46 | with: 47 | name: ${{ steps.package.outputs.name }} 48 | path: ${{ steps.package.outputs.file }} 49 | 50 | create-unix-binaries: 51 | 52 | strategy: 53 | matrix: 54 | os: [ubuntu-latest, macos-latest] 55 | include: 56 | - os: ubuntu-latest 57 | target: x86_64-unknown-linux-musl 58 | - os: macos-latest 59 | target: x86_64-apple-darwin 60 | 61 | runs-on: ${{ matrix.os }} 62 | 63 | steps: 64 | - uses: actions/checkout@v4 65 | 66 | - name: Install Rust stable 67 | uses: dtolnay/rust-toolchain@stable 68 | with: 69 | toolchain: stable 70 | target: ${{ matrix.target }} 71 | 72 | - name: Install musl 73 | if: contains(matrix.target, 'linux-musl') 74 | run: | 75 | sudo apt-get update && sudo apt-get install musl-tools 76 | 77 | - name: Build cargo-valgrind 78 | run: | 79 | # TODO: Remember to add RUSTFLAGS=+crt-static for musl target when 80 | # static linkage will not be the default behaviour 81 | cargo build --release --target ${{ matrix.target }} 82 | 83 | - name: Strip binary 84 | run: | 85 | strip target/${{ matrix.target }}/release/cargo-valgrind 86 | 87 | - name: Get the version 88 | id: tagName 89 | run: | 90 | VERSION=$(cargo pkgid | cut -d# -f2) 91 | echo "tag=$VERSION" >> $GITHUB_OUTPUT 92 | 93 | - name: Build package 94 | id: package 95 | run: | 96 | TAR_FILE=cargo-valgrind-${{ steps.tagName.outputs.tag }}-${{ matrix.target }} 97 | cd target/${{ matrix.target }}/release 98 | tar -czvf $GITHUB_WORKSPACE/$TAR_FILE.tar.gz cargo-valgrind 99 | echo "name=$TAR_FILE" >> $GITHUB_OUTPUT 100 | echo "file=$TAR_FILE.tar.gz" >> $GITHUB_OUTPUT 101 | 102 | - name: Upload artifacts 103 | uses: actions/upload-artifact@v4 104 | with: 105 | name: ${{ steps.package.outputs.name }} 106 | path: ${{ steps.package.outputs.file }} 107 | 108 | 109 | deploy: 110 | 111 | needs: [create-windows-binaries, create-unix-binaries] 112 | 113 | runs-on: ubuntu-latest 114 | 115 | steps: 116 | - uses: actions/checkout@v4 117 | 118 | - name: Install Rust stable 119 | uses: dtolnay/rust-toolchain@stable 120 | with: 121 | toolchain: stable 122 | 123 | - name: Create Cargo.lock 124 | run: | 125 | cargo update 126 | 127 | - name: Get version 128 | id: tagName 129 | run: | 130 | VERSION=$(cargo pkgid | cut -d# -f2) 131 | echo "tag=$VERSION" >> $GITHUB_OUTPUT 132 | 133 | - name: Download artifacts 134 | uses: actions/download-artifact@v4 135 | with: 136 | path: ./binaries 137 | 138 | - name: Create a release 139 | uses: softprops/action-gh-release@v2 140 | with: 141 | name: v${{ steps.tagName.outputs.tag }} 142 | files: | 143 | ./binaries/**/*.zip 144 | ./binaries/**/*.tar.gz 145 | env: 146 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 147 | -------------------------------------------------------------------------------- /.github/workflows/msrv.yaml: -------------------------------------------------------------------------------- 1 | name: Minimum Supported Rust Version (MRSV) 2 | on: 3 | push: 4 | pull_request: 5 | env: 6 | CARGO_TERM_COLOR: always 7 | MSRV: "1.58.1" 8 | jobs: 9 | # Build and test the tool with the Minimum Supported Rust Version 10 | msrv: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Install valgrind 15 | run: sudo apt-get update && sudo apt-get install valgrind 16 | - run: rustup update $MSRV && rustup default $MSRV 17 | - name: Run tests 18 | run: cargo test 19 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | ## Automatic release process 2 | # This workflow is a manually-triggered workflow, which automatically bumps the 3 | # version and publishes the new crate version to crates.io. This uses an API 4 | # token stored in the Github secret `CRATES_IO`. 5 | name: Release crate 6 | on: 7 | workflow_dispatch: 8 | inputs: 9 | version: 10 | description: The new crate version to release, must be a non-existing version 11 | required: true 12 | env: 13 | CRATES_IO: ${{ secrets.CRATES_IO }} 14 | VERSION: ${{ github.event.inputs.version }} 15 | jobs: 16 | release: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | ssh-key: ${{ secrets.DEPLOY_KEY }} 22 | - name: Configure git user for Bot 23 | run: | 24 | git config --global user.email 'noreply@github.com' 25 | git config --global user.name 'GitHub Actions Release Bot' 26 | - name: Create release commit 27 | run: | 28 | sed -i "0,/version = /{s/^version = .*$/version = \"${VERSION}\"/}" Cargo.toml 29 | cargo check 30 | git add Cargo.toml Cargo.lock 31 | 32 | sed -i "s/^## unreleased$/## Version ${VERSION}/" CHANGELOG.md 33 | sed -i '3i ## unreleased\n' CHANGELOG.md 34 | git add CHANGELOG.md 35 | 36 | git commit -m "Release version \`${VERSION}\`" || : 37 | - name: Create tag 38 | run: git tag -a "${VERSION}" -m "v${VERSION}" 39 | - name: Show commit 40 | run: git show 41 | - name: Push changes 42 | run: | 43 | git push --tags 44 | git push 45 | - name: Publish to crates.io 46 | run: cargo publish --token "${CRATES_IO}" 47 | -------------------------------------------------------------------------------- /.github/workflows/style.yaml: -------------------------------------------------------------------------------- 1 | name: Style 2 | on: 3 | push: 4 | pull_request: 5 | defaults: 6 | run: 7 | shell: bash 8 | jobs: 9 | # Check correct formatting 10 | rustfmt: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - run: rustup update stable && rustup default stable 15 | - run: rustup component add rustfmt 16 | - name: Check formatting 17 | run: cargo fmt --all -- --check 18 | # Check for code issues 19 | clippy: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | - run: rustup update --no-self-update 1.83 && rustup default 1.83 24 | - run: rustup component add clippy 25 | - name: Run linter 26 | run: cargo clippy --color=always -- -D warnings 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /.vscode 4 | vgcore.* 5 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Julian Frimmel 2 | Julian Frimmel <31166235+jfrimmel@users.noreply.github.com> 3 | Julian Frimmel 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## unreleased 4 | 5 | ## Version 2.3.1 6 | - Fix suppression files for 1.86 7 | 8 | ## Version 2.3.0 9 | - Suppress wrongly reported leaks in the Rust standard library versions 1.83 and 1.86 ([#121](https://github.com/jfrimmel/cargo-valgrind/pull/121)) 10 | - Add more helpful error message if the program under test overflows its stack ([#93](https://github.com/jfrimmel/cargo-valgrind/pull/93)) 11 | - Perform LTO on release ([#109](https://github.com/jfrimmel/cargo-valgrind/pull/109)) 12 | - Use more robust host information detection ([#77](https://github.com/jfrimmel/cargo-valgrind/pull/77), [#95](https://github.com/jfrimmel/cargo-valgrind/pull/95)) 13 | - Updated documentation 14 | - Raised MSRV to 1.58 15 | 16 | ## Version 2.2.1 17 | - Ensure, that consistent tag names are used ([#89](https://github.com/jfrimmel/cargo-valgrind/pull/89)). 18 | This will cause future releases to automatically include the pre-built artifacts thanks to running the deploy workflow when a new tag is pushed. 19 | 20 | ## Version 2.2.0 21 | - Don't crash on valgrind output with multiple stack traces (e.g. an invalid read as in [#80](https://github.com/jfrimmel/cargo-valgrind/pull/80)) 22 | - Display auxiliary information and stack traces if available ([#81](https://github.com/jfrimmel/cargo-valgrind/pull/81)) 23 | - Update dependencies and CI configuration to fix vulnerabilities (`atty` and `actions/checkout`) 24 | 25 | ## Version 2.1.0 26 | - Support passing additional flags to valgrind via the `VALGRINDFLAGS` env var 27 | - Accept more Valgrind XML output (leading to fewer panics) 28 | - Update dependencies including a fix for a vulnerability in `regex` (which didn't affect this crate, though) 29 | 30 | ## Version 2.0.3 31 | - rework continuos integration tests and automate release workflow 32 | - Update dependencies 33 | 34 | ## Version 2.0.2 35 | - Support absence of `xwhat` in valgrind XML output 36 | 37 | ## Version 2.0.1 38 | - Check, if valgrind is installed and print a helpful error if not 39 | - Handle other valgrind errors more gracefully 40 | - Add a custom panic hook, that points the user to the bug-tracker 41 | - Format help and panic messages depending on the terminal size 42 | 43 | ## Version 2.0.0 44 | Breaking API and CLI change! 45 | - Support running _every_ `cargo` executable (binary, unit tests, doctests, ...) 46 | - changed command line 47 | - `cargo valgrind` -> `cargo valgrind run` 48 | - `cargo valgrind --tests` -> `cargo valgrind test` 49 | - `cargo valgrind --example asdf` -> `cargo valgrind run --example asdf` 50 | - etc. 51 | - currently no valgrind parameter support 52 | 53 | ## Version 1.3.0 54 | - Support user flags for the analyzed binary. 55 | 56 | ## Version 1.2.3 57 | - Updated dependencies 58 | 59 | ## Version 1.2.2 60 | - Better error message if valgrind is not found 61 | - support multiple feature flags, similar to normal `cargo` 62 | - support comma separation of features, similar to normal `cargo` 63 | - Bugfix: replace `-` by `_` in integration test target names 64 | 65 | ## Version 1.2.1 66 | - Support running of integration tests (normal tests are not yet supported) 67 | - Fixed panic if the crate under test contains a build script 68 | - Print an error if there are no runnable targets available 69 | 70 | ## Version 1.2.0 71 | - Support the valgrind parameter `--show-leak-kinds=` 72 | - Support the valgrind parameter `--leak-check=` 73 | 74 | ## Version 1.1.2 75 | - Manually implement `Hash` for `Target`. 76 | This was previously derived, which was wrong due to the custom `PartialEq`-implementation (refer to the [`Hash` documentation](https://doc.rust-lang.org/std/hash/trait.Hash.html#hash-and-eq)). 77 | 78 | ## Version 1.1.1 79 | - Print the total number of leaked bytes as a summary 80 | 81 | ## Version 1.1.0 82 | - Added `--features` flag. 83 | This flag is the `cargo valgrind` analog to the same flag on other `cargo` subcommands. 84 | - deprecated `cargo_valgrind::build_target()` in favor of the more flexible `cargo_valgrind::Cargo` type. 85 | 86 | ## Version 1.0.0 87 | - Initial release 88 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.20" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "assert_cmd" 16 | version = "2.0.5" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d5c2ca00549910ec251e3bd15f87aeeb206c9456b9a77b43ff6c97c54042a472" 19 | dependencies = [ 20 | "bstr", 21 | "doc-comment", 22 | "predicates", 23 | "predicates-core", 24 | "predicates-tree", 25 | "wait-timeout", 26 | ] 27 | 28 | [[package]] 29 | name = "autocfg" 30 | version = "1.4.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 33 | 34 | [[package]] 35 | name = "bitflags" 36 | version = "1.3.2" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 39 | 40 | [[package]] 41 | name = "bstr" 42 | version = "0.2.17" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" 45 | dependencies = [ 46 | "lazy_static", 47 | "memchr", 48 | "regex-automata", 49 | ] 50 | 51 | [[package]] 52 | name = "bytesize" 53 | version = "1.1.0" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70" 56 | 57 | [[package]] 58 | name = "cargo-valgrind" 59 | version = "2.3.1" 60 | dependencies = [ 61 | "assert_cmd", 62 | "bytesize", 63 | "colored", 64 | "predicates", 65 | "serde", 66 | "serde-xml-rs", 67 | "temp-file", 68 | "textwrap", 69 | ] 70 | 71 | [[package]] 72 | name = "colored" 73 | version = "1.9.4" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "5a5f741c91823341bebf717d4c71bda820630ce065443b58bd1b7451af008355" 76 | dependencies = [ 77 | "is-terminal", 78 | "lazy_static", 79 | "winapi", 80 | ] 81 | 82 | [[package]] 83 | name = "difflib" 84 | version = "0.4.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 87 | 88 | [[package]] 89 | name = "doc-comment" 90 | version = "0.3.3" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 93 | 94 | [[package]] 95 | name = "either" 96 | version = "1.13.0" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 99 | 100 | [[package]] 101 | name = "errno" 102 | version = "0.3.10" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 105 | dependencies = [ 106 | "libc", 107 | "windows-sys 0.52.0", 108 | ] 109 | 110 | [[package]] 111 | name = "float-cmp" 112 | version = "0.9.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" 115 | dependencies = [ 116 | "num-traits", 117 | ] 118 | 119 | [[package]] 120 | name = "hermit-abi" 121 | version = "0.3.9" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 124 | 125 | [[package]] 126 | name = "io-lifetimes" 127 | version = "1.0.11" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" 130 | dependencies = [ 131 | "hermit-abi", 132 | "libc", 133 | "windows-sys 0.48.0", 134 | ] 135 | 136 | [[package]] 137 | name = "is-terminal" 138 | version = "0.4.7" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" 141 | dependencies = [ 142 | "hermit-abi", 143 | "io-lifetimes", 144 | "rustix", 145 | "windows-sys 0.48.0", 146 | ] 147 | 148 | [[package]] 149 | name = "itertools" 150 | version = "0.10.5" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 153 | dependencies = [ 154 | "either", 155 | ] 156 | 157 | [[package]] 158 | name = "lazy_static" 159 | version = "1.5.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 162 | 163 | [[package]] 164 | name = "libc" 165 | version = "0.2.163" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "1fdaeca4cf44ed4ac623e86ef41f056e848dbeab7ec043ecb7326ba300b36fd0" 168 | 169 | [[package]] 170 | name = "linux-raw-sys" 171 | version = "0.3.8" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" 174 | 175 | [[package]] 176 | name = "log" 177 | version = "0.4.18" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" 180 | 181 | [[package]] 182 | name = "memchr" 183 | version = "2.5.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 186 | 187 | [[package]] 188 | name = "normalize-line-endings" 189 | version = "0.3.0" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 192 | 193 | [[package]] 194 | name = "num-traits" 195 | version = "0.2.18" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" 198 | dependencies = [ 199 | "autocfg", 200 | ] 201 | 202 | [[package]] 203 | name = "predicates" 204 | version = "2.1.1" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" 207 | dependencies = [ 208 | "difflib", 209 | "float-cmp", 210 | "itertools", 211 | "normalize-line-endings", 212 | "predicates-core", 213 | "regex", 214 | ] 215 | 216 | [[package]] 217 | name = "predicates-core" 218 | version = "1.0.3" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" 221 | 222 | [[package]] 223 | name = "predicates-tree" 224 | version = "1.0.5" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" 227 | dependencies = [ 228 | "predicates-core", 229 | "termtree", 230 | ] 231 | 232 | [[package]] 233 | name = "proc-macro2" 234 | version = "1.0.94" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 237 | dependencies = [ 238 | "unicode-ident", 239 | ] 240 | 241 | [[package]] 242 | name = "quote" 243 | version = "1.0.40" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 246 | dependencies = [ 247 | "proc-macro2", 248 | ] 249 | 250 | [[package]] 251 | name = "regex" 252 | version = "1.7.3" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" 255 | dependencies = [ 256 | "aho-corasick", 257 | "memchr", 258 | "regex-syntax", 259 | ] 260 | 261 | [[package]] 262 | name = "regex-automata" 263 | version = "0.1.10" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 266 | 267 | [[package]] 268 | name = "regex-syntax" 269 | version = "0.6.29" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 272 | 273 | [[package]] 274 | name = "rustix" 275 | version = "0.37.28" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" 278 | dependencies = [ 279 | "bitflags", 280 | "errno", 281 | "io-lifetimes", 282 | "libc", 283 | "linux-raw-sys", 284 | "windows-sys 0.48.0", 285 | ] 286 | 287 | [[package]] 288 | name = "serde" 289 | version = "1.0.210" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 292 | dependencies = [ 293 | "serde_derive", 294 | ] 295 | 296 | [[package]] 297 | name = "serde-xml-rs" 298 | version = "0.5.1" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "65162e9059be2f6a3421ebbb4fef3e74b7d9e7c60c50a0e292c6239f19f1edfa" 301 | dependencies = [ 302 | "log", 303 | "serde", 304 | "thiserror", 305 | "xml-rs", 306 | ] 307 | 308 | [[package]] 309 | name = "serde_derive" 310 | version = "1.0.210" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 313 | dependencies = [ 314 | "proc-macro2", 315 | "quote", 316 | "syn", 317 | ] 318 | 319 | [[package]] 320 | name = "smawk" 321 | version = "0.3.2" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" 324 | 325 | [[package]] 326 | name = "syn" 327 | version = "2.0.56" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "6e2415488199887523e74fd9a5f7be804dfd42d868ae0eca382e3917094d210e" 330 | dependencies = [ 331 | "proc-macro2", 332 | "quote", 333 | "unicode-ident", 334 | ] 335 | 336 | [[package]] 337 | name = "temp-file" 338 | version = "0.1.9" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "b5ff282c3f91797f0acb021f3af7fffa8a78601f0f2fd0a9f79ee7dcf9a9af9e" 341 | 342 | [[package]] 343 | name = "terminal_size" 344 | version = "0.1.17" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 347 | dependencies = [ 348 | "libc", 349 | "winapi", 350 | ] 351 | 352 | [[package]] 353 | name = "termtree" 354 | version = "0.2.4" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" 357 | 358 | [[package]] 359 | name = "textwrap" 360 | version = "0.14.2" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" 363 | dependencies = [ 364 | "smawk", 365 | "terminal_size", 366 | "unicode-linebreak", 367 | "unicode-width", 368 | ] 369 | 370 | [[package]] 371 | name = "thiserror" 372 | version = "1.0.65" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" 375 | dependencies = [ 376 | "thiserror-impl", 377 | ] 378 | 379 | [[package]] 380 | name = "thiserror-impl" 381 | version = "1.0.65" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" 384 | dependencies = [ 385 | "proc-macro2", 386 | "quote", 387 | "syn", 388 | ] 389 | 390 | [[package]] 391 | name = "unicode-ident" 392 | version = "1.0.18" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 395 | 396 | [[package]] 397 | name = "unicode-linebreak" 398 | version = "0.1.5" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" 401 | 402 | [[package]] 403 | name = "unicode-width" 404 | version = "0.1.12" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" 407 | 408 | [[package]] 409 | name = "wait-timeout" 410 | version = "0.2.1" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" 413 | dependencies = [ 414 | "libc", 415 | ] 416 | 417 | [[package]] 418 | name = "winapi" 419 | version = "0.3.9" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 422 | dependencies = [ 423 | "winapi-i686-pc-windows-gnu", 424 | "winapi-x86_64-pc-windows-gnu", 425 | ] 426 | 427 | [[package]] 428 | name = "winapi-i686-pc-windows-gnu" 429 | version = "0.4.0" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 432 | 433 | [[package]] 434 | name = "winapi-x86_64-pc-windows-gnu" 435 | version = "0.4.0" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 438 | 439 | [[package]] 440 | name = "windows-sys" 441 | version = "0.48.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 444 | dependencies = [ 445 | "windows-targets 0.48.5", 446 | ] 447 | 448 | [[package]] 449 | name = "windows-sys" 450 | version = "0.52.0" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 453 | dependencies = [ 454 | "windows-targets 0.52.6", 455 | ] 456 | 457 | [[package]] 458 | name = "windows-targets" 459 | version = "0.48.5" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 462 | dependencies = [ 463 | "windows_aarch64_gnullvm 0.48.5", 464 | "windows_aarch64_msvc 0.48.5", 465 | "windows_i686_gnu 0.48.5", 466 | "windows_i686_msvc 0.48.5", 467 | "windows_x86_64_gnu 0.48.5", 468 | "windows_x86_64_gnullvm 0.48.5", 469 | "windows_x86_64_msvc 0.48.5", 470 | ] 471 | 472 | [[package]] 473 | name = "windows-targets" 474 | version = "0.52.6" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 477 | dependencies = [ 478 | "windows_aarch64_gnullvm 0.52.6", 479 | "windows_aarch64_msvc 0.52.6", 480 | "windows_i686_gnu 0.52.6", 481 | "windows_i686_gnullvm", 482 | "windows_i686_msvc 0.52.6", 483 | "windows_x86_64_gnu 0.52.6", 484 | "windows_x86_64_gnullvm 0.52.6", 485 | "windows_x86_64_msvc 0.52.6", 486 | ] 487 | 488 | [[package]] 489 | name = "windows_aarch64_gnullvm" 490 | version = "0.48.5" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 493 | 494 | [[package]] 495 | name = "windows_aarch64_gnullvm" 496 | version = "0.52.6" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 499 | 500 | [[package]] 501 | name = "windows_aarch64_msvc" 502 | version = "0.48.5" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 505 | 506 | [[package]] 507 | name = "windows_aarch64_msvc" 508 | version = "0.52.6" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 511 | 512 | [[package]] 513 | name = "windows_i686_gnu" 514 | version = "0.48.5" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 517 | 518 | [[package]] 519 | name = "windows_i686_gnu" 520 | version = "0.52.6" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 523 | 524 | [[package]] 525 | name = "windows_i686_gnullvm" 526 | version = "0.52.6" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 529 | 530 | [[package]] 531 | name = "windows_i686_msvc" 532 | version = "0.48.5" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 535 | 536 | [[package]] 537 | name = "windows_i686_msvc" 538 | version = "0.52.6" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 541 | 542 | [[package]] 543 | name = "windows_x86_64_gnu" 544 | version = "0.48.5" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 547 | 548 | [[package]] 549 | name = "windows_x86_64_gnu" 550 | version = "0.52.6" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 553 | 554 | [[package]] 555 | name = "windows_x86_64_gnullvm" 556 | version = "0.48.5" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 559 | 560 | [[package]] 561 | name = "windows_x86_64_gnullvm" 562 | version = "0.52.6" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 565 | 566 | [[package]] 567 | name = "windows_x86_64_msvc" 568 | version = "0.48.5" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 571 | 572 | [[package]] 573 | name = "windows_x86_64_msvc" 574 | version = "0.52.6" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 577 | 578 | [[package]] 579 | name = "xml-rs" 580 | version = "0.8.20" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" 583 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-valgrind" 3 | version = "2.3.1" 4 | authors = ["Julian Frimmel "] 5 | edition = "2018" 6 | rust-version = "1.58.1" 7 | description = "A cargo subcommand for running valgrind" 8 | repository = "https://github.com/jfrimmel/cargo-valgrind" 9 | readme = "README.md" 10 | license = "MIT OR Apache-2.0" 11 | keywords = ["cargo", "subcommand", "cargo-subcommand", "valgrind", "cli"] 12 | categories = ["development-tools", "development-tools::cargo-plugins"] 13 | exclude = [".clippy.toml", ".github", ".mailmap", "CHANGELOG.md", "tests"] 14 | 15 | [features] 16 | default = ["textwrap"] 17 | 18 | [dependencies] 19 | serde = { version = "1", features = ["derive"] } 20 | serde-xml-rs = { version = "0.5", default-features = false } 21 | colored = "1.9.4" 22 | bytesize = "1" 23 | textwrap = { version = "0.14", optional = true, features = ["terminal_size"] } 24 | temp-file = "0.1.9" 25 | 26 | [dev-dependencies] 27 | assert_cmd = "2" 28 | predicates = "2" 29 | 30 | [profile.release] 31 | lto = true 32 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `cargo-valgrind` 2 | > A cargo subcommand, that runs valgrind and collects its output in a helpful manner. 3 | 4 | [![Latest version](https://img.shields.io/crates/v/cargo-valgrind.svg)](https://crates.io/crates/cargo-valgrind) 5 | [![Latest GitHub release](https://img.shields.io/github/v/release/jfrimmel/cargo-valgrind)](https://github.com/jfrimmel/cargo-valgrind/releases/latest) 6 | ![License](https://img.shields.io/crates/l/cargo-valgrind) 7 | [![Crates.io Total Downloads](https://img.shields.io/crates/d/cargo-valgrind)](https://crates.io/crates/cargo-valgrind) 8 | 9 | 10 | This command extends cargo with the capability to directly run `valgrind` on any crate executable. 11 | The output of valgrind is then used to mark the binary as pass/fail. 12 | 13 | This command should not be necessary for ordinary Rust programs, especially if you are only using safe Rust code. 14 | But if you do FFI-related stuff (either by simply using a FFI-binding crate or because you are developing a safe wrapper for such FFI bindings) it may be really helpful to check, whether the memory usages across the FFI borders are correct. 15 | 16 | ## Usage 17 | A typical mistake would be: 18 | ```rust 19 | use std::ffi::CString; 20 | use std::os::raw::c_char; 21 | 22 | extern "C" { 23 | fn puts(s: *const c_char); 24 | } 25 | 26 | fn main() { 27 | let string = CString::new("Test").unwrap(); 28 | 29 | let ptr = string.into_raw(); 30 | unsafe { puts(ptr) }; 31 | 32 | // unsafe { CString::from_raw(ptr) }; 33 | } 34 | ``` 35 | The memory of the variable `string` will never be freed. 36 | If you run `cargo valgrind run` in your shell, it detects the leak: 37 | ```bash 38 | $ cargo valgrind run 39 | Finished dev [unoptimized + debuginfo] target(s) in 0.01s 40 | Running `target/debug/cstring` 41 | Test 42 | Error leaked 5 B in 1 block 43 | Info stack trace (user code at the bottom) 44 | at malloc (vg_replace_malloc.c:446) 45 | at alloc (alloc.rs:100) 46 | at alloc_impl (alloc.rs:183) 47 | at allocate (alloc.rs:243) 48 | at try_allocate_in (raw_vec.rs:230) 49 | at with_capacity_in (raw_vec.rs:158) 50 | at with_capacity_in (mod.rs:699) 51 | at with_capacity (mod.rs:481) 52 | at spec_new_impl_bytes (c_str.rs:290) 53 | at <&str as alloc::ffi::c_str::CString::new::SpecNewImpl>::spec_new_impl (c_str.rs:309) 54 | at alloc::ffi::c_str::CString::new (c_str.rs:319) 55 | at ffi_bug::main (main.rs:9) 56 | at core::ops::function::FnOnce::call_once (function.rs:250) 57 | at std::sys::backtrace::__rust_begin_short_backtrace (backtrace.rs:152) 58 | at std::rt::lang_start::{{closure}} (rt.rs:162) 59 | at call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284) 60 | at do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:557) 61 | at try + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:521) 62 | at catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:350) 63 | at {closure#2} (rt.rs:141) 64 | at do_call (panicking.rs:557) 65 | at try (panicking.rs:521) 66 | at catch_unwind (panic.rs:350) 67 | at std::rt::lang_start_internal (rt.rs:141) 68 | at std::rt::lang_start (rt.rs:161) 69 | at main 70 | Summary Leaked 5 B total (0 other errors) 71 | ``` 72 | Un-commenting the `unsafe { CString::from_raw(ptr) };` re-takes the memory and frees it correctly. 73 | `cargo valgrind run` will compile the binary for you and won't detect a leak, since there is no leak anymore. 74 | 75 | If you would like to pass flags to valgrind (for example to run an alternate subtool), you can set the `VALGRINDFLAGS` environment variable to a space-delimited list of valid Valgrind options. 76 | 77 | `cargo valgrind` automatically applies some suppressions for wrongly reported leaks within the Rust standard library. 78 | This makes the tool more powerful than a normal valgrind invocation. 79 | 80 | _Note_: users of `cargo-valgrind` version 1.x should mind the changed command line. 81 | Previously there was a `cargo valgrind` subcommand, that replaced the `cargo run` or `cargo test` commands. 82 | Now the command line is `cargo valgrind `, where `` can be any normal cargo subcommand. 83 | 84 | # Installation 85 | ## Requirements 86 | You need to have `valgrind` installed and in the `PATH` (you can test this by running `valgrind --help` in your shell). 87 | 88 | You'll also need to have `cargo` installed and in the `PATH`, but since this is a cargo subcommand, you will almost certainly have it already installed. 89 | 90 | ## Install the binary 91 | ### Use pre-built binaries 92 | Head over to the [latest release] and download the artifact for your platform. 93 | The binary has to be extracted into the `.cargo`-directory, typically under `$HOME/.cargo`. 94 | Note, that it is not possible to directly run the program itself, as it must be invoked via `cargo valgrind`, so it must be located in a directory, `cargo` searches its subcommands in. 95 | 96 | [latest release]: https://github.com/jfrimmel/cargo-valgrind/releases/latest 97 | 98 | ### From Source 99 | Run the following command to install from [crates.io](https://crates.io/crates/cargo-valgrind): 100 | ```bash 101 | $ cargo install cargo-valgrind 102 | ``` 103 | This will install the latest official released version. 104 | 105 | If you want to use the latest changes, that were not yet published to `crates.io`, you can install the binary from the git-repository like this: 106 | ```bash 107 | $ cargo install --git https://github.com/jfrimmel/cargo-valgrind 108 | ``` 109 | 110 | # License 111 | Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or [MIT license](LICENSE-MIT) at your option. 112 | 113 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in `cargo-valgrind` by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 114 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | //! Build-script to automatically generate the contents of a suppression file 2 | //! ready for inclusion into the binary and to be passed to Valgrind. 3 | //! 4 | //! The [`SUPPRESSIONS_DIR`] is searched for files any their entire content is 5 | //! written as a string constant to a source file, so that their contents is 6 | //! embedded into the resulting binary. 7 | 8 | /// The directory containing the suppression files. 9 | const SUPPRESSIONS_DIR: &str = "suppressions"; 10 | 11 | use std::io::{BufWriter, Write}; 12 | use std::path::PathBuf; 13 | use std::{env, fs}; 14 | 15 | /// Search for suppression files inside the [`SUPPRESSIONS_DIR`]. 16 | /// 17 | /// This will return an iterator over the contents of the files, that may be 18 | /// suitable for Valgrind suppression files. There is no recursive search. 19 | /// Non-readable entries and other I/O errors are ignored. 20 | fn search_suppressions() -> impl Iterator { 21 | fs::read_dir(SUPPRESSIONS_DIR) 22 | .expect("could not find the suppression directory") 23 | .filter_map(|entry| entry.ok()) 24 | .filter(|path| path.file_type().map_or(false, |path| path.is_file())) 25 | .filter(|file| file.file_name() != "README.md") 26 | .map(|file| file.path()) 27 | .filter_map(|path| fs::read_to_string(path).ok()) 28 | } 29 | 30 | fn main() -> Result<(), Box> { 31 | // Create file `$OUT_DIR/suppressions.rs`, which will be written to later 32 | let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("cargo sets $OUT_DIR")); 33 | let out_file = fs::File::create(out_dir.join("suppressions.rs"))?; 34 | let mut out_file = BufWriter::new(out_file); 35 | 36 | // Create the file contents of the suppression file given to valgrind. This 37 | // file contains all the individual suppressions joined together into a long 38 | // string, so that the application can use that single string. 39 | // Normally, this should be stored as a fixed `OsStr`, since it is not re- 40 | // quired to be UTF-8 (the interpretation is done by Valgrind alone), but 41 | // this is not possible in Rust at the moment. Therefore it is actually 42 | // stored as a string (which makes handling the file contents in this build 43 | // script easier as well). So, this will generate a Rust string constant 44 | // (`const SUPPRESSIONS: &str = "...";`) with all the file contents joined 45 | // together of each file in order of the iterations. 46 | out_file.write_all(b"/// Rust-std suppression file contents generated by build script\n")?; 47 | out_file.write_all(b"const SUPPRESSIONS: &str = \"")?; 48 | for file in search_suppressions() { 49 | out_file.write_all(file.as_bytes())?; 50 | } 51 | out_file.write_all(b"\";")?; 52 | 53 | // Cargo should monitor the whole directory for changes/new files, so that 54 | // this build script is run on new/changed suppression files. 55 | println!("cargo:rerun-if-changed={SUPPRESSIONS_DIR}"); 56 | Ok(()) 57 | } 58 | -------------------------------------------------------------------------------- /src/driver.rs: -------------------------------------------------------------------------------- 1 | //! A module providing the wrapping driver for a custom runner. 2 | 3 | use std::env; 4 | use std::ffi::OsString; 5 | use std::io; 6 | use std::path::Path; 7 | use std::process::Command; 8 | 9 | /// The prefix line for the target host output. 10 | const HOST_PREFIX: &str = "host: "; 11 | 12 | /// Search for [`HOST_PREFIX`] inside the command output and extract its value. 13 | fn search_for_host(command: &mut Command) -> Option { 14 | let output = command.output().ok()?.stdout; 15 | let output = String::from_utf8(output).ok()?; 16 | 17 | output 18 | .lines() 19 | .find(|line| line.starts_with(HOST_PREFIX)) 20 | .map(|host_line| host_line.trim_start_matches(HOST_PREFIX).to_string()) 21 | } 22 | 23 | /// Act as a driver for `cargo run`/`cargo test`, but with special runner. 24 | /// 25 | /// This function returns `Ok(true)` if all subprograms were successfully 26 | /// executed, or `Ok(false)` if there was a non-successful subcommand. 27 | /// 28 | /// # Errors 29 | /// This function returns an I/O error, if a subprocess could not be spawned or 30 | /// executed. 31 | pub fn driver() -> io::Result { 32 | let cargo = env::var_os("CARGO").expect("CARGO environment variable is not set"); 33 | let rustc = Path::new(&cargo).with_file_name("rustc"); 34 | 35 | // Search for the host currently running to be able to override the runner. 36 | // The host field is extracted from `cargo version -v` if possible, since 37 | // this relies entirely on the used `cargo` binary. Older versions of cargo 38 | // don't provide the host in that output, though, so there is a fallback to 39 | // `rustc -vV` in that case. 40 | let host = search_for_host(Command::new(&cargo).arg("version").arg("-v")) 41 | .or_else(|| search_for_host(Command::new(rustc).arg("rustc").arg("-vV"))) 42 | .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "could not determine host"))?; 43 | 44 | /* convert to runner env variable */ 45 | let host = host.replace(['-', '.'], "_").to_uppercase(); 46 | let runner = format!("CARGO_TARGET_{host}_RUNNER"); 47 | 48 | /* cargo run with a custom runner */ 49 | let cargo_valgrind = env::args_os() 50 | .next() 51 | .unwrap_or_else(|| OsString::from("cargo-valgrind")); 52 | 53 | Ok(Command::new(cargo) 54 | .args(env::args_os().skip(2)) 55 | .envs(env::vars_os()) 56 | .env(runner, cargo_valgrind) 57 | .spawn()? 58 | .wait()? 59 | .success()) 60 | } 61 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //! The `cargo-valgrind` executable. 2 | #![forbid(unsafe_code)] 3 | #![deny(clippy::correctness)] 4 | #![warn( 5 | clippy::perf, 6 | clippy::complexity, 7 | clippy::style, 8 | clippy::nursery, 9 | clippy::pedantic, 10 | clippy::clone_on_ref_ptr, 11 | clippy::decimal_literal_representation, 12 | clippy::float_cmp_const, 13 | clippy::missing_docs_in_private_items, 14 | clippy::multiple_inherent_impl, 15 | clippy::unwrap_used, 16 | clippy::cargo_common_metadata, 17 | clippy::used_underscore_binding 18 | )] 19 | 20 | mod driver; 21 | mod output; 22 | mod panic; 23 | mod valgrind; 24 | 25 | use colored::Colorize as _; 26 | use std::env; 27 | use std::process; 28 | 29 | /// Part of the output message of `valgrind` if a possible stack overflow is 30 | /// detected. 31 | const STACK_OVERFLOW: &str = "main thread stack using the --main-stacksize= flag"; 32 | 33 | fn main() { 34 | panic::replace_hook(); 35 | 36 | let number_of_arguments = || env::args_os().skip(1).count(); 37 | let help_requested = || env::args_os().any(|arg| arg == "--help" || arg == "-h"); 38 | let is_cargo_subcommand = || env::args_os().nth(1).map_or(false, |arg| arg == "valgrind"); 39 | if number_of_arguments() == 0 || help_requested() { 40 | let text = format!( 41 | "cargo valgrind {version}\n\ 42 | {authors}\n\ 43 | Analyze your Rust binary for memory errors\n\ 44 | \n\ 45 | This program is a cargo subcommand, i.e. it integrates with the \ 46 | normal cargo workflow. You specify this subcommand and another \ 47 | \"target\", what valgrind should do. For example: `cargo valgrind \ 48 | run` will do the same thing as `cargo run` (i.e. compile and run \ 49 | your binary), but the execution will be done using valgrind. \ 50 | Similarly to execute the tests, simply use `cargo valgrind test`.", 51 | version = env!("CARGO_PKG_VERSION"), 52 | authors = env!("CARGO_PKG_AUTHORS").replace(':', ", "), 53 | ); 54 | #[cfg(feature = "textwrap")] 55 | let text = textwrap::wrap( 56 | textwrap::dedent(&text).trim_start(), 57 | textwrap::Options::with_termwidth(), 58 | ) 59 | .join("\n"); 60 | println!("{text}"); 61 | } else if is_cargo_subcommand() { 62 | if !driver::driver().expect("Could not execute subcommand") { 63 | process::exit(200); 64 | } 65 | } else { 66 | // we are running as the cargo runner, therefore everything except the 67 | // first argument is the command to execute. 68 | let command = env::args_os().skip(1); 69 | 70 | let exit_code = match valgrind::execute(command) { 71 | Ok(valgrind::xml::Output { 72 | errors: Some(errors), 73 | .. 74 | }) => { 75 | output::display_errors(&errors); 76 | 127 77 | } 78 | Ok(_) => 0, 79 | Err(e @ valgrind::Error::MalformedOutput(..)) => std::panic::panic_any(e), // the panic handler catches this and reports it appropriately 80 | Err(valgrind::Error::ValgrindFailure(output)) if output.contains(STACK_OVERFLOW) => { 81 | output::display_stack_overflow(&output); 82 | 134 // default exit code for stack overflows 83 | } 84 | Err(e) => { 85 | eprintln!("{}: {}", "error".red().bold(), e); 86 | 1 87 | } 88 | }; 89 | process::exit(exit_code); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/output.rs: -------------------------------------------------------------------------------- 1 | //! Write human-readable and colored output the the console. 2 | use crate::valgrind; 3 | use colored::Colorize as _; 4 | 5 | /// Nicely format the errors in the valgrind output, if there are any. 6 | pub fn display_errors(errors: &[valgrind::xml::Error]) { 7 | // format the output in a helpful manner 8 | for error in errors { 9 | if error.kind.is_leak() { 10 | display_leak(error); 11 | } else { 12 | display_generic_error(error); 13 | } 14 | } 15 | 16 | let total: usize = errors.iter().map(|error| error.resources.bytes).sum(); 17 | eprintln!( 18 | "{:>12} Leaked {} total ({} other errors)", 19 | "Summary".red().bold(), 20 | bytesize::to_string(total as _, true), 21 | errors.iter().filter(|e| !e.kind.is_leak()).count() 22 | ); 23 | } 24 | 25 | /// Nicely format a single memory leak error. 26 | fn display_leak(error: &valgrind::xml::Error) { 27 | eprintln!( 28 | "{:>12} leaked {} in {} block{}", 29 | "Error".red().bold(), 30 | bytesize::to_string(error.resources.bytes as _, true), 31 | error.resources.blocks, 32 | if error.resources.blocks == 1 { "" } else { "s" } 33 | ); 34 | 35 | let stack = &error.stack_trace[0]; // always available 36 | display_stack_trace("stack trace (user code at the bottom)", stack); 37 | } 38 | 39 | /// Nicely format a non-memory-leak error. 40 | fn display_generic_error(error: &valgrind::xml::Error) { 41 | eprintln!( 42 | "{:>12} {}", 43 | "Error".red().bold(), 44 | error.main_info.as_ref().map_or("unknown", String::as_str) 45 | ); 46 | 47 | let stack = &error.stack_trace[0]; // always available 48 | display_stack_trace("main stack trace (user code at the bottom)", stack); 49 | error 50 | .stack_trace 51 | .iter() 52 | .skip(1) 53 | .enumerate() 54 | .map(|(index, stack)| (error.auxiliary_info.get(index), stack)) 55 | .for_each(|(msg, stack)| { 56 | display_stack_trace( 57 | msg.map_or_else(|| "additional stack trace", String::as_str), 58 | stack, 59 | ); 60 | }); 61 | } 62 | 63 | /// Write out the full stack trace (indented to match other messages). 64 | fn display_stack_trace(msg: &str, stack: &valgrind::xml::Stack) { 65 | eprintln!("{:>12} {}", "Info".cyan().bold(), msg); 66 | stack 67 | .frames 68 | .iter() 69 | .for_each(|frame| eprintln!(" at {frame}")); 70 | } 71 | 72 | /// Write out an error message for describing the stack overflow message. 73 | pub fn display_stack_overflow(output: &str) { 74 | let error = "Error".red().bold(); 75 | let info = "Info".cyan().bold(); 76 | eprintln!("{error:>12}: looks like the program overflowed its stack"); 77 | eprintln!("{info:>12}: valgrind says:"); 78 | output 79 | .lines() 80 | .for_each(|line| eprintln!(" {line}")); 81 | } 82 | -------------------------------------------------------------------------------- /src/panic.rs: -------------------------------------------------------------------------------- 1 | //! Provides the panic handling neceises. 2 | //! 3 | //! This module allows to customize the panicking of the application. First, it 4 | //! changes the normal panic handler to print a custom message, that guides the 5 | //! user to the bug tracker. Secondly, the panic message presented will change 6 | //! depending on the payload-type. If that panic is an implementation bug, some 7 | //! addition information is printed (e.g. for [`Error::MalformedOutput`]). 8 | //! 9 | //! [`Error::MalformedOutput`]: crate::valgrind::Error::MalformedOutput 10 | #![allow(clippy::module_name_repetitions)] 11 | 12 | use crate::valgrind::Error; 13 | use std::panic; 14 | 15 | /// The header used in the custom panic message. 16 | /// 17 | /// Every panic is prefixed with this string (but formatting might be applied). 18 | /// After that text, some more panic information is printed. 19 | const PANIC_HEADER: &str = " 20 | Oooops. cargo valgrind unexpectedly crashed. This is a bug! 21 | 22 | This is an error in this program, which should be fixed. If you can, \ 23 | please submit a bug report at 24 | 25 | https://github.com/jfrimmel/cargo-valgrind/issues/new/choose 26 | 27 | To make fixing the error more easy, please provide the information below \ 28 | as well as additional information on which project the error occurred or \ 29 | how to reproduce it. 30 | "; 31 | 32 | /// Replaces any previous hook with the custom hook of this application. 33 | /// 34 | /// This custom hook points the user to the project repository and asks them to 35 | /// open a bug report. 36 | pub fn replace_hook() { 37 | let old_hook = panic::take_hook(); 38 | panic::set_hook(Box::new(move |panic| { 39 | #[cfg(not(feature = "textwrap"))] 40 | let text = PANIC_HEADER; 41 | #[cfg(feature = "textwrap")] 42 | let text = textwrap::wrap( 43 | textwrap::dedent(PANIC_HEADER).trim_start(), 44 | textwrap::Options::with_termwidth(), 45 | ) 46 | .join("\n"); 47 | eprintln!("{text}"); 48 | 49 | eprintln!( 50 | "{}: version {}", 51 | env!("CARGO_PKG_NAME"), 52 | env!("CARGO_PKG_VERSION") 53 | ); 54 | 55 | // intentionally not wrapped using `textwrap`, since own formatting 56 | // might be applied. 57 | if let Some(Error::MalformedOutput(e, content)) = panic.payload().downcast_ref() { 58 | eprintln!("XML format mismatch between `valgrind` and `cargo valgrind`: {e}"); 59 | eprintln!( 60 | "XML output of valgrind:\n```xml\n{}```", 61 | String::from_utf8_lossy(content) 62 | ); 63 | } else { 64 | old_hook(panic); 65 | } 66 | })); 67 | } 68 | -------------------------------------------------------------------------------- /src/valgrind/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module provides utility functions around valgrind. 2 | 3 | pub mod xml; 4 | 5 | use serde::Deserialize; 6 | use std::ffi::OsString; 7 | use std::net::{SocketAddr, TcpListener}; 8 | use std::process::Command; 9 | use std::{env, fmt, io::Read}; 10 | use std::{ffi::OsStr, process::Stdio}; 11 | 12 | /// Error type for valgrind-execution-related failures. 13 | #[derive(Debug)] 14 | pub enum Error { 15 | /// The `valgrind` binary is not installed or not executable. 16 | /// 17 | /// This is an user error. 18 | ValgrindNotInstalled, 19 | /// Something around the socket creation did fail. 20 | SocketConnection, 21 | /// The sub-process could not be waited on. 22 | ProcessFailed, 23 | /// Valgrind execution did fail. 24 | /// 25 | /// The error output of valgrind is captured. 26 | ValgrindFailure(String), 27 | /// The valgrind output was malformed or otherwise unexpected. 28 | /// 29 | /// This variant contains the inner deserialization error and the output of 30 | /// valgrind. 31 | MalformedOutput(serde_xml_rs::Error, Vec), 32 | } 33 | 34 | impl std::error::Error for Error {} 35 | impl fmt::Display for Error { 36 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 37 | match self { 38 | Self::ValgrindNotInstalled => write!(f, "valgrind executable not found"), 39 | Self::SocketConnection => write!(f, "local TCP I/O error"), 40 | Self::ProcessFailed => write!(f, "cannot start valgrind process"), 41 | Self::ValgrindFailure(s) => write!(f, "invalid valgrind usage: {s}"), 42 | Self::MalformedOutput(e, _) => write!(f, "unexpected valgrind output: {e}"), 43 | } 44 | } 45 | } 46 | 47 | /// Execute a certain command inside of valgrind and collect the [`Output`]. 48 | /// 49 | /// [`Output`]: xml::Output 50 | pub fn execute(command: I) -> Result 51 | where 52 | S: AsRef, 53 | I: IntoIterator, 54 | { 55 | // open a TCP socket on localhost, port selected by OS 56 | let address: SocketAddr = ([127, 0, 0, 1], 0).into(); 57 | let listener = TcpListener::bind(address).map_err(|_| Error::SocketConnection)?; 58 | let address = listener.local_addr().map_err(|_| Error::SocketConnection)?; 59 | 60 | let mut valgrind = Command::new("valgrind"); 61 | 62 | // additional options to pass to valgrind? 63 | if let Ok(additional_args) = env::var("VALGRINDFLAGS") { 64 | valgrind.args(additional_args.split(' ')); 65 | } 66 | 67 | // Apply the list of suppressions provided in the `suppressions` directory 68 | // (and by the build-script). The suppression file contents will all be 69 | // appended into a long string, which is written to a temporary file. This 70 | // file is then used as a suppression-file-argument to `valgrind`. 71 | let suppressions = temp_file::TempFile::with_prefix("valgrind-suppressions") 72 | .expect("could not create temporary suppression file") 73 | .with_contents(SUPPRESSIONS.as_bytes()) 74 | .expect("could not write to temporary suppression file"); 75 | valgrind.arg({ 76 | let mut option = OsString::from("--suppressions="); 77 | option.push(suppressions.path()); 78 | option 79 | }); 80 | 81 | let cargo = valgrind 82 | .arg("--xml=yes") 83 | .arg(format!("--xml-socket={}:{}", address.ip(), address.port())) 84 | .args(command) 85 | .stderr(Stdio::piped()) 86 | .spawn() 87 | .map_err(|_| Error::ValgrindNotInstalled)?; 88 | 89 | // spawn a new thread, that receives the XML and parses it. This has to be 90 | // a separate execution unit (a thread is currently used, but an `async` 91 | // task would be suitable as well), as the `accept()` call blocks until the 92 | // valgrind binary writes something to the TCP connection. This is normally 93 | // fine, but if we consider errors, e.g. wrong command line flags, valgrind 94 | // won't write anything to the connection, so the program will hang forever. 95 | // The thread can simply be thrown away, if valgrind fails. 96 | let xml = std::thread::spawn(move || { 97 | // collect the output of valgrind 98 | let (mut listener, _) = listener.accept().map_err(|_| Error::SocketConnection)?; 99 | let mut output = Vec::new(); 100 | listener 101 | .read_to_end(&mut output) 102 | .map_err(|_| Error::SocketConnection)?; 103 | let xml: xml::Output = xml::Output::deserialize( 104 | &mut serde_xml_rs::Deserializer::new_from_reader(&*output) 105 | .non_contiguous_seq_elements(true), 106 | ) 107 | .map(|output_: xml::Output| { 108 | let mut output = output_; 109 | if let Some(err) = output.errors { 110 | let new_err: Vec = err 111 | .into_iter() 112 | .filter(|e| { 113 | !e.kind.is_leak() || e.resources.bytes > 0 || e.resources.blocks > 0 114 | }) 115 | .collect(); 116 | if new_err.is_empty() { 117 | output.errors = None; 118 | } else { 119 | output.errors = Some(new_err); 120 | } 121 | } 122 | output 123 | }) 124 | .map_err(|e| Error::MalformedOutput(e, output))?; 125 | Ok(xml) 126 | }); 127 | 128 | let output = cargo.wait_with_output().map_err(|_| Error::ProcessFailed)?; 129 | if output.status.success() { 130 | let xml = xml.join().expect("Reader-thread panicked")?; 131 | Ok(xml) 132 | } else { 133 | // this does not really terminalte the thread, but detaches it. Despite 134 | // that, the thread will be killed, if the main thread exits. 135 | drop(xml); 136 | Err(Error::ValgrindFailure( 137 | String::from_utf8_lossy(&output.stderr).to_string(), 138 | )) 139 | } 140 | 141 | // TODO: use drop guard, that waits on child in order to prevent printing to stdout of the child 142 | } 143 | 144 | // Include the list of suppression file contents provided by this repository. 145 | include!(concat!(env!("OUT_DIR"), "/suppressions.rs")); 146 | -------------------------------------------------------------------------------- /src/valgrind/xml/memory-leaks.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 4 6 | memcheck 7 | 8 | 9 | Memcheck, a memory error detector 10 | Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. 11 | Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info 12 | Command: target/debug/examples/creation 13 | 14 | 15 | 17670 16 | 9061 17 | memcheck 18 | 19 | 20 | 21 | /usr/bin/valgrind 22 | --leak-check=full 23 | --show-leak-kinds=all 24 | --error-exitcode=1 25 | --xml=yes 26 | --xml-file=vg.xml 27 | 28 | 29 | target/debug/examples/creation 30 | 31 | 32 | 33 | 34 | RUNNING 35 | 36 | 37 | 38 | 39 | 40 | FINISHED 41 | 42 | 43 | 44 | 45 | 0x0 46 | 1 47 | Leak_DefinitelyLost 48 | 49 | 15 bytes in 1 blocks are definitely lost in loss record 1 of 8 50 | 15 51 | 1 52 | 53 | 54 | 55 | 0x483AD7B 56 | /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so 57 | realloc 58 | /build/valgrind/src/valgrind/coregrind/m_replacemalloc 59 | vg_replace_malloc.c 60 | 826 61 | 62 | 63 | 0x12B6F4 64 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 65 | realloc 66 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/liballoc 67 | alloc.rs 68 | 125 69 | 70 | 71 | 0x12B6F4 72 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 73 | realloc 74 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/liballoc 75 | alloc.rs 76 | 184 77 | 78 | 79 | 0x12B6F4 80 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 81 | reserve_internal<u8,alloc::alloc::Global> 82 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/liballoc 83 | raw_vec.rs 84 | 666 85 | 86 | 87 | 0x12B6F4 88 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 89 | reserve_exact<u8,alloc::alloc::Global> 90 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/liballoc 91 | raw_vec.rs 92 | 411 93 | 94 | 95 | 0x12B6F4 96 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 97 | reserve_exact<u8> 98 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/liballoc 99 | vec.rs 100 | 482 101 | 102 | 103 | 0x12B6F4 104 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 105 | std::ffi::c_str::CString::from_vec_unchecked 106 | src/libstd/ffi 107 | c_str.rs 108 | 355 109 | 110 | 111 | 0x12B68C 112 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 113 | std::ffi::c_str::CString::_new 114 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c//src/libstd/ffi 115 | c_str.rs 116 | 330 117 | 118 | 119 | 0x125331 120 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 121 | std::ffi::c_str::CString::new 122 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/libstd/ffi 123 | c_str.rs 124 | 324 125 | 126 | 127 | 0x123253 128 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 129 | vk::create_instance::{{closure}} 130 | /home/jfrimmel/git/lava.rs/vk-core/src 131 | lib.rs 132 | 368 133 | 134 | 135 | 0x120AE0 136 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 137 | <core::iter::adapters::Map<I,F> as core::iter::traits::iterator::Iterator>::fold::{{closure}} 138 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/libcore/iter/adapters 139 | mod.rs 140 | 589 141 | 142 | 143 | 0x11D899 144 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 145 | <core::slice::Iter<T> as core::iter::traits::iterator::Iterator>::fold 146 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/libcore/slice 147 | mod.rs 148 | 3159 149 | 150 | 151 | 0x1207FA 152 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 153 | <core::iter::adapters::Map<I,F> as core::iter::traits::iterator::Iterator>::fold 154 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/libcore/iter/adapters 155 | mod.rs 156 | 589 157 | 158 | 159 | 0x12093A 160 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 161 | <core::iter::adapters::Map<I,F> as core::iter::traits::iterator::Iterator>::fold 162 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/libcore/iter/adapters 163 | mod.rs 164 | 589 165 | 166 | 167 | 0x1208BA 168 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 169 | <core::iter::adapters::Map<I,F> as core::iter::traits::iterator::Iterator>::fold 170 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/libcore/iter/adapters 171 | mod.rs 172 | 589 173 | 174 | 175 | 0x1205FC 176 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 177 | core::iter::traits::iterator::Iterator::for_each 178 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/libcore/iter/traits 179 | iterator.rs 180 | 604 181 | 182 | 183 | 0x11ACDB 184 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 185 | <alloc::vec::Vec<T> as alloc::vec::SpecExtend<T,I>>::spec_extend 186 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/liballoc 187 | vec.rs 188 | 1862 189 | 190 | 191 | 192 | 193 | 194 | 0x1 195 | 1 196 | Leak_StillReachable 197 | 198 | 24 bytes in 1 blocks are still reachable in loss record 2 of 8 199 | 24 200 | 1 201 | 202 | 203 | 204 | 0x483877F 205 | /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so 206 | malloc 207 | /build/valgrind/src/valgrind/coregrind/m_replacemalloc 208 | vg_replace_malloc.c 209 | 299 210 | 211 | 212 | 0x401CBBE 213 | /usr/lib/ld-2.29.so 214 | strdup 215 | 216 | 217 | 0x40175C5 218 | /usr/lib/ld-2.29.so 219 | _dl_load_cache_lookup 220 | 221 | 222 | 0x400A2AB 223 | /usr/lib/ld-2.29.so 224 | _dl_map_object 225 | 226 | 227 | 0x400E704 228 | /usr/lib/ld-2.29.so 229 | openaux 230 | 231 | 232 | 0x4A6F3D8 233 | /usr/lib/libc-2.29.so 234 | _dl_catch_exception 235 | 236 | 237 | 0x400EA82 238 | /usr/lib/ld-2.29.so 239 | _dl_map_object_deps 240 | 241 | 242 | 0x40143E3 243 | /usr/lib/ld-2.29.so 244 | dl_open_worker 245 | 246 | 247 | 0x4A6F3D8 248 | /usr/lib/libc-2.29.so 249 | _dl_catch_exception 250 | 251 | 252 | 0x4013F5D 253 | /usr/lib/ld-2.29.so 254 | _dl_open 255 | 256 | 257 | 0x48EC34B 258 | /usr/lib/libdl-2.29.so 259 | 260 | 261 | 0x4A6F3D8 262 | /usr/lib/libc-2.29.so 263 | _dl_catch_exception 264 | 265 | 266 | 267 | 268 | 269 | 0x2 270 | 1 271 | Leak_StillReachable 272 | 273 | 24 bytes in 1 blocks are still reachable in loss record 3 of 8 274 | 24 275 | 1 276 | 277 | 278 | 279 | 0x483877F 280 | /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so 281 | malloc 282 | /build/valgrind/src/valgrind/coregrind/m_replacemalloc 283 | vg_replace_malloc.c 284 | 299 285 | 286 | 287 | 0x400CAC7 288 | /usr/lib/ld-2.29.so 289 | _dl_new_object 290 | 291 | 292 | 0x400731F 293 | /usr/lib/ld-2.29.so 294 | _dl_map_object_from_fd 295 | 296 | 297 | 0x4009F75 298 | /usr/lib/ld-2.29.so 299 | _dl_map_object 300 | 301 | 302 | 0x400E704 303 | /usr/lib/ld-2.29.so 304 | openaux 305 | 306 | 307 | 0x4A6F3D8 308 | /usr/lib/libc-2.29.so 309 | _dl_catch_exception 310 | 311 | 312 | 0x400EA82 313 | /usr/lib/ld-2.29.so 314 | _dl_map_object_deps 315 | 316 | 317 | 0x40143E3 318 | /usr/lib/ld-2.29.so 319 | dl_open_worker 320 | 321 | 322 | 0x4A6F3D8 323 | /usr/lib/libc-2.29.so 324 | _dl_catch_exception 325 | 326 | 327 | 0x4013F5D 328 | /usr/lib/ld-2.29.so 329 | _dl_open 330 | 331 | 332 | 0x48EC34B 333 | /usr/lib/libdl-2.29.so 334 | 335 | 336 | 0x4A6F3D8 337 | /usr/lib/libc-2.29.so 338 | _dl_catch_exception 339 | 340 | 341 | 342 | 343 | 344 | 0x3 345 | 1 346 | Leak_DefinitelyLost 347 | 348 | 185 bytes in 6 blocks are definitely lost in loss record 4 of 8 349 | 185 350 | 6 351 | 352 | 353 | 354 | 0x483AD7B 355 | /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so 356 | realloc 357 | /build/valgrind/src/valgrind/coregrind/m_replacemalloc 358 | vg_replace_malloc.c 359 | 826 360 | 361 | 362 | 0x12B6F4 363 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 364 | realloc 365 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/liballoc 366 | alloc.rs 367 | 125 368 | 369 | 370 | 0x12B6F4 371 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 372 | realloc 373 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/liballoc 374 | alloc.rs 375 | 184 376 | 377 | 378 | 0x12B6F4 379 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 380 | reserve_internal<u8,alloc::alloc::Global> 381 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/liballoc 382 | raw_vec.rs 383 | 666 384 | 385 | 386 | 0x12B6F4 387 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 388 | reserve_exact<u8,alloc::alloc::Global> 389 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/liballoc 390 | raw_vec.rs 391 | 411 392 | 393 | 394 | 0x12B6F4 395 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 396 | reserve_exact<u8> 397 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/liballoc 398 | vec.rs 399 | 482 400 | 401 | 402 | 0x12B6F4 403 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 404 | std::ffi::c_str::CString::from_vec_unchecked 405 | src/libstd/ffi 406 | c_str.rs 407 | 355 408 | 409 | 410 | 0x12B68C 411 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 412 | std::ffi::c_str::CString::_new 413 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c//src/libstd/ffi 414 | c_str.rs 415 | 330 416 | 417 | 418 | 0x125331 419 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 420 | std::ffi::c_str::CString::new 421 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/libstd/ffi 422 | c_str.rs 423 | 324 424 | 425 | 426 | 0x122BA3 427 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 428 | vk::create_instance::{{closure}} 429 | /home/jfrimmel/git/lava.rs/vk-core/src 430 | lib.rs 431 | 361 432 | 433 | 434 | 0x120BA0 435 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 436 | <core::iter::adapters::Map<I,F> as core::iter::traits::iterator::Iterator>::fold::{{closure}} 437 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/libcore/iter/adapters 438 | mod.rs 439 | 589 440 | 441 | 442 | 0x11D989 443 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 444 | <core::slice::Iter<T> as core::iter::traits::iterator::Iterator>::fold 445 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/libcore/slice 446 | mod.rs 447 | 3159 448 | 449 | 450 | 0x12087A 451 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 452 | <core::iter::adapters::Map<I,F> as core::iter::traits::iterator::Iterator>::fold 453 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/libcore/iter/adapters 454 | mod.rs 455 | 589 456 | 457 | 458 | 0x1208FA 459 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 460 | <core::iter::adapters::Map<I,F> as core::iter::traits::iterator::Iterator>::fold 461 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/libcore/iter/adapters 462 | mod.rs 463 | 589 464 | 465 | 466 | 0x12083A 467 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 468 | <core::iter::adapters::Map<I,F> as core::iter::traits::iterator::Iterator>::fold 469 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/libcore/iter/adapters 470 | mod.rs 471 | 589 472 | 473 | 474 | 0x12064C 475 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 476 | core::iter::traits::iterator::Iterator::for_each 477 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/libcore/iter/traits 478 | iterator.rs 479 | 604 480 | 481 | 482 | 0x11A76B 483 | /home/jfrimmel/git/lava.rs/target/debug/examples/creation 484 | <alloc::vec::Vec<T> as alloc::vec::SpecExtend<T,I>>::spec_extend 485 | /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/liballoc 486 | vec.rs 487 | 1862 488 | 489 | 490 | 491 | 492 | 493 | 0x4 494 | 1 495 | Leak_StillReachable 496 | 497 | 1,191 bytes in 1 blocks are still reachable in loss record 5 of 8 498 | 1191 499 | 1 500 | 501 | 502 | 503 | 0x483AB65 504 | /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so 505 | calloc 506 | /build/valgrind/src/valgrind/coregrind/m_replacemalloc 507 | vg_replace_malloc.c 508 | 752 509 | 510 | 511 | 0x400C801 512 | /usr/lib/ld-2.29.so 513 | _dl_new_object 514 | 515 | 516 | 0x400731F 517 | /usr/lib/ld-2.29.so 518 | _dl_map_object_from_fd 519 | 520 | 521 | 0x4009F75 522 | /usr/lib/ld-2.29.so 523 | _dl_map_object 524 | 525 | 526 | 0x400E704 527 | /usr/lib/ld-2.29.so 528 | openaux 529 | 530 | 531 | 0x4A6F3D8 532 | /usr/lib/libc-2.29.so 533 | _dl_catch_exception 534 | 535 | 536 | 0x400EA82 537 | /usr/lib/ld-2.29.so 538 | _dl_map_object_deps 539 | 540 | 541 | 0x40143E3 542 | /usr/lib/ld-2.29.so 543 | dl_open_worker 544 | 545 | 546 | 0x4A6F3D8 547 | /usr/lib/libc-2.29.so 548 | _dl_catch_exception 549 | 550 | 551 | 0x4013F5D 552 | /usr/lib/ld-2.29.so 553 | _dl_open 554 | 555 | 556 | 0x48EC34B 557 | /usr/lib/libdl-2.29.so 558 | 559 | 560 | 0x4A6F3D8 561 | /usr/lib/libc-2.29.so 562 | _dl_catch_exception 563 | 564 | 565 | 566 | 567 | 568 | 0x5 569 | 1 570 | Leak_StillReachable 571 | 572 | 1,416 bytes in 1 blocks are still reachable in loss record 6 of 8 573 | 1416 574 | 1 575 | 576 | 577 | 578 | 0x483AB65 579 | /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so 580 | calloc 581 | /build/valgrind/src/valgrind/coregrind/m_replacemalloc 582 | vg_replace_malloc.c 583 | 752 584 | 585 | 586 | 0x4011E1A 587 | /usr/lib/ld-2.29.so 588 | _dl_check_map_versions 589 | 590 | 591 | 0x401442D 592 | /usr/lib/ld-2.29.so 593 | dl_open_worker 594 | 595 | 596 | 0x4A6F3D8 597 | /usr/lib/libc-2.29.so 598 | _dl_catch_exception 599 | 600 | 601 | 0x4013F5D 602 | /usr/lib/ld-2.29.so 603 | _dl_open 604 | 605 | 606 | 0x48EC34B 607 | /usr/lib/libdl-2.29.so 608 | 609 | 610 | 0x4A6F3D8 611 | /usr/lib/libc-2.29.so 612 | _dl_catch_exception 613 | 614 | 615 | 0x4A6F472 616 | /usr/lib/libc-2.29.so 617 | _dl_catch_error 618 | 619 | 620 | 0x48ECAB8 621 | /usr/lib/libdl-2.29.so 622 | 623 | 624 | 0x48EC3D9 625 | /usr/lib/libdl-2.29.so 626 | dlopen 627 | 628 | 629 | 0x48BB1DD 630 | /usr/lib/libvulkan.so.1.1.115 631 | 632 | 633 | 0x48C2073 634 | /usr/lib/libvulkan.so.1.1.115 635 | vkCreateInstance 636 | 637 | 638 | 639 | 640 | 641 | 0x6 642 | 1 643 | Leak_StillReachable 644 | 645 | 4,064 bytes in 1 blocks are still reachable in loss record 7 of 8 646 | 4064 647 | 1 648 | 649 | 650 | 651 | 0x483AB65 652 | /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so 653 | calloc 654 | /build/valgrind/src/valgrind/coregrind/m_replacemalloc 655 | vg_replace_malloc.c 656 | 752 657 | 658 | 659 | 0x400B506 660 | /usr/lib/ld-2.29.so 661 | do_lookup_x 662 | 663 | 664 | 0x400B7FA 665 | /usr/lib/ld-2.29.so 666 | _dl_lookup_symbol_x 667 | 668 | 669 | 0x400D2E3 670 | /usr/lib/ld-2.29.so 671 | _dl_relocate_object 672 | 673 | 674 | 0x4014554 675 | /usr/lib/ld-2.29.so 676 | dl_open_worker 677 | 678 | 679 | 0x4A6F3D8 680 | /usr/lib/libc-2.29.so 681 | _dl_catch_exception 682 | 683 | 684 | 0x4013F5D 685 | /usr/lib/ld-2.29.so 686 | _dl_open 687 | 688 | 689 | 0x48EC34B 690 | /usr/lib/libdl-2.29.so 691 | 692 | 693 | 0x4A6F3D8 694 | /usr/lib/libc-2.29.so 695 | _dl_catch_exception 696 | 697 | 698 | 0x4A6F472 699 | /usr/lib/libc-2.29.so 700 | _dl_catch_error 701 | 702 | 703 | 0x48ECAB8 704 | /usr/lib/libdl-2.29.so 705 | 706 | 707 | 0x48EC3D9 708 | /usr/lib/libdl-2.29.so 709 | dlopen 710 | 711 | 712 | 713 | 714 | 715 | 0x7 716 | 1 717 | Leak_StillReachable 718 | 719 | 72,704 bytes in 1 blocks are still reachable in loss record 8 of 8 720 | 72704 721 | 1 722 | 723 | 724 | 725 | 0x483877F 726 | /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so 727 | malloc 728 | /build/valgrind/src/valgrind/coregrind/m_replacemalloc 729 | vg_replace_malloc.c 730 | 299 731 | 732 | 733 | 0x5AEDAEA 734 | /usr/lib/libstdc++.so.6.0.26 735 | pool 736 | /build/gcc/src/gcc/libstdc++-v3/libsupc++ 737 | eh_alloc.cc 738 | 123 739 | 740 | 741 | 0x5AEDAEA 742 | /usr/lib/libstdc++.so.6.0.26 743 | __static_initialization_and_destruction_0 744 | /build/gcc/src/gcc/libstdc++-v3/libsupc++ 745 | eh_alloc.cc 746 | 262 747 | 748 | 749 | 0x5AEDAEA 750 | /usr/lib/libstdc++.so.6.0.26 751 | _GLOBAL__sub_I_eh_alloc.cc 752 | /build/gcc/src/gcc/libstdc++-v3/libsupc++ 753 | eh_alloc.cc 754 | 338 755 | 756 | 757 | 0x4010799 758 | /usr/lib/ld-2.29.so 759 | call_init.part.0 760 | 761 | 762 | 0x40108A0 763 | /usr/lib/ld-2.29.so 764 | _dl_init 765 | 766 | 767 | 0x4014682 768 | /usr/lib/ld-2.29.so 769 | dl_open_worker 770 | 771 | 772 | 0x4A6F3D8 773 | /usr/lib/libc-2.29.so 774 | _dl_catch_exception 775 | 776 | 777 | 0x4013F5D 778 | /usr/lib/ld-2.29.so 779 | _dl_open 780 | 781 | 782 | 0x48EC34B 783 | /usr/lib/libdl-2.29.so 784 | 785 | 786 | 0x4A6F3D8 787 | /usr/lib/libc-2.29.so 788 | _dl_catch_exception 789 | 790 | 791 | 0x4A6F472 792 | /usr/lib/libc-2.29.so 793 | _dl_catch_error 794 | 795 | 796 | 0x48ECAB8 797 | /usr/lib/libdl-2.29.so 798 | 799 | 800 | 0x48EC3D9 801 | /usr/lib/libdl-2.29.so 802 | dlopen 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | -------------------------------------------------------------------------------- /src/valgrind/xml/mod.rs: -------------------------------------------------------------------------------- 1 | //! A module containing the structure of the valgrind XML output. 2 | //! 3 | //! Only the memcheck tool is implemented in accordance to [this][link] 4 | //! description]. 5 | //! 6 | //! Note, that not all fields are implemented. 7 | //! 8 | //! [link]: https://github.com/fredericgermain/valgrind/blob/master/docs/internals/xml-output-protocol4.txt 9 | #![allow(clippy::missing_docs_in_private_items)] 10 | 11 | #[cfg(test)] 12 | mod tests; 13 | 14 | use serde::{de::Visitor, Deserialize, Deserializer}; 15 | use std::fmt::{self, Display, Formatter}; 16 | 17 | /// The output of a valgrind run. 18 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] 19 | #[serde(rename = "valgrindoutput")] 20 | pub struct Output { 21 | #[serde(rename = "protocolversion")] 22 | protocol_version: ProtocolVersion, 23 | #[serde(rename = "protocoltool")] 24 | tool: Tool, 25 | #[serde(rename = "error")] 26 | pub errors: Option>, 27 | } 28 | 29 | /// The version of the XML format. 30 | /// 31 | /// Although there are also versions 1-3, there is only a variant for version 4, 32 | /// so that all older formats will fail. The other `struct`s in this file assume 33 | /// the newest protocol version. 34 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)] 35 | enum ProtocolVersion { 36 | #[serde(rename = "4")] 37 | Version4, 38 | // other formats are not supported 39 | } 40 | 41 | /// The check tool used by valgrind. 42 | /// 43 | /// Although there are other tools available, there is only a variant for the 44 | /// so-called `memcheck` tool, so that all other tools will fail. The other 45 | /// `struct`s in this file assume the memcheck output. 46 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)] 47 | enum Tool { 48 | #[serde(rename = "memcheck")] 49 | MemCheck, 50 | // other tools are not supported 51 | } 52 | 53 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] 54 | pub struct Error { 55 | #[serde(deserialize_with = "deserialize_hex")] 56 | unique: u64, 57 | pub kind: Kind, 58 | #[serde(default)] 59 | #[serde(rename = "xwhat")] 60 | pub resources: Resources, 61 | #[serde(default)] 62 | #[serde(rename = "what")] 63 | pub main_info: Option, 64 | #[serde(default)] 65 | #[serde(rename = "auxwhat")] 66 | pub auxiliary_info: Vec, 67 | #[serde(rename = "stack")] 68 | pub stack_trace: Vec, 69 | } 70 | 71 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)] 72 | pub enum Kind { 73 | #[serde(rename = "Leak_DefinitelyLost")] 74 | LeakDefinitelyLost, 75 | #[serde(rename = "Leak_StillReachable")] 76 | LeakStillReachable, 77 | #[serde(rename = "Leak_IndirectlyLost")] 78 | LeakIndirectlyLost, 79 | #[serde(rename = "Leak_PossiblyLost")] 80 | LeakPossiblyLost, 81 | InvalidFree, 82 | MismatchedFree, 83 | InvalidRead, 84 | InvalidWrite, 85 | InvalidJump, 86 | Overlap, 87 | InvalidMemPool, 88 | UninitCondition, 89 | UninitValue, 90 | SyscallParam, 91 | ClientCheck, 92 | } 93 | impl Kind { 94 | /// Query, if the current error kind is a memory leak 95 | pub(crate) const fn is_leak(self) -> bool { 96 | match self { 97 | Self::LeakDefinitelyLost 98 | | Self::LeakStillReachable 99 | | Self::LeakIndirectlyLost 100 | | Self::LeakPossiblyLost => true, 101 | Self::InvalidFree 102 | | Self::MismatchedFree 103 | | Self::InvalidRead 104 | | Self::InvalidWrite 105 | | Self::InvalidJump 106 | | Self::Overlap 107 | | Self::InvalidMemPool 108 | | Self::UninitCondition 109 | | Self::UninitValue 110 | | Self::SyscallParam 111 | | Self::ClientCheck => false, 112 | } 113 | } 114 | } 115 | impl Display for Kind { 116 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 117 | match self { 118 | Self::LeakDefinitelyLost => write!(f, "Leak (definitely lost)"), 119 | Self::LeakStillReachable => write!(f, "Leak (still reachable)"), 120 | Self::LeakIndirectlyLost => write!(f, "Leak (indirectly lost)"), 121 | Self::LeakPossiblyLost => write!(f, "Leak (possibly lost)"), 122 | Self::InvalidFree => write!(f, "invalid free"), 123 | Self::MismatchedFree => write!(f, "mismatched free"), 124 | Self::InvalidRead => write!(f, "invalid read"), 125 | Self::InvalidWrite => write!(f, "invalid write"), 126 | Self::InvalidJump => write!(f, "invalid jump"), 127 | Self::Overlap => write!(f, "overlap"), 128 | Self::InvalidMemPool => write!(f, "invalid memory pool"), 129 | Self::UninitCondition => write!(f, "uninitialized condition"), 130 | Self::UninitValue => write!(f, "uninitialized value"), 131 | Self::SyscallParam => write!(f, "syscall parameter"), 132 | Self::ClientCheck => write!(f, "client check"), 133 | } 134 | } 135 | } 136 | 137 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Deserialize)] 138 | pub struct Resources { 139 | #[serde(rename = "leakedbytes")] 140 | pub bytes: usize, 141 | #[serde(rename = "leakedblocks")] 142 | pub blocks: usize, 143 | } 144 | 145 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] 146 | pub struct Stack { 147 | #[serde(rename = "frame")] 148 | pub frames: Vec, 149 | } 150 | 151 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] 152 | pub struct Frame { 153 | #[serde(rename = "ip")] 154 | #[serde(deserialize_with = "deserialize_hex")] 155 | pub instruction_pointer: u64, 156 | #[serde(rename = "obj")] 157 | pub object: Option, 158 | #[serde(rename = "dir")] 159 | pub directory: Option, 160 | #[serde(rename = "fn")] 161 | pub function: Option, 162 | pub file: Option, 163 | pub line: Option, 164 | } 165 | impl Display for Frame { 166 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 167 | f.write_str(self.function.as_ref().map_or("unknown", |s| s.as_str()))?; 168 | if let Some(file) = &self.file { 169 | f.write_str(" (")?; 170 | f.write_str(file)?; 171 | if let Some(line) = self.line { 172 | write!(f, ":{line}")?; 173 | } 174 | f.write_str(")")?; 175 | } 176 | Ok(()) 177 | } 178 | } 179 | 180 | fn deserialize_hex<'de, D: Deserializer<'de>>(deserializer: D) -> Result { 181 | deserializer.deserialize_str(HexVisitor) 182 | } 183 | 184 | /// A visitor for parsing a `u64` in the format `0xDEADBEEF`. 185 | struct HexVisitor; 186 | impl Visitor<'_> for HexVisitor { 187 | type Value = u64; 188 | 189 | fn expecting(&self, f: &mut Formatter) -> fmt::Result { 190 | f.write_str("hexadecimal number with leading '0x'") 191 | } 192 | 193 | fn visit_str(self, value: &str) -> Result { 194 | let value = value.to_ascii_lowercase(); 195 | let value = value 196 | .strip_prefix("0x") 197 | .ok_or_else(|| E::custom("'0x' prefix missing"))?; 198 | Self::Value::from_str_radix(value, 16) 199 | .map_err(|_| E::custom(format!("invalid hex number '{value}'"))) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/valgrind/xml/tests.rs: -------------------------------------------------------------------------------- 1 | use super::{Error, Frame, Kind, Output, Resources}; 2 | use std::{fs, io::BufReader}; 3 | 4 | use serde_xml_rs::{from_reader, from_str}; 5 | 6 | #[test] 7 | fn sample_output() { 8 | let xml: Output = from_reader(BufReader::new( 9 | fs::File::open("src/valgrind/xml/memory-leaks.xml").expect("Could not open test file"), 10 | )) 11 | .expect("Could not read test file"); 12 | 13 | let errors = xml.errors.expect("There are errors in the test case"); 14 | assert_eq!(errors.len(), 8); 15 | assert_eq!(errors[0].kind, Kind::LeakDefinitelyLost); 16 | assert_eq!(errors[0].unique, 0x0); 17 | assert_eq!( 18 | errors[0].resources, 19 | Resources { 20 | bytes: 15, 21 | blocks: 1, 22 | } 23 | ); 24 | assert_eq!( 25 | &errors[0].stack_trace[0].frames[..2], 26 | &[ 27 | Frame { 28 | instruction_pointer: 0x483_AD7B, 29 | object: Some("/usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so".into()), 30 | directory: Some("/build/valgrind/src/valgrind/coregrind/m_replacemalloc".into()), 31 | function: Some("realloc".into()), 32 | file: Some("vg_replace_malloc.c".into()), 33 | line: Some(826), 34 | }, 35 | Frame { 36 | instruction_pointer: 0x12_B6F4, 37 | object: Some("/home/jfrimmel/git/lava.rs/target/debug/examples/creation".into()), 38 | directory: Some( 39 | "/rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c/src/liballoc".into() 40 | ), 41 | function: Some("realloc".into()), 42 | file: Some("alloc.rs".into()), 43 | line: Some(125), 44 | }, 45 | ] 46 | ); 47 | 48 | assert_eq!(errors[1].kind, Kind::LeakStillReachable); 49 | assert_eq!(errors[1].unique, 0x1); 50 | assert_eq!( 51 | errors[1].resources, 52 | Resources { 53 | bytes: 24, 54 | blocks: 1, 55 | } 56 | ); 57 | } 58 | 59 | #[test] 60 | fn unique_ids_have_to_be_in_hex_with_prefix() { 61 | let result: Error = from_str( 62 | r"\ 63 | 0xDEAD1234\ 64 | 1\ 65 | Leak_DefinitelyLost\ 66 | \ 67 | ...\ 68 | 15\ 69 | 1\ 70 | \ 71 | \ 72 | \ 73 | 0x483AD7B\ 74 | \ 75 | \ 76 | ", 77 | ) 78 | .expect("Could not parse test XML"); 79 | assert_eq!(result.unique, 0xDEAD_1234); 80 | } 81 | 82 | #[test] 83 | fn missing_hex_prefix_is_an_error() { 84 | let result: Result = from_str( 85 | r"\ 86 | 0DEADBEEF\ 87 | 1\ 88 | Leak_DefinitelyLost\ 89 | \ 90 | ...\ 91 | 15\ 92 | 1\ 93 | \ 94 | \ 95 | \ 96 | 0x483AD7B\ 97 | \ 98 | \ 99 | ", 100 | ); 101 | assert!(result.is_err()); 102 | 103 | let result: Result = from_str( 104 | r"\ 105 | xDEADBEEF\ 106 | 1\ 107 | Leak_DefinitelyLost\ 108 | \ 109 | ...\ 110 | 15\ 111 | 1\ 112 | \ 113 | \ 114 | \ 115 | 0x483AD7B\ 116 | \ 117 | \ 118 | ", 119 | ); 120 | assert!(result.is_err()); 121 | 122 | let result: Result = from_str( 123 | r"\ 124 | DEADBEEF\ 125 | 1\ 126 | Leak_DefinitelyLost\ 127 | \ 128 | ...\ 129 | 15\ 130 | 1\ 131 | \ 132 | \ 133 | \ 134 | 0x483AD7B\ 135 | \ 136 | \ 137 | ", 138 | ); 139 | assert!(result.is_err()); 140 | } 141 | 142 | #[test] 143 | fn invalid_hex_digits_are_an_error() { 144 | let result: Result = from_str( 145 | r"\ 146 | 0xhello\ 147 | 1\ 148 | Leak_DefinitelyLost\ 149 | \ 150 | ...\ 151 | 15\ 152 | 1\ 153 | \ 154 | \ 155 | \ 156 | 0x483AD7B\ 157 | \ 158 | \ 159 | ", 160 | ); 161 | assert!(result.is_err()); 162 | } 163 | 164 | #[test] 165 | fn hex_and_prefix_case_is_ignored() { 166 | let result: Error = from_str( 167 | r"\ 168 | 0XdEaDbEeF\ 169 | 1\ 170 | Leak_DefinitelyLost\ 171 | \ 172 | ...\ 173 | 15\ 174 | 1\ 175 | \ 176 | \ 177 | \ 178 | 0x483AD7B\ 179 | \ 180 | \ 181 | ", 182 | ) 183 | .expect("Could not parse test XML"); 184 | assert_eq!(result.unique, 0xDEAD_BEEF); 185 | } 186 | 187 | #[test] 188 | fn unique_id_is_64bit() { 189 | let result: Error = from_str( 190 | r"\ 191 | 0x123456789ABCDEF0\ 192 | 1\ 193 | Leak_DefinitelyLost\ 194 | \ 195 | ...\ 196 | 15\ 197 | 1\ 198 | \ 199 | \ 200 | \ 201 | 0x483AD7B\ 202 | \ 203 | \ 204 | ", 205 | ) 206 | .expect("Could not parse test XML"); 207 | assert_eq!(result.unique, 0x1234_5678_9ABC_DEF0); 208 | } 209 | -------------------------------------------------------------------------------- /suppressions/README.md: -------------------------------------------------------------------------------- 1 | # Curated list of suppressions for the Rust Standard Library 2 | 3 | ## Background 4 | Valgrind may detect leaks in the Rust standard library as it has done in the past (e.g. [here][rust1.83] and [here][beta]). 5 | Those "leaks" of Valgrind are not considered as leaks by the Rust team when they are small permanent allocation, which is not growing (as described [here][comment-1]) and there is no guarantee, that the Rust standard library is free of memory leaks (as described [here][comment-2]). 6 | Therefore some reports of Valgrind are entirely non-actionable by a user of this crate. 7 | 8 | In order to solve this, this directory contains a list of suppressions for the Rust `std`. 9 | Those are applied automatically, so that those "leaks" are never reported. 10 | 11 | ## When to add new suppressions? 12 | This repository runs a [periodic test against the beta compiler][beta-job], that there are no new leaks in the standard library. 13 | If such a new leak is detected, an issue is this repository is created, so that actions can be performed. 14 | Then there should be two steps: 15 | 1. report the issue to the [Rust-project][new-rust-issue] similar to [this one][example-issue] 16 | 2. if this leak is accepted over there, then a suppression should be added here 17 | 18 | ## How to add a new suppression? 19 | Create a minimal reproducer of the leak (often a blank cargo project is enough, since the leak is part of the runtime). 20 | Then execute the following commands: 21 | ```shell 22 | $ cargo new --lib reproducer 23 | $ cd reproducer 24 | $ cargo test --lib # note down the path executed, as this is required here ↓↓↓↓↓ 25 | $ valgrind --leak-check=full --gen-suppressions=yes target/debug/reproducer-$HASH 26 | ``` 27 | Valgrind will generate output in a form suitable to be used as suppressions. 28 | That output should be trimmed down as necessary and then added to this directory. 29 | 30 | 31 | [rust1.83]: https://github.com/rust-lang/rust/issues/133574 32 | [beta]: https://github.com/rust-lang/rust/issues/138430 33 | [comment-1]: https://github.com/rust-lang/rust/issues/133574#issuecomment-2506547194 34 | [comment-2]: https://github.com/rust-lang/rust/issues/135608#issuecomment-2597205627 35 | [beta-job]: https://github.com/jfrimmel/cargo-valgrind/actions/workflows/beta.yaml 36 | [new-rust-issue]: https://github.com/rust-lang/rust/issues/new?template=regression.md 37 | [example-issue]: https://github.com/rust-lang/rust/issues/138430 38 | -------------------------------------------------------------------------------- /suppressions/rust-1.83: -------------------------------------------------------------------------------- 1 | { 2 | Rust 1.83 standard library 3 | Memcheck:Leak 4 | match-leak-kinds: possible 5 | fun:malloc 6 | fun:alloc 7 | fun:alloc_impl 8 | fun:allocate 9 | fun:{closure#0} 10 | fun:allocate_for_layout, alloc::sync::{impl#14}::new_uninit::{closure_env#0}, fn(*mut u8) -> *mut alloc::sync::ArcInner>> 11 | fun:new_uninit 12 | fun:new_inner 13 | fun:new_main 14 | fun:init 15 | fun:{closure#0} 16 | fun:do_call 17 | fun:try<(), std::rt::lang_start_internal::{closure_env#0}> 18 | fun:catch_unwind 19 | fun:_ZN3std2rt19lang_start_internal* 20 | fun:_ZN3std2rt10lang_start* 21 | fun:main 22 | } 23 | -------------------------------------------------------------------------------- /suppressions/rust-1.86: -------------------------------------------------------------------------------- 1 | { 2 | Rust 1.86 standard library 3 | Memcheck:Leak 4 | match-leak-kinds: possible 5 | fun:malloc 6 | fun:alloc 7 | fun:alloc_impl 8 | fun:allocate 9 | fun:{closure#0} 10 | fun:allocate_for_layout, alloc::sync::{impl#14}::new_uninit::{closure_env#0}, fn(*mut u8) -> *mut alloc::sync::ArcInner>> 11 | fun:new_uninit 12 | fun:_ZN3std6thread6Thread3new* 13 | fun:_ZN3std6thread7current12init_current* 14 | fun:current_or_unnamed 15 | } 16 | -------------------------------------------------------------------------------- /tests/a-default-cargo-project.rs: -------------------------------------------------------------------------------- 1 | //! Tests against an almost empty crate generated by `cargo new --lib`. 2 | use assert_cmd::Command; 3 | 4 | fn cargo_valgrind() -> Command { 5 | let mut cmd = Command::cargo_bin("cargo-valgrind").unwrap(); 6 | cmd.arg("valgrind"); 7 | cmd 8 | } 9 | 10 | /// Test, that running `cargo valgrind test` does not result in any reported 11 | /// leaks. This is the "minimal" crate (in the sense, that people might start 12 | /// with such a minimal crate to experiment with `cargo valgrind`), where 13 | /// `cargo valgrind` must never report any issues. If that test fails, then 14 | /// there is a leak in the Rust standard library, which is reported to be 15 | /// [allowed][leak-issue]: 16 | /// 17 | /// > Based on the consensus in rust-lang/rust#133574, non-default full 18 | /// > leakcheck is "convenience if we can, but no hard guarantees", [...]. 19 | /// 20 | /// [leak-issue]: https://github.com/rust-lang/rust/issues/135608#issuecomment-2597205627 21 | #[test] 22 | fn default_cargo_project_reports_no_violations() { 23 | cargo_valgrind() 24 | .arg("test") 25 | .args(["--manifest-path", "tests/default-new-project/Cargo.toml"]) 26 | .assert() 27 | .success(); 28 | } 29 | -------------------------------------------------------------------------------- /tests/cli.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::Command; 2 | 3 | fn cargo_valgrind() -> Command { 4 | let mut cmd = Command::cargo_bin("cargo-valgrind").unwrap(); 5 | cmd.arg("valgrind"); 6 | cmd 7 | } 8 | 9 | const TARGET_CRATE: &[&str] = &["--manifest-path", "tests/ffi-bug/Cargo.toml"]; 10 | 11 | #[test] 12 | fn leak_detected() { 13 | cargo_valgrind() 14 | .arg("run") 15 | .args(TARGET_CRATE) 16 | .assert() 17 | .failure(); 18 | } 19 | 20 | #[test] 21 | fn examples_are_runnable() { 22 | cargo_valgrind() 23 | .args(["run", "--example", "no-leak"]) 24 | .args(TARGET_CRATE) 25 | .assert() 26 | .success() 27 | .stdout("Hello, world!\n"); 28 | } 29 | 30 | #[test] 31 | fn tests_are_runnable() { 32 | cargo_valgrind() 33 | .arg("test") 34 | .args(TARGET_CRATE) 35 | .assert() 36 | .success(); 37 | } 38 | 39 | #[test] 40 | fn help_is_supported() { 41 | cargo_valgrind() 42 | .arg("--help") 43 | .assert() 44 | .success() 45 | .stdout(predicates::str::contains("cargo valgrind")); 46 | } 47 | -------------------------------------------------------------------------------- /tests/corpus/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /tests/corpus/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "corpus" 7 | version = "0.0.0" 8 | -------------------------------------------------------------------------------- /tests/corpus/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "corpus" 3 | version = "0.0.0" 4 | publish = false 5 | 6 | [[bin]] 7 | name = "issue-13" 8 | path = "issue-13.rs" 9 | 10 | [[bin]] 11 | name = "issue-74" 12 | path = "issue-74.rs" 13 | 14 | [[bin]] 15 | name = "issue-20" 16 | path = "issue-20.rs" 17 | 18 | [[bin]] 19 | name = "issue-70" 20 | path = "issue-70.rs" 21 | -------------------------------------------------------------------------------- /tests/corpus/issue-13.rs: -------------------------------------------------------------------------------- 1 | fn stack_overflow() { 2 | stack_overflow() 3 | } 4 | 5 | fn main() { 6 | stack_overflow(); 7 | } 8 | -------------------------------------------------------------------------------- /tests/corpus/issue-20.rs: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | fn malloc(n: usize) -> *mut std::ffi::c_void; 3 | fn free(ptr: *mut std::ffi::c_void); 4 | } 5 | 6 | fn main() { 7 | let ptr = unsafe { malloc(8) }; 8 | unsafe { free(ptr) }; 9 | unsafe { free(ptr) }; 10 | } 11 | -------------------------------------------------------------------------------- /tests/corpus/issue-70.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | for (key, value) in std::env::vars_os() { 3 | println!("{}={}", key.to_string_lossy(), value.to_string_lossy()) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/corpus/issue-74.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_variables)] 3 | 4 | fn ex1() { 5 | let mut num = 6; 6 | 7 | let r1 = &num as *const i32; 8 | let r2 = &mut num as *mut i32; 9 | unsafe { 10 | println!("r1: {:?}", *r1); 11 | println!("r2: {:?}", *r2); 12 | } 13 | } 14 | fn ex2() { 15 | let address = 0x_012345_usize; 16 | let r = address as *const i32; 17 | unsafe { 18 | // error? 19 | println!("r={}", *r); 20 | } 21 | } 22 | fn ex3() { 23 | unsafe fn dangerous() {} 24 | unsafe { dangerous() } 25 | } 26 | fn ex4() { 27 | let mut v: Vec<_> = (1..7).collect(); 28 | let r = &mut v[..]; 29 | let (a, b) 30 | = split_at_mut(r, 4); 31 | 32 | println!("a={:?} (should be [1, 2, 3, 4]", a); 33 | drop(a); 34 | println!("b={:?} (should be [5, 6])", b); 35 | // assert_eq!(a, &mut[1, 2, 3, 4]); 36 | // assert_eq!(b, &mut[5, 6]); 37 | } 38 | fn split_at_mut(values: &mut[i32], ind: usize) 39 | -> (&mut[i32], &mut[i32]) { 40 | 41 | use std::slice::from_raw_parts_mut as part; 42 | 43 | assert!(ind < values.len()); 44 | 45 | let len = values.len(); 46 | let ptr = values.as_mut_ptr(); 47 | 48 | unsafe {( 49 | part(ptr, ind), 50 | // bug! 51 | part(ptr.add(ind + 1), len - ind) 52 | )} 53 | } 54 | extern "C" { 55 | fn abs(input: i32) -> i32; 56 | } 57 | static mut COUNTER: u32 = 0; 58 | fn add_to_cound(inc: u32) { 59 | unsafe { COUNTER += inc } 60 | } 61 | fn ex5() { 62 | add_to_cound(3); 63 | unsafe { println!("{}", COUNTER) } 64 | } 65 | fn main() { 66 | ex4() 67 | } 68 | unsafe trait Doo {} 69 | unsafe impl Doo for i32 {} -------------------------------------------------------------------------------- /tests/default-new-project/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /tests/default-new-project/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "default-new-project" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/default-new-project/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "default-new-project" 3 | version = "0.1.0" 4 | edition = "2021" # set to 2021 for MSRV-compatibility 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/default-new-project/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(left: u64, right: u64) -> u64 { 2 | left + right 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn it_works() { 11 | let result = add(2, 2); 12 | assert_eq!(result, 4); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/ffi-bug/.gitignore: -------------------------------------------------------------------------------- 1 | target/ -------------------------------------------------------------------------------- /tests/ffi-bug/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ffi-bug" 5 | version = "0.0.0" 6 | -------------------------------------------------------------------------------- /tests/ffi-bug/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ffi-bug" 3 | version = "0.0.0" 4 | edition = "2018" 5 | publish = false 6 | 7 | [workspace] 8 | -------------------------------------------------------------------------------- /tests/ffi-bug/examples/no-leak.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /tests/ffi-bug/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | use std::os::raw::c_char; 3 | 4 | extern "C" { 5 | fn puts(s: *const c_char); 6 | } 7 | 8 | fn main() { 9 | let string = CString::new("Test").unwrap(); 10 | 11 | let ptr = string.into_raw(); 12 | unsafe { puts(ptr) }; 13 | 14 | // unsafe { CString::from_raw(ptr) }; 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | #[test] 20 | fn t1() {} 21 | #[test] 22 | fn t2() {} 23 | #[test] 24 | fn t3() {} 25 | #[test] 26 | fn t4() {} 27 | #[test] 28 | fn t5() {} 29 | } 30 | -------------------------------------------------------------------------------- /tests/regression.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::Command; 2 | 3 | fn cargo_valgrind() -> Command { 4 | let mut cmd = Command::cargo_bin("cargo-valgrind").unwrap(); 5 | cmd.arg("valgrind"); 6 | cmd 7 | } 8 | 9 | const TARGET_CRATE: &[&str] = &["--manifest-path", "tests/corpus/Cargo.toml"]; 10 | 11 | /// Issues: [#55], [#68], [#74]. 12 | /// 13 | /// [#55]: https://github.com/jfrimmel/cargo-valgrind/issues/55 14 | /// [#68]: https://github.com/jfrimmel/cargo-valgrind/issues/68 15 | /// [#74]: https://github.com/jfrimmel/cargo-valgrind/issues/74 16 | #[test] 17 | fn duplicate_stack_fields() { 18 | cargo_valgrind() 19 | .arg("run") 20 | .args(TARGET_CRATE) 21 | .arg("--bin=issue-74") 22 | .assert() 23 | .failure() 24 | .stderr(predicates::str::contains("Error Invalid read of size 4")) 25 | .stderr(predicates::str::contains( 26 | "Summary Leaked 0 B total (1 other errors)", 27 | )); 28 | } 29 | 30 | /// Issue: [#13] 31 | /// 32 | /// [#13]: https://github.com/jfrimmel/cargo-valgrind/issues/13 33 | #[test] 34 | fn stack_overflow_in_program_under_test() { 35 | let _delete_all_vg_core_files_on_exit = DeleteVgCoreFiles; 36 | 37 | cargo_valgrind() 38 | .arg("run") 39 | .args(TARGET_CRATE) 40 | .arg("--bin=issue-13") 41 | .assert() 42 | .failure() 43 | .stderr(predicates::str::contains( 44 | "looks like the program overflowed its stack", 45 | )); 46 | } 47 | 48 | /// Issue: [#20] 49 | /// 50 | /// [#20]: https://github.com/jfrimmel/cargo-valgrind/issues/20 51 | #[test] 52 | fn invalid_free() { 53 | cargo_valgrind() 54 | .arg("run") 55 | .args(TARGET_CRATE) 56 | .arg("--bin=issue-20") 57 | .assert() 58 | .failure() 59 | .stderr(predicates::str::contains("Error Invalid free")) 60 | .stderr(predicates::str::contains( 61 | "is 0 bytes inside a block of size 8 free'd", 62 | )) 63 | .stderr(predicates::str::contains("Info Block was alloc'd at")); 64 | } 65 | 66 | /// Issue: [#70] 67 | /// 68 | /// [#70]: https://github.com/jfrimmel/cargo-valgrind/issues/70 69 | #[test] 70 | fn environment_variables_are_passed_to_program_under_test() { 71 | cargo_valgrind() 72 | .arg("run") 73 | .args(TARGET_CRATE) 74 | .arg("--bin=issue-70") 75 | .env("RUST_LOG", "debug") 76 | .assert() 77 | .stdout(predicates::str::contains("RUST_LOG=debug")); 78 | } 79 | 80 | /// If a program crashes within running it in Valgrind, a `vgcore.`-file 81 | /// might be created in the current working directory. In order to not clutter 82 | /// the main project directory, this type can be used as a drop-guard to delete 83 | /// all `vgcore.*`-files within a test by using it like this: 84 | /// ``` 85 | /// let _delete_all_vg_core_files_on_exit = DeleteVgCoreFiles; 86 | /// ``` 87 | /// Note, that this deletes all found `vgcore.*`-files, not only the one created 88 | /// by this specific test. One should add this drop-guard to each crashing test 89 | /// nevertheless, as else the files will be left over, if only that specific 90 | /// test is run (e.g. due to test filtering). 91 | struct DeleteVgCoreFiles; 92 | impl Drop for DeleteVgCoreFiles { 93 | fn drop(&mut self) { 94 | std::fs::read_dir(".") 95 | .unwrap() 96 | .filter_map(|entry| entry.ok()) 97 | .filter(|entry| entry.file_type().map_or(false, |type_| type_.is_file())) 98 | .filter(|file| match file.file_name().into_string() { 99 | Ok(name) if name.starts_with("vgcore.") => true, 100 | _ => false, 101 | }) 102 | .for_each(|vg_core| std::fs::remove_file(vg_core.path()).unwrap()); 103 | } 104 | } 105 | --------------------------------------------------------------------------------