diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..b92be7c --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,4 @@ +[alias] +bless = "test --config env.RUSTC_BLESS='1'" +uitest = "test --test compile-test" +uibless = "bless --test compile-test" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4b919a1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.stderr text eol=lf +*.stdout text eol=lf \ No newline at end of file diff --git a/.github/workflows/cargo-vet.yml b/.github/workflows/cargo-vet.yml index 864c138..98aca0a 100644 --- a/.github/workflows/cargo-vet.yml +++ b/.github/workflows/cargo-vet.yml @@ -16,7 +16,7 @@ jobs: name: vet-dependencies runs-on: ubuntu-latest env: - CARGO_VET_VERSION: 0.10.1 + CARGO_VET_VERSION: 0.10.2 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 8f2b035..ae4f950 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -1,115 +1,48 @@ -# This workflow runs whenever a PR is opened or updated, or a commit is pushed to main. It runs -# several checks: -# - commit_list: produces a list of commits to be checked -# - fmt: checks that the code is formatted according to rustfmt -# - clippy: checks that the code does not contain any clippy warnings -# - doc: checks that the code can be documented without errors -# - hack: check combinations of feature flags -# - msrv: check that the msrv specified in the crate is correct +name: CGA Check + permissions: contents: read -# This configuration allows maintainers of this repo to create a branch and pull request based on -# the new branch. Restricting the push trigger to the main branch ensures that the PR only gets -# built once. + on: push: branches: [main] pull_request: -# If new code is pushed to a PR branch, then cancel in progress workflows for that PR. Ensures that -# we don't waste CI time, and returns results quicker https://github.com/jonhoo/rust-ci-conf/pull/5 -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true -name: check -jobs: - fmt: - runs-on: ubuntu-latest - name: stable / fmt - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install stable - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt - - name: cargo fmt --check - run: cargo fmt --check - - # Enable once we have a released crate - # semver: - # runs-on: ubuntu-latest - # name: semver - # strategy: - # fail-fast: false - # steps: - # - uses: actions/checkout@v4 - # with: - # submodules: true - # - name: Install stable - # uses: dtolnay/rust-toolchain@stable - # with: - # components: rustfmt - # - name: cargo-semver-checks - # uses: obi1kenobi/cargo-semver-checks-action@v2 - - doc: - # run docs generation on nightly rather than stable. This enables features like - # https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an - # API be documented as only available in some specific platforms. - runs-on: ubuntu-latest - name: nightly / doc - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install nightly - uses: dtolnay/rust-toolchain@nightly - - name: cargo doc - run: cargo doc --no-deps --all-features - env: - RUSTDOCFLAGS: --cfg docsrs + branches: [main] - hack-clippy: - # cargo-hack checks combinations of feature flags to ensure that features are all additive - # which is required for feature unification +jobs: + check: runs-on: ubuntu-latest - name: ${{ matrix.toolchain }} / ${{ matrix.target }} / clippy - strategy: - fail-fast: false - matrix: - # Get early warning of new lints which are regularly introduced in beta channels. - toolchain: [stable, beta] - target: [x86_64-unknown-linux-gnu, thumbv8m.main-none-eabihf] + name: check steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install ${{ matrix.toolchain }} - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} - components: clippy - - name: cargo install cargo-hack - uses: taiki-e/install-action@cargo-hack - # intentionally no target specifier; see https://github.com/jonhoo/rust-ci-conf/pull/4 - # --feature-powerset runs for every combination of features - - name: cargo hack - run: cargo hack --feature-powerset --target ${{ matrix.target }} clippy --locked -- -Dwarnings -D clippy::suspicious -D clippy::correctness -D clippy::perf -D clippy::style + - name: Checkout + uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Install toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Fetch dependencies + run: cargo fetch --locked + - name: Format check + run: cargo fmt --all --check + - name: Build + run: | + cargo build --locked + cargo build --locked --tests + - name: Clippy + run: cargo clippy --locked + - name: Test + run: cargo test --locked deny: # cargo-deny checks licenses, advisories, sources, and bans for # our dependencies. runs-on: ubuntu-latest - name: ubuntu / stable / deny + name: deny steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: - submodules: true - - name: Install stable - uses: dtolnay/rust-toolchain@stable - - name: cargo install cargo-deny + persist-credentials: false + - name: Deny uses: EmbarkStudios/cargo-deny-action@v2 with: log-level: warn @@ -117,54 +50,18 @@ jobs: command: check arguments: --all-features --locked - test: - runs-on: ubuntu-latest - name: ubuntu / stable / test - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install stable - uses: dtolnay/rust-toolchain@stable - - name: cargo install cargo-hack - uses: taiki-e/install-action@cargo-hack - - name: cargo test - run: cargo hack --feature-powerset test --locked - - name: cargo clippy test - run: cargo hack --feature-powerset clippy --tests --locked -- -Dwarnings -D clippy::suspicious -D clippy::correctness -D clippy::perf -D clippy::style - - msrv: - # check that we can build using the minimal rust version that is specified by this crate - runs-on: ubuntu-latest - # we use a matrix here just because env can't be used in job names - # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability - strategy: - fail-fast: false - matrix: - msrv: ["1.85"] - name: ubuntu / ${{ matrix.msrv }} - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install ${{ matrix.msrv }} - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ matrix.msrv }} - - name: cargo +${{ matrix.msrv }} check - run: cargo check --locked - machete: # cargo-machete checks for unused dependencies in Cargo.toml files runs-on: ubuntu-latest - name: ubuntu / stable / machete + name: machete steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: - submodules: true - - name: Install stable - uses: dtolnay/rust-toolchain@stable - - name: cargo install cargo-machete + persist-credentials: false + - name: Install toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Install cargo-machete uses: taiki-e/install-action@cargo-machete - - name: cargo machete + - name: Machete run: cargo machete + diff --git a/.github/workflows/nostd.yml b/.github/workflows/nostd.yml deleted file mode 100644 index 8fa3551..0000000 --- a/.github/workflows/nostd.yml +++ /dev/null @@ -1,30 +0,0 @@ -# This workflow checks whether the library is able to run without the std library (e.g., embedded). -# This entire file should be removed if this crate does not support no-std. See check.yml for -# information about how the concurrency cancellation and workflow triggering works -permissions: - contents: read -on: - push: - branches: [main] - pull_request: -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true -name: no-std -jobs: - nostd: - runs-on: ubuntu-latest - name: ${{ matrix.target }} - strategy: - matrix: - target: [thumbv8m.main-none-eabihf] - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install stable - uses: dtolnay/rust-toolchain@stable - - name: rustup target add ${{ matrix.target }} - run: rustup target add ${{ matrix.target }} - - name: cargo check - run: cargo check --target ${{ matrix.target }} --locked diff --git a/.gitignore b/.gitignore index 26fc367..7545121 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,10 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ +/target +main +rustc-ice-* +/tests/**/target +callgraph.json -# Avoid ignoring the lock file by default, if you want to check it in, do so manually -# Cargo.lock +*/Cargo.lock -# These are backup files generated by rustfmt **/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information *.pdb diff --git a/.vscode/settings.json b/.vscode/settings.json index ace00d4..9108093 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,14 +1,3 @@ { - "editor.formatOnSave": true, - "rust-analyzer.checkOnSave": true, - "rust-analyzer.check.allTargets": false, - "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", - "rust-analyzer.cargo.features": "all", - "rust-analyzer.check.command": "clippy", - "[toml]": { - "editor.defaultFormatter": "tamasfe.even-better-toml" - }, - "[rust]": { - "editor.defaultFormatter": "rust-lang.rust-analyzer" - } -} + "rust-analyzer.rustc.source": "discover" +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index f1c4f76..a3c9472 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,5 +3,887 @@ version = 4 [[package]] -name = "embedded-rust-template" +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "annotate-snippets" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" +dependencies = [ + "anstyle", + "unicode-width", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "bstr" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79" +dependencies = [ + "memchr", + "regex-automata", + "serde_core", +] + +[[package]] +name = "camino" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2d30e4173c4026932d51d31d6b0613b1fd3014bf3f9f8943d4ba139c437ba0" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122ec45a44b270afd1402f351b782c676b173e3c3fb28d86ff7ebfb4d86a4ee4" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cga" +version = "0.1.0" +dependencies = [ + "cga_analysis", + "cga_cargo", + "cga_data", + "serde_json", + "ui_test", +] + +[[package]] +name = "cga_analysis" +version = "0.1.0" +dependencies = [ + "cga_data", + "indexmap", + "itertools", + "lazy-regex", +] + +[[package]] +name = "cga_cargo" version = "0.1.0" +dependencies = [ + "anyhow", + "cga_data", + "clap", + "lazy-regex", + "serde_json", + "toml", + "tracing-subscriber", +] + +[[package]] +name = "cga_data" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "comma" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "lazy-regex" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bae91019476d3ec7147de9aa291cadb6d870abf2f3015d2da73a90325ac1496" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de9c1e1439d8b7b3061b2d209809f447ca33241733d9a3c01eabf2dc8d94358" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "owo-colors" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "prettydiff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac17546d82912e64874e3d5b40681ce32eac4e5834344f51efcf689ff1550a65" +dependencies = [ + "owo-colors", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustfix" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fa69b198d894d84e23afde8e9ab2af4400b2cba20d6bf2b428a8b01c222c5a" +dependencies = [ + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "spanned" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4b0c055fde758f086eb4a6e73410247df8a3837fd606d2caeeaf72aa566d" +dependencies = [ + "anyhow", + "bstr", + "color-eyre", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "ui_test" +version = "0.30.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c8811281d587a786747c0c49245925016c07767bc996305bdd34d5ce076786a" +dependencies = [ + "annotate-snippets", + "anyhow", + "bstr", + "cargo-platform", + "cargo_metadata", + "color-eyre", + "colored", + "comma", + "crossbeam-channel", + "levenshtein", + "prettydiff", + "regex", + "rustc_version", + "rustfix", + "serde", + "serde_json", + "spanned", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index d8d9f3f..089aa33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,52 @@ [package] -name = "embedded-rust-template" +name = "cga" version = "0.1.0" -edition = "2021" +description = "Call graph analysis tool for Rust" +repository = "https://github.com/OpenDevicePartnership/cga" +authors = ["Wouter Geraedts "] +readme = "README.md" license = "MIT" -repository = "https://github.com/OpenDevicePartnership/embedded-rust-template" -rust-version = "1.85" +keywords = ["lint", "plugin"] +categories = ["development-tools", "development-tools::cargo-plugins"] +build = "build.rs" +edition = "2024" +publish = false + +[[bin]] +name = "cargo-cga" +test = false +path = "bin/cargo-cga.rs" + +[[bin]] +name = "cga-driver" +path = "bin/cga-driver.rs" [dependencies] -# dependencies for all targets +cga_analysis = { path = "cga_analysis" } +cga_data = { path = "cga_data" } +cga_cargo = { path = "cga_cargo" } + +serde_json = "1.0" + +[dev-dependencies] +ui_test = { version = "0.30.7", default-features = false, features = ["rustc"] } + +[features] + +[package.metadata.rust-analyzer] +# This package uses #[feature(rustc_private)] +rustc_private = true + +[[test]] +name = "compile-test" +harness = false -[target.'cfg(target_os = "none")'.dependencies] -# dependencies for no-std targets +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = ['cfg(bootstrap)'] [lints.clippy] suspicious = "deny" correctness = "deny" perf = "deny" -style = "deny" +style = "deny" \ No newline at end of file diff --git a/LICENSE b/LICENSE index 2b120f1..6d14cbb 100644 --- a/LICENSE +++ b/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index 60b09c0..36b7d1c 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,173 @@ -# embedded-rust-template -Template repository for Embedded Rust development +# Call Graph Analyzer (CGA) +Rust tool to perform call graph analysis on the monomorphized MIR. -## Customizing This Template +Explicit goal is to be able to recursively check for an application to panic. +Only works on crates with 'entrypoints', so binary crates with a `fn main` and library crates containing symbols with exported names. (`no_mangle` and `export_name`) -### Changing the Target Architecture +As a rustc-driver, it uses a specific nightly version to be built, installed and run. Your project does not gain the dependency on nightly though, unless you wish to finely grained configure (i.e. silence) diagnostic lint warnings emitted by this tool. In that case it requires nightly rust to register as a rustc tool (but you can conditionally enable this with `cfg_attr`). -This template is configured for `thumbv8m.main-none-eabihf`, by default, but you can modify it for other targets (i.e. `aarch64-unknown-none`): +This tool also supports a separate configuration file in which specific functions/paths/modules/crates can be whitelisted or blacklisted. This feature does not require nightly for your project. -1. **VSCode Settings**: Update the target in `.vscode/settings.json`: - ```json - "rust-analyzer.cargo.target": "your-target-architecture" - ``` +⚠️ **Warning**: CGA is inherently a bit fragile as it is not maintained in lockstep with rustc (like clippy is for example), and it is not exhaustively tested on various crates (like rustc and clippy are). Please create an issue if your project can not be analysed using this tool, or if the callgraph exported is incorrect. +## Attribution +Based on a blog by [Jyn](https://jyn.dev/rustc-driver/) and completely forked parts of [rustc](https://github.com/rust-lang/rust) and [ferrocene](https://github.com/ferrocene/ferrocene/tree/main/compiler). -This configuration ensures that: -- Only the specified target architecture is analyzed, not the host platform -- Code is checked against the no_std environment +## Alternatives +CGA extracts the callgraph directly from MIR as a rustc-driver. That approach can work well to detect panics, but it is not the only way to achieve it. All approaches come with varying up- and down-sides. -To temporarily analyze code for the host platform instead, you can remove the `rust-analyzer.cargo.target` setting. +Other ways to extract callgraphs or detect panics include: +* [rustig](https://github.com/Technolution/rustig) (ELF debug info analysis) +* [findpanics](https://github.com/philipc/findpanics) (ELF debug info analysis) +* [cargo-callgraph](https://github.com/robinmoussu/cargo-callgraph) (Rust HIR analyzer) +* [cargo-callgraph](https://crates.io/crates/cargo-callgraph) (Rust-analyzer based exporter) +* [charon](https://github.com/AeneasVerif/charon) (Rust MIR exporter) +* [no-panic](https://crates.io/crates/no-panic) (Linker-based panic detection) +* [dont_panic](https://github.com/Kixunil/dont_panic) (Linker-based panic detection) -2. **GitHub Workflows**: Modify the target in two workflow files: - - `.github/workflows/nostd.yml`: Update the targets in the matrix: - ```yaml - matrix: - target: [your-target-architecture] - ``` - - `.github/workflows/check.yml`: If there are any target-specific checks, update them accordingly. +Please let us know if a project is missing from this list. -3. **Cargo Configuration**: If needed, you can add target-specific configuration in a `.cargo/config.toml` file. +## Examples -### Converting from Binary to Library +### Panic +``` +error: call to method `core::num::::strict_add` which can panic + --> tests/ui/dyn_trait.rs:18:17 + | +LL | let _ = 2u64.strict_add(500); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: 3 different panics from this location + = note: backtrace (1 of 2): + 0: std::rt::panic_fmt (called) + 1: core::num::imp::overflow_panic::add (called) + 2: core::num::::strict_add (called) <-- + 3: ::test (possible vtable resolution) + 4: ::test - virtual#3 (called) + 5: dyn_trait::main (entrypoint) +``` + +### Blacklist +``` +error: call to drop of `std::process::Command` which can call blacklisted functions + --> src/main.rs:7:47 + | +7 | let _ = std::process::Command::new("evil"); + | ^ + | + = note: 105 different blacklisted functions from this location + = note: backtrace (1 of 42169425): + 0: std::sys::fs::unix::debug_assert_fd_is_open (called) + 1: ::drop (called) + 2: drop of `std::os::fd::OwnedFd` (called) + 3: drop of `std::sys::fd::unix::FileDesc` (called) + 4: drop of `std::sys::process::unix::common::Stdio` (called) + 5: drop of `std::option::Option` (called) + 6: drop of `std::sys::process::unix::common::Command` (called) + 7: drop of `std::process::Command` (called) <-- + 8: fail_blacklist::test (called) + 9: fail_blacklist::main (entrypoint) +``` -To convert this project from a binary to a library: +## How to use +To install CGA you must check out this repository and run: +```bash +cargo install --path . +``` -1. **Cargo.toml**: Update your project structure: - ```toml - [lib] - name = "your_library_name" - ``` +The tool then works like a cargo subcommand, which you can inspect using +``` +$ cargo cga -h +Usage: cga -2. **Directory Structure**: - - For a library, ensure you have a `src/lib.rs` file instead of `src/main.rs` - - Move your code from `main.rs` to `lib.rs` and adjust as needed +Commands: + check Checks a package to catch whether functions are used that are not allowed, such as panics + extract Extracts the callgraph for a package and all their dependencies + help Print this message or the help of the given subcommand(s) -3. **No-std Configuration**: If you're creating a no-std library, ensure you have: - ```rust - // In lib.rs - #![cfg_attr(target_os = "none", no_std)] - // Add other attributes as needed - ``` +Options: + -h, --help Print help +``` -### Project Dependencies +### Checking +``` +$ cargo cga check -h +Checks a package to catch whether functions are used that are not allowed, such as panics -Update the dependencies in `Cargo.toml` based on your target platform: +Usage: cga check [OPTIONS] +Options: + --allow-async-panic Configure the cga::panics lint to allow implicit panics from async generation + --allow-assert Configure the cga::panics lint to allow implicit asserts + --config-path Path to a TOML file containing rules + -h, --help +``` + +When checking options can also be passed: +``` +cargo cga check --config-path ./cga.toml --allow-assert --allow-async-panic +``` + +With the following example configuration file: ```toml -[dependencies] -# Common dependencies for all targets +whitelist = [ + { crate = "core" }, + { canonical = "^std::ptr::drop_glue:: Result<(), i32> { + cga_cargo::main() +} diff --git a/bin/cga-driver.rs b/bin/cga-driver.rs new file mode 100644 index 0000000..13c5e1b --- /dev/null +++ b/bin/cga-driver.rs @@ -0,0 +1,128 @@ +#![feature(rustc_private)] +extern crate rustc_driver; +extern crate rustc_errors; +extern crate rustc_hir; +extern crate rustc_interface; +extern crate rustc_lint; +extern crate rustc_middle; +extern crate rustc_mir_transform; +extern crate rustc_monomorphize; +extern crate rustc_session; +extern crate rustc_span; + +use std::process::ExitCode; +use std::{env, fs::File}; + +use rustc_driver::Callbacks; +use rustc_interface::interface; +use rustc_lint::LintStore; +use rustc_middle::ty::TyCtxt; +use rustc_session::EarlyDiagCtxt; +use rustc_session::config::ErrorOutputType; +use rustc_span::Symbol; + +use cga_analysis::{BLACKLISTED, PANICS}; + +struct Cga; + +fn post_mono_pass<'tcx>( + tcx: TyCtxt<'tcx>, + _: (), +) -> rustc_middle::queries::collect_and_partition_mono_items::ProvidedValue<'tcx> { + let items = (rustc_interface::passes::DEFAULT_QUERY_PROVIDERS + .queries + .collect_and_partition_mono_items)(tcx, ()); + + if let Ok(input) = std::env::var("CGA_INPUT") { + let input: cga_data::cmd::DriverInput = serde_json::from_str(&input).expect("Invalid CGA_INPUT format"); + + let roots = cga_analysis::monomorphize::collect_roots( + tcx, + cga_analysis::monomorphize::MonoItemCollectionStrategy::NoConstOrStatic, + ) + .into_iter() + .collect(); + + let callgraph = cga_analysis::post_mono::generate_callgraph(tcx, roots); + + if let Some(config) = input.lint { + cga_analysis::diagnostics::emit_lints(callgraph.clone(), &config, tcx); + } + + if input.extract { + let filename = tcx.output_filenames(()).with_extension("callgraph.json"); + + let callgraph = callgraph.export(tcx); + serde_json::to_writer_pretty(File::create(&filename).unwrap(), &callgraph).unwrap(); + + eprintln!("cga-emit:{}", filename.display()); + + // Emit that we generated an artifact (but currently cargo filters these out). + // Not useful until https://github.com/rust-lang/cargo/issues/13672 is solved. + // For now commented out as ui-test does not play nice with this. + // tcx.sess + // .dcx() + // .emit_artifact_notification(&filename, "cga-callgraph"); + } + } + + items +} + +impl Callbacks for Cga { + fn config(&mut self, config: &mut interface::Config) { + config.track_state = Some(Box::new(|sess| { + let mut env_depinfo = sess.env_depinfo.lock(); + + env_depinfo.insert(( + Symbol::intern("RUSTC_LOG"), + env::var("RUSTC_LOG").map(|log| Symbol::intern(&log)).ok(), + )); + env_depinfo.insert(( + Symbol::intern("CGA_INPUT"), + env::var("CGA_INPUT").map(|input| Symbol::intern(&input)).ok(), + )); + })); + + config.register_lints = Some(Box::new(|_sess, store: &mut LintStore| { + store.register_lints(&[PANICS, BLACKLISTED]); + })); + + config.override_queries = + Some(|_session, queries| queries.queries.collect_and_partition_mono_items = post_mono_pass); + } + + fn after_analysis<'tcx>( + &mut self, + _compiler: &interface::Compiler, + tcx: TyCtxt<'tcx>, + ) -> rustc_driver::Compilation { + let _ = tcx.collect_and_partition_mono_items(()); + rustc_driver::Compilation::Continue + } +} + +fn main() -> ExitCode { + // rustc_driver::install_ice_hook("https://github.com/Wassasin/cga/issues/new", |_| ()); + let early_dcx = EarlyDiagCtxt::new(ErrorOutputType::default()); + + rustc_driver::init_rustc_env_logger(&early_dcx); + + rustc_driver::catch_with_exit_code(move || { + let args = rustc_driver::args::raw_args(&early_dcx); + + let ignore = args + .windows(2) + .find(|slice| { + (slice[0] == "--crate-name" && slice[1] == "build_script_build") + || (slice[0] == "--crate-type" && slice[1] == "proc-macro") + }) + .is_some(); + + if ignore { + rustc_driver::run_compiler(&args, &mut rustc_driver::TimePassesCallbacks::default()); + } else { + rustc_driver::run_compiler(&args, &mut Cga) + } + }) +} diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..be4a21f --- /dev/null +++ b/build.rs @@ -0,0 +1,10 @@ +fn main() { + let rustc = std::env::var("RUSTC").expect("RUSTC env var must be set for build script"); + let output = std::process::Command::new(rustc) + .args(["--print", "sysroot"]) + .output() + .expect("failed to run rustc --print sysroot"); + let stdout = String::from_utf8(output.stdout).expect("rustc --print sysroot output was not UTF-8"); + let sysroot = stdout.trim_end(); + println!("cargo:rustc-link-arg=-Wl,-rpath={sysroot}/lib") +} diff --git a/cga_analysis/Cargo.toml b/cga_analysis/Cargo.toml new file mode 100644 index 0000000..b6fa23b --- /dev/null +++ b/cga_analysis/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "cga_analysis" +version = "0.1.0" +edition = "2024" +license = "MIT" + +[dependencies] +lazy-regex = "3.6" +itertools = "0.15" +indexmap = "2.14" + +cga_data = { path = "../cga_data" } + +[package.metadata.rust-analyzer] +rustc_private = true diff --git a/cga_analysis/src/callgraph/bigraph.rs b/cga_analysis/src/callgraph/bigraph.rs new file mode 100644 index 0000000..a165473 --- /dev/null +++ b/cga_analysis/src/callgraph/bigraph.rs @@ -0,0 +1,360 @@ +use std::rc::Rc; + +use indexmap::{IndexMap, IndexSet}; +use rustc_middle::ty::{Instance, TyCtxt}; + +use crate::{Use, callgraph::Callgraph}; + +#[derive(Clone, Debug)] +pub struct BiCallgraph<'tcx> { + edges: IndexMap, IndexSet>>, + rev_edges: IndexMap, IndexSet>>, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Node<'tcx> { + Entrypoint, + Assert, + AsyncPanic, + Item(Instance<'tcx>), +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct Edge<'tcx> { + pub from: Node<'tcx>, + pub to: Node<'tcx>, + pub use_: Use<'tcx>, +} + +impl<'tcx> From<&Use<'tcx>> for Node<'tcx> { + fn from(value: &Use<'tcx>) -> Self { + match value.kind { + crate::UseKind::Entrypoint(instance) + | crate::UseKind::Called(instance) + | crate::UseKind::VTableCalled(instance) + | crate::UseKind::FnPtrCast(instance) => Node::Item(instance), + crate::UseKind::BuiltinMirAssert => Node::Assert, + crate::UseKind::AsyncPanic => Node::AsyncPanic, + } + } +} + +impl<'tcx> From<&Callgraph<'tcx>> for BiCallgraph<'tcx> { + fn from(value: &Callgraph<'tcx>) -> Self { + let mut edges: IndexMap, IndexSet>> = IndexMap::default(); + let mut rev_edges: IndexMap, IndexSet>> = IndexMap::default(); + + for (from, tos) in value.edges.iter() { + let from = Node::Item(*from); + let from_edges = edges.entry(from).or_default(); + + for use_ in tos.iter() { + let to = Node::from(use_); + let edge = Edge { + from, + to, + use_: use_.clone(), + }; + + from_edges.insert(edge); + rev_edges.entry(to).or_default().insert(edge); + } + } + + let entrypoint = Node::Entrypoint; + let entrypoints = edges.entry(entrypoint).or_default(); + for use_ in value.roots.iter() { + let to = Node::from(use_); + let edge = Edge { + from: entrypoint, + to, + use_: use_.clone(), + }; + entrypoints.insert(edge); + rev_edges.entry(to).or_default().insert(edge); + } + + Self { edges, rev_edges } + } +} + +impl<'tcx> BiCallgraph<'tcx> { + pub fn edges(&self) -> impl Iterator> { + self.edges.iter().flat_map(|(_, edges)| edges.iter()) + } + + /// Find all the crate boundaries from a leaf node. + pub fn find_boundaries<'a>(&'a self, leaf: &'a Use<'tcx>, tcx: TyCtxt<'tcx>, mut f: impl FnMut(&'a Use<'tcx>)) { + fn visit_node<'a, 'tcx>( + this: &'a BiCallgraph<'tcx>, + to: Node<'tcx>, + tcx: TyCtxt<'tcx>, + visited: &mut IndexSet>, + f: &mut impl FnMut(&'a Use<'tcx>), + ) { + if !visited.insert(to) { + return; + } + + if let Some(edges) = this.rev_edges.get(&to) { + for edge in edges { + if edge.use_.hir_id.is_some() { + f(&edge.use_); + continue; + } + + visit_node(this, edge.from, tcx, visited, f); + } + } + } + + if leaf.hir_id.is_some() { + f(leaf); + return; + } + + let callee = Node::from(leaf); + let mut visited = IndexSet::default(); + + if let Some(edges) = self.rev_edges.get(&callee) { + for edge in edges { + if edge.use_ != *leaf { + continue; + } + + visit_node(self, edge.from, tcx, &mut visited, &mut f); + } + } + } + + pub fn find_shortest_from_roots(&self, boundaries: &IndexSet>) -> Option>> { + for e in self.edges() { + if boundaries.contains(&e.use_) + && let Some(mut trace) = + self.bfs_bidirectional(std::iter::once(Node::Entrypoint), std::iter::once(e.from)) + { + trace.push(*e); + return Some(trace); + } + } + None + } + + pub fn find_shortest_to_leafs( + &self, + boundaries: impl Iterator>, + leafs: impl Iterator>, + ) -> Option>> { + self.bfs_bidirectional(boundaries, leafs) + } + + pub fn count_paths_from_roots(&self, dest: Node<'tcx>) -> usize { + cached_count_paths( + dest, + &std::iter::once(Node::Entrypoint).collect(), + &|node| self.predecessors(node).map(|edge: &Edge<'tcx>| edge.from), + &mut IndexMap::default(), + &Trace::new(), + ) + } + + pub fn count_paths_to_leafs(&self, boundary: Node<'tcx>, leafs: &IndexSet>) -> usize { + cached_count_paths( + boundary, + leafs, + &|node| self.successors(node).map(|edge| edge.to), + &mut IndexMap::default(), + &Trace::new(), + ) + } + + fn successors(&self, node: Node<'tcx>) -> impl Iterator> { + self.edges.get(&node).into_iter().flat_map(|edges| edges.iter()) + } + + fn predecessors(&self, node: Node<'tcx>) -> impl Iterator> { + self.rev_edges.get(&node).into_iter().flat_map(|edges| edges.iter()) + } + + /// Bidirectional Breadth First Search + /// + /// Copied from the `pathfinding` crate and adapted to this graph structure. + fn bfs_bidirectional( + &self, + starts: impl Iterator>, + ends: impl Iterator>, + ) -> Option>> { + let mut predecessors: IndexMap, Option> = IndexMap::default(); + predecessors.extend(starts.map(|n| (n, None))); + let mut successors: IndexMap, Option> = IndexMap::default(); + successors.extend(ends.map(|n| (n, None))); + + if predecessors.keys().any(|node| successors.contains_key(node)) { + return Some(vec![]); // We are already here. + } + + let mut i_forwards = 0; + let mut i_backwards = 0; + let middle = 'l: loop { + for _ in 0..(predecessors.len() - i_forwards) { + let node = *predecessors.get_index(i_forwards).unwrap().0; + for successor_edge in self.successors(node) { + let successor_node = successor_edge.to; + if !predecessors.contains_key(&successor_node) { + predecessors.insert(successor_node, Some(i_forwards)); + } + if successors.contains_key(&successor_node) { + break 'l Some(successor_node); + } + } + i_forwards += 1; + } + + for _ in 0..(successors.len() - i_backwards) { + let node = *successors.get_index(i_backwards).unwrap().0; + for predecessor_edge in self.predecessors(node) { + let predecessor_node = predecessor_edge.from; + if !successors.contains_key(&predecessor_node) { + successors.insert(predecessor_node, Some(i_backwards)); + } + if predecessors.contains_key(&predecessor_node) { + break 'l Some(predecessor_node); + } + } + i_backwards += 1; + } + + if i_forwards == predecessors.len() && i_backwards == successors.len() { + break 'l None; + } + }; + + middle.map(|middle| { + // Path found! + // Build the path. + let mut path: Vec> = vec![]; + // From middle to the start. + let mut node = Some(middle.clone()); + while let Some(n) = node { + node = predecessors[&n].map(|i| { + let prev = *predecessors.get_index(i).unwrap().0; + path.push(*self.predecessors(n).find(|e| e.from == prev).unwrap()); + prev + }); + } + // Reverse, to put start at the front. + path.reverse(); + // And from middle to the end. + let mut node = successors[&middle].map(|i| { + let next = successors.get_index(i).unwrap().0.clone(); + path.push(*self.successors(middle).find(|e| e.to == next).unwrap()); + next + }); + while let Some(n) = node { + node = successors[&n].map(|i| { + let next = *successors.get_index(i).unwrap().0; + path.push(*self.successors(n).find(|e| e.to == next).unwrap()); + next + }); + } + + path + }) + } +} + +fn cached_count_paths<'tcx, NI>( + start: Node<'tcx>, + dest: &IndexSet>, + successors: &impl Fn(Node<'tcx>) -> NI, + cache: &mut IndexMap, usize>, + trace: &Trace<'tcx>, +) -> usize +where + NI: Iterator>, +{ + if let Some(&n) = cache.get(&start) { + return n; + } + + if dest.contains(&start) { + return 1; + } + + // Prevent paths with cycles from expanding a node twice. + if trace.contains(start) { + return 0; + } + + let trace = trace.extend(start); + let count = successors(start) + .map(|node| cached_count_paths(node, dest, successors, cache, &trace)) + .sum(); + + if let Some(old) = cache.insert(start, count) { + assert_eq!(old, count); + } + + count +} + +#[derive(Clone, Hash, PartialEq, Eq)] +struct TraceNode<'tcx> { + parent: Option>>, + current: Node<'tcx>, +} + +#[derive(Clone, Hash, PartialEq, Eq)] +pub struct Trace<'tcx> { + inner: Option>>, +} + +pub struct TraceIterator<'tcx> { + inner: Option>>, +} + +impl<'tcx> Trace<'tcx> { + pub fn new() -> Self { + Self { inner: None } + } + + pub fn extend(&self, current: Node<'tcx>) -> Self { + Self { + inner: Some(Rc::new(TraceNode { + parent: self.inner.clone(), + current, + })), + } + } + + pub fn contains(&self, needle: Node<'tcx>) -> bool { + self.iter().find(|node| node == &needle).is_some() + } + + pub fn iter(&self) -> TraceIterator<'tcx> { + // Note(clone): it is relatively cheap, this is a linked list. + self.clone().into_iter() + } +} + +impl<'tcx> IntoIterator for Trace<'tcx> { + type Item = Node<'tcx>; + type IntoIter = TraceIterator<'tcx>; + + fn into_iter(self) -> Self::IntoIter { + TraceIterator { inner: self.inner } + } +} + +impl<'tcx> Iterator for TraceIterator<'tcx> { + type Item = Node<'tcx>; + + fn next(&mut self) -> Option { + if let Some(inner) = self.inner.take() { + self.inner = inner.parent.clone(); + Some(inner.current.clone()) + } else { + None + } + } +} diff --git a/cga_analysis/src/callgraph/data.rs b/cga_analysis/src/callgraph/data.rs new file mode 100644 index 0000000..55cf586 --- /dev/null +++ b/cga_analysis/src/callgraph/data.rs @@ -0,0 +1,132 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use cga_data::callgraph as data; +use rustc_data_structures::fx::FxHashMap; +use rustc_middle::ty::{Instance, TyCtxt}; +use rustc_span::Span; + +use crate::{Use, callgraph::Callgraph, canonical_path}; + +struct Assembler<'tcx> { + /// Final output of the assembler. + output: data::Callgraph, + + /// Mapping fom Instance to a NodeId. + instance_map: FxHashMap, data::NodeId>, + + /// Rustc context. + tcx: TyCtxt<'tcx>, +} + +impl<'tcx> Assembler<'tcx> { + fn new(tcx: TyCtxt<'tcx>) -> Self { + Self { + output: data::Callgraph { + roots: BTreeSet::default(), + nodes: Vec::new(), + edges: BTreeMap::default(), + }, + instance_map: FxHashMap::default(), + tcx, + } + } + + fn span_location(&self, span: Span) -> data::Location { + // TODO figure out if we want to do macro expansions. + + let (file, sl, sc, el, ec) = self.tcx.sess.source_map().span_to_location_info(span); + + let path = file + .map(|file| { + file.name + .clone() + .into_local_path() + .map(|path| path.to_string_lossy().to_string()) + }) + .flatten() + .unwrap_or_else(|| "{unknown}".to_string()); + + let span = data::Span( + data::FilePosition { + line: sl as u32, + column: sc as u32, + }..=data::FilePosition { + line: el as u32, + column: ec as u32, + }, + ); + + data::Location { path, span } + } + + fn notify_node(&mut self, instance: Instance<'tcx>, root: bool) -> data::NodeId { + // TODO move span_location into lamda (but self is borrowed, which makes it complicated). + let location = self.span_location(self.tcx.def_span(instance.def_id())); + + *self.instance_map.entry(instance).or_insert_with(|| { + let node = data::Node { + name: canonical_path(&instance), + location, + }; + + let node_id = (self.output.nodes.len() as u32).into(); + self.output.nodes.push(node); + + if root { + self.output.roots.insert(node_id); + } + + node_id + }) + } + + fn notify_edge(&mut self, from: data::NodeId, use_: Use<'tcx>) { + let location = self.span_location(use_.span); + let kind = match use_.kind { + crate::UseKind::Entrypoint(..) => panic!("Entrypoints should not be an edge."), + crate::UseKind::Called(instance) => data::EdgeKind::Called(self.notify_node(instance, false)), + crate::UseKind::VTableCalled(instance) => data::EdgeKind::VTableCalled(self.notify_node(instance, false)), + crate::UseKind::FnPtrCast(instance) => data::EdgeKind::FnPtrCast(self.notify_node(instance, false)), + crate::UseKind::BuiltinMirAssert => data::EdgeKind::BuiltinAssert, + crate::UseKind::AsyncPanic => data::EdgeKind::AsyncPanic, + }; + + self.output + .edges + .entry(from) + .or_default() + .push(data::Edge { kind, location }); + } +} + +impl<'tcx> Callgraph<'tcx> { + pub fn export(&self, tcx: TyCtxt<'tcx>) -> data::Callgraph { + let mut edges: Vec<(Option>, Use<'tcx>)> = vec![]; + edges.reserve_exact(self.roots.len() + self.edges.len()); + + for root in self.roots.iter() { + edges.push((None, root.clone())); + } + + for (from, tos) in self.edges.iter() { + for to in tos.iter() { + edges.push((Some(*from), to.clone())); + } + } + + let mut assembler = Assembler::new(tcx); + for (from, to) in edges { + if let Some(from) = from { + let node_id = assembler.notify_node(from, false); + assembler.notify_edge(node_id, to); + } else { + // From is empty, meaning it must be a root node. + let instance = to.opt_instance().expect("Root use_ nodes need to be Entrypoints"); + + assembler.notify_node(*instance, true); + } + } + + assembler.output + } +} diff --git a/cga_analysis/src/callgraph/mod.rs b/cga_analysis/src/callgraph/mod.rs new file mode 100644 index 0000000..2b3a31e --- /dev/null +++ b/cga_analysis/src/callgraph/mod.rs @@ -0,0 +1,74 @@ +use indexmap::{ + IndexSet, + map::{Entry, IndexMap}, +}; +use rustc_middle::ty::{Instance, TyCtxt}; + +use crate::{Use, UseKind}; + +mod data; + +pub mod bigraph; + +/// The callgraph as used in the analysis step. +/// +/// Each [`Use`] is annotated with the [`NodeId`] to give a deterministic ordering to the callgraph. +#[derive(Clone, Debug)] +pub struct Callgraph<'tcx> { + roots: IndexSet>, + edges: IndexMap, IndexSet>>, +} + +impl<'tcx> Callgraph<'tcx> { + pub fn new() -> Self { + Self { + roots: IndexSet::default(), + edges: IndexMap::default(), + } + } + + pub fn register_root(&mut self, instance: Instance<'tcx>, tcx: TyCtxt<'tcx>) { + let root_node = instance.def_id().expect_local(); + let hir_id = tcx.local_def_id_to_hir_id(root_node); + let span = tcx.hir_span(hir_id); + + let use_ = Use { + kind: UseKind::Entrypoint(instance), + span, + hir_id: Some(hir_id), + }; + + self.roots.insert(use_); + } + + pub fn register_edge(&mut self, from: Instance<'tcx>, to: Use<'tcx>) { + self.edges.entry(from).or_default().insert(to); + } + + /// Insert a node into the callgraph if it did not exist already. + /// + /// Does not touch the edges if the node did exist previously. + /// + /// Returns `true` if the node was not yet visited (and we just inserted it). + pub fn visit(&mut self, instance: Instance<'tcx>) -> bool { + let entry = self.edges.entry(instance); + let was_vacant = matches!(entry, Entry::<_, _>::Vacant(..)); + entry.or_default(); + was_vacant + } + + /// Quickly iterates over all edges. + pub fn edges(&self) -> impl Iterator>, &Use<'tcx>)> { + self.roots.iter().map(|use_| (None, use_)).chain( + self.edges + .iter() + .flat_map(|(from, tos)| tos.iter().map(move |to| (Some(from), to))), + ) + } + + pub fn cut_edges(&mut self, f: impl Fn(&Use<'tcx>) -> bool) { + for (_, tos) in self.edges.iter_mut() { + tos.retain(|use_| !f(use_)); + } + } +} diff --git a/cga_analysis/src/diagnostics/mod.rs b/cga_analysis/src/diagnostics/mod.rs new file mode 100644 index 0000000..afb3d96 --- /dev/null +++ b/cga_analysis/src/diagnostics/mod.rs @@ -0,0 +1,373 @@ +use cga_data::cmd::LintConfig; +use indexmap::{IndexMap, IndexSet}; +use itertools::Itertools; +use rustc_errors::{Diag, MultiSpan}; +use rustc_hir::LangItem; +use rustc_lint::Lint; +use rustc_log::tracing::*; +use rustc_middle::ty::TyCtxt; +use rustc_span::{Span, SyntaxContext}; + +mod rules; + +use crate::{ + BLACKLISTED, Callgraph, PANICS, Use, UseKind, + callgraph::bigraph::{BiCallgraph, Edge, Node}, + canonical_path, + diagnostics::rules::LintRuleSet, + panics::PanicStatus, +}; + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +struct BoundaryLeaf<'a, 'tcx> { + boundary: &'a Use<'tcx>, + leaf: &'a Use<'tcx>, +} + +pub fn emit_lints<'tcx>(mut callgraph: Callgraph<'tcx>, config: &LintConfig, tcx: TyCtxt<'tcx>) { + // TODO better error handling + let whitelist = LintRuleSet::new(&config.whitelist).unwrap(); + let blacklist = LintRuleSet::new(&config.blacklist).unwrap(); + + // Cut away all edges in the callgraph that are whitelisted. + callgraph.cut_edges(|use_| { + (config.options.allow_async_panic && matches!(use_.kind, UseKind::AsyncPanic)) + || (config.options.allow_assert && matches!(use_.kind, UseKind::BuiltinMirAssert)) + || whitelist.use_applies(use_, tcx) + }); + + let bigraph = BiCallgraph::from(&callgraph); + + // Compute all boundaries and leafs, and group them per span so that we have one lint per location. + let mut panic_boundaries: IndexMap>> = IndexMap::default(); + let mut blacklist_boundaries: IndexMap>> = IndexMap::default(); + + for Edge { use_: leaf, .. } in bigraph.edges() { + if leaf.panic_status(tcx).any() { + info!("Leaf {leaf:?}"); + bigraph.find_boundaries(leaf, tcx, |boundary| { + let root_span = rustc_span::hygiene::walk_chain(boundary.span, SyntaxContext::root()); + panic_boundaries + .entry(root_span) + .or_default() + .insert(BoundaryLeaf { boundary, leaf }); + }); + } + + if blacklist.use_applies(leaf, tcx) { + bigraph.find_boundaries(leaf, tcx, |boundary| { + let root_span = rustc_span::hygiene::walk_chain(boundary.span, SyntaxContext::root()); + blacklist_boundaries + .entry(root_span) + .or_default() + .insert(BoundaryLeaf { boundary, leaf }); + }); + } + } + + process_boundaries::(&bigraph, panic_boundaries, tcx); + process_boundaries::(&bigraph, blacklist_boundaries, tcx); +} + +trait LintEngine { + const LINT: &'static Lint; + const INDIRECT_ISSUE: &'static str; + const DIRECT_ISSUE: &'static str; + const PLURAL_NOUN: &'static str; + + fn select<'a, 'tcx: 'a>( + boundary_leafs: IndexSet>, + _tcx: TyCtxt<'tcx>, + ) -> IndexSet> { + boundary_leafs // Do nothing but pass it. + } + + fn is_direct(data: &LintData<'_, '_>, tcx: TyCtxt<'_>) -> bool; +} + +struct Panics; +struct Blacklisted; + +impl LintEngine for Panics { + const LINT: &'static Lint = PANICS; + const INDIRECT_ISSUE: &'static str = "can panic"; + const DIRECT_ISSUE: &'static str = "is a panic"; + const PLURAL_NOUN: &'static str = "panics"; + + fn select<'a, 'tcx: 'a>( + mut boundary_leafs: IndexSet>, + tcx: TyCtxt<'tcx>, + ) -> IndexSet> { + let direct_panics: IndexSet<_> = boundary_leafs + .iter() + .filter(|bl| matches!(bl.leaf.panic_status(tcx), PanicStatus::PanicImpl)) + .copied() + .collect(); + + // If there are any direct panics, they take precedence over any other panics. + if !direct_panics.is_empty() { + boundary_leafs = direct_panics; + } + + boundary_leafs + } + + fn is_direct(data: &LintData<'_, '_>, tcx: TyCtxt<'_>) -> bool { + // Note(unwrap): trace will have at least one element. + &data.boundary == data.trace.last().unwrap() + && matches!(data.boundary.panic_status(tcx), PanicStatus::PanicImpl) + } +} + +impl LintEngine for Blacklisted { + const LINT: &'static Lint = BLACKLISTED; + const INDIRECT_ISSUE: &'static str = "can call blacklisted functions"; + const DIRECT_ISSUE: &'static str = "is blacklisted"; + const PLURAL_NOUN: &'static str = "blacklisted functions"; + + fn is_direct(data: &LintData<'_, '_>, _tcx: TyCtxt<'_>) -> bool { + // Note(unwrap): trace will have at least one element. + &data.boundary == data.trace.last().unwrap() + } +} + +fn process_boundaries<'tcx, Engine: LintEngine>( + bigraph: &BiCallgraph<'tcx>, + boundaries: IndexMap>>, + tcx: TyCtxt<'tcx>, +) { + for (span, boundary_leafs) in boundaries { + info!("Boundary {span:?} {boundary_leafs:?}"); + + let all_leafs: IndexSet<_> = boundary_leafs.iter().map(|bl| Node::from(bl.leaf)).collect(); + + let boundary_leafs = Engine::select(boundary_leafs, tcx); + let boundaries: IndexSet<_> = boundary_leafs.iter().map(|bl| *bl.boundary).collect(); + + // Path of trace root to boundary. + let Some(path_from_root) = bigraph.find_shortest_from_roots(&boundaries) else { + info!("No path to root?!"); + continue; + }; + + // Note(unwrap): `find_shortest_from_roots` generates traces with at least 1 edge. + let boundary = path_from_root.last().unwrap().use_; + + // Note(unwrap): we got here, hence there should be a path. + let path_to_panics = bigraph + .find_shortest_to_leafs( + std::iter::once(Node::from(&boundary)), + boundary_leafs.iter().map(|bl| Node::from(bl.leaf)), + ) + .unwrap(); + + if let (Some(root_last), Some(panic_first)) = (path_from_root.last(), path_to_panics.first()) { + assert_eq!(root_last.to, panic_first.from); + info!("Path consecutive"); + } else { + info!("Path not consecutive"); + } + + let trace: Vec<_> = path_from_root.iter().chain(path_to_panics.iter()).collect(); + + { + // Double-check if the result is a consistent trace. + let mut iter = trace.iter(); + let mut prev = iter.next().unwrap().to; + for e in iter { + assert_eq!(prev, e.from); + prev = e.to; + } + } + + let trace = trace.iter().map(|e| e.use_).collect(); + + let data = LintData { + boundary, + above_paths: bigraph.count_paths_from_roots(Node::from(&boundary)), + below_paths: bigraph.count_paths_to_leafs(Node::from(&boundary), &all_leafs), + below_leafs: all_leafs.len(), + trace: &trace, + }; + + lint_use::(data, tcx); + } +} + +#[derive(Debug)] +struct LintData<'a, 'tcx> { + boundary: Use<'tcx>, + + /// Number of call paths to end up at the boundary. + above_paths: usize, + + /// Number of call paths from boundary to panic locations. + below_paths: usize, + + /// Number of reachable panic locations. + below_leafs: usize, + + /// The trace picked as the example to illustrate the panic path. + trace: &'a Vec>, +} + +impl<'tcx> Use<'tcx> { + fn descriptor(&self) -> &'static str { + match self.kind { + UseKind::Entrypoint(..) => "entrypoint", + UseKind::Called(..) => "called", + UseKind::VTableCalled(..) => "possible vtable resolution", + UseKind::FnPtrCast(..) => "fn-ptr cast", + UseKind::BuiltinMirAssert => "assert", + UseKind::AsyncPanic => "implicit async panic", + } + } + + fn noun(&self) -> &'static str { + match self.kind { + UseKind::Entrypoint(..) => "start to", + UseKind::Called(..) => "call to", + UseKind::VTableCalled(..) => "possible dyn object call to", + UseKind::FnPtrCast(..) => "possible later call to", + UseKind::BuiltinMirAssert => "implicit assert", + UseKind::AsyncPanic => "can be poll'd after Ready", + } + } +} + +fn decorate_expansion(use_: &Use<'_>, multi_span: &mut MultiSpan) { + if use_.span.from_expansion() { + let root_span = rustc_span::hygiene::walk_chain(use_.span, SyntaxContext::root()); + if use_.span != root_span { + multi_span.push_span_label(root_span, "expanded from here"); + } + } +} + +/// Decorate with extra notes if the leaf node is a panic associated with an async coroutine +fn decorate_leaf(use_: &Use<'_>, diag: &mut Diag<'_, ()>) { + match use_.kind { + UseKind::AsyncPanic => { + diag.note("an async block can always panic when poll'd after Ready"); + } + _ => {} + } +} + +/// Decorate with extra notes if the boundary is not a straightforward function call +fn decorate_cast(use_: &Use<'_>, diag: &mut Diag<'_, ()>) { + if matches!(use_.kind, UseKind::FnPtrCast(..)) { + diag.note("once a function is cast to a function pointer, cga can no longer tell how it is used"); + diag.note("as a precaution, it must assume you will eventually call the function"); + } +} + +fn lint_use(data: LintData<'_, '_>, tcx: TyCtxt<'_>) { + let boundary_use = data.boundary; + let drop_glue_fn = tcx.require_lang_item(LangItem::DropGlue, boundary_use.span); + + // Note(unwrap): hir_id is Some because TraceBoundary guarantees that it is local. + let lint_node = boundary_use + .hir_id + .expect("Local boundary should have an associated HirId"); + + info!("linting {boundary_use:?}"); + + let mut multi_span = MultiSpan::from_span(boundary_use.span); + + decorate_expansion(&boundary_use, &mut multi_span); + + tcx.emit_node_span_lint( + Engine::LINT, + lint_node, + multi_span, + rustc_errors::DiagDecorator(|diag| { + let issue = if Engine::is_direct(&data, tcx) { + Engine::DIRECT_ISSUE + } else { + Engine::INDIRECT_ISSUE + }; + + if let Some(callee) = boundary_use.opt_def_id() { + if callee == drop_glue_fn { + // Note(unwrap): we are in drop glue, hence there must be an instance. + let boundary_instance = boundary_use.opt_instance().unwrap(); + + diag.primary_message(format!( + "{} drop of `{}` which {}", + boundary_use.noun(), + boundary_instance.args.type_at(0), + issue + )); + } else { + diag.primary_message(format!( + "{} {} `{}` which {}", + boundary_use.noun(), + tcx.def_descr(callee), + tcx.def_path_str(callee), + issue + )); + } + } else { + diag.primary_message(format!("{} which {}", boundary_use.noun(), issue)); + } + + if data.above_paths > 1 { + diag.note(format!( + "this location is called in {} different traces", + data.above_paths + )); + } + + if data.below_leafs > 1 { + diag.note(format!( + "{} different {} from this location", + data.below_leafs, + Engine::PLURAL_NOUN, + )); + } + + if let Some(leaf) = data.trace.last() { + decorate_leaf(&leaf, diag); + } + + decorate_cast(&boundary_use, diag); + + { + let backtrace = data + .trace + .iter() + .rev() + .enumerate() + .map(|(i, use_)| { + let use_str = match use_.kind { + UseKind::Entrypoint(instance) + | UseKind::Called(instance) + | UseKind::VTableCalled(instance) + | UseKind::FnPtrCast(instance) => { + let instance_path = if instance.def_id() == drop_glue_fn { + format!("drop of `{}`", instance.args.type_at(0)) + } else { + canonical_path(&instance) + }; + + format!("{instance_path} ({})", use_.descriptor()) + } + UseKind::BuiltinMirAssert | UseKind::AsyncPanic => use_.descriptor().to_string(), + }; + let is_boundary = use_ == &boundary_use; + + format!("{i}: {use_str}{}", if is_boundary { " <--" } else { "" }) + }) + .join("\n"); + + let number_of_paths = data.above_paths * data.below_paths; + if number_of_paths == 1 { + diag.note(format!("backtrace:\n{backtrace}")); + } else { + diag.note(format!("backtrace (1 of {number_of_paths}):\n{backtrace}")); + } + } + }), + ); +} diff --git a/cga_analysis/src/diagnostics/rules.rs b/cga_analysis/src/diagnostics/rules.rs new file mode 100644 index 0000000..8dd5f02 --- /dev/null +++ b/cga_analysis/src/diagnostics/rules.rs @@ -0,0 +1,64 @@ +use cga_data::cmd::LintRule; +use lazy_regex::regex::RegexSet; +use rustc_data_structures::fx::FxHashSet; +use rustc_middle::ty::TyCtxt; +use rustc_span::{Span, Symbol}; + +use crate::{Use, canonical_path, simplified_path}; + +pub struct LintRuleSet { + path: RegexSet, + canonical: RegexSet, + crate_: FxHashSet, +} + +impl LintRuleSet { + pub fn new<'a>(rules: &Vec) -> Result { + let mut path_regexes = vec![]; + let mut canonical_regexes = vec![]; + let mut crates = FxHashSet::default(); + + for rule in rules.iter() { + match rule { + LintRule::Path(expr) => path_regexes.push(expr), + LintRule::Canonical(expr) => canonical_regexes.push(expr), + LintRule::Crate(str) => { + crates.insert(str.clone()); + } + } + } + + Ok(Self { + path: RegexSet::new(path_regexes)?, + canonical: RegexSet::new(canonical_regexes)?, + crate_: crates, + }) + } + + /// Use is covered by any rule. + pub fn use_applies<'tcx>(&self, use_: &Use<'tcx>, tcx: TyCtxt<'tcx>) -> bool { + // Compute path-based rule. + if let Some(instance) = use_.opt_instance() { + if self.path.is_match(&simplified_path(instance)) { + return true; + } + + if self.canonical.is_match(&canonical_path(instance)) { + return true; + } + } + + // Compute crate-based rule. + if self.crate_.contains(span_to_cratename(&use_.span, tcx).as_str()) { + return true; + } + + // No rule applied. + false + } +} + +/// Lookup the symbol of the crate from which the span originates. +fn span_to_cratename(span: &Span, tcx: TyCtxt) -> Symbol { + tcx.crate_name(tcx.sess.source_map().lookup_char_pos(span.lo()).file.cnum) +} diff --git a/cga_analysis/src/dyn_object.rs b/cga_analysis/src/dyn_object.rs new file mode 100644 index 0000000..c74978e --- /dev/null +++ b/cga_analysis/src/dyn_object.rs @@ -0,0 +1,58 @@ +use rustc_hir::def_id::DefId; +use rustc_log::tracing::*; +use rustc_middle::ty::{GenericArgs, GenericArgsRef, Instance, TyCtxt, TypingEnv}; +use rustc_type_ir::TypeVisitableExt; + +pub(crate) fn dyn_object_call_to_impl_methods<'tcx>( + virtual_def_id: DefId, + // Not necessary? + _args: GenericArgsRef<'tcx>, + tcx: TyCtxt<'tcx>, +) -> Vec> { + let trait_def_id = tcx.parent(virtual_def_id); + + debug!("determining implementors {virtual_def_id:?} by checking out {trait_def_id:?}"); + + let typing_env = TypingEnv::codegen(tcx, virtual_def_id); + + // When a dyn object vtable call is made, assume all impls of that trait are called. + let trait_impls = tcx.trait_impls_of(trait_def_id); + trait_impls + .blanket_impls() + .iter() + .chain(trait_impls.non_blanket_impls().values().flatten()) + .filter_map(|impl_def_id| { + let Ok(impl_trait) = + tcx.try_normalize_erasing_regions(typing_env, tcx.impl_trait_ref(*impl_def_id).instantiate_identity()) + else { + info!("Failed to normalize {impl_def_id:?}"); + return None; + }; + let impl_trait_ty = impl_trait.self_ty(); + + // Filter out uninstantiated impls with generic parameters. + // TODO Unclear why we encounter these in monomorphized code, probably a bug somewhere. + if impl_trait_ty.has_non_region_infer() || impl_trait_ty.has_non_region_param() { + return None; + } + + let impl_args = GenericArgs::identity_for_item(tcx, *impl_def_id); + + // Get the type for thing having an impl for the trait. + let self_ty = tcx.type_of(*impl_def_id).instantiate(tcx, impl_args).skip_norm_wip(); + + // Replace dyn binder Self with a concrete Self of the type of self_ty. + // Without this replacement a &dyn would be passed to the function, but it expects &self. + let trait_args = tcx.mk_args_from_iter( + std::iter::once(self_ty.into()) + .chain(GenericArgs::identity_for_item(tcx, virtual_def_id).iter().skip(1)), + ); + + if let Ok(instance_opt) = Instance::try_resolve(tcx, typing_env, virtual_def_id, trait_args) { + instance_opt + } else { + None + } + }) + .collect() +} diff --git a/cga_analysis/src/errors.rs b/cga_analysis/src/errors.rs new file mode 100644 index 0000000..d6e89ee --- /dev/null +++ b/cga_analysis/src/errors.rs @@ -0,0 +1,8 @@ +use rustc_macros::Diagnostic; + +#[derive(Diagnostic)] +#[diag("using `fn main` requires the standard library")] +#[help( + "use `#![no_main]` to bypass the Rust generated entrypoint and declare a platform specific entrypoint yourself, usually with `#[no_mangle]`" +)] +pub(crate) struct StartNotFound; diff --git a/cga_analysis/src/lib.rs b/cga_analysis/src/lib.rs new file mode 100644 index 0000000..37bbf08 --- /dev/null +++ b/cga_analysis/src/lib.rs @@ -0,0 +1,177 @@ +#![feature(rustc_private)] +#![feature(impl_trait_in_assoc_type)] + +extern crate rustc_abi; +extern crate rustc_data_structures; +extern crate rustc_driver; +extern crate rustc_errors; +extern crate rustc_hir; +extern crate rustc_index; +extern crate rustc_infer; +extern crate rustc_interface; +extern crate rustc_lint; +extern crate rustc_log; +extern crate rustc_macros; +extern crate rustc_middle; +extern crate rustc_mir_transform; +extern crate rustc_monomorphize; +extern crate rustc_public; +extern crate rustc_serialize; +extern crate rustc_session; +extern crate rustc_span; +extern crate rustc_trait_selection; +extern crate rustc_type_ir; + +mod callgraph; +mod dyn_object; +mod errors; +mod panics; + +pub mod diagnostics; +pub mod monomorphize; +pub mod post_mono; + +use rustc_hir::{HirId, def_id::DefId}; +use rustc_middle::{ + mir::{Body, SourceInfo}, + ty::{Instance, TyCtxt, print::with_resolve_crate_name}, +}; +use rustc_session::declare_tool_lint; +use rustc_span::Span; + +declare_tool_lint! { + /// The `cga::panics` lint detects code that calls panicking functions. + pub cga::PANICS, + Deny, + "a function (indirectly) panics", + report_in_external_macro: true +} + +declare_tool_lint! { + /// The `cga::blacklisted` lint detects code that was blacklisted. + pub cga::BLACKLISTED, + Deny, + "a function (indirectly) is blacklisted", + report_in_external_macro: true +} + +// for intra-doc links +#[rustfmt::skip] +#[allow(unused_imports)] +use rustc_middle::{ + mir::TerminatorKind, + ty::{GenericArgsRef, ParamEnv}, +}; + +use crate::callgraph::Callgraph; + +/// Context used for analysis for a given root node. +struct AnalysisState<'a, 'tcx> { + /// Type context. + tcx: TyCtxt<'tcx>, + + /// The callgraph currently being constructed. + callgraph: &'a mut Callgraph<'tcx>, +} + +impl<'tcx> AnalysisState<'_, 'tcx> { + pub fn register_root(&mut self, instance: Instance<'tcx>) { + self.callgraph.register_root(instance, self.tcx); + } + + pub fn register_edge(&mut self, from: Instance<'tcx>, to: Use<'tcx>) { + self.callgraph.register_edge(from, to); + } +} + +/// A Use of a function or a panic. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct Use<'tcx> { + /// The kind associated with the [`Use`]. + /// + /// When calling a function, refers to the monomorphized [`Instance`] of the function called. + kind: UseKind<'tcx>, + + /// The calling location of the [`Use`]. + span: Span, + + /// The HIR node associated with the calling location, necessary for emitting diagnostics. + /// + /// Only applicable for nodes within our local crate. + hir_id: Option, +} + +/// Nodes in a call graph. +/// +/// Each node is a *possible* call to monomorphized functions (possibly), or panic. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +enum UseKind<'tcx> { + /// The instance is an entrypoint for the application. + /// + /// And entrypoint could be an extern "c" function, an ISR, main, etc. + Entrypoint(Instance<'tcx>), + + /// An [`Instance`] function is called directly. + Called(Instance<'tcx>), + + /// An [`Instance`] function is possibly called via a dyn object vtable. + VTableCalled(Instance<'tcx>), + + /// An [`Instance`] function pointer is created here, and might possibly be called later. + FnPtrCast(Instance<'tcx>), + + /// A builtin assertion in MIR which can panic. + /// + /// For example OverflowAdd will generate a terminator with [TerminatorKind::Assert]. + BuiltinMirAssert, + + /// The implicit panic(s) associated with any async coroutine. + /// + /// All async coroutines have a "poll after Ready" state, which always panics. + /// These panics are (as of yet) unavoidable when using async. + /// + /// Because there is no mitigation for this, we put these panics into a separate category. + AsyncPanic, +} + +impl<'tcx> Use<'tcx> { + fn for_location(kind: UseKind<'tcx>, body: &Body<'tcx>, source: SourceInfo) -> Use<'tcx> { + Use { + kind, + span: source.span, + hir_id: source.scope.lint_root(&body.source_scopes), + } + } + + pub fn opt_def_id(&self) -> Option { + match self.kind { + UseKind::Called(instance) + | UseKind::VTableCalled(instance) + | UseKind::FnPtrCast(instance) + | UseKind::Entrypoint(instance) => Some(instance.def_id()), + UseKind::BuiltinMirAssert => None, + UseKind::AsyncPanic => None, + } + } + + pub fn opt_instance(&self) -> Option<&Instance<'tcx>> { + match &self.kind { + UseKind::FnPtrCast(instance) + | UseKind::Called(instance) + | UseKind::VTableCalled(instance) + | UseKind::Entrypoint(instance) => Some(instance), + UseKind::BuiltinMirAssert | UseKind::AsyncPanic => None, + } + } +} + +/// The canonical path (or name) of an instance used throughout this crate. +/// +/// Used as the qualifiers in the exported callgraph, as well as in the lint backtrace and to which rules are applied. +pub(crate) fn canonical_path(instance: &Instance<'_>) -> String { + with_resolve_crate_name!(format!("{instance}")) +} + +pub(crate) fn simplified_path(instance: &Instance<'_>) -> String { + with_resolve_crate_name!(rustc_middle::ty::tls::with(|tcx| tcx.def_path_str(instance.def_id()))) +} diff --git a/cga_analysis/src/monomorphize.rs b/cga_analysis/src/monomorphize.rs new file mode 100644 index 0000000..bd947c2 --- /dev/null +++ b/cga_analysis/src/monomorphize.rs @@ -0,0 +1,577 @@ +//! A shallow fork of [`rustc_monomorphize`]. +//! +//! We need to use [`collect_roots`] for our analysis pass, but this function is not exposed by [`rustc_monomorphize`]. +//! Instead we have forked the crate to regain the same functionality. +//! +//! `NoConstOrStatic` has been added, as well as an extra check for the existence of the `no_mangle` and `export_name` attributes. + +use rustc_data_structures::fx::FxIndexMap; +use rustc_hir::{ + ImplItemId, ItemId, LangItem, + attrs::{HasAttrs, InlineAttr}, + def::DefKind, + def_id::{DefId, LocalDefId}, +}; +use rustc_middle::{ + bug, + middle::codegen_fn_attrs::CodegenFnAttrFlags, + mir::{self}, + mono::MonoItem, + ty::{self, GenericParamDefKind, Instance, Ty, TyCtxt, Unnormalized, layout::ValidityRequirement}, +}; +use rustc_middle::{ + mir::interpret::{AllocId, GlobalAlloc, Scalar}, + ty::GenericArgs, +}; +use rustc_session::config::EntryFnType; +use rustc_span::{DUMMY_SP, Span, Spanned, dummy_spanned, respan}; +use rustc_type_ir::TypeVisitableExt; + +use rustc_log::tracing::*; + +#[derive(PartialEq)] +pub enum MonoItemCollectionStrategy { + /// Collect all items, including const items that are not necessarily used. + Eager, + /// Collect all items, but only const items that are directly used. + Lazy, + /// Only collect extern and normal entrypoints. + /// + /// Do not collect const/static. + NoConstOrStatic, +} + +struct MonoItems<'tcx> { + // We want a set of MonoItem + Span where trying to re-insert a MonoItem with a different Span + // is ignored. Map does that, but it looks odd. + items: FxIndexMap, Span>, +} + +impl<'tcx> MonoItems<'tcx> { + fn new() -> Self { + Self { + items: FxIndexMap::default(), + } + } + + fn push(&mut self, item: Spanned>) { + // Insert only if the entry does not exist. A normal insert would stomp the first span that + // got inserted. + self.items.entry(item.node).or_insert(item.span); + } +} + +impl<'tcx> IntoIterator for MonoItems<'tcx> { + type Item = Spanned>; + type IntoIter = impl Iterator>>; + + fn into_iter(self) -> Self::IntoIter { + self.items.into_iter().map(|(item, span)| respan(span, item)) + } +} + +impl<'tcx> Extend>> for MonoItems<'tcx> { + fn extend(&mut self, iter: I) + where + I: IntoIterator>>, + { + for item in iter { + self.push(item) + } + } +} + +// Find all non-generic items by walking the HIR. These items serve as roots to +// start monomorphizing from. +pub fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec> { + debug!("collecting roots"); + let mut roots = MonoItems::new(); + + { + let entry_fn = tcx.entry_fn(()); + + debug!("collect_roots: entry_fn = {:?}", entry_fn); + + let mut collector = RootCollector { + tcx, + strategy: mode, + entry_fn, + output: &mut roots, + }; + + let crate_items = tcx.hir_crate_items(()); + + for id in crate_items.free_items() { + collector.process_item(id); + } + + for id in crate_items.impl_items() { + collector.process_impl_item(id); + } + + for id in crate_items.nested_bodies() { + collector.process_nested_body(id); + } + + collector.push_extra_entry_roots(); + } + + // We can only codegen items that are instantiable - items all of + // whose predicates hold. Luckily, items that aren't instantiable + // can't actually be used, so we can just skip codegenning them. + roots + .into_iter() + .filter_map(|Spanned { node: mono_item, .. }| mono_item.is_instantiable(tcx).then_some(mono_item)) + .collect() +} + +struct RootCollector<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + strategy: MonoItemCollectionStrategy, + output: &'a mut MonoItems<'tcx>, + entry_fn: Option<(DefId, EntryFnType)>, +} + +impl<'v> RootCollector<'_, 'v> { + fn process_item(&mut self, id: ItemId) { + match self.tcx.def_kind(id.owner_id) { + DefKind::Enum | DefKind::Struct | DefKind::Union => { + if self.strategy == MonoItemCollectionStrategy::Eager + && !self.tcx.generics_of(id.owner_id).requires_monomorphization(self.tcx) + { + debug!("RootCollector: ADT drop-glue for `{id:?}`",); + let id_args = + ty::GenericArgs::for_item(self.tcx, id.owner_id.to_def_id(), |param, _| match param.kind { + GenericParamDefKind::Lifetime => self.tcx.lifetimes.re_erased.into(), + GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { + unreachable!( + "`own_requires_monomorphization` check means that \ + we should have no type/const params" + ) + } + }); + + // This type is impossible to instantiate, so we should not try to + // generate a `drop_in_place` instance for it. + if self + .tcx + .instantiate_and_check_impossible_predicates((id.owner_id.to_def_id(), id_args)) + { + return; + } + + let ty = self.tcx.type_of(id.owner_id.to_def_id()).instantiate(self.tcx, id_args); + // FIXME CGA rustc #155345 + let ty = ty.skip_norm_wip(); + assert!(!ty.has_non_region_param()); + + visit_drop_use(self.tcx, ty, true, DUMMY_SP, self.output); + } + } + DefKind::GlobalAsm => { + debug!( + "RootCollector: ItemKind::GlobalAsm({})", + self.tcx.def_path_str(id.owner_id) + ); + self.output.push(dummy_spanned(MonoItem::GlobalAsm(id))); + } + DefKind::Static { .. } => match self.strategy { + MonoItemCollectionStrategy::NoConstOrStatic => {} + _ => { + let def_id = id.owner_id.to_def_id(); + debug!("RootCollector: ItemKind::Static({})", self.tcx.def_path_str(def_id)); + self.output.push(dummy_spanned(MonoItem::Static(def_id))); + } + }, + DefKind::Const { .. } => { + // Const items only generate mono items if they are actually used somewhere. + // Just declaring them is insufficient. + + // If we're collecting items eagerly, then recurse into all constants. + // Otherwise the value is only collected when explicitly mentioned in other items. + if self.strategy == MonoItemCollectionStrategy::Eager { + let def_id = id.owner_id.to_def_id(); + // Type Consts don't have bodies to evaluate + // nor do they make sense as a static. + if self.tcx.is_type_const(def_id) { + // FIXME(mgca): Is this actually what we want? We may want to + // normalize to a ValTree then convert to a const allocation and + // collect that? + return; + } + if self.tcx.generics_of(id.owner_id).own_requires_monomorphization() { + return; + } + let Ok(val) = self.tcx.const_eval_poly(def_id) else { + return; + }; + collect_const_value(self.tcx, val, self.output); + } + } + DefKind::Impl { of_trait: true } => { + if self.strategy == MonoItemCollectionStrategy::Eager { + create_mono_items_for_default_impls(self.tcx, id, self.output); + } + } + DefKind::Fn => { + self.push_if_root(id.owner_id.def_id); + } + _ => {} + } + } + + fn process_impl_item(&mut self, id: ImplItemId) { + if self.tcx.def_kind(id.owner_id) == DefKind::AssocFn { + self.push_if_root(id.owner_id.def_id); + } + } + + fn process_nested_body(&mut self, def_id: LocalDefId) { + match self.tcx.def_kind(def_id) { + DefKind::Closure => { + // for 'pub async fn foo(..)' also trying to monomorphize foo::{closure} + let is_pub_fn_coroutine = match *self.tcx.type_of(def_id).instantiate_identity().skip_norm_wip().kind() + { + ty::Coroutine(cor_id, _args) => { + let tcx = self.tcx; + let parent_id = tcx.parent(cor_id); + tcx.def_kind(parent_id) == DefKind::Fn + && tcx.asyncness(parent_id).is_async() + && tcx.visibility(parent_id).is_public() + } + ty::Closure(..) | ty::CoroutineClosure(..) => false, + _ => unreachable!(), + }; + if (self.strategy == MonoItemCollectionStrategy::Eager || is_pub_fn_coroutine) + && !self + .tcx + .generics_of(self.tcx.typeck_root_def_id_local(def_id)) + .requires_monomorphization(self.tcx) + { + let instance = match *self.tcx.type_of(def_id).instantiate_identity().skip_norm_wip().kind() { + ty::Closure(def_id, args) + | ty::Coroutine(def_id, args) + | ty::CoroutineClosure(def_id, args) => { + Instance::new_raw(def_id, self.tcx.erase_and_anonymize_regions(args)) + } + _ => unreachable!(), + }; + let Ok(instance) = self.tcx.try_normalize_erasing_regions( + ty::TypingEnv::fully_monomorphized(), + Unnormalized::new_wip(instance), + ) else { + // Don't ICE on an impossible-to-normalize closure. + return; + }; + let mono_item = create_fn_mono_item(self.tcx, instance, DUMMY_SP); + if mono_item.node.is_instantiable(self.tcx) { + self.output.push(mono_item); + } + } + } + _ => {} + } + } + + fn is_root(&self, def_id: LocalDefId) -> bool { + !self.tcx.generics_of(def_id).requires_monomorphization(self.tcx) + && (self.is_extern(def_id) + || match self.strategy { + MonoItemCollectionStrategy::Eager => { + !matches!(self.tcx.codegen_fn_attrs(def_id).inline, InlineAttr::Force { .. }) + } + MonoItemCollectionStrategy::Lazy => { + self.entry_fn.and_then(|(id, _)| id.as_local()) == Some(def_id) + || self.tcx.is_reachable_non_generic(def_id) + || { + let attrs = self.tcx.codegen_fn_attrs(def_id); + attrs.flags.intersects( + CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL + | CodegenFnAttrFlags::EXTERNALLY_IMPLEMENTABLE_ITEM, + ) + } + } + MonoItemCollectionStrategy::NoConstOrStatic => false, + }) + } + + fn is_extern(&self, def_id: LocalDefId) -> bool { + let attrs = self.tcx.codegen_fn_attrs(def_id); + + if attrs.flags.contains(CodegenFnAttrFlags::FOREIGN_ITEM) { + return false; + } + + // If it is a builtin, it is not an extern. (used to remove weakly linked compiler builtins) + if self.tcx.is_compiler_builtins(def_id.to_def_id().krate) { + return false; + } + + let has_export_link_name = HasAttrs::get_attrs(def_id, &self.tcx).iter().any(|attr| match attr { + rustc_hir::Attribute::Parsed(rustc_hir::attrs::AttributeKind::ExportName { .. }) => true, + _ => false, + }); + let no_mangle = attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE); + + has_export_link_name || no_mangle + } + + /// If `def_id` represents a root, pushes it onto the list of + /// outputs. (Note that all roots must be monomorphic.) + + fn push_if_root(&mut self, def_id: LocalDefId) { + if self.is_root(def_id) { + let instance = Instance::mono(self.tcx, def_id.to_def_id()); + self.output.push(create_fn_mono_item(self.tcx, instance, DUMMY_SP)); + } + } + + /// As a special case, when/if we encounter the + /// `main()` function, we also have to generate a + /// monomorphized copy of the start lang item based on + /// the return type of `main`. This is not needed when + /// the user writes their own `start` manually. + fn push_extra_entry_roots(&mut self) { + let Some((main_def_id, EntryFnType::Main { .. })) = self.entry_fn else { + return; + }; + + let main_instance = Instance::mono(self.tcx, main_def_id); + if self.tcx.should_codegen_locally(main_instance) { + self.output.push(create_fn_mono_item( + self.tcx, + main_instance, + self.tcx.def_span(main_def_id), + )); + } + + let Some(start_def_id) = self.tcx.lang_items().start_fn() else { + self.tcx.dcx().emit_fatal(crate::errors::StartNotFound); + }; + let main_ret_ty = self.tcx.fn_sig(main_def_id).no_bound_vars().unwrap().output(); + + // Given that `main()` has no arguments, + // then its return type cannot have + // late-bound regions, since late-bound + // regions must appear in the argument + // listing. + let main_ret_ty = self.tcx.normalize_erasing_regions( + ty::TypingEnv::fully_monomorphized(), + Unnormalized::new_wip(main_ret_ty.no_bound_vars().unwrap()), + ); + + let start_instance = Instance::expect_resolve( + self.tcx, + ty::TypingEnv::fully_monomorphized(), + start_def_id, + self.tcx.mk_args(&[main_ret_ty.into()]), + DUMMY_SP, + ); + + self.output + .push(create_fn_mono_item(self.tcx, start_instance, DUMMY_SP)); + } +} + +fn visit_drop_use<'tcx>( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + is_direct_call: bool, + source: Span, + output: &mut MonoItems<'tcx>, +) { + let instance = Instance::resolve_drop_glue(tcx, ty); + visit_instance_use(tcx, instance, is_direct_call, source, output); +} + +fn collect_const_value<'tcx>(tcx: TyCtxt<'tcx>, value: mir::ConstValue, output: &mut MonoItems<'tcx>) { + match value { + mir::ConstValue::Scalar(Scalar::Ptr(ptr, _size)) => collect_alloc(tcx, ptr.provenance.alloc_id(), output), + mir::ConstValue::Indirect { alloc_id, .. } | mir::ConstValue::Slice { alloc_id, meta: _ } => { + collect_alloc(tcx, alloc_id, output) + } + _ => {} + } +} + +fn visit_instance_use<'tcx>( + tcx: TyCtxt<'tcx>, + instance: ty::Instance<'tcx>, + is_direct_call: bool, + source: Span, + output: &mut MonoItems<'tcx>, +) { + debug!("visit_item_use({:?}, is_direct_call={:?})", instance, is_direct_call); + if !tcx.should_codegen_locally(instance) { + return; + } + if let Some(intrinsic) = tcx.intrinsic(instance.def_id()) { + if let Some(_requirement) = ValidityRequirement::from_intrinsic(intrinsic.name) { + // The intrinsics assert_inhabited, assert_zero_valid, and assert_mem_uninitialized_valid will + // be lowered in codegen to nothing or a call to panic_nounwind. So if we encounter any + // of those intrinsics, we need to include a mono item for panic_nounwind, else we may try to + // codegen a call to that function without generating code for the function itself. + let def_id = tcx.require_lang_item(LangItem::PanicNounwind, source); + let panic_instance = Instance::mono(tcx, def_id); + if tcx.should_codegen_locally(panic_instance) { + output.push(create_fn_mono_item(tcx, panic_instance, source)); + } + } else if !intrinsic.must_be_overridden && !tcx.sess.replaced_intrinsics.contains(&intrinsic.name) { + // Codegen the fallback body of intrinsics with fallback bodies. + // We have to skip this otherwise as there's no body to codegen. + // We also skip intrinsics the backend handles, to reduce monomorphizations. + let instance = ty::Instance::new_raw(instance.def_id(), instance.args); + if tcx.should_codegen_locally(instance) { + output.push(create_fn_mono_item(tcx, instance, source)); + } + } + } + + match instance.def { + ty::InstanceKind::Virtual(..) | ty::InstanceKind::Intrinsic(_) => { + if !is_direct_call { + bug!("{:?} being reified", instance); + } + } + ty::InstanceKind::Shim(ty::ShimKind::ThreadLocal(..)) => { + bug!("{:?} being reified", instance); + } + ty::InstanceKind::Shim(ty::ShimKind::DropGlue(_, None)) => { + // Don't need to emit noop drop glue if we are calling directly. + // + // Note that we also optimize away the call to visit_instance_use in vtable construction + // (see create_mono_items_for_vtable_methods). + if !is_direct_call { + output.push(create_fn_mono_item(tcx, instance, source)); + } + } + ty::InstanceKind::Shim( + ty::ShimKind::DropGlue(_, Some(_)) + | ty::ShimKind::FutureDropPoll(..) + | ty::ShimKind::AsyncDropGlue(_, _) + | ty::ShimKind::AsyncDropGlueCtor(_, _) + | ty::ShimKind::VTable(..) + | ty::ShimKind::Reify(..) + | ty::ShimKind::ClosureOnce { .. } + | ty::ShimKind::ConstructCoroutineInClosure { .. } + | ty::ShimKind::FnPtr(..) + | ty::ShimKind::Clone(..) + | ty::ShimKind::FnPtrAddr(..), + ) + | ty::InstanceKind::Item(..) => { + output.push(create_fn_mono_item(tcx, instance, source)); + } + } +} + +fn create_fn_mono_item<'tcx>(_tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, source: Span) -> Spanned> { + respan(source, MonoItem::Fn(instance)) +} + +/// Scans the CTFE alloc in order to find function pointers and statics that must be monomorphized. +fn collect_alloc<'tcx>(tcx: TyCtxt<'tcx>, alloc_id: AllocId, output: &mut MonoItems<'tcx>) { + match tcx.global_alloc(alloc_id) { + GlobalAlloc::Static(def_id) => { + assert!(!tcx.is_thread_local_static(def_id)); + let instance = Instance::mono(tcx, def_id); + if tcx.should_codegen_locally(instance) { + trace!("collecting static {:?}", def_id); + output.push(dummy_spanned(MonoItem::Static(def_id))); + } + } + GlobalAlloc::Memory(alloc) => { + trace!("collecting {:?} with {:#?}", alloc_id, alloc); + let ptrs = alloc.inner().provenance().ptrs(); + // avoid `ensure_sufficient_stack` in the common case of "no pointers" + if !ptrs.is_empty() { + rustc_data_structures::stack::ensure_sufficient_stack(move || { + for &prov in ptrs.values() { + collect_alloc(tcx, prov.alloc_id(), output); + } + }); + } + } + GlobalAlloc::Function { instance, .. } => { + if tcx.should_codegen_locally(instance) { + trace!("collecting {:?} with {:#?}", alloc_id, instance); + output.push(create_fn_mono_item(tcx, instance, DUMMY_SP)); + } + } + GlobalAlloc::VTable(ty, dyn_ty) => { + let alloc_id = tcx.vtable_allocation(( + ty, + dyn_ty + .principal() + .map(|principal| tcx.instantiate_bound_regions_with_erased(principal)), + )); + collect_alloc(tcx, alloc_id, output) + } + GlobalAlloc::TypeId { .. } => {} + } +} + +fn create_mono_items_for_default_impls<'tcx>(tcx: TyCtxt<'tcx>, item: ItemId, output: &mut MonoItems<'tcx>) { + let impl_ = tcx.impl_trait_header(item.owner_id); + + if impl_.polarity == ty::ImplPolarity::Negative { + return; + } + + if tcx.generics_of(item.owner_id).own_requires_monomorphization() { + return; + } + + // Lifetimes never affect trait selection, so we are allowed to eagerly + // instantiate an instance of an impl method if the impl (and method, + // which we check below) is only parameterized over lifetime. In that case, + // we use the ReErased, which has no lifetime information associated with + // it, to validate whether or not the impl is legal to instantiate at all. + let only_region_params = |param: &ty::GenericParamDef, _: &_| match param.kind { + GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(), + GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { + unreachable!( + "`own_requires_monomorphization` check means that \ + we should have no type/const params" + ) + } + }; + let impl_args = GenericArgs::for_item(tcx, item.owner_id.to_def_id(), only_region_params); + let trait_ref = impl_.trait_ref.instantiate(tcx, impl_args); + + // Unlike 'lazy' monomorphization that begins by collecting items transitively + // called by `main` or other global items, when eagerly monomorphizing impl + // items, we never actually check that the predicates of this impl are satisfied + // in a empty param env (i.e. with no assumptions). + // + // Even though this impl has no type or const generic parameters, because we don't + // consider higher-ranked predicates such as `for<'a> &'a mut [u8]: Copy` to + // be trivially false. We must now check that the impl has no impossible-to-satisfy + // predicates. + if tcx.instantiate_and_check_impossible_predicates((item.owner_id.to_def_id(), impl_args)) { + return; + } + + let typing_env = ty::TypingEnv::fully_monomorphized(); + let trait_ref = tcx.normalize_erasing_regions(typing_env, trait_ref); + let overridden_methods = tcx.impl_item_implementor_ids(item.owner_id); + for method in tcx.provided_trait_methods(trait_ref.def_id) { + if overridden_methods.contains_key(&method.def_id) { + continue; + } + + if tcx.generics_of(method.def_id).own_requires_monomorphization() { + continue; + } + + // As mentioned above, the method is legal to eagerly instantiate if it + // only has lifetime generic parameters. This is validated by calling + // `own_requires_monomorphization` on both the impl and method. + let args = trait_ref.args.extend_to(tcx, method.def_id, only_region_params); + let instance = ty::Instance::expect_resolve(tcx, typing_env, method.def_id, args, DUMMY_SP); + + let mono_item = create_fn_mono_item(tcx, instance, DUMMY_SP); + if mono_item.node.is_instantiable(tcx) && tcx.should_codegen_locally(instance) { + output.push(mono_item); + } + } +} diff --git a/cga_analysis/src/panics.rs b/cga_analysis/src/panics.rs new file mode 100644 index 0000000..0d763a5 --- /dev/null +++ b/cga_analysis/src/panics.rs @@ -0,0 +1,75 @@ +use lazy_regex::regex; +use rustc_hir::def_id::DefId; +use rustc_middle::ty::TyCtxt; + +use crate::{Use, UseKind}; + +/// Determination if a [`Use`] is a kind of panic. +/// +/// Derived using [`Use::panic_status`]. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum PanicStatus { + /// Use is not a call any panic impl. + NoDirectPanic, + /// Use is a call to a panic impl. + PanicImpl, + /// Use is a MIR assert terminator. + /// + /// An example would be the overflow check with a plain addition ex.: `1 + 1` + /// + /// These are translated to a panic in a later compiler pass. + Asserts, +} + +impl PanicStatus { + /// This variant can not be considered to be a panic. + pub fn none(&self) -> bool { + matches!(self, PanicStatus::NoDirectPanic) + } + + /// This variant is some form of a panic. + pub fn any(&self) -> bool { + !self.none() + } +} + +/// Check if the item is one of the multiple invocations in core/std that could be called panic. +/// +/// Examples would be the panic! or assert! macros. However in the MIR callgraph we could see a call to +/// `core::panicking` or `core::rt::panic_fmt`, depending on the version of core. +/// +/// This analysis needs to be conservative. If you don't have enough information to determine the status, assume it panics. +pub fn item_is_panic_impl(tcx: TyCtxt<'_>, def_id: DefId) -> PanicStatus { + // A closure is panic free if the function it's defined in is panic free. + let owner = tcx.typeck_root_def_id(def_id); + + // TODO replace with #[rustc_panic_entrypoint] + let path = tcx.def_path_str(owner); + let regex = regex!("^(core|std)::(panicking::|rt::panic_fmt)"); + if regex.is_match(&path) { + return PanicStatus::PanicImpl; + } + + PanicStatus::NoDirectPanic +} + +impl Use<'_> { + /// Determines whether the [`Use`] panics and how. + /// + /// Only checks this specific [`Use`], and does not expand the call tree below. + pub fn panic_status(&self, tcx: TyCtxt<'_>) -> PanicStatus { + match &self.kind { + UseKind::BuiltinMirAssert => PanicStatus::Asserts, + UseKind::AsyncPanic => PanicStatus::PanicImpl, // Will always panic. + _ => { + // Check if callee is an implementation which + // colloquially would be called a 'panic'. + if let Some(callee) = self.opt_def_id() { + item_is_panic_impl(tcx, callee) + } else { + PanicStatus::NoDirectPanic + } + } + } + } +} diff --git a/cga_analysis/src/post_mono.rs b/cga_analysis/src/post_mono.rs new file mode 100644 index 0000000..f71a559 --- /dev/null +++ b/cga_analysis/src/post_mono.rs @@ -0,0 +1,369 @@ +//! Run a post-mono pass on MIR, possibly from other crates. +//! In post-mono MIR, all functions are possible to resolve to an [`Instance`]. +//! +//! This traverses the callgraph along function call edges, starting from mono roots. +//! +//! A "mono root" is an externally reachable function, such as `main`, `pub fn`, something with `no_mangle` or `export_name`, +//! or weird things such as `std::rt::lang_start`. +//! +//! There are three functions in play during this pass. +//! 1. The potentially panicking function being called, which we call the 'callee'. +//! 2. The generic function being instantiated, *which may not be in the current crate*, called the +//! 'caller'. +//! 3. The functions that instantiated it (recursively, back to the mono root). +//! +//! ## Recommended reading +//! - [MIR Debugging](https://rustc-dev-guide.rust-lang.org/mir/debugging.html) + +use rustc_data_structures::{graph::StartNode, unord::UnordSet}; +use rustc_hir::{CoroutineDesugaring, CoroutineKind, def_id::DefId}; +use rustc_log::tracing::*; +use rustc_middle::mir::{self, Body, CastKind, Location, Rvalue, Terminator, TerminatorKind, visit::Visitor as _}; +use rustc_middle::mono::MonoItem; +use rustc_middle::span_bug; +use rustc_middle::ty::adjustment::PointerCoercion; +use rustc_middle::ty::{ + self, EarlyBinder, GenericArgsRef, Instance, InstanceKind, ShimKind, TyCtxt, TypeFoldable, TypingEnv, +}; +use rustc_span::Span; +use rustc_type_ir::CoroutineArgs; + +use crate::dyn_object::dyn_object_call_to_impl_methods; +use crate::{AnalysisState, Callgraph, Use, UseKind}; + +/// A node in the post-monomorphisation analysis. +struct PostMonoNode<'b, 'a, 'tcx> { + /// The function we are currently traversing. + instance: Instance<'tcx>, + + /// Its body. + body: &'tcx Body<'tcx>, + + // The global analysis context. + state: &'b mut AnalysisState<'a, 'tcx>, +} + +/// Analyse all used items recursively, starting from roots. +pub fn generate_callgraph<'tcx>(tcx: TyCtxt<'tcx>, roots: UnordSet>) -> Callgraph<'tcx> { + trace!("all roots: {roots:?}"); + + let mut callgraph = Callgraph::new(); + + // We need to sort these for query stability. + let roots = tcx.with_stable_hashing_context(move |mut hcx| roots.into_sorted(&mut hcx, true)); + for root in roots { + let instance = match root { + // global asm is always an exported constraint + MonoItem::GlobalAsm(..) => continue, + // NOTE: `mono` panics if item has generics rather than silently doing the wrong thing + MonoItem::Static(def_id) => Instance::mono(tcx, def_id), + MonoItem::Fn(instance) => { + let def = instance.def_id(); + + // Skip std::rt::lang_start. Technically we could lint on it as if it were the span + // of `main`, but the lint would never be useful. + // Note that we may not have `lang_start` yet if we're still compiling core. + if Some(def) == tcx.lang_items().start_fn() { + continue; + } + + instance + } + }; + debug!("linting root: {instance:?}"); + let mut state = AnalysisState { + tcx, + callgraph: &mut callgraph, + }; + + state.register_root(instance); + + PostMonoNode::visit_instance(&mut state, instance); + } + + callgraph +} + +impl<'b, 'a, 'tcx> mir::visit::Visitor<'tcx> for PostMonoNode<'b, 'a, 'tcx> { + fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { + debug!("visit_terminator"); + for use_ in self.get_call_def_mir(terminator) { + debug!("visit_terminator def mir {use_:#?}"); + self.on_edge(use_); + } + + if let TerminatorKind::Assert { .. } = terminator.kind { + debug!("visit_terminator assert"); + let use_ = Use::for_location(UseKind::BuiltinMirAssert, self.body, terminator.source_info); + self.on_edge(use_); + } + + self.super_terminator(terminator, location); + } + + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + let Some((_call_span, use_kind)) = self.find_dynamic_cast(rvalue) else { + return; + }; + + let use_ = Use::for_location(use_kind, self.body, self.body.source_info(location).clone()); + self.on_edge(use_); + self.super_rvalue(rvalue, location); + } +} + +impl<'b, 'a, 'tcx> PostMonoNode<'b, 'a, 'tcx> { + fn find_dynamic_cast(&self, rval: &Rvalue<'tcx>) -> Option<(Span, UseKind<'tcx>)> { + match rval { + Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer(_), _), operand, _fn_ptr_ty) => { + let call_span = operand.span(self.body); + let Some((pre_mono_callee, generic_args)) = operand.const_fn_def() else { + span_bug!( + call_span, + "don't know how to handle ReifyFnPointer cast of non-constant fn {operand:?}" + ); + }; + + let callee_instance = self.monomorphize_instance(pre_mono_callee, generic_args, call_span); + + Some((call_span, UseKind::FnPtrCast(callee_instance))) + } + _ => None, + } + } + + /// On MIR callgraph edge. + /// + /// An edge is a [`Use`] in the definition of a monomorphized [`Instance`]. + fn on_edge(&mut self, use_: Use<'tcx>) { + info!("edge {:#?} {use_:#?}", self.instance); + + self.state.register_edge(self.instance, use_.clone()); + + match use_.kind { + UseKind::BuiltinMirAssert | UseKind::AsyncPanic => { + // Non-recursive leaf node + return; + } + // In any other case we should have fully monomorphized the function. + UseKind::Called(_) | UseKind::VTableCalled(_) | UseKind::FnPtrCast(_) => { + let callee_instance = use_.opt_instance().unwrap_or_else(|| { + span_bug!( + use_.span, + "called expect_instance on a use_ that does not have an instance" + ) + }); + + if matches!(callee_instance.def, InstanceKind::Virtual(..)) { + self.visit_virtual(*callee_instance, use_.span); + } else { + PostMonoNode::visit_instance(self.state, *callee_instance); + } + } + UseKind::Entrypoint(..) => { + span_bug!( + use_.span, + "Entrypoint is a root node of the graph, and cannot be logically generated mid-analysis" + ); + } + } + } + + fn visit_virtual(&mut self, instance: Instance<'tcx>, span: Span) { + let InstanceKind::Virtual(virtual_def_id, _) = instance.def else { + span_bug!(self.body.span, "Visiting a non-virtual {instance:?}"); + }; + + if !self.state.callgraph.visit(instance) { + debug!("already linted {instance:?}"); + return; + } + + info!("visiting virtual {instance:?}"); + + let mut this: PostMonoNode<'_, 'a, 'tcx> = PostMonoNode { + instance, + body: self.body, // Re-use old body + state: self.state, + }; + + for method in dyn_object_call_to_impl_methods(virtual_def_id, this.instance.args, this.state.tcx) { + // Add a 'fake' use to each of the VTable entries. + this.on_edge(Use { + kind: UseKind::VTableCalled(method), + hir_id: None, + span, + }); + } + } + + fn visit_instance(state: &mut AnalysisState<'a, 'tcx>, mut instance: Instance<'tcx>) { + if !state.callgraph.visit(instance) { + debug!("already linted {instance:?}"); + return; + } + + info!("visiting {instance:?}"); + + let owner = instance.def_id(); + + if let Some(intrinsic) = state.tcx.intrinsic(owner) { + if intrinsic.must_be_overridden { + // Instrinsics with no fallback body are qualified as part of the compiler, + // and will panic in `instance_mir`. + debug!("ignoring intrinsic {owner:?}"); + return; + } + + // See equivalent code in `rustc_monomorphize::collector::visit_instance_use`. + if state.tcx.sess.replaced_intrinsics.contains(&intrinsic.name) { + // This is normal: LLVM in particular has specialized overrides for many + // integer operations. But that means the fallback body won't actually be + // used, so don't try to check it. + debug!("ignoring overridden intrinsic body for {owner:?}"); + return; + } + + debug!("using fallback body for {owner:?}"); + instance = ty::Instance::new_raw(owner, instance.args); + } else if !state.tcx.is_mir_available(owner) { + info!("no MIR for {owner:?}"); + return; + } + + let body = state.tcx.instance_mir(instance.def); + info!("instanced {instance:?}"); + + let mut this: PostMonoNode<'_, 'a, 'tcx> = PostMonoNode { instance, body, state }; + + if let Some(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) = body.coroutine_kind() { + // TODO this implementation block is fragile. + debug!("visiting (async coroutine) {instance:#?} {body:#?}"); + + // Visit all BB's associated with Unresumed discriminators. + // Do not visit "Returned" and "Panicked" discriminator bb's, instead emit a UseKind::AsyncPanic + // BB0 is the switch statement + + let switch_bb = &body[body.basic_blocks.start_node()]; + let TerminatorKind::SwitchInt { targets, .. } = &switch_bb.terminator().kind else { + span_bug!( + body.span, + "Expected async coroutine with a switch for the discriminator in the first basic block" + ); + }; + + for (value, bb) in targets.iter() { + const RETURNED: u128 = > as ty::CoroutineArgsExt>::RETURNED as u128; + const POISONED: u128 = > as ty::CoroutineArgsExt>::POISONED as u128; + + match value { + RETURNED => { + let data = &body[bb]; + let source_info = data.terminator().source_info; + + this.on_edge(Use::for_location(UseKind::AsyncPanic, body, source_info)); + } + POISONED => {} // Ignore + _ => { + for (bb, data) in mir::traversal::Preorder::new(body, bb) { + this.visit_basic_block_data(bb, data); + } + } + } + } + + // TODO coroutine drop? + } else { + // TODO instead of directly traversing the MIR, try to const_eval any calls to eliminate unreachable SwitchInt branches. + debug!("visiting (normal preorder) {instance:#?}"); + for (bb, data) in mir::traversal::preorder(body) { + this.visit_basic_block_data(bb, data); + } + } + } + + /// For a given terminator, yield all definitions that are called. + fn get_call_def_mir(&self, terminator: &Terminator<'tcx>) -> Vec> { + let tcx = self.state.tcx; + let span = terminator.source_info.span; + + let call_instance = match &terminator.kind { + TerminatorKind::Call { func, .. } | TerminatorKind::TailCall { func, .. } => { + let Some((pre_mono_call, generic_args)) = func.const_fn_def() else { + match func.ty(self.body, tcx).kind() { + kind @ ty::FnDef(..) => { + span_bug!(span, "{kind:?} should have been a const_fn_def?") + } + ty::FnPtr(..) => {} + _ => { + // If this is anything other than a function item, it can't have generics and + // therefore must have been checked by the THIR pass. + // FIXME: are we sure is this true when we're passed an `impl Fn`? + tcx.dcx() + .span_delayed_bug(span, format!("called a non-function? {func:?}")); + } + } + debug!("ignoring call to non-constant function {func:?}"); + return vec![]; + }; + let mut instance = self.monomorphize_instance(pre_mono_call, generic_args, span); + + // Look for `::call` for some T. + // If T is a function type, the compiler synthesizes an impl, so we'll check + // the trait declaration in libcore which isn't what we want. Check if the + // function is annotated instead. + if let InstanceKind::Shim(ShimKind::FnPtr(_, ty) | ShimKind::FnPtrAddr(_, ty)) = instance.def + && let ty::FnDef(fn_item, fn_args) = ty.kind() + { + debug!("found function item {fn_item:?}"); + instance = self.monomorphize_instance(*fn_item, fn_args, span); + } + + // Double check if our instance is OK + + instance + } + TerminatorKind::Drop { place, .. } => { + let (ty, _) = self.monomorphize_args(place.ty(self.body, tcx)); + let instance = Instance::resolve_drop_glue(tcx, ty.ty); + if matches!(instance.def, InstanceKind::Shim(ShimKind::DropGlue(_, None))) { + debug!("ty `{ty:?}` does not need to be dropped"); + return vec![]; + } + debug!("resolve drop glue => instance={instance:?}, ty={ty:?}"); + instance + } + _ => return vec![], + }; + + vec![Use::for_location( + UseKind::Called(call_instance), + self.body, + terminator.source_info, + )] + } + + fn monomorphize_instance(&self, def_id: DefId, generic_args: GenericArgsRef<'tcx>, span: Span) -> Instance<'tcx> { + info!( + "monomorphize_instance {def_id:?} {generic_args:?} for {:?}", + self.instance + ); + let (mono_args, typing_env) = self.monomorphize_args(generic_args); + Instance::expect_resolve(self.state.tcx, typing_env, def_id, mono_args, span) + } + + fn monomorphize_args(&self, generic_args: T) -> (T, TypingEnv<'tcx>) + where + T: TypeFoldable>, + { + let tcx = self.state.tcx; + + let env = TypingEnv::codegen(tcx, self.instance.def_id()); + let args = + self.instance + .instantiate_mir_and_normalize_erasing_regions(tcx, env, EarlyBinder::bind(tcx, generic_args)); + + info!("monomorphize_args OK"); + (args, env) + } +} diff --git a/cga_cargo/Cargo.toml b/cga_cargo/Cargo.toml new file mode 100644 index 0000000..b58de7a --- /dev/null +++ b/cga_cargo/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "cga_cargo" +version = "0.1.0" +edition = "2024" +license = "MIT" + +[dependencies] +cga_data = { path = "../cga_data" } + +anyhow = "1.0" + +clap = { version = "4.6", features = ["derive"] } +serde_json = "1.0" +toml = "1.1" + +tracing-subscriber = "0.3" + +lazy-regex = "3.6" diff --git a/cga_cargo/src/args/cargo.rs b/cga_cargo/src/args/cargo.rs new file mode 100644 index 0000000..742a7cb --- /dev/null +++ b/cga_cargo/src/args/cargo.rs @@ -0,0 +1,218 @@ +use clap::{Parser, ValueEnum}; +use std::path::PathBuf; +use std::process::Command; + +#[derive(Debug, Parser)] +#[clap(next_help_heading = "Package Selection")] +pub struct PackageArgs { + /// Package(s) to process + #[clap(long, short = 'p')] + pub package: Option, + + /// Check all packages in the workspace + #[clap(long)] + pub workspace: bool, +} + +#[derive(Debug, Parser)] +#[clap(next_help_heading = "Feature Selection")] +pub struct FeatureArgs { + /// Space or comma separated list of features to activate + #[clap(long, short = 'F')] + pub features: Option, + + /// Activate all available features + #[clap(long)] + pub all_features: bool, + + /// Do not activate the `default` feature + #[clap(long)] + pub no_default_features: bool, +} + +#[derive(Debug, Parser)] +#[clap(next_help_heading = "Compilation Options")] +pub struct CompilationArgs { + /// Number of parallel jobs, defaults to # of CPUs + #[clap(long, short = 'j')] + pub jobs: Option, + + /// Check artifacts in release mode, with optimizations + #[clap(long, short)] + pub release: bool, + + /// Check artifacts with the specified profile + #[clap(long)] + pub profile: Option, + + /// Check for the target triple + #[clap(long)] + pub target: Option, + + /// Directory for all generated artifacts + #[clap(long, default_value = "./target/cga")] + pub target_dir: PathBuf, +} + +#[derive(Debug, Parser)] +#[clap(next_help_heading = "Manifest Options")] +pub struct ManifestArgs { + /// Path to Cargo.toml + #[clap(long, short)] + pub manifest_path: Option, + + /// Assert that `Cargo.lock` will remain unchanged + #[clap(long)] + pub locked: bool, + + /// Run without accessing the network + #[clap(long)] + pub offline: bool, + + /// Equivalent to specifying both --locked and --offline + #[clap(long)] + pub frozen: bool, +} + +#[derive(Debug, ValueEnum, Default, Clone, Copy)] +#[clap(rename_all = "snake_case")] +pub enum Color { + #[default] + Auto, + Always, + Never, +} + +impl core::fmt::Display for Color { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Color::Auto => "auto", + Color::Always => "always", + Color::Never => "never", + }) + } +} + +/// Set of Cargo arguments that CGA also supports (for some commands). +#[derive(Debug, Parser)] +#[clap(next_help_heading = "Cargo Options")] +pub struct CargoArgs { + /// Do not print cargo log messages + #[clap(long, short)] + pub quiet: bool, + + /// Use verbose output + #[clap(long, short)] + pub verbose: bool, + + /// Coloring + #[clap(long)] + pub color: Option, + + /// Pass extra arguments to '-Z build-std' when cargo is invoked + #[clap(long = "build-std")] + pub build_std: Option, + + #[clap(flatten)] + pub package: PackageArgs, + + #[clap(flatten)] + pub feature: FeatureArgs, + + #[clap(flatten)] + pub compilation: CompilationArgs, + + #[clap(flatten)] + pub manifest: ManifestArgs, +} + +impl PackageArgs { + pub fn apply(&self, command: &mut Command) { + if let Some(package) = self.package.as_ref() { + command.args(&["--package", package]); + } + + if self.workspace { + command.arg("--workspace"); + } + } +} + +impl FeatureArgs { + pub fn apply(&self, command: &mut Command) { + if let Some(features) = self.features.as_ref() { + command.args(&["--features", features]); + } + + if self.all_features { + command.arg("--all-features"); + } + + if self.no_default_features { + command.arg("--no-default-features"); + } + } +} + +impl CompilationArgs { + pub fn apply(&self, command: &mut Command) { + if let Some(jobs) = self.jobs.as_ref() { + command.args(&["--jobs", &format!("{jobs}")]); + } + + if self.release { + command.arg("--release"); + } + + if let Some(profile) = self.profile.as_ref() { + command.args(&["--profile", profile]); + } + + if let Some(target) = self.target.as_ref() { + command.args(&["--target", target]); + } + + command.args(&["--target-dir", &format!("{}", self.target_dir.display())]); + } +} + +impl ManifestArgs { + pub fn apply(&self, command: &mut Command) { + if let Some(manifest_path) = self.manifest_path.as_ref() { + command.args(&["--manifest-path", &format!("{}", manifest_path.display())]); + } + + if self.locked { + command.arg("--locked"); + } + + if self.offline { + command.arg("--offline"); + } + + if self.frozen { + command.arg("--frozen"); + } + } +} + +impl CargoArgs { + pub fn apply(&self, command: &mut Command) { + if self.quiet { + command.arg("--quiet"); + } + + if self.verbose { + command.arg("--verbose"); + } + + if let Some(color) = self.color { + command.arg(format!("--color={color}")); + } + + self.package.apply(command); + self.feature.apply(command); + self.compilation.apply(command); + self.manifest.apply(command); + } +} diff --git a/cga_cargo/src/args/mod.rs b/cga_cargo/src/args/mod.rs new file mode 100644 index 0000000..f13ff29 --- /dev/null +++ b/cga_cargo/src/args/mod.rs @@ -0,0 +1,70 @@ +use clap::{Parser, ValueEnum}; +use std::path::PathBuf; + +use crate::args::cargo::CargoArgs; + +pub(crate) mod cargo; + +#[derive(Parser)] +pub struct Opts { + #[clap(subcommand)] + pub subcommand: Subcommand, +} + +#[derive(Debug, ValueEnum, Default, Clone, Copy)] +#[clap(rename_all = "snake_case")] +pub enum OutputFormat { + #[default] + Json, + Dot, +} + +#[derive(Debug, Parser)] +pub enum Subcommand { + /// Checks a package to catch whether functions are used that are not allowed, such as panics. + Check { + /// Configure the cga::panics lint to allow implicit panics from async generation. + #[clap(long)] + allow_async_panic: bool, + + /// Configure the cga::panics lint to allow implicit asserts. + #[clap(long)] + allow_assert: bool, + + /// Path to a TOML file containing rules. + #[clap(long)] + config_path: Option, + + #[clap(flatten)] + cargo: CargoArgs, + }, + /// Extracts the callgraph for a package and all their dependencies. + Extract { + /// Path file will be written to [default: ./callgraph.] + #[clap(short, long)] + output_path: Option, + + /// Format of the output file + #[clap(short = 'T', long, default_value = "json")] + output_format: OutputFormat, + + #[clap(flatten)] + cargo: CargoArgs, + }, + + /// Perform both checking and extracting at the same time for use in tests. + /// + /// Hidden from general help because this is meant for internal use only. + #[clap(hide = true)] + Test { + #[clap(long)] + config_path: Option, + + #[clap(flatten)] + cargo: CargoArgs, + + /// Path file will be written to + #[clap(short, long)] + output_path: PathBuf, + }, +} diff --git a/cga_cargo/src/callgraph.rs b/cga_cargo/src/callgraph.rs new file mode 100644 index 0000000..77f5a3e --- /dev/null +++ b/cga_cargo/src/callgraph.rs @@ -0,0 +1,55 @@ +use cga_data::callgraph::{Callgraph, EdgeKind, NodeId}; +use std::io::Write; + +pub fn emit_dot(callgraph: &Callgraph, mut out: impl Write) -> anyhow::Result<()> { + out.write_fmt(format_args!("digraph {{\n"))?; + out.write_fmt(format_args!("\tgraph [ splines=true overlap=false ]\n"))?; + + for (node_id, node) in callgraph.nodes.iter().enumerate() { + let node_id = NodeId::from(node_id as u32); + + const NAME_MAX_LEN: usize = 100; + let name = if let Some((idx, _)) = node.name.char_indices().nth(NAME_MAX_LEN) { + format!("{}...", &node.name[..idx]) + } else { + node.name.clone() + }; + + let is_root = callgraph.roots.contains(&node_id); + let decoration = if is_root { + format!("penwidth=\"3\" ") + } else { + String::new() + }; + + out.write_fmt(format_args!( + "\t{} [ label = {:?} {decoration}]\n", + Into::::into(node_id), + name + ))?; + } + + for (from, tos) in callgraph.edges.iter() { + let from = Into::::into(*from); + for to in tos { + let edge = match to.kind { + EdgeKind::Called(node_id) | EdgeKind::FnPtrCast(node_id) => { + let to = Into::::into(node_id); + format!("{from} -> {to}") + } + EdgeKind::VTableCalled(node_id) => { + let to = Into::::into(node_id); + format!("{from} -> {to} [style=dashed]") + } + EdgeKind::BuiltinAssert => format!("{from} -> assert"), + EdgeKind::AsyncPanic => format!("{from} -> async_panic"), + }; + + out.write_fmt(format_args!("\t{edge}\n"))?; + } + } + + out.write_fmt(format_args!("}}\n"))?; + + Ok(()) +} diff --git a/cga_cargo/src/command.rs b/cga_cargo/src/command.rs new file mode 100644 index 0000000..053864d --- /dev/null +++ b/cga_cargo/src/command.rs @@ -0,0 +1,176 @@ +use cga_data::{ + self as data, + callgraph::Callgraph, + cmd::{DriverInput, LintConfig}, +}; +use lazy_regex::Regex; +use std::{ + env, + fs::File, + io::{BufRead, BufReader, BufWriter}, + path::PathBuf, + process::{Command, ExitStatus, Stdio}, + str::FromStr, +}; + +use crate::args::{Opts, OutputFormat, Subcommand, cargo::CargoArgs}; + +fn toolchain() -> String { + let toolchain = env!("RUSTUP_TOOLCHAIN"); + let regex = Regex::new("^(nightly-[0-9]{4}-[0-9]{2}-[0-9]{2})(?:-.+)?$").unwrap(); + regex + .captures(toolchain) + .expect(&format!( + "RUSTUP_TOOLCHAIN env not set with expected name, got {toolchain:?}" + )) + .get(1) + .unwrap() + .as_str() + .to_owned() +} + +fn prepare_cargo(input: &DriverInput, args: CargoArgs) -> Command { + // Note: use rustup cargo alias instead of a toolchain-specific cargo binary. + let mut cmd = Command::new("cargo"); + let driver = env::current_exe().unwrap().with_file_name("cga-driver"); + + let build_std = if let Some(build_std) = &args.build_std { + &format!("build-std={build_std}") + } else { + "build-std" + }; + + cmd + // Require nightly **cargo** for "-Z build-std" (our rustc-driver does not care) + .arg(&format!("+{}", toolchain())) + // Force std to be freshly built so that we can get MIR. + .args(["-Z", build_std]) + .arg("check") + .env("RUSTC", driver) + .env("CGA_INPUT", serde_json::to_string(&input).unwrap()) + // Disable incremental compilation such that we always get MIR. + .env("CARGO_INCREMENTAL", "0") + // Request to always encode mir, and speed up compilation. + .env("RUSTFLAGS", "-Zalways-encode-mir -Zhint-mostly-unused"); + + args.apply(&mut cmd); + + cmd +} + +struct ExtractInstructions { + output_path: PathBuf, + output_format: OutputFormat, +} + +pub fn exec(opts: Opts) -> ExitStatus { + let mut input = data::cmd::DriverInput::default(); + + let (mut cmd, extract_instr) = match opts.subcommand { + Subcommand::Check { + cargo, + allow_assert, + allow_async_panic, + config_path, + } => { + let mut config = if let Some(config_path) = config_path { + toml::from_str(&std::fs::read_to_string(config_path).unwrap()).unwrap() + } else { + LintConfig::default() + }; + + config.options.allow_async_panic |= allow_async_panic; + config.options.allow_assert |= allow_assert; + + input.lint = Some(config); + (prepare_cargo(&input, cargo), None) + } + Subcommand::Extract { + output_path, + output_format, + cargo, + } => { + input.extract = true; + + let output_path = output_path.unwrap_or_else(|| { + PathBuf::from_str(match output_format { + OutputFormat::Json => "./callgraph.json", + OutputFormat::Dot => "./callgraph.dot", + }) + .unwrap() + }); + + ( + prepare_cargo(&input, cargo), + Some(ExtractInstructions { + output_path, + output_format, + }), + ) + } + Subcommand::Test { + config_path, + cargo, + output_path, + } => { + let config = if let Some(config_path) = config_path { + toml::from_str(&std::fs::read_to_string(config_path).unwrap()).unwrap() + } else { + LintConfig::default() + }; + + input.extract = true; + input.lint = Some(config); + + ( + prepare_cargo(&input, cargo), + Some(ExtractInstructions { + output_path, + output_format: OutputFormat::Json, + }), + ) + } + }; + + if let Some(ExtractInstructions { + output_path, + output_format, + }) = extract_instr + { + cmd.stdout(Stdio::piped()); + cmd.stderr(Stdio::piped()); + + let mut child = cmd.spawn().unwrap(); + let reader = BufReader::new(child.stderr.as_mut().unwrap()); + + let mut emissions = vec![]; + + let lines = reader.lines(); + for line in lines { + let line = line.unwrap(); + if let Some((_, suffix)) = line.split_once("cga-emit:") { + emissions.push(suffix.to_owned()); + } else { + // Pass the line through our parent process. + eprintln!("{line}"); + } + } + + let callgraph = Callgraph::merge( + emissions + .iter() + .map(|e| serde_json::from_reader::<_, Callgraph>(BufReader::new(File::open(e).unwrap())).unwrap()), + ); + + let file = BufWriter::new(File::create(output_path).unwrap()); + + match output_format { + OutputFormat::Json => serde_json::to_writer_pretty(file, &callgraph).unwrap(), + OutputFormat::Dot => crate::callgraph::emit_dot(&callgraph, file).unwrap(), + }; + + child.wait().unwrap() + } else { + cmd.status().unwrap() + } +} diff --git a/cga_cargo/src/lib.rs b/cga_cargo/src/lib.rs new file mode 100644 index 0000000..fefa684 --- /dev/null +++ b/cga_cargo/src/lib.rs @@ -0,0 +1,31 @@ +use clap::Parser; + +use crate::{args::Opts, command::exec}; + +mod args; +mod callgraph; +mod command; + +pub fn main() -> Result<(), i32> { + tracing_subscriber::fmt().pretty().init(); + + let args: Vec<_> = std::env::args().collect(); + + let args = if let Some(second) = args.get(1) + && second == "cga" + { + // Pop the first arg as we invoke from 'cargo cga'. + &args[1..] + } else { + &args[0..] + }; + + let opts: Opts = Opts::parse_from(args); + + let status = exec(opts); + match status.code() { + Some(0) => Ok(()), + Some(other) => Err(other), + None => Err(-1), + } +} diff --git a/cga_data/Cargo.toml b/cga_data/Cargo.toml new file mode 100644 index 0000000..5aa9032 --- /dev/null +++ b/cga_data/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "cga_data" +version = "0.1.0" +edition = "2024" +license = "MIT" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } \ No newline at end of file diff --git a/cga_data/src/callgraph.rs b/cga_data/src/callgraph.rs new file mode 100644 index 0000000..7b03289 --- /dev/null +++ b/cga_data/src/callgraph.rs @@ -0,0 +1,138 @@ +use std::{ + collections::{BTreeMap, BTreeSet, HashMap}, + ops::RangeInclusive, +}; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct FilePosition { + pub line: u32, + pub column: u32, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct Span(pub RangeInclusive); + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct Location { + pub path: String, + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct Node { + pub name: String, + pub location: Location, +} + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)] +pub struct NodeId(u32); + +impl From for NodeId { + fn from(value: u32) -> Self { + NodeId(value) + } +} + +impl Into for NodeId { + fn into(self) -> u32 { + self.0 + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum EdgeKind { + /// Directly called a function. + Called(NodeId), + + /// Possibly called a function as part of a vtable call. + VTableCalled(NodeId), + + /// Created a function pointer, hence we must assume it was called. + FnPtrCast(NodeId), + + /// A builtin Rust MIR assert was called. + /// + /// Useful for panic analysis. + BuiltinAssert, + + /// The implicit panic(s) associated with any async coroutine. + /// + /// All async coroutines have a "poll after Ready" state, which always panics. + /// These panics are (as of yet) unavoidable when using async. + /// + /// Because there is no mitigation for this, we put these panics into a separate category. + AsyncPanic, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct Edge { + pub kind: EdgeKind, + + /// The location associated with the edge. + /// + /// An example would be the location of the expression which is a function call or a pointer cast. + pub location: Location, +} + +#[derive(Default, Debug, Clone, PartialEq, Hash, Serialize, Deserialize)] +pub struct Callgraph { + pub roots: BTreeSet, + pub nodes: Vec, + pub edges: BTreeMap>, +} + +impl EdgeKind { + pub fn map(&self, mut f: impl FnMut(&NodeId) -> NodeId) -> EdgeKind { + match self { + EdgeKind::Called(node_id) => EdgeKind::Called(f(node_id)), + EdgeKind::VTableCalled(node_id) => EdgeKind::VTableCalled(f(node_id)), + EdgeKind::FnPtrCast(node_id) => EdgeKind::FnPtrCast(f(node_id)), + EdgeKind::BuiltinAssert => EdgeKind::BuiltinAssert, + EdgeKind::AsyncPanic => EdgeKind::AsyncPanic, + } + } +} + +impl Callgraph { + pub fn merge<'a>(others: impl Iterator) -> Callgraph { + let mut result = Callgraph::default(); + let mut node_dict = HashMap::new(); + + for other in others { + for node in other.nodes.iter() { + node_dict.entry(node.clone()).or_insert_with(|| { + let node_id = NodeId(result.nodes.len() as u32); + result.nodes.push(node.clone()); + node_id + }); + } + + for root in other.roots.iter() { + result.roots.insert(node_dict[&other.nodes[root.0 as usize]]); + } + + for (from, tos) in other.edges.iter() { + let from = node_dict[&other.nodes[from.0 as usize]]; + let new_tos = result + .edges + .entry(from) + .or_insert_with(|| Vec::with_capacity(tos.len())); + + for to in tos { + let new_to = Edge { + kind: to.kind.map(|to| node_dict[&other.nodes[to.0 as usize]]), + location: to.location.clone(), + }; + + if !new_tos.contains(&new_to) { + new_tos.push(new_to); + } + } + } + } + + result + } +} diff --git a/cga_data/src/cmd.rs b/cga_data/src/cmd.rs new file mode 100644 index 0000000..c58a28b --- /dev/null +++ b/cga_data/src/cmd.rs @@ -0,0 +1,44 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct DriverInput { + pub lint: Option, + pub extract: bool, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct LintOptions { + #[serde(default)] + pub allow_async_panic: bool, + + #[serde(default)] + pub allow_assert: bool, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct LintConfig { + #[serde(default)] + pub options: LintOptions, + + /// If any element in the trace is from this whitelist, it will not emit a lint. + #[serde(default)] + pub whitelist: Vec, + + // If the leaf element is from this blacklist, and the whitelist does not apply to the trace, + // a `cga::blacklisted` lint will be emitted. + #[serde(default)] + pub blacklist: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum LintRule { + /// Apply rule to the simplified path of the instance, with the string as a regex. + Path(String), + + /// Apply rule to the canonical path of the instance, with the string as a regex. + Canonical(String), + + /// Apply rule to the crate of the instance, as exact string. + Crate(String), +} diff --git a/cga_data/src/lib.rs b/cga_data/src/lib.rs new file mode 100644 index 0000000..a007803 --- /dev/null +++ b/cga_data/src/lib.rs @@ -0,0 +1,2 @@ +pub mod callgraph; +pub mod cmd; diff --git a/cga_test_deps/Cargo.toml b/cga_test_deps/Cargo.toml new file mode 100644 index 0000000..5484965 --- /dev/null +++ b/cga_test_deps/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cga_test_deps" +version = "0.1.0" +edition = "2021" +license = "MIT" + +# Add dependencies here to make them available in ui tests. + +[dependencies] + +# Make sure we are not part of the rustc workspace. +[workspace] diff --git a/cga_test_deps/src/lib.rs b/cga_test_deps/src/lib.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/cga_test_deps/src/lib.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/deny.toml b/deny.toml index 9ddd12f..4aac543 100644 --- a/deny.toml +++ b/deny.toml @@ -74,8 +74,6 @@ ignore = [ #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, - { id = "RUSTSEC-2024-0370", reason = "proc-macro-error is unmaintained, no safe upgrade available, need upstream dependencies to migrate away from it." }, - { id = "RUSTSEC-2024-0436", reason = "there are no suitable replacements for paste right now; paste has been archived as read-only. It only affects compile time concatenation in macros. We will allow it for now" }, ] # If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is false, then it uses a built-in git library. @@ -94,7 +92,7 @@ allow = [ "MIT", "Apache-2.0", "Unicode-3.0", - "BSD-3-Clause", + #"BSD-3-Clause", #"Apache-2.0 WITH LLVM-exception", ] # The confidence threshold for detecting a license from license text. @@ -232,7 +230,7 @@ allow-git = [] [sources.allow-org] # github.com organizations to allow git sources for -github = ["embassy-rs"] +github = [] # gitlab.com organizations to allow git sources for gitlab = [] # bitbucket.org organizations to allow git sources for diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 367cc1f..e2bc4fa 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,5 @@ [toolchain] -components = ["rustfmt", "clippy"] +channel = "nightly-2026-06-29" +components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt", "clippy"] +profile = "minimal" +targets = ["thumbv8m.main-none-eabihf"] diff --git a/src/baremetal/mod.rs b/src/baremetal/mod.rs deleted file mode 100644 index c03b953..0000000 --- a/src/baremetal/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -use core::panic::PanicInfo; - -#[panic_handler] -fn panic(_info: &PanicInfo) -> ! { - loop {} -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 7111536..0000000 --- a/src/main.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![cfg_attr(target_os = "none", no_std)] -#![cfg_attr(target_os = "none", no_main)] - -#[cfg(target_os = "none")] -mod baremetal; - -#[cfg(not(target_os = "none"))] -fn main() { - println!("Hello, world!"); -} - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 2772ccb..8619957 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -1,4 +1,332 @@ # cargo-vet audits file -[audits] +[[audits.annotate-snippets]] +who = "Jerry Xie " +criteria = "safe-to-run" +version = "0.11.5" +notes = "Full audit: no unsafe, no build.rs/proc-macros, no fs/net/process/env access; pure in-memory snippet rendering with anstyle/unicode-width/memchr. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.anstream]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.0.0" +notes = "Full audit: ANSI stream adapters; no build script (build=false)/proc-macros, no fs/net/process/env; sole unsafe from_utf8_unchecked is sound (str-only callers, UTF-8-preserving parser, debug-checked). Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.anstyle]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.0.14" +notes = "Full audit: no_std ANSI styling types; no build script/proc-macros, no fs/net/process/env/FFI, zero runtime deps; single unsafe from_utf8_unchecked is sound (buffer only ever holds ASCII digits and valid static str). Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.anstyle-parse]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.0.0" +notes = "Full audit: no_std in-memory ANSI/VT escape parser; no build script/proc-macros, no fs/net/process/env. Three sound unsafe blocks: u8->State/Action transmutes guarded by 4-bit mask over repr(u8) enums with exactly 16 variants, and MaybeUninit osc-slice array pattern exposing only initialized prefix. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.anstyle-query]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.1.5" +notes = "Full audit: single sound unsafe block wrapping Windows GetConsoleMode/SetConsoleMode FFI (handles from std stdio, null-checked); no build script/proc-macros; only read-only std::env terminal-capability queries. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.anstyle-wincon]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "3.0.11" +notes = "Full audit: Windows console color styling. Two unsafe FFI blocks (GetConsoleScreenBufferInfo/SetConsoleTextAttribute) sound: null-checked handles from live borrows, zeroed POD struct, checked returns; no build script, no proc-macros, no fs/net/process/env beyond windows-sys console FFI. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.anyhow]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.102 -> 1.0.103" +notes = "Delta 1.0.102->1.0.103: refactors downcast unsafe code to use ptr::addr_of! field projection + NonNull/Ref::from_raw (soundness hardening, enables downcast_mut); no new deps, no new ambient capabilities, build.rs unchanged. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.backtrace]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "0.3.75 -> 0.3.76" +notes = "Delta 0.3.75->0.3.76: dep bumps (cpp_demangle 0.5, object 0.37, addr2line 0.25, ruzstd 0.8, windows-targets->windows-link), no build script/proc-macros, no new fs/net/process/env. All unsafe changes are reductions/soundness fixes (safe static Mutex, object::pod parsing replaces transmute, MaybeUninit padding fix). Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.bstr]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "1.12.1 -> 1.12.3" +notes = "Delta 1.12.1->1.12.3: MSRV 1.73->1.65, optional serde->serde_core dep swap, doc/must_use attrs, and a safe ASCII-fast-path Chars::count() override; no new unsafe, no build script/proc-macros, no fs/net/process/env. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.camino]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "1.2.3 -> 1.2.4" +notes = "Delta 1.2.3->1.2.4: adds Box<->Box conversions + FromBoxedPathError; new unsafe Box::from_raw transmutes sound via repr(transparent), UTF-8 validated before Path->Utf8Path reinterpret; no build script/proc-macros, no new fs/net/process/env. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.cargo-platform]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "0.1.3 -> 0.3.1" +notes = "Delta 0.1.3->0.3.1: pure cfg() parser/matcher changes (raw idents, true/false literals, keyword-deprecation warnings, edition 2024). No unsafe, no build.rs (build=false), no proc-macros, no fs/net/process/env in lib. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.cargo_metadata]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "0.15.3 -> 0.23.1" +notes = "Delta 0.15.3->0.23.1: no unsafe, no build script, no proc macros; only expected std::process/std::env use to invoke 'cargo metadata'. New API (env_remove, newtype/enum wrappers) consistent with advertised purpose. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.clap]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "4.5.20 -> 4.6.1" +notes = "Delta 4.5.20->4.6.1 is docs/examples/tutorials plus a new _concepts doc module and docsrs attr swap; forbid(unsafe_code) retained, no build.rs/proc-macros/fs/net/process additions. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.clap_builder]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "4.5.18 -> 4.5.20" +notes = "Delta 4.5.18->4.5.20: doc/typo fixes, internal AppTag->AppExt rename, unstable-ext CommandExt API, dep bumps; no unsafe, build, or ambient capabilities. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.clap_builder]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "4.5.20 -> 4.6.0" +notes = "Delta 4.5.20->4.6.0: doc reformatting, edition 2024/MSRV 1.85 + dep bumps, new default_values_if API, parser/validator refactor; forbid(unsafe_code) retained, no build script, no new fs/net/process. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.clap_derive]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "4.6.1" +notes = "Full audit: clap Parser/Args/Subcommand/ValueEnum derive proc-macro; forbid(unsafe_code), no build.rs, no fs/net/process; only env reads are hard-coded CARGO_PKG_* metadata at expansion; generated code is plain clap builder calls. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.clap_lex]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.1.0" +notes = "Full audit: minimal in-memory arg lexer; no build script/proc-macros, no fs/net/process/env; unsafe limited to sound OsStr as_encoded_bytes/from_encoded_bytes_unchecked at UTF-8 boundaries with valid callers. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.color-eyre]] +who = "Jerry Xie " +criteria = "safe-to-run" +version = "0.6.5" +notes = "Full audit: no unsafe, no build.rs/proc-macros, no net/process; only read-only fs access (source snippets) and read-only RUST_BACKTRACE/SPANTRACE env reads, consistent with advertised colorful error reporting. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.color-spantrace]] +who = "Jerry Xie " +criteria = "safe-to-run" +version = "0.3.0" +notes = "Full audit: no unsafe; build.rs only runs rustc --version for toolchain cfg; runtime fs reads are read-only source-snippet display gated by RUST_BACKTRACE; behavior matches advertised SpanTrace pretty-printer. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.colorchoice]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.0.5" +notes = "Full audit: single-file no_std crate, no unsafe, no build script/proc-macros, zero deps, no fs/net/process/env; only core atomic state for global ColorChoice override. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.colored]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "2.0.4 -> 3.1.1" +notes = "Delta 2.0.4->3.1.1: additive style trait impls + tests; swapped lazy_static/is_terminal for std LazyLock/IsTerminal; only unsafe is unchanged Windows console FFI; reads only color env vars; no build.rs/proc-macros/net/fs. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.comma]] +who = "Jerry Xie " +criteria = "safe-to-run" +version = "1.0.0" +notes = "Full audit: pure-Rust shell-style string tokenizer; no unsafe, no build.rs/proc-macros, no deps, only std iter/str imports, no fs/net/process/env access. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.crossbeam-utils]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "0.8.15 -> 0.8.14" +notes = "Delta (downgrade): only CHANGELOG/version, build-script target-list data, a clippy lint attr, a park_timeout overflow-handling tweak, doc comment, and a test differ; no unsafe code, build-script logic, or fs/net/process/env changes. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.eyre]] +who = "Jerry Xie " +criteria = "safe-to-run" +version = "0.6.12" +notes = "Full audit: no proc-macros; build.rs only probes rustc features (writes OUT_DIR, runs rustc); runtime has no fs/net/process/env; unsafe is sound anyhow-style repr(C) type-erasure vtable and TypeId-guarded Any downcasts. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.indenter]] +who = "Jerry Xie " +criteria = "safe-to-run" +version = "0.3.4" +notes = "Full audit: pure-safe-Rust fmt::Write indentation adapter; no unsafe, no build.rs/proc-macro, no deps, no fs/net/process/env access. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.is_terminal_polyfill]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.70.2" +notes = "Full audit: trivial 58-line polyfill forwarding to std::io::IsTerminal; no unsafe, no build script (build=false), no proc-macros, no deps, no fs/net/process/env access. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.itertools]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.14.0 -> 0.15.0" +notes = "Delta 0.14.0->0.15.0 adds array-combination & with_hasher adaptors, AllEqualValueError, strip_prefix, and formatting modernization; no unsafe, no build.rs/proc-macros, no fs/net/process/env or new runtime deps; pure iterator-adaptor behavior. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.lazy-regex]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "3.6.0" +notes = "Full audit: no unsafe, no build.rs, no fs/net/process/env. Macros validate patterns at compile time via regex::Regex::new and emit safe once_cell Lazy RegexBuilder code; remove.rs uses only safe slicing at regex match boundaries. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.lazy-regex-proc_macros]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "3.6.0" +notes = "Full audit: no unsafe, no build script, no fs/net/process/env. Proc macro validates regex literals in-memory (regex::Regex::new) and emits predictable lazy-static regex builder code; user exprs passed through verbatim, no code injection. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.levenshtein]] +who = "Jerry Xie " +criteria = "safe-to-run" +version = "1.0.5" +notes = "Full audit: single pure function computing Levenshtein edit distance; no unsafe, no build script, no deps, no fs/net/process/env access. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.libc]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "0.2.177 -> 0.2.186" +notes = "Delta 0.2.177->0.2.186: bindings-only (constants, structs, externs) plus new src/new module tree; build.rs only reads env vars and emits cfgs; macros generate plain repr(C) bindings; no new deps, fs, net, or process access. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.log]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.4.32 -> 0.4.33" +notes = "Delta 0.4.32->0.4.33: key-comparison bugfix replacing derived Eq/Ord/Hash on MaybeStaticStr with content-based impls; no unsafe, no build/proc-macro, no new powerful imports. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.memchr]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "2.8.1 -> 2.8.2" +notes = "Delta 2.8.1->2.8.2: fixes pointer-arithmetic UB in packedpair search (out-of-bounds end.sub replaced with sound end.distance comparison), adds Miri regression test, minor into_owned move-vs-clone. No new deps/build scripts/imports. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.object]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "0.36.7 -> 0.36.5" +notes = "Delta 0.36.7->0.36.5: removes Relr/M68k support, no_std core::error impl and a benign rustc-version-probe build.rs; only unsafe change is removing Relr POD impls (sound repr(C) pattern); no fs/net/process in lib. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.once_cell_polyfill]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.70.2" +notes = "Full audit: ~54 LOC newtype wrapper forwarding to std::sync::OnceLock; no unsafe, no build.rs (build=false), no proc-macros, no deps, no fs/net/process/env/FFI. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.owo-colors]] +who = "Jerry Xie " +criteria = "safe-to-run" +version = "4.3.0" +notes = "Full audit: forbid(unsafe_code), no unsafe/fs/net; build.rs only runs rustc --version for cfg detection; color detection delegated to optional supports-color dep. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.prettydiff]] +who = "Jerry Xie " +criteria = "safe-to-run" +version = "0.9.0" +notes = "Full audit: no unsafe, no build.rs/proc-macros, no fs/net/process/env access; pure LCS diff/pretty-print logic using std fmt/cmp + owo-colors/prettytable. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.quote]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.45 -> 1.0.46" +notes = "Delta 1.0.45->1.0.46: quote_spanned! macro refactor (single span eval) + import reordering; no unsafe, no build.rs/dep changes, no new ambient capabilities. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.regex]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.12.3 -> 1.12.4" +notes = "Delta 1.12.3->1.12.4 is metadata-only (CHANGELOG, version bump, added benches/ to include, regex-syntax dep 0.8.5->0.8.11); no source, unsafe, build, or capability changes. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.rustc-demangle]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "0.1.25 -> 0.1.26" +notes = "Delta 0.1.25->0.1.26 adds v0 pattern-type demangling (print_pat, W tag) plus CHANGELOG/CI files; no unsafe, no build script, no fs/net/process/env access; behavior matches symbol-demangling purpose. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.rustc-demangle]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "0.1.26 -> 0.1.27" +notes = "Delta 0.1.26->0.1.27: adds v0 demangling of trait-object assoc-const bindings plus a test; no unsafe, no build script/proc-macros, no fs/net/process/env. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.rustfix]] +who = "Jerry Xie " +criteria = "safe-to-run" +version = "0.8.7" +notes = "Full audit: no unsafe, no build.rs/proc-macros, no fs/net/process/env access; purely in-memory string/byte replacement of rustc JSON suggestions, matches advertised purpose (rust-lang authored). Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.serde_json]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.149 -> 1.0.150" +notes = "Delta 1.0.149->1.0.150: no unsafe/build/proc-macro/ambient-capability changes; hardens enum variant key parsing (issue979) plus cosmetic refactors and version bumps. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.serde_spanned]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.4 -> 1.1.1" +notes = "Delta 1.0.4->1.1.1 is edition 2024 migration: manifest/edition/rust-version bumps, serde_core 1.0.225->1.0.228, one cosmetic import reorder; no logic, unsafe, build script, or new capabilities. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.spanned]] +who = "Jerry Xie " +criteria = "safe-to-run" +version = "0.4.1" +notes = "Full audit: one sound unsafe offset_from over same-allocation bstr line subslices; no build script or proc macros; only read-only std::fs file reads consistent with span-tracking purpose; no net/process/env. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.syn]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "2.0.117 -> 2.0.118" +notes = "Delta 2.0.117->2.0.118: no unsafe, no build.rs, no new ambient capabilities; src changes are doc comments, version bump, and a semantically-equivalent bool-literal parse refactor plus lint-allow additions; remaining changes tests/benches only. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.thiserror]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "1.0.68 -> 1.0.69" +notes = "Delta 1.0.68->1.0.69: version bump only - html_root_url + Cargo.toml version/dep pin + one UI test; no unsafe, no build.rs change, no new fs/net/process/env. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.thiserror-impl]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "1.0.68 -> 1.0.69" +notes = "Delta 1.0.68->1.0.69: version bump plus one syn parser-progress fix in fmt.rs (consume unmatched TokenTree); no unsafe, no build script, no fs/net/process/env. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.toml]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.8.23 -> 1.1.2+spec-1.1.0" +notes = "Delta 0.8.23->1.1.2+spec-1.1.0: major rearchitecture to in-house toml_parser/toml_writer + no_std; forbid(unsafe_code), no build script/proc-macros, no fs/net/process/env; pure TOML parse/serialize. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.toml_datetime]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.7.5+spec-1.1.0 -> 1.1.1+spec-1.1.0" +notes = "Delta: pure logic changes making Time::second/nanosecond optional plus edition/MSRV/dep-version bumps; no unsafe, no build script, no proc macros, no new powerful imports; bounds checks preserved. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.toml_parser]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.1.2+spec-1.1.0" +notes = "Full audit: no_std TOML lexer/parser; no build script/proc-macros, no fs/net/process/env. forbid(unsafe_code) by default; all unsafe gated behind non-default unsafe feature with safe fallbacks and sound UTF-8-boundary get_unchecked/next_slice_unchecked. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.toml_writer]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.6+spec-1.1.0 -> 1.1.1+spec-1.1.0" +notes = "Delta 1.0.6->1.1.1: metadata-only (edition 2024, MSRV 1.85, dev-dep + clippy-lint bumps); no source, unsafe, build-script, or runtime-dependency changes. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.tracing-error]] +who = "Jerry Xie " +criteria = "safe-to-run" +version = "0.2.1" +notes = "Full audit: no build.rs/proc-macros, no fs/net/process/env access; unsafe limited to sound anyhow-style type-erasure vtable and standard Layer::downcast_raw. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.ui_test]] +who = "Jerry Xie " +criteria = "safe-to-run" +version = "0.30.7" +notes = "Full audit: no build.rs/proc-macros, no network access; only unsafe is inside test-program string fixtures (not crate code). process/fs/env usage limited to invoking rustc/cargo and the harness's own test binaries and standard CARGO/RUSTC/GITHUB env vars - consistent with a UI test harness. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" + +[[audits.winnow]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.7.15 -> 1.0.3" +notes = "Delta 0.7.15->1.0.3: 1.0 API cleanup. No build script, no proc macros, no fs/net/process/env. Net unsafe reduction: two from_utf8_unchecked replaced with safe checked from_utf8. Behavior matches parser-combinator purpose. Assisted-by: copilot-cli:claude-opus-4.8 cargo-vet" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 55618c2..8fa8409 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -7,6 +7,9 @@ version = "0.10" [imports.OpenDevicePartnership] url = "https://raw.githubusercontent.com/OpenDevicePartnership/rust-crate-audits/main/audits.toml" +[imports.bytecode-alliance] +url = "https://raw.githubusercontent.com/bytecodealliance/wasmtime/main/supply-chain/audits.toml" + [imports.google] url = "https://raw.githubusercontent.com/google/rust-crate-audits/main/audits.toml" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 219dba4..9795c59 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -1,8 +1,1560 @@ # cargo-vet imports lock -[audits.OpenDevicePartnership.audits] +[[publisher.unicode-width]] +version = "0.2.2" +when = "2025-10-06" +user-id = 1139 +user-login = "Manishearth" +user-name = "Manish Goregaokar" -[audits.google.audits] +[[audits.OpenDevicePartnership.audits.aho-corasick]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.1.4" +notes = "Multi-pattern string matching by BurntSushi. SIMD/DFA optimized. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/odp-utilities/refs/heads/main/supply-chain/audits.toml" -[audits.mozilla.audits] +[[audits.OpenDevicePartnership.audits.anyhow]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.0.102" +notes = "Full audit: vtable-based type-erased Error. All unsafe sound. deny(unsafe_op_in_unsafe_fn). build.rs does rustc feature probing only. Assisted-by: GitHub Copilot:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/pcal6416a/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.backtrace]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "0.3.75" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.cfg-if]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.0 -> 1.0.3" +notes = "Delta 1.0.0->1.0.3: formatting/readability refactor of macro identifiers, removed compiler_builtins dep, updated CI. No unsafe, no build script, no imports. Pure macro_rules crate. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.cfg-if]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.3 -> 1.0.4" +notes = "Delta 1.0.3->1.0.4: macro refactored from two top-level rules to one using optional else syntax, fragment specifiers changed from :meta to :tt to support cfg(true)/cfg(false), internal helper renamed from @__identity to @__temp_group with added comment. MSRV 1.32 declared. No unsafe, no build script, no proc macros, no imports. Pure no_std macro_rules crate. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.either]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.15.0 -> 1.16.0" +notes = "Delta audit: No unsafe code changes (existing Pin unsafe unchanged, only macro rename). No build script, no proc macros, no powerful imports. Adds safe combinators and map_both! macro. No new deps. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.gimli]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "0.31.1" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.hashbrown]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.13.2 -> 0.14.5" +notes = "SwissTable hash map delta. Major changes: allocator refactored out of RawTableInner, new InsertSlot type, NEON SIMD for aarch64, allocator-api2 integration, new HashTable API, strict provenance. Extensive unsafe reviewed for soundness. No build script, no proc macros, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.hashbrown]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.16.0 -> 0.17.0" +notes = "Delta 0.16.0->0.17.0. Edition 2024, MSRV 1.85. Alloc module extracted from raw/. InsertSlot replaced with usize. Renames: buckets->num_buckets, get_many_mut->get_disjoint_mut. Serde->serde_core. Unsafe wrapped in blocks for unsafe_op_in_unsafe_fn - mechanical only. Over-sized alloc utilization. Bug fix 710: rehash panic guard. Fix 692 fallible_with_capacity. New bucket-index APIs. TryReserveError: Error+Display. No build script, no proc macros, no new powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.hashbrown]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.17.0 -> 0.17.1" +notes = "Patch release adding HashMap::rustc_try_insert (safe code using existing RawTable find/insert_entry). LSX SIMD optimization in control/group/lsx.rs replaces two intrinsics with immediate-operand equivalents (functionally equivalent, existing unsafe block). README formatting only. No build script, no proc macros, no new powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.heck]] +who = "Matteo Tullo " +criteria = "safe-to-deploy" +version = "0.4.1" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.indexmap]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "2.11.4 -> 2.14.0" +notes = "Delta 2.11.4->2.14.0: Large internal refactor - IndexMapCore renamed to Core and moved to inner module, RefMut indirection eliminated. Entry types restructured across inner/map modules. Edition 2024, MSRV 1.85, hashbrown bumped to 0.17. No new unsafe code; existing unsafe in extract.rs (#![allow(unsafe_code)] for vec drain) and slice.rs (DST casts with repr(transparent)) unchanged and sound. New MaybeUninit usage in IntoKeys/IntoValues Clone impls is safe (uninit values created but never read). No build script, no proc macros, no filesystem/network/process access. New APIs: pop_if, split_at_checked, split_at_mut_checked, Clone for IntoKeys/IntoValues. Many methods made const. Error types use core::error::Error. Uses hash_one() and get_disjoint_mut (std API renames). Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.itertools]] +who = "Robert Zieba " +criteria = "safe-to-deploy" +version = "0.10.5" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.itoa]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.15 -> 1.0.18" +notes = "Delta: internal algorithm refactor replacing raw ptr arithmetic with MaybeUninit::write. Unsafe reduced. No build script, no_std. Assisted-by: GitHub Copilot:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/pcal6416a/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.libc]] +who = "Felipe Balbi " +criteria = "safe-to-run" +delta = "0.2.170 -> 0.2.177" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-mcu/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.log]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.4.29 -> 0.4.32" +notes = "Delta 0.4.29->0.4.32: No new unsafe. Key type now distinguishes static vs borrowed strings; added Display-based ToValue for std::net types (no I/O); MSRV bump to 1.71; typo fixes. Assisted-by: GitHub Copilot:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embassy-npcx/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.memchr]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "2.7.6" +notes = "Optimized byte searching by BurntSushi. 332 unsafe for SIMD. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/odp-utilities/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.memchr]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "2.7.6 -> 2.8.1" +notes = "Delta 2.7.6->2.8.1: clippy cleanups, owned finder builder APIs (alloc-gated), big-endian aarch64 NEON movemask. New unsafe movemask variant is sound. No new dependencies or ambient capabilities. Assisted-by: GitHub Copilot:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embassy-npcx/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.nu-ansi-term]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.46.0 -> 0.50.1" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.object]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "0.36.7" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.once_cell]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.20.1" +notes = "Single-assignment cells and lazy values. All unsafe reviewed: UnsafeCell access, Send/Sync impls, atomic waiter queue, strict provenance polyfill - all sound with correct bounds. No build script, no proc macros, no powerful imports beyond std::thread/atomic. Assisted-by: copilot-chat:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.once_cell]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.21.3 -> 1.21.4" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/bq25723/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.pin-project-lite]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.2.16 -> 0.2.17" +notes = "Delta audit: no unsafe code, no build script, no proc macros, no powerful imports. Changes are cosmetic only: updated clippy lint suppressions, doc comment formatting, test stderr updates for newer compiler messages. No functional changes to macro logic. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.regex]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.11.1" +notes = "Regex by BurntSushi. 1 unsafe for Searcher impl. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/odp-utilities/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.regex-automata]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.4.9" +notes = "Regex engine by BurntSushi. 58 unsafe for DFA perf. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/odp-utilities/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.regex-syntax]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.8.5 -> 0.8.10" +notes = "Delta audit: crate uses #![forbid(unsafe_code)], no build.rs, no proc macros, no powerful imports. Changes are cosmetic: inline format args migration, typo fixes in comments/docs, switched docsrs cfg to docsrs_regex, added Cargo include list, deleted shell test script, minor test additions for negated unicode property classes. No security-relevant changes. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.regex-syntax]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.8.10 -> 0.8.11" +notes = "Delta: single optimization to IntervalSet::push() using binary search + incremental merge. No unsafe, no build script, no proc macros. Assisted-by: GitHub Copilot:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embassy-npcx/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.rustc-demangle]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "0.1.24 -> 0.1.25" +notes = "Delta: version bump, removed compiler_builtins. No code changes. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/is31fl3743b/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.semver]] +who = "Billy Price " +criteria = "safe-to-run" +delta = "1.0.26 -> 1.0.27" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.semver]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "1.0.27 -> 1.0.28" +notes = "Delta audit: edition bump 2018->2021, MSRV 1.61->1.68, let-else modernization, safer pointer ops in identifier.rs (ptr::addr_of!/cast replacing raw as casts). Benchmark migrated to criterion. No new unsafe, no new capabilities. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.serde]] +who = "Robert Zieba " +criteria = "safe-to-deploy" +version = "1.0.228" +notes = "Changes are mostly a reorganization of the internal module structure" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.serde_core]] +who = "Robert Zieba " +criteria = "safe-to-deploy" +version = "1.0.226" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.serde_derive]] +who = "Robert Zieba " +criteria = "safe-to-deploy" +version = "1.0.228" +notes = "Diff is clean-up in proc macros" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.serde_json]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.0.149" +notes = "Full audit: 12 unsafe blocks all sound (repr(transparent) transmutes, from_utf8_unchecked on validated data, WTF-8 encoding). build.rs reads cfg env vars only. Assisted-by: GitHub Copilot:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/pcal6416a/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.syn]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "2.0.110" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.syn]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "2.0.110 -> 2.0.117" +notes = "Delta: no_std migration, receiver parsing fix. No new unsafe. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/bq40z50/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.thiserror]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "2.0.17" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.thiserror]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "2.0.12 -> 1.0.68" +notes = "Cross-major delta v2-v1. v1 is simplification. No unsafe. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-regulator/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.thiserror]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "2.0.17 -> 2.0.12" +notes = "Downgrade delta: build.rs generates versioned __private module. No new unsafe. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-fans/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.thiserror]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "2.0.17 -> 2.0.18" +notes = "Delta: build.rs match-to-let-else refactor. No new unsafe. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/bq40z50/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.thiserror-impl]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "2.0.17" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.thiserror-impl]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "2.0.12 -> 1.0.68" +notes = "Cross-major delta v2-v1. v1 simpler. No unsafe. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-regulator/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.thiserror-impl]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "2.0.17 -> 2.0.12" +notes = "Downgrade delta: __private module refactor. No unsafe. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-fans/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.thiserror-impl]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "2.0.17 -> 2.0.18" +notes = "Delta: conditional clippy lint allows. No unsafe. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/bq40z50/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.thread_local]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.1.4 -> 1.1.9" +notes = "No build script, no FS/net/process capability expansion; unsafe refactor to lock-free insertion and nightly TLS path appears sound on review. Assisted-by: copilot-cli:GPT-5.3-Codex" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.toml]] +who = "jerrysxie " +criteria = "safe-to-deploy" +version = "0.8.22" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.toml]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.8.22 -> 0.8.23" +notes = "Delta: adds TupleVariant/StructVariant serialization support. All new code is thin wrappers delegating to toml_edit. No unsafe (forbid(unsafe_code)), no build script, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.tracing]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.1.41 -> 0.1.44" +notes = "Delta audit: one new unsafe (FieldName::as_str using str::from_utf8_unchecked) — sound because private field is only constructed from const fn new() which processes &str input by removing r#, preserving UTF-8 validity; compile-time assert guards N. No build script (build=false). No proc macros. No powerful imports (fs/net/process). Key changes: valueset! macro split into valueset!/valueset_all! to fix record_all panic (#3432); FieldName added for raw identifier support; stdlib.rs removed in favor of #![no_std] + extern crate std; lifetime elision cleanup; doc fixes; MSRV 1.63→1.65. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.tracing-core]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.1.33 -> 0.1.34" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.tracing-core]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.1.34 -> 0.1.36" +notes = "Delta audit: major refactor replacing crate::stdlib shim with direct alloc::/core::/std:: imports and unconditional #![no_std]. Removed stdlib.rs, added sync.rs (extracted Mutex wrapper). New Values::All enum variant for efficient ValueSet construction (value_set_all, #[doc(hidden)]). Two downcast_raw calls wrapped in unsafe blocks (sound - type ID checked first). No new unsafe patterns, no build script, no proc macros, no filesystem/network access. No new dependencies. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.tracing-log]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.1.3 -> 0.2.0" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.tracing-subscriber]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.3.20 -> 0.3.23" +notes = "Delta audit of tracing-subscriber 0.3.20->0.3.23. Changes: (1) no_std support refactoring - imports moved from std to alloc/core, unconditional #![no_std] with extern crate std gated on feature; (2) ANSI sanitization made configurable via with_ansi_sanitization(bool), defaulting to true (secure default preserved); (3) Escape renamed to EscapeGuard with conditional sanitize flag; (4) downcast_raw calls wrapped in unsafe blocks to match trait signature change in tracing-core 0.1.35 - all call sites forward to existing safe implementations; (5) Registry enter/exit simplified - removes clone_span on enter and try_close on exit for performance; (6) Layered now propagates on_register_dispatch to inner+layer; (7) Doc fixes and test improvements. No new filesystem/network/process access. No build script. No proc macros. Unsafe changes are mechanical wrapping of existing downcast_raw calls. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.unicode-ident]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.18 -> 1.0.24" +notes = "Delta: Unicode 16->17 table data update only. No unsafe, no build.rs, no new deps. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.valuable]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.1.1" +notes = "No unsafe code; build.rs only sets target atomic cfg via env; no fs/net/process capability use observed; behavior matches value-inspection purpose. Assisted-by: copilot-cli:GPT-5.3-Codex" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.windows-link]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.2.0 -> 0.2.1" +notes = "Metadata-only delta: version bump and readme field casing. No source code changes. Assisted-by: GitHub Copilot:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embassy-npcx/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.windows-sys]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.59.0" +notes = "Auto-generated Windows API FFI by Microsoft. No unsafe blocks. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/odp-utilities/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.windows-sys]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.59.0 -> 0.61.2" +notes = "Auto-generated Windows API FFI bindings. No unsafe blocks, no build script, no proc macros, no_std. Delta switches link dep from windows-targets to windows-link. All changes are mechanical API binding additions/removals. Assisted-by: GitHub Copilot:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embassy-npcx/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.winnow]] +who = "jerrysxie " +criteria = "safe-to-deploy" +version = "0.7.10" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.winnow]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.7.10 -> 0.7.13" +notes = "Delta adds Accumulate impls (Cow str, String, VecDeque), fixes macro PartialEq/PartialOrd, optimizes str::next_token, adds tests, improves docs. No unsafe changes, no build script, no new powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.winnow]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.7.13 -> 0.7.15" +notes = "Delta 0.7.13->0.7.15: Removes internal lib::std facade (mechanical s/crate::lib::std/core|alloc|std/), adds expression() Pratt parser (~569 lines) and unordered_seq! macro (~592 lines, replaces deprecated permutation). No unsafe changes (existing 4 sound blocks unchanged). No build script, no proc macros, no new powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.zmij]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.20 -> 1.0.21" +notes = "Delta: refactors pow10 table to const fn. Unsafe pointer arithmetic bounds verified sound. No API changes. no_std. Assisted-by: GitHub Copilot:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/pcal6416a/refs/heads/main/supply-chain/audits.toml" + +[[audits.bytecode-alliance.audits.addr2line]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.19.0 -> 0.20.0" +notes = "This version brings support for split-dwarf which while it uses the filesystem is always done at the behest of the caller, so everything is as expected for this update." + +[[audits.bytecode-alliance.audits.addr2line]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.20.0 -> 0.21.0" +notes = "This version bump updated some dependencies and optimized some internals. All looks good." + +[[audits.bytecode-alliance.audits.addr2line]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.21.0 -> 0.22.0" + +[[audits.bytecode-alliance.audits.addr2line]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.22.0 -> 0.24.1" +notes = "Lots of internal code refactorings and code movement. Nothing out of place however." + +[[audits.bytecode-alliance.audits.addr2line]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.24.1 -> 0.25.0" +notes = "All minor changes, even a net reduction of `unsafe`." + +[[audits.bytecode-alliance.audits.addr2line]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.25.0 -> 0.25.1" +notes = "Minor updates, looks like a minor bug fix, nothing awry." + +[[audits.bytecode-alliance.audits.adler2]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +version = "2.0.0" +notes = "Fork of the original `adler` crate, zero unsfae code, works in `no_std`, does what it says on th tin." + +[[audits.bytecode-alliance.audits.camino]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "1.1.4" + +[[audits.bytecode-alliance.audits.cargo-platform]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "0.1.2" +notes = "no build, no ambient capabilities, no unsafe" + +[[audits.bytecode-alliance.audits.cargo_metadata]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "0.15.3" +notes = "no build, no unsafe, inputs to cargo command are reasonably sanitized" + +[[audits.bytecode-alliance.audits.cfg-if]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +version = "1.0.0" +notes = "I am the author of this crate." + +[[audits.bytecode-alliance.audits.gimli]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.31.1 -> 0.32.0" +notes = "Ever more DWARF to parse, but also no new `unsafe` and everything looks like gimli." + +[[audits.bytecode-alliance.audits.gimli]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.32.0 -> 0.32.3" +notes = "Ever more dwarf, it never ends! (nothing out of the ordinary)" + +[[audits.bytecode-alliance.audits.hashbrown]] +who = "Chris Fallin " +criteria = "safe-to-deploy" +delta = "0.12.3 -> 0.13.1" +notes = "The diff looks plausible. Much of it is low-level memory-layout code and I can't be 100% certain without a deeper dive into the implementation logic, but nothing looks actively malicious." + +[[audits.bytecode-alliance.audits.hashbrown]] +who = "Trevor Elliott " +criteria = "safe-to-deploy" +delta = "0.13.1 -> 0.13.2" +notes = "I read through the diff between v0.13.1 and v0.13.2, and verified that the changes made matched up with the changelog entries. There were very few changes between these two releases, and it was easy to verify what they did." + +[[audits.bytecode-alliance.audits.hashbrown]] +who = "Chris Fallin " +criteria = "safe-to-deploy" +delta = "0.14.5 -> 0.15.2" + +[[audits.bytecode-alliance.audits.heck]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.4.1 -> 0.5.0" +notes = "Minor changes for a `no_std` upgrade but otherwise everything looks as expected." + +[[audits.bytecode-alliance.audits.itertools]] +who = "Nick Fitzgerald " +criteria = "safe-to-deploy" +delta = "0.10.5 -> 0.12.1" +notes = """ +Minimal `unsafe` usage. Few blocks that existed looked reasonable. Does what it +says on the tin: lots of iterators. +""" + +[[audits.bytecode-alliance.audits.itertools]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.12.1 -> 0.14.0" +notes = """ +Lots of new iterators and shuffling some things around. Some new unsafe code but +it's well-documented and well-tested. Nothing suspicious. +""" + +[[audits.bytecode-alliance.audits.itoa]] +who = "Dan Gohman " +criteria = "safe-to-deploy" +delta = "1.0.11 -> 1.0.14" + +[[audits.bytecode-alliance.audits.libc]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.2.146 -> 0.2.147" +notes = "Only new type definitions and updating others for some platforms, no major changes" + +[[audits.bytecode-alliance.audits.miniz_oxide]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +version = "0.7.1" +notes = """ +This crate is a Rust implementation of zlib compression/decompression and has +been used by default by the Rust standard library for quite some time. It's also +a default dependency of the popular `backtrace` crate for decompressing debug +information. This crate forbids unsafe code and does not otherwise access system +resources. It's originally a port of the `miniz.c` library as well, and given +its own longevity should be relatively hardened against some of the more common +compression-related issues. +""" + +[[audits.bytecode-alliance.audits.miniz_oxide]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.7.1 -> 0.8.0" +notes = "Minor updates, using new Rust features like `const`, no major changes." + +[[audits.bytecode-alliance.audits.miniz_oxide]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.8.0 -> 0.8.5" +notes = """ +Lots of small updates here and there, for example around modernizing Rust +idioms. No new `unsafe` code and everything looks like what you'd expect a +compression library to be doing. +""" + +[[audits.bytecode-alliance.audits.miniz_oxide]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.8.5 -> 0.8.9" +notes = "No new unsafe code, just refactorings." + +[[audits.bytecode-alliance.audits.nu-ansi-term]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "0.46.0" +notes = "one use of unsafe to call windows specific api to get console handle." + +[[audits.bytecode-alliance.audits.nu-ansi-term]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.50.1 -> 0.50.3" +notes = "CI changes, Rust changes, nothing out of the ordinary." + +[[audits.bytecode-alliance.audits.object]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.36.5 -> 0.37.1" +notes = "New object file formats, new formatting, new other minor changes, no new `unsafe`." + +[[audits.bytecode-alliance.audits.object]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.37.1 -> 0.37.3" +notes = "Lots of new support for new object features, no new unsafe or anything suspicious." + +[[audits.bytecode-alliance.audits.pin-project-lite]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.2.13 -> 0.2.14" +notes = "No substantive changes in this update" + +[[audits.bytecode-alliance.audits.rustc-demangle]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +version = "0.1.21" +notes = "I am the author of this crate." + +[[audits.bytecode-alliance.audits.rustc-demangle]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.1.21 -> 0.1.24" + +[[audits.bytecode-alliance.audits.semver]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "1.0.17" +notes = "plenty of unsafe pointer and vec tricks, but in well-structured and commented code that appears to be correct" + +[[audits.bytecode-alliance.audits.sharded-slab]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "0.1.4" +notes = "I always really enjoy reading eliza's code, she left perfect comments at every use of unsafe." + +[[audits.bytecode-alliance.audits.smallvec]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "1.13.2 -> 1.14.0" +notes = "Minor new feature, nothing out of the ordinary." + +[[audits.bytecode-alliance.audits.thread_local]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "1.1.4" +notes = "uses unsafe to implement thread local storage of objects" + +[[audits.bytecode-alliance.audits.tracing-log]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +version = "0.1.3" +notes = """ +This is a standard adapter between the `log` ecosystem and the `tracing` +ecosystem. There's one `unsafe` block in this crate and it's well-scoped. +""" + +[[audits.bytecode-alliance.audits.tracing-subscriber]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "0.3.17" + +[[audits.google.audits.addr2line]] +who = "George Burgess IV " +criteria = "safe-to-run" +version = "0.19.0" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.bstr]] +who = "danakj " +criteria = "safe-to-deploy" +version = "1.10.0" +notes = """ +WARNING: This certification is a result of a **partial** audit. The +`unicode` feature has **not** been audited. The unicode feature has +soundness that depends on the correctness of regex automata that are +shipped as binary blobs. They have not been reviewed here.Ability to +track partial audits is tracked in +https://github.com/mozilla/cargo-vet/issues/380. +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.bstr]] +who = "Adrian Taylor " +criteria = "safe-to-deploy" +delta = "1.10.0 -> 1.11.0" +notes = "Changes two unsafe blocks to use core::mem::align_of instead of core::mem::size_of which shouldn't differ on mainstream platforms." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.bstr]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +delta = "1.11.0 -> 1.11.1" +notes = "This release just excludes Unicode data files from being published to crates.io" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.bstr]] +who = "Dustin J. Mitchell " +criteria = "safe-to-deploy" +delta = "1.11.1 -> 1.11.3" +notes = "No unsafe changes" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.clap]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +version = "4.5.15" +notes = ''' +Grepped for `-i cipher`, `-i crypto`, `'\bfs\b'`, `'\bnet\b'`, `'\bunsafe\b'` +and there were no hits, except for `std::net::IpAddr` usage in +`examples/typed-derive.rs`. +''' +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.clap]] +who = "danakj " +criteria = "safe-to-deploy" +delta = "4.5.15 -> 4.5.17" +notes = "Minor code change and toml changes." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.clap]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +delta = "4.5.17 -> 4.5.18" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.clap]] +who = "danakj " +criteria = "safe-to-deploy" +delta = "4.5.18 -> 4.5.20" +notes = "Trivial changes" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.clap_builder]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +version = "4.5.15" +notes = ''' +Grepped for `-i cipher`, `-i crypto`, `'\bfs\b'`, `'\bnet\b'`, `'\bunsafe\b'` +and there were no hits. +''' +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.clap_builder]] +who = "danakj " +criteria = "safe-to-deploy" +delta = "4.5.15 -> 4.5.17" +notes = "No new unsafe, net, fs" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.clap_builder]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +delta = "4.5.17 -> 4.5.18" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.colored]] +who = "George Burgess IV " +criteria = "safe-to-run" +version = "2.0.4" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.crossbeam-channel]] +who = "George Burgess IV " +criteria = "safe-to-run" +version = "0.5.7" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.crossbeam-channel]] +who = "George Burgess IV " +criteria = "safe-to-run" +delta = "0.5.7 -> 0.5.8" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.crossbeam-utils]] +who = "George Burgess IV " +criteria = "safe-to-run" +version = "0.8.15" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.either]] +who = "Manish Goregaokar " +criteria = "safe-to-deploy" +version = "1.13.0" +notes = "Unsafe code pertaining to wrapping Pin APIs. Mostly passes invariants down." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.either]] +who = "Daniel Cheng " +criteria = "safe-to-deploy" +delta = "1.13.0 -> 1.14.0" +notes = """ +Inheriting ub-risk-1 from the baseline review of 1.13.0. While the delta has some diffs in unsafe code, they are either: +- migrating code to use helper macros +- migrating match patterns to take advantage of default bindings mode from RFC 2005 +Either way, the result is code that does exactly the same thing and does not change the risk of UB. + +See https://crrev.com/c/6323164 for more audit details. +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.either]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +delta = "1.14.0 -> 1.15.0" +notes = 'The delta in `lib.rs` only tweaks doc comments and `#[cfg(feature = "std")]`.' +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.equivalent]] +who = "George Burgess IV " +criteria = "safe-to-deploy" +version = "1.0.1" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.equivalent]] +who = "Jonathan Hao " +criteria = "safe-to-deploy" +delta = "1.0.1 -> 1.0.2" +notes = "No changes to any .rs files or Rust code." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.indexmap]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +version = "2.7.1" +notes = ''' +Grepped for `-i cipher`, `-i crypto`, `'\bfs\b'`, `'\bnet\b'` +and there were no hits. + +There is a little bit of `unsafe` Rust code - the audit can be found at +https://chromium-review.googlesource.com/c/chromium/src/+/6187726/2 +''' +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.indexmap]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +delta = "2.7.1 -> 2.8.0" +notes = """ +No `unsafe` introduced or affected in: +* `indexmap_with_default!` and `indexset_with_default!` macros +* New `PartialEq` implementations +* `fn slice_eq` in `util.rs` +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.itoa]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +version = "1.0.10" +notes = ''' +I grepped for \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits. + +There are a few places where `unsafe` is used. Unsafe review notes can be found +in https://crrev.com/c/5350697. + +Version 1.0.1 of this crate has been added to Chromium in +https://crrev.com/c/3321896. +''' +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.itoa]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +delta = "1.0.10 -> 1.0.11" +notes = """ +Straightforward diff between 1.0.10 and 1.0.11 - only 3 commits: + +* Bumping up the version +* A touch up of comments +* And my own PR to make `unsafe` blocks more granular: + https://github.com/dtolnay/itoa/pull/42 +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.itoa]] +who = "Daniel Cheng " +criteria = "safe-to-deploy" +delta = "1.0.14 -> 1.0.15" +notes = "Only minor rustdoc changes." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.lazy_static]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +version = "1.4.0" +notes = ''' +I grepped for \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits. + +There are two places where `unsafe` is used. Unsafe review notes can be found +in https://crrev.com/c/5347418. + +This crate has been added to Chromium in https://crrev.com/c/3321895. +''' +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.lazy_static]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +delta = "1.4.0 -> 1.5.0" +notes = "Unsafe review notes: https://crrev.com/c/5650836" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.libc]] +who = "George Burgess IV " +criteria = "safe-to-run" +version = "0.2.146" +notes = """ +Much like the getrandom crate, this exports interfaces to APIs which perform +crypto, but does not implement any crypto itself. +""" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.libc]] +who = "Daniel Verkamp " +criteria = "safe-to-run" +delta = "0.2.147 -> 0.2.153" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.libc]] +who = "Hsin-chen Chuang " +criteria = "safe-to-run" +delta = "0.2.153 -> 0.2.170" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.log]] +who = "danakj " +criteria = "safe-to-deploy" +version = "0.4.22" +notes = """ +Unsafe review in https://docs.google.com/document/d/1IXQbD1GhTRqNHIGxq6yy7qHqxeO4CwN5noMFXnqyDIM/edit?usp=sharing + +Unsafety is generally very well-documented, with one exception, which we +describe in the review doc. +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.log]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +delta = "0.4.22 -> 0.4.25" +notes = "No impact on `unsafe` usage in `lib.rs`." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.log]] +who = "Daniel Cheng " +criteria = "safe-to-deploy" +delta = "0.4.25 -> 0.4.26" +notes = "Only trivial code and documentation changes." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.pin-project-lite]] +who = "ChromeOS" +criteria = "safe-to-run" +version = "0.2.9" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.pin-project-lite]] +who = "David Koloski " +criteria = "safe-to-deploy" +delta = "0.2.9 -> 0.2.13" +notes = "Audited at https://fxrev.dev/946396" +aggregated-from = "https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/third_party/rust_crates/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.proc-macro2]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +version = "1.0.78" +notes = """ +Grepped for "crypt", "cipher", "fs", "net" - there were no hits +(except for a benign "fs" hit in a doc comment) + +Notes from the `unsafe` review can be found in https://crrev.com/c/5385745. +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.proc-macro2]] +who = "Adrian Taylor " +criteria = "safe-to-deploy" +delta = "1.0.78 -> 1.0.79" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.proc-macro2]] +who = "Adrian Taylor " +criteria = "safe-to-deploy" +delta = "1.0.79 -> 1.0.80" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.proc-macro2]] +who = "Dustin J. Mitchell " +criteria = "safe-to-deploy" +delta = "1.0.80 -> 1.0.81" +notes = "Comment changes only" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.proc-macro2]] +who = "danakj " +criteria = "safe-to-deploy" +delta = "1.0.81 -> 1.0.82" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.proc-macro2]] +who = "Dustin J. Mitchell " +criteria = "safe-to-deploy" +delta = "1.0.82 -> 1.0.83" +notes = "Substantive change is replacing String with Box, saving memory." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.proc-macro2]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +delta = "1.0.83 -> 1.0.84" +notes = "Only doc comment changes in `src/lib.rs`." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.proc-macro2]] +who = "danakj@chromium.org" +criteria = "safe-to-deploy" +delta = "1.0.84 -> 1.0.85" +notes = "Test-only changes." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.proc-macro2]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +delta = "1.0.85 -> 1.0.86" +notes = """ +Comment-only changes in `build.rs`. +Reordering of `Cargo.toml` entries. +Just bumping up the version number in `lib.rs`. +Config-related changes in `test_size.rs`. +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.proc-macro2]] +who = "danakj " +criteria = "safe-to-deploy" +delta = "1.0.86 -> 1.0.87" +notes = "No new unsafe interactions." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.proc-macro2]] +who = "Liza Burakova Self { + let mut args = Args::test().unwrap(); + args.bless |= var_os("RUSTC_BLESS").is_some_and(|v| v != "0"); + Self { args } + } + + fn base_config(&self, test_dir: &str, mandatory_annotations: bool) -> Config { + let target_dir = PathBuf::from(var_os("CARGO_TARGET_DIR").unwrap_or_else(|| "target".into())); + let mut config = Config { + output_conflict_handling: error_on_output_conflict, + // Pre-fill filters with TESTNAME; will be later extended with `self.args`. + filter_files: env::var("TESTNAME") + .map(|filters| { + filters + .split(',') + // Make sure that if TESTNAME is empty we produce the empty list here, + // not a list containing an empty string. + .filter(|s| !s.is_empty()) + .map(str::to_string) + .collect() + }) + .unwrap_or_default(), + target: None, + bless_command: Some("cargo uibless".into()), + out_dir: target_dir.join("ui_test"), + ..Config::rustc(Path::new("tests").join(test_dir)) + }; + + let defaults = config.comment_defaults.base(); + defaults.set_custom("edition", Edition("2024".into())); + defaults.set_custom( + "dependencies", + DependencyBuilder { + program: { + let mut p = CommandBuilder::cargo(); + + // Override to check instead of build. + p.args = vec![ + "check".into(), + "--color=never".into(), + "--quiet".into(), + "--jobs".into(), + "1".into(), + ]; + + p.envs.push(( + "RUSTFLAGS".into(), + Some("-Zalways-encode-mir -Zhint-mostly-unused".into()), + )); + p + }, + crate_manifest_path: Path::new("cga_test_deps").join("Cargo.toml"), + build_std: Some("".into()), + bless_lockfile: self.args.bless, + }, + ); + defaults.exit_status = None.into(); + if mandatory_annotations { + defaults.require_annotations = Some(Spanned::dummy(true)).into(); + } else { + defaults.require_annotations = None.into(); + } + defaults.diagnostic_code_prefix = Some(Spanned::dummy("cga::".into())).into(); + config.with_args(&self.args); + + config.filter(&get_rustc_sysroot().to_string_lossy(), "rustc-sysroot"); + config.filter("cga-emit:.*\n", ""); + + config.program.args.extend( + [ + "--emit=metadata", + "-Aunused", + "-Ainternal_features", + "-Zui-testing", + "-Zdeduplicate-diagnostics=no", + "-Zalways-encode-mir", + "-Zhint-mostly-unused", + "-Dwarnings", + ] + .map(OsString::from), + ); + + let cga_input = serde_json::to_string(&DriverInput { + lint: Some(LintConfig::default()), + extract: true, + }) + .unwrap(); + + config.program.envs.extend( + [ + // Prevent rustc from creating `rustc-ice-*` files the console output is enough. + ("RUSTC_ICE".into(), Some("0".into())), + ("CGA_INPUT".into(), Some(cga_input.into())), + ] + .into_iter(), + ); + + if let Some(host_libs) = option_env!("HOST_LIBS") { + let deps_dir = Path::new(host_libs).join("deps"); + + if deps_dir.exists() { + let dep = format!("-Ldependency={}", deps_dir.display()); + config.program.args.push(dep.into()); + } else { + // If `/deps` does not exist, assume Cargo v2 build-dir layout + let build_dir = Path::new(host_libs).join("build"); + let dependencies = discover_out_dirs(&build_dir); + + for dep in dependencies { + let dep = format!("-Ldependency={}", dep.display()); + config.program.args.push(dep.into()); + } + } + } + + config.program.program = PathBuf::from(env!("CARGO_BIN_EXE_cga-driver")); + + config + } +} + +fn run_ui(cx: &TestContext) { + let mut config = cx.base_config("ui", true); + config.program.envs.push(("CGA_CONF_DIR".into(), Some("tests".into()))); + + ui_test::run_tests_generic( + vec![config], + ui_test::default_file_filter, + ui_test::default_per_file_config, + Box::::from(cx.args.format), + ) + .unwrap(); +} + +fn run_ui_cargo(cx: &TestContext) { + let mut config = cx.base_config("ui-cargo", false); + config.program.input_file_flag = CommandBuilder::cargo().input_file_flag; + config.program.out_dir_flag = CommandBuilder::cargo().out_dir_flag; + config.program.args = vec![ + "cga".into(), + "test".into(), + "--quiet".into(), + "--color=never".into(), + "--locked".into(), + ]; + config.program.envs.extend([ + ("RUSTFLAGS".into(), Some("-Dwarnings".into())), + ("CARGO_INCREMENTAL".into(), Some("0".into())), + ]); + // We need to do this while we still have a rustc in the `program` field. + config.fill_host_and_target().unwrap(); + config + .program + .program + .set_file_name(if cfg!(windows) { "cargo-cga.exe" } else { "cargo-cga" }); + config.comment_defaults.base().custom.clear(); + + config + .comment_defaults + .base() + .normalize_stderr + .push((Match::from(env::current_dir().unwrap().as_path()), b"$DIR".into())); + + ui_test::run_tests_generic( + vec![config], + |path, config| { + path.ends_with("Cargo.toml") + .then(|| ui_test::default_any_file_filter(path, config)) + }, + |config, file_contents| { + let mut path = file_contents.span().file; + path.set_file_name("callgraph.json"); + config + .program + .args + .extend_from_slice(&["--output-path".into(), (&path).into()]); + + path.set_file_name("cga.toml"); + if std::fs::exists(&path).unwrap() { + config + .program + .args + .extend_from_slice(&["--config-path".into(), (&path).into()]); + } + + path.set_file_name("target-triple"); + if std::fs::exists(&path).unwrap() { + config.target = Some(std::fs::read_to_string(path).unwrap()); + } + }, + Box::::from(cx.args.format), + ) + .unwrap(); +} + +fn main() { + unsafe { + set_var("CGA_DISABLE_DOCS_LINKS", "true"); + } + + let cx = TestContext::new(); + + run_ui(&cx); + run_ui_cargo(&cx); +} + +/// Gets all of the `out` dirs in a given Cargo `build-dir//build` dir. +fn discover_out_dirs(dir: &Path) -> Vec { + if !dir.exists() { + return Vec::new(); + } + + let read_dir = |path: &Path| path.read_dir().ok().into_iter().flatten().filter_map(Result::ok); + dir.read_dir() + .unwrap_or_else(|e| panic!("Couldn't read {}: {}", dir.display(), e)) + .map(|e| e.unwrap()) + .flat_map(|e| read_dir(&e.path())) + .flat_map(|e| read_dir(&e.path())) + .map(|e| e.path()) + .filter(|path| path.ends_with("out")) + .collect::>() +} + +fn get_rustc_sysroot() -> PathBuf { + let rustc = std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()); + let output = Command::new(rustc) + .args(["--print", "sysroot"]) + .output() + .expect("Failed to run rustc"); + + let sysroot_str = String::from_utf8(output.stdout).expect("Invalid UTF-8 output from rustc"); + + PathBuf::from(sysroot_str.trim()) +} diff --git a/tests/ui-cargo/fail-blacklist/Cargo.lock b/tests/ui-cargo/fail-blacklist/Cargo.lock new file mode 100644 index 0000000..7e65a1f --- /dev/null +++ b/tests/ui-cargo/fail-blacklist/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "fail-blacklist" +version = "0.1.0" diff --git a/tests/ui-cargo/fail-blacklist/Cargo.stderr b/tests/ui-cargo/fail-blacklist/Cargo.stderr new file mode 100644 index 0000000..190dd10 --- /dev/null +++ b/tests/ui-cargo/fail-blacklist/Cargo.stderr @@ -0,0 +1,76 @@ +error: call to associated function `std::process::Command::new` which can panic + --> src/main.rs:7:13 + | +7 | let _ = std::process::Command::new("evil"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: 14 different panics from this location + = note: backtrace (1 of 3843059837): + 0: std::rt::panic_fmt (called) + 1: alloc::raw_vec::RawVec::::into_box (called) + 2: std::vec::Vec::::into_boxed_slice (called) + 3: as std::clone::Clone>::clone (called) + 4: ::clone (called) + 5: std::sys::process::unix::common::Command::new (called) + 6: std::process::Command::new::<&str> (called) <-- + 7: fail_blacklist::test (called) + 8: fail_blacklist::main (entrypoint) + = note: `#[deny(cga::panics)]` on by default + +error: call to drop of `std::process::Command` which can panic + --> src/main.rs:7:47 + | +7 | let _ = std::process::Command::new("evil"); + | ^ + | + = note: 14 different panics from this location + = note: backtrace (1 of 3843090268): + 0: core::panicking::panic_nounwind_fmt (called) + 1: >::get_unchecked_mut::precondition_check (called) + 2: >::get_unchecked_mut (called) + 3: core::slice::::get_unchecked_mut:: (called) + 4: ::drop (called) + 5: drop of `std::ffi::CString` (called) + 6: drop of `std::sys::process::unix::common::Command` (called) + 7: drop of `std::process::Command` (called) <-- + 8: fail_blacklist::test (called) + 9: fail_blacklist::main (entrypoint) + +error: call to associated function `std::process::Command::new` which is blacklisted + --> src/main.rs:7:13 + | +7 | let _ = std::process::Command::new("evil"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: 104 different blacklisted functions from this location + = note: backtrace: + 0: std::process::Command::new::<&str> (called) <-- + 1: fail_blacklist::test (called) + 2: fail_blacklist::main (entrypoint) +note: the lint level is defined here + --> src/main.rs:3:11 + | +3 | #![forbid(cga::blacklisted)] + | ^^^^^^^^^^^^^^^^ + +error: call to drop of `std::process::Command` which can call blacklisted functions + --> src/main.rs:7:47 + | +7 | let _ = std::process::Command::new("evil"); + | ^ + | + = note: 105 different blacklisted functions from this location + = note: backtrace (1 of 42160433): + 0: std::sys::fs::unix::debug_assert_fd_is_open (called) + 1: ::drop (called) + 2: drop of `std::os::fd::OwnedFd` (called) + 3: drop of `std::sys::fd::unix::FileDesc` (called) + 4: drop of `std::sys::process::unix::common::Stdio` (called) + 5: drop of `std::option::Option` (called) + 6: drop of `std::sys::process::unix::common::Command` (called) + 7: drop of `std::process::Command` (called) <-- + 8: fail_blacklist::test (called) + 9: fail_blacklist::main (entrypoint) + +error: could not compile `fail-blacklist` (bin "fail-blacklist") due to 4 previous errors +Error: 101 diff --git a/tests/ui-cargo/fail-blacklist/Cargo.toml b/tests/ui-cargo/fail-blacklist/Cargo.toml new file mode 100644 index 0000000..6800fdf --- /dev/null +++ b/tests/ui-cargo/fail-blacklist/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "fail-blacklist" +version = "0.1.0" +publish = false +edition = "2024" + +[dependencies] diff --git a/tests/ui-cargo/fail-blacklist/cga.toml b/tests/ui-cargo/fail-blacklist/cga.toml new file mode 100644 index 0000000..b62f70d --- /dev/null +++ b/tests/ui-cargo/fail-blacklist/cga.toml @@ -0,0 +1,5 @@ +blacklist = [ + { path = "^std::process::" }, + { path = "^std::fs::" }, + { path = "^std::sys::" } +] \ No newline at end of file diff --git a/tests/ui-cargo/fail-blacklist/src/main.rs b/tests/ui-cargo/fail-blacklist/src/main.rs new file mode 100644 index 0000000..a5b2406 --- /dev/null +++ b/tests/ui-cargo/fail-blacklist/src/main.rs @@ -0,0 +1,12 @@ +#![feature(register_tool)] +#![register_tool(cga)] +#![forbid(cga::blacklisted)] +#![allow(unused)] + +fn test() { + let _ = std::process::Command::new("evil"); +} + +fn main() { + test(); +} diff --git a/tests/ui-cargo/fail-embedded/.cargo/config.toml b/tests/ui-cargo/fail-embedded/.cargo/config.toml new file mode 100644 index 0000000..40f2457 --- /dev/null +++ b/tests/ui-cargo/fail-embedded/.cargo/config.toml @@ -0,0 +1,10 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip RP235x" +#runner = "elf2uf2-rs -d" +#runner = "picotool load -u -v -x -t elf" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "debug" diff --git a/tests/ui-cargo/fail-embedded/Cargo.lock b/tests/ui-cargo/fail-embedded/Cargo.lock new file mode 100644 index 0000000..afc26f6 --- /dev/null +++ b/tests/ui-cargo/fail-embedded/Cargo.lock @@ -0,0 +1,1541 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "az" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "defmt", + "num-traits", + "pure-rust-locales", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "cordyceps" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" +dependencies = [ + "loom", + "tracing", +] + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc-any" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46db9f663dfb869b80fcf59e32d7a80fc6c464a4f6328f3f06a00f5e36d05f8c" +dependencies = [ + "debug-helper", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "debug-helper" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" + +[[package]] +name = "defmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f73a4a4a91609e977ae3b7bd831ffa292edfd42ad140a3244a61d805b0e05e" +dependencies = [ + "critical-section", + "defmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "embassy-embedded-hal" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0641612053b2f34fc250bb63f6630ae75de46e02ade7f457268447081d709ce" +dependencies = [ + "embassy-futures", + "embassy-hal-internal 0.4.0", + "embassy-sync", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-storage", + "embedded-storage-async", + "nb 1.1.0", +] + +[[package]] +name = "embassy-executor" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0d3b15c9d7dc4fec1d8cb77112472fb008b3b28c51ad23838d83587a6d2f1e" +dependencies = [ + "cordyceps", + "cortex-m", + "critical-section", + "defmt", + "document-features", + "embassy-executor-macros", + "embassy-executor-timer-queue", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d11a246f53de5f97a387f40ac24726817cd0b6f833e7603baac784f29d6ff276" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "embassy-executor-timer-queue" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c" + +[[package]] +name = "embassy-futures" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" + +[[package]] +name = "embassy-hal-internal" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f10ce10a4dfdf6402d8e9bd63128986b96a736b1a0a6680547ed2ac55d55dba" +dependencies = [ + "num-traits", +] + +[[package]] +name = "embassy-hal-internal" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568659fc53866d3d85c60fa33723fb751aa69e71507634fc2c19e7649432fb75" +dependencies = [ + "cortex-m", + "critical-section", + "defmt", + "num-traits", +] + +[[package]] +name = "embassy-rp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d98f472894c1ecffac5596c657af5305c57282d29e8746d7fec033951931bc8" +dependencies = [ + "cfg-if", + "chrono", + "cortex-m", + "cortex-m-rt", + "critical-section", + "defmt", + "document-features", + "embassy-embedded-hal", + "embassy-futures", + "embassy-hal-internal 0.5.0", + "embassy-sync", + "embassy-time", + "embassy-time-driver", + "embassy-time-queue-utils", + "embassy-usb-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io 0.7.1", + "embedded-io-async 0.7.0", + "embedded-storage", + "embedded-storage-async", + "fixed", + "nb 1.1.0", + "pio", + "rand_core 0.6.4", + "rand_core 0.9.5", + "rp-binary-info", + "rp-pac", + "rp2040-boot2", + "sha2-const-stable", + "smart-leds", +] + +[[package]] +name = "embassy-sync" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async 0.7.0", + "futures-core", + "futures-sink", + "heapless", +] + +[[package]] +name = "embassy-time" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" +dependencies = [ + "cfg-if", + "critical-section", + "defmt", + "document-features", + "embassy-time-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-core", +] + +[[package]] +name = "embassy-time-driver" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ee71af1b3a0deaa53eaf2d39252f83504c853646e472400b763060389b9fcc9" +dependencies = [ + "document-features", +] + +[[package]] +name = "embassy-time-queue-utils" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168297bf80aaf114b3c9ad589bf38b01b3009b9af7f97cd18086c5bbf96f5693" +dependencies = [ + "embassy-executor-timer-queue", + "heapless", +] + +[[package]] +name = "embassy-usb-driver" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa675c5f4349b6aa0fcffc4bf9b241f18cd11b97c1f8323273fb9a5449937fbd" +dependencies = [ + "defmt", + "embedded-io-async 0.6.1", + "embedded-io-async 0.7.0", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-io" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "embedded-io 0.6.1", +] + +[[package]] +name = "embedded-io-async" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564b9f813c544241430e147d8bc454815ef9ac998878d30cc3055449f7fd4c0" +dependencies = [ + "embedded-io 0.7.1", +] + +[[package]] +name = "embedded-storage" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" + +[[package]] +name = "embedded-storage-async" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1763775e2323b7d5f0aa6090657f5e21cfa02ede71f5dc40eead06d64dcd15cc" +dependencies = [ + "embedded-storage", +] + +[[package]] +name = "ena" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabffdaee24bd1bf95c5ef7cec31260444317e72ea56c4c91750e8b7ee58d5f1" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fail-embedded" +version = "0.1.0" +dependencies = [ + "cortex-m-rt", + "defmt", + "defmt-rtt", + "embassy-executor", + "embassy-rp", + "embassy-time", + "panic-probe", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixed" +version = "1.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9af2cbf772fa6d1c11358f92ef554cb6b386201210bcf0e91fb7fba8a907fb40" +dependencies = [ + "az", + "bytemuck", + "half", + "typenum", +] + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "generator" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "regex-automata", + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "panic-probe" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" +dependencies = [ + "cortex-m", + "defmt", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pio" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ba4153cee9585abc451271aa437d9e8defdea8b468d48ba6b8f098cbe03d7f" +dependencies = [ + "pio-core", + "pio-proc", +] + +[[package]] +name = "pio-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61d90fddc3d67f21bbf93683bc461b05d6a29c708caf3ffb79947d7ff7095406" +dependencies = [ + "arrayvec", + "num_enum", + "paste", +] + +[[package]] +name = "pio-parser" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825266c1eaddf54f636d06eefa4bf3c99d774c14ec46a4a6c6e5128a0f10d205" +dependencies = [ + "lalrpop", + "lalrpop-util", + "pio-core", +] + +[[package]] +name = "pio-proc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed4a76571f5fe51af43cc80ac870fe0c79cc0cdd686b9002a6c4c84bfdd0176b" +dependencies = [ + "codespan-reporting", + "lalrpop-util", + "pio-core", + "pio-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pure-rust-locales" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869675ad2d7541aea90c6d88c81f46a7f4ea9af8cd0395d38f11a95126998a0d" +dependencies = [ + "defmt", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" + +[[package]] +name = "rp-binary-info" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f582261945fa215d40e2988b4df595d11c0908c0fff97a0fe23df766d117b790" + +[[package]] +name = "rp-pac" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8af65855c40b2c35079514c5489abffc0429347fef25d8467ff98ad84b4322d3" +dependencies = [ + "cortex-m", + "cortex-m-rt", +] + +[[package]] +name = "rp2040-boot2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21" +dependencies = [ + "crc-any", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "smart-leds" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66df34e571fa9993fa6f99131a374d58ca3d694b75f9baac93458fe0d6057bf0" +dependencies = [ + "smart-leds-trait", +] + +[[package]] +name = "smart-leds-trait" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f4441a131924d58da6b83a7ad765c460e64630cce504376c3a87a2558c487f" +dependencies = [ + "rgb", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "term" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/tests/ui-cargo/fail-embedded/Cargo.stderr b/tests/ui-cargo/fail-embedded/Cargo.stderr new file mode 100644 index 0000000..821312d --- /dev/null +++ b/tests/ui-cargo/fail-embedded/Cargo.stderr @@ -0,0 +1,189 @@ +error: call to method `embassy_executor::Executor::run` which can panic + --> src/main.rs:11:1 + | +11 | #[embassy_executor::main] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ expanded from here + | + = note: 13 different panics from this location + = note: backtrace (1 of 17249841): + 0: core::panicking::panic_fmt (called) + 1: core::result::unwrap_failed (called) + 2: core::result::Result::, embassy_executor::SpawnError>::unwrap (called) + 3: fail_embedded::__cortex_m_rt_main::{closure#0} (called) + 4: embassy_executor::Executor::run::<{closure@src/main.rs:11:1: 11:26}> (called) <-- + 5: fail_embedded::__cortex_m_rt_main (called) + 6: fail_embedded::__cortex_m_rt_main_trampoline (entrypoint) + = note: `#[deny(cga::panics)]` on by default + = note: this error originates in the attribute macro `embassy_executor::main` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: call to function `embassy_rp::init` which can panic + --> src/main.rs:13:13 + | +13 | let p = embassy_rp::init(Default::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: 7 different panics from this location + = note: backtrace (1 of 721): + 0: core::panicking::panic_fmt (called) + 1: core::sync::atomic::compiler_fence (called) + 2: ::enable (called) + 3: embassy_rp::time_driver::init (called) + 4: embassy_rp::init (called) <-- + 5: fail_embedded::____embassy_main_task::____embassy_main_task_inner_function::{closure#0} (called) + 6: embassy_executor::raw::TaskStorage::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}>::poll (fn-ptr cast) + 7: embassy_executor::raw::AvailableTask::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}>::initialize_impl::<{closure@src/main.rs:11:1: 11:26}, {closure@src/main.rs:11:1: 11:26}> (called) + 8: embassy_executor::raw::TaskPool::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}, 1>::spawn_impl::<{closure@src/main.rs:11:1: 11:26}, {closure@src/main.rs:11:1: 11:26}> (called) + 9: embassy_executor::raw::TaskPool::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}, 1>::_spawn_async_fn::<{closure@src/main.rs:11:1: 11:26}> (called) + 10: fail_embedded::__embassy_main (called) + 11: fail_embedded::__cortex_m_rt_main::{closure#0} (called) + 12: embassy_executor::Executor::run::<{closure@src/main.rs:11:1: 11:26}> (called) + 13: fail_embedded::__cortex_m_rt_main (called) + 14: fail_embedded::__cortex_m_rt_main_trampoline (entrypoint) + +error: call to associated function `embassy_rp::gpio::Output::<'d>::new` which can panic + --> src/main.rs:14:19 + | +14 | let mut led = Output::new(p.PIN_25, Level::Low); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: 7 different panics from this location + = note: backtrace (1 of 91): + 0: core::panicking::panic (called) + 1: embassy_rp::rp_pac::pads::Pads::gpio (called) + 2: ::pad_ctrl (called) + 3: embassy_rp::gpio::Flex::<'_>::new:: (called) + 4: embassy_rp::gpio::Output::<'_>::new:: (called) <-- + 5: fail_embedded::____embassy_main_task::____embassy_main_task_inner_function::{closure#0} (called) + 6: embassy_executor::raw::TaskStorage::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}>::poll (fn-ptr cast) + 7: embassy_executor::raw::AvailableTask::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}>::initialize_impl::<{closure@src/main.rs:11:1: 11:26}, {closure@src/main.rs:11:1: 11:26}> (called) + 8: embassy_executor::raw::TaskPool::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}, 1>::spawn_impl::<{closure@src/main.rs:11:1: 11:26}, {closure@src/main.rs:11:1: 11:26}> (called) + 9: embassy_executor::raw::TaskPool::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}, 1>::_spawn_async_fn::<{closure@src/main.rs:11:1: 11:26}> (called) + 10: fail_embedded::__embassy_main (called) + 11: fail_embedded::__cortex_m_rt_main::{closure#0} (called) + 12: embassy_executor::Executor::run::<{closure@src/main.rs:11:1: 11:26}> (called) + 13: fail_embedded::__cortex_m_rt_main (called) + 14: fail_embedded::__cortex_m_rt_main_trampoline (entrypoint) + +error: call to associated function `embassy_rp::gpio::Input::<'d>::new` which can panic + --> src/main.rs:15:27 + | +15 | let mut async_input = Input::new(p.PIN_16, Pull::None); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: 7 different panics from this location + = note: backtrace (1 of 83): + 0: core::panicking::panic (called) + 1: embassy_rp::rp_pac::pads::Pads::gpio (called) + 2: ::pad_ctrl (called) + 3: embassy_rp::gpio::Flex::<'_>::new:: (called) + 4: embassy_rp::gpio::Input::<'_>::new:: (called) <-- + 5: fail_embedded::____embassy_main_task::____embassy_main_task_inner_function::{closure#0} (called) + 6: embassy_executor::raw::TaskStorage::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}>::poll (fn-ptr cast) + 7: embassy_executor::raw::AvailableTask::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}>::initialize_impl::<{closure@src/main.rs:11:1: 11:26}, {closure@src/main.rs:11:1: 11:26}> (called) + 8: embassy_executor::raw::TaskPool::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}, 1>::spawn_impl::<{closure@src/main.rs:11:1: 11:26}, {closure@src/main.rs:11:1: 11:26}> (called) + 9: embassy_executor::raw::TaskPool::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}, 1>::_spawn_async_fn::<{closure@src/main.rs:11:1: 11:26}> (called) + 10: fail_embedded::__embassy_main (called) + 11: fail_embedded::__cortex_m_rt_main::{closure#0} (called) + 12: embassy_executor::Executor::run::<{closure@src/main.rs:11:1: 11:26}> (called) + 13: fail_embedded::__cortex_m_rt_main (called) + 14: fail_embedded::__cortex_m_rt_main_trampoline (entrypoint) + +error: call to method `embassy_rp::gpio::Output::<'d>::set_high` which can panic + --> src/main.rs:19:9 + | +19 | led.set_high(); + | ^^^^^^^^^^^^^^ + | + = note: 7 different panics from this location + = note: backtrace (1 of 15): + 0: core::panicking::panic (called) + 1: embassy_rp::rp_pac::sio::Sio::gpio_out (called) + 2: ::sio_out (called) + 3: embassy_rp::gpio::Flex::<'_>::set_high (called) + 4: embassy_rp::gpio::Output::<'_>::set_high (called) <-- + 5: fail_embedded::____embassy_main_task::____embassy_main_task_inner_function::{closure#0} (called) + 6: embassy_executor::raw::TaskStorage::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}>::poll (fn-ptr cast) + 7: embassy_executor::raw::AvailableTask::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}>::initialize_impl::<{closure@src/main.rs:11:1: 11:26}, {closure@src/main.rs:11:1: 11:26}> (called) + 8: embassy_executor::raw::TaskPool::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}, 1>::spawn_impl::<{closure@src/main.rs:11:1: 11:26}, {closure@src/main.rs:11:1: 11:26}> (called) + 9: embassy_executor::raw::TaskPool::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}, 1>::_spawn_async_fn::<{closure@src/main.rs:11:1: 11:26}> (called) + 10: fail_embedded::__embassy_main (called) + 11: fail_embedded::__cortex_m_rt_main::{closure#0} (called) + 12: embassy_executor::Executor::run::<{closure@src/main.rs:11:1: 11:26}> (called) + 13: fail_embedded::__cortex_m_rt_main (called) + 14: fail_embedded::__cortex_m_rt_main_trampoline (entrypoint) + +error: call to method `embassy_rp::gpio::Output::<'d>::set_low` which can panic + --> src/main.rs:24:9 + | +24 | led.set_low(); + | ^^^^^^^^^^^^^ + | + = note: 7 different panics from this location + = note: backtrace (1 of 15): + 0: core::panicking::panic (called) + 1: embassy_rp::rp_pac::sio::Sio::gpio_out (called) + 2: ::sio_out (called) + 3: embassy_rp::gpio::Flex::<'_>::set_low (called) + 4: embassy_rp::gpio::Output::<'_>::set_low (called) <-- + 5: fail_embedded::____embassy_main_task::____embassy_main_task_inner_function::{closure#0} (called) + 6: embassy_executor::raw::TaskStorage::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}>::poll (fn-ptr cast) + 7: embassy_executor::raw::AvailableTask::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}>::initialize_impl::<{closure@src/main.rs:11:1: 11:26}, {closure@src/main.rs:11:1: 11:26}> (called) + 8: embassy_executor::raw::TaskPool::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}, 1>::spawn_impl::<{closure@src/main.rs:11:1: 11:26}, {closure@src/main.rs:11:1: 11:26}> (called) + 9: embassy_executor::raw::TaskPool::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}, 1>::_spawn_async_fn::<{closure@src/main.rs:11:1: 11:26}> (called) + 10: fail_embedded::__embassy_main (called) + 11: fail_embedded::__cortex_m_rt_main::{closure#0} (called) + 12: embassy_executor::Executor::run::<{closure@src/main.rs:11:1: 11:26}> (called) + 13: fail_embedded::__cortex_m_rt_main (called) + 14: fail_embedded::__cortex_m_rt_main_trampoline (entrypoint) + +error: call to async fn `embassy_rp::gpio::Input::<'d>::wait_for_high::{closure#0}` which can panic + --> src/main.rs:21:37 + | +21 | async_input.wait_for_high().await; + | ^^^^^ expanded from here + | + = note: 7 different panics from this location + = note: backtrace (1 of 122): + 0: core::panicking::panic (called) + 1: embassy_rp::rp_pac::io::Io::intr (called) + 2: embassy_rp::gpio::InputFuture::<'_>::new (called) + 3: embassy_rp::gpio::Flex::<'_>::wait_for_high::{closure#0} (called) + 4: embassy_rp::gpio::Input::<'_>::wait_for_high::{closure#0} (called) <-- + 5: fail_embedded::____embassy_main_task::____embassy_main_task_inner_function::{closure#0} (called) + 6: embassy_executor::raw::TaskStorage::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}>::poll (fn-ptr cast) + 7: embassy_executor::raw::AvailableTask::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}>::initialize_impl::<{closure@src/main.rs:11:1: 11:26}, {closure@src/main.rs:11:1: 11:26}> (called) + 8: embassy_executor::raw::TaskPool::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}, 1>::spawn_impl::<{closure@src/main.rs:11:1: 11:26}, {closure@src/main.rs:11:1: 11:26}> (called) + 9: embassy_executor::raw::TaskPool::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}, 1>::_spawn_async_fn::<{closure@src/main.rs:11:1: 11:26}> (called) + 10: fail_embedded::__embassy_main (called) + 11: fail_embedded::__cortex_m_rt_main::{closure#0} (called) + 12: embassy_executor::Executor::run::<{closure@src/main.rs:11:1: 11:26}> (called) + 13: fail_embedded::__cortex_m_rt_main (called) + 14: fail_embedded::__cortex_m_rt_main_trampoline (entrypoint) + +error: call to associated function `embassy_time::Timer::after_secs` which can panic + --> src/main.rs:26:9 + | +26 | Timer::after_secs(2).await; + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: 14 different panics from this location + = note: backtrace (1 of 2): + 0: core::panicking::panic_display::<&str> (called) + 1: core::option::expect_failed (called) + 2: core::option::Option::::expect (called) + 3: >::add (called) + 4: embassy_time::Timer::after (called) + 5: embassy_time::Timer::after_secs (called) <-- + 6: fail_embedded::____embassy_main_task::____embassy_main_task_inner_function::{closure#0} (called) + 7: embassy_executor::raw::TaskStorage::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}>::poll (fn-ptr cast) + 8: embassy_executor::raw::AvailableTask::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}>::initialize_impl::<{closure@src/main.rs:11:1: 11:26}, {closure@src/main.rs:11:1: 11:26}> (called) + 9: embassy_executor::raw::TaskPool::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}, 1>::spawn_impl::<{closure@src/main.rs:11:1: 11:26}, {closure@src/main.rs:11:1: 11:26}> (called) + 10: embassy_executor::raw::TaskPool::<{async fn body of fail_embedded::____embassy_main_task::____embassy_main_task_inner_function()}, 1>::_spawn_async_fn::<{closure@src/main.rs:11:1: 11:26}> (called) + 11: fail_embedded::__embassy_main (called) + 12: fail_embedded::__cortex_m_rt_main::{closure#0} (called) + 13: embassy_executor::Executor::run::<{closure@src/main.rs:11:1: 11:26}> (called) + 14: fail_embedded::__cortex_m_rt_main (called) + 15: fail_embedded::__cortex_m_rt_main_trampoline (entrypoint) + +error: could not compile `fail-embedded` (bin "fail-embedded") due to 8 previous errors +Error: 101 diff --git a/tests/ui-cargo/fail-embedded/Cargo.toml b/tests/ui-cargo/fail-embedded/Cargo.toml new file mode 100644 index 0000000..5acaa92 --- /dev/null +++ b/tests/ui-cargo/fail-embedded/Cargo.toml @@ -0,0 +1,22 @@ +[package] +edition = "2024" +name = "fail-embedded" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +publish = false + +[dependencies] +embassy-executor = { version = "0.10.0", features = ["platform-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-rp = { version = "0.10.0", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp235xa", "binary-info", "rt"] } +embassy-time = { version = "0.5.1", features = ["defmt", "defmt-timestamp-uptime"] } + +defmt = "1.0.1" +defmt-rtt = "1.0.0" + +cortex-m-rt = "0.7.0" + +panic-probe = { version = "1.0.0", features = ["print-defmt"] } + +[package.metadata.cargo-machete] +ignored = ["cortex-m-rt"] \ No newline at end of file diff --git a/tests/ui-cargo/fail-embedded/cga.toml b/tests/ui-cargo/fail-embedded/cga.toml new file mode 100644 index 0000000..cd6938f --- /dev/null +++ b/tests/ui-cargo/fail-embedded/cga.toml @@ -0,0 +1,5 @@ +whitelist = [] + +[options] +allow_async_panic = true +allow_assert = false diff --git a/tests/ui-cargo/fail-embedded/src/main.rs b/tests/ui-cargo/fail-embedded/src/main.rs new file mode 100644 index 0000000..9bb3038 --- /dev/null +++ b/tests/ui-cargo/fail-embedded/src/main.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::gpio::{self, Input, Pull}; +use embassy_time::Timer; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_25, Level::Low); + let mut async_input = Input::new(p.PIN_16, Pull::None); + + loop { + info!("wait_for_high. Turn on LED"); + led.set_high(); + + async_input.wait_for_high().await; + + info!("done wait_for_high. Turn off LED"); + led.set_low(); + + Timer::after_secs(2).await; + } +} diff --git a/tests/ui-cargo/fail-embedded/target-triple b/tests/ui-cargo/fail-embedded/target-triple new file mode 100644 index 0000000..40c8822 --- /dev/null +++ b/tests/ui-cargo/fail-embedded/target-triple @@ -0,0 +1 @@ +thumbv8m.main-none-eabihf \ No newline at end of file diff --git a/tests/ui-cargo/fail-simple/Cargo.lock b/tests/ui-cargo/fail-simple/Cargo.lock new file mode 100644 index 0000000..8a9b0b5 --- /dev/null +++ b/tests/ui-cargo/fail-simple/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "embassy-futures" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" + +[[package]] +name = "fail-simple" +version = "0.1.0" +dependencies = [ + "embassy-futures", +] diff --git a/tests/ui-cargo/fail-simple/Cargo.stderr b/tests/ui-cargo/fail-simple/Cargo.stderr new file mode 100644 index 0000000..9c7b359 --- /dev/null +++ b/tests/ui-cargo/fail-simple/Cargo.stderr @@ -0,0 +1,49 @@ +error: call to method `core::num::::strict_add` which can panic + --> src/main.rs:7:13 + | +7 | let _ = 2u64.strict_add(500); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: 3 different panics from this location + = note: backtrace (1 of 2): + 0: core::panicking::panic_fmt (called) + 1: core::num::imp::overflow_panic::add (called) + 2: core::num::::strict_add (called) <-- + 3: fail_simple::test (called) + 4: fail_simple::main (entrypoint) +note: the lint level is defined here + --> src/main.rs:3:11 + | +3 | #![forbid(cga::panics)] + | ^^^^^^^^^^^ + +error: call to function `core::panicking::panic_fmt` which is a panic + --> src/main.rs:16:9 + | +16 | panic!("Oh no!"); + | ^^^^^^^^^^^^^^^^ expanded from here + | + = note: this location is called in 2 different traces + = note: 3 different panics from this location + = note: backtrace (1 of 2): + 0: core::panicking::panic_fmt (called) <-- + 1: ::drop (called) + 2: drop of `Foo` (called) + 3: std::mem::drop:: (called) + 4: fail_simple::main (entrypoint) + +error: can be poll'd after Ready which is a panic + --> src/main.rs:10:16 + | +10 | async fn bar() {} + | ^^ + | + = note: an async block can always panic when poll'd after Ready + = note: backtrace: + 0: implicit async panic <-- + 1: fail_simple::bar::{closure#0} (called) + 2: embassy_futures::block_on::<{async fn body of fail_simple::bar()}> (called) + 3: fail_simple::main (entrypoint) + +error: could not compile `fail-simple` (bin "fail-simple") due to 3 previous errors +Error: 101 diff --git a/tests/ui-cargo/fail-simple/Cargo.toml b/tests/ui-cargo/fail-simple/Cargo.toml new file mode 100644 index 0000000..7b17802 --- /dev/null +++ b/tests/ui-cargo/fail-simple/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fail-simple" +version = "0.1.0" +publish = false +edition = "2024" + +[dependencies] +embassy-futures = "0.1" \ No newline at end of file diff --git a/tests/ui-cargo/fail-simple/src/main.rs b/tests/ui-cargo/fail-simple/src/main.rs new file mode 100644 index 0000000..c8cfbf3 --- /dev/null +++ b/tests/ui-cargo/fail-simple/src/main.rs @@ -0,0 +1,26 @@ +#![feature(register_tool)] +#![register_tool(cga)] +#![forbid(cga::panics)] +#![allow(unused)] + +fn test() { + let _ = 2u64.strict_add(500); +} + +async fn bar() {} + +struct Foo; + +impl Drop for Foo { + fn drop(&mut self) { + panic!("Oh no!"); + } +} + +fn main() { + test(); + + let foo = Foo; + core::mem::drop(foo); + embassy_futures::block_on(bar()); +} diff --git a/tests/ui/assert_macro.rs b/tests/ui/assert_macro.rs new file mode 100644 index 0000000..1954805 --- /dev/null +++ b/tests/ui/assert_macro.rs @@ -0,0 +1,12 @@ +#![feature(register_tool)] +#![register_tool(cga)] +#![warn(cga::panics)] + +fn test() { + assert!(false); + //~^ panics +} + +fn main() { + test(); +} diff --git a/tests/ui/assert_macro.stderr b/tests/ui/assert_macro.stderr new file mode 100644 index 0000000..19c6649 --- /dev/null +++ b/tests/ui/assert_macro.stderr @@ -0,0 +1,16 @@ +error: call to function `core::panicking::panic` which is a panic + --> tests/ui/assert_macro.rs:6:5 + | +LL | assert!(false); + | ^^^^^^^^^^^^^^ expanded from here + | + = note: 4 different panics from this location + = note: backtrace: + 0: core::panicking::panic (called) <-- + 1: assert_macro::test (called) + 2: assert_macro::main (entrypoint) + = note: `-D cga::panics` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(cga::panics)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/assert_mir.rs b/tests/ui/assert_mir.rs new file mode 100644 index 0000000..0f1058c --- /dev/null +++ b/tests/ui/assert_mir.rs @@ -0,0 +1,10 @@ +fn test() { + // Performs overflow add, which can panic when overflowing. + // This is then a MIR terminator assert, and not an explicit function call to panic. + 1 + 1; + //~^ panics +} + +fn main() { + test(); +} diff --git a/tests/ui/assert_mir.stderr b/tests/ui/assert_mir.stderr new file mode 100644 index 0000000..6d3e214 --- /dev/null +++ b/tests/ui/assert_mir.stderr @@ -0,0 +1,14 @@ +error: implicit assert which can panic + --> tests/ui/assert_mir.rs:4:5 + | +LL | 1 + 1; + | ^^^^^ + | + = note: backtrace: + 0: assert <-- + 1: assert_mir::test (called) + 2: assert_mir::main (entrypoint) + = note: `#[deny(cga::panics)]` on by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/async.rs b/tests/ui/async.rs new file mode 100644 index 0000000..78132ec --- /dev/null +++ b/tests/ui/async.rs @@ -0,0 +1,26 @@ +//~v panics +async fn test() { + // Do nothing. +} + +//~v panics +async fn test_with_panic() { + let x = 5u32; + let x = x.saturating_add(10); + let (x, _) = x.overflowing_add(5); + let _ = x.strict_add(3); + //~^ panics +} + +fn main() { + let waker = core::task::Waker::noop(); + let mut ctx = core::task::Context::from_waker(waker); + + let mut fut = test(); + + core::pin::pin!(fut).poll(&mut ctx); + + let mut fut = test_with_panic(); + + core::pin::pin!(fut).poll(&mut ctx); +} diff --git a/tests/ui/async.stderr b/tests/ui/async.stderr new file mode 100644 index 0000000..d192607 --- /dev/null +++ b/tests/ui/async.stderr @@ -0,0 +1,52 @@ +error: can be poll'd after Ready which is a panic + --> tests/ui/async.rs:2:17 + | +LL | async fn test() { + | _________________^ +LL | | // Do nothing. +LL | | } + | |_^ + | + = note: this location is called in 2 different traces + = note: an async block can always panic when poll'd after Ready + = note: backtrace (1 of 2): + 0: implicit async panic <-- + 1: async::test::{closure#0} (called) + 2: async::main (entrypoint) + = note: `#[deny(cga::panics)]` on by default + +error: can be poll'd after Ready which is a panic + --> tests/ui/async.rs:7:28 + | +LL | async fn test_with_panic() { + | ____________________________^ +LL | | let x = 5u32; +LL | | let x = x.saturating_add(10); +LL | | let (x, _) = x.overflowing_add(5); +... | +LL | | } + | |_^ + | + = note: this location is called in 2 different traces + = note: an async block can always panic when poll'd after Ready + = note: backtrace (1 of 2): + 0: implicit async panic <-- + 1: async::test_with_panic::{closure#0} (called) + 2: async::main (entrypoint) + +error: call to method `core::num::::strict_add` which can panic + --> tests/ui/async.rs:11:13 + | +LL | let _ = x.strict_add(3); + | ^^^^^^^^^^^^^^^ + | + = note: 3 different panics from this location + = note: backtrace (1 of 2): + 0: core::panicking::panic_fmt (called) + 1: core::num::imp::overflow_panic::add (called) + 2: core::num::::strict_add (called) <-- + 3: async::test_with_panic::{closure#0} (called) + 4: async::main (entrypoint) + +error: aborting due to 3 previous errors + diff --git a/tests/ui/cycle.rs b/tests/ui/cycle.rs new file mode 100644 index 0000000..2587dc4 --- /dev/null +++ b/tests/ui/cycle.rs @@ -0,0 +1,15 @@ +fn test(i: u32) { + if i > 100 { + return; + } + + test(i.saturating_add(1)); +} + +pub fn main() { + // Test if MIR reachability expansion does not infinite loop when recursing. + test(0); + + let _ = 2u64.strict_add(500); + //~^ panics +} diff --git a/tests/ui/cycle.stderr b/tests/ui/cycle.stderr new file mode 100644 index 0000000..a45ef4d --- /dev/null +++ b/tests/ui/cycle.stderr @@ -0,0 +1,16 @@ +error: call to method `core::num::::strict_add` which can panic + --> tests/ui/cycle.rs:13:13 + | +LL | let _ = 2u64.strict_add(500); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: 3 different panics from this location + = note: backtrace (1 of 2): + 0: std::rt::panic_fmt (called) + 1: core::num::imp::overflow_panic::add (called) + 2: core::num::::strict_add (called) <-- + 3: cycle::main (entrypoint) + = note: `#[deny(cga::panics)]` on by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/drop.rs b/tests/ui/drop.rs new file mode 100644 index 0000000..8c8238c --- /dev/null +++ b/tests/ui/drop.rs @@ -0,0 +1,13 @@ +struct Foo; + +impl Drop for Foo { + fn drop(&mut self) { + panic!("Oh no!"); + //~^ panics + } +} + +fn main() { + let foo = Foo; + core::mem::drop(foo); +} diff --git a/tests/ui/drop.stderr b/tests/ui/drop.stderr new file mode 100644 index 0000000..0c579b7 --- /dev/null +++ b/tests/ui/drop.stderr @@ -0,0 +1,17 @@ +error: call to function `core::panicking::panic_fmt` which is a panic + --> tests/ui/drop.rs:5:9 + | +LL | panic!("Oh no!"); + | ^^^^^^^^^^^^^^^^ expanded from here + | + = note: 3 different panics from this location + = note: backtrace: + 0: core::panicking::panic_fmt (called) <-- + 1: ::drop (called) + 2: drop of `Foo` (called) + 3: std::mem::drop:: (called) + 4: drop::main (entrypoint) + = note: `#[deny(cga::panics)]` on by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/dyn_trait.rs b/tests/ui/dyn_trait.rs new file mode 100644 index 0000000..ce47965 --- /dev/null +++ b/tests/ui/dyn_trait.rs @@ -0,0 +1,37 @@ +trait Test { + fn test(&self) { + panic!("Default impl") + //~^ panics + } +} + +struct StrictAdd; +struct StrictSub; +struct DefaultImpl; + +impl Test for StrictAdd { + fn test(&self) { + let _ = 2u64.strict_add(500); + //~^ panics + } +} + +impl Test for StrictSub { + fn test(&self) { + let _ = 2u64.strict_sub(500); + //~^ panics + } +} + +impl Test for DefaultImpl {} + +fn main() { + // Even though only StrictAdd is instantiated here, + // the dyn object cast makes it unclear which is being called. + // + // Hence both `StrictAdd` and `StrictSub` emit a panic. + let x = StrictAdd; + + let r: &dyn Test = &x; + r.test(); +} diff --git a/tests/ui/dyn_trait.stderr b/tests/ui/dyn_trait.stderr new file mode 100644 index 0000000..a7eede3 --- /dev/null +++ b/tests/ui/dyn_trait.stderr @@ -0,0 +1,47 @@ +error: call to method `core::num::::strict_add` which can panic + --> tests/ui/dyn_trait.rs:14:17 + | +LL | let _ = 2u64.strict_add(500); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: 3 different panics from this location + = note: backtrace (1 of 2): + 0: std::rt::panic_fmt (called) + 1: core::num::imp::overflow_panic::add (called) + 2: core::num::::strict_add (called) <-- + 3: ::test (possible vtable resolution) + 4: ::test - virtual#3 (called) + 5: dyn_trait::main (entrypoint) + = note: `#[deny(cga::panics)]` on by default + +error: call to method `core::num::::strict_sub` which can panic + --> tests/ui/dyn_trait.rs:21:17 + | +LL | let _ = 2u64.strict_sub(500); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: 3 different panics from this location + = note: backtrace (1 of 2): + 0: std::rt::panic_fmt (called) + 1: core::num::imp::overflow_panic::sub (called) + 2: core::num::::strict_sub (called) <-- + 3: ::test (possible vtable resolution) + 4: ::test - virtual#3 (called) + 5: dyn_trait::main (entrypoint) + +error: call to function `std::rt::panic_fmt` which is a panic + --> tests/ui/dyn_trait.rs:3:9 + | +LL | panic!("Default impl") + | ^^^^^^^^^^^^^^^^^^^^^^ expanded from here + | + = note: this location is called in 3 different traces + = note: 3 different panics from this location + = note: backtrace (1 of 3): + 0: std::rt::panic_fmt (called) <-- + 1: ::test (possible vtable resolution) + 2: ::test - virtual#3 (called) + 3: dyn_trait::main (entrypoint) + +error: aborting due to 3 previous errors + diff --git a/tests/ui/extern_c.rs b/tests/ui/extern_c.rs new file mode 100644 index 0000000..3cf0871 --- /dev/null +++ b/tests/ui/extern_c.rs @@ -0,0 +1,15 @@ +#[unsafe(no_mangle)] +pub unsafe extern "C" fn also_entrypoint() { + let _ = 2u64.strict_add(500); + //~^ panics +} + +#[unsafe(export_name = "rust_fn_that_panics")] +pub unsafe extern "C" fn call_me_outside() { + let _ = 2u64.strict_add(500); + //~^ panics +} + +pub unsafe extern "C" fn uncallable_so_no_panic() { + let _ = 2u64.strict_add(500); +} diff --git a/tests/ui/extern_c.stderr b/tests/ui/extern_c.stderr new file mode 100644 index 0000000..ce61b65 --- /dev/null +++ b/tests/ui/extern_c.stderr @@ -0,0 +1,31 @@ +error: call to method `core::num::::strict_add` which can panic + --> tests/ui/extern_c.rs:3:13 + | +LL | let _ = 2u64.strict_add(500); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: this location is called in 2 different traces + = note: 3 different panics from this location + = note: backtrace (1 of 4): + 0: std::rt::panic_fmt (called) + 1: core::num::imp::overflow_panic::add (called) + 2: core::num::::strict_add (called) <-- + 3: extern_c::also_entrypoint (entrypoint) + = note: `#[deny(cga::panics)]` on by default + +error: call to method `core::num::::strict_add` which can panic + --> tests/ui/extern_c.rs:9:13 + | +LL | let _ = 2u64.strict_add(500); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: this location is called in 2 different traces + = note: 3 different panics from this location + = note: backtrace (1 of 4): + 0: std::rt::panic_fmt (called) + 1: core::num::imp::overflow_panic::add (called) + 2: core::num::::strict_add (called) <-- + 3: extern_c::call_me_outside (entrypoint) + +error: aborting due to 2 previous errors + diff --git a/tests/ui/fn_ptr.rs b/tests/ui/fn_ptr.rs new file mode 100644 index 0000000..630ad6c --- /dev/null +++ b/tests/ui/fn_ptr.rs @@ -0,0 +1,11 @@ +fn test() { + let _ = 2u64.strict_add(500); + //~^ panics +} + +fn main() { + let _ptr = test as *const fn(); + + let _ptr = u64::strict_add as *const fn(u64) -> u64; + //~^ panics +} diff --git a/tests/ui/fn_ptr.stderr b/tests/ui/fn_ptr.stderr new file mode 100644 index 0000000..1779f48 --- /dev/null +++ b/tests/ui/fn_ptr.stderr @@ -0,0 +1,34 @@ +error: possible later call to method `core::num::::strict_add` which can panic + --> tests/ui/fn_ptr.rs:9:16 + | +LL | let _ptr = u64::strict_add as *const fn(u64) -> u64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this location is called in 2 different traces + = note: 3 different panics from this location + = note: once a function is cast to a function pointer, cga can no longer tell how it is used + = note: as a precaution, it must assume you will eventually call the function + = note: backtrace (1 of 4): + 0: std::rt::panic_fmt (called) + 1: core::num::imp::overflow_panic::add (called) + 2: core::num::::strict_add (fn-ptr cast) <-- + 3: fn_ptr::main (entrypoint) + = note: `#[deny(cga::panics)]` on by default + +error: call to method `core::num::::strict_add` which can panic + --> tests/ui/fn_ptr.rs:2:13 + | +LL | let _ = 2u64.strict_add(500); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: this location is called in 2 different traces + = note: 3 different panics from this location + = note: backtrace (1 of 4): + 0: std::rt::panic_fmt (called) + 1: core::num::imp::overflow_panic::add (called) + 2: core::num::::strict_add (called) <-- + 3: fn_ptr::test (fn-ptr cast) + 4: fn_ptr::main (entrypoint) + +error: aborting due to 2 previous errors + diff --git a/tests/ui/large_std_process.rs b/tests/ui/large_std_process.rs new file mode 100644 index 0000000..e5d9c61 --- /dev/null +++ b/tests/ui/large_std_process.rs @@ -0,0 +1,9 @@ +fn test() { + let _ = std::process::Command::new("evil"); + //~^ panics + //~^^ panics +} + +fn main() { + test(); +} diff --git a/tests/ui/large_std_process.stderr b/tests/ui/large_std_process.stderr new file mode 100644 index 0000000..b4d5c1e --- /dev/null +++ b/tests/ui/large_std_process.stderr @@ -0,0 +1,40 @@ +error: call to associated function `std::process::Command::new` which can panic + --> tests/ui/large_std_process.rs:2:13 + | +LL | let _ = std::process::Command::new("evil"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: 14 different panics from this location + = note: backtrace (1 of 3843059837): + 0: std::rt::panic_fmt (called) + 1: alloc::raw_vec::RawVec::::into_box (called) + 2: std::vec::Vec::::into_boxed_slice (called) + 3: as std::clone::Clone>::clone (called) + 4: ::clone (called) + 5: std::sys::process::unix::common::Command::new (called) + 6: std::process::Command::new::<&str> (called) <-- + 7: large_std_process::test (called) + 8: large_std_process::main (entrypoint) + = note: `#[deny(cga::panics)]` on by default + +error: call to drop of `std::process::Command` which can panic + --> tests/ui/large_std_process.rs:2:47 + | +LL | let _ = std::process::Command::new("evil"); + | ^ + | + = note: 14 different panics from this location + = note: backtrace (1 of 3843090268): + 0: core::panicking::panic_nounwind_fmt (called) + 1: >::get_unchecked_mut::precondition_check (called) + 2: >::get_unchecked_mut (called) + 3: core::slice::::get_unchecked_mut:: (called) + 4: ::drop (called) + 5: drop of `std::ffi::CString` (called) + 6: drop of `std::sys::process::unix::common::Command` (called) + 7: drop of `std::process::Command` (called) <-- + 8: large_std_process::test (called) + 9: large_std_process::main (entrypoint) + +error: aborting due to 2 previous errors + diff --git a/tests/ui/simple.rs b/tests/ui/simple.rs new file mode 100644 index 0000000..1419869 --- /dev/null +++ b/tests/ui/simple.rs @@ -0,0 +1,11 @@ +fn test() { + let _ = 2u64.strict_add(500); + //~^ panics +} + +fn main() { + // Do not emit three diagnostics, but instead note that the site is called 3 different ways. + test(); + test(); + test(); +} diff --git a/tests/ui/simple.stderr b/tests/ui/simple.stderr new file mode 100644 index 0000000..2002320 --- /dev/null +++ b/tests/ui/simple.stderr @@ -0,0 +1,18 @@ +error: call to method `core::num::::strict_add` which can panic + --> tests/ui/simple.rs:2:13 + | +LL | let _ = 2u64.strict_add(500); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: this location is called in 3 different traces + = note: 3 different panics from this location + = note: backtrace (1 of 6): + 0: std::rt::panic_fmt (called) + 1: core::num::imp::overflow_panic::add (called) + 2: core::num::::strict_add (called) <-- + 3: simple::test (called) + 4: simple::main (entrypoint) + = note: `#[deny(cga::panics)]` on by default + +error: aborting due to 1 previous error +