diff --git a/.github/labeler.yml b/.github/labeler.yml index 1195ee4a14d2..9ef0a5a3c742 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -5,9 +5,20 @@ # # Note that we enable dot, so "**" matches all files in a folder -Z-BenchCI: +Z-EndToEndBenchCI: - any: - changed-files: - - any-glob-to-any-file: ['kani-compiler/**', 'kani-driver/src/call-*', 'cprover_bindings/**', 'library/**'] + - any-glob-to-any-file: ['kani-compiler/**', 'kani-driver/src/call_*', 'cprover_bindings/**', 'library/**'] - any-glob-to-any-file: ['rust-toolchain.toml', 'Cargo.lock'] - any-glob-to-any-file: ['kani-dependencies'] + +Z-CompilerBenchCI: +- any: + # we want to run compiler benchmarks if: + - changed-files: + # any parts of the compiler change + - any-glob-to-any-file: ['kani-compiler/**', 'cprover_bindings/**', 'library/**'] + # the way we call the compiler changes + - any-glob-to-any-file: ['kani-driver/src/call_cargo.rs', 'kani-driver/src/call_single_file.rs'] + # or if our dependencies change + - any-glob-to-any-file: ['rust-toolchain.toml', 'Cargo.lock'] \ No newline at end of file diff --git a/.github/workflows/bench-compiler.yml b/.github/workflows/bench-compiler.yml new file mode 100644 index 000000000000..c7dbe7d88747 --- /dev/null +++ b/.github/workflows/bench-compiler.yml @@ -0,0 +1,140 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# Run performance benchmarks comparing the compiler performance of two different Kani versions. +name: Kani Compiler Performance Benchmarks +on: + push: + branches: + - 'main' + workflow_call: + +jobs: + compile-timer-short: + runs-on: ubuntu-24.04 + steps: + - name: Save push event HEAD and HEAD~ to environment variables + if: ${{ github.event_name == 'push' }} + run: | + echo "NEW_REF=${{ github.event.after}}" | tee -a "$GITHUB_ENV" + echo "OLD_REF=${{ github.event.before }}" | tee -a "$GITHUB_ENV" + - name: Save pull request HEAD and base to environment variables + if: ${{ contains(fromJSON('["pull_request", "pull_request_target"]'), github.event_name) }} + run: | + echo "OLD_REF=${{ github.event.pull_request.base.sha }}" | tee -a "$GITHUB_ENV" + echo "NEW_REF=${{ github.event.pull_request.head.sha }}" | tee -a "$GITHUB_ENV" + - name: Check out Kani (old variant) + uses: actions/checkout@v4 + with: + path: ./old + ref: ${{ env.OLD_REF }} + fetch-depth: 2 + + - name: Check out Kani (new variant) + uses: actions/checkout@v4 + with: + path: ./new + ref: ${{ env.NEW_REF }} + fetch-depth: 1 + + - name: Set up Kani Dependencies (old variant) + uses: ./old/.github/actions/setup + with: + os: ubuntu-24.04 + kani_dir: old + + - name: Set up Kani Dependencies (new variant) + uses: ./new/.github/actions/setup + with: + os: ubuntu-24.04 + kani_dir: new + + - name: Copy benchmarks from new to old + run: rm -rf ./old/tests/perf ; cp -r ./new/tests/perf ./old/tests/ + + - name: Build `compile-timer` in old + run: cd old/tools/compile-timer && cargo build --release + - name: Build `kani` in old + run: cd old && cargo build-dev --release + + - name: Build `compile-timer` in new + run: cd new/tools/compile-timer && cargo build --release + - name: Build `kani` in new + run: cd new && cargo build-dev --release + + - name: Run `compile-timer` on old + run: | + export PATH="${{ github.workspace }}/old/scripts:$PATH" + cd old/tests/perf && ../../target/release/compile-timer --out-path compile-times-old.json --ignore kani-lib --ignore display_trait --ignore s2n-quic + - name: Run `compile-timer` on new + run: | + export PATH="${{ github.workspace }}/new/scripts:$PATH" + cd new/tests/perf && ../../target/release/compile-timer --out-path compile-times-new.json --ignore kani-lib --ignore display_trait --ignore s2n-quic + - name: Run analysis between the two + run: ./new/target/release/compile-analyzer --path-pre old/tests/perf/compile-times-old.json --path-post new/tests/perf/compile-times-new.json --only-markdown --suite-name short >> "$GITHUB_STEP_SUMMARY" + + compile-timer-long: + runs-on: ubuntu-24.04 + steps: + - name: Save push event HEAD and HEAD~ to environment variables + if: ${{ github.event_name == 'push' }} + run: | + echo "NEW_REF=${{ github.event.after}}" | tee -a "$GITHUB_ENV" + echo "OLD_REF=${{ github.event.before }}" | tee -a "$GITHUB_ENV" + - name: Save pull request HEAD and base to environment variables + if: ${{ contains(fromJSON('["pull_request", "pull_request_target"]'), github.event_name) }} + run: | + echo "OLD_REF=${{ github.event.pull_request.base.sha }}" | tee -a "$GITHUB_ENV" + echo "NEW_REF=${{ github.event.pull_request.head.sha }}" | tee -a "$GITHUB_ENV" + + - name: Check out Kani (old variant) + uses: actions/checkout@v4 + with: + path: ./old + ref: ${{ env.OLD_REF }} + fetch-depth: 2 + + - name: Check out Kani (new variant) + uses: actions/checkout@v4 + with: + path: ./new + ref: ${{ env.NEW_REF }} + fetch-depth: 1 + + - name: Set up Kani Dependencies (old variant) + uses: ./old/.github/actions/setup + with: + os: ubuntu-24.04 + kani_dir: old + + - name: Set up Kani Dependencies (new variant) + uses: ./new/.github/actions/setup + with: + os: ubuntu-24.04 + kani_dir: new + + # Ensures that a PR changing the benchmarks will have the new benchmarks run + # for both commits. + - name: Copy benchmarks from new to old + run: rm -rf ./old/tests/perf ; cp -r ./new/tests/perf ./old/tests/ + + - name: Build `compile-timer` in old + run: cd old/tools/compile-timer && cargo build --release + - name: Build `kani` in old + run: cd old && cargo build-dev --release + + - name: Build `compile-timer` in new + run: cd new/tools/compile-timer && cargo build --release + - name: Build `kani` in new + run: cd new && cargo build-dev --release + + - name: Run `compile-timer` on old + run: | + export PATH="${{ github.workspace }}/old/scripts:$PATH" + cd old/tests/perf/s2n-quic && ../../../target/release/compile-timer --out-path compile-times-old.json --also-visit quic/s2n-quic-core --also-visit quic/s2n-quic-platform --also-visit common/s2n-codec --skip-current + - name: Run `compile-timer` on new + run: | + export PATH="${{ github.workspace }}/new/scripts:$PATH" + cd new/tests/perf/s2n-quic && ../../../target/release/compile-timer --out-path compile-times-new.json --also-visit quic/s2n-quic-core --also-visit quic/s2n-quic-platform --also-visit common/s2n-codec --skip-current + - name: Run analysis between the two + run: ./new/target/release/compile-analyzer --path-pre old/tests/perf/s2n-quic/compile-times-old.json --path-post new/tests/perf/s2n-quic/compile-times-new.json --only-markdown --suite-name long >> "$GITHUB_STEP_SUMMARY" \ No newline at end of file diff --git a/.github/workflows/bench.yml b/.github/workflows/bench-e2e.yml similarity index 98% rename from .github/workflows/bench.yml rename to .github/workflows/bench-e2e.yml index dcd5b5004f2f..954b84c727c4 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench-e2e.yml @@ -6,7 +6,7 @@ # This workflow will run when: # - Changes are pushed to 'main'. # - Triggered by another workflow -name: Kani Performance Benchmarks +name: Kani End-To-End Performance Benchmarks on: push: branches: diff --git a/.github/workflows/cbmc-update.yml b/.github/workflows/cbmc-update.yml index 8d0bc057da2f..927dde0037be 100644 --- a/.github/workflows/cbmc-update.yml +++ b/.github/workflows/cbmc-update.yml @@ -75,7 +75,7 @@ jobs: Upgrade CBMC to its latest release. - name: Create Issue - if: ${{ env.next_step == 'create_issue' }} + if: ${{ env.next_step == 'create_issue' && github.repository_owner == 'model-checking' }} uses: dacbd/create-issue-action@main with: token: ${{ github.token }} diff --git a/.github/workflows/extra_jobs.yml b/.github/workflows/extra_jobs.yml index ddf242ba3956..416fe94072db 100644 --- a/.github/workflows/extra_jobs.yml +++ b/.github/workflows/extra_jobs.yml @@ -43,9 +43,16 @@ jobs: with: dot: true - verification-bench: - name: Verification Benchmarks + end-to-end-bench: + name: End-to-End Benchmarks needs: auto-label permissions: {} - if: ${{ contains(needs.auto-label.outputs.all-labels, 'Z-BenchCI') && github.event_name != 'merge_group' }} - uses: ./.github/workflows/bench.yml + if: ${{ contains(needs.auto-label.outputs.all-labels, 'Z-EndToEndBenchCI') && github.event_name != 'merge_group' }} + uses: ./.github/workflows/bench-e2e.yml + + compiler-bench: + name: Compiler Benchmarks + needs: auto-label + permissions: {} + if: ${{ contains(needs.auto-label.outputs.all-labels, 'Z-CompilerBenchCI') && github.event_name != 'merge_group' }} + uses: ./.github/workflows/bench-compiler.yml diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml index c75b457073b8..16afb80a1b5e 100644 --- a/.github/workflows/format-check.yml +++ b/.github/workflows/format-check.yml @@ -47,7 +47,8 @@ jobs: - name: 'Run Clippy' run: | - cargo clippy --workspace -- -D warnings + cargo clippy --workspace --tests -- -D warnings + RUSTFLAGS="--cfg=kani_sysroot" cargo clippy --workspace -- -D warnings - name: 'Print Clippy Statistics' run: | diff --git a/.github/workflows/kani.yml b/.github/workflows/kani.yml index 1c8cd119c393..5892ba13d26a 100644 --- a/.github/workflows/kani.yml +++ b/.github/workflows/kani.yml @@ -70,6 +70,23 @@ jobs: env: RUST_TEST_THREADS: 1 + llbc-regression: + runs-on: ubuntu-24.04 + steps: + - name: Checkout Kani + uses: actions/checkout@v4 + + - name: Setup Kani Dependencies + uses: ./.github/actions/setup + with: + os: ubuntu-24.04 + + - name: Build Kani with Charon + run: cargo build-dev -- --features cprover --features llbc + + - name: Run tests + run: ./scripts/kani-llbc-regression.sh + documentation: runs-on: ubuntu-24.04 permissions: diff --git a/.github/workflows/toolchain-upgrade.yml b/.github/workflows/toolchain-upgrade.yml index 8a7f448ca84e..c242cae0d4cf 100644 --- a/.github/workflows/toolchain-upgrade.yml +++ b/.github/workflows/toolchain-upgrade.yml @@ -68,7 +68,7 @@ jobs: }) - name: Create Issue - if: ${{ env.next_step == 'create_issue' }} + if: ${{ env.next_step == 'create_issue' && github.repository_owner == 'model-checking' }} uses: dacbd/create-issue-action@main with: token: ${{ github.token }} diff --git a/CHANGELOG.md b/CHANGELOG.md index d205f8af8e31..b8b95786ec9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,51 @@ This file contains notable changes (e.g. breaking changes, major changes, etc.) This file was introduced starting Kani 0.23.0, so it only contains changes from version 0.23.0 onwards. +## [0.62.0] + +### What's Changed +* Disable llbc feature by default by @zhassan-aws in https://github.com/model-checking/kani/pull/3980 +* Add an option to skip codegen by @zhassan-aws in https://github.com/model-checking/kani/pull/4002 +* Add support for loop-contract historic values by @thanhnguyen-aws in https://github.com/model-checking/kani/pull/3951 +* Clarify Rust intrinsic assumption error message by @carolynzech in https://github.com/model-checking/kani/pull/4015 +* Autoharness: enable function-contracts and loop-contracts features by default by @carolynzech in https://github.com/model-checking/kani/pull/4016 +* Autoharness: Harness Generation Improvements by @carolynzech in https://github.com/model-checking/kani/pull/4017 +* Add support for Loop-loops by @thanhnguyen-aws in https://github.com/model-checking/kani/pull/4011 +* Clarify installation instructions by @zhassan-aws in https://github.com/model-checking/kani/pull/4023 +* Fix the bug of while loop invariant contains no local variables by @thanhnguyen-aws in https://github.com/model-checking/kani/pull/4022 +* List Subcommand: include crate name by @carolynzech in https://github.com/model-checking/kani/pull/4024 +* Autoharness: Update Filtering Options by @carolynzech in https://github.com/model-checking/kani/pull/4025 +* Introduce BoundedArbitrary trait and macro for bounded proofs by @sgpthomas in https://github.com/model-checking/kani/pull/4000 +* Support `trait_upcasting` by @clubby789 in https://github.com/model-checking/kani/pull/4001 +* Analyze unsafe code reachability by @carolynzech in https://github.com/model-checking/kani/pull/4037 +* Scanner: log crate-level visibility of functions by @tautschnig in https://github.com/model-checking/kani/pull/4041 +* Autoharness: exit code 1 upon harness failure by @carolynzech in https://github.com/model-checking/kani/pull/4043 +* Overflow operators can also be used with vectors by @tautschnig in https://github.com/model-checking/kani/pull/4049 +* Remove bool typedef by @zhassan-aws in https://github.com/model-checking/kani/pull/4058 +* Update CBMC dependency to 6.6.0 by @qinheping in https://github.com/model-checking/kani/pull/4050 +* Automatic toolchain upgrade to nightly-2025-04-24 by @zhassan-aws in https://github.com/model-checking/kani/pull/4042 + +## New Contributors +* @sgpthomas made their first contribution in https://github.com/model-checking/kani/pull/4000 +* @clubby789 made their first contribution in https://github.com/model-checking/kani/pull/4001 + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.61.0...kani-0.62.0 + +## [0.61.0] + +### What's Changed +* Make `is_inbounds` public by @rajath-mk in https://github.com/model-checking/kani/pull/3958 +* Finish adding support for `f16` and `f128` by @carolynzech in https://github.com/model-checking/kani/pull/3943 +* Support user overrides of Rust built-ins by @tautschnig in https://github.com/model-checking/kani/pull/3945 +* Add support for anonymous nested statics by @carolynzech in https://github.com/model-checking/kani/pull/3953 +* Add support for struct field access in loop contracts by @thanhnguyen-aws in https://github.com/model-checking/kani/pull/3970 +* Autoharness: Don't panic on `_` argument by @carolynzech in https://github.com/model-checking/kani/pull/3942 +* Autoharness: improve metdata printed to terminal and enable standard library application by @carolynzech in https://github.com/model-checking/kani/pull/3948, https://github.com/model-checking/kani/pull/3952, https://github.com/model-checking/kani/pull/3971 +* Upgrade toolchain to nightly-2025-04-03 by @qinheping, @tautschnig, @zhassan-aws, @carolynzech in https://github.com/model-checking/kani/pull/3988 +* Update CBMC dependency to 6.5.0 by @tautschnig in https://github.com/model-checking/kani/pull/3936 + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.60.0...kani-0.61.0 + ## [0.60.0] ### Breaking Changes diff --git a/Cargo.lock b/Cargo.lock index d694071f54d4..00aecfa95664 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,12 +19,12 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.15", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arrayvec" @@ -113,9 +113,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "assert_cmd" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" +checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" dependencies = [ "anstyle", "bstr", @@ -135,9 +135,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -150,9 +150,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "brownstone" @@ -165,9 +165,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "regex-automata 0.4.9", @@ -176,7 +176,7 @@ dependencies = [ [[package]] name = "build-kani" -version = "0.60.0" +version = "0.62.0" dependencies = [ "anyhow", "cargo_metadata", @@ -224,9 +224,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.17" +version = "1.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" dependencies = [ "shlex", ] @@ -276,9 +276,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.34" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" dependencies = [ "clap_builder", "clap_derive", @@ -286,9 +286,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.34" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" dependencies = [ "anstream", "anstyle", @@ -341,6 +341,15 @@ dependencies = [ "unicode-width 0.2.0", ] +[[package]] +name = "compile-timer" +version = "0.1.0" +dependencies = [ + "clap", + "serde", + "serde_json", +] + [[package]] name = "compiletest" version = "0.0.0" @@ -381,7 +390,7 @@ dependencies = [ [[package]] name = "cprover_bindings" -version = "0.60.0" +version = "0.62.0" dependencies = [ "lazy_static", "linear-map", @@ -571,9 +580,9 @@ checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" [[package]] name = "env_logger" -version = "0.11.7" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", @@ -590,9 +599,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.59.0", @@ -639,20 +648,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", @@ -693,9 +691,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "foldhash", ] @@ -748,12 +746,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.3", ] [[package]] @@ -794,9 +792,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.5" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260" +checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" dependencies = [ "jiff-static", "log", @@ -807,9 +805,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.5" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c" +checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" dependencies = [ "proc-macro2", "quote", @@ -824,7 +822,7 @@ checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" [[package]] name = "kani" -version = "0.60.0" +version = "0.62.0" dependencies = [ "kani_core", "kani_macros", @@ -832,7 +830,7 @@ dependencies = [ [[package]] name = "kani-compiler" -version = "0.60.0" +version = "0.62.0" dependencies = [ "charon", "clap", @@ -868,7 +866,7 @@ dependencies = [ [[package]] name = "kani-driver" -version = "0.60.0" +version = "0.62.0" dependencies = [ "anyhow", "cargo_metadata", @@ -897,7 +895,7 @@ dependencies = [ [[package]] name = "kani-verifier" -version = "0.60.0" +version = "0.62.0" dependencies = [ "anyhow", "home", @@ -906,14 +904,14 @@ dependencies = [ [[package]] name = "kani_core" -version = "0.60.0" +version = "0.62.0" dependencies = [ "kani_macros", ] [[package]] name = "kani_macros" -version = "0.60.0" +version = "0.62.0" dependencies = [ "proc-macro-error2", "proc-macro2", @@ -923,7 +921,7 @@ dependencies = [ [[package]] name = "kani_metadata" -version = "0.60.0" +version = "0.62.0" dependencies = [ "clap", "cprover_bindings", @@ -940,9 +938,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "linear-map" @@ -962,9 +960,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "lock_api" @@ -1020,9 +1018,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] @@ -1185,9 +1183,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "os_info" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a604e53c24761286860eba4e2c8b23a0161526476b1de520139d69cdb85a6b5" +checksum = "41fc863e2ca13dc2d5c34fb22ea4a588248ac14db929616ba65c45f21744b1e9" dependencies = [ "log", "windows-sys 0.52.0", @@ -1326,18 +1324,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "psm" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58e5423e24c18cc840e1c98370b3993c6649cd1678b4d24318bcf0a083cbe88" +checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" dependencies = [ "cc", ] @@ -1379,9 +1377,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.10" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ "bitflags", ] @@ -1460,14 +1458,14 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.3" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.9.3", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] @@ -1619,24 +1617,24 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "stacker" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601f9201feb9b09c00266478bf459952b9ef9a6b94edb2f21eba14ab681a60a9" +checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" dependencies = [ "cc", "cfg-if", @@ -1647,7 +1645,7 @@ dependencies = [ [[package]] name = "std" -version = "0.60.0" +version = "0.62.0" dependencies = [ "kani", ] @@ -1664,7 +1662,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23de088478b31c349c9ba67816fa55d9355232d63c3afea8bf513e31f0f1d2c0" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.3", "serde", ] @@ -1695,9 +1693,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -1712,14 +1710,14 @@ checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" [[package]] name = "tempfile" -version = "3.19.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom", "once_cell", - "rustix 1.0.3", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -1838,9 +1836,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.20" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", "serde_spanned", @@ -1850,26 +1848,33 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + [[package]] name = "tracing" version = "0.1.41" @@ -1971,9 +1976,9 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.25.3" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ac5ea5e7f2f1700842ec071401010b9c59bf735295f6e9fa079c3dc035b167" +checksum = "69aff09fea9a41fb061ae6b206cb87cac1b8db07df31be3ba271fbc26760f213" dependencies = [ "cc", "regex", @@ -2083,13 +2088,13 @@ dependencies = [ [[package]] name = "which" -version = "7.0.2" +version = "7.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2774c861e1f072b3aadc02f8ba886c26ad6321567ecc294c935434cad06f1283" +checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" dependencies = [ "either", "env_home", - "rustix 0.38.44", + "rustix 1.0.7", "winsafe", ] @@ -2208,9 +2213,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.4" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] @@ -2232,18 +2237,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index beabf68f6e53..d14ad335241a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-verifier" -version = "0.60.0" +version = "0.62.0" edition = "2021" description = "A bit-precise model checker for Rust." readme = "README.md" @@ -42,6 +42,7 @@ members = [ "tools/build-kani", "tools/kani-cov", "tools/scanner", + "tools/compile-timer", "kani-driver", "kani-compiler", "kani_metadata", @@ -73,6 +74,8 @@ exclude = [ "tests/script-based-pre/build-cache-dirty/target/new_dep", "tests/script-based-pre/verify_std_cmd/tmp_dir/target/kani_verify_std", "tests/script-based-pre/kani_lib_dep", + "tests/script-based-pre/no_codegen", + "tests/script-based-pre/no_codegen_error", ] [workspace.lints.clippy] diff --git a/cprover_bindings/Cargo.toml b/cprover_bindings/Cargo.toml index 9024dc3fb388..641d7862d776 100644 --- a/cprover_bindings/Cargo.toml +++ b/cprover_bindings/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "cprover_bindings" -version = "0.60.0" +version = "0.62.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/cprover_bindings/src/goto_program/expr.rs b/cprover_bindings/src/goto_program/expr.rs index 6f140b67ec84..469df299ee5e 100644 --- a/cprover_bindings/src/goto_program/expr.rs +++ b/cprover_bindings/src/goto_program/expr.rs @@ -177,6 +177,14 @@ pub enum ExprValue { Vector { elems: Vec, }, + Forall { + variable: Expr, // symbol + domain: Expr, // where + }, + Exists { + variable: Expr, // symbol + domain: Expr, // where + }, } /// Binary operators. The names are the same as in the Irep representation. @@ -972,6 +980,16 @@ impl Expr { let typ = typ.aggr_tag().unwrap(); expr!(Union { value, field }, typ) } + + pub fn forall_expr(typ: Type, variable: Expr, domain: Expr) -> Expr { + assert!(variable.is_symbol()); + expr!(Forall { variable, domain }, typ) + } + + pub fn exists_expr(typ: Type, variable: Expr, domain: Expr) -> Expr { + assert!(variable.is_symbol()); + expr!(Exists { variable, domain }, typ) + } } /// Constructors for Binary Operations @@ -1017,11 +1035,12 @@ impl Expr { IeeeFloatEqual | IeeeFloatNotequal => lhs.typ == rhs.typ && lhs.typ.is_floating_point(), // Overflow flags OverflowMinus | OverflowResultMinus => { - (lhs.typ == rhs.typ && (lhs.typ.is_pointer() || lhs.typ.is_numeric())) + (lhs.typ == rhs.typ + && (lhs.typ.is_pointer() || lhs.typ.is_numeric() || lhs.typ.is_vector())) || (lhs.typ.is_pointer() && rhs.typ.is_integer()) } OverflowMult | OverflowPlus | OverflowResultMult | OverflowResultPlus => { - (lhs.typ == rhs.typ && lhs.typ.is_integer()) + (lhs.typ == rhs.typ && (lhs.typ.is_numeric() || lhs.typ.is_vector())) || (lhs.typ.is_pointer() && rhs.typ.is_integer()) } ROk => lhs.typ.is_pointer() && rhs.typ.is_c_size_t(), diff --git a/cprover_bindings/src/goto_program/symbol.rs b/cprover_bindings/src/goto_program/symbol.rs index 5c86dd04909a..bb40ed9ed9d9 100644 --- a/cprover_bindings/src/goto_program/symbol.rs +++ b/cprover_bindings/src/goto_program/symbol.rs @@ -172,6 +172,10 @@ impl Symbol { } } + pub fn update(&mut self, value: SymbolValues) { + self.value = value; + } + /// Add this contract to the symbol (symbol must be a function) or fold the /// conditions into an existing contract. pub fn attach_contract(&mut self, contract: FunctionContract) { diff --git a/cprover_bindings/src/goto_program/symbol_table.rs b/cprover_bindings/src/goto_program/symbol_table.rs index 97567670dee0..b66eb581e3cb 100644 --- a/cprover_bindings/src/goto_program/symbol_table.rs +++ b/cprover_bindings/src/goto_program/symbol_table.rs @@ -10,13 +10,18 @@ use std::collections::BTreeMap; #[derive(Clone, Debug)] pub struct SymbolTable { symbol_table: BTreeMap, + parameters_map: BTreeMap>, machine_model: MachineModel, } /// Constructors impl SymbolTable { pub fn new(machine_model: MachineModel) -> SymbolTable { - let mut symtab = SymbolTable { machine_model, symbol_table: BTreeMap::new() }; + let mut symtab = SymbolTable { + machine_model, + symbol_table: BTreeMap::new(), + parameters_map: BTreeMap::new(), + }; env::machine_model_symbols(symtab.machine_model()) .into_iter() .for_each(|s| symtab.insert(s)); @@ -54,6 +59,19 @@ impl SymbolTable { self.symbol_table.insert(symbol.name, symbol); } + /// Inserts a parameter into the parameters map for a given function symbol. + /// If the function does not exist in the parameters map, it initializes it with an empty vector. + pub fn insert_parameter, P: Into>( + &mut self, + function_name: T, + parameter: P, + ) { + let function_name = function_name.into(); + let parameter = parameter.into(); + + self.parameters_map.entry(function_name).or_default().push(parameter); + } + /// Validates the previous value of the symbol using the validator function, then replaces it. /// Useful to replace declarations with the actual definition. pub fn replace) -> bool>( @@ -102,6 +120,10 @@ impl SymbolTable { self.symbol_table.iter() } + pub fn iter_mut(&mut self) -> std::collections::btree_map::IterMut<'_, InternedString, Symbol> { + self.symbol_table.iter_mut() + } + pub fn lookup>(&self, name: T) -> Option<&Symbol> { let name = name.into(); self.symbol_table.get(&name) @@ -112,6 +134,14 @@ impl SymbolTable { self.symbol_table.get_mut(&name) } + pub fn lookup_parameters>( + &self, + name: T, + ) -> Option<&Vec> { + let name = name.into(); + self.parameters_map.get(&name) + } + pub fn machine_model(&self) -> &MachineModel { &self.machine_model } diff --git a/cprover_bindings/src/goto_program/typ.rs b/cprover_bindings/src/goto_program/typ.rs index 5a0ad76b81c6..df1503b60201 100644 --- a/cprover_bindings/src/goto_program/typ.rs +++ b/cprover_bindings/src/goto_program/typ.rs @@ -1427,69 +1427,6 @@ impl Type { } types } - - /// Generate a string which uniquely identifies the given type - /// while also being a valid variable/funcion name - pub fn to_identifier(&self) -> String { - // Use String instead of InternedString, since we don't want to intern temporaries. - match self { - Type::Array { typ, size } => { - format!("array_of_{size}_{}", typ.to_identifier()) - } - Type::Bool => "bool".to_string(), - Type::CBitField { width, typ } => { - format!("cbitfield_of_{width}_{}", typ.to_identifier()) - } - Type::CInteger(int_kind) => format!("c_int_{int_kind:?}"), - // e.g. `int my_func(double x, float_y) {` - // => "code_from_double_float_to_int" - Type::Code { parameters, return_type } => { - let parameter_string = parameters - .iter() - .map(|param| param.typ().to_identifier()) - .collect::>() - .join("_"); - let return_string = return_type.to_identifier(); - format!("code_from_{parameter_string}_to_{return_string}") - } - Type::Constructor => "constructor".to_string(), - Type::Double => "double".to_string(), - Type::Empty => "empty".to_string(), - Type::FlexibleArray { typ } => format!("flexarray_of_{}", typ.to_identifier()), - Type::Float => "float".to_string(), - Type::Float16 => "float16".to_string(), - Type::Float128 => "float128".to_string(), - Type::IncompleteStruct { tag } => tag.to_string(), - Type::IncompleteUnion { tag } => tag.to_string(), - Type::InfiniteArray { typ } => { - format!("infinite_array_of_{}", typ.to_identifier()) - } - Type::Integer => "integer".to_string(), - Type::Pointer { typ } => format!("pointer_to_{}", typ.to_identifier()), - Type::Signedbv { width } => format!("signed_bv_{width}"), - Type::Struct { tag, .. } => format!("struct_{tag}"), - Type::StructTag(tag) => format!("struct_tag_{tag}"), - Type::TypeDef { name: tag, .. } => format!("type_def_{tag}"), - Type::Union { tag, .. } => format!("union_{tag}"), - Type::UnionTag(tag) => format!("union_tag_{tag}"), - Type::Unsignedbv { width } => format!("unsigned_bv_{width}"), - // e.g. `int my_func(double x, float_y, ..) {` - // => "variadic_code_from_double_float_to_int" - Type::VariadicCode { parameters, return_type } => { - let parameter_string = parameters - .iter() - .map(|param| param.typ().to_identifier()) - .collect::>() - .join("_"); - let return_string = return_type.to_identifier(); - format!("variadic_code_from_{parameter_string}_to_{return_string}") - } - Type::Vector { typ, size } => { - let typ = typ.to_identifier(); - format!("vec_of_{size}_{typ}") - } - } - } } #[cfg(test)] @@ -1509,14 +1446,6 @@ mod type_tests { assert_eq!(type_def.type_name().unwrap().to_string(), format!("tag-{NAME}")); } - #[test] - fn check_typedef_identifier() { - let type_def = Bool.to_typedef(NAME); - let id = type_def.to_identifier(); - assert!(id.ends_with(NAME)); - assert!(id.starts_with("type_def")); - } - #[test] fn check_typedef_create() { assert!(matches!(Bool.to_typedef(NAME), TypeDef { .. })); diff --git a/cprover_bindings/src/irep/goto_binary_serde.rs b/cprover_bindings/src/irep/goto_binary_serde.rs index 56578cc87aa4..8bc036b3c07f 100644 --- a/cprover_bindings/src/irep/goto_binary_serde.rs +++ b/cprover_bindings/src/irep/goto_binary_serde.rs @@ -275,7 +275,7 @@ impl IrepNumbering { return self.inv_cache.index[*number]; } // This is where the key gets its unique number assigned. - let number = self.inv_cache.add_key(&key); + let number = self.inv_cache.add_key(key); self.cache.insert(key.clone(), number); self.inv_cache.index[number] } @@ -449,17 +449,17 @@ where self.write_usize_varenc(num); if self.is_first_write_irep(num) { - let id = &self.numbering.id(&irep); + let id = &self.numbering.id(irep); self.write_numbered_string_ref(id); - for sub_idx in 0..(self.numbering.nof_sub(&irep)) { + for sub_idx in 0..(self.numbering.nof_sub(irep)) { self.write_u8(b'S'); - self.write_numbered_irep_ref(&self.numbering.sub(&irep, sub_idx)); + self.write_numbered_irep_ref(&self.numbering.sub(irep, sub_idx)); } - for named_sub_idx in 0..(self.numbering.nof_named_sub(&irep)) { + for named_sub_idx in 0..(self.numbering.nof_named_sub(irep)) { self.write_u8(b'N'); - let (k, v) = self.numbering.named_sub(&irep, named_sub_idx); + let (k, v) = self.numbering.named_sub(irep, named_sub_idx); self.write_numbered_string_ref(&k); self.write_numbered_irep_ref(&v); } @@ -1339,7 +1339,7 @@ mod tests { let mut writer = BufWriter::new(&mut vec); let mut serializer = GotoBinarySerializer::new(&mut writer); for string in strings.iter() { - serializer.write_string_ref(&string); + serializer.write_string_ref(string); } println!("Serializer stats {:?}", serializer.get_stats()); } @@ -1365,12 +1365,12 @@ mod tests { let mut serializer = GotoBinarySerializer::new(&mut writer); // Number an irep - let num1 = serializer.numbering.number_irep(&irep1); + let num1 = serializer.numbering.number_irep(irep1); // Number an structurally different irep let identifiers2 = vec!["foo", "bar", "baz", "different", "zab", "rab", "oof"]; let irep2 = &fold_with_op(&identifiers2, IrepId::And); - let num2 = serializer.numbering.number_irep(&irep2); + let num2 = serializer.numbering.number_irep(irep2); // Check that they have the different numbers. assert_ne!(num1, num2); diff --git a/cprover_bindings/src/irep/symbol_table.rs b/cprover_bindings/src/irep/symbol_table.rs index 7e1ae6b40ec6..cee110075ff5 100644 --- a/cprover_bindings/src/irep/symbol_table.rs +++ b/cprover_bindings/src/irep/symbol_table.rs @@ -12,6 +12,12 @@ pub struct SymbolTable { } /// Constructors +impl Default for SymbolTable { + fn default() -> Self { + Self::new() + } +} + impl SymbolTable { pub fn new() -> SymbolTable { SymbolTable { symbol_table: BTreeMap::new() } diff --git a/cprover_bindings/src/irep/to_irep.rs b/cprover_bindings/src/irep/to_irep.rs index b9cc6978ea85..90c8189ba1b4 100644 --- a/cprover_bindings/src/irep/to_irep.rs +++ b/cprover_bindings/src/irep/to_irep.rs @@ -377,6 +377,30 @@ impl ToIrep for ExprValue { sub: elems.iter().map(|x| x.to_irep(mm)).collect(), named_sub: linear_map![], }, + ExprValue::Forall { variable, domain } => Irep { + id: IrepId::Forall, + sub: vec![ + Irep { + id: IrepId::Tuple, + sub: vec![variable.to_irep(mm)], + named_sub: linear_map![], + }, + domain.to_irep(mm), + ], + named_sub: linear_map![], + }, + ExprValue::Exists { variable, domain } => Irep { + id: IrepId::Exists, + sub: vec![ + Irep { + id: IrepId::Tuple, + sub: vec![variable.to_irep(mm)], + named_sub: linear_map![], + }, + domain.to_irep(mm), + ], + named_sub: linear_map![], + }, } } } diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 8cd6a1ebb7b0..9d7553a668d7 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -17,6 +17,7 @@ - [Reference](./reference.md) - [Attributes](./reference/attributes.md) + - [Bounded Non-deterministic variables](./reference/bounded_arbitrary.md) - [Experimental features](./reference/experimental/experimental-features.md) - [Automatic Harness Generation](./reference/experimental/autoharness.md) - [Coverage](./reference/experimental/coverage.md) @@ -24,6 +25,7 @@ - [Contracts](./reference/experimental/contracts.md) - [Loop Contracts](./reference/experimental/loop-contracts.md) - [Concrete Playback](./reference/experimental/concrete-playback.md) + - [Quantifiers](./reference/experimental/quantifiers.md) - [Application](./application.md) - [Comparison with other tools](./tool-comparison.md) - [Where to start on real code](./tutorial-real-code.md) diff --git a/docs/src/build-from-source.md b/docs/src/build-from-source.md index c3d7ecfd4b9a..e7e43b07c6c8 100644 --- a/docs/src/build-from-source.md +++ b/docs/src/build-from-source.md @@ -27,6 +27,7 @@ is following our CI scripts: git clone https://github.com/model-checking/kani.git cd kani git submodule update --init + # For Ubuntu 20.04, use: `./scripts/setup/ubuntu-20.04/install_deps.sh` ./scripts/setup/ubuntu/install_deps.sh # If you haven't already (or from https://rustup.rs/): ./scripts/setup/install_rustup.sh diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index 1acfd039ce97..dbf626dc793d 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -20,7 +20,7 @@ We also publish updates on Kani use cases and features on our [blog](https://mod There is support for a fair amount of Rust language features, but not all (e.g., concurrency). Please see [Limitations](./limitations.md) for a detailed list of supported features. -Kani releases every two weeks. +Kani releases every month. As part of every release, Kani will synchronize with a recent nightly release of Rust, and so is generally up-to-date with the latest Rust language features. If you encounter issues when using Kani, we encourage you to [report them to us](https://github.com/model-checking/kani/issues/new/choose). diff --git a/docs/src/install-guide.md b/docs/src/install-guide.md index d64ad3c89ce0..446b5a9b3c9b 100644 --- a/docs/src/install-guide.md +++ b/docs/src/install-guide.md @@ -14,20 +14,24 @@ GitHub CI workflows, see [GitHub CI Action](./install-github-ci.md). The following must already be installed: -* **Python version 3.7 or newer** and the package installer `pip`. * Rust 1.58 or newer installed via `rustup`. ## Installing the latest version -To install the latest version of Kani, run: +Installing the latest version of Kani is a two step process. +First, download and build Kani's installer package using: ```bash cargo install --locked kani-verifier +``` +This will build and place in `~/.cargo/bin` (in a typical environment) the `kani` and `cargo-kani` binaries. + +Next, run the installer to download and install the prebuilt binaries as well as supporting libraries and data: +```bash cargo kani setup ``` -This will build and place in `~/.cargo/bin` (in a typical environment) the `kani` and `cargo-kani` binaries. -The second step (`cargo kani setup`) will download the Kani compiler and other necessary dependencies, and place them under `~/.kani/` by default. +The second step will download the Kani compiler and other necessary dependencies, and place them under `~/.kani/` by default. A custom path can be specified using the `KANI_HOME` environment variable. ## Installing an older version diff --git a/docs/src/reference/bounded_arbitrary.md b/docs/src/reference/bounded_arbitrary.md new file mode 100644 index 000000000000..03b7acf629e1 --- /dev/null +++ b/docs/src/reference/bounded_arbitrary.md @@ -0,0 +1,81 @@ +# Bounded Non-deterministic variables + +This is an experimental feature that allows you to bound otherwise unbounded types. For example, `Vec` does not have an `Arbitrary` implementation because vectors can grow arbitrarily in size. One way of handling proofs about such types is to make the problem easier, and only prove a property up to some bound. Of course, the proof is only valid up to the bound, but can still be useful in providing confidence that your code is correct. + +## Example + +As a toy example, let's prove, up to some bound, that reversing a vector twice gives you back the original vector. Here's a reversing function: + +```rust +fn reverse_vector(mut input: Vec) -> Vec { + let mut reversed = vec![]; + for _ in 0..input.len() { + reversed.push(input.pop().unwrap()); + } + reversed +} +``` + +We can use `BoundedAny` to write a proof harness: + +```rust +#[kani::proof] +#[kani::unwind(17)] +fn check_reverse_is_its_own_inverse() { + // We use BoundedAny to construct a vector that has at most length 16 + let input: Vec = kani::bounded_any::<_, 16>(); + + let double_reversed = reverse_vector(reverse_vector(input.clone())); + + // we assert that every value in the input is the same as the value in the + // doubly reversed list + for i in 0..input.len() { + assert_eq!(input[i], double_reversed[i]) + } +} +``` + +Then, with `kani` we can prove that our reverse function is indeed its own inverse, for vectors up to size 16. + +## Proof Incompleteness + +It's very important to note, that this is **not** a complete proof that this function is correct. To drive this point home, consider this bad implementation of `reverse_vector`: + +```rust +fn bad_reverse_vector(mut input: Vec) -> Vec { + let mut reversed = vec![]; + for i in 0..input.len() { + if i < 16 { + reversed.push(input.pop().unwrap()); + } else { + reversed.push(T::default()) + } + } + reversed +} +``` + +Now the same harness as before is still successful! Even though this implementation is obviously wrong. If only we had tried a slightly bigger bound... + +So, while bounded proofs can be useful, beware that they are also incomplete. It might be worth-while to test multiple bounds. + +## Custom Bounded Arbitrary implementations + +Kani provides several implementations of `BoundedArbitrary`, but you can also implement `BoundedArbitrary` for yourself. + +We provide a derive macro that should work in most cases: + +```rust +#[derive(BoundedArbitrary)] +struct MyVector { + #[bounded] + vector: Vec, + capacity: usize +} +``` + +You must specify which fields should be bounded using the `#[bounded]` attribute. All other fields must derive `Arbitrary`. + +### Limitations + +Currently you can only specify a single bound for the entire type, and all bounded fields use the same bound. If different bounds would be useful, let us know through [filing an issue](https://github.com/model-checking/kani/issues/new/choose) and we can probably lift this restriction. diff --git a/docs/src/reference/experimental/autoharness.md b/docs/src/reference/experimental/autoharness.md index 67f46a9abf47..1fa184c484bc 100644 --- a/docs/src/reference/experimental/autoharness.md +++ b/docs/src/reference/experimental/autoharness.md @@ -29,26 +29,60 @@ Autoharness: Checking function foo against all possible inputs... ``` -However, if Kani detects that `foo` has a [contract](./contracts.md), it will instead generate a `#[kani::proof_for_contract]` harness and verify the contract: +However, if Kani detects that `foo` has a [function contract](./contracts.md), it will instead generate a `#[kani::proof_for_contract]` harness and verify the contract: ``` Autoharness: Checking function foo's contract against all possible inputs... ``` +Similarly, Kani will detect the presence of [loop contracts](./loop-contracts.md) and verify them. + +Thus, `-Z autoharness` implies `-Z function-contracts` and `-Z loop-contracts`, i.e., opting into the experimental +autoharness feature means that you are also opting into the function contracts and loop contracts features. + Kani generates and runs these harnesses internally—the user only sees the verification results. -The `autoharness` subcommand has options `--include-function` and `--exclude-function` to include and exclude particular functions. +### Options +The `autoharness` subcommand has options `--include-pattern` and `--exclude-pattern` to include and exclude particular functions. These flags look for partial matches against the fully qualified name of a function. For example, if a module `my_module` has many functions, but we are only interested in `my_module::foo` and `my_module::bar`, we can run: ``` -cargo run autoharness -Z autoharness --include-function foo --include-function bar +cargo run autoharness -Z autoharness --include-pattern my_module::foo --include-pattern my_module::bar ``` To exclude `my_module` entirely, run: ``` -cargo run autoharness -Z autoharness --exclude-function my_module +cargo run autoharness -Z autoharness --exclude-pattern my_module +``` + +The selection algorithm is as follows: +- If only `--include-pattern`s are provided, include a function if it matches any of the provided patterns. +- If only `--exclude-pattern`s are provided, include a function if it does not match any of the provided patterns. +- If both are provided, include a function if it matches an include pattern *and* does not match any of the exclude patterns. Note that this implies that the exclude pattern takes precedence, i.e., if a function matches both an include pattern and an exclude pattern, it will be excluded. + +Here are some more examples: + +``` +# Include functions whose paths contain the substring foo or baz, but not foo::bar +kani autoharness -Z autoharness --include-pattern foo --include-pattern baz --exclude-pattern foo::bar + +# Include functions whose paths contain the substring foo, but not bar. +kani autoharness -Z autoharness --include-pattern foo --exclude-pattern bar + +# Include functions whose paths contain the substring foo::bar, but not bar. +# This ends up including nothing, since all foo::bar matches will also contain bar. +# Kani will emit a warning that these flags conflict. +kani autoharness -Z autoharness --include-pattern foo::bar --exclude-pattern bar + +# Include functions whose paths contain the substring foo, but not foo. +# This ends up including nothing, and Kani will emit a warning that these flags conflict. +kani autoharness -Z autoharness --include-pattern foo --exclude--pattern foo ``` +Currently, the only supported "patterns" are substrings of the fully qualified path of the function. +However, if more sophisticated patterns (e.g., regular expressions) would be useful for you, +please let us know in a comment on [this GitHub issue](https://github.com/model-checking/kani/issues/3832). + ## Example Using the `estimate_size` example from [First Steps](../../tutorial-first-steps.md) again: ```rust @@ -112,8 +146,3 @@ Failed Checks: x and y are equal VERIFICATION:- FAILED ``` - -### Loop Contracts -If a function contains a loop with a loop contract, Kani will detect the presence of a loop contract and verify that contract. -If, however, the loop does not have a contract, then there is currently no way to specify an unwinding bound for the function, meaning that Kani may hang as it tries to unwind the loop. -We recommend using the `--exclude-function` option to exclude any functions that have this issue (or `--harness-timeout` to bail after attempting verification for some amount of time). \ No newline at end of file diff --git a/docs/src/reference/experimental/loop-contracts.md b/docs/src/reference/experimental/loop-contracts.md index 3cf5ecd429cc..2a07f5c2c1aa 100644 --- a/docs/src/reference/experimental/loop-contracts.md +++ b/docs/src/reference/experimental/loop-contracts.md @@ -139,13 +139,36 @@ In proof path 1, we prove properties inside the loop and at last check that the In proof path 2, we prove properties after leaving the loop. As we leave the loop only when the loop guard is violated, the post condition of the loop can be expressed as `!guard && inv`, which is `x <= 1 && x >= 1` in the example. The postcondition implies `x == 1`—the property we want to prove at the end of `simple_loop_with_loop_contracts`. +## Loop contracts inside functions with contracts +Kani supports using loop contracts together with function contracts, as demonstrated in the following example: +``` Rust +#![feature(proc_macro_hygiene)] +#![feature(stmt_expr_attributes)] + +#[kani::requires(i>=2)] +#[kani::ensures(|ret| *ret == 2)] +pub fn has_loop(mut i: u16) -> u16 { + #[kani::loop_invariant(i>=2)] + while i > 2 { + i = i - 1 + } + i +} + +#[kani::proof_for_contract(has_loop)] +fn contract_proof() { + let i: u16 = kani::any(); + let j = has_loop(i); +} +``` +When loop contracts and function contracts are both enabled (by flags `-Z loop-contracts -Z function-contracts`), +Kani automatically contracts (instead of unwinds) all loops in the functions that we want to prove contracts for. ## Limitations Loop contracts comes with the following limitations. -1. Only `while` loops are supported. The other three kinds of loops are not supported: [`loop` loops](https://doc.rust-lang.org/reference/expressions/loop-expr.html#infinite-loops) - , [`while let` loops](https://doc.rust-lang.org/reference/expressions/loop-expr.html#predicate-pattern-loops), and [`for` loops](https://doc.rust-lang.org/reference/expressions/loop-expr.html#iterator-loops). +1. `while` loops and `loop` loops are supported. The other kinds of loops are not supported: [`while let` loops](https://doc.rust-lang.org/reference/expressions/loop-expr.html#predicate-pattern-loops), and [`for` loops](https://doc.rust-lang.org/reference/expressions/loop-expr.html#iterator-loops). 2. Kani infers *loop modifies* with alias analysis. Loop modifies are those variables we assume to be arbitrary in the inductive hypothesis, and should cover all memory locations that are written to during the execution of the loops. A proof will fail if the inferred loop modifies misses some targets written in the loops. We observed this happens when some fields of structs are modified by some other functions called in the loops. diff --git a/docs/src/reference/experimental/quantifiers.md b/docs/src/reference/experimental/quantifiers.md new file mode 100644 index 000000000000..c62a70f2c7b3 --- /dev/null +++ b/docs/src/reference/experimental/quantifiers.md @@ -0,0 +1,56 @@ +# Quantifiers in Kani + +Quantifiers are a powerful feature in formal verification that allow you to express properties over a range of values. Kani provides experimental support for quantifiers, enabling users to write concise and expressive specifications for their programs. + +## Supported Quantifiers + +Kani currently supports the following quantifiers: + +1. **Universal Quantifier**: + - Ensures that a property holds for all values in a given range. + - Syntax: `kani::forall!(|variable in range| condition)` + - Example: + +```rust +#[kani::proof] +fn test_forall() { + let v = vec![10; 10]; + kani::assert(kani::forall!(|i in 0..10| v[i] == 10)); +} +``` + +2. **Existential Quantifier**: + - Ensures that there exists at least one value in a given range for which a property holds. + - Syntax: `kani::exists!(|variable in range| condition)` + - Example: + +```rust +#[kani::proof] +fn test_exists() { + let v = vec![1, 2, 3, 4, 5]; + kani::assert(kani::exists!(|i in 0..v.len()| v[i] == 3)); +} +``` + +### Limitations + +#### Array Indexing + +The performance of quantifiers can be affected by the depth of call stacks in the quantified expressions. If the call stack is too deep, Kani may not be able to evaluate the quantifier effectively, leading to potential timeouts or running out of memory. Actually, array indexing in Rust leads to a deep call stack, which can cause issues with quantifiers. To mitigate this, consider using *unsafe* pointer dereferencing instead of array indexing when working with quantifiers. For example: + +```rust + +#[kani::proof] +fn vec_assert_forall_harness() { + let v = vec![10 as u8; 128]; + let ptr = v.as_ptr(); + unsafe { + kani::assert(kani::forall!(|i in (0,128)| *ptr.wrapping_byte_offset(i as isize) == 10), ""); + } +} +``` + +#### Types of Quantified Variables + +We now assume that all quantified variables are of type `usize`. This means that the range specified in the quantifier must be compatible with `usize`. + We plan to support other types in the future, but for now, ensure that your quantifiers use `usize` ranges. diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index f1640a0027ba..8a77ade4b9e2 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-compiler" -version = "0.60.0" +version = "0.62.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false @@ -28,7 +28,7 @@ tracing-tree = "0.4.0" # Future proofing: enable backend dependencies using feature. [features] -default = ['cprover', 'llbc'] +default = ['cprover'] llbc = ['charon'] cprover = ['cbmc', 'num', 'serde'] diff --git a/kani-compiler/src/args.rs b/kani-compiler/src/args.rs index ce969d195675..34a913088037 100644 --- a/kani-compiler/src/args.rs +++ b/kani-compiler/src/args.rs @@ -91,16 +91,14 @@ pub struct Arguments { /// Print the final LLBC file to stdout. #[clap(long)] pub print_llbc: bool, - /// If we are running the autoharness subcommand, the functions to include - #[arg( - long = "autoharness-include-function", - num_args(1), - conflicts_with = "autoharness_excluded_functions" - )] - pub autoharness_included_functions: Vec, - /// If we are running the autoharness subcommand, the functions to exclude - #[arg(long = "autoharness-exclude-function", num_args(1))] - pub autoharness_excluded_functions: Vec, + /// If we are running the autoharness subcommand, the paths to include. + /// See kani_driver::autoharness_args for documentation. + #[arg(long = "autoharness-include-pattern", num_args(1))] + pub autoharness_included_patterns: Vec, + /// If we are running the autoharness subcommand, the paths to exclude. + /// See kani_driver::autoharness_args for documentation. + #[arg(long = "autoharness-exclude-pattern", num_args(1))] + pub autoharness_excluded_patterns: Vec, } #[derive(Debug, Clone, Copy, AsRefStr, EnumString, VariantNames, PartialEq, Eq)] diff --git a/kani-compiler/src/codegen_aeneas_llbc/mir_to_ullbc/mod.rs b/kani-compiler/src/codegen_aeneas_llbc/mir_to_ullbc/mod.rs index 219bc48c4d91..546a3aa4f127 100644 --- a/kani-compiler/src/codegen_aeneas_llbc/mir_to_ullbc/mod.rs +++ b/kani-compiler/src/codegen_aeneas_llbc/mir_to_ullbc/mod.rs @@ -813,7 +813,7 @@ impl<'a, 'tcx> Context<'a, 'tcx> { let disambiguator = CharonDisambiguator::new(data.disambiguator as usize); use rustc_hir::definitions::DefPathData; match &data.data { - DefPathData::TypeNs(Some(symbol)) => { + DefPathData::TypeNs(symbol) => { error_assert!(self, span, data.disambiguator == 0); // Sanity check name.push(CharonPathElem::Ident(symbol.to_string(), disambiguator)); } @@ -956,7 +956,7 @@ impl<'a, 'tcx> Context<'a, 'tcx> { let disambiguator = CharonDisambiguator::new(data.disambiguator as usize); use rustc_hir::definitions::DefPathData; match &data.data { - DefPathData::TypeNs(Some(symbol)) => { + DefPathData::TypeNs(symbol) => { error_assert!(self, span, data.disambiguator == 0); // Sanity check name.push(CharonPathElem::Ident(symbol.to_string(), disambiguator)); } @@ -1063,7 +1063,7 @@ impl<'a, 'tcx> Context<'a, 'tcx> { let disambiguator = CharonDisambiguator::new(data.disambiguator as usize); use rustc_hir::definitions::DefPathData; match &data.data { - DefPathData::TypeNs(Some(symbol)) => { + DefPathData::TypeNs(symbol) => { error_assert!(self, span, data.disambiguator == 0); // Sanity check name.push(CharonPathElem::Ident(symbol.to_string(), disambiguator)); } diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs index 2392a801809f..b82a0c108424 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs @@ -101,7 +101,6 @@ pub enum PropertyClass { Unreachable, } -#[allow(dead_code)] impl PropertyClass { pub fn as_str(&self) -> &str { self.as_ref() diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index d3d4481bf258..b0583056b88b 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -160,7 +160,7 @@ impl GotocCtx<'_> { }; for ty in &modifies_tys { - assert!(ty.kind().is_any_ptr(), "Expected pointer, but found {}", ty); + assert!(ty.kind().is_any_ptr(), "Expected pointer, but found {ty}"); } let assigns: Vec<_> = modifies_tys diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs index dd6909483694..2cd693fa2256 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs @@ -6,6 +6,7 @@ use crate::codegen_cprover_gotoc::GotocCtx; use crate::codegen_cprover_gotoc::codegen::block::reverse_postorder; use cbmc::InternString; +use cbmc::InternedString; use cbmc::goto_program::{Expr, Stmt, Symbol}; use stable_mir::CrateDef; use stable_mir::mir::mono::Instance; @@ -20,7 +21,7 @@ impl GotocCtx<'_> { /// - Index 0 represents the return value. /// - Indices [1, N] represent the function parameters where N is the number of parameters. /// - Indices that are greater than N represent local variables. - fn codegen_declare_variables(&mut self, body: &Body) { + fn codegen_declare_variables(&mut self, body: &Body, function_name: InternedString) { let ldecls = body.local_decls(); let num_args = body.arg_locals().len(); for (lc, ldata) in ldecls { @@ -35,13 +36,23 @@ impl GotocCtx<'_> { let loc = self.codegen_span_stable(ldata.span); // Indices [1, N] represent the function parameters where N is the number of parameters. // Except that ZST fields are not included as parameters. - let sym = - Symbol::variable(name, base_name, var_type, self.codegen_span_stable(ldata.span)) - .with_is_hidden(!self.is_user_variable(&lc)) - .with_is_parameter((lc > 0 && lc <= num_args) && !self.is_zst_stable(ldata.ty)); + let sym = Symbol::variable( + name.clone(), + base_name, + var_type, + self.codegen_span_stable(ldata.span), + ) + .with_is_hidden(!self.is_user_variable(&lc)) + .with_is_parameter((lc > 0 && lc <= num_args) && !self.is_zst_stable(ldata.ty)); let sym_e = sym.to_expr(); self.symbol_table.insert(sym); + // Store the parameter symbols of the function, which will be used for function + // inlining. + if lc > 0 && lc <= num_args { + self.symbol_table.insert_parameter(function_name, name); + } + // Index 0 represents the return value, which does not need to be // declared in the first block if lc < 1 || lc > body.arg_locals().len() { @@ -64,7 +75,7 @@ impl GotocCtx<'_> { self.set_current_fn(instance, &body); self.print_instance(instance, &body); self.codegen_function_prelude(&body); - self.codegen_declare_variables(&body); + self.codegen_declare_variables(&body, name.clone().into()); // Get the order from internal body for now. reverse_postorder(&body).for_each(|bb| self.codegen_block(bb, &body.blocks[bb])); @@ -247,6 +258,24 @@ pub mod rustc_smir { region_from_coverage(tcx, bcb, instance) } + pub fn merge_source_region(source_regions: Vec) -> SourceRegion { + let start_line = source_regions.iter().map(|sr| sr.start_line).min().unwrap(); + let start_col = source_regions + .iter() + .filter(|sr| sr.start_line == start_line) + .map(|sr| sr.start_col) + .min() + .unwrap(); + let end_line = source_regions.iter().map(|sr| sr.end_line).max().unwrap(); + let end_col = source_regions + .iter() + .filter(|sr| sr.end_line == end_line) + .map(|sr| sr.end_col) + .max() + .unwrap(); + SourceRegion { start_line, start_col, end_line, end_col } + } + /// Retrieves the `SourceRegion` associated with a `BasicCoverageBlock` object. /// /// Note: This function could be in the internal `rustc` impl for `Coverage`. @@ -258,22 +287,25 @@ pub mod rustc_smir { // We need to pull the coverage info from the internal MIR instance. let instance_def = rustc_smir::rustc_internal::internal(tcx, instance.def.def_id()); let body = tcx.instance_mir(rustc_middle::ty::InstanceKind::Item(instance_def)); - + let filename = rustc_internal::stable(body.span).get_filename(); // Some functions, like `std` ones, may not have coverage info attached // to them because they have been compiled without coverage flags. if let Some(cov_info) = &body.function_coverage_info { // Iterate over the coverage mappings and match with the coverage term. + let mut source_regions: Vec = Vec::new(); for mapping in &cov_info.mappings { let Code { bcb } = mapping.kind else { unreachable!() }; let source_map = tcx.sess.source_map(); let file = source_map.lookup_source_file(mapping.span.lo()); - if bcb == coverage { - return Some(( - make_source_region(source_map, &file, mapping.span).unwrap(), - rustc_internal::stable(mapping.span).get_filename(), - )); + if bcb == coverage + && let Some(source_region) = make_source_region(source_map, &file, mapping.span) + { + source_regions.push(source_region); } } + if !source_regions.is_empty() { + return Some((merge_source_region(source_regions), filename)); + } } None } diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs index 88f4763be49e..5f56c69a9041 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs @@ -299,7 +299,7 @@ impl GotocCtx<'_> { Intrinsic::Assume => self.codegen_assert_assume( fargs.remove(0).cast_to(Type::bool()), PropertyClass::Assume, - "assumption failed", + "Rust intrinsic assumption failed", loc, ), Intrinsic::AtomicAnd(_) => codegen_atomic_binop!(bitand), @@ -564,7 +564,7 @@ impl GotocCtx<'_> { self.intrinsics_typecheck_fail(span, "ctpop", "integer type", arg_rust_ty) } else { let loc = self.codegen_span_stable(span); - self.codegen_expr_to_place_stable(&target_place, arg.popcount(), loc) + self.codegen_expr_to_place_stable(target_place, arg.popcount(), loc) } } @@ -1253,10 +1253,10 @@ impl GotocCtx<'_> { let size = sized_size.plus(unsized_size); // Packed types ignore the alignment of their fields. - if let TyKind::RigidTy(RigidTy::Adt(def, _)) = ty.kind() { - if rustc_internal::internal(self.tcx, def).repr().packed() { - unsized_align = sized_align.clone(); - } + if let TyKind::RigidTy(RigidTy::Adt(def, _)) = ty.kind() + && rustc_internal::internal(self.tcx, def).repr().packed() + { + unsized_align = sized_align.clone(); } // The alignment should be the maximum of the alignments for the @@ -1882,12 +1882,11 @@ impl GotocCtx<'_> { assert!(ty.kind().is_float()); let TyKind::RigidTy(integral_ty) = res_ty.kind() else { panic!( - "Expected intrinsic `{}` type to be `RigidTy`, but found: `{:?}`", - intrinsic, res_ty + "Expected intrinsic `{intrinsic}` type to be `RigidTy`, but found: `{res_ty:?}`" ); }; let TyKind::RigidTy(RigidTy::Float(float_type)) = ty.kind() else { - panic!("Expected intrinsic `{}` type to be `Float`, but found: `{:?}`", intrinsic, ty); + panic!("Expected intrinsic `{intrinsic}` type to be `Float`, but found: `{ty:?}`"); }; let mm = self.symbol_table.machine_model(); let in_range = utils::codegen_in_range_expr(&expr, float_type, integral_ty, mm); diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs index 1a110d8dab24..0cfa0e6c1352 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs @@ -335,7 +335,7 @@ impl<'tcx> GotocCtx<'tcx> { let typ = self.codegen_ty_stable(ty); let operation_name = "C string literal"; self.codegen_unimplemented_expr( - &operation_name, + operation_name, typ, loc, "https://github.com/model-checking/kani/issues/2549", diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs index f2327b51b6ad..243262a875b5 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs @@ -26,7 +26,10 @@ use stable_mir::mir::mono::Instance; use stable_mir::mir::{ AggregateKind, BinOp, CastKind, NullOp, Operand, Place, PointerCoercion, Rvalue, UnOp, }; -use stable_mir::ty::{ClosureKind, IntTy, RigidTy, Size, Ty, TyConst, TyKind, UintTy, VariantIdx}; +use stable_mir::ty::{ + Binder, ClosureKind, ExistentialPredicate, IntTy, RigidTy, Size, Ty, TyConst, TyKind, UintTy, + VariantIdx, +}; use std::collections::BTreeMap; use tracing::{debug, trace, warn}; @@ -731,7 +734,7 @@ impl GotocCtx<'_> { } } } - AggregateKind::Coroutine(_, _, _) => self.codegen_rvalue_coroutine(&operands, res_ty), + AggregateKind::Coroutine(_, _, _) => self.codegen_rvalue_coroutine(operands, res_ty), AggregateKind::CoroutineClosure(_, _) => { let ty = self.codegen_ty_stable(res_ty); self.codegen_unimplemented_expr( @@ -751,10 +754,10 @@ impl GotocCtx<'_> { Rvalue::Use(p) => self.codegen_operand_stable(p), Rvalue::Repeat(op, sz) => self.codegen_rvalue_repeat(op, sz, loc), Rvalue::Ref(_, _, p) | Rvalue::AddressOf(_, p) => { - let place_ref = self.codegen_place_ref_stable(&p, loc); + let place_ref = self.codegen_place_ref_stable(p, loc); let place_ref_type = place_ref.typ().clone(); match self.codegen_raw_ptr_deref_validity_check( - &p, + p, place_ref.clone(), self.place_ty_stable(p), &loc, @@ -1528,9 +1531,26 @@ impl GotocCtx<'_> { VtblEntry::MetadataSize => Some(vt_size.clone()), VtblEntry::MetadataAlign => Some(vt_align.clone()), VtblEntry::Vacant => None, - // TODO: trait upcasting - // https://github.com/model-checking/kani/issues/358 - VtblEntry::TraitVPtr(_trait_ref) => None, + VtblEntry::TraitVPtr(trait_ref) => { + let projections = match dst_mir_type.kind() { + TyKind::RigidTy(RigidTy::Dynamic(predicates, ..)) => predicates + .iter() + .filter_map(|pred| match &pred.value { + ExistentialPredicate::Projection(proj) => { + Some(Binder::bind_with_vars( + proj.clone(), + pred.bound_vars.clone(), + )) + } + _ => None, + }) + .collect(), + _ => vec![], + }; + + let dyn_ref = ctx.trait_ref_to_dyn_trait(*trait_ref, projections); + Some(ctx.codegen_vtable(src_mir_type, dyn_ref, loc).address_of()) + } VtblEntry::Method(instance) => Some(ctx.codegen_vtable_method_field( rustc_internal::stable(instance), trait_type, @@ -1593,8 +1613,15 @@ impl GotocCtx<'_> { }; slice_fat_ptr(fat_ptr_type, dst_data_expr, dst_goto_len, &self.symbol_table) } - (TyKind::RigidTy(RigidTy::Dynamic(..)), TyKind::RigidTy(RigidTy::Dynamic(..))) => { - // Cast between fat pointers. Cast the data and the source + ( + ref src_ty @ TyKind::RigidTy(RigidTy::Dynamic(.., ref src_dyn_kind)), + ref dst_ty @ TyKind::RigidTy(RigidTy::Dynamic(.., ref dst_dyn_kind)), + ) => { + assert_eq!( + src_dyn_kind, dst_dyn_kind, + "casting from `{src_dyn_kind:?}` to `{dst_dyn_kind:?}` is not supported" + ); + // Cast between dyn pointers. Cast the data and the source let src_data = src_goto_expr.to_owned().member("data", &self.symbol_table); let dst_data = src_data.cast_to(dst_data_type); @@ -1602,9 +1629,28 @@ impl GotocCtx<'_> { let src_vtable = src_goto_expr.member("vtable", &self.symbol_table); let vtable_name = self.vtable_name_stable(metadata_dst_type); let vtable_ty = Type::struct_tag(vtable_name).to_pointer(); - let dst_vtable = src_vtable.cast_to(vtable_ty); - // Construct a fat pointer with the same (casted) fields and new type + // Note: Logic based on upstream rustc_codegen_ssa: + // https://github.com/rust-lang/rust/blob/8bf5a8d/compiler/rustc_codegen_ssa/src/base.rs#L171 + let dst_vtable = if src_ty.trait_principal() == dst_ty.trait_principal() + || dst_ty.trait_principal().is_none() + { + // If we're casting to the same dyn trait, we can reuse the old vtable + src_vtable.cast_to(vtable_ty) + } else if let Some(vptr_entry_idx) = self.tcx.supertrait_vtable_slot(( + rustc_internal::internal(self.tcx, metadata_src_type), + rustc_internal::internal(self.tcx, metadata_dst_type), + )) { + // Otherwise, if the principals differ and our vtable contains a pointer to the supertrait vtable, + // we can load that + src_vtable + .dereference() + .member(vptr_entry_idx.to_string(), &self.symbol_table) + .cast_to(vtable_ty) + } else { + // Fallback to original vtable + src_vtable.cast_to(vtable_ty) + }; dynamic_fat_ptr(fat_ptr_type, dst_data, dst_vtable, &self.symbol_table) } (_, TyKind::RigidTy(RigidTy::Dynamic(..))) => { diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/span.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/span.rs index 549fbcf3447d..178e0b7c5d07 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/span.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/span.rs @@ -59,10 +59,8 @@ impl GotocCtx<'_> { let arg = parse_word(attr).expect( "incorrect value passed to `disable_checks`, expected a single identifier", ); - *PRAGMAS.get(arg.as_str()).expect(format!( - "attempting to disable an unexisting check, the possible options are {:?}", - PRAGMAS.keys() - ).as_str()) + *PRAGMAS.get(arg.as_str()).unwrap_or_else(|| panic!("attempting to disable an unexisting check, the possible options are {:?}", + PRAGMAS.keys())) }) .collect::>() .leak() // This is to preserve `Location` being Copy, but could blow up the memory utilization of compiler. diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs index 0ffa893056be..3fb7b58ca91c 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs @@ -12,6 +12,7 @@ use rustc_abi::{FieldsShape, Primitive, TagEncoding, Variants}; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::{List, TypingEnv}; use rustc_smir::rustc_internal; +use stable_mir::CrateDef; use stable_mir::abi::{ArgAbi, FnAbi, PassMode}; use stable_mir::mir::mono::{Instance, InstanceKind}; use stable_mir::mir::{ @@ -148,15 +149,18 @@ impl GotocCtx<'_> { // Pack the operands and their types, then call `codegen_copy` let fargs = operands.iter().map(|op| self.codegen_operand_stable(op)).collect::>(); - let farg_types = operands.map(|op| self.operand_ty_stable(&op)); + let farg_types = operands.map(|op| self.operand_ty_stable(op)); self.codegen_copy("copy_nonoverlapping", true, fargs, &farg_types, None, location) } + // https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/mir/enum.NonDivergingIntrinsic.html#variant.Assume + // Informs the optimizer that a condition is always true. + // If the condition is false, the behavior is undefined. StatementKind::Intrinsic(NonDivergingIntrinsic::Assume(ref op)) => { let cond = self.codegen_operand_stable(op).cast_to(Type::bool()); self.codegen_assert_assume( cond, PropertyClass::Assume, - "assumption failed", + "Rust intrinsic assumption failed", location, ) } @@ -165,7 +169,7 @@ impl GotocCtx<'_> { let instance = self.current_fn().instance_stable(); let counter_data = format!("{coverage_opaque:?} ${function_name}$"); let maybe_source_region = - region_from_coverage_opaque(self.tcx, &coverage_opaque, instance); + region_from_coverage_opaque(self.tcx, coverage_opaque, instance); if let Some((source_region, file_name)) = maybe_source_region { let coverage_stmt = self.codegen_coverage(&counter_data, stmt.span, source_region, &file_name); @@ -280,6 +284,7 @@ impl GotocCtx<'_> { | AssertMessage::DivisionByZero { .. } | AssertMessage::RemainderByZero { .. } | AssertMessage::ResumedAfterReturn { .. } + | AssertMessage::ResumedAfterDrop { .. } | AssertMessage::ResumedAfterPanic { .. } => { (msg.description().unwrap(), PropertyClass::Assertion) } @@ -563,12 +568,41 @@ impl GotocCtx<'_> { /// Generate Goto-C for each argument to a function call. /// /// N.B. public only because instrinsics use this directly, too. - pub(crate) fn codegen_funcall_args(&mut self, fn_abi: &FnAbi, args: &[Operand]) -> Vec { + pub(crate) fn codegen_funcall_args_for_quantifiers( + &mut self, + fn_abi: &FnAbi, + args: &[Operand], + ) -> Vec { let fargs: Vec = args .iter() .enumerate() .filter_map(|(i, op)| { // Functions that require caller info will have an extra parameter. + let arg_abi = &fn_abi.args.get(i); + let ty = self.operand_ty_stable(op); + if ty.kind().is_bool() { + Some(self.codegen_operand_stable(op).cast_to(Type::c_bool())) + } else if ty.kind().is_closure() + || (arg_abi.is_none_or(|abi| abi.mode != PassMode::Ignore)) + { + Some(self.codegen_operand_stable(op)) + } else { + None + } + }) + .collect(); + debug!(?fargs, args_abi=?fn_abi.args, "codegen_funcall_args"); + fargs + } + + /// Generate Goto-C for each argument to a function call. + /// + /// N.B. public only because instrinsics use this directly, too. + pub(crate) fn codegen_funcall_args(&mut self, fn_abi: &FnAbi, args: &[Operand]) -> Vec { + let fargs: Vec = args + .iter() + .enumerate() + .filter_map(|(i, op)| { let arg_abi = &fn_abi.args.get(i); let ty = self.operand_ty_stable(op); if ty.kind().is_bool() { @@ -618,8 +652,8 @@ impl GotocCtx<'_> { if def.as_intrinsic().unwrap().must_be_overridden() || !instance.has_body() { return self.codegen_funcall_of_intrinsic( instance, - &args, - &destination, + args, + destination, target.map(|bb| bb), span, ); @@ -635,10 +669,16 @@ impl GotocCtx<'_> { let mut fargs = if args.is_empty() || fn_def.fn_sig().unwrap().value.abi != Abi::RustCall { - self.codegen_funcall_args(&fn_abi, &args) + if instance.def.name() == "kani::internal::kani_forall" + || (instance.def.name() == "kani::internal::kani_exists") + { + self.codegen_funcall_args_for_quantifiers(&fn_abi, args) + } else { + self.codegen_funcall_args(&fn_abi, args) + } } else { let (untupled, first_args) = args.split_last().unwrap(); - let mut fargs = self.codegen_funcall_args(&fn_abi, &first_args); + let mut fargs = self.codegen_funcall_args(&fn_abi, first_args); fargs.append( &mut self.codegen_untupled_args(untupled, &fn_abi.args[first_args.len()..]), ); @@ -685,11 +725,11 @@ impl GotocCtx<'_> { self.tcx .fn_abi_of_fn_ptr( TypingEnv::fully_monomorphized() - .as_query_input((fn_sig_internal, &List::empty())), + .as_query_input((fn_sig_internal, List::empty())), ) .unwrap(), ); - let fargs = self.codegen_funcall_args(&fn_ptr_abi, &args); + let fargs = self.codegen_funcall_args(&fn_ptr_abi, args); let func_expr = self.codegen_operand_stable(func).dereference(); // Actually generate the function call and return. Stmt::block( diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs index 84d2c52afba4..cd8feb9e7b6c 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs @@ -10,20 +10,24 @@ use rustc_abi::{ }; use rustc_ast::ast::Mutability; use rustc_index::IndexVec; -use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::print::FmtPrinter; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{ - self, AdtDef, Const, CoroutineArgs, CoroutineArgsExt, FloatTy, Instance, IntTy, PolyFnSig, Ty, - TyCtxt, TyKind, UintTy, VariantDef, VtblEntry, + self, AdtDef, Const, CoroutineArgs, CoroutineArgsExt, FloatTy, Instance, IntTy, PolyFnSig, + TraitRef, Ty, TyCtxt, TyKind, UintTy, VariantDef, VtblEntry, }; +use rustc_middle::ty::{ExistentialTraitRef, GenericArgsRef}; use rustc_middle::ty::{List, TypeFoldable}; use rustc_smir::rustc_internal; use rustc_span::def_id::DefId; use stable_mir::abi::{ArgAbi, FnAbi, PassMode}; use stable_mir::mir::Body; use stable_mir::mir::mono::Instance as InstanceStable; +use stable_mir::ty::{ + Binder, DynKind, ExistentialPredicate, ExistentialProjection, Region, RegionKind, RigidTy, + Ty as StableTy, +}; use tracing::{debug, trace, warn}; /// Map the unit type to an empty struct @@ -254,8 +258,7 @@ impl<'tcx> GotocCtx<'tcx> { .is_sized(*self.tcx.at(rustc_span::DUMMY_SP), ty::TypingEnv::fully_monomorphized()) } - /// Generates the type for a single field for a dynamic vtable. - /// In particular, these fields are function pointers. + /// Generates the type for a supertrait vtable pointer field for a vtable. fn trait_method_vtable_field_type( &mut self, instance: Instance<'tcx>, @@ -274,6 +277,44 @@ impl<'tcx> GotocCtx<'tcx> { DatatypeComponent::field(vtable_field_name, fn_ptr) } + pub fn trait_ref_to_dyn_trait( + &self, + trait_ref: TraitRef<'tcx>, + projections: Vec>, + ) -> StableTy { + let existential_trait_ref = + rustc_internal::stable(ExistentialTraitRef::erase_self_ty(self.tcx, trait_ref)); + let binder = Binder::dummy(ExistentialPredicate::Trait(existential_trait_ref)); + + let mut predictates = vec![binder]; + predictates.extend( + projections.into_iter().map(|proj| proj.map_bound(ExistentialPredicate::Projection)), + ); + let rigid = + RigidTy::Dynamic(predictates, Region { kind: RegionKind::ReErased }, DynKind::Dyn); + + stable_mir::ty::Ty::from_rigid_kind(rigid) + } + + /// Generates the type for a single field for a dynamic vtable. + /// In particular, these fields are function pointers. + fn trait_vptr_vtable_field_type( + &mut self, + idx: usize, + trait_ref: TraitRef<'tcx>, + projections: Vec>, + ) -> DatatypeComponent { + let dyn_trait = self.trait_ref_to_dyn_trait(trait_ref, projections); + let vtable_ty = + self.codegen_trait_vtable_type(rustc_internal::internal(self.tcx, dyn_trait)); + let vtable_ptr = vtable_ty.to_pointer(); + + // vtable field name, i.e., 3_vol (idx_method) + let vtable_field_name = self.vtable_field_name(idx); + + DatatypeComponent::field(vtable_field_name, vtable_ptr) + } + /// Generates a vtable that looks like this: /// struct io::error::vtable { /// void *drop_in_place; @@ -383,9 +424,11 @@ impl<'tcx> GotocCtx<'tcx> { VtblEntry::Method(instance) => { Some(self.trait_method_vtable_field_type(instance, idx)) } - // TODO: trait upcasting - // https://github.com/model-checking/kani/issues/358 - VtblEntry::TraitVPtr(..) => None, + VtblEntry::TraitVPtr(trait_ref) => Some(self.trait_vptr_vtable_field_type( + idx, + trait_ref, + binder.projection_bounds().map(rustc_internal::stable).collect(), + )), VtblEntry::MetadataDropInPlace | VtblEntry::MetadataSize | VtblEntry::MetadataAlign @@ -1505,11 +1548,11 @@ impl<'tcx> GotocCtx<'tcx> { // components as parameters with a special naming convention // so that we can "retuple" them in the function prelude. // See: compiler/rustc_codegen_llvm/src/gotoc/mod.rs:codegen_function_prelude - if let Some(spread) = body.spread_arg() { - if lc >= spread { - let (name, _) = self.codegen_spread_arg_name(&lc); - ident = name; - } + if let Some(spread) = body.spread_arg() + && lc >= spread + { + let (name, _) = self.codegen_spread_arg_name(&lc); + ident = name; } Some( self.codegen_ty_stable(ty) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 8ed90b2c975e..9080d1a16c00 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -29,7 +29,7 @@ use rustc_codegen_ssa::back::archive::{ }; use rustc_codegen_ssa::back::link::link_binary; use rustc_codegen_ssa::traits::CodegenBackend; -use rustc_codegen_ssa::{CodegenResults, CrateInfo}; +use rustc_codegen_ssa::{CodegenResults, CrateInfo, TargetConfig}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_errors::DEFAULT_LOCALE_RESOURCE; use rustc_hir::def_id::{DefId as InternalDefId, LOCAL_CRATE}; @@ -41,9 +41,10 @@ use rustc_session::Session; use rustc_session::config::{CrateType, OutputFilenames, OutputType}; use rustc_session::output::out_filename; use rustc_smir::rustc_internal; +use rustc_span::symbol::Symbol; use rustc_target::spec::PanicStrategy; +use stable_mir::CrateDef; use stable_mir::mir::mono::{Instance, MonoItem}; -use stable_mir::{CrateDef, DefId}; use std::any::Any; use std::collections::BTreeMap; use std::fmt::Write; @@ -198,20 +199,43 @@ impl GotocCodegenBackend { None }; + gcx.handle_quantifiers(); + // No output should be generated if user selected no_codegen. if !tcx.sess.opts.unstable_opts.no_codegen && tcx.sess.opts.output_types.should_codegen() { let pretty = self.queries.lock().unwrap().args().output_pretty_json; - write_file(&symtab_goto, ArtifactType::PrettyNameMap, &pretty_name_map, pretty); + write_file(symtab_goto, ArtifactType::PrettyNameMap, &pretty_name_map, pretty); write_goto_binary_file(symtab_goto, &gcx.symbol_table); - write_file(&symtab_goto, ArtifactType::TypeMap, &type_map, pretty); + write_file(symtab_goto, ArtifactType::TypeMap, &type_map, pretty); // If they exist, write out vtable virtual call function pointer restrictions if let Some(restrictions) = vtable_restrictions { - write_file(&symtab_goto, ArtifactType::VTableRestriction, &restrictions, pretty); + write_file(symtab_goto, ArtifactType::VTableRestriction, &restrictions, pretty); } } (gcx, items, contract_info) } + + /// Given a contract harness, get the DefId of its target. + /// For manual harnesses, extract it from the #[proof_for_contract] attribute. + /// For automatic harnesses, extract the target from the harness's GenericArgs. + fn target_def_id_for_harness( + &self, + tcx: TyCtxt, + harness: &Instance, + is_automatic_harness: bool, + ) -> Option { + if is_automatic_harness { + let kind = harness.args().0[0].expect_ty().kind(); + let (fn_to_verify_def, _) = kind.fn_def().unwrap(); + let def_id = fn_to_verify_def.def_id(); + let attrs = KaniAttributes::for_def_id(tcx, def_id); + if attrs.has_contract() { Some(rustc_internal::internal(tcx, def_id)) } else { None } + } else { + let harness_attrs = KaniAttributes::for_def_id(tcx, harness.def.def_id()); + harness_attrs.interpret_for_contract_attribute().map(|(_, id, _)| id) + } + } } impl CodegenBackend for GotocCodegenBackend { @@ -228,6 +252,24 @@ impl CodegenBackend for GotocCodegenBackend { DEFAULT_LOCALE_RESOURCE } + fn target_config(&self, _sess: &Session) -> TargetConfig { + TargetConfig { + target_features: vec![], + unstable_target_features: vec![ + Symbol::intern("sse"), + Symbol::intern("neon"), + Symbol::intern("x87"), + Symbol::intern("sse2"), + ], + // `true` is used as a default so backends need to acknowledge when they do not + // support the float types, rather than accidentally quietly skipping all tests. + has_reliable_f16: true, + has_reliable_f16_math: true, + has_reliable_f128: true, + has_reliable_f128_math: true, + } + } + fn codegen_crate( &self, tcx: TyCtxt, @@ -273,10 +315,11 @@ impl CodegenBackend for GotocCodegenBackend { // We reset the body cache for now because each codegen unit has different // configurations that affect how we transform the instance body. for harness in &unit.harnesses { - let transformer = BodyTransformation::new(&queries, tcx, &unit); + let transformer = BodyTransformation::new(&queries, tcx, unit); let model_path = units.harness_model_path(*harness).unwrap(); + let is_automatic_harness = units.is_automatic_harness(harness); let contract_metadata = - contract_metadata_for_harness(tcx, harness.def.def_id()); + self.target_def_id_for_harness(tcx, harness, is_automatic_harness); let (gcx, items, contract_info) = self.codegen_items( tcx, &[MonoItem::Fn(*harness)], @@ -333,14 +376,15 @@ impl CodegenBackend for GotocCodegenBackend { for (test_fn, test_desc) in harnesses.iter().zip(descriptions.iter()) { let instance = if let MonoItem::Fn(instance) = test_fn { instance } else { continue }; - let metadata = - gen_test_metadata(tcx, *test_desc, *instance, &base_filename); + let metadata = gen_test_metadata(tcx, *test_desc, *instance, base_filename); let test_model_path = &metadata.goto_file.as_ref().unwrap(); - std::fs::copy(&model_path, test_model_path).expect(&format!( - "Failed to copy {} to {}", - model_path.display(), - test_model_path.display() - )); + std::fs::copy(&model_path, test_model_path).unwrap_or_else(|_| { + panic!( + "Failed to copy {} to {}", + model_path.display(), + test_model_path.display() + ) + }); results.harnesses.push(metadata); } } @@ -383,7 +427,7 @@ impl CodegenBackend for GotocCodegenBackend { // To avoid overriding the metadata for its verification, we skip this step when // reachability is None, even because there is nothing to record. write_file( - &base_filename, + base_filename, ArtifactType::Metadata, &results.generate_metadata(), queries.args().output_pretty_json, @@ -453,11 +497,6 @@ impl ArchiveBuilderBuilder for ArArchiveBuilderBuilder { } } -fn contract_metadata_for_harness(tcx: TyCtxt, def_id: DefId) -> Option { - let attrs = KaniAttributes::for_def_id(tcx, def_id); - attrs.interpret_for_contract_attribute().map(|(_, id, _)| id) -} - fn check_target(session: &Session) { // The requirement below is needed to build a valid CBMC machine model // in function `machine_model_from_session` from diff --git a/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs b/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs index 79410bd3373e..e3073f87ba6b 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs @@ -22,7 +22,8 @@ use crate::codegen_cprover_gotoc::utils::full_crate_name; use crate::kani_middle::transform::BodyTransformation; use crate::kani_queries::QueryDb; use cbmc::goto_program::{ - DatatypeComponent, Expr, Location, Stmt, Symbol, SymbolTable, SymbolValues, Type, + CIntType, DatatypeComponent, Expr, ExprValue, Location, Stmt, StmtBody, SwitchCase, Symbol, + SymbolTable, SymbolValues, Type, }; use cbmc::utils::aggr_tag; use cbmc::{InternedString, MachineModel}; @@ -40,6 +41,7 @@ use rustc_target::callconv::FnAbi; use stable_mir::mir::Body; use stable_mir::mir::mono::Instance; use stable_mir::ty::Allocation; +use std::collections::{BTreeMap, HashSet}; use std::fmt::Debug; pub struct GotocCtx<'tcx> { @@ -304,6 +306,461 @@ impl<'tcx> GotocCtx<'tcx> { } } +/// Quantifiers Related +impl GotocCtx<'_> { + /// Find all quantifier expressions and recursively inline functions in the quantifier bodies. + /// We inline all the function calls in quantifier expressions because CBMC accept only + /// statement expressiont without function calls in quantifier expressions: + /// see https://github.com/diffblue/cbmc/pull/8605 for detail. + pub fn handle_quantifiers(&mut self) { + // Store the found quantifiers and the inlined results. + let mut to_modify: BTreeMap = BTreeMap::new(); + for (key, symbol) in self.symbol_table.iter() { + if let SymbolValues::Stmt(stmt) = &symbol.value { + let new_stmt_val = SymbolValues::Stmt(self.handle_quantifiers_in_stmt(stmt)); + to_modify.insert(*key, new_stmt_val); + } + } + + // Update the found quantifiers with the inlined results. + for (key, symbol_value) in to_modify { + self.symbol_table.lookup_mut(key).unwrap().update(symbol_value); + } + } + + /// Find all quantifier expressions in `stmt` and recursively inline functions. + fn handle_quantifiers_in_stmt(&self, stmt: &Stmt) -> Stmt { + match &stmt.body() { + // According to the hook handling for quantifiers, quantifier expressions must be of form + // lhs = typecast(qex, c_bool) + // where qex is either a forall-expression or an exists-expression. + StmtBody::Assign { lhs, rhs } => { + let new_rhs = match &rhs.value() { + ExprValue::Typecast(quantified_expr) => match &quantified_expr.value() { + ExprValue::Forall { variable, domain } => { + // We store the function symbols we have inlined to avoid recursion. + let mut visited_func_symbols: HashSet = HashSet::new(); + // We count the number of function that we have inlined, and use the count to + // make inlined labeled unique. + let mut suffix_count: u16 = 0; + + let end_stmt = Stmt::code_expression( + self.inline_function_calls_in_expr( + domain, + &mut visited_func_symbols, + &mut suffix_count, + ) + .unwrap(), + *domain.location(), + ); + + // Make the result a statement expression. + let res = Expr::forall_expr( + Type::Bool, + variable.clone(), + Expr::statement_expression( + vec![Stmt::skip(*domain.location()), end_stmt], + Type::Bool, + *domain.location(), + ), + ); + res.cast_to(Type::CInteger(CIntType::Bool)) + } + ExprValue::Exists { variable, domain } => { + // We store the function symbols we have inlined to avoid recursion. + let mut visited_func_symbols: HashSet = HashSet::new(); + // We count the number of function that we have inlined, and use the count to + // make inlined labeled unique. + let mut suffix_count = 0; + + let end_stmt = Stmt::code_expression( + self.inline_function_calls_in_expr( + domain, + &mut visited_func_symbols, + &mut suffix_count, + ) + .unwrap(), + *domain.location(), + ); + + // Make the result a statement expression. + let res = Expr::exists_expr( + Type::Bool, + variable.clone(), + Expr::statement_expression( + vec![Stmt::skip(*domain.location()), end_stmt], + Type::Bool, + *domain.location(), + ), + ); + res.cast_to(Type::CInteger(CIntType::Bool)) + } + _ => rhs.clone(), + }, + _ => rhs.clone(), + }; + Stmt::assign(lhs.clone(), new_rhs, *stmt.location()) + } + // Recursively find quantifier expressions. + StmtBody::Block(stmts) => Stmt::block( + stmts.iter().map(|stmt| self.handle_quantifiers_in_stmt(stmt)).collect(), + *stmt.location(), + ), + StmtBody::Label { label, body } => { + self.handle_quantifiers_in_stmt(body).with_label(*label) + } + _ => stmt.clone(), + } + } + + /// Count and return the number of return statements in `stmt`. + fn count_return_stmts(stmt: &Stmt) -> usize { + match stmt.body() { + StmtBody::Return(_) => 1, + StmtBody::Block(stmts) => stmts.iter().map(Self::count_return_stmts).sum(), + StmtBody::Label { label: _, body } => Self::count_return_stmts(body), + _ => 0, + } + } + + /// Rewrite return statements in `stmt` with a goto statement to `end_label`. + /// It also stores the return symbol in `return_symbol`. + /// When inlining the function body of some function foo + /// fn foo(params) { + /// body; + /// return res; // ** rewrite this statement + /// } + /// with the statement expression + /// { + /// DECL params + /// ASSIGN params = args + /// inline(body) + /// GOTO end_label // ** to this statement + /// end_label: + /// EXPRESSION res + /// }, + /// this function rewrites all return statements + /// into a goto statement to `end_label` + fn rewrite_return_stmt_with_goto( + stmt: &Stmt, + return_symbol: &mut Option, + end_label: &InternedString, + ) -> Stmt { + match stmt.body() { + StmtBody::Return(Some(expr)) => { + if let ExprValue::Symbol { ref identifier } = expr.value() { + *return_symbol = Some(Expr::symbol_expression(*identifier, expr.typ().clone())); + Stmt::goto(*end_label, *stmt.location()) + } else { + panic!("Expected symbol expression in return statement"); + } + } + StmtBody::Block(stmts) => Stmt::block( + stmts + .iter() + .map(|s| Self::rewrite_return_stmt_with_goto(s, return_symbol, end_label)) + .collect(), + *stmt.location(), + ), + StmtBody::Label { label, body } => { + Self::rewrite_return_stmt_with_goto(body, return_symbol, end_label) + .with_label(*label) + } + _ => stmt.clone(), + } + } + + /// Append a given suffix to all labels and goto destinations in `stmt`. + fn append_suffix_to_stmt(stmt: &Stmt, suffix: &str) -> Stmt { + match stmt.body() { + StmtBody::Label { label, body } => { + let new_label = format!("{label}{suffix}"); + Self::append_suffix_to_stmt(body, suffix).with_label(new_label) + } + StmtBody::Goto { dest, .. } => { + let new_target = format!("{dest}{suffix}"); + Stmt::goto(new_target, *stmt.location()) + } + StmtBody::Block(stmts) => Stmt::block( + stmts.iter().map(|s| Self::append_suffix_to_stmt(s, suffix)).collect(), + *stmt.location(), + ), + StmtBody::Ifthenelse { i, t, e } => Stmt::if_then_else( + i.clone(), + Self::append_suffix_to_stmt(t, suffix), + e.clone().map(|s| Self::append_suffix_to_stmt(&s, suffix)), + *stmt.location(), + ), + StmtBody::Switch { control, cases, default } => { + // Append the suffix to each case + let new_cases: Vec<_> = cases + .iter() + .map(|case| { + let new_body = Self::append_suffix_to_stmt(case.body(), suffix); + SwitchCase::new(case.case().clone(), new_body) + }) + .collect(); + + // Append the suffix to the default case, if it exists + let new_default = + default.as_ref().map(|stmt| Self::append_suffix_to_stmt(stmt, suffix)); + + // Construct the new switch statement + Stmt::switch(control.clone(), new_cases, new_default, *stmt.location()) + } + StmtBody::While { .. } | StmtBody::For { .. } => { + unimplemented!() + } + _ => stmt.clone(), + } + } + + /// Recursively inline all function calls in `expr`. + /// `visited_func_symbols` contain all function symbols in the stack. + /// `suffix_count` is used to make inlined labels unique. + fn inline_function_calls_in_expr( + &self, + expr: &Expr, + visited_func_symbols: &mut HashSet, + suffix_count: &mut u16, + ) -> Option { + match &expr.value() { + // For function call expression, we find the function symbol and function body from the + // symbol table for inlining. + ExprValue::FunctionCall { function, arguments } => { + if let ExprValue::Symbol { identifier } = &function.value() { + // Check if the function symbol exists in the symbol table + if let Some(function_body) = + self.symbol_table.lookup(*identifier).and_then(|sym| match &sym.value { + SymbolValues::Stmt(stmt) => Some(stmt), + _ => None, + }) + { + // For function calls to foo(args) where the definition of foo is + // fn foo(params) { + // body; + // return res; + // } + // The inlining result will be a statement expression + // { + // DECL params + // ASSIGN params = args + // inline(body) + // GOTO end_label + // end_label: + // EXPRESSION res + // } + // where res is the end expression of the statement expression. + + // Keep suffix unique in difference inlining. + *suffix_count += 1; + + // Use call stacks to avoid recursion. + assert!( + !visited_func_symbols.contains(identifier), + "Detected recursions in the usage of quantifiers." + ); + visited_func_symbols.insert(*identifier); + + let inlined_body: &Stmt = function_body; + let mut stmts_of_inlined_body: Vec = + inlined_body.get_stmts().unwrap().clone(); + + // Substitute parameters with arguments in the function body. + if let Some(parameters) = self.symbol_table.lookup_parameters(*identifier) { + // Create decl statements of parameters. + let mut param_decls: Vec = parameters + .iter() + .zip(arguments.iter()) + .map(|(param, arg)| { + Stmt::decl( + Expr::symbol_expression(*param, arg.typ().clone()), + None, + *arg.location(), + ) + }) + .collect(); + + // Create assignment statements from arguments to parameters. + let mut param_assigs: Vec = parameters + .iter() + .zip(arguments.iter()) + .map(|(param, arg)| { + Stmt::assign( + Expr::symbol_expression(*param, arg.typ().clone()), + arg.clone(), + *arg.location(), + ) + }) + .collect(); + + // Prepend the assignments to stmts_of_inlined_body + param_decls.append(&mut param_assigs); + param_decls.append(&mut stmts_of_inlined_body); + stmts_of_inlined_body = param_decls; + } + + let count_return: usize = stmts_of_inlined_body + .clone() + .iter() + .map(|stmt: &Stmt| Self::count_return_stmts(stmt)) + .sum(); + // The function is a void function, we safely ignore it. + if count_return == 0 { + return None; + } + // For simplicity, we currently only handle cases with one return statement. + assert_eq!(count_return, 1); + + // Make labels in the inlined body unique. + let suffix = format!("_{suffix_count}"); + stmts_of_inlined_body = stmts_of_inlined_body + .iter() + .map(|stmt| Self::append_suffix_to_stmt(stmt, &suffix)) + .collect(); + + // Replace all return stmts with symbol expressions. + let end_label: InternedString = + format!("KANI_quantifier_end{suffix}").into(); + let mut end_stmt = None; + stmts_of_inlined_body = stmts_of_inlined_body + .iter() + .map(|stmt| { + Self::rewrite_return_stmt_with_goto(stmt, &mut end_stmt, &end_label) + }) + .collect(); + stmts_of_inlined_body + .push(Stmt::skip(*expr.location()).with_label(end_label)); + stmts_of_inlined_body + .push(Stmt::code_expression(end_stmt.unwrap(), *expr.location())); + + // Recursively inline function calls in the function body. + let res = self.inline_function_calls_in_expr( + &Expr::statement_expression( + stmts_of_inlined_body, + expr.typ().clone(), + *expr.location(), + ), + visited_func_symbols, + suffix_count, + ); + + visited_func_symbols.remove(identifier); + + return res; + } else { + unreachable!() + } + } + } + // Recursively inline function calls in ops. + ExprValue::BinOp { op, lhs, rhs } => { + return Some( + self.inline_function_calls_in_expr(lhs, visited_func_symbols, suffix_count) + .unwrap() + .binop( + *op, + self.inline_function_calls_in_expr( + rhs, + visited_func_symbols, + suffix_count, + ) + .unwrap(), + ), + ); + } + ExprValue::StatementExpression { statements, location: _ } => { + let inlined_stmts: Vec = statements + .iter() + .filter_map(|stmt| { + self.inline_function_calls_in_stmt(stmt, visited_func_symbols, suffix_count) + }) + .collect(); + return Some(Expr::statement_expression( + inlined_stmts, + expr.typ().clone(), + *expr.location(), + )); + } + _ => {} + } + Some(expr.clone()) + } + + /// Recursively inline all function calls in `stmt`. + /// `visited_func_symbols` contain all function symbols in the stack. + /// `suffix_count` is used to make inlined labels unique. + fn inline_function_calls_in_stmt( + &self, + stmt: &Stmt, + visited_func_symbols: &mut HashSet, + suffix_count: &mut u16, + ) -> Option { + match stmt.body() { + StmtBody::Expression(expr) => self + .inline_function_calls_in_expr(expr, visited_func_symbols, suffix_count) + .map(|inlined_expr| Stmt::code_expression(inlined_expr, *expr.location())), + StmtBody::Assign { lhs, rhs } => self + .inline_function_calls_in_expr(rhs, visited_func_symbols, suffix_count) + .map(|inlined_rhs| Stmt::assign(lhs.clone(), inlined_rhs, *stmt.location())), + StmtBody::Block(stmts) => { + let inlined_block = stmts + .iter() + .filter_map(|s| { + self.inline_function_calls_in_stmt(s, visited_func_symbols, suffix_count) + }) + .collect(); + Some(Stmt::block(inlined_block, *stmt.location())) + } + StmtBody::Label { label, body } => { + match self.inline_function_calls_in_stmt(body, visited_func_symbols, suffix_count) { + None => Some(Stmt::skip(*stmt.location()).with_label(*label)), + Some(inlined_body) => Some(inlined_body.with_label(*label)), + } + } + StmtBody::Switch { control, cases, default } => { + // Inline function calls in the discriminant expression + let inlined_control = self + .inline_function_calls_in_expr(control, visited_func_symbols, suffix_count) + .unwrap_or_else(|| control.clone()); + + // Inline function calls in each case + let inlined_cases: Vec<_> = cases + .iter() + .map(|sc| { + let inlined_stmt = self + .inline_function_calls_in_stmt( + sc.body(), + visited_func_symbols, + suffix_count, + ) + .unwrap_or_else(|| sc.body().clone()); + SwitchCase::new(sc.case().clone(), inlined_stmt) + }) + .collect(); + + // Inline function calls in the default case, if it exists + let inlined_default = default.as_ref().map(|stmt| { + self.inline_function_calls_in_stmt(stmt, visited_func_symbols, suffix_count) + .unwrap_or_else(|| stmt.clone()) + }); + + // Construct the new switch statement + Some(Stmt::switch( + inlined_control, + inlined_cases, + inlined_default, + *stmt.location(), + )) + } + StmtBody::While { .. } | StmtBody::For { .. } => { + unimplemented!() + } + _ => Some(stmt.clone()), + } + } +} + /// Mutators impl GotocCtx<'_> { pub fn set_current_fn(&mut self, instance: Instance, body: &Body) { diff --git a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs index e54a1fc4dd9e..6c6319baf4eb 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs @@ -14,11 +14,13 @@ use crate::kani_middle::attributes; use crate::kani_middle::kani_functions::{KaniFunction, KaniHook}; use crate::unwrap_or_return_codegen_unimplemented_stmt; use cbmc::goto_program::CIntType; -use cbmc::goto_program::{BuiltinFn, Expr, Stmt, Type}; +use cbmc::goto_program::Symbol as GotoSymbol; +use cbmc::goto_program::{BuiltinFn, Expr, Location, Stmt, Type}; use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; use stable_mir::mir::mono::Instance; use stable_mir::mir::{BasicBlockIdx, Place}; +use stable_mir::ty::ClosureKind; use stable_mir::ty::RigidTy; use stable_mir::{CrateDef, ty::Span}; use std::collections::HashMap; @@ -761,10 +763,146 @@ impl GotocHook for LoopInvariantRegister { } } +struct Forall; +struct Exists; + +#[derive(Debug, Clone, Copy)] +enum QuantifierKind { + ForAll, + Exists, +} + +impl GotocHook for Forall { + fn hook_applies(&self, _tcx: TyCtxt, _instance: Instance) -> bool { + unreachable!("{UNEXPECTED_CALL}") + } + + fn handle( + &self, + gcx: &mut GotocCtx, + instance: Instance, + fargs: Vec, + assign_to: &Place, + target: Option, + span: Span, + ) -> Stmt { + handle_quantifier(gcx, instance, fargs, assign_to, target, span, QuantifierKind::ForAll) + } +} + +impl GotocHook for Exists { + fn hook_applies(&self, _tcx: TyCtxt, _instance: Instance) -> bool { + unreachable!("{UNEXPECTED_CALL}") + } + + fn handle( + &self, + gcx: &mut GotocCtx, + instance: Instance, + fargs: Vec, + assign_to: &Place, + target: Option, + span: Span, + ) -> Stmt { + handle_quantifier(gcx, instance, fargs, assign_to, target, span, QuantifierKind::Exists) + } +} + +fn handle_quantifier( + gcx: &mut GotocCtx, + instance: Instance, + fargs: Vec, + assign_to: &Place, + target: Option, + span: Span, + quantifier_kind: QuantifierKind, +) -> Stmt { + let loc = gcx.codegen_span_stable(span); + let target = target.unwrap(); + let lower_bound = &fargs[0]; + let upper_bound = &fargs[1]; + let closure_call_expr = find_closure_call_expr(&instance, gcx, loc) + .unwrap_or_else(|| unreachable!("Failed to find closure call expression")); + let closure_arg = fargs[2].clone(); + let predicate = if closure_arg.is_symbol() { + Expr::address_of(closure_arg) + } else { + let predicate_ty = fargs[2].typ().clone().to_pointer(); + Expr::nondet(predicate_ty) + }; + + // Quantified variable. + let base_name = "kani_quantified_var".to_string(); + let mut counter = 0; + let mut unique_name = format!("{base_name}_{counter}"); + // Ensure the name is not already in the symbol table + while gcx.symbol_table.lookup(&unique_name).is_some() { + counter += 1; + unique_name = format!("{base_name}_{counter}"); + } + let new_variable_expr = { + let new_symbol = + GotoSymbol::variable(unique_name.clone(), unique_name, lower_bound.typ().clone(), loc); + gcx.symbol_table.insert(new_symbol.clone()); + new_symbol.to_expr() + }; + + let lower_bound_comparison = lower_bound.clone().le(new_variable_expr.clone()); + let upper_bound_comparison = new_variable_expr.clone().lt(upper_bound.clone()); + let range = lower_bound_comparison.and(upper_bound_comparison); + + let quantifier_expr = match quantifier_kind { + QuantifierKind::ForAll => { + let domain = range + .clone() + .implies(closure_call_expr.call(vec![predicate.clone(), new_variable_expr.clone()])) + .and(range.not().implies(Expr::bool_true())); + Expr::forall_expr(Type::Bool, new_variable_expr, domain) + } + QuantifierKind::Exists => { + let domain = range + .clone() + .and(closure_call_expr.call(vec![predicate.clone(), new_variable_expr.clone()])) + .and(range.not().implies(Expr::bool_false())); + Expr::exists_expr(Type::Bool, new_variable_expr, domain) + } + }; + + Stmt::block( + vec![ + unwrap_or_return_codegen_unimplemented_stmt!( + gcx, + gcx.codegen_place_stable(assign_to, loc) + ) + .goto_expr + .assign(quantifier_expr.cast_to(Type::CInteger(CIntType::Bool)), loc), + Stmt::goto(bb_label(target), loc), + ], + loc, + ) +} + +fn find_closure_call_expr(instance: &Instance, gcx: &mut GotocCtx, loc: Location) -> Option { + for arg in instance.args().0.iter() { + let arg_ty = arg.ty()?; + let kind = arg_ty.kind(); + let arg_kind = kind.rigid()?; + + if let RigidTy::Closure(def_id, args) = arg_kind { + let instance_closure = + Instance::resolve_closure(*def_id, args, ClosureKind::Fn).ok()?; + return Some(gcx.codegen_func_expr(instance_closure, loc)); + } + } + None +} + pub fn fn_hooks() -> GotocHooks { let kani_lib_hooks = [ (KaniHook::Assert, Rc::new(Assert) as Rc), (KaniHook::Assume, Rc::new(Assume)), + (KaniHook::Exists, Rc::new(Exists)), + (KaniHook::Forall, Rc::new(Forall)), (KaniHook::Panic, Rc::new(Panic)), (KaniHook::Check, Rc::new(Check)), (KaniHook::Cover, Rc::new(Cover)), diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index 20d5f3ad6a4b..4dfb8cd64bee 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -357,19 +357,19 @@ impl<'tcx> KaniAttributes<'tcx> { } match kind { KaniAttributeKind::ShouldPanic => { - expect_single(self.tcx, kind, &attrs); + expect_single(self.tcx, kind, attrs); attrs.iter().for_each(|attr| { expect_no_args(self.tcx, kind, attr); }) } KaniAttributeKind::Recursion => { - expect_single(self.tcx, kind, &attrs); + expect_single(self.tcx, kind, attrs); attrs.iter().for_each(|attr| { expect_no_args(self.tcx, kind, attr); }) } KaniAttributeKind::Solver => { - expect_single(self.tcx, kind, &attrs); + expect_single(self.tcx, kind, attrs); attrs.iter().for_each(|attr| { parse_solver(self.tcx, attr); }) @@ -378,7 +378,7 @@ impl<'tcx> KaniAttributes<'tcx> { parse_stubs(self.tcx, self.item, attrs); } KaniAttributeKind::Unwind => { - expect_single(self.tcx, kind, &attrs); + expect_single(self.tcx, kind, attrs); attrs.iter().for_each(|attr| { parse_unwind(self.tcx, attr); }) @@ -389,7 +389,7 @@ impl<'tcx> KaniAttributes<'tcx> { "`proof` and `proof_for_contract` may not be used on the same function.".to_string(), ); } - expect_single(self.tcx, kind, &attrs); + expect_single(self.tcx, kind, attrs); attrs.iter().for_each(|attr| self.check_proof_attribute(kind, attr)) } KaniAttributeKind::Unstable => attrs.iter().for_each(|attr| { @@ -401,7 +401,7 @@ impl<'tcx> KaniAttributes<'tcx> { "`proof` and `proof_for_contract` may not be used on the same function.".to_string(), ); } - expect_single(self.tcx, kind, &attrs); + expect_single(self.tcx, kind, attrs); attrs.iter().for_each(|attr| self.check_proof_attribute(kind, attr)) } KaniAttributeKind::StubVerified => { @@ -510,14 +510,14 @@ impl<'tcx> KaniAttributes<'tcx> { /// Check that the function specified in the `proof_for_contract` attribute /// is reachable and emit an error if it isn't pub fn check_proof_for_contract(&self, reachable_functions: &HashSet) { - if let Some((symbol, function, span)) = self.interpret_for_contract_attribute() { - if !reachable_functions.contains(&function) { - let err_msg = format!( - "The function specified in the `proof_for_contract` attribute, `{symbol}`, was not found.\ + if let Some((symbol, function, span)) = self.interpret_for_contract_attribute() + && !reachable_functions.contains(&function) + { + let err_msg = format!( + "The function specified in the `proof_for_contract` attribute, `{symbol}`, was not found.\ \nMake sure the function is reachable from the harness." - ); - self.tcx.dcx().span_err(span, err_msg); - } + ); + self.tcx.dcx().span_err(span, err_msg); } } @@ -609,11 +609,11 @@ impl<'tcx> KaniAttributes<'tcx> { if seen.contains(&name) { dcx.struct_span_warn( span, - format!("Multiple occurrences of `stub_verified({})`.", name), + format!("Multiple occurrences of `stub_verified({name})`."), ) .with_span_note( self.tcx.def_span(def_id), - format!("Use a single `stub_verified({})` annotation.", name), + format!("Use a single `stub_verified({name})` annotation."), ) .emit(); } else { @@ -623,8 +623,7 @@ impl<'tcx> KaniAttributes<'tcx> { dcx.struct_span_err( span, format!( - "Target function in `stub_verified({})` has no contract.", - name, + "Target function in `stub_verified({name})` has no contract.", ), ) .with_span_note( @@ -721,7 +720,7 @@ pub fn test_harness_name(tcx: TyCtxt, def: &impl CrateDef) -> String { let def_id = rustc_internal::internal(tcx, def.def_id()); let attrs = tcx.get_attrs_unchecked(def_id); let marker = attr::find_by_name(attrs, rustc_span::symbol::sym::rustc_test_marker).unwrap(); - parse_str_value(&marker).unwrap() + parse_str_value(marker).unwrap() } /// Expect the contents of this attribute to be of the format #[attribute = @@ -749,9 +748,9 @@ fn expect_single<'a>( kind: KaniAttributeKind, attributes: &'a Vec<&'a Attribute>, ) -> &'a Attribute { - let attr = attributes - .first() - .expect(&format!("expected at least one attribute {} in {attributes:?}", kind.as_ref())); + let attr = attributes.first().unwrap_or_else(|| { + panic!("expected at least one attribute {} in {attributes:?}", kind.as_ref()) + }); if attributes.len() > 1 { tcx.dcx().span_err( attr.span(), @@ -1061,7 +1060,7 @@ fn syn_attr(tcx: TyCtxt, attr: &Attribute) -> syn::Attribute { /// Parse a stable attribute using `syn`. fn syn_attr_stable(attr: &AttributeStable) -> syn::Attribute { let parser = syn::Attribute::parse_outer; - parser.parse_str(&attr.as_str()).unwrap().pop().unwrap() + parser.parse_str(attr.as_str()).unwrap().pop().unwrap() } /// Return a more user-friendly string for path by trying to remove unneeded whitespace. @@ -1112,7 +1111,7 @@ pub(crate) fn fn_marker(def: T) -> Option { let marker = def.tool_attrs(&fn_marker).pop()?; let attribute = syn_attr_stable(&marker); let meta_name = attribute.meta.require_name_value().unwrap_or_else(|_| { - panic!("Expected name value attribute for `kanitool::fn_marker`, but found: `{:?}`", marker) + panic!("Expected name value attribute for `kanitool::fn_marker`, but found: `{marker:?}`") }); let Expr::Lit(ExprLit { lit: Lit::Str(lit_str), .. }) = &meta_name.value else { panic!( diff --git a/kani-compiler/src/kani_middle/codegen_units.rs b/kani-compiler/src/kani_middle/codegen_units.rs index c3b6e45c15b9..336c45ab55d6 100644 --- a/kani-compiler/src/kani_middle/codegen_units.rs +++ b/kani-compiler/src/kani_middle/codegen_units.rs @@ -90,6 +90,7 @@ impl CodegenUnits { let (chosen, skipped) = automatic_harness_partition( tcx, args, + &crate_info.name, *kani_fns.get(&KaniModel::Any.into()).unwrap(), ); AUTOHARNESS_MD @@ -97,7 +98,7 @@ impl CodegenUnits { chosen: chosen.iter().map(|func| func.name()).collect::>(), skipped, }) - .expect("Initializing the autoharness metdata failed"); + .expect("Initializing the autoharness metadata failed"); let automatic_harnesses = get_all_automatic_harnesses( tcx, @@ -133,6 +134,10 @@ impl CodegenUnits { self.units.iter() } + pub fn is_automatic_harness(&self, harness: &Harness) -> bool { + self.harness_info.get(harness).is_some_and(|md| md.is_automatically_generated) + } + /// We store which instance of modifies was generated. pub fn store_modifies(&mut self, harness_modifies: &[(Harness, AssignsContract)]) { for (harness, modifies) in harness_modifies { @@ -229,10 +234,10 @@ fn extract_contracts( ) -> BTreeSet { let def = harness.def; let mut result = BTreeSet::new(); - if let HarnessKind::ProofForContract { target_fn } = &metadata.attributes.kind { - if let Ok(check_def) = expect_resolve_fn(tcx, def, target_fn, "proof_for_contract") { - result.insert(ContractUsage::Check(check_def.def_id().to_index())); - } + if let HarnessKind::ProofForContract { target_fn } = &metadata.attributes.kind + && let Ok(check_def) = expect_resolve_fn(tcx, def, target_fn, "proof_for_contract") + { + result.insert(ContractUsage::Check(check_def.def_id().to_index())); } for stub in &metadata.attributes.verified_stubs { @@ -313,7 +318,7 @@ fn get_all_manual_harnesses( harnesses .into_iter() .map(|harness| { - let metadata = gen_proof_metadata(tcx, harness, &base_filename); + let metadata = gen_proof_metadata(tcx, harness, base_filename); (harness, metadata) }) .collect::>() @@ -340,7 +345,12 @@ fn get_all_automatic_harnesses( &GenericArgs(vec![GenericArgKind::Type(fn_to_verify.ty())]), ) .unwrap(); - let metadata = gen_automatic_proof_metadata(tcx, &base_filename, &fn_to_verify); + let metadata = gen_automatic_proof_metadata( + tcx, + base_filename, + &fn_to_verify, + harness.mangled_name(), + ); (harness, metadata) }) .collect::>() @@ -351,6 +361,7 @@ fn get_all_automatic_harnesses( fn automatic_harness_partition( tcx: TyCtxt, args: &Arguments, + crate_name: &str, kani_any_def: FnDef, ) -> (Vec, BTreeMap) { // If `filter_list` contains `name`, either as an exact match or a substring. @@ -375,7 +386,8 @@ fn automatic_harness_partition( return Some(AutoHarnessSkipReason::NoBody); } - let name = instance.name(); + // Preprend the crate name so that users can filter out entire crates using the existing function filter flags. + let name = format!("{crate_name}::{}", instance.name()); let body = instance.body().unwrap(); if is_proof_harness(tcx, instance) @@ -385,12 +397,34 @@ fn automatic_harness_partition( return Some(AutoHarnessSkipReason::KaniImpl); } - if (!args.autoharness_included_functions.is_empty() - && !filter_contains(&name, &args.autoharness_included_functions)) - || (!args.autoharness_excluded_functions.is_empty() - && filter_contains(&name, &args.autoharness_excluded_functions)) - { - return Some(AutoHarnessSkipReason::UserFilter); + match ( + args.autoharness_included_patterns.is_empty(), + args.autoharness_excluded_patterns.is_empty(), + ) { + // If no filters were specified, then continue. + (true, true) => {} + // If only --exclude-pattern was provided, filter out the function if excluded_patterns contains its name. + (true, false) => { + if filter_contains(&name, &args.autoharness_excluded_patterns) { + return Some(AutoHarnessSkipReason::UserFilter); + } + } + // If only --include-pattern was provided, filter out the function if included_patterns does not contain its name. + (false, true) => { + if !filter_contains(&name, &args.autoharness_included_patterns) { + return Some(AutoHarnessSkipReason::UserFilter); + } + } + // If both are specified, filter out the function if included_patterns does not contain its name. + // Then, filter out any functions that excluded_patterns does match. + // This order is important, since it preserves the semantics described in kani_driver::autoharness_args where exclude takes precedence over include. + (false, false) => { + if !filter_contains(&name, &args.autoharness_included_patterns) + || filter_contains(&name, &args.autoharness_excluded_patterns) + { + return Some(AutoHarnessSkipReason::UserFilter); + } + } } // Each argument of `instance` must implement Arbitrary. @@ -407,7 +441,7 @@ fn automatic_harness_partition( &kani_any_body.blocks[0].terminator.kind { if let Some((def, args)) = func.ty(body.arg_locals()).unwrap().kind().fn_def() { - Instance::resolve(def, &args).is_ok() + Instance::resolve(def, args).is_ok() } else { false } diff --git a/kani-compiler/src/kani_middle/coercion.rs b/kani-compiler/src/kani_middle/coercion.rs index afb0ff5e6c73..5fe1e50948a5 100644 --- a/kani-compiler/src/kani_middle/coercion.rs +++ b/kani-compiler/src/kani_middle/coercion.rs @@ -90,14 +90,12 @@ pub fn extract_unsize_casting<'tcx>( .last() .unwrap(); // Extract the pointee type that is being coerced. - let src_pointee_ty = extract_pointee(tcx, coerce_info.src_ty).expect(&format!( - "Expected source to be a pointer. Found {:?} instead", - coerce_info.src_ty - )); - let dst_pointee_ty = extract_pointee(tcx, coerce_info.dst_ty).expect(&format!( - "Expected destination to be a pointer. Found {:?} instead", - coerce_info.dst_ty - )); + let src_pointee_ty = extract_pointee(tcx, coerce_info.src_ty).unwrap_or_else(|| { + panic!("Expected source to be a pointer. Found {:?} instead", coerce_info.src_ty) + }); + let dst_pointee_ty = extract_pointee(tcx, coerce_info.dst_ty).unwrap_or_else(|| { + panic!("Expected destination to be a pointer. Found {:?} instead", coerce_info.dst_ty) + }); // Find the tail of the coercion that determines the type of metadata to be stored. let (src_base_ty, dst_base_ty) = tcx.struct_lockstep_tails_for_codegen( src_pointee_ty, @@ -222,8 +220,8 @@ impl Iterator for CoerceUnsizedIterator<'_> { let coerce_index = coerce_index.as_usize(); assert!(coerce_index < src_fields.len()); - self.src_ty = Some(src_fields[coerce_index].ty_with_args(&src_args)); - self.dst_ty = Some(dst_fields[coerce_index].ty_with_args(&dst_args)); + self.src_ty = Some(src_fields[coerce_index].ty_with_args(src_args)); + self.dst_ty = Some(dst_fields[coerce_index].ty_with_args(dst_args)); Some(src_fields[coerce_index].name.clone()) } _ => { diff --git a/kani-compiler/src/kani_middle/intrinsics.rs b/kani-compiler/src/kani_middle/intrinsics.rs index a53abef90e88..7013feaf34d9 100644 --- a/kani-compiler/src/kani_middle/intrinsics.rs +++ b/kani-compiler/src/kani_middle/intrinsics.rs @@ -108,10 +108,10 @@ fn resolve_rust_intrinsic<'tcx>( tcx: TyCtxt<'tcx>, func_ty: Ty<'tcx>, ) -> Option<(IntrinsicDef, GenericArgsRef<'tcx>)> { - if let ty::FnDef(def_id, args) = *func_ty.kind() { - if let Some(symbol) = tcx.intrinsic(def_id) { - return Some((symbol, args)); - } + if let ty::FnDef(def_id, args) = *func_ty.kind() + && let Some(symbol) = tcx.intrinsic(def_id) + { + return Some((symbol, args)); } None } diff --git a/kani-compiler/src/kani_middle/kani_functions.rs b/kani-compiler/src/kani_middle/kani_functions.rs index 85936041cc38..f1d064ed08eb 100644 --- a/kani-compiler/src/kani_middle/kani_functions.rs +++ b/kani-compiler/src/kani_middle/kani_functions.rs @@ -133,6 +133,10 @@ pub enum KaniHook { Check, #[strum(serialize = "CoverHook")] Cover, + #[strum(serialize = "ExistsHook")] + Exists, + #[strum(serialize = "ForallHook")] + Forall, // TODO: this is temporarily implemented as a hook, but should be implemented as an intrinsic #[strum(serialize = "FloatToIntInRangeHook")] FloatToIntInRange, diff --git a/kani-compiler/src/kani_middle/metadata.rs b/kani-compiler/src/kani_middle/metadata.rs index 50487ce45714..4a7a0aa07b94 100644 --- a/kani-compiler/src/kani_middle/metadata.rs +++ b/kani-compiler/src/kani_middle/metadata.rs @@ -112,12 +112,13 @@ pub fn gen_contracts_metadata( /// Generate metadata for automatically generated harnesses. /// For now, we just use the data from the function we are verifying; since we only generate one automatic harness per function, /// the metdata from that function uniquely identifies the harness. -/// In future iterations of this feature, we will likely have multiple harnesses for a single function (e.g., for generic functions), +/// TODO: In future iterations of this feature, we will likely have multiple harnesses for a single function (e.g., for generic functions), /// in which case HarnessMetadata will need to change further to differentiate between those harnesses. pub fn gen_automatic_proof_metadata( tcx: TyCtxt, base_name: &Path, fn_to_verify: &Instance, + harness_mangled_name: String, ) -> HarnessMetadata { let def = fn_to_verify.def; let pretty_name = fn_to_verify.name(); @@ -137,8 +138,10 @@ pub fn gen_automatic_proof_metadata( }; HarnessMetadata { + // pretty_name is what gets displayed to the user, and that should be the name of the function being verified, hence using fn_to_verify name pretty_name, - mangled_name, + // We pass --function mangled_name to CBMC to select the entry point, which should be the mangled name of the automatic harness intrinsic + mangled_name: harness_mangled_name, crate_name: def.krate().name, original_file: loc.filename, original_start_line: loc.start_line, diff --git a/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs index 2008ef1eb0b8..9276ae03e744 100644 --- a/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs +++ b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs @@ -361,7 +361,7 @@ fn try_resolve_instance<'tcx>( tcx, TypingEnv::fully_monomorphized(), *def, - &args, + args, DUMMY_SP, )) } diff --git a/kani-compiler/src/kani_middle/points_to/points_to_graph.rs b/kani-compiler/src/kani_middle/points_to/points_to_graph.rs index 6e473a84eecc..f27c53fcc3b0 100644 --- a/kani-compiler/src/kani_middle/points_to/points_to_graph.rs +++ b/kani-compiler/src/kani_middle/points_to/points_to_graph.rs @@ -187,7 +187,7 @@ impl<'tcx> PointsToGraph<'tcx> { /// Dump the graph into a file using the graphviz format for later visualization. pub fn dump(&self, file_path: &str) { let mut nodes: Vec = - self.nodes.keys().map(|from| format!("\t\"{:?}\"", from)).collect(); + self.nodes.keys().map(|from| format!("\t\"{from:?}\"")).collect(); nodes.sort(); let nodes_str = nodes.join("\n"); @@ -195,9 +195,9 @@ impl<'tcx> PointsToGraph<'tcx> { .nodes .iter() .flat_map(|(from, to)| { - let from = format!("\"{:?}\"", from); + let from = format!("\"{from:?}\""); to.successors.iter().map(move |to| { - let to = format!("\"{:?}\"", to); + let to = format!("\"{to:?}\""); format!("\t{} -> {}", from.clone(), to) }) }) @@ -205,7 +205,7 @@ impl<'tcx> PointsToGraph<'tcx> { edges.sort(); let edges_str = edges.join("\n"); - std::fs::write(file_path, format!("digraph {{\n{}\n{}\n}}", nodes_str, edges_str)).unwrap(); + std::fs::write(file_path, format!("digraph {{\n{nodes_str}\n{edges_str}\n}}")).unwrap(); } /// Find a transitive closure of the graph starting from a set of given locations; this also diff --git a/kani-compiler/src/kani_middle/reachability.rs b/kani-compiler/src/kani_middle/reachability.rs index 237fbf5f8b41..5ccd611cbeab 100644 --- a/kani-compiler/src/kani_middle/reachability.rs +++ b/kani-compiler/src/kani_middle/reachability.rs @@ -121,14 +121,14 @@ where // Filter regular items. for item in crate_items { // Only collect monomorphic items. - if let Ok(instance) = Instance::try_from(item) { - if predicate(tcx, instance) { - let body = transformer.body(tcx, instance); - let mut collector = - MonoItemsFnCollector { tcx, body: &body, collected: FxHashSet::default() }; - collector.visit_body(&body); - roots.extend(collector.collected.into_iter()); - } + if let Ok(instance) = Instance::try_from(item) + && predicate(tcx, instance) + { + let body = transformer.body(tcx, instance); + let mut collector = + MonoItemsFnCollector { tcx, body: &body, collected: FxHashSet::default() }; + collector.visit_body(&body); + roots.extend(collector.collected.into_iter()); } } roots.into_iter().map(|root| root.item).collect() @@ -443,7 +443,7 @@ impl MirVisitor for MonoItemsFnCollector<'_, '_> { return; } }; - self.collect_allocation(&allocation); + self.collect_allocation(allocation); } /// Collect function calls. diff --git a/kani-compiler/src/kani_middle/resolve.rs b/kani-compiler/src/kani_middle/resolve.rs index ea51e80fbf81..060f68ac88f7 100644 --- a/kani-compiler/src/kani_middle/resolve.rs +++ b/kani-compiler/src/kani_middle/resolve.rs @@ -59,14 +59,14 @@ pub fn resolve_fn_path<'tcx>( match &path.qself { // Qualified path for a trait method implementation, like `::bar`. Some(QSelf { ty: syn_ty, position, .. }) if *position > 0 => { - let ty = type_resolution::resolve_ty(tcx, current_module, &syn_ty)?; + let ty = type_resolution::resolve_ty(tcx, current_module, syn_ty)?; let def_id = resolve_path(tcx, current_module, &path.path)?; validate_kind!(tcx, def_id, "function / method", DefKind::Fn | DefKind::AssocFn)?; Ok(FnResolution::FnImpl { def: stable_fn_def(tcx, def_id).unwrap(), ty }) } // Qualified path for a primitive type, such as `<[u8]::sort>`. Some(QSelf { ty: syn_ty, .. }) if type_resolution::is_type_primitive(syn_ty) => { - let ty = type_resolution::resolve_ty(tcx, current_module, &syn_ty)?; + let ty = type_resolution::resolve_ty(tcx, current_module, syn_ty)?; let resolved = resolve_in_primitive(tcx, ty, path.path.segments.iter())?; if resolved.segments.is_empty() { Ok(FnResolution::Fn(stable_fn_def(tcx, resolved.base).unwrap())) @@ -76,7 +76,7 @@ pub fn resolve_fn_path<'tcx>( } // Qualified path for a non-primitive type, such as `::foo>`. Some(QSelf { ty: syn_ty, .. }) => { - let ty = type_resolution::resolve_ty(tcx, current_module, &syn_ty)?; + let ty = type_resolution::resolve_ty(tcx, current_module, syn_ty)?; let def_id = resolve_in_user_type(tcx, ty, path.path.segments.iter())?; validate_kind!(tcx, def_id, "function / method", DefKind::Fn | DefKind::AssocFn)?; Ok(FnResolution::Fn(stable_fn_def(tcx, def_id).unwrap())) @@ -708,6 +708,8 @@ fn is_item_name(tcx: TyCtxt, item: DefId, name: &str) -> bool { last == name } +/// Use this when we don't just care about the item name matching (c.f. is_item_name), +/// but also if the generic arguments are the same, e.g. ::unchecked_add. fn is_item_name_with_generic_args( tcx: TyCtxt, item: DefId, @@ -715,6 +717,54 @@ fn is_item_name_with_generic_args( name: &str, ) -> bool { let item_path = tcx.def_path_str(item); - let all_but_base_type = item_path.find("::").map_or("", |idx| &item_path[idx..]); - all_but_base_type == format!("{}::{}", generic_args, name) + last_two_items_of_path_match(&item_path, generic_args, name) +} + +// This is just a helper function for is_item_name_with_generic_args. +// It's in a separate function so we can unit-test it without a mock TyCtxt or DefIds. +fn last_two_items_of_path_match(item_path: &str, generic_args: &str, name: &str) -> bool { + let parts: Vec<&str> = item_path.split("::").collect(); + + if parts.len() < 2 { + return false; + } + + let actual_last_two = + format!("{}{}{}{}", "::", parts[parts.len() - 2], "::", parts[parts.len() - 1]); + + let last_two = format!("{}{}{}", generic_args, "::", name); + + // The last two components of the item_path should be the same as ::{generic_args}::{name} + last_two == actual_last_two +} + +#[cfg(test)] +mod tests { + mod simple_last_two_items_of_path_match { + use crate::kani_middle::resolve::last_two_items_of_path_match; + + #[test] + fn length_one_item_prefix() { + let generic_args = "::"; + let name = "unchecked_add"; + let item_path = format!("NonZero{generic_args}::{name}"); + assert!(last_two_items_of_path_match(&item_path, generic_args, name)) + } + + #[test] + fn length_three_item_prefix() { + let generic_args = "::"; + let name = "unchecked_add"; + let item_path = format!("core::num::NonZero{generic_args}::{name}"); + assert!(last_two_items_of_path_match(&item_path, generic_args, name)) + } + + #[test] + fn wrong_generic_arg() { + let generic_args = "::"; + let name = "unchecked_add"; + let item_path = format!("core::num::NonZero{}::{}", "::", name); + assert!(!last_two_items_of_path_match(&item_path, generic_args, name)) + } + } } diff --git a/kani-compiler/src/kani_middle/resolve/type_resolution.rs b/kani-compiler/src/kani_middle/resolve/type_resolution.rs index c8951bf1e263..50183634637d 100644 --- a/kani-compiler/src/kani_middle/resolve/type_resolution.rs +++ b/kani-compiler/src/kani_middle/resolve/type_resolution.rs @@ -76,7 +76,7 @@ pub fn resolve_ty<'tcx>( let elems = tuple .elems .iter() - .map(|elem| resolve_ty(tcx, current_module, &elem)) + .map(|elem| resolve_ty(tcx, current_module, elem)) .collect::, _>>()?; Ok(Ty::new_tuple(&elems)) } @@ -191,12 +191,11 @@ pub(super) fn is_type_primitive(typ: &syn::Type) -> bool { /// Parse the length of the array. /// We currently only support a constant length. fn parse_len(len: &Expr) -> Result { - if let Expr::Lit(ExprLit { lit: Lit::Int(lit), .. }) = len { - if matches!(lit.suffix(), "" | "usize") - && let Ok(val) = usize::from_str(lit.base10_digits()) - { - return Ok(val); - } + if let Expr::Lit(ExprLit { lit: Lit::Int(lit), .. }) = len + && matches!(lit.suffix(), "" | "usize") + && let Ok(val) = usize::from_str(lit.base10_digits()) + { + return Ok(val); } Err(format!("Expected a `usize` constant, but found `{}`", len.to_token_stream())) } diff --git a/kani-compiler/src/kani_middle/stubbing/annotations.rs b/kani-compiler/src/kani_middle/stubbing/annotations.rs index 9fc7be491d85..e75cd04aa53f 100644 --- a/kani-compiler/src/kani_middle/stubbing/annotations.rs +++ b/kani-compiler/src/kani_middle/stubbing/annotations.rs @@ -45,18 +45,18 @@ pub fn update_stub_mapping( ) { if let Some((orig_id, stub_id)) = stub_def_ids(tcx, harness, stub) { let other_opt = stub_pairs.insert(orig_id, stub_id); - if let Some(other) = other_opt { - if other != stub_id { - tcx.dcx().span_err( - tcx.def_span(harness), - format!( - "duplicate stub mapping: {} mapped to {} and {}", - tcx.def_path_str(orig_id), - tcx.def_path_str(stub_id), - tcx.def_path_str(other) - ), - ); - } + if let Some(other) = other_opt + && other != stub_id + { + tcx.dcx().span_err( + tcx.def_span(harness), + format!( + "duplicate stub mapping: {} mapped to {} and {}", + tcx.def_path_str(orig_id), + tcx.def_path_str(stub_id), + tcx.def_path_str(other) + ), + ); } } } diff --git a/kani-compiler/src/kani_middle/transform/automatic.rs b/kani-compiler/src/kani_middle/transform/automatic.rs index 705a8ac26a88..2eaf82078287 100644 --- a/kani-compiler/src/kani_middle/transform/automatic.rs +++ b/kani-compiler/src/kani_middle/transform/automatic.rs @@ -7,21 +7,24 @@ //! then transform its body to be a harness for that function. use crate::args::ReachabilityType; +use crate::kani_middle::attributes::KaniAttributes; use crate::kani_middle::codegen_units::CodegenUnit; -use crate::kani_middle::kani_functions::{KaniIntrinsic, KaniModel}; +use crate::kani_middle::kani_functions::{KaniHook, KaniIntrinsic, KaniModel}; use crate::kani_middle::transform::body::{InsertPosition, MutableBody, SourceInstruction}; use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; use rustc_middle::ty::TyCtxt; +use stable_mir::CrateDef; use stable_mir::mir::mono::Instance; -use stable_mir::mir::{Body, Operand, Place, TerminatorKind}; -use stable_mir::ty::{FnDef, GenericArgKind, GenericArgs}; +use stable_mir::mir::{Body, Mutability, Operand, Place, TerminatorKind}; +use stable_mir::ty::{FnDef, GenericArgKind, GenericArgs, RigidTy, Ty}; use tracing::debug; #[derive(Debug)] pub struct AutomaticHarnessPass { /// The FnDef of KaniModel::Any kani_any: FnDef, + init_contracts_hook: Instance, /// All of the automatic harness Instances that we generated in the CodegenUnits constructor automatic_harnesses: Vec, } @@ -37,6 +40,9 @@ impl AutomaticHarnessPass { let kani_fns = query_db.kani_functions(); let harness_intrinsic = *kani_fns.get(&KaniIntrinsic::AutomaticHarness.into()).unwrap(); let kani_any = *kani_fns.get(&KaniModel::Any.into()).unwrap(); + let init_contracts_hook = *kani_fns.get(&KaniHook::InitContracts.into()).unwrap(); + let init_contracts_hook = + Instance::resolve(init_contracts_hook, &GenericArgs(vec![])).unwrap(); let automatic_harnesses = unit .harnesses .iter() @@ -46,7 +52,7 @@ impl AutomaticHarnessPass { def == harness_intrinsic }) .collect::>(); - Self { kani_any, automatic_harnesses } + Self { kani_any, init_contracts_hook, automatic_harnesses } } } @@ -65,7 +71,7 @@ impl TransformPass for AutomaticHarnessPass { matches!(query_db.args().reachability_analysis, ReachabilityType::AllFns) } - fn transform(&mut self, _tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { + fn transform(&mut self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { debug!(function=?instance.name(), "AutomaticHarnessPass::transform"); if !self.automatic_harnesses.contains(&instance) { @@ -76,13 +82,30 @@ impl TransformPass for AutomaticHarnessPass { // and then resolve `fn_to_verify`. let kind = instance.args().0[0].expect_ty().kind(); let (def, args) = kind.fn_def().unwrap(); - let fn_to_verify = Instance::resolve(def, &args).unwrap(); + let fn_to_verify = Instance::resolve(def, args).unwrap(); let fn_to_verify_body = fn_to_verify.body().unwrap(); let mut harness_body = MutableBody::from(body); harness_body.clear_body(TerminatorKind::Return); let mut source = SourceInstruction::Terminator { bb: 0 }; + // Contract harnesses need a free(NULL) statement, c.f. kani_core::init_contracts(). + let attrs = KaniAttributes::for_def_id(tcx, def.def_id()); + if attrs.has_contract() { + let ret_local = harness_body.new_local( + Ty::from_rigid_kind(RigidTy::Tuple(vec![])), + source.span(harness_body.blocks()), + Mutability::Not, + ); + harness_body.insert_call( + &self.init_contracts_hook, + &mut source, + InsertPosition::Before, + vec![], + Place::from(ret_local), + ); + } + let mut arg_locals = vec![]; // For each argument of `fn_to_verify`, create a nondeterministic value of its type diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs index 6f7b59041d12..60b99e5cf13d 100644 --- a/kani-compiler/src/kani_middle/transform/body.rs +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // //! Utility functions that allow us to modify a function body. +//! +//! TODO: We should not need this code anymore now that https://github.com/rust-lang/rust/pull/138536 is merged use crate::kani_middle::kani_functions::KaniHook; use crate::kani_queries::QueryDb; @@ -54,12 +56,10 @@ impl MutableBody { &self.locals } - #[allow(dead_code)] pub fn arg_count(&self) -> usize { self.arg_count } - #[allow(dead_code)] pub fn var_debug_info(&self) -> &Vec { &self.var_debug_info } @@ -328,7 +328,6 @@ impl MutableBody { /// `InsertPosition` is `InsertPosition::Before`, `source` will point to the same instruction as /// before. If `InsertPosition` is `InsertPosition::After`, `source` will point to the /// terminator of the newly inserted basic block. - #[allow(dead_code)] pub fn insert_bb( &mut self, mut bb: BasicBlock, diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/initial_target_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/initial_target_visitor.rs index dfd16594146f..7fa3315577cb 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/initial_target_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/initial_target_visitor.rs @@ -79,10 +79,10 @@ impl MirVisitor for InitialTargetVisitor { RigidTy::RawPtr(ty, _) | RigidTy::Ref(_, ty, _) => Some(ty), _ => None, }; - if let (Some(from_ty), Some(to_ty)) = (from_ty, to_ty) { - if !tys_layout_equal_to_size(from_ty, to_ty) { - self.push_operand(operand); - } + if let (Some(from_ty), Some(to_ty)) = (from_ty, to_ty) + && !tys_layout_equal_to_size(from_ty, to_ty) + { + self.push_operand(operand); } } _ => {} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs index ea50654ddcec..1b6ce5228a10 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs @@ -123,7 +123,7 @@ impl MirVisitor for InstrumentationVisitor<'_, '_> { let needs_get = { self.points_to .resolve_place_stable(place.clone(), self.current_instance, self.tcx) - .intersection(&self.analysis_targets) + .intersection(self.analysis_targets) .next() .is_some() }; diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index e7178e2a7eb0..9736d3d43005 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -214,7 +214,7 @@ impl<'a> UninitInstrumenter<'a> { let ptr_operand = operation.mk_operand(body, &mut statements, source); let terminator = match pointee_info.layout() { PointeeLayout::Sized { layout } => { - let layout_operand = mk_layout_operand(body, &mut statements, source, &layout); + let layout_operand = mk_layout_operand(body, &mut statements, source, layout); // Depending on whether accessing the known number of elements in the slice, need to // pass is as an argument. let (diagnostic, args) = match &operation { @@ -232,7 +232,7 @@ impl<'a> UninitInstrumenter<'a> { _ => unreachable!(), }; let is_ptr_initialized_instance = resolve_mem_init_fn( - get_mem_init_fn_def(diagnostic, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(diagnostic, self.mem_init_fn_cache), layout.len(), *pointee_info.ty(), ); @@ -263,12 +263,12 @@ impl<'a> UninitInstrumenter<'a> { _ => unreachable!(), }; let is_ptr_initialized_instance = resolve_mem_init_fn( - get_mem_init_fn_def(diagnostic, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(diagnostic, self.mem_init_fn_cache), element_layout.len(), slicee_ty, ); let layout_operand = - mk_layout_operand(body, &mut statements, source, &element_layout); + mk_layout_operand(body, &mut statements, source, element_layout); Terminator { kind: TerminatorKind::Call { func: Operand::Copy(Place::from(body.new_local( @@ -342,7 +342,7 @@ impl<'a> UninitInstrumenter<'a> { let value = operation.expect_value(); let terminator = match pointee_info.layout() { PointeeLayout::Sized { layout } => { - let layout_operand = mk_layout_operand(body, &mut statements, source, &layout); + let layout_operand = mk_layout_operand(body, &mut statements, source, layout); // Depending on whether writing to the known number of elements in the slice, need to // pass is as an argument. let (diagnostic, args) = match &operation { @@ -376,7 +376,7 @@ impl<'a> UninitInstrumenter<'a> { _ => unreachable!(), }; let set_ptr_initialized_instance = resolve_mem_init_fn( - get_mem_init_fn_def(diagnostic, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(diagnostic, self.mem_init_fn_cache), layout.len(), *pointee_info.ty(), ); @@ -407,12 +407,12 @@ impl<'a> UninitInstrumenter<'a> { _ => unreachable!(), }; let set_ptr_initialized_instance = resolve_mem_init_fn( - get_mem_init_fn_def(diagnostic, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(diagnostic, self.mem_init_fn_cache), element_layout.len(), slicee_ty, ); let layout_operand = - mk_layout_operand(body, &mut statements, source, &element_layout); + mk_layout_operand(body, &mut statements, source, element_layout); Terminator { kind: TerminatorKind::Call { func: Operand::Copy(Place::from(body.new_local( @@ -471,7 +471,7 @@ impl<'a> UninitInstrumenter<'a> { }), ]; let set_ptr_initialized_instance = resolve_mem_init_fn( - get_mem_init_fn_def(diagnostic, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(diagnostic, self.mem_init_fn_cache), layout.len(), *pointee_info.ty(), ); @@ -509,7 +509,7 @@ impl<'a> UninitInstrumenter<'a> { }; let layout_size = pointee_info.layout().maybe_size().unwrap(); let copy_init_state_instance = resolve_mem_init_fn( - get_mem_init_fn_def(KANI_COPY_INIT_STATE, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(KANI_COPY_INIT_STATE, self.mem_init_fn_cache), layout_size, *pointee_info.ty(), ); @@ -547,7 +547,7 @@ impl<'a> UninitInstrumenter<'a> { _ => unreachable!(), }; let argument_operation_instance = resolve_mem_init_fn( - get_mem_init_fn_def(diagnostic, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(diagnostic, self.mem_init_fn_cache), layout_size, *pointee_info.ty(), ); @@ -595,7 +595,7 @@ impl<'a> UninitInstrumenter<'a> { let mut statements = vec![]; let layout_size = pointee_info.layout().maybe_size().unwrap(); let copy_init_state_instance = resolve_mem_init_fn( - get_mem_init_fn_def(KANI_COPY_INIT_STATE_SINGLE, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(KANI_COPY_INIT_STATE_SINGLE, self.mem_init_fn_cache), layout_size, *pointee_info.ty(), ); diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs index 9ae4ffd79ad6..1bd4566ab96a 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs @@ -136,18 +136,17 @@ impl MirVisitor for CheckUninitVisitor { for projection_elem in place_without_deref.projection.iter() { // If the projection is Deref and the current type is raw pointer, check // if it points to initialized memory. - if *projection_elem == ProjectionElem::Deref { - if let TyKind::RigidTy(RigidTy::RawPtr(..)) = - place_to_add_projections.ty(&&self.locals).unwrap().kind() - { - self.push_target(MemoryInitOp::Check { - operand: Operand::Copy(place_to_add_projections.clone()), - }); - }; - } + if *projection_elem == ProjectionElem::Deref + && let TyKind::RigidTy(RigidTy::RawPtr(..)) = + place_to_add_projections.ty(&self.locals).unwrap().kind() + { + self.push_target(MemoryInitOp::Check { + operand: Operand::Copy(place_to_add_projections.clone()), + }); + }; place_to_add_projections.projection.push(projection_elem.clone()); } - if place_without_deref.ty(&&self.locals).unwrap().kind().is_raw_ptr() { + if place_without_deref.ty(&self.locals).unwrap().kind().is_raw_ptr() { self.push_target(MemoryInitOp::Set { operand: Operand::Copy(place_without_deref), value: true, @@ -156,14 +155,14 @@ impl MirVisitor for CheckUninitVisitor { } } // Check whether Rvalue creates a new initialized pointer previously not captured inside shadow memory. - if place.ty(&self.locals).unwrap().kind().is_raw_ptr() { - if let Rvalue::AddressOf(..) = rvalue { - self.push_target(MemoryInitOp::Set { - operand: Operand::Copy(place.clone()), - value: true, - position: InsertPosition::After, - }); - } + if place.ty(&self.locals).unwrap().kind().is_raw_ptr() + && let Rvalue::AddressOf(..) = rvalue + { + self.push_target(MemoryInitOp::Set { + operand: Operand::Copy(place.clone()), + value: true, + position: InsertPosition::After, + }); } // TODO: add support for ADTs which could have unions as subfields. Currently, @@ -407,13 +406,13 @@ impl MirVisitor for CheckUninitVisitor { } TerminatorKind::Drop { place, .. } => { self.super_terminator(term, location); - let place_ty = place.ty(&&self.locals).unwrap(); + let place_ty = place.ty(&self.locals).unwrap(); // When drop is codegen'ed for types that could define their own dropping // behavior, a reference is taken to the place which is later implicitly coerced // to a pointer. Hence, we need to bless this pointer as initialized. match place - .ty(&&self.locals) + .ty(&self.locals) .unwrap() .kind() .rigid() @@ -496,20 +495,20 @@ impl MirVisitor for CheckUninitVisitor { } fn visit_operand(&mut self, operand: &Operand, location: Location) { - if let Operand::Constant(constant) = operand { - if let ConstantKind::Allocated(allocation) = constant.const_.kind() { - for (_, prov) in &allocation.provenance.ptrs { - if let GlobalAlloc::Static(_) = GlobalAlloc::from(prov.0) { - if constant.ty().kind().is_raw_ptr() { - // If a static is a raw pointer, need to mark it as initialized. - self.push_target(MemoryInitOp::Set { - operand: Operand::Constant(constant.clone()), - value: true, - position: InsertPosition::Before, - }); - } - }; - } + if let Operand::Constant(constant) = operand + && let ConstantKind::Allocated(allocation) = constant.const_.kind() + { + for (_, prov) in &allocation.provenance.ptrs { + if let GlobalAlloc::Static(_) = GlobalAlloc::from(prov.0) + && constant.ty().kind().is_raw_ptr() + { + // If a static is a raw pointer, need to mark it as initialized. + self.push_target(MemoryInitOp::Set { + operand: Operand::Constant(constant.clone()), + value: true, + position: InsertPosition::Before, + }); + }; } } self.super_operand(operand, location); @@ -519,17 +518,17 @@ impl MirVisitor for CheckUninitVisitor { if let Rvalue::Cast(cast_kind, operand, ty) = rvalue { match cast_kind { CastKind::PointerCoercion(PointerCoercion::Unsize) => { - if let TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) = ty.kind() { - if pointee_ty.kind().is_trait() { - self.push_target(MemoryInitOp::Unsupported { + if let TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) = ty.kind() + && pointee_ty.kind().is_trait() + { + self.push_target(MemoryInitOp::Unsupported { reason: "Kani does not support reasoning about memory initialization of unsized pointers.".to_string(), }); - } } } CastKind::Transmute => { let operand_ty = operand.ty(&self.locals).unwrap(); - if !tys_layout_compatible_to_size(&operand_ty, &ty) { + if !tys_layout_compatible_to_size(&operand_ty, ty) { // If transmuting between two types of incompatible layouts, padding // bytes are exposed, which is UB. self.push_target(MemoryInitOp::TriviallyUnsafe { diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index 73dce8c2e898..20a98d1b8e91 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -210,7 +210,7 @@ fn data_bytes_for_ty( Ok(result) } FieldsShape::Arbitrary { ref offsets } => { - match ty.kind().rigid().expect(&format!("unexpected type: {ty:?}")) { + match ty.kind().rigid().unwrap_or_else(|| panic!("unexpected type: {ty:?}")) { RigidTy::Adt(def, args) => { match def.kind() { AdtKind::Enum => { @@ -224,7 +224,7 @@ fn data_bytes_for_ty( let mut fields_data_bytes = vec![]; for idx in layout.fields.fields_by_offset_order() { let field_offset = offsets[idx].bytes(); - let field_ty = fields[idx].ty_with_args(&args); + let field_ty = fields[idx].ty_with_args(args); fields_data_bytes.append(&mut data_bytes_for_ty( machine_info, field_ty, @@ -241,7 +241,7 @@ fn data_bytes_for_ty( // Retrieve data bytes for the tag. let tag_size = match tag { Scalar::Initialized { value, .. } => { - value.size(&machine_info) + value.size(machine_info) } Scalar::Union { .. } => { unreachable!("Enum tag should not be a union.") @@ -266,7 +266,7 @@ fn data_bytes_for_ty( }; for field_idx in variant.fields.fields_by_offset_order() { let field_offset = field_offsets[field_idx].bytes(); - let field_ty = fields[field_idx].ty_with_args(&args); + let field_ty = fields[field_idx].ty_with_args(args); field_data_bytes_for_variant.append( &mut data_bytes_for_ty( machine_info, @@ -321,7 +321,7 @@ fn data_bytes_for_ty( let fields = def.variants_iter().next().unwrap().fields(); for idx in layout.fields.fields_by_offset_order() { let field_offset = offsets[idx].bytes(); - let field_ty = fields[idx].ty_with_args(&args); + let field_ty = fields[idx].ty_with_args(args); struct_data_bytes.append(&mut data_bytes_for_ty( machine_info, field_ty, diff --git a/kani-compiler/src/kani_middle/transform/check_values.rs b/kani-compiler/src/kani_middle/transform/check_values.rs index b536de1e2695..26c337446ad6 100644 --- a/kani-compiler/src/kani_middle/transform/check_values.rs +++ b/kani-compiler/src/kani_middle/transform/check_values.rs @@ -953,7 +953,7 @@ pub fn ty_validity_per_offset( Ok(result) } FieldsShape::Arbitrary { ref offsets } => { - match ty.kind().rigid().expect(&format!("unexpected type: {ty:?}")) { + match ty.kind().rigid().unwrap_or_else(|| panic!("unexpected type: {ty:?}")) { RigidTy::Adt(def, args) => { match def.kind() { AdtKind::Enum => { @@ -967,7 +967,7 @@ pub fn ty_validity_per_offset( let mut fields_validity = vec![]; for idx in layout.fields.fields_by_offset_order() { let field_offset = offsets[idx].bytes(); - let field_ty = fields[idx].ty_with_args(&args); + let field_ty = fields[idx].ty_with_args(args); fields_validity.append(&mut ty_validity_per_offset( machine_info, field_ty, @@ -989,7 +989,7 @@ pub fn ty_validity_per_offset( let fields = ty_variants[index].fields(); for field_idx in variant.fields.fields_by_offset_order() { let field_offset = offsets[field_idx].bytes(); - let field_ty = fields[field_idx].ty_with_args(&args); + let field_ty = fields[field_idx].ty_with_args(args); fields_validity.append(&mut ty_validity_per_offset( machine_info, field_ty, @@ -1015,7 +1015,7 @@ pub fn ty_validity_per_offset( let fields = def.variants_iter().next().unwrap().fields(); for idx in layout.fields.fields_by_offset_order() { let field_offset = offsets[idx].bytes(); - let field_ty = fields[idx].ty_with_args(&args); + let field_ty = fields[idx].ty_with_args(args); struct_validity.append(&mut ty_validity_per_offset( machine_info, field_ty, diff --git a/kani-compiler/src/kani_middle/transform/contracts.rs b/kani-compiler/src/kani_middle/transform/contracts.rs index df5e4209e900..e3ca9a53c66b 100644 --- a/kani-compiler/src/kani_middle/transform/contracts.rs +++ b/kani-compiler/src/kani_middle/transform/contracts.rs @@ -350,12 +350,11 @@ impl FunctionWithContractPass { && !harness_generic_args.is_empty() { let kind = harness.args().0[0].expect_ty().kind(); - let (def, args) = kind.fn_def().unwrap(); - let fn_to_verify = Instance::resolve(def, &args).unwrap(); + let (fn_to_verify_def, _) = kind.fn_def().unwrap(); // For automatic harnesses, the target is the function to verify, // and stubs are empty. ( - Some(rustc_internal::internal(tcx, fn_to_verify.def.def_id())), + Some(rustc_internal::internal(tcx, fn_to_verify_def.def_id())), HashSet::default(), ) } else { @@ -492,10 +491,10 @@ impl FunctionWithContractPass { fn mark_unused(&mut self, tcx: TyCtxt, fn_def: FnDef, body: &Body, mode: ContractMode) { let contract = KaniAttributes::for_def_id(tcx, fn_def.def_id()).contract_attributes().unwrap(); - let recursion_closure = find_closure(tcx, fn_def, &body, contract.recursion_check.as_str()); - let check_closure = find_closure(tcx, fn_def, &body, contract.checked_with.as_str()); - let replace_closure = find_closure(tcx, fn_def, &body, contract.replaced_with.as_str()); - let assert_closure = find_closure(tcx, fn_def, &body, contract.asserted_with.as_str()); + let recursion_closure = find_closure(tcx, fn_def, body, contract.recursion_check.as_str()); + let check_closure = find_closure(tcx, fn_def, body, contract.checked_with.as_str()); + let replace_closure = find_closure(tcx, fn_def, body, contract.replaced_with.as_str()); + let assert_closure = find_closure(tcx, fn_def, body, contract.asserted_with.as_str()); match mode { ContractMode::Original => { // No contract instrumentation needed. Add all closures to the list of unused. diff --git a/kani-compiler/src/kani_middle/transform/internal_mir.rs b/kani-compiler/src/kani_middle/transform/internal_mir.rs index 090c74311fa4..fba62152f38e 100644 --- a/kani-compiler/src/kani_middle/transform/internal_mir.rs +++ b/kani-compiler/src/kani_middle/transform/internal_mir.rs @@ -549,6 +549,9 @@ impl RustcInternalMir for AssertMessage { AssertMessage::NullPointerDereference => { rustc_middle::mir::AssertMessage::NullPointerDereference } + AssertMessage::ResumedAfterDrop(coroutine_kind) => { + rustc_middle::mir::AssertMessage::ResumedAfterDrop(coroutine_kind.internal_mir(tcx)) + } } } } @@ -579,6 +582,8 @@ impl RustcInternalMir for TerminatorKind { target: rustc_middle::mir::BasicBlock::from_usize(*target), unwind: unwind.internal_mir(tcx), replace: false, + drop: None, + async_fut: None, } } TerminatorKind::Call { func, args, destination, target, unwind } => { diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index 343fc2fe0c92..1e6cb58b7642 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -240,7 +240,7 @@ impl IntrinsicGeneratorPass { *pointee_info.ty(), ); let layout_operand = - mk_layout_operand(&mut new_body, &mut statements, &mut source, &layout); + mk_layout_operand(&mut new_body, &mut statements, &mut source, layout); let terminator = Terminator { kind: TerminatorKind::Call { @@ -283,7 +283,7 @@ impl IntrinsicGeneratorPass { &mut new_body, &mut statements, &mut source, - &element_layout, + element_layout, ); let terminator = Terminator { kind: TerminatorKind::Call { @@ -314,7 +314,7 @@ impl IntrinsicGeneratorPass { &mut source, InsertPosition::Before, None, - &reason, + reason, ); } PointeeLayout::Union { .. } => { @@ -326,7 +326,7 @@ impl IntrinsicGeneratorPass { &mut source, InsertPosition::Before, None, - &reason, + reason, ); } }; @@ -565,7 +565,7 @@ impl IntrinsicGeneratorPass { fn return_model( &mut self, new_body: &mut MutableBody, - mut source: &mut SourceInstruction, + source: &mut SourceInstruction, model: KaniModel, args: &GenericArgs, operands: Vec, @@ -574,7 +574,7 @@ impl IntrinsicGeneratorPass { let size_of_dyn = Instance::resolve(*def, args).unwrap(); new_body.insert_call( &size_of_dyn, - &mut source, + source, InsertPosition::Before, operands, Place::from(RETURN_LOCAL), diff --git a/kani-compiler/src/kani_middle/transform/loop_contracts.rs b/kani-compiler/src/kani_middle/transform/loop_contracts.rs index 323cff742ee7..c3fdbcfc06d5 100644 --- a/kani-compiler/src/kani_middle/transform/loop_contracts.rs +++ b/kani-compiler/src/kani_middle/transform/loop_contracts.rs @@ -19,7 +19,7 @@ use stable_mir::mir::{ AggregateKind, BasicBlock, BasicBlockIdx, Body, ConstOperand, Operand, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, VarDebugInfoContents, }; -use stable_mir::ty::{FnDef, MirConst, RigidTy, UintTy}; +use stable_mir::ty::{FnDef, GenericArgKind, MirConst, RigidTy, TyKind, UintTy}; use std::collections::{HashMap, HashSet, VecDeque}; use std::fmt::Debug; @@ -103,39 +103,10 @@ impl TransformPass for LoopContractPass { let run = Instance::resolve(self.run_contract_fn.unwrap(), args).unwrap(); (true, run.body().unwrap()) } else { - let mut new_body = MutableBody::from(body); - let mut contain_loop_contracts: bool = false; - - // Visit basic blocks in control flow order (BFS). - let mut visited: HashSet = HashSet::new(); - let mut queue: VecDeque = VecDeque::new(); - // Visit blocks in loops only when there is no blocks in queue. - let mut loop_queue: VecDeque = VecDeque::new(); - queue.push_back(0); - - while let Some(bb_idx) = queue.pop_front().or_else(|| loop_queue.pop_front()) { - visited.insert(bb_idx); - - let terminator = new_body.blocks()[bb_idx].terminator.clone(); - - let is_loop_head = self.transform_bb(tcx, &mut new_body, bb_idx); - contain_loop_contracts |= is_loop_head; - - // Add successors of the current basic blocks to - // the visiting queue. - for to_visit in terminator.successors() { - if !visited.contains(&to_visit) { - if is_loop_head { - loop_queue.push_back(to_visit); - } else { - queue.push_back(to_visit) - }; - } - } - } - (contain_loop_contracts, new_body.into()) + self.transform_body_with_loop(tcx, body) } } + RigidTy::Closure(_, _) => self.transform_body_with_loop(tcx, body), _ => { /* static variables case */ (false, body) @@ -194,6 +165,43 @@ impl LoopContractPass { )) } + /// This function transform the function body as described in fn transform. + /// It is the core of fn transform, and is separated just to avoid code repetition. + fn transform_body_with_loop(&mut self, tcx: TyCtxt, body: Body) -> (bool, Body) { + let mut new_body = MutableBody::from(body); + let mut contain_loop_contracts: bool = false; + + // Visit basic blocks in control flow order (BFS). + let mut visited: HashSet = HashSet::new(); + let mut queue: VecDeque = VecDeque::new(); + // Visit blocks in loops only when there is no blocks in queue. + let mut loop_queue: VecDeque = VecDeque::new(); + queue.push_back(0); + + while let Some(bb_idx) = queue.pop_front().or_else(|| loop_queue.pop_front()) { + visited.insert(bb_idx); + + let terminator = new_body.blocks()[bb_idx].terminator.clone(); + + let is_loop_head = self.transform_bb(tcx, &mut new_body, bb_idx); + contain_loop_contracts |= is_loop_head; + + // Add successors of the current basic blocks to + // the visiting queue. + for to_visit in terminator.successors() { + if !visited.contains(&to_visit) { + if is_loop_head { + loop_queue.push_back(to_visit); + } else { + queue.push_back(to_visit) + }; + } + } + } + + (contain_loop_contracts, new_body.into()) + } + /// Transform loops with contracts from /// ```ignore /// bb_idx: { @@ -237,18 +245,16 @@ impl LoopContractPass { let mut contain_loop_contracts = false; // Redirect loop latches to the new latches. - if let TerminatorKind::Goto { target: terminator_target } = &terminator.kind { - if self.new_loop_latches.contains_key(terminator_target) { - new_body.replace_terminator( - &SourceInstruction::Terminator { bb: bb_idx }, - Terminator { - kind: TerminatorKind::Goto { - target: self.new_loop_latches[terminator_target], - }, - span: terminator.span, - }, - ); - } + if let TerminatorKind::Goto { target: terminator_target } = &terminator.kind + && self.new_loop_latches.contains_key(terminator_target) + { + new_body.replace_terminator( + &SourceInstruction::Terminator { bb: bb_idx }, + Terminator { + kind: TerminatorKind::Goto { target: self.new_loop_latches[terminator_target] }, + span: terminator.span, + }, + ); } // Transform loop heads with loop contracts. @@ -261,7 +267,7 @@ impl LoopContractPass { } = &terminator.kind { // Get the function signature of the terminator call. - let Some(RigidTy::FnDef(fn_def, ..)) = terminator_func + let Some(RigidTy::FnDef(fn_def, genarg)) = terminator_func .ty(new_body.locals()) .ok() .map(|fn_ty| fn_ty.kind().rigid().unwrap().clone()) @@ -286,10 +292,18 @@ impl LoopContractPass { Please report github.com/model-checking/kani/issues/new?template=bug_report.md" ); } - - let ori_condition_bb_idx = - new_body.blocks()[terminator_target.unwrap()].terminator.successors()[1]; - self.make_invariant_closure_alive(new_body, ori_condition_bb_idx); + let GenericArgKind::Type(arg_ty) = genarg.0[0] else { return false }; + let TyKind::RigidTy(RigidTy::Closure(_, genarg)) = arg_ty.kind() else { + return false; + }; + let GenericArgKind::Type(arg_ty) = genarg.0[2] else { return false }; + let TyKind::RigidTy(RigidTy::Tuple(args)) = arg_ty.kind() else { return false }; + // Check if the invariant involves any local variable + if !args.is_empty() { + let ori_condition_bb_idx = + new_body.blocks()[terminator_target.unwrap()].terminator.successors()[1]; + self.make_invariant_closure_alive(new_body, ori_condition_bb_idx); + } contain_loop_contracts = true; @@ -310,7 +324,7 @@ impl LoopContractPass { for stmt in &new_body.blocks()[bb_idx].statements { if let StatementKind::Assign(place, rvalue) = &stmt.kind { match rvalue { - Rvalue::Ref(_,_,rplace) => { + Rvalue::Ref(_,_,rplace) | Rvalue::CopyForDeref(rplace) => { if supported_vars.contains(&rplace.local) { supported_vars.push(place.local); } } diff --git a/kani-compiler/src/kani_middle/transform/mod.rs b/kani-compiler/src/kani_middle/transform/mod.rs index fbe9bebf42f8..164730499efb 100644 --- a/kani-compiler/src/kani_middle/transform/mod.rs +++ b/kani-compiler/src/kani_middle/transform/mod.rs @@ -78,10 +78,10 @@ impl BodyTransformation { transformer.add_pass(queries, AutomaticHarnessPass::new(unit, queries)); transformer.add_pass(queries, FnStubPass::new(&unit.stubs)); transformer.add_pass(queries, ExternFnStubPass::new(&unit.stubs)); - transformer.add_pass(queries, FunctionWithContractPass::new(tcx, queries, &unit)); + transformer.add_pass(queries, FunctionWithContractPass::new(tcx, queries, unit)); // This has to come after the contract pass since we want this to only replace the closure // body that is relevant for this harness. - transformer.add_pass(queries, AnyModifiesPass::new(tcx, queries, &unit)); + transformer.add_pass(queries, AnyModifiesPass::new(tcx, queries, unit)); transformer.add_pass( queries, ValidValuePass { @@ -103,10 +103,9 @@ impl BodyTransformation { mem_init_fn_cache: queries.kani_functions().clone(), }, ); - transformer - .add_pass(queries, IntrinsicGeneratorPass::new(unsupported_check_type, &queries)); - transformer.add_pass(queries, LoopContractPass::new(tcx, queries, &unit)); - transformer.add_pass(queries, RustcIntrinsicsPass::new(&queries)); + transformer.add_pass(queries, IntrinsicGeneratorPass::new(unsupported_check_type, queries)); + transformer.add_pass(queries, LoopContractPass::new(tcx, queries, unit)); + transformer.add_pass(queries, RustcIntrinsicsPass::new(queries)); transformer } @@ -140,7 +139,7 @@ impl BodyTransformation { } fn add_pass(&mut self, query_db: &QueryDb, pass: P) { - if pass.is_enabled(&query_db) { + if pass.is_enabled(query_db) { match P::transformation_type() { TransformationType::Instrumentation => self.inst_passes.push(Box::new(pass)), TransformationType::Stubbing => self.stub_passes.push(Box::new(pass)), @@ -220,7 +219,7 @@ impl GlobalPasses { } fn add_global_pass(&mut self, query_db: &QueryDb, pass: P) { - if pass.is_enabled(&query_db) { + if pass.is_enabled(query_db) { self.global_passes.push(Box::new(pass)) } } diff --git a/kani-compiler/src/kani_middle/transform/rustc_intrinsics.rs b/kani-compiler/src/kani_middle/transform/rustc_intrinsics.rs index 43f8e5b7013f..b14f21a9b24b 100644 --- a/kani-compiler/src/kani_middle/transform/rustc_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/rustc_intrinsics.rs @@ -156,34 +156,30 @@ impl MutMirVisitor for ReplaceIntrinsicCallVisitor<'_> { /// Note that we only need to replace function calls since intrinsics must always be called /// directly. I.e., no need to handle function pointers. fn visit_terminator(&mut self, term: &mut Terminator) { - if let TerminatorKind::Call { func, .. } = &mut term.kind { - if let TyKind::RigidTy(RigidTy::FnDef(def, args)) = + if let TerminatorKind::Call { func, .. } = &mut term.kind + && let TyKind::RigidTy(RigidTy::FnDef(def, args)) = func.ty(&self.locals).unwrap().kind() - { - if def.is_intrinsic() { - let instance = Instance::resolve(def, &args).unwrap(); - let intrinsic = Intrinsic::from_instance(&instance); - debug!(?intrinsic, "handle_terminator"); - let model = match intrinsic { - Intrinsic::SizeOfVal => self.models[&KaniModel::SizeOfVal], - Intrinsic::MinAlignOfVal => self.models[&KaniModel::AlignOfVal], - Intrinsic::PtrOffsetFrom => self.models[&KaniModel::PtrOffsetFrom], - Intrinsic::PtrOffsetFromUnsigned => { - self.models[&KaniModel::PtrOffsetFromUnsigned] - } - // The rest is handled in codegen. - _ => { - return self.super_terminator(term); - } - }; - let new_instance = Instance::resolve(model, &args).unwrap(); - let literal = MirConst::try_new_zero_sized(new_instance.ty()).unwrap(); - let span = term.span; - let new_func = ConstOperand { span, user_ty: None, const_: literal }; - *func = Operand::Constant(new_func); - self.changed = true; + && def.is_intrinsic() + { + let instance = Instance::resolve(def, &args).unwrap(); + let intrinsic = Intrinsic::from_instance(&instance); + debug!(?intrinsic, "handle_terminator"); + let model = match intrinsic { + Intrinsic::SizeOfVal => self.models[&KaniModel::SizeOfVal], + Intrinsic::MinAlignOfVal => self.models[&KaniModel::AlignOfVal], + Intrinsic::PtrOffsetFrom => self.models[&KaniModel::PtrOffsetFrom], + Intrinsic::PtrOffsetFromUnsigned => self.models[&KaniModel::PtrOffsetFromUnsigned], + // The rest is handled in codegen. + _ => { + return self.super_terminator(term); } - } + }; + let new_instance = Instance::resolve(model, &args).unwrap(); + let literal = MirConst::try_new_zero_sized(new_instance.ty()).unwrap(); + let span = term.span; + let new_func = ConstOperand { span, user_ty: None, const_: literal }; + *func = Operand::Constant(new_func); + self.changed = true; } self.super_terminator(term); } diff --git a/kani-compiler/src/kani_middle/transform/stubs.rs b/kani-compiler/src/kani_middle/transform/stubs.rs index 3d72cff0d56d..1c96be291594 100644 --- a/kani-compiler/src/kani_middle/transform/stubs.rs +++ b/kani-compiler/src/kani_middle/transform/stubs.rs @@ -46,14 +46,13 @@ impl TransformPass for FnStubPass { fn transform(&mut self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { trace!(function=?instance.name(), "transform"); let ty = instance.ty(); - if let TyKind::RigidTy(RigidTy::FnDef(fn_def, args)) = ty.kind() { - if let Some(replace) = self.stubs.get(&fn_def) { - let new_instance = Instance::resolve(*replace, &args).unwrap(); - debug!(from=?instance.name(), to=?new_instance.name(), "FnStubPass::transform"); - if let Some(body) = FnStubValidator::validate(tcx, (fn_def, *replace), new_instance) - { - return (true, body); - } + if let TyKind::RigidTy(RigidTy::FnDef(fn_def, args)) = ty.kind() + && let Some(replace) = self.stubs.get(&fn_def) + { + let new_instance = Instance::resolve(*replace, &args).unwrap(); + debug!(from=?instance.name(), to=?new_instance.name(), "FnStubPass::transform"); + if let Some(body) = FnStubValidator::validate(tcx, (fn_def, *replace), new_instance) { + return (true, body); } } (false, body) @@ -152,28 +151,28 @@ impl FnStubValidator<'_, '_> { impl MirVisitor for FnStubValidator<'_, '_> { fn visit_operand(&mut self, op: &Operand, loc: Location) { let op_ty = op.ty(self.locals).unwrap(); - if let TyKind::RigidTy(RigidTy::FnDef(def, args)) = op_ty.kind() { - if Instance::resolve(def, &args).is_err() { - self.is_valid = false; - let callee = def.name(); - let receiver_ty = args.0[0].expect_ty(); - let sep = callee.rfind("::").unwrap(); - let trait_ = &callee[..sep]; - self.tcx.dcx().span_err( - rustc_internal::internal(self.tcx, loc.span()), - format!( - "`{}` doesn't implement \ + if let TyKind::RigidTy(RigidTy::FnDef(def, args)) = op_ty.kind() + && Instance::resolve(def, &args).is_err() + { + self.is_valid = false; + let callee = def.name(); + let receiver_ty = args.0[0].expect_ty(); + let sep = callee.rfind("::").unwrap(); + let trait_ = &callee[..sep]; + self.tcx.dcx().span_err( + rustc_internal::internal(self.tcx, loc.span()), + format!( + "`{}` doesn't implement \ `{}`. The function `{}` \ cannot be stubbed by `{}` due to \ generic bounds not being met. Callee: {}", - receiver_ty, - trait_, - self.stub.0.name(), - self.stub.1.name(), - callee, - ), - ); - } + receiver_ty, + trait_, + self.stub.0.name(), + self.stub.1.name(), + callee, + ), + ); } } } @@ -187,36 +186,34 @@ struct ExternFnStubVisitor<'a> { impl MutMirVisitor for ExternFnStubVisitor<'_> { fn visit_terminator(&mut self, term: &mut Terminator) { // Replace direct calls - if let TerminatorKind::Call { func, .. } = &mut term.kind { - if let TyKind::RigidTy(RigidTy::FnDef(def, args)) = + if let TerminatorKind::Call { func, .. } = &mut term.kind + && let TyKind::RigidTy(RigidTy::FnDef(def, args)) = func.ty(&self.locals).unwrap().kind() - { - if let Some(new_def) = self.stubs.get(&def) { - let instance = Instance::resolve(*new_def, &args).unwrap(); - let literal = MirConst::try_new_zero_sized(instance.ty()).unwrap(); - let span = term.span; - let new_func = ConstOperand { span, user_ty: None, const_: literal }; - *func = Operand::Constant(new_func); - self.changed = true; - } - } + && let Some(new_def) = self.stubs.get(&def) + { + let instance = Instance::resolve(*new_def, &args).unwrap(); + let literal = MirConst::try_new_zero_sized(instance.ty()).unwrap(); + let span = term.span; + let new_func = ConstOperand { span, user_ty: None, const_: literal }; + *func = Operand::Constant(new_func); + self.changed = true; } self.super_terminator(term); } fn visit_operand(&mut self, operand: &mut Operand) { let func_ty = operand.ty(&self.locals).unwrap(); - if let TyKind::RigidTy(RigidTy::FnDef(orig_def, args)) = func_ty.kind() { - if let Some(new_def) = self.stubs.get(&orig_def) { - let Operand::Constant(ConstOperand { span, .. }) = operand else { - unreachable!(); - }; - let instance = Instance::resolve_for_fn_ptr(*new_def, &args).unwrap(); - let literal = MirConst::try_new_zero_sized(instance.ty()).unwrap(); - let new_func = ConstOperand { span: *span, user_ty: None, const_: literal }; - *operand = Operand::Constant(new_func); - self.changed = true; - } + if let TyKind::RigidTy(RigidTy::FnDef(orig_def, args)) = func_ty.kind() + && let Some(new_def) = self.stubs.get(&orig_def) + { + let Operand::Constant(ConstOperand { span, .. }) = operand else { + unreachable!(); + }; + let instance = Instance::resolve_for_fn_ptr(*new_def, &args).unwrap(); + let literal = MirConst::try_new_zero_sized(instance.ty()).unwrap(); + let new_func = ConstOperand { span: *span, user_ty: None, const_: literal }; + *operand = Operand::Constant(new_func); + self.changed = true; } } } diff --git a/kani-dependencies b/kani-dependencies index f620835102de..b3667d086efa 100644 --- a/kani-dependencies +++ b/kani-dependencies @@ -1,5 +1,5 @@ CBMC_MAJOR="6" -CBMC_MINOR="5" -CBMC_VERSION="6.5.0" +CBMC_MINOR="6" +CBMC_VERSION="6.6.0" KISSAT_VERSION="4.0.1" diff --git a/kani-driver/Cargo.toml b/kani-driver/Cargo.toml index e5ac4aec0c68..a4a030335c74 100644 --- a/kani-driver/Cargo.toml +++ b/kani-driver/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-driver" -version = "0.60.0" +version = "0.62.0" edition = "2021" description = "Build a project with Kani and run all proof harnesses" license = "MIT OR Apache-2.0" diff --git a/kani-driver/src/args/autoharness_args.rs b/kani-driver/src/args/autoharness_args.rs index 3dc8f6973703..8b7b66c4cd7c 100644 --- a/kani-driver/src/args/autoharness_args.rs +++ b/kani-driver/src/args/autoharness_args.rs @@ -9,27 +9,28 @@ use crate::args::{ValidateArgs, VerificationArgs, validate_std_path}; use clap::{Error, Parser, error::ErrorKind}; use kani_metadata::UnstableFeature; +// TODO: It would be nice if we could borrow --exact here from VerificationArgs to differentiate between partial/exact matches, +// like --harnesses does. Sharing arguments with VerificationArgs doesn't work with our current structure, though. #[derive(Debug, Parser)] pub struct CommonAutoharnessArgs { - /// If specified, only autoharness functions that match this filter. This option can be provided - /// multiple times, which will verify all functions matching any of the filters. - /// Note that this filter will match against partial names, i.e., providing the name of a module will include all functions from that module. - /// Also note that if the function specified is unable to be automatically verified, this flag will have no effect. - #[arg( - long = "include-function", - num_args(1), - value_name = "FUNCTION", - conflicts_with = "exclude_function" - )] - pub include_function: Vec, - - /// If specified, only autoharness functions that do not match this filter. This option can be provided - /// multiple times, which will verify all functions that do not match any of the filters. - /// Note that this filter will match against partial names, i.e., providing the name of a module will exclude all functions from that module. - #[arg(long = "exclude-function", num_args(1), value_name = "FUNCTION")] - pub exclude_function: Vec, - // TODO: It would be nice if we could borrow --exact here from VerificationArgs to differentiate between partial/exact matches, - // like --harnesses does. Sharing arguments with VerificationArgs doesn't work with our current structure, though. + /// Only create automatic harnesses for functions that match the given pattern. + /// This option can be provided multiple times, which will verify functions matching any of the patterns. + /// Kani considers a function to match the pattern if its fully qualified path contains PATTERN as a substring. + /// Example: `--include-pattern foo` matches all functions whose fully qualified paths contain the substring "foo". + #[arg(long = "include-pattern", num_args(1), value_name = "PATTERN")] + pub include_pattern: Vec, + + /// Only create automatic harnesses for functions that do not match the given pattern. + /// This option can be provided multiple times, which will verify functions that do not match any of the patterns. + /// Kani considers a function to match the pattern if its fully qualified path contains PATTERN as a substring. + + /// This option takes precedence over `--include-pattern`, i.e., Kani will first select all functions that match `--include-pattern`, + /// then exclude those that match `--exclude-pattern.` + /// Example: `--include-pattern foo --exclude-pattern foo::bar` creates automatic harnesses for all functions whose paths contain "foo" without "foo::bar". + /// Example: `--include-pattern foo::bar --exclude-pattern foo` makes the `--include-pattern` a no-op, since the exclude pattern is a superset of the include pattern. + #[arg(long = "exclude-pattern", num_args(1), value_name = "PATTERN")] + pub exclude_pattern: Vec, + /// Run the `list` subcommand after generating the automatic harnesses. Requires -Z list. Note that this option implies --only-codegen. #[arg(long)] pub list: bool, @@ -93,7 +94,8 @@ impl ValidateArgs for CargoAutoharnessArgs { )); } - if self.common_autoharness_args.format == Format::Pretty + if self.common_autoharness_args.list + && self.common_autoharness_args.format == Format::Pretty && self.verify_opts.common_args.quiet { return Err(Error::raw( @@ -140,7 +142,8 @@ impl ValidateArgs for StandaloneAutoharnessArgs { )); } - if self.common_autoharness_args.format == Format::Pretty + if self.common_autoharness_args.list + && self.common_autoharness_args.format == Format::Pretty && self.verify_opts.common_args.quiet { return Err(Error::raw( diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index fb18504c6182..5b19236a4334 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -146,7 +146,7 @@ pub enum StandaloneSubcommand { VerifyStd(Box), /// List contracts and harnesses. List(Box), - /// Scan the input file for functions eligible for automatic (i.e., harness-free) verification and verify them. + /// Create and run harnesses automatically for eligible functions. Implies -Z function-contracts and -Z loop-contracts. Autoharness(Box), } @@ -177,7 +177,8 @@ pub enum CargoKaniSubcommand { /// List contracts and harnesses. List(Box), - /// Scan the crate for functions eligible for automatic (i.e., harness-free) verification and verify them. + /// Create and run harnesses automatically for eligible functions. Implies -Z function-contracts and -Z loop-contracts. + /// See https://model-checking.github.io/kani/reference/experimental/autoharness.html for documentation. Autoharness(Box), } @@ -242,6 +243,11 @@ pub struct VerificationArgs { #[arg(long, hide_short_help = true)] pub only_codegen: bool, + /// Run Kani without codegen. Useful for quick feedback on whether the code would compile successfully (similar to `cargo check`). + /// This feature is unstable and requires `-Z unstable-options` to be used + #[arg(long, hide_short_help = true)] + pub no_codegen: bool, + /// Specify the value used for loop unwinding in CBMC #[arg(long)] pub default_unwind: Option, @@ -479,7 +485,7 @@ fn check_no_cargo_opt(is_set: bool, name: &str) -> Result<(), Error> { if is_set { Err(Error::raw( ErrorKind::UnknownArgument, - format!("argument `{}` cannot be used with standalone Kani.", name), + format!("argument `{name}` cannot be used with standalone Kani."), )) } else { Ok(()) @@ -510,16 +516,16 @@ impl ValidateArgs for StandaloneArgs { check_no_cargo_opt(!self.verify_opts.cargo.exclude.is_empty(), "--exclude")?; check_no_cargo_opt(self.verify_opts.cargo.workspace, "--workspace")?; check_no_cargo_opt(self.verify_opts.cargo.manifest_path.is_some(), "--manifest-path")?; - if let Some(input) = &self.input { - if !input.is_file() { - return Err(Error::raw( - ErrorKind::InvalidValue, - format!( - "Invalid argument: Input invalid. `{}` is not a regular file.", - input.display() - ), - )); - } + if let Some(input) = &self.input + && !input.is_file() + { + return Err(Error::raw( + ErrorKind::InvalidValue, + format!( + "Invalid argument: Input invalid. `{}` is not a regular file.", + input.display() + ), + )); } Ok(()) } @@ -628,16 +634,17 @@ impl ValidateArgs for VerificationArgs { "Conflicting options: --jobs requires `--output-format=terse`", )); } - if let Some(out_dir) = &self.target_dir { - if out_dir.exists() && !out_dir.is_dir() { - return Err(Error::raw( - ErrorKind::InvalidValue, - format!( - "Invalid argument: `--target-dir` argument `{}` is not a directory", - out_dir.display() - ), - )); - } + if let Some(out_dir) = &self.target_dir + && out_dir.exists() + && !out_dir.is_dir() + { + return Err(Error::raw( + ErrorKind::InvalidValue, + format!( + "Invalid argument: `--target-dir` argument `{}` is not a directory", + out_dir.display() + ), + )); } self.common_args.check_unstable( @@ -660,6 +667,12 @@ impl ValidateArgs for VerificationArgs { UnstableFeature::UnstableOptions, )?; + self.common_args.check_unstable( + self.no_codegen, + "--no-codegen", + UnstableFeature::UnstableOptions, + )?; + self.common_args.check_unstable( self.jobs.is_some(), "--jobs", @@ -945,12 +958,12 @@ mod tests { } fn check(args: &str, feature: Option, pred: fn(StandaloneArgs) -> bool) { - let mut res = parse_unstable_disabled(&args); + let mut res = parse_unstable_disabled(args); if let Some(unstable) = feature { // Should fail without -Z unstable-options. assert_eq!(res.unwrap_err().kind(), ErrorKind::MissingRequiredArgument); // Should succeed with -Z unstable-options. - res = parse_unstable_enabled(&args, unstable); + res = parse_unstable_enabled(args, unstable); } assert!(res.is_ok()); assert!(pred(res.unwrap())); @@ -1004,7 +1017,7 @@ mod tests { args: &str, unstable: UnstableFeature, ) -> Result { - let args = format!("kani -Z {} file.rs {args}", unstable); + let args = format!("kani -Z {unstable} file.rs {args}"); let parse_res = StandaloneArgs::try_parse_from(args.split(' '))?; parse_res.verify_opts.validate()?; Ok(parse_res) diff --git a/kani-driver/src/args_toml.rs b/kani-driver/src/args_toml.rs index 2fff01c4f5f9..cff00e6368f6 100644 --- a/kani-driver/src/args_toml.rs +++ b/kani-driver/src/args_toml.rs @@ -105,21 +105,21 @@ fn toml_to_args(tomldata: &str) -> Result<(Vec, Vec)> { for table in tables { if let Some(table) = get_table(&config, table) { - if let Some(entry) = table.get("flags") { - if let Some(val) = entry.as_table() { - map.extend(val.iter().map(|(x, y)| (x.to_owned(), y.to_owned()))); - } + if let Some(entry) = table.get("flags") + && let Some(val) = entry.as_table() + { + map.extend(val.iter().map(|(x, y)| (x.to_owned(), y.to_owned()))); } - if let Some(entry) = table.get("unstable") { - if let Some(val) = entry.as_table() { - args.append( - &mut val - .iter() - .filter_map(|(k, v)| unstable_entry(k, v).transpose()) - .collect::>>()?, - ); - } + if let Some(entry) = table.get("unstable") + && let Some(val) = entry.as_table() + { + args.append( + &mut val + .iter() + .filter_map(|(k, v)| unstable_entry(k, v).transpose()) + .collect::>>()?, + ); } } } diff --git a/kani-driver/src/assess/scan.rs b/kani-driver/src/assess/scan.rs index 63a778b294b4..4af4838094f1 100644 --- a/kani-driver/src/assess/scan.rs +++ b/kani-driver/src/assess/scan.rs @@ -72,11 +72,11 @@ pub(crate) fn assess_scan_main(session: KaniSession, args: &ScanArgs) -> Result< for workspace in &project_metadata { let workspace_root = workspace.workspace_root.as_std_path(); for package in workspace.workspace_packages() { - if let Some(filter) = &package_filter { - if !filter.contains(&package.name) { - println!("Skipping filtered-out package {}", package.name); - continue; - } + if let Some(filter) = &package_filter + && !filter.contains(&package.name) + { + println!("Skipping filtered-out package {}", package.name); + continue; } // This is a hack. Some repos contains workspaces with "examples" (not actually cargo examples, but // full packages as workspace members) that are named after other crates. diff --git a/kani-driver/src/autoharness/mod.rs b/kani-driver/src/autoharness/mod.rs index 498a8d3e94fe..f4bcd221ec08 100644 --- a/kani-driver/src/autoharness/mod.rs +++ b/kani-driver/src/autoharness/mod.rs @@ -7,13 +7,14 @@ use crate::args::Timeout; use crate::args::autoharness_args::{ CargoAutoharnessArgs, CommonAutoharnessArgs, StandaloneAutoharnessArgs, }; +use crate::args::common::UnstableFeature; use crate::call_cbmc::VerificationStatus; -use crate::call_single_file::to_rustc_arg; use crate::harness_runner::HarnessResult; use crate::list::collect_metadata::process_metadata; use crate::list::output::output_list_results; use crate::project::{Project, standalone_project, std_project}; use crate::session::KaniSession; +use crate::util::warning; use crate::{InvocationType, print_kani_version, project, verify_project}; use anyhow::Result; use comfy_table::Table as PrettyTable; @@ -55,8 +56,8 @@ fn setup_session(session: &mut KaniSession, common_autoharness_args: &CommonAuto session.enable_autoharness(); session.add_default_bounds(); session.add_auto_harness_args( - &common_autoharness_args.include_function, - &common_autoharness_args.exclude_function, + &common_autoharness_args.include_pattern, + &common_autoharness_args.exclude_pattern, ); } @@ -83,29 +84,34 @@ fn postprocess_project( /// Print automatic harness metadata to the terminal. fn print_autoharness_metadata(metadata: Vec) { let mut chosen_table = PrettyTable::new(); - chosen_table.set_header(vec!["Selected Function"]); + chosen_table.set_header(vec!["Crate", "Selected Function"]); let mut skipped_table = PrettyTable::new(); - skipped_table.set_header(vec!["Skipped Function", "Reason for Skipping"]); + skipped_table.set_header(vec!["Crate", "Skipped Function", "Reason for Skipping"]); for md in metadata { let autoharness_md = md.autoharness_md.unwrap(); - chosen_table.add_rows(autoharness_md.chosen.into_iter().map(|func| vec![func])); + chosen_table.add_rows( + autoharness_md.chosen.into_iter().map(|func| vec![md.crate_name.clone(), func]), + ); skipped_table.add_rows(autoharness_md.skipped.into_iter().filter_map(|(func, reason)| { match reason { AutoHarnessSkipReason::MissingArbitraryImpl(ref args) => Some(vec![ + md.crate_name.clone(), func, format!( "{reason} {}", args.iter() - .map(|(name, typ)| format!("{}: {}", name, typ)) + .map(|(name, typ)| format!("{name}: {typ}")) .collect::>() .join(", ") ), ]), AutoHarnessSkipReason::GenericFn | AutoHarnessSkipReason::NoBody - | AutoHarnessSkipReason::UserFilter => Some(vec![func, reason.to_string()]), + | AutoHarnessSkipReason::UserFilter => { + Some(vec![md.crate_name.clone(), func, reason.to_string()]) + } // We don't report Kani implementations to the user to avoid exposing Kani functions we insert during instrumentation. // For those we don't insert during instrumentation that are in this category (manual harnesses or Kani trait implementations), // it should be obvious that we wouldn't generate harnesses, so reporting those functions as "skipped" is unlikely to be useful. @@ -150,21 +156,35 @@ fn print_skipped_table(table: &mut PrettyTable) { impl KaniSession { /// Enable autoharness mode. pub fn enable_autoharness(&mut self) { - self.auto_harness = true; + self.autoharness_compiler_flags = Some(vec![]); + self.args.common_args.unstable_features.enable_feature(UnstableFeature::FunctionContracts); + self.args.common_args.unstable_features.enable_feature(UnstableFeature::LoopContracts); } /// Add the compiler arguments specific to the `autoharness` subcommand. - /// TODO: this should really be appending onto the `kani_compiler_flags()` output instead of `pkg_args`. - /// It doesn't affect functionality since autoharness doesn't examine dependencies, but would still be better practice. pub fn add_auto_harness_args(&mut self, included: &[String], excluded: &[String]) { - for func in included { - self.pkg_args - .push(to_rustc_arg(vec![format!("--autoharness-include-function {}", func)])); + for include_pattern in included { + for exclude_pattern in excluded { + // Check if include pattern contains exclude pattern + // This catches cases like include="foo::bar" exclude="bar" or include="foo" exclude="foo" + if include_pattern.contains(exclude_pattern) { + warning(&format!( + "Include pattern '{include_pattern}' contains exclude pattern '{exclude_pattern}'. \ + This combination will never match any functions since all functions matching \ + the include pattern will also match the exclude pattern, and the exclude pattern takes precedence." + )); + } + } + } + + let mut args = vec![]; + for pattern in included { + args.push(format!("--autoharness-include-pattern {pattern}")); } - for func in excluded { - self.pkg_args - .push(to_rustc_arg(vec![format!("--autoharness-exclude-function {}", func)])); + for pattern in excluded { + args.push(format!("--autoharness-exclude-pattern {pattern}")); } + self.autoharness_compiler_flags = Some(args); } /// Add global harness timeout and loop unwinding bounds if not provided. @@ -180,7 +200,10 @@ impl KaniSession { } /// Prints the results from running the `autoharness` subcommand. - pub fn print_autoharness_summary(&self, mut automatic: Vec<&HarnessResult<'_>>) -> Result<()> { + pub fn print_autoharness_summary( + &self, + mut automatic: Vec<&HarnessResult<'_>>, + ) -> Result { automatic.sort_by(|a, b| a.harness.pretty_name.cmp(&b.harness.pretty_name)); let (successes, failures): (Vec<_>, Vec<_>) = automatic.into_iter().partition(|r| r.result.status == VerificationStatus::Success); @@ -193,6 +216,7 @@ impl KaniSession { let mut verified_fns = PrettyTable::new(); verified_fns.set_header(vec![ + "Crate", "Selected Function", "Kind of Automatic Harness", "Verification Result", @@ -200,6 +224,7 @@ impl KaniSession { for success in successes { verified_fns.add_row(vec![ + success.harness.crate_name.clone(), success.harness.pretty_name.clone(), success.harness.attributes.kind.to_string(), success.result.status.to_string(), @@ -208,13 +233,16 @@ impl KaniSession { for failure in failures { verified_fns.add_row(vec![ + failure.harness.crate_name.clone(), failure.harness.pretty_name.clone(), failure.harness.attributes.kind.to_string(), failure.result.status.to_string(), ]); } - println!("{verified_fns}"); + if total > 0 { + println!("{verified_fns}"); + } if failing > 0 { println!( @@ -233,6 +261,6 @@ impl KaniSession { println!("No functions were eligible for automatic verification."); } - Ok(()) + Ok(failing) } } diff --git a/kani-driver/src/call_cargo.rs b/kani-driver/src/call_cargo.rs index 5f9f18d03563..a61e3f532d86 100644 --- a/kani-driver/src/call_cargo.rs +++ b/kani-driver/src/call_cargo.rs @@ -194,7 +194,7 @@ crate-type = ["lib"] // If you are adding a new `kani-compiler` argument, you likely want to put it `kani_compiler_flags()` instead, // unless there a reason it shouldn't be passed to dependencies. // (Note that at the time of writing, passing the other compiler args to dependencies is a no-op, since `--reachability=None` skips codegen anyway.) - self.pkg_args.push(self.reachability_arg()); + let pkg_args = vec!["--".into(), self.reachability_arg()]; let mut found_target = false; let packages = self.packages_to_verify(&self.args, &metadata)?; @@ -206,7 +206,7 @@ crate-type = ["lib"] cmd.args(&cargo_args) .args(vec!["-p", &package.id.to_string()]) .args(verification_target.to_args()) - .args(&self.pkg_args) + .args(&pkg_args) .env("RUSTC", &self.kani_compiler) // Use CARGO_ENCODED_RUSTFLAGS instead of RUSTFLAGS is preferred. See // https://doc.rust-lang.org/cargo/reference/environment-variables.html @@ -356,7 +356,13 @@ crate-type = ["lib"] && t1.doc == t2.doc) } + let compile_start = std::time::Instant::now(); let artifacts = self.run_build(cargo_cmd)?; + if std::env::var("TIME_COMPILER").is_ok() { + // conditionally print the compilation time for debugging & use by `compile-timer` + // doesn't just use the existing `--debug` flag because the number of prints significantly affects performance + println!("BUILT {} IN {:?}μs", target.name, compile_start.elapsed().as_micros()); + } debug!(?artifacts, "run_build_target"); // We generate kani specific artifacts only for the build target. The build target is diff --git a/kani-driver/src/call_goto_instrument.rs b/kani-driver/src/call_goto_instrument.rs index 09b8cbb18fc8..905b22c116c6 100644 --- a/kani-driver/src/call_goto_instrument.rs +++ b/kani-driver/src/call_goto_instrument.rs @@ -26,9 +26,9 @@ impl KaniSession { // We actually start by calling goto-cc to start the specialization: self.specialize_to_proof_harness(input, output, &harness.mangled_name)?; - let restrictions = project.get_harness_artifact(&harness, ArtifactType::VTableRestriction); + let restrictions = project.get_harness_artifact(harness, ArtifactType::VTableRestriction); if let Some(restrictions_path) = restrictions { - self.apply_vtable_restrictions(&output, restrictions_path)?; + self.apply_vtable_restrictions(output, restrictions_path)?; } // Run sanity checks in the model generated by kani-compiler before any goto-instrument @@ -66,7 +66,7 @@ impl KaniSession { let c_demangled = alter_extension(output, "demangled.c"); let prett_name_map = - project.get_harness_artifact(&harness, ArtifactType::PrettyNameMap).unwrap(); + project.get_harness_artifact(harness, ArtifactType::PrettyNameMap).unwrap(); self.demangle_c(prett_name_map, &c_outfile, &c_demangled)?; if !self.args.common_args.quiet { println!("Demangled GotoC code written to {}", c_demangled.to_string_lossy()) diff --git a/kani-driver/src/call_single_file.rs b/kani-driver/src/call_single_file.rs index a24028dd84ba..f3e488791ddc 100644 --- a/kani-driver/src/call_single_file.rs +++ b/kani-driver/src/call_single_file.rs @@ -151,6 +151,10 @@ impl KaniSession { flags.push("--no-assert-contracts".into()); } + if let Some(args) = self.autoharness_compiler_flags.clone() { + flags.extend(args); + } + flags.extend(self.args.common_args.unstable_features.as_arguments().map(str::to_string)); flags @@ -182,6 +186,11 @@ impl KaniSession { .map(OsString::from), ); + if self.args.no_codegen { + flags.push("-Z".into()); + flags.push("no-codegen".into()); + } + if let Some(seed_opt) = self.args.randomize_layout { flags.push("-Z".into()); flags.push("randomize-layout".into()); diff --git a/kani-driver/src/cbmc_output_parser.rs b/kani-driver/src/cbmc_output_parser.rs index 79a2c20732b4..749bb9abec1e 100644 --- a/kani-driver/src/cbmc_output_parser.rs +++ b/kani-driver/src/cbmc_output_parser.rs @@ -773,8 +773,8 @@ mod tests { } ] }"#; - let parser_item: Result = serde_json::from_str(&data); - let result_struct: Result = serde_json::from_str(&data); + let parser_item: Result = serde_json::from_str(data); + let result_struct: Result = serde_json::from_str(data); assert!(parser_item.is_ok()); assert!(result_struct.is_ok()); } diff --git a/kani-driver/src/cbmc_property_renderer.rs b/kani-driver/src/cbmc_property_renderer.rs index 95a2175a2eac..f3e6791cde8f 100644 --- a/kani-driver/src/cbmc_property_renderer.rs +++ b/kani-driver/src/cbmc_property_renderer.rs @@ -441,7 +441,7 @@ pub fn format_coverage( let verification_output = format_result(&non_coverage_checks, status, should_panic, failed_properties, show_checks); let cov_results_intro = "Source-based code coverage results:"; - let result = format!("{}\n{}\n\n{}", verification_output, cov_results_intro, cov_results); + let result = format!("{verification_output}\n{cov_results_intro}\n\n{cov_results}"); result } @@ -557,8 +557,8 @@ fn has_check_failure(properties: &Vec, description: &str) -> bool { // Determines if there were unwinding assertion failures in a set of properties fn has_unwinding_assertion_failures(properties: &Vec) -> bool { - has_check_failure(&properties, UNWINDING_ASSERT_DESC) - || has_check_failure(&properties, UNWINDING_ASSERT_REC_DESC) + has_check_failure(properties, UNWINDING_ASSERT_DESC) + || has_check_failure(properties, UNWINDING_ASSERT_REC_DESC) } /// Replaces the description of all properties from functions with a missing diff --git a/kani-driver/src/concrete_playback/test_generator.rs b/kani-driver/src/concrete_playback/test_generator.rs index d397c5f00dff..9393eec1999c 100644 --- a/kani-driver/src/concrete_playback/test_generator.rs +++ b/kani-driver/src/concrete_playback/test_generator.rs @@ -46,7 +46,7 @@ impl KaniSession { .iter() .map(|(prop, concrete_items)| { let pretty_name = harness.get_harness_name_unqualified(); - format_unit_test(&pretty_name, &concrete_items, gen_test_doc(harness, prop)) + format_unit_test(pretty_name, concrete_items, gen_test_doc(harness, prop)) }) .collect(); unit_tests.dedup_by(|a, b| a.name == b.name); @@ -86,10 +86,12 @@ impl KaniSession { harness.original_end_line, unit_tests, ) - .expect(&format!( - "Failed to modify source code for the file `{}`", - &harness.original_file - )); + .unwrap_or_else(|_| { + panic!( + "Failed to modify source code for the file `{}`", + &harness.original_file + ) + }); } } verification_result.generated_concrete_test = true; @@ -122,7 +124,7 @@ impl KaniSession { line_range: Some((unit_test_start_line, unit_test_end_line)), }]; self.run_rustfmt(&file_line_ranges, Some(&path)) - .unwrap_or_else(|err| println!("WARNING: {}", err)); + .unwrap_or_else(|err| println!("WARNING: {err}")); } Ok(()) @@ -413,7 +415,7 @@ mod concrete_vals_extractor { let trace = property .trace .as_ref() - .expect(&format!("Missing trace for {}", property.property_name())); + .unwrap_or_else(|| panic!("Missing trace for {}", property.property_name())); let concrete_items: Vec = trace.iter().filter_map(&extract_from_trace_item).collect(); @@ -452,8 +454,9 @@ mod concrete_vals_extractor { str_chunk_len, 8, "Tried to read a chunk of 8 bits of actually read {str_chunk_len} bits" ); - let next_byte = u8::from_str_radix(str_chunk, 2) - .expect(&format!("Couldn't convert the string chunk `{str_chunk}` to u8")); + let next_byte = u8::from_str_radix(str_chunk, 2).unwrap_or_else(|_| { + panic!("Couldn't convert the string chunk `{str_chunk}` to u8") + }); next_num.push(next_byte); } diff --git a/kani-driver/src/harness_runner.rs b/kani-driver/src/harness_runner.rs index 2b52aa78d0e6..db47c7c8d40a 100644 --- a/kani-driver/src/harness_runner.rs +++ b/kani-driver/src/harness_runner.rs @@ -72,12 +72,12 @@ impl<'pr> HarnessRunner<'_, 'pr> { .enumerate() .map(|(idx, harness)| -> Result> { let goto_file = - self.project.get_harness_artifact(&harness, ArtifactType::Goto).unwrap(); + self.project.get_harness_artifact(harness, ArtifactType::Goto).unwrap(); - self.sess.instrument_model(goto_file, goto_file, &self.project, &harness)?; + self.sess.instrument_model(goto_file, goto_file, self.project, harness)?; if self.sess.args.synthesize_loop_contracts { - self.sess.synthesize_loop_contracts(goto_file, &goto_file, &harness)?; + self.sess.synthesize_loop_contracts(goto_file, goto_file, harness)?; } let result = self.sess.check_harness(goto_file, harness)?; @@ -180,7 +180,7 @@ impl KaniSession { file_output = format!("Thread {thread_index}:\n{file_output}"); } - if let Err(e) = writeln!(file, "{}", file_output) { + if let Err(e) = writeln!(file, "{file_output}") { eprintln!( "Failed to write to file {}: {}", file_name.into_os_string().into_string().unwrap(), @@ -298,11 +298,13 @@ impl KaniSession { self.show_coverage_summary()?; } - if self.auto_harness { - self.print_autoharness_summary(automatic)?; - } + let autoharness_failing = if self.autoharness_compiler_flags.is_some() { + self.print_autoharness_summary(automatic)? + } else { + 0 + }; - if failing > 0 && !self.auto_harness { + if failing + autoharness_failing > 0 { // Failure exit code without additional error message drop(self); std::process::exit(1); diff --git a/kani-driver/src/list/collect_metadata.rs b/kani-driver/src/list/collect_metadata.rs index 5a5ed290dcad..c0d452ee6051 100644 --- a/kani-driver/src/list/collect_metadata.rs +++ b/kani-driver/src/list/collect_metadata.rs @@ -10,69 +10,64 @@ use crate::{ VerificationArgs, list_args::{CargoListArgs, StandaloneListArgs}, }, - list::ListMetadata, list::output::output_list_results, + list::{FileName, HarnessName, ListMetadata}, project::{Project, cargo_project, standalone_project, std_project}, session::KaniSession, version::print_kani_version, }; use anyhow::Result; -use kani_metadata::{ContractedFunction, HarnessKind, KaniMetadata}; +use kani_metadata::{ContractedFunction, HarnessKind, HarnessMetadata, KaniMetadata}; /// Process the KaniMetadata output from kani-compiler and output the list subcommand results -pub fn process_metadata(metadata: Vec) -> ListMetadata { - // We use ordered maps and sets so that the output is in lexicographic order (and consistent across invocations). - - // Map each file to a vector of its harnesses. - let mut standard_harnesses: BTreeMap> = BTreeMap::new(); - let mut contract_harnesses: BTreeMap> = BTreeMap::new(); +pub fn process_metadata(metadata: Vec) -> BTreeSet { + let mut list_metadata: BTreeSet = BTreeSet::new(); + + let insert = |harness_meta: HarnessMetadata, + map: &mut BTreeMap>, + count: &mut usize| { + *count += 1; + if let Some(harnesses) = map.get_mut(&harness_meta.original_file) { + harnesses.insert(harness_meta.pretty_name); + } else { + map.insert(harness_meta.original_file, BTreeSet::from([harness_meta.pretty_name])); + }; + }; - let mut contracted_functions: BTreeSet = BTreeSet::new(); + for kani_meta in metadata { + // We use ordered maps and sets so that the output is in lexicographic order (and consistent across invocations). + let mut standard_harnesses: BTreeMap> = BTreeMap::new(); + let mut contract_harnesses: BTreeMap> = BTreeMap::new(); + let mut contracted_functions: BTreeSet = BTreeSet::new(); - let mut standard_harnesses_count = 0; - let mut contract_harnesses_count = 0; + let mut standard_harnesses_count = 0; + let mut contract_harnesses_count = 0; - for kani_meta in metadata { for harness_meta in kani_meta.proof_harnesses { match harness_meta.attributes.kind { HarnessKind::Proof => { - standard_harnesses_count += 1; - if let Some(harnesses) = standard_harnesses.get_mut(&harness_meta.original_file) - { - harnesses.insert(harness_meta.pretty_name); - } else { - standard_harnesses.insert( - harness_meta.original_file, - BTreeSet::from([harness_meta.pretty_name]), - ); - } + insert(harness_meta, &mut standard_harnesses, &mut standard_harnesses_count); } HarnessKind::ProofForContract { .. } => { - contract_harnesses_count += 1; - if let Some(harnesses) = contract_harnesses.get_mut(&harness_meta.original_file) - { - harnesses.insert(harness_meta.pretty_name); - } else { - contract_harnesses.insert( - harness_meta.original_file, - BTreeSet::from([harness_meta.pretty_name]), - ); - } + insert(harness_meta, &mut contract_harnesses, &mut contract_harnesses_count); } HarnessKind::Test => {} } } contracted_functions.extend(kani_meta.contracted_functions.into_iter()); - } - ListMetadata { - standard_harnesses, - standard_harnesses_count, - contract_harnesses, - contract_harnesses_count, - contracted_functions, + list_metadata.insert(ListMetadata { + crate_name: kani_meta.crate_name, + standard_harnesses, + standard_harnesses_count, + contract_harnesses, + contract_harnesses_count, + contracted_functions, + }); } + + list_metadata } pub fn list_cargo(args: CargoListArgs, mut verify_opts: VerificationArgs) -> Result<()> { diff --git a/kani-driver/src/list/mod.rs b/kani-driver/src/list/mod.rs index 0a5aa523ea6a..d300908d476d 100644 --- a/kani-driver/src/list/mod.rs +++ b/kani-driver/src/list/mod.rs @@ -8,15 +8,41 @@ use std::collections::{BTreeMap, BTreeSet}; pub mod collect_metadata; pub mod output; +type FileName = String; +type HarnessName = String; + +/// Metadata for the list subcommand for a given crate. +/// It is important that crate_name is the first field so that `Ord` orders two ListMetadata objects by crate name. +#[derive(PartialEq, Eq, PartialOrd, Ord)] pub struct ListMetadata { + crate_name: String, // Files mapped to their #[kani::proof] harnesses - standard_harnesses: BTreeMap>, + standard_harnesses: BTreeMap>, // Total number of #[kani::proof] harnesses standard_harnesses_count: usize, // Files mapped to their #[kani::proof_for_contract] harnesses - contract_harnesses: BTreeMap>, + contract_harnesses: BTreeMap>, // Total number of #[kani:proof_for_contract] harnesses contract_harnesses_count: usize, // Set of all functions under contract contracted_functions: BTreeSet, } + +/// Given a collection of ListMetadata objects, merge them into a single ListMetadata object. +pub fn merge_list_metadata(collection: T) -> ListMetadata +where + T: Extend, + T: IntoIterator, +{ + collection + .into_iter() + .reduce(|mut acc, item| { + acc.standard_harnesses.extend(item.standard_harnesses); + acc.standard_harnesses_count += item.standard_harnesses_count; + acc.contract_harnesses.extend(item.contract_harnesses); + acc.contract_harnesses_count += item.contract_harnesses_count; + acc.contracted_functions.extend(item.contracted_functions); + acc + }) + .expect("Cannot merge empty collection of ListMetadata objects") +} diff --git a/kani-driver/src/list/output.rs b/kani-driver/src/list/output.rs index 47b32bd6ed5e..1db9af6c2c19 100644 --- a/kani-driver/src/list/output.rs +++ b/kani-driver/src/list/output.rs @@ -3,13 +3,18 @@ //! This module handles outputting the result for the list subcommand use std::{ + collections::BTreeSet, fmt::Display, fs::File, io::{BufWriter, Write}, path::Path, }; -use crate::{args::list_args::Format, list::ListMetadata, version::KANI_VERSION}; +use crate::{ + args::list_args::Format, + list::{ListMetadata, merge_list_metadata}, + version::KANI_VERSION, +}; use anyhow::Result; use comfy_table::Table as PrettyTable; use serde_json::json; @@ -21,7 +26,11 @@ const FILE_VERSION: &str = "0.1"; const OUTPUT_FILENAME: &str = "kani-list"; /// Output the results of the list subcommand. -pub fn output_list_results(list_metadata: ListMetadata, format: Format, quiet: bool) -> Result<()> { +pub fn output_list_results( + list_metadata: BTreeSet, + format: Format, + quiet: bool, +) -> Result<()> { match format { Format::Pretty => pretty(list_metadata), Format::Markdown => markdown(list_metadata, quiet), @@ -29,36 +38,64 @@ pub fn output_list_results(list_metadata: ListMetadata, format: Format, quiet: b } } -/// Print results to the terminal. -fn pretty(list_metadata: ListMetadata) -> Result<()> { - let table = if list_metadata.contracted_functions.is_empty() { - None - } else { - let (header, rows) = construct_contracts_table(&list_metadata); - let mut t = PrettyTable::new(); - t.set_header(header).add_rows(rows); - Some(t) +fn pretty_constructor(header: Vec, rows: Vec>) -> Result { + let mut t = PrettyTable::new(); + t.set_header(header).add_rows(rows); + Ok(t) +} + +fn markdown_constructor(header: Vec, rows: Vec>) -> Result { + Ok(MarkdownTable::new(Some(header), rows)?) +} + +/// Construct the "Contracts" and "Standard Harnesses" tables. +/// `table_constructor` is a function that, given the header and rows for the tables, creates a particular kind of table. +fn construct_output( + list_metadata: BTreeSet, + table_constructor: fn(Vec, Vec>) -> Result, +) -> Result<(String, String)> { + let contract_output = { + const CONTRACTS_SECTION: &str = "Contracts:"; + const NO_CONTRACTS_MSG: &str = "No contracts or contract harnesses found."; + let contract_table = if list_metadata.iter().all(|md| md.contracted_functions.is_empty()) { + None + } else { + let (header, rows) = construct_contracts_table(&list_metadata); + let t = table_constructor(header, rows)?; + Some(t) + }; + format_results(contract_table, CONTRACTS_SECTION.to_string(), NO_CONTRACTS_MSG.to_string()) }; - let output = format_results(table, &list_metadata); - println!("{}", output); + let standard_output = { + const HARNESSES_SECTION: &str = "Standard Harnesses (#[kani::proof]):"; + const NO_HARNESSES_MSG: &str = "No standard harnesses found."; + let standard_table = { + let (header, rows) = construct_standard_table(&list_metadata); + let t = table_constructor(header, rows)?; + Some(t) + }; + format_results(standard_table, HARNESSES_SECTION.to_string(), NO_HARNESSES_MSG.to_string()) + }; + Ok((contract_output, standard_output)) +} + +/// Print results to the terminal. +fn pretty(list_metadata: BTreeSet) -> Result<()> { + let (contract_output, standard_output) = construct_output(list_metadata, pretty_constructor)?; + println!("{contract_output}"); + println!("{standard_output}"); Ok(()) } /// Output results to a Markdown file. -fn markdown(list_metadata: ListMetadata, quiet: bool) -> Result<()> { - let table = if list_metadata.contracted_functions.is_empty() { - None - } else { - let (header, rows) = construct_contracts_table(&list_metadata); - Some(MarkdownTable::new(Some(header), rows)?) - }; - - let output = format_results(table, &list_metadata); +fn markdown(list_metadata: BTreeSet, quiet: bool) -> Result<()> { + let (contract_output, standard_output) = construct_output(list_metadata, markdown_constructor)?; let out_path = Path::new(OUTPUT_FILENAME).with_extension("md"); let mut out_file = File::create(&out_path).unwrap(); - out_file.write_all(output.as_bytes()).unwrap(); + out_file.write_all(contract_output.as_bytes()).unwrap(); + out_file.write_all(standard_output.as_bytes()).unwrap(); if !quiet { println!("Wrote list results to {}", std::fs::canonicalize(&out_path)?.display()); } @@ -66,21 +103,23 @@ fn markdown(list_metadata: ListMetadata, quiet: bool) -> Result<()> { } /// Output results as a JSON file. -fn json(list_metadata: ListMetadata, quiet: bool) -> Result<()> { +fn json(list_metadata: BTreeSet, quiet: bool) -> Result<()> { let out_path = Path::new(OUTPUT_FILENAME).with_extension("json"); let out_file = File::create(&out_path).unwrap(); let writer = BufWriter::new(out_file); + let combined_md = merge_list_metadata(list_metadata); + let json_obj = json!({ "kani-version": KANI_VERSION, "file-version": FILE_VERSION, - "standard-harnesses": &list_metadata.standard_harnesses, - "contract-harnesses": &list_metadata.contract_harnesses, - "contracts": &list_metadata.contracted_functions, + "standard-harnesses": combined_md.standard_harnesses, + "contract-harnesses": combined_md.contract_harnesses, + "contracts": combined_md.contracted_functions, "totals": { - "standard-harnesses": list_metadata.standard_harnesses_count, - "contract-harnesses": list_metadata.contract_harnesses_count, - "functions-under-contract": list_metadata.contracted_functions.len(), + "standard-harnesses": combined_md.standard_harnesses_count, + "contract-harnesses": combined_md.contract_harnesses_count, + "functions-under-contract": combined_md.contracted_functions.len(), } }); @@ -95,65 +134,95 @@ fn json(list_metadata: ListMetadata, quiet: bool) -> Result<()> { /// Construct the rows for the table of contracts information. /// Returns a tuple of the table header and the rows. -fn construct_contracts_table(list_metadata: &ListMetadata) -> (Vec, Vec>) { +fn construct_contracts_table( + list_metadata: &BTreeSet, +) -> (Vec, Vec>) { const NO_HARNESSES_MSG: &str = "NONE"; + const CRATE_NAME: &str = "Crate"; const FUNCTION_HEADER: &str = "Function"; const CONTRACT_HARNESSES_HEADER: &str = "Contract Harnesses (#[kani::proof_for_contract])"; const TOTALS_HEADER: &str = "Total"; - let header = - vec![String::new(), FUNCTION_HEADER.to_string(), CONTRACT_HARNESSES_HEADER.to_string()]; + let header = vec![ + String::new(), + CRATE_NAME.to_string(), + FUNCTION_HEADER.to_string(), + CONTRACT_HARNESSES_HEADER.to_string(), + ]; let mut rows: Vec> = vec![]; - - for cf in &list_metadata.contracted_functions { - let mut row = vec![String::new(), cf.function.clone()]; - if cf.harnesses.is_empty() { - row.push(NO_HARNESSES_MSG.to_string()); - } else { - row.push(cf.harnesses.join(", ")); + let mut functions_under_contract_total = 0; + let mut contract_harnesses_total = 0; + + for crate_md in list_metadata { + for cf in &crate_md.contracted_functions { + let mut row = vec![String::new(), crate_md.crate_name.to_string(), cf.function.clone()]; + if cf.harnesses.is_empty() { + row.push(NO_HARNESSES_MSG.to_string()); + } else { + row.push(cf.harnesses.join(", ")); + } + rows.push(row); } - rows.push(row); + functions_under_contract_total += crate_md.contracted_functions.len(); + contract_harnesses_total += crate_md.contract_harnesses_count; } let totals_row = vec![ TOTALS_HEADER.to_string(), - list_metadata.contracted_functions.len().to_string(), - list_metadata.contract_harnesses_count.to_string(), + String::new(), + functions_under_contract_total.to_string(), + contract_harnesses_total.to_string(), ]; rows.push(totals_row); (header, rows) } -/// Format results as a String -fn format_results(table: Option, list_metadata: &ListMetadata) -> String { - const CONTRACTS_SECTION: &str = "Contracts:"; - const HARNESSES_SECTION: &str = "Standard Harnesses (#[kani::proof]):"; - const NO_CONTRACTS_MSG: &str = "No contracts or contract harnesses found."; - const NO_HARNESSES_MSG: &str = "No standard harnesses found."; +fn construct_standard_table( + list_metadata: &BTreeSet, +) -> (Vec, Vec>) { + const CRATE_NAME: &str = "Crate"; + const HARNESS_HEADER: &str = "Harness"; + const TOTALS_HEADER: &str = "Total"; - let mut output: Vec = vec![]; - output.push(format!("\n{CONTRACTS_SECTION}")); + let header = vec![String::new(), CRATE_NAME.to_string(), HARNESS_HEADER.to_string()]; - if let Some(table) = table { - output.push(format!("{table}")); - } else { - output.push(NO_CONTRACTS_MSG.to_string()); - } + let mut rows: Vec> = vec![]; - output.push(format!("\n{HARNESSES_SECTION}")); - if list_metadata.standard_harnesses.is_empty() { - output.push(NO_HARNESSES_MSG.to_string()); + let mut total = 0; + + for crate_md in list_metadata { + for harnesses in crate_md.standard_harnesses.values() { + for harness in harnesses { + rows.push(vec![ + String::new(), + crate_md.crate_name.to_string(), + harness.to_string(), + ]); + } + total += harnesses.len(); + } } - let mut std_harness_index = 0; + let totals_row = vec![TOTALS_HEADER.to_string(), String::new(), total.to_string()]; + rows.push(totals_row); + + (header, rows) +} - for harnesses in (&list_metadata.standard_harnesses).values() { - for harness in harnesses { - output.push(format!("{}. {harness}", std_harness_index + 1)); - std_harness_index += 1; - } +fn format_results( + table: Option, + section_name: String, + absent_name: String, +) -> String { + let mut output: Vec = vec![]; + output.push(format!("\n{section_name}")); + + if let Some(table) = table { + output.push(format!("{table}")); + } else { + output.push(absent_name); } output.join("\n") diff --git a/kani-driver/src/project.rs b/kani-driver/src/project.rs index 2a1aad2c1eff..80a4b050adff 100644 --- a/kani-driver/src/project.rs +++ b/kani-driver/src/project.rs @@ -6,7 +6,7 @@ use crate::metadata::from_json; use crate::session::KaniSession; -use crate::util::crate_name; +use crate::util::{crate_name, info_operation}; use anyhow::{Context, Result}; use kani_metadata::{ ArtifactType, ArtifactType::*, HarnessMetadata, KaniMetadata, artifact::convert_type, @@ -177,6 +177,13 @@ impl Artifact { /// be collected from the project. pub fn cargo_project(session: &mut KaniSession, keep_going: bool) -> Result { let outputs = session.cargo_build(keep_going)?; + if session.args.no_codegen { + info_operation( + "info:", + "Compilation succeeded up until codegen. Skipping codegen because of `--no-codegen` option. Rerun without `--no-codegen` to perform codegen.", + ); + return Ok(Project::default()); + } let outdir = outputs.outdir.canonicalize()?; // For the MIR Linker we know there is only one metadata per crate. Use that in our favor. let metadata = @@ -225,7 +232,7 @@ impl<'a> StandaloneProjectBuilder<'a> { } else { input.canonicalize().unwrap().parent().unwrap().to_path_buf() }; - let crate_name = if let Some(name) = krate_name { name } else { crate_name(&input) }; + let crate_name = if let Some(name) = krate_name { name } else { crate_name(input) }; let metadata = standalone_artifact(&outdir, &crate_name, Metadata); Ok(StandaloneProjectBuilder { outdir, diff --git a/kani-driver/src/session.rs b/kani-driver/src/session.rs index 20ebef0e2aaa..2448c593b47d 100644 --- a/kani-driver/src/session.rs +++ b/kani-driver/src/session.rs @@ -31,11 +31,9 @@ pub struct KaniSession { /// The common command-line arguments pub args: VerificationArgs, - /// Automatically verify functions in the crate, in addition to running manual harnesses. - pub auto_harness: bool, - - /// The arguments that will be passed only to the target package, not its dependencies. - pub pkg_args: Vec, + /// The autoharness-specific compiler arguments. + /// Invariant: this field is_some() iff the autoharness subcommand is enabled. + pub autoharness_compiler_flags: Option>, /// Include all publicly-visible symbols in the generated goto binary, not just those reachable from /// a proof harness. Useful when attempting to verify things that were not annotated with kani @@ -71,8 +69,7 @@ impl KaniSession { Ok(KaniSession { args, - auto_harness: false, - pkg_args: vec!["--".to_string()], + autoharness_compiler_flags: None, codegen_tests: false, kani_compiler: install.kani_compiler()?, kani_lib_c: install.kani_lib_c()?, @@ -101,7 +98,7 @@ impl KaniSession { pub fn reachability_mode(&self) -> ReachabilityMode { if self.codegen_tests { ReachabilityMode::Tests - } else if self.auto_harness { + } else if self.autoharness_compiler_flags.is_some() { ReachabilityMode::AllFns } else { ReachabilityMode::ProofHarnesses @@ -213,7 +210,7 @@ async fn run_terminal_timeout( cmd.stderr(std::process::Stdio::null()); } if verbosity.verbose() { - println!("[Kani] Running: `{}`", render_command(&cmd.as_std()).to_string_lossy()); + println!("[Kani] Running: `{}`", render_command(cmd.as_std()).to_string_lossy()); } let program = cmd.as_std().get_program().to_string_lossy().to_string(); let result = with_timer( diff --git a/kani_metadata/Cargo.toml b/kani_metadata/Cargo.toml index 638b81f609d2..73eee459900d 100644 --- a/kani_metadata/Cargo.toml +++ b/kani_metadata/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_metadata" -version = "0.60.0" +version = "0.62.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/kani_metadata/src/unstable.rs b/kani_metadata/src/unstable.rs index a901f11b357d..f72c53b91e57 100644 --- a/kani_metadata/src/unstable.rs +++ b/kani_metadata/src/unstable.rs @@ -142,4 +142,13 @@ impl EnabledUnstableFeatures { pub fn contains(&self, feature: UnstableFeature) -> bool { self.enabled_unstable_features.contains(&feature) } + + /// Enable an additional unstable feature. + /// Note that this enables an unstable feature that the user did not pass on the command line, so this function should be called with caution. + /// At time of writing, the only use is to enable -Z function-contracts and -Z loop-contracts when the autoharness subcommand is running. + pub fn enable_feature(&mut self, feature: UnstableFeature) { + if !self.contains(feature) { + self.enabled_unstable_features.push(feature); + } + } } diff --git a/library/kani/Cargo.toml b/library/kani/Cargo.toml index 4b32b92485e8..71fc4275a606 100644 --- a/library/kani/Cargo.toml +++ b/library/kani/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani" -version = "0.60.0" +version = "0.62.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani/kani_lib.c b/library/kani/kani_lib.c index eca17a3abb0e..02a50708a7ff 100644 --- a/library/kani/kani_lib.c +++ b/library/kani/kani_lib.c @@ -10,8 +10,6 @@ void *memcpy(void *dst, const void *src, size_t n); void *calloc(size_t nmemb, size_t size); void *malloc(size_t size); -typedef __CPROVER_bool bool; - /// Mapping unit to `void` works for functions with no return type but not for /// variables with type unit. We treat both uniformly by declaring an empty /// struct type: `struct Unit {}` and a global variable `struct Unit VoidUnit` @@ -22,13 +20,13 @@ extern struct Unit VoidUnit; // `assert` then `assume` #define __KANI_assert(cond, msg) \ do { \ - bool __KANI_temp = (cond); \ + __CPROVER_bool __KANI_temp = (cond); \ __CPROVER_assert(__KANI_temp, msg); \ __CPROVER_assume(__KANI_temp); \ } while (0) // Check that the input is either a power of 2, or 0. Algorithm from Hackers Delight. -bool __KANI_is_nonzero_power_of_two(size_t i) { return (i != 0) && (i & (i - 1)) == 0; } +__CPROVER_bool __KANI_is_nonzero_power_of_two(size_t i) { return (i != 0) && (i & (i - 1)) == 0; } // This is a C implementation of the __rust_alloc function. // https://stdrs.dev/nightly/x86_64-unknown-linux-gnu/alloc/alloc/fn.__rust_alloc.html diff --git a/library/kani/src/bounded_arbitrary.rs b/library/kani/src/bounded_arbitrary.rs new file mode 100644 index 000000000000..09486df9a13e --- /dev/null +++ b/library/kani/src/bounded_arbitrary.rs @@ -0,0 +1,62 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This module introduces implementations for some std containers. + +use kani::{Arbitrary, BoundedArbitrary}; + +// This implementation overlaps with `kani::any_vec` in `kani/library/kani/src/vec.rs`. +// This issue `https://github.com/model-checking/kani/issues/4027` tracks deprecating +// `kani::any_vec` in favor of this implementation. +impl BoundedArbitrary for Vec { + fn bounded_any() -> Self { + let real_length = kani::any_where(|&size| size <= N); + let array: [T; N] = kani::any(); + let mut vec = Vec::from(array); + vec.truncate(real_length); + vec + } +} + +impl BoundedArbitrary for String { + fn bounded_any() -> Self { + let bytes: [u8; N] = kani::any(); + + if let Some(s) = bytes.utf8_chunks().next() { s.valid().into() } else { String::new() } + } +} + +impl BoundedArbitrary + for std::collections::HashMap> +where + K: Arbitrary + std::cmp::Eq + std::hash::Hash, + V: Arbitrary, +{ + fn bounded_any() -> Self { + let mut hash_map = std::collections::HashMap::default(); + for _ in 0..N { + // this check seems to perform better than 0..kany::any_where(|l| *l <= N) + if bool::any() { + hash_map.insert(K::any(), V::any()); + } + } + hash_map + } +} + +impl BoundedArbitrary + for std::collections::HashSet> +where + V: Arbitrary + std::cmp::Eq + std::hash::Hash, +{ + fn bounded_any() -> Self { + let mut hash_set = std::collections::HashSet::default(); + for _ in 0..N { + // this check seems to perform better than 0..kany::any_where(|l| *l <= N) + if bool::any() { + hash_set.insert(V::any()); + } + } + hash_set + } +} diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index b284a3544b67..7c7a6a756917 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -26,6 +26,7 @@ extern crate self as kani; pub mod arbitrary; +pub mod bounded_arbitrary; #[cfg(feature = "concrete_playback")] mod concrete_playback; pub mod futures; diff --git a/library/kani_core/Cargo.toml b/library/kani_core/Cargo.toml index 17338d03f12f..bd824f0e1c97 100644 --- a/library/kani_core/Cargo.toml +++ b/library/kani_core/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_core" -version = "0.60.0" +version = "0.62.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani_core/src/arbitrary.rs b/library/kani_core/src/arbitrary.rs index 3ef474b65364..18b84a72e8e5 100644 --- a/library/kani_core/src/arbitrary.rs +++ b/library/kani_core/src/arbitrary.rs @@ -14,11 +14,10 @@ mod slice; #[macro_export] #[allow(clippy::crate_in_macro_def)] macro_rules! generate_arbitrary { - ($core:path) => { + () => { use core_path::marker::{PhantomData, PhantomPinned}; use core_path::mem::MaybeUninit; use core_path::ptr::{self, addr_of_mut}; - use $core as core_path; pub trait Arbitrary where diff --git a/library/kani_core/src/bounded_arbitrary.rs b/library/kani_core/src/bounded_arbitrary.rs new file mode 100644 index 000000000000..a35bc1b53b1b --- /dev/null +++ b/library/kani_core/src/bounded_arbitrary.rs @@ -0,0 +1,37 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This module introduces the `BoundedArbitrary` trait. + +#[macro_export] +#[allow(clippy::crate_in_macro_def)] +macro_rules! generate_bounded_arbitrary { + () => { + use core_path::ops::Deref; + + pub trait BoundedArbitrary { + fn bounded_any() -> Self; + } + + impl BoundedArbitrary for Option + where + T: BoundedArbitrary, + { + fn bounded_any() -> Self { + let opt: Option<()> = any(); + opt.map(|_| T::bounded_any::()) + } + } + + impl BoundedArbitrary for Result + where + T: BoundedArbitrary, + E: BoundedArbitrary, + { + fn bounded_any() -> Self { + let res: Result<(), ()> = any(); + res.map(|_| T::bounded_any::()).map_err(|_| E::bounded_any::()) + } + } + }; +} diff --git a/library/kani_core/src/lib.rs b/library/kani_core/src/lib.rs index 51b243c9ad33..b0c27436227f 100644 --- a/library/kani_core/src/lib.rs +++ b/library/kani_core/src/lib.rs @@ -21,6 +21,7 @@ #![feature(f128)] mod arbitrary; +mod bounded_arbitrary; mod float; mod mem; mod mem_init; @@ -41,9 +42,12 @@ macro_rules! kani_lib { #[unstable(feature = "kani", issue = "none")] pub mod kani { // We need to list them all today because there is conflict with unstable. + use core as core_path; pub use kani_core::*; - kani_core::kani_intrinsics!(core); - kani_core::generate_arbitrary!(core); + + kani_core::kani_intrinsics!(); + kani_core::generate_arbitrary!(); + kani_core::generate_bounded_arbitrary!(); kani_core::generate_models!(); pub mod float { @@ -62,8 +66,11 @@ macro_rules! kani_lib { (kani) => { pub use kani_core::*; - kani_core::kani_intrinsics!(std); - kani_core::generate_arbitrary!(std); + use std as core_path; + + kani_core::kani_intrinsics!(); + kani_core::generate_arbitrary!(); + kani_core::generate_bounded_arbitrary!(); kani_core::generate_models!(); pub mod float { @@ -138,7 +145,7 @@ macro_rules! kani_lib { #[allow(clippy::crate_in_macro_def)] #[macro_export] macro_rules! kani_intrinsics { - ($core:tt) => { + () => { /// Creates an assumption that will be valid after this statement run. Note that the assumption /// will only be applied for paths that follow the assumption. If the assumption doesn't hold, the /// program will exit successfully. @@ -200,6 +207,34 @@ macro_rules! kani_intrinsics { assert!(cond, "{}", msg); } + #[macro_export] + macro_rules! forall { + (|$i:ident in ($lower_bound:expr, $upper_bound:expr)| $predicate:expr) => {{ + let lower_bound: usize = $lower_bound; + let upper_bound: usize = $upper_bound; + let predicate = |$i| $predicate; + kani::internal::kani_forall(lower_bound, upper_bound, predicate) + }}; + (|$i:ident | $predicate:expr) => {{ + let predicate = |$i| $predicate; + kani::internal::kani_forall(usize::MIN, usize::MAX, predicate) + }}; + } + + #[macro_export] + macro_rules! exists { + (|$i:ident in ($lower_bound:expr, $upper_bound:expr)| $predicate:expr) => {{ + let lower_bound: usize = $lower_bound; + let upper_bound: usize = $upper_bound; + let predicate = |$i| $predicate; + kani::internal::kani_exists(lower_bound, upper_bound, predicate) + }}; + (|$i:ident | $predicate:expr) => {{ + let predicate = |$i| $predicate; + kani::internal::kani_exists(usize::MIN, usize::MAX, predicate) + }}; + } + /// Creates a cover property with the specified condition and message. /// /// # Example: @@ -253,6 +288,15 @@ macro_rules! kani_intrinsics { T::any() } + /// Creates a symbolic value *bounded* by `N`. Bounded means `|T| <= N`. The type + /// implementing BoundedArbitrary decides exactly what size means for them. + /// + /// *Note*: Any proof using a bounded symbolic value is only valid up to that bound. + #[inline(always)] + pub fn bounded_any() -> T { + T::bounded_any::() + } + /// This function is only used for function contract instrumentation. /// It behaves exaclty like `kani::any()`, except it will check for the trait bounds /// at compilation time. It allows us to avoid type checking errors while using function @@ -595,6 +639,24 @@ macro_rules! kani_intrinsics { pub(crate) const fn check(cond: bool, msg: &'static str) { assert!(cond, "{}", msg); } + + #[inline(never)] + #[kanitool::fn_marker = "ForallHook"] + pub fn kani_forall(lower_bound: T, upper_bound: T, predicate: F) -> bool + where + F: Fn(T) -> bool, + { + predicate(lower_bound) + } + + #[inline(never)] + #[kanitool::fn_marker = "ExistsHook"] + pub fn kani_exists(lower_bound: T, upper_bound: T, predicate: F) -> bool + where + F: Fn(T) -> bool, + { + predicate(lower_bound) + } } }; } diff --git a/library/kani_macros/Cargo.toml b/library/kani_macros/Cargo.toml index 8a68cd8cc124..ccb88d4fb098 100644 --- a/library/kani_macros/Cargo.toml +++ b/library/kani_macros/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_macros" -version = "0.60.0" +version = "0.62.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani_macros/src/derive.rs b/library/kani_macros/src/derive.rs index f8eaf8354280..cfc3b1df8fc9 100644 --- a/library/kani_macros/src/derive.rs +++ b/library/kani_macros/src/derive.rs @@ -19,23 +19,23 @@ use syn::{ }; #[cfg(feature = "no_core")] -macro_rules! kani_path { - ($span:expr) => { - quote_spanned! { $span => core::kani } - }; - () => { - quote! { core::kani } - }; +pub(crate) fn kani_path() -> TokenStream { + quote! { core::kani } } #[cfg(not(feature = "no_core"))] -macro_rules! kani_path { - ($span:expr) => { - quote_spanned! { $span => kani } - }; - () => { - quote! { kani } - }; +pub(crate) fn kani_path() -> TokenStream { + quote! { kani } +} + +#[cfg(feature = "no_core")] +pub(crate) fn kani_path_spanned(span: Span) -> TokenStream { + quote_spanned! { span=> core::kani } +} + +#[cfg(not(feature = "no_core"))] +pub(crate) fn kani_path_spanned(span: Span) -> TokenStream { + quote_spanned! { span=> kani } } /// Generate the Arbitrary implementation for the given type. @@ -48,11 +48,11 @@ pub fn expand_derive_arbitrary(item: proc_macro::TokenStream) -> proc_macro::Tok let trait_name = "Arbitrary"; let derive_item = parse_macro_input!(item as DeriveInput); let item_name = &derive_item.ident; - let kani_path = kani_path!(); + let kani_path = kani_path(); - let body = fn_any_body(&item_name, &derive_item.data); + let body = fn_any_body(item_name, &derive_item.data); // Get the safety constraints (if any) to produce type-safe values - let safety_conds_opt = safety_conds_opt(&item_name, &derive_item, trait_name); + let safety_conds_opt = safety_conds_opt(item_name, &derive_item, trait_name); // Add a bound `T: Arbitrary` to every type parameter T. let generics = add_trait_bound_arbitrary(derive_item.generics); @@ -60,7 +60,7 @@ pub fn expand_derive_arbitrary(item: proc_macro::TokenStream) -> proc_macro::Tok let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let expanded = if let Some(safety_conds) = safety_conds_opt { - let field_refs = field_refs(&item_name, &derive_item.data); + let field_refs = field_refs(item_name, &derive_item.data); quote! { // The generated implementation. impl #impl_generics #kani_path::Arbitrary for #item_name #ty_generics #where_clause { @@ -87,7 +87,7 @@ pub fn expand_derive_arbitrary(item: proc_macro::TokenStream) -> proc_macro::Tok /// Add a bound `T: Arbitrary` to every type parameter T. fn add_trait_bound_arbitrary(mut generics: Generics) -> Generics { - let kani_path = kani_path!(); + let kani_path = kani_path(); generics.params.iter_mut().for_each(|param| { if let GenericParam::Type(type_param) = param { type_param.bounds.push(parse_quote!(#kani_path::Arbitrary)); @@ -251,7 +251,7 @@ fn init_symbolic_item(ident: &Ident, fields: &Fields) -> TokenStream { // Self(kani::any(), kani::any(), ..., kani::any()); let init = fields.unnamed.iter().map(|field| { let span = field.span(); - let kani_path = kani_path!(span); + let kani_path = kani_path_spanned(span); quote_spanned! {span=> #kani_path::any() } @@ -385,7 +385,7 @@ fn fn_any_enum(ident: &Ident, data: &DataEnum) -> TokenStream { } }); - let kani_path = kani_path!(); + let kani_path = kani_path(); quote! { match #kani_path::any() { #(#arms)* @@ -399,8 +399,8 @@ fn safe_body_with_calls( derive_input: &DeriveInput, trait_name: &str, ) -> TokenStream { - let safety_conds_opt = safety_conds_opt(&item_name, &derive_input, trait_name); - let safe_body_default = safe_body_default(&item_name, &derive_input.data); + let safety_conds_opt = safety_conds_opt(item_name, derive_input, trait_name); + let safe_body_default = safe_body_default(item_name, &derive_input.data); if let Some(safety_conds) = safety_conds_opt { quote! { #safe_body_default && #safety_conds } @@ -413,10 +413,10 @@ pub fn expand_derive_invariant(item: proc_macro::TokenStream) -> proc_macro::Tok let trait_name = "Invariant"; let derive_item = parse_macro_input!(item as DeriveInput); let item_name = &derive_item.ident; - let kani_path = kani_path!(); + let kani_path = kani_path(); - let safe_body = safe_body_with_calls(&item_name, &derive_item, trait_name); - let field_refs = field_refs(&item_name, &derive_item.data); + let safe_body = safe_body_with_calls(item_name, &derive_item, trait_name); + let field_refs = field_refs(item_name, &derive_item.data); // Add a bound `T: Invariant` to every type parameter T. let generics = add_trait_bound_invariant(derive_item.generics); @@ -446,9 +446,9 @@ fn safety_conds_opt( trait_name: &str, ) -> Option { let has_item_safety_constraint = - has_item_safety_constraint(&item_name, &derive_input, trait_name); + has_item_safety_constraint(item_name, derive_input, trait_name); - let has_field_safety_constraints = has_field_safety_constraints(&item_name, &derive_input.data); + let has_field_safety_constraints = has_field_safety_constraints(item_name, &derive_input.data); if has_item_safety_constraint && has_field_safety_constraints { abort!(Span::call_site(), "Cannot derive `{}` for `{}`", trait_name, item_name; @@ -458,9 +458,9 @@ fn safety_conds_opt( } if has_item_safety_constraint { - Some(safe_body_from_struct_attr(&item_name, &derive_input, trait_name)) + Some(safe_body_from_struct_attr(item_name, derive_input, trait_name)) } else if has_field_safety_constraints { - Some(safe_body_from_fields_attr(&item_name, &derive_input.data, trait_name)) + Some(safe_body_from_fields_attr(item_name, &derive_input.data, trait_name)) } else { None } @@ -501,7 +501,7 @@ fn has_field_safety_constraints_inner(_ident: &Ident, fields: &Fields) -> bool { /// Add a bound `T: Invariant` to every type parameter T. pub fn add_trait_bound_invariant(mut generics: Generics) -> Generics { - let kani_path = kani_path!(); + let kani_path = kani_path(); generics.params.iter_mut().for_each(|param| { if let GenericParam::Type(type_param) = param { type_param.bounds.push(parse_quote!(#kani_path::Invariant)); diff --git a/library/kani_macros/src/derive_bounded.rs b/library/kani_macros/src/derive_bounded.rs new file mode 100644 index 000000000000..b95624594453 --- /dev/null +++ b/library/kani_macros/src/derive_bounded.rs @@ -0,0 +1,155 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use proc_macro_error2::abort; +use proc_macro2::{Span, TokenStream}; +use quote::quote; + +use crate::derive::kani_path; + +/// Generate the `DeriveArbitrary` implementation for the given type. +/// +/// Fields of the given type marked with `#[bounded]` will use +/// `BoundedArbitrary::bounded_any()` while other fields fall back to `kani::any()` +/// +/// Current limitation: Generic bounds are restricted to `T: kani::Arbitrary` rather than +/// `T: kani::BoundedArbitrary`. This is the right thing to do when the generic is +/// used in some other container that you want to be bounded; like in the following +/// example: +/// +/// ```rust +/// #[derive(BoundedArbitrary)] +/// struct MyVec { +/// #[bounded] +/// vec: Vec, +/// cap: usize +/// } +/// ``` +/// +/// However, if you use the generic raw in a field and want it to be bounded, this +/// won't work. The following doesn't compile: +/// +/// ```rust +/// #[derive(BoundedArbitrary)] +/// struct Foo { +/// #[bounded] +/// bar: T +/// } +/// ``` +/// +/// TODO: have the generic bound change based on how it's used if we can detect this +/// automatically, otherwise support an attribute on the generic. +pub(crate) fn expand_derive_bounded_arbitrary( + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let parsed = syn::parse_macro_input!(item as syn::DeriveInput); + + let constructor = match &parsed.data { + syn::Data::Struct(data_struct) => { + generate_type_constructor(quote!(Self), &data_struct.fields) + } + syn::Data::Enum(data_enum) => enum_constructor(&parsed.ident, data_enum), + syn::Data::Union(data_union) => union_constructor(&parsed.ident, data_union), + }; + + // add `T: Arbitrary` bounds for generics + let (generics, clauses) = quote_generics(&parsed.generics); + let name = &parsed.ident; + + // generate the implementation + let kani_path = kani_path(); + quote! { + impl #generics #kani_path::BoundedArbitrary for #name #generics + #clauses + { + fn bounded_any() -> Self { + #constructor + } + } + } + .into() +} + +/// Generates the call to construct the given type like so: +/// +/// ``` +/// Foo(kani::any::(), String::bounded_any::()) +/// Foo { +/// x: kani::any::(), +/// y: kani::bounded_any::() +/// } +/// ``` +fn generate_type_constructor(type_name: TokenStream, fields: &syn::Fields) -> TokenStream { + let field_calls = fields.iter().map(generate_any_call); + if fields.iter().all(|f| f.ident.is_some()) { + quote!(#type_name { #(#field_calls),* }) + } else { + quote!(#type_name( #(#field_calls),* )) + } +} + +/// Generates a `match` case to construct each variant of the given type. Uses a +/// symbolic `usize` to decide which variant to construct. +fn enum_constructor(ident: &syn::Ident, data_enum: &syn::DataEnum) -> TokenStream { + let variant_constructors = data_enum.variants.iter().map(|variant| { + let variant_name = &variant.ident; + generate_type_constructor(quote!(#ident::#variant_name), &variant.fields) + }); + let n_variants = data_enum.variants.len(); + let cases = variant_constructors.enumerate().map(|(idx, var_constr)| { + if idx < n_variants - 1 { quote!(#idx => #var_constr) } else { quote!(_ => #var_constr) } + }); + + let kani_path = kani_path(); + quote! { + match #kani_path::any() { + #(#cases),* , + } + } +} + +fn union_constructor(ident: &syn::Ident, _data_union: &syn::DataUnion) -> TokenStream { + abort!(Span::call_site(), "Cannot derive `BoundedArbitrary` for `{}` union", ident; + note = ident.span() => + "`#[derive(BoundedArbitrary)]` cannot be used for unions such as `{}`", ident + ) +} + +/// Generate the necessary generic parameter declarations and generic bounds for a +/// type. +/// +/// ```rust +/// impl BoundedArbitrary for Foo +/// where +/// A: Arbitrary +/// B: Arbitrary +/// { +/// ... +/// } +/// ``` +fn quote_generics(generics: &syn::Generics) -> (TokenStream, TokenStream) { + let kani_path = kani_path(); + let params = generics.type_params().map(|param| quote!(#param)).collect::>(); + let where_clauses = generics.type_params().map(|param| quote!(#param : #kani_path::Arbitrary)); + if !params.is_empty() { + (quote!(<#(#params),*>), quote!(where #(#where_clauses),*)) + } else { + Default::default() + } +} + +/// Generates a symbolic value based on whether the field has the `#[bounded]` +/// attribute. If the field is not bounded, generate `kani::any()` otherwise generate +/// `kani::bounded_any()`. +fn generate_any_call(field: &syn::Field) -> TokenStream { + let ty = &field.ty; + let kani_path = kani_path(); + let any_call = if field.attrs.iter().any(|attr| attr.path().is_ident("bounded")) { + quote!(#kani_path::bounded_any::<#ty, N>()) + } else { + quote!(#kani_path::any::<#ty>()) + }; + + let ident_tok = field.ident.as_ref().map(|ident| quote!(#ident: )); + quote!(#ident_tok #any_call) +} diff --git a/library/kani_macros/src/lib.rs b/library/kani_macros/src/lib.rs index f571e4592c4c..7693eb2c38f9 100644 --- a/library/kani_macros/src/lib.rs +++ b/library/kani_macros/src/lib.rs @@ -10,6 +10,7 @@ #![feature(proc_macro_diagnostic)] #![feature(proc_macro_span)] mod derive; +mod derive_bounded; // proc_macro::quote is nightly-only, so we'll cobble things together instead use proc_macro::TokenStream; @@ -207,6 +208,38 @@ pub fn derive_arbitrary(item: TokenStream) -> TokenStream { derive::expand_derive_arbitrary(item) } +/// Allow users to generate `BoundedArbitrary` implementations by using the +/// `#[derive(BoundedArbitrary)]` macro. +/// +/// ## Specifying bounded fields +/// +/// By default, every field of a struct is required to derive `Arbitrary`, not +/// `BoundedArbitrary`. This is because there is no general way to determine if a +/// particular field should be bounded or not. You must annotate the fields you want +/// to be bounded using the `#[bounded]` attribute. +/// +/// For example, we only want the vector field in the following definition to be +/// bounded. +/// +/// ```rust +/// #[derive(BoundedArbitrary)] +/// struct MyVector { +/// #[bounded] +/// vector: Vec, +/// capacity: usize +/// } +/// ``` +/// +/// ## Safety +/// +/// Using `BoundedArbitrary` makes proofs incomplete. It is useful for increasing +/// confidence that some code is correct, but does not prove that absolutely. +#[proc_macro_error] +#[proc_macro_derive(BoundedArbitrary, attributes(bounded))] +pub fn derive_bounded_arbitrary(item: TokenStream) -> TokenStream { + derive_bounded::expand_derive_bounded_arbitrary(item) +} + /// Allow users to auto generate `Invariant` implementations by using /// `#[derive(Invariant)]` macro. /// diff --git a/library/kani_macros/src/sysroot/contracts/assert.rs b/library/kani_macros/src/sysroot/contracts/assert.rs index 777fa6722587..d954c10b6425 100644 --- a/library/kani_macros/src/sysroot/contracts/assert.rs +++ b/library/kani_macros/src/sysroot/contracts/assert.rs @@ -44,7 +44,7 @@ impl<'a> ContractConditionsHandler<'a> { fn initial_assert_stmts(&self) -> Vec { let body_wrapper_ident = Ident::new("body_wrapper", Span::call_site()); let output = &self.annotated_fn.sig.output; - let return_type = return_type_to_type(&output); + let return_type = return_type_to_type(output); let stmts = &self.annotated_fn.block.stmts; let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site()); diff --git a/library/kani_macros/src/sysroot/contracts/check.rs b/library/kani_macros/src/sysroot/contracts/check.rs index 6a473a2060d2..a1a174360895 100644 --- a/library/kani_macros/src/sysroot/contracts/check.rs +++ b/library/kani_macros/src/sysroot/contracts/check.rs @@ -123,9 +123,10 @@ impl<'a> ContractConditionsHandler<'a> { /// First find the modifies body and expand that. Then expand the rest of the body. pub fn expand_check(&self, closure: &mut Stmt) { let body = closure_body(closure); - self.expand_modifies(find_contract_closure(&mut body.block.stmts, "wrapper").expect( - &format!("Internal Failure: Expected to find `wrapper` closure, but found none"), - )); + self.expand_modifies( + find_contract_closure(&mut body.block.stmts, "wrapper") + .expect("Internal Failure: Expected to find `wrapper` closure, but found none"), + ); *body = syn::parse2(self.make_check_body(mem::take(&mut body.block.stmts))).unwrap(); } diff --git a/library/kani_macros/src/sysroot/contracts/helpers.rs b/library/kani_macros/src/sysroot/contracts/helpers.rs index becb07d43033..c79a5520ceb3 100644 --- a/library/kani_macros/src/sysroot/contracts/helpers.rs +++ b/library/kani_macros/src/sysroot/contracts/helpers.rs @@ -94,8 +94,9 @@ pub fn find_contract_closure<'a>( /// /// Panic if no closure was found. pub fn expect_closure<'a>(stmts: &'a mut [Stmt], name: &'static str) -> &'a mut Stmt { - find_contract_closure(stmts, name) - .expect(&format!("Internal Failure: Expected to find `{name}` closure, but found none")) + find_contract_closure(stmts, name).unwrap_or_else(|| { + panic!("Internal Failure: Expected to find `{name}` closure, but found none") + }) } /// Find a closure inside a match block. @@ -112,7 +113,9 @@ pub fn expect_closure_in_match<'a>(stmts: &'a mut [Stmt], name: &'static str) -> None } }); - closure.expect(&format!("Internal Failure: Expected to find `{name}` closure, but found none")) + closure.unwrap_or_else(|| { + panic!("Internal Failure: Expected to find `{name}` closure, but found none") + }) } /// Extract the body of a closure declaration. diff --git a/library/kani_macros/src/sysroot/contracts/mod.rs b/library/kani_macros/src/sysroot/contracts/mod.rs index 5c70cc0cc581..cc792614e2d5 100644 --- a/library/kani_macros/src/sysroot/contracts/mod.rs +++ b/library/kani_macros/src/sysroot/contracts/mod.rs @@ -37,7 +37,7 @@ //! contracts need to be analyzed. It will do the following: //! 1. Annotate the function with extra `kanitool` attributes //! 2. Generate closures for each contract processing scenario (recursive check, simple check, -//! replacement, and regular execution). +//! replacement, and regular execution). //! //! Subsequent expansions will detect the existence of the extra `kanitool` attributes, //! and they will only expand the body of the closures generated in the initial phase. diff --git a/library/kani_macros/src/sysroot/contracts/replace.rs b/library/kani_macros/src/sysroot/contracts/replace.rs index bb2ba01406c0..5a450b3ca9d4 100644 --- a/library/kani_macros/src/sysroot/contracts/replace.rs +++ b/library/kani_macros/src/sysroot/contracts/replace.rs @@ -24,7 +24,7 @@ impl<'a> ContractConditionsHandler<'a> { // https://github.com/model-checking/kani/issues/3667 let redefs = self.arg_redefinitions(false); let redefs_block: Block = syn::parse_quote!({#redefs}); - vec![ + [ vec![syn::parse_quote!( let #result : #return_type = kani::any_modifies(); )], @@ -128,7 +128,7 @@ impl<'a> ContractConditionsHandler<'a> { let sig = &self.annotated_fn.sig; let output = &sig.output; let before = self.initial_replace_stmts(); - let body = self.expand_replace_body(&before, &vec![]); + let body = self.expand_replace_body(&before, &[]); quote!( #[kanitool::is_contract_generated(replace)] diff --git a/library/kani_macros/src/sysroot/contracts/shared.rs b/library/kani_macros/src/sysroot/contracts/shared.rs index 833303307af8..816a3623f6b2 100644 --- a/library/kani_macros/src/sysroot/contracts/shared.rs +++ b/library/kani_macros/src/sysroot/contracts/shared.rs @@ -119,8 +119,8 @@ pub fn try_as_result_assign(stmt: &syn::Stmt) -> Option<&syn::LocalInit> { pub fn build_ensures(data: &ExprClosure) -> (TokenStream2, Expr) { let mut remembers_exprs = HashMap::new(); let mut vis = OldVisitor { t: OldLifter::new(), remembers_exprs: &mut remembers_exprs }; - let mut expr = &mut data.clone(); - vis.visit_expr_closure_mut(&mut expr); + let expr = &mut data.clone(); + vis.visit_expr_closure_mut(expr); let remembers_stmts: TokenStream2 = remembers_exprs .iter() @@ -174,7 +174,7 @@ impl syn::visit_mut::VisitMut for OldVisitor<'_, T> { qself: None, path: Path { leading_colon: None, segments }, }) if segments.len() == 1 - && segments.first().map_or(false, |sgm| sgm.ident == "old") => + && segments.first().is_some_and(|sgm| sgm.ident == "old") => { let first_segment = segments.first().unwrap(); assert_spanned_err!(first_segment.arguments.is_empty(), first_segment); diff --git a/library/kani_macros/src/sysroot/loop_contracts/mod.rs b/library/kani_macros/src/sysroot/loop_contracts/mod.rs index 01166af3d988..d1dbfd06aaa8 100644 --- a/library/kani_macros/src/sysroot/loop_contracts/mod.rs +++ b/library/kani_macros/src/sysroot/loop_contracts/mod.rs @@ -9,27 +9,221 @@ use proc_macro_error2::abort_call_site; use quote::{format_ident, quote}; use syn::spanned::Spanned; use syn::token::AndAnd; -use syn::{BinOp, Expr, ExprBinary, Stmt}; - -/// Expand loop contracts macros. -/// -/// A while loop of the form -/// ``` rust -/// while guard { -/// body -/// } -/// ``` -/// will be annotated as -/// ``` rust -/// #[inline(never)] -/// #[kanitool::fn_marker = "kani_register_loop_contract"] -/// const fn kani_register_loop_contract_id T>(f: F) -> T { -/// unreachable!() -/// } -/// while kani_register_loop_contract_id(|| -> bool {inv};) && guard { -/// body -/// } -/// ``` +use syn::{BinOp, Block, Expr, ExprBinary, Ident, Stmt, parse_quote, visit_mut::VisitMut}; + +/* + Transform the loop to support on_entry(expr) : the value of expr before entering the loop + 1. For each on_entry(expr) in the loop variant, replace it with a newly generated "memory" variable old_k + 2. Add the declaration of i before the loop: let old_k = expr + For example: + #[kani::loop_invariant(on_entry(x+y) = x + y -1)] + while(....) + + is transformed into + let old_1 = x + y + #[kani::loop_invariant(old_1 = x + y -1)] + while(....) + + Then the loop_invartiant is transformed. + + Transform the loop to support prev(expr) : the value of expr at the end of the previous iteration + Semantic: If the loop has at least 1 iteration: prev(expr) is the value of expr at the end of the previous iteration. Otherwise, just remove the loop (without check for the invariant too). + + Transformation: basically, if the loop has at least 1 iteration (loop_quard is satisfied at the beginning), we unfold the loop once, declare the variables for prev values and update them at the beginning of the loop body. + Otherwise, we remove the loop. + If there is a prev(expr) in the loop_invariant: + 1. Firstly, add an if block whose condition is the loop_quard, inside its body add/do the followings: + 2. For each prev(expr) in the loop variant, replace it with a newly generated "memory" variable prev_k + 3. Add the declaration of prev_k before the loop: let mut prev_k = expr + 4. Define a mut closure whose body is exactly the loop body, but replace all continue/break statements with return true/false statements, + then add a final return true statement at the end of it + 5. Add an if statement with condition to be the that closure's call (the same as run the loop once): + True block: add the loop with expanded macros (see next section) and inside the loop body: + add the assignment statements (exactly the same as the declarations without the "let mut") on the top to update the "memory" variables + Else block: Add the assertion for the loop_invariant (not includes the loop_quard): check if the loop_invariant holds after the first iteration. + + For example: + #[kani::loop_invariant(prev(x+y) = x + y -1 && ...)] + while(loop_guard) + { + loop_body + } + + is transformed into + + assert!(loop_guard); + let mut prev_1 = x + y; + let mut loop_body_closure = || { + loop_body_replaced //replace breaks/continues in loop_body with returns + }; + if loop_body_closure(){ + #[kani::loop_invariant(prev_1 = x + y -1)] + while(loop_guard) + { + prev_1 = x + y; + loop_body + } + } + else{ + assert!(prev_1 = x + y -1 && ...); + } + + Finally, expand the loop contract macro. + + A while loop of the form + ``` rust + while guard { + body + } + ``` + will be annotated as + ``` rust + #[inline(never)] + #[kanitool::fn_marker = "kani_register_loop_contract"] + const fn kani_register_loop_contract_id T>(f: F) -> T { + unreachable!() + } + while kani_register_loop_contract_id(|| -> bool {inv};) && guard { + body + } + ``` +*/ + +struct TransformationResult { + transformed_expr: Expr, + declarations_block: Block, + assignments_block: Block, +} + +struct CallReplacer { + old_name: String, + replacements: Vec<(Expr, proc_macro2::Ident)>, + counter: usize, + var_prefix: String, +} + +// This impl replaces any function call of a function name : old_name with a newly generated variable. +impl CallReplacer { + fn new(old_name: &str, var_prefix: String) -> Self { + Self { old_name: old_name.to_string(), replacements: Vec::new(), counter: 0, var_prefix } + } + + fn generate_var_name(&mut self) -> proc_macro2::Ident { + let var_name = format_ident!("{}_{}", self.var_prefix, self.counter); + self.counter += 1; + var_name + } + + //Check if the function name is old_name + fn should_replace(&self, expr_path: &syn::ExprPath) -> bool { + let full_path = expr_path + .path + .segments + .iter() + .map(|seg| seg.ident.to_string()) + .collect::>() + .join("::"); + + full_path == self.old_name + } +} + +impl VisitMut for CallReplacer { + fn visit_expr_mut(&mut self, expr: &mut Expr) { + // Visit nested expressions first + syn::visit_mut::visit_expr_mut(self, expr); + + if let Expr::Call(call) = expr { + if let Expr::Path(expr_path) = &*call.func { + if self.should_replace(expr_path) { + let new_var = self.generate_var_name(); + self.replacements.push((expr.clone(), new_var.clone())); + *expr = syn::parse_quote!(#new_var); + } + } + } + } +} + +// The main function to replace the function call with the variables +fn transform_function_calls( + expr: Expr, + function_name: &str, + var_prefix: String, +) -> TransformationResult { + let mut replacer = CallReplacer::new(function_name, var_prefix); + let mut transformed_expr = expr; + replacer.visit_expr_mut(&mut transformed_expr); + + let mut newreplace: Vec<(Expr, Ident)> = Vec::new(); + for (call, var) in replacer.replacements { + if let Expr::Call(call_expr) = call { + let insideexpr = call_expr.args[0].clone(); + newreplace.push((insideexpr, var.clone())); + } + } + + // Generate declarations block of the newly generated variables (will added before the loop) + let declarations: Vec = newreplace + .iter() + .map(|(call, var)| syn::parse_quote!(let mut #var = #call.clone();)) + .collect(); + let declarations_block: Block = syn::parse_quote!({ + #(#declarations)* + }); + + // Generate declarations block of the newly generated variables (will be added on the loop of the loop body) + let assignments: Vec = newreplace + .into_iter() + .map(|(call, var)| syn::parse_quote!(#var = #call.clone();)) + .collect(); + let assignments_block: Block = syn::parse_quote!({ + #(#assignments)* + }); + + TransformationResult { transformed_expr, declarations_block, assignments_block } +} + +struct BreakContinueReplacer; + +impl VisitMut for BreakContinueReplacer { + fn visit_expr_mut(&mut self, expr: &mut Expr) { + // Visit nested expressions first + syn::visit_mut::visit_expr_mut(self, expr); + + // Replace the expression + *expr = match expr { + Expr::Break(_) => { + syn::parse_quote!(return (false, None)) + } + Expr::Continue(_) => { + syn::parse_quote!(return (true, None)) + } + Expr::Return(rexpr) => match rexpr.expr.clone() { + Some(ret) => syn::parse_quote!(return (false, Some(#ret))), + _ => syn::parse_quote!(return (false, Some(()))), + }, + _ => return, + }; + } +} + +// This function replace the break/continue statements inside a loop body with return statements +fn transform_break_continue(block: &mut Block) { + let mut replacer = BreakContinueReplacer; + replacer.visit_block_mut(block); + let return_stmt: Stmt = syn::parse_quote! { + return (true, None); + }; + // Add semicolon to the last statement if it's an expression without semicolon + if let Some(Stmt::Expr(_, ref mut semi)) = block.stmts.last_mut() { + if semi.is_none() { + *semi = Some(Default::default()); + } + } + block.stmts.push(return_stmt); +} + pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream { // parse the stmt of the loop let mut loop_stmt: Stmt = syn::parse(item.clone()).unwrap(); @@ -41,7 +235,55 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream { inv_name.push_str(&loop_id); // expr of the loop invariant - let inv_expr: Expr = syn::parse(attr).unwrap(); + let mut inv_expr: Expr = syn::parse(attr).unwrap(); + + // adding on_entry variables + let mut onentry_var_prefix: String = "__kani_onentry_var".to_owned(); + onentry_var_prefix.push_str(&loop_id); + let replace_onentry: TransformationResult = + transform_function_calls(inv_expr.clone(), "on_entry", onentry_var_prefix); + inv_expr = replace_onentry.transformed_expr.clone(); + let onentry_decl_stms = replace_onentry.declarations_block.stmts.clone(); + + // adding prev variables + let mut prev_var_prefix: String = "__kani_prev_var".to_owned(); + prev_var_prefix.push_str(&loop_id); + let transform_inv: TransformationResult = + transform_function_calls(inv_expr.clone(), "prev", prev_var_prefix); + let has_prev = !transform_inv.declarations_block.stmts.is_empty(); + let prev_decl_stms = transform_inv.declarations_block.stmts.clone(); + let mut assign_stms = transform_inv.assignments_block.stmts.clone(); + let (mut loop_body, loop_guard) = match loop_stmt { + Stmt::Expr(ref mut e, _) => match e { + Expr::While(ew) => (ew.body.clone(), ew.cond.clone()), + Expr::Loop(el) => (el.body.clone(), parse_quote!(true)), + _ => panic!(), + }, + _ => panic!(), + }; + let loop_body_stms = loop_body.stmts.clone(); + assign_stms.extend(loop_body_stms); + transform_break_continue(&mut loop_body); + let mut loop_body_closure_name: String = "__kani_loop_body_closure".to_owned(); + loop_body_closure_name.push_str(&loop_id); + let loop_body_closure = format_ident!("{}", loop_body_closure_name); + let mut loop_body_closure_ret_1_name: String = "__kani_loop_body_closure_ret_1".to_owned(); + loop_body_closure_ret_1_name.push_str(&loop_id); + let loop_body_closure_ret_1 = format_ident!("{}", loop_body_closure_ret_1_name); + let mut loop_body_closure_ret_2_name: String = "__kani_loop_body_closure_ret_2".to_owned(); + loop_body_closure_ret_2_name.push_str(&loop_id); + let loop_body_closure_ret_2 = format_ident!("{}", loop_body_closure_ret_2_name); + if has_prev { + inv_expr = transform_inv.transformed_expr.clone(); + match loop_stmt { + Stmt::Expr(ref mut e, _) => match e { + Expr::While(ew) => ew.body.stmts = assign_stms.clone(), + Expr::Loop(el) => el.body.stmts = assign_stms.clone(), + _ => panic!(), + }, + _ => panic!(), + }; + } // ident of the register function let mut register_name: String = "kani_register_loop_contract".to_owned(); @@ -64,6 +306,14 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream { right: ew.cond.clone(), }); } + Expr::Loop(ref mut el) => { + //let retexpr = get_return_statement(&el.body); + let invstmt: Stmt = syn::parse(quote!(if !(#register_ident(&||->bool{#inv_expr}, 0)) {assert!(false); unreachable!()};).into()).unwrap(); + let mut new_stmts: Vec = Vec::new(); + new_stmts.push(invstmt); + new_stmts.extend(el.body.stmts.clone()); + el.body.stmts = new_stmts.clone(); + } _ => { abort_call_site!("`#[kani::loop_invariant]` is now only supported for while-loops."; note = "for now, loop contracts is only supported for while-loops."; @@ -74,8 +324,40 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream { note = "for now, loop contracts is only supported for while-loops."; ), } - quote!( + + if has_prev { + quote!( { + if (#loop_guard) { + #(#onentry_decl_stms)* + #(#prev_decl_stms)* + let mut #loop_body_closure = || + #loop_body; + let (#loop_body_closure_ret_1, #loop_body_closure_ret_2) = #loop_body_closure (); + if #loop_body_closure_ret_2.is_some() { + return #loop_body_closure_ret_2.unwrap(); + } + if #loop_body_closure_ret_1 { + // Dummy function used to force the compiler to capture the environment. + // We cannot call closures inside constant functions. + // This function gets replaced by `kani::internal::call_closure`. + #[inline(never)] + #[kanitool::fn_marker = "kani_register_loop_contract"] + const fn #register_ident bool>(_f: &F, _transformed: usize) -> bool { + true + } + #loop_stmt + } + else { + assert!(#inv_expr); + }; + } + }) + .into() + } else { + quote!( + { + #(#onentry_decl_stms)* // Dummy function used to force the compiler to capture the environment. // We cannot call closures inside constant functions. // This function gets replaced by `kani::internal::call_closure`. @@ -84,8 +366,10 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream { const fn #register_ident bool>(_f: &F, _transformed: usize) -> bool { true } - #loop_stmt}) - .into() + #loop_stmt + }) + .into() + } } fn generate_unique_id_from_span(stmt: &Stmt) -> String { diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index b4ca6fee7822..724a4abca104 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -5,7 +5,7 @@ # Note: this package is intentionally named std to make sure the names of # standard library symbols are preserved name = "std" -version = "0.60.0" +version = "0.62.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/rfc/src/intro.md b/rfc/src/intro.md index c10b1486dfba..8278dd682edd 100644 --- a/rfc/src/intro.md +++ b/rfc/src/intro.md @@ -61,8 +61,10 @@ This is the overall workflow for the RFC process: 4. Add regression tests to cover all expected behaviors and unit tests whenever possible. 5. Stabilization. 1. Propose to stabilize the feature when feature is well tested and UX has received positive feedback. - 2. Create a new PR that removes the `-Z ` guard and that marks the RFC status as "STABLE". + 2. Create a new PR that makes the option a no-op with a deprecation warning. + 3. *Only after the PR from #2 is included in a release*, create another PR that actually removes the option and marks the RFC status as "STABLE". 1. Make sure the RFC reflects the final implementation and user experience. + 2. See [#3561](https://github.com/model-checking/kani/issues/3561) for an example of such a two-phase deletion, where we first deprecate the option in one release, then remove it in the next. See also [0006-unstable-api](rfcs/0006-unstable-api.md). 3. In some cases, we might decide not to incorporate a feature (E.g.: performance degradation, bad user experience, better alternative). In those cases, please update the RFC status to "CANCELLED as per " and remove the code diff --git a/rfc/src/rfcs/0010-quantifiers.md b/rfc/src/rfcs/0010-quantifiers.md index 07a5f7548974..9127834a4887 100644 --- a/rfc/src/rfcs/0010-quantifiers.md +++ b/rfc/src/rfcs/0010-quantifiers.md @@ -1,7 +1,7 @@ - **Feature Name:** Quantifiers - **Feature Request Issue:** [#2546](https://github.com/model-checking/kani/issues/2546) and [#836](https://github.com/model-checking/kani/issues/836) - **RFC PR:** [#](https://github.com/model-checking/kani/pull/) -- **Status:** Unstable +- **Status:** Under Review - **Version:** 1.0 ------------------- @@ -20,15 +20,15 @@ There are two primary quantifiers: the existential quantifier (∃) and the univ Rather than exhaustively listing all elements in a domain, quantifiers enable users to make statements about the entire domain at once. This compact representation is crucial when dealing with large or unbounded inputs. Quantifiers also facilitate abstraction and generalization of properties. Instead of specifying properties for specific instances, quantified properties can capture general patterns and behaviors that hold across different objects in a domain. Additionally, by replacing loops in the specification with quantifiers, Kani can encode the properties more efficiently within the specified bounds, making the verification process more manageable and computationally feasible. -This new feature doesn't introduce any breaking changes to users. It will only allow them to write properites using the existential (∃) and universal (∀) quantifiers. +This new feature doesn't introduce any breaking changes to users. It will only allow them to write properties using the existential (∃) and universal (∀) quantifiers. ## User Experience -We propose a syntax inspired by ["Pattern Types"](https://github.com/rust-lang/rust/pull/120131). The syntax of existential (i.e., `kani::exists`) and universal (i.e., `kani::forall`) quantifiers are: +The syntax of existential (i.e., `kani::exists`) and universal (i.e., `kani::forall`) quantifiers are: ```rust -kani::exists(|: [is ] | ) -kani::forall(|: [is ] | ) +kani::exists(|: [in ()] | ) +kani::forall(|: [in ()] | ) ``` If `` is not provided, we assume `` can range over all possible values of the given `` (i.e., syntactic sugar for full range `|: as .. |`). CBMC's SAT backend only supports bounded quantification under **constant** lower and upper bounds (for more details, see the [documentation for quantifiers in CBMC](https://diffblue.github.io/cbmc/contracts-quantifiers.html)). The SMT backend, on the other hand, supports arbitrary Boolean expressions. In any case, `` should not have side effects, as the purpose of quantifiers is to assert a condition over a domain of objects without altering the state. @@ -36,7 +36,6 @@ If `` is not provided, we assume `` can range over all possible Consider the following example adapted from the documentation for the [from_raw_parts](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.from_raw_parts) function: ```rust -use std::ptr; use std::mem; #[kani::proof] @@ -67,7 +66,6 @@ fn main() { Given the `v` vector has non-deterministic values, there are potential arithmetic overflows that might happen in the for loop. So we need to constrain all values of the array. We may also want to check all values of `rebuilt` after the operation. Without quantifiers, we might be tempted to use loops as follows: ```rust -use std::ptr; use std::mem; #[kani::proof] @@ -102,17 +100,23 @@ fn main() { } ``` -This, however, might unnecessary increase the complexity of the verication process. We can achieve the same effect using quantifiers as shown below. +This, however, might unnecessary increase the complexity of the verification process. We can achieve the same effect using quantifiers as shown below. ```rust -use std::ptr; use std::mem; #[kani::proof] fn main() { - let original_v = vec![kani::any::(); 3]; + let original_v = vec![kani::any::(); 3]; let v = original_v.clone(); - kani::assume(kani::forall(|i: usize is ..v.len() | v[i] < 5)); + let v_len = v.len(); + let v_ptr = v.as_ptr(); + let original_v_ptr = original_v.as_ptr(); + unsafe { + kani::assume( + kani::forall!(|i in (0,v_len) | *v_ptr.wrapping_byte_offset(4*i as isize) < 5), + ); + } // Prevent running `v`'s destructor so we are in complete control // of the allocation. @@ -127,11 +131,17 @@ fn main() { // Overwrite memory for i in 0..len { *p.add(i) += 1; + if i == 1 { + *p.add(i) = 0; + } } // Put everything back together into a Vec let rebuilt = Vec::from_raw_parts(p, len, cap); - assert!(kani::forall(|i: usize is ..len | rebuilt[i] == original_v[i]+1)); + let rebuilt_ptr = v.as_ptr(); + assert!( + kani::exists!(| i in (0, len) | *rebuilt_ptr.wrapping_byte_offset(4*i as isize) == original_v_ptr.wrapping_byte_offset(4*i as isize) + 1) + ); } } ``` @@ -139,14 +149,19 @@ fn main() { The same principle applies if we want to use the existential quantifier. ```rust -use std::ptr; use std::mem; #[kani::proof] fn main() { - let original_v = vec![kani::any::(); 3]; + let original_v = vec![kani::any::(); 3]; let v = original_v.clone(); - kani::assume(kani::forall(|i: usize is ..v.len() | v[i] < 5)); + let v_len = v.len(); + let v_ptr = v.as_ptr(); + unsafe { + kani::assume( + kani::forall!(|i in (0,v_len) | *v_ptr.wrapping_byte_offset(4*i as isize) < 5), + ); + } // Prevent running `v`'s destructor so we are in complete control // of the allocation. @@ -162,13 +177,16 @@ fn main() { for i in 0..len { *p.add(i) += 1; if i == 1 { - *p.add(i) = 0; + *p.add(i) = 0; } } // Put everything back together into a Vec let rebuilt = Vec::from_raw_parts(p, len, cap); - assert!(kani::exists(|i: usize is ..len | rebuilt[i] == 0)); + let rebuilt_ptr = v.as_ptr(); + assert!( + kani::exists!(| i in (0, len) | *rebuilt_ptr.wrapping_byte_offset(4*i as isize) == 0) + ); } } ``` @@ -181,6 +199,17 @@ The usage of quantifiers should be valid in any part of the Rust code analysed b Kani should have the same support that CBMC has for quantifiers. For more details, see [Quantifiers](https://github.com/diffblue/cbmc/blob/0a69a64e4481473d62496f9975730d24f194884a/doc/cprover-manual/contracts-quantifiers.md). +The implementation of quantifiers in Kani will be based on the following principle: + +- **Single expression without function calls**: CBMC's quantifiers only support + single expressions without function calls. This means that the CBMC expressions generated + from the quantifiers in Kani should be limited to a single expression without any + function calls. + +To achieve this, we will need to implement the function inlining pass in Kani. This + pass will inline all function calls in quantifiers before they are codegened to CBMC + expressions. This will ensure that the generated expressions are compliant with CBMC's + quantifier support. ## Open questions @@ -192,8 +221,7 @@ Kani should have the same support that CBMC has for quantifiers. For more detail interface is familiar to developers, but the code generation is tricky, as CBMC level quantifiers only allow certain kinds of expressions. This necessitates a rewrite of the `Fn` closure to a compliant expression. - - Which kind of expressions should be accepted as a "compliant expression"? - + - Which kind of expressions should be accepted as a "compliant expression"? ## Future possibilities diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e4b1be5194bd..80e86e534993 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [toolchain] -channel = "1.87.0" +channel = "1.88.0" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] diff --git a/scripts/ci/Dockerfile.bundle-release-20-04 b/scripts/ci/Dockerfile.bundle-release-24-04 similarity index 93% rename from scripts/ci/Dockerfile.bundle-release-20-04 rename to scripts/ci/Dockerfile.bundle-release-24-04 index 963aa952d6df..7231da5afb76 100644 --- a/scripts/ci/Dockerfile.bundle-release-20-04 +++ b/scripts/ci/Dockerfile.bundle-release-24-04 @@ -1,9 +1,9 @@ # Copyright Kani Contributors # SPDX-License-Identifier: Apache-2.0 OR MIT -# Docker image for Kani GitHub Package release ubuntu-20-04. +# Docker image for Kani GitHub Package release ubuntu-24-04. -FROM ubuntu:20.04 +FROM ubuntu:24.04 ENV DEBIAN_FRONTEND=noninteractive \ DEBCONF_NONINTERACTIVE_SEEN=true \ PATH="/root/.cargo/bin:${PATH}" diff --git a/scripts/kani-fmt.sh b/scripts/kani-fmt.sh index 6a136f244ff8..d4ddfaf19b58 100755 --- a/scripts/kani-fmt.sh +++ b/scripts/kani-fmt.sh @@ -12,11 +12,20 @@ set -o nounset ROOT_FOLDER=$(git rev-parse --show-toplevel) cd ${ROOT_FOLDER} +# Parse arguments to check for --check flag +check_flag="" +for arg in "$@"; do + if [ "$arg" = "--check" ]; then + check_flag="--check" + break + fi +done + # Verify crates. error=0 # Check all crates. Only fail at the end. -cargo fmt "$@" || error=1 +cargo fmt ${check_flag} || error=1 # Check test source files. TESTS=("tests" "docs/src/tutorial") @@ -37,7 +46,7 @@ for suite in "${TESTS[@]}"; do set +f; unset IFS # Note: We set the configuration file here because some submodules have # their own configuration file. - rustfmt --config-path rustfmt.toml "${files[@]}" || error=1 + rustfmt --config-path rustfmt.toml ${check_flag} "${files[@]}" || error=1 done exit $error diff --git a/scripts/kani-llbc-regression.sh b/scripts/kani-llbc-regression.sh new file mode 100755 index 000000000000..9d07814e4ab3 --- /dev/null +++ b/scripts/kani-llbc-regression.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +if [[ -z $KANI_REGRESSION_KEEP_GOING ]]; then + set -o errexit +fi +set -o pipefail +set -o nounset + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +export PATH=$SCRIPT_DIR:$PATH + +# Formatting check +${SCRIPT_DIR}/kani-fmt.sh --check + +# Build kani +cargo build-dev -- --features cprover --features llbc + +# Build compiletest and print configuration. We pick suite / mode combo so there's no test. +echo "--- Compiletest configuration" +cargo run -p compiletest --quiet -- --suite kani --mode cargo-kani --dry-run --verbose +echo "-----------------------------" + +suite="llbc" +mode="expected" +echo "Check compiletest suite=$suite mode=$mode" +cargo run -p compiletest --quiet -- --suite $suite --mode $mode \ + --quiet --no-fail-fast + +echo +echo "All Kani llbc regression tests completed successfully." +echo diff --git a/scripts/setup/ubuntu-20.04 b/scripts/setup/ubuntu-20.04 deleted file mode 120000 index 7d13753d73e8..000000000000 --- a/scripts/setup/ubuntu-20.04 +++ /dev/null @@ -1 +0,0 @@ -ubuntu \ No newline at end of file diff --git a/scripts/setup/ubuntu-20.04/install_cbmc.sh b/scripts/setup/ubuntu-20.04/install_cbmc.sh new file mode 100755 index 000000000000..f9744f51f61f --- /dev/null +++ b/scripts/setup/ubuntu-20.04/install_cbmc.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +set -eu + +# Source kani-dependencies to get CBMC_VERSION +source kani-dependencies + +if [ -z "${CBMC_VERSION:-}" ]; then + echo "$0: Error: CBMC_VERSION is not specified" + exit 1 +fi + +# Binaries are not released for Ubuntu 20.04, so build from source +WORK_DIR=$(mktemp -d) +git clone \ + --branch cbmc-${CBMC_VERSION} --depth 1 \ + https://github.com/diffblue/cbmc \ + "${WORK_DIR}" + +pushd "${WORK_DIR}" + +cmake -S . -Bbuild -DWITH_JBMC=OFF -Dsat_impl="minisat2;cadical" \ + -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 \ + -DCMAKE_CXX_STANDARD_LIBRARIES=-lstdc++fs \ + -DCMAKE_CXX_FLAGS=-Wno-error=register +cmake --build build -- -j$(nproc) +sudo make -C build install + +popd +rm -rf "${WORK_DIR}" diff --git a/scripts/setup/ubuntu-20.04/install_deps.sh b/scripts/setup/ubuntu-20.04/install_deps.sh new file mode 100755 index 000000000000..d9e272d18a84 --- /dev/null +++ b/scripts/setup/ubuntu-20.04/install_deps.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +set -eu + +# Dependencies. +DEPS=( + bison + cmake + curl + flex + # CBMC needs g++-10 which doesn't come by default on Ubuntu 20.04 + g++-10 + gcc + git + gpg-agent + make + patch + wget + zlib1g + zlib1g-dev +) + +set -x + +# Github promises weekly build image updates, but recommends running +# `sudo apt-get update` before installing packages in case the `apt` +# index is stale. This prevents package installation failures. +# https://docs.github.com/en/actions/using-github-hosted-runners/customizing-github-hosted-runners#installing-software-on-ubuntu-runners +sudo apt-get --yes update + +sudo DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends --yes "${DEPS[@]}" + +# Get the directory containing this script +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +${SCRIPT_DIR}/install_cbmc.sh +# The Kissat installation script is platform-independent, so is placed one level up +${SCRIPT_DIR}/../install_kissat.sh diff --git a/scripts/setup/ubuntu-20.04/install_doc_deps.sh b/scripts/setup/ubuntu-20.04/install_doc_deps.sh new file mode 100755 index 000000000000..e3f4dd2c0d74 --- /dev/null +++ b/scripts/setup/ubuntu-20.04/install_doc_deps.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +set -eux + +cargo install mdbook-graphviz +DEBIAN_FRONTEND=noninteractive sudo apt-get install --no-install-recommends --yes graphviz diff --git a/tests/README.md b/tests/README.md index 67cae9791d2c..971012b95f0c 100644 --- a/tests/README.md +++ b/tests/README.md @@ -2,3 +2,4 @@ You can see more details about each test suite in this folder in the [Kani testing suites](https://model-checking.github.io/kani/regression-testing.html#kani-testing-suites). + diff --git a/tests/coverage/abort/expected b/tests/coverage/abort/expected index e9c9727a6f03..ae63575af3bc 100644 --- a/tests/coverage/abort/expected +++ b/tests/coverage/abort/expected @@ -8,13 +8,13 @@ 8| | #[kani::proof]\ 9| 1| fn main() {\ 10| 1| for i in 0..4 {\ - 11| | if i == 1 {\ + 11| 1| if i == 1 {\ 12| | // This comes first and it should be reachable.\ 13| 1| process::abort();\ 14| 1| }\ 15| 1| if i == 2 {\ 16| | // This should never happen.\ - 17| 0| ```process::exit(1)''';\ + 17| 0| ```process::exit'''(1);\ 18| 1| } \ 19| | }\ 20| 0| ```assert!'''(false, ```"This is unreachable"''');\ diff --git a/tests/coverage/assert_eq/expected b/tests/coverage/assert_eq/expected index 0cc1e01fbca9..8665bb8bcbeb 100644 --- a/tests/coverage/assert_eq/expected +++ b/tests/coverage/assert_eq/expected @@ -5,7 +5,7 @@ 5| 1| fn main() {\ 6| 1| let x: i32 = kani::any();\ 7| 1| let y = if x > 10 { 15 } else { 33 };\ - 8| 0| if y > 50 ```{'''\ + 8| 1| if y > 50 ```{'''\ 9| 0| ``` assert_eq!(y, 55);'''\ 10| 1| ``` }'''\ 11| | }\ diff --git a/tests/coverage/break/expected b/tests/coverage/break/expected index e1570030f6ae..25d192336b7c 100644 --- a/tests/coverage/break/expected +++ b/tests/coverage/break/expected @@ -3,7 +3,7 @@ 3| | \ 4| 1| fn find_positive(nums: &[i32]) -> Option {\ 5| 1| for &num in nums {\ - 6| | if num > 0 {\ + 6| 1| if num > 0 {\ 7| 1| return Some(num);\ 8| 1| } \ 9| | }\ diff --git a/tests/coverage/debug-assert/expected b/tests/coverage/debug-assert/expected index 82ad7c992ca3..d36fc11843d0 100644 --- a/tests/coverage/debug-assert/expected +++ b/tests/coverage/debug-assert/expected @@ -9,7 +9,7 @@ 9| | #[kani::proof]\ 10| 1| fn main() {\ 11| 1| for i in 0..4 {\ - 12| 0| debug_assert!(i > 0, ```"This should fail and stop the execution"''');\ + 12| 1| debug_assert!(i > 0, ```"This should fail and stop the execution"''');\ 13| 0| ```assert!(i == 0''', ```"This should be unreachable"''');\ 14| | }\ 15| | }\ diff --git a/tests/coverage/early-return/expected b/tests/coverage/early-return/expected index efdd71ee91f0..4df1f1922603 100644 --- a/tests/coverage/early-return/expected +++ b/tests/coverage/early-return/expected @@ -3,7 +3,7 @@ 3| | \ 4| 1| fn find_index(nums: &[i32], target: i32) -> Option {\ 5| 1| for (index, &num) in nums.iter().enumerate() {\ - 6| | if num == target {\ + 6| 1| if num == target {\ 7| 1| return Some(index);\ 8| 1| } \ 9| | }\ diff --git a/tests/coverage/known_issues/variant/expected b/tests/coverage/known_issues/variant/expected index 6445c038f060..a42769c39f40 100644 --- a/tests/coverage/known_issues/variant/expected +++ b/tests/coverage/known_issues/variant/expected @@ -16,12 +16,12 @@ 16| 1| fn print_direction(dir: Direction) {\ 17| | // For some reason, `dir`'s span is reported as `UNCOVERED` too\ 18| 0| match ```dir''' {\ - 19| 0| Direction::Up => ```println!("Going up!")''',\ - 20| 0| Direction::Down => ```println!("Going down!")''',\ + 19| 0| Direction::Up => ```println!("Going up!"'''),\ + 20| 0| Direction::Down => ```println!("Going down!"'''),\ 21| 1| Direction::Left => println!("Going left!"),\ - 22| 0| Direction::Right if 1 == ```1 => println!("Going right!")''',\ + 22| 0| Direction::Right if 1 == ```1 => println!("Going right!"'''),\ 23| | // This part is unreachable since we cover all variants in the match.\ - 24| 0| _ => ```println!("Not going anywhere!")''',\ + 24| 0| _ => ```println!("Not going anywhere!"'''),\ 25| | }\ 26| | }\ 27| | \ diff --git a/tests/expected/any_vec/exact_length.expected b/tests/expected/any_vec/exact_length.expected index f64d2830a7b8..7191e2175f0a 100644 --- a/tests/expected/any_vec/exact_length.expected +++ b/tests/expected/any_vec/exact_length.expected @@ -1,11 +1,11 @@ Checking harness check_access_length_17... -Failed Checks: assumption failed\ +Failed Checks: Rust intrinsic assumption failed\ in >::get_unchecked Checking harness check_access_length_zero... -Failed Checks: assumption failed\ +Failed Checks: Rust intrinsic assumption failed\ in >::get_unchecked Verification failed for - check_access_length_17 diff --git a/tests/expected/any_vec/out_bounds.expected b/tests/expected/any_vec/out_bounds.expected index 24121aee4ee8..b457127fec4c 100644 --- a/tests/expected/any_vec/out_bounds.expected +++ b/tests/expected/any_vec/out_bounds.expected @@ -1,6 +1,6 @@ Checking harness check_always_out_bounds... -Failed Checks: assumption failed +Failed Checks: Rust intrinsic assumption failed in >::get_unchecked Verification failed for - check_always_out_bounds diff --git a/tests/expected/bounded-arbitrary/hash/hash.expected b/tests/expected/bounded-arbitrary/hash/hash.expected new file mode 100644 index 000000000000..7b5dde8cb438 --- /dev/null +++ b/tests/expected/bounded-arbitrary/hash/hash.expected @@ -0,0 +1,10 @@ +Checking harness check_hashset... + + ** 2 of 2 cover properties satisfied + +Checking harness check_hashmap... + + ** 2 of 2 cover properties satisfied + +Manual Harness Summary: +Complete - 2 successfully verified harnesses, 0 failures, 2 total. \ No newline at end of file diff --git a/tests/expected/bounded-arbitrary/hash/hash.rs b/tests/expected/bounded-arbitrary/hash/hash.rs new file mode 100644 index 000000000000..3f676d133a4c --- /dev/null +++ b/tests/expected/bounded-arbitrary/hash/hash.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This file tests whether we can generate a bounded Hashmap/Hashset that has any possible size between 0-BOUND + +#[kani::proof] +#[kani::unwind(5)] +fn check_hashmap() { + // A larger bound causes this to take a long time + const BOUND: usize = 1; + let hash_map: std::collections::HashMap = kani::bounded_any::<_, BOUND>(); + kani::cover!(hash_map.len() == 0); + kani::cover!(hash_map.len() == 1); +} + +#[kani::proof] +#[kani::unwind(5)] +fn check_hashset() { + const BOUND: usize = 1; + let hash_set: std::collections::HashSet = kani::bounded_any::<_, BOUND>(); + kani::cover!(hash_set.len() == 0); + kani::cover!(hash_set.len() == 1); +} diff --git a/tests/expected/bounded-arbitrary/option/option.expected b/tests/expected/bounded-arbitrary/option/option.expected new file mode 100644 index 000000000000..0d1521b75d9a --- /dev/null +++ b/tests/expected/bounded-arbitrary/option/option.expected @@ -0,0 +1,5 @@ +Checking harness check_option... + +** 6 of 6 cover properties satisfied + +Complete - 1 successfully verified harnesses, 0 failures, 1 total. \ No newline at end of file diff --git a/tests/expected/bounded-arbitrary/option/option.rs b/tests/expected/bounded-arbitrary/option/option.rs new file mode 100644 index 000000000000..6acb7be8f3aa --- /dev/null +++ b/tests/expected/bounded-arbitrary/option/option.rs @@ -0,0 +1,18 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This file tests whether we can generate a bounded option (`T: BoundedArbitrary` in `Option`) that correctly +//! represents None or Some(t) where t is bounded by N. + +#[kani::proof] +fn check_option() { + let my_option: Option> = kani::bounded_any::<_, 4>(); + kani::cover!(my_option.is_none()); + if let Some(inner) = my_option { + kani::cover!(inner.len() == 0); + kani::cover!(inner.len() == 1); + kani::cover!(inner.len() == 2); + kani::cover!(inner.len() == 3); + kani::cover!(inner.len() == 4); + } +} diff --git a/tests/expected/bounded-arbitrary/result/result.expected b/tests/expected/bounded-arbitrary/result/result.expected new file mode 100644 index 000000000000..517f0b5a99df --- /dev/null +++ b/tests/expected/bounded-arbitrary/result/result.expected @@ -0,0 +1,5 @@ +Checking harness check_result... + +** 10 of 10 cover properties satisfied + +Complete - 1 successfully verified harnesses, 0 failures, 1 total. \ No newline at end of file diff --git a/tests/expected/bounded-arbitrary/result/result.rs b/tests/expected/bounded-arbitrary/result/result.rs new file mode 100644 index 000000000000..c095c2cf3833 --- /dev/null +++ b/tests/expected/bounded-arbitrary/result/result.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[kani::proof] +fn check_result() { + let my_result: Result, Vec> = kani::bounded_any::<_, 4>(); + match my_result { + Ok(inner_vec) => { + kani::cover!(inner_vec.len() == 0); + kani::cover!(inner_vec.len() == 1); + kani::cover!(inner_vec.len() == 2); + kani::cover!(inner_vec.len() == 3); + kani::cover!(inner_vec.len() == 4); + } + Err(inner_vec) => { + kani::cover!(inner_vec.len() == 0); + kani::cover!(inner_vec.len() == 1); + kani::cover!(inner_vec.len() == 2); + kani::cover!(inner_vec.len() == 3); + kani::cover!(inner_vec.len() == 4); + } + } +} diff --git a/tests/expected/bounded-arbitrary/reverse_vec/vec.expected b/tests/expected/bounded-arbitrary/reverse_vec/vec.expected new file mode 100644 index 000000000000..af714c630c4b --- /dev/null +++ b/tests/expected/bounded-arbitrary/reverse_vec/vec.expected @@ -0,0 +1,8 @@ +Checking harness check_reverse_is_its_own_inverse_should_fail...\ + +Checking harness check_reverse_is_its_own_inverse_incomplete...\ + +Checking harness check_reverse_is_its_own_inverse...\ + +Verification failed for - check_reverse_is_its_own_inverse_should_fail +Complete - 2 successfully verified harnesses, 1 failures, 3 total. \ No newline at end of file diff --git a/tests/expected/bounded-arbitrary/reverse_vec/vec.rs b/tests/expected/bounded-arbitrary/reverse_vec/vec.rs new file mode 100644 index 000000000000..252b1edf1f42 --- /dev/null +++ b/tests/expected/bounded-arbitrary/reverse_vec/vec.rs @@ -0,0 +1,56 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Check that users can use BoundedArbitrary to perform bounded verification of functions that use Vec. + +extern crate kani; + +fn reverse_vector(mut input: Vec) -> Vec { + let mut reversed = vec![]; + for _ in 0..input.len() { + reversed.push(input.pop().unwrap()); + } + reversed +} + +fn bad_reverse_vector(mut input: Vec) -> Vec { + let mut reversed = vec![]; + for i in 0..input.len() { + if i < N { + reversed.push(input.pop().unwrap()); + } else { + reversed.push(T::default()) + } + } + reversed +} + +#[kani::proof] +#[kani::unwind(5)] +fn check_reverse_is_its_own_inverse() { + let input: Vec = kani::bounded_any::<_, 4>(); + let double_reversed = reverse_vector(reverse_vector(input.clone())); + for i in 0..input.len() { + assert_eq!(input[i], double_reversed[i]) + } +} + +#[kani::proof] +#[kani::unwind(17)] +fn check_reverse_is_its_own_inverse_incomplete() { + let input: Vec = kani::bounded_any::<_, 16>(); + let double_reversed = bad_reverse_vector::<_, 16>(bad_reverse_vector::<_, 16>(input.clone())); + for i in 0..input.len() { + assert_eq!(input[i], double_reversed[i]) + } +} + +#[kani::proof] +#[kani::unwind(6)] +fn check_reverse_is_its_own_inverse_should_fail() { + let input: Vec = kani::bounded_any::<_, 5>(); + let double_reversed = bad_reverse_vector::<_, 4>(bad_reverse_vector::<_, 4>(input.clone())); + for i in 0..input.len() { + assert_eq!(input[i], double_reversed[i]) + } +} diff --git a/tests/expected/bounded-arbitrary/string/string.expected b/tests/expected/bounded-arbitrary/string/string.expected new file mode 100644 index 000000000000..0451aadd20e5 --- /dev/null +++ b/tests/expected/bounded-arbitrary/string/string.expected @@ -0,0 +1,6 @@ +Checking harness check_string... + +** 6 of 6 cover properties satisfied + +Manual Harness Summary: +Complete - 1 successfully verified harnesses, 0 failures, 1 total. \ No newline at end of file diff --git a/tests/expected/bounded-arbitrary/string/string.rs b/tests/expected/bounded-arbitrary/string/string.rs new file mode 100644 index 000000000000..721de20c1d4c --- /dev/null +++ b/tests/expected/bounded-arbitrary/string/string.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[kani::proof] +#[kani::unwind(6)] +fn check_string() { + let s: String = kani::bounded_any::<_, 5>(); + + kani::cover!(s == String::from("")); + kani::cover!(s == String::from("a")); + kani::cover!(s == String::from("ab")); + kani::cover!(s == String::from("abc")); + kani::cover!(s == String::from("abcd")); + kani::cover!(s == String::from("abcde")); +} diff --git a/tests/expected/derive-bounded-arbitrary/enum.expected b/tests/expected/derive-bounded-arbitrary/enum.expected new file mode 100644 index 000000000000..96b9bc8624da --- /dev/null +++ b/tests/expected/derive-bounded-arbitrary/enum.expected @@ -0,0 +1,5 @@ +Checking harness check_enum... + + ** 15 of 15 cover properties satisfied + +Complete - 1 successfully verified harnesses, 0 failures, 1 total. \ No newline at end of file diff --git a/tests/expected/derive-bounded-arbitrary/enum.rs b/tests/expected/derive-bounded-arbitrary/enum.rs new file mode 100644 index 000000000000..67ef8a71c8bb --- /dev/null +++ b/tests/expected/derive-bounded-arbitrary/enum.rs @@ -0,0 +1,45 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Check that derive BoundedArbitrary macro works on enums + +#[allow(unused)] +#[derive(kani::BoundedArbitrary)] +enum Enum { + A(#[bounded] String), + B(#[bounded] Vec, usize), + C { + #[bounded] + x: Vec, + y: bool, + }, +} + +#[kani::proof] +#[kani::unwind(6)] +fn check_enum() { + let any_enum: Enum = kani::bounded_any::<_, 4>(); + match any_enum { + Enum::A(s) => { + kani::cover!(s.len() == 0); + kani::cover!(s.len() == 1); + kani::cover!(s.len() == 2); + kani::cover!(s.len() == 3); + kani::cover!(s.len() == 4); + } + Enum::B(v, _) => { + kani::cover!(v.len() == 0); + kani::cover!(v.len() == 1); + kani::cover!(v.len() == 2); + kani::cover!(v.len() == 3); + kani::cover!(v.len() == 4); + } + Enum::C { x, y: _ } => { + kani::cover!(x.len() == 0); + kani::cover!(x.len() == 1); + kani::cover!(x.len() == 2); + kani::cover!(x.len() == 3); + kani::cover!(x.len() == 4); + } + } +} diff --git a/tests/expected/derive-bounded-arbitrary/struct.expected b/tests/expected/derive-bounded-arbitrary/struct.expected new file mode 100644 index 000000000000..d0eaf7a6d8b5 --- /dev/null +++ b/tests/expected/derive-bounded-arbitrary/struct.expected @@ -0,0 +1,5 @@ +Checking harness check_my_vec... + + ** 5 of 5 cover properties satisfied + +Complete - 1 successfully verified harnesses, 0 failures, 1 total. \ No newline at end of file diff --git a/tests/expected/derive-bounded-arbitrary/struct.rs b/tests/expected/derive-bounded-arbitrary/struct.rs new file mode 100644 index 000000000000..311680dbe65b --- /dev/null +++ b/tests/expected/derive-bounded-arbitrary/struct.rs @@ -0,0 +1,26 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Check that derive BoundedArbitrary macro works on enums + +extern crate kani; +use kani::BoundedArbitrary; + +#[derive(BoundedArbitrary)] +#[allow(unused)] +struct MyVector { + #[bounded] + vector: Vec, + cap: usize, +} + +#[kani::proof] +#[kani::unwind(6)] +fn check_my_vec() { + let my_vec: MyVector = kani::bounded_any::<_, 4>(); + kani::cover!(my_vec.vector.len() == 0); + kani::cover!(my_vec.vector.len() == 1); + kani::cover!(my_vec.vector.len() == 2); + kani::cover!(my_vec.vector.len() == 3); + kani::cover!(my_vec.vector.len() == 4); +} diff --git a/tests/expected/function-contract/history/block.expected b/tests/expected/function-contract/history/block.expected index 83e958f52d77..b54752699237 100644 --- a/tests/expected/function-contract/history/block.expected +++ b/tests/expected/function-contract/history/block.expected @@ -1,5 +1,5 @@ assertion\ - Status: SUCCESS\ - - Description: "|result| old({ let x = &ptr; let y = **x; y + 1 }) == *ptr"\ + - Description: "|result| old({let x = &ptr; let y = **x; y + 1}) == *ptr"\ VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/history/side_effect.expected b/tests/expected/function-contract/history/side_effect.expected index 386399c67941..0a2aa2a7273c 100644 --- a/tests/expected/function-contract/history/side_effect.expected +++ b/tests/expected/function-contract/history/side_effect.expected @@ -1,2 +1,2 @@ -Failed Checks: |result| old({ *ptr+=1; *ptr }) == _val +Failed Checks: |result| old({*ptr+=1; *ptr}) == _val VERIFICATION:- FAILED diff --git a/tests/expected/function-contract/interior-mutability/api/unsafecell.expected b/tests/expected/function-contract/interior-mutability/api/unsafecell.expected index 1646a8a78e7f..3639d3fd5deb 100644 --- a/tests/expected/function-contract/interior-mutability/api/unsafecell.expected +++ b/tests/expected/function-contract/interior-mutability/api/unsafecell.expected @@ -1,6 +1,6 @@ assertion\ - Status: SUCCESS\ -- Description: "|_| unsafe{ *im.x.get() } < 101"\ +- Description: "|_| unsafe{*im.x.get()} < 101"\ in function modify VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/interior-mutability/whole-struct/refcell.expected b/tests/expected/function-contract/interior-mutability/whole-struct/refcell.expected index 225c290a171e..978d86752068 100644 --- a/tests/expected/function-contract/interior-mutability/whole-struct/refcell.expected +++ b/tests/expected/function-contract/interior-mutability/whole-struct/refcell.expected @@ -1,6 +1,6 @@ assertion\ - Status: SUCCESS\ -- Description: "|_| unsafe{ *im.x.as_ptr() } < 101"\ +- Description: "|_| unsafe{*im.x.as_ptr()} < 101"\ in function modify VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/interior-mutability/whole-struct/unsafecell.expected b/tests/expected/function-contract/interior-mutability/whole-struct/unsafecell.expected index 1646a8a78e7f..3639d3fd5deb 100644 --- a/tests/expected/function-contract/interior-mutability/whole-struct/unsafecell.expected +++ b/tests/expected/function-contract/interior-mutability/whole-struct/unsafecell.expected @@ -1,6 +1,6 @@ assertion\ - Status: SUCCESS\ -- Description: "|_| unsafe{ *im.x.get() } < 101"\ +- Description: "|_| unsafe{*im.x.get()} < 101"\ in function modify VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/loop-contract/contract_proof_function_with_loop.expected b/tests/expected/loop-contract/contract_proof_function_with_loop.expected new file mode 100644 index 000000000000..0109678c22fd --- /dev/null +++ b/tests/expected/loop-contract/contract_proof_function_with_loop.expected @@ -0,0 +1,13 @@ +has_loop::{closure#2}::{closure#1}.loop_invariant_step.1\ + - Status: SUCCESS\ + - Description: "Check invariant after step for loop has_loop::{closure#2}::{closure#1}.0"\ +in function has_loop::{closure#2}::{closure#1} + + + +has_loop::{closure#2}::{closure#1}.precondition_instance.1\ + - Status: SUCCESS\ + - Description: "free argument must be NULL or valid pointer"\ +in function has_loop::{closure#2}::{closure#1} + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/loop-contract/contract_proof_function_with_loop.rs b/tests/expected/loop-contract/contract_proof_function_with_loop.rs new file mode 100644 index 000000000000..446214979c69 --- /dev/null +++ b/tests/expected/loop-contract/contract_proof_function_with_loop.rs @@ -0,0 +1,25 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// kani-flags: -Z loop-contracts -Z function-contracts + +//! Test that we can prove a function contract and a loop contract in tandem. + +#![feature(proc_macro_hygiene)] +#![feature(stmt_expr_attributes)] + +#[kani::requires(i>=2)] +#[kani::ensures(|ret| *ret == 2)] +pub fn has_loop(mut i: u16) -> u16 { + #[kani::loop_invariant(i>=2)] + while i > 2 { + i = i - 1 + } + i +} + +#[kani::proof_for_contract(has_loop)] +fn contract_proof() { + let i: u16 = kani::any(); + let j = has_loop(i); +} diff --git a/tests/expected/loop-contract/function_call_with_loop.expected b/tests/expected/loop-contract/function_call_with_loop.expected new file mode 100644 index 000000000000..d16789b1d209 --- /dev/null +++ b/tests/expected/loop-contract/function_call_with_loop.expected @@ -0,0 +1,11 @@ +has_loop::{closure#3}::{closure#0}.loop_invariant_base.1\ + - Status: SUCCESS\ + - Description: "Check invariant before entry for loop has_loop::{closure#3}::{closure#0}.0"\ +in function has_loop::{closure#3}::{closure#0} + +has_loop::{closure#3}::{closure#0}.precondition_instance.5\ + - Status: SUCCESS\ + - Description: "free called for new[] object"\ +in function has_loop::{closure#3}::{closure#0} + +Failed Checks: i>=2 diff --git a/tests/expected/loop-contract/function_call_with_loop.rs b/tests/expected/loop-contract/function_call_with_loop.rs new file mode 100644 index 000000000000..cb2453496187 --- /dev/null +++ b/tests/expected/loop-contract/function_call_with_loop.rs @@ -0,0 +1,25 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// kani-flags: -Z loop-contracts -Z function-contracts + +//! Calling a function that contains loops and test that using a #[kani::proof] harness fails because the function's precondition gets asserted. + +#![feature(proc_macro_hygiene)] +#![feature(stmt_expr_attributes)] + +#[kani::requires(i>=2)] +#[kani::ensures(|ret| *ret == 2)] +pub fn has_loop(mut i: u16) -> u16 { + #[kani::loop_invariant(i>=2)] + while i > 2 { + i = i - 1 + } + i +} + +#[kani::proof] +fn call_has_loop() { + let i: u16 = kani::any(); + let j = has_loop(i); +} diff --git a/tests/expected/loop-contract/function_with_loop_no_assertion.expected b/tests/expected/loop-contract/function_with_loop_no_assertion.expected new file mode 100644 index 000000000000..afd863b2937b --- /dev/null +++ b/tests/expected/loop-contract/function_with_loop_no_assertion.expected @@ -0,0 +1,8 @@ +has_loop.loop_invariant_base.1\ + - Status: SUCCESS\ + - Description: "Check invariant before entry for loop has_loop.0"\ +in function has_loop + + + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/loop-contract/function_with_loop_no_assertion.rs b/tests/expected/loop-contract/function_with_loop_no_assertion.rs new file mode 100644 index 000000000000..5609796d56be --- /dev/null +++ b/tests/expected/loop-contract/function_with_loop_no_assertion.rs @@ -0,0 +1,25 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// kani-flags: -Z loop-contracts -Z function-contracts --no-assert-contracts + +//Call a function with loop without checking the contract. + +#![feature(proc_macro_hygiene)] +#![feature(stmt_expr_attributes)] + +#[kani::requires(i>=2)] +#[kani::ensures(|ret| *ret == 2)] +pub fn has_loop(mut i: u16) -> u16 { + #[kani::loop_invariant(i>=2)] + while i > 2 { + i = i - 1 + } + i +} + +#[kani::proof] +fn contract_proof() { + let i: u16 = kani::any(); + let j = has_loop(i); +} diff --git a/tests/expected/loop-contract/loop_with_old.expected b/tests/expected/loop-contract/loop_with_old.expected new file mode 100644 index 000000000000..33035940db68 --- /dev/null +++ b/tests/expected/loop-contract/loop_with_old.expected @@ -0,0 +1,5 @@ +loop_with_old.loop_invariant_base.1\ + - Status: SUCCESS\ + - Description: "Check invariant before entry for loop loop_with_old.0" + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/loop-contract/loop_with_old.rs b/tests/expected/loop-contract/loop_with_old.rs new file mode 100644 index 000000000000..32371b926831 --- /dev/null +++ b/tests/expected/loop-contract/loop_with_old.rs @@ -0,0 +1,24 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// kani-flags: -Z loop-contracts + +//! Check the use of "old" in loop invariant + +#![feature(stmt_expr_attributes)] +#![feature(proc_macro_hygiene)] + +#[kani::proof] +pub fn loop_with_old() { + let mut x: u8 = kani::any_where(|v| *v < 10); + let mut y: u8 = kani::any(); + let mut i = 0; + #[kani::loop_invariant( (i<=5) && (x <= on_entry(x) + i) && (on_entry(x) + i == on_entry(i) + x))] + while i < 5 { + if i == 0 { + y = x + } + x += 1; + i += 1; + } +} diff --git a/tests/expected/loop-contract/loop_with_old_and_prev.expected b/tests/expected/loop-contract/loop_with_old_and_prev.expected new file mode 100644 index 000000000000..3d74c06488b8 --- /dev/null +++ b/tests/expected/loop-contract/loop_with_old_and_prev.expected @@ -0,0 +1,6 @@ +loop_with_old_and_prev.loop_invariant_base.1\ + - Status: SUCCESS\ + - Description: "Check invariant before entry for loop loop_with_old_and_prev.0"\ + + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/loop-contract/loop_with_old_and_prev.rs b/tests/expected/loop-contract/loop_with_old_and_prev.rs new file mode 100644 index 000000000000..87273c8d79df --- /dev/null +++ b/tests/expected/loop-contract/loop_with_old_and_prev.rs @@ -0,0 +1,22 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// kani-flags: -Z loop-contracts + +//! Check the use of both "old" and "prev" in loop invariant + +#![feature(stmt_expr_attributes)] +#![feature(proc_macro_hygiene)] + +#[kani::proof] +pub fn loop_with_old_and_prev() { + let mut i = 100; + #[kani::loop_invariant((i >= 2) && (i <= 100) && (i % 2 == 0) && (on_entry(i) == 100) && (prev(i) == i + 2))] + while i > 2 { + if i == 1 { + break; + } + i = i - 2; + } + assert!(i == 2); +} diff --git a/tests/expected/loop-contract/loop_with_prev.expected b/tests/expected/loop-contract/loop_with_prev.expected new file mode 100644 index 000000000000..1ff35b1c3e37 --- /dev/null +++ b/tests/expected/loop-contract/loop_with_prev.expected @@ -0,0 +1,5 @@ +loop_with_prev.loop_invariant_step.1\ + - Status: SUCCESS\ + - Description: "Check invariant after step for loop loop_with_prev.0" + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/loop-contract/loop_with_prev.rs b/tests/expected/loop-contract/loop_with_prev.rs new file mode 100644 index 000000000000..d64e766a4abf --- /dev/null +++ b/tests/expected/loop-contract/loop_with_prev.rs @@ -0,0 +1,24 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// kani-flags: -Z loop-contracts + +//! Check the use of "prev" in loop invariant + +#![feature(stmt_expr_attributes)] +#![feature(proc_macro_hygiene)] + +#[kani::proof] +pub fn loop_with_prev() { + let mut i = 100; + let mut j = 100; + #[kani::loop_invariant((i >= 2) && (i <= 100) && (i % 2 == 0) && (j == 2*i-100) && (prev(i) == i + 2) && (prev(j) == j + 4) && (prev(i-j) == i-j-2) )] + while i > 2 { + if i == 1 { + break; + } + i = i - 2; + j = j - 4 + } + assert!(i == 2); +} diff --git a/tests/expected/loop-contract/loop_with_prev_break_first_iter.expected b/tests/expected/loop-contract/loop_with_prev_break_first_iter.expected new file mode 100644 index 000000000000..68dfcd51d085 --- /dev/null +++ b/tests/expected/loop-contract/loop_with_prev_break_first_iter.expected @@ -0,0 +1,3 @@ +Failed Checks: assertion failed: (i >= 2) && (i <= 100) && (__kani_prev_var_ + +VERIFICATION:- FAILED diff --git a/tests/expected/loop-contract/loop_with_prev_break_first_iter.rs b/tests/expected/loop-contract/loop_with_prev_break_first_iter.rs new file mode 100644 index 000000000000..87cab0426373 --- /dev/null +++ b/tests/expected/loop-contract/loop_with_prev_break_first_iter.rs @@ -0,0 +1,22 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// kani-flags: -Z loop-contracts + +//! Check if loop contracts is correctly applied. + +#![feature(stmt_expr_attributes)] +#![feature(proc_macro_hygiene)] + +#[kani::proof] +pub fn loop_with_prev() { + let mut i = 100; + #[kani::loop_invariant((i >= 2) && (i <= 100) && (prev(i) == i + 2))] + while i > 2 { + if i == 100 { + break; + } + i = i - 2; + } + assert!(i == 2); +} diff --git a/tests/expected/loop-contract/loop_with_true_invariant.expected b/tests/expected/loop-contract/loop_with_true_invariant.expected new file mode 100644 index 000000000000..556c39e4d7e8 --- /dev/null +++ b/tests/expected/loop-contract/loop_with_true_invariant.expected @@ -0,0 +1,5 @@ +main.loop_invariant_base.1\ + - Status: SUCCESS\ + - Description: "Check invariant before entry for loop main.0" + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/loop-contract/loop_with_true_invariant.rs b/tests/expected/loop-contract/loop_with_true_invariant.rs new file mode 100644 index 000000000000..be03534e0a09 --- /dev/null +++ b/tests/expected/loop-contract/loop_with_true_invariant.rs @@ -0,0 +1,18 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// kani-flags: -Z loop-contracts + +//! Check the use of "true" in loop invariant + +#![feature(stmt_expr_attributes)] +#![feature(proc_macro_hygiene)] + +#[kani::proof] +fn main() { + let mut i = 100; + #[kani::loop_invariant(true)] + while i > 1 { + i /= 2; + } +} diff --git a/tests/expected/loop-contract/simple_loop_loop.expected b/tests/expected/loop-contract/simple_loop_loop.expected new file mode 100644 index 000000000000..80289982610e --- /dev/null +++ b/tests/expected/loop-contract/simple_loop_loop.expected @@ -0,0 +1,6 @@ +main.loop_invariant_base.1\ + - Status: SUCCESS\ + - Description: "Check invariant before entry for loop main.0"\ +in function main + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/loop-contract/simple_loop_loop.rs b/tests/expected/loop-contract/simple_loop_loop.rs new file mode 100644 index 000000000000..7114342f730b --- /dev/null +++ b/tests/expected/loop-contract/simple_loop_loop.rs @@ -0,0 +1,22 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// kani-flags: -Z loop-contracts + +//! Check if loop-loop invariant is correctly applied. + +#![feature(proc_macro_hygiene)] +#![feature(stmt_expr_attributes)] + +#[kani::proof] +fn main() { + let mut i = 100; + #[kani::loop_invariant(i<=100 && i >= 4 && i % 2 ==0)] + loop { + i = i - 2; + if i == 2 { + break; + } + } + assert!(i == 2); +} diff --git a/tests/expected/ptr_to_ref_cast/alignment/expected b/tests/expected/ptr_to_ref_cast/alignment/expected index c7d8f5109e21..2e4d4c3be02a 100644 --- a/tests/expected/ptr_to_ref_cast/alignment/expected +++ b/tests/expected/ptr_to_ref_cast/alignment/expected @@ -1,4 +1,4 @@ check_misaligned_ptr_cast_fail.safety_check\ Status: FAILURE\ -Description: "misaligned pointer to reference cast: address must be a multiple of its type's alignment"\ +Description: "misaligned pointer dereference: address must be a multiple of its type's alignment"\ in function check_misaligned_ptr_cast_fail diff --git a/tests/expected/quantifiers/assert_with_exists_fail.expected b/tests/expected/quantifiers/assert_with_exists_fail.expected new file mode 100644 index 000000000000..7c93e835e724 --- /dev/null +++ b/tests/expected/quantifiers/assert_with_exists_fail.expected @@ -0,0 +1,4 @@ +- Status: FAILURE\ +- Description: "assertion with exists"\ + +VERIFICATION:- FAILED diff --git a/tests/expected/quantifiers/assert_with_exists_fail.rs b/tests/expected/quantifiers/assert_with_exists_fail.rs new file mode 100644 index 000000000000..3e2c6cdb3781 --- /dev/null +++ b/tests/expected/quantifiers/assert_with_exists_fail.rs @@ -0,0 +1,9 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[kani::proof] +fn exists_assert_harness() { + let j = kani::any(); + kani::assume(j > 1); + kani::assert(kani::exists!(|i in (3,5)| i < j ), "assertion with exists"); +} diff --git a/tests/expected/quantifiers/assert_with_exists_pass.expected b/tests/expected/quantifiers/assert_with_exists_pass.expected new file mode 100644 index 000000000000..33abfd607aaa --- /dev/null +++ b/tests/expected/quantifiers/assert_with_exists_pass.expected @@ -0,0 +1,4 @@ +- Status: SUCCESS\ +- Description: "assertion with exists"\ + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/quantifiers/assert_with_exists_pass.rs b/tests/expected/quantifiers/assert_with_exists_pass.rs new file mode 100644 index 000000000000..7d43d6117729 --- /dev/null +++ b/tests/expected/quantifiers/assert_with_exists_pass.rs @@ -0,0 +1,9 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[kani::proof] +fn exists_assert_harness() { + let j = kani::any(); + kani::assume(j > 2); + kani::assert(kani::exists!(|i in (2,5)| i < j ), "assertion with exists"); +} diff --git a/tests/expected/quantifiers/assert_with_forall_fail.expected b/tests/expected/quantifiers/assert_with_forall_fail.expected new file mode 100644 index 000000000000..d8eaf5db2b9a --- /dev/null +++ b/tests/expected/quantifiers/assert_with_forall_fail.expected @@ -0,0 +1,5 @@ +- Status: FAILURE\ +- Description: "assertion with forall"\ + +VERIFICATION:- FAILED + diff --git a/tests/expected/quantifiers/assert_with_forall_fail.rs b/tests/expected/quantifiers/assert_with_forall_fail.rs new file mode 100644 index 000000000000..c9bb7bbf62dd --- /dev/null +++ b/tests/expected/quantifiers/assert_with_forall_fail.rs @@ -0,0 +1,9 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[kani::proof] +fn forall_assert_harness() { + let j = kani::any(); + kani::assume(j > 3); + kani::assert(kani::forall!(|i in (2,5)| i < j ), "assertion with forall"); +} diff --git a/tests/expected/quantifiers/assert_with_forall_pass.expected b/tests/expected/quantifiers/assert_with_forall_pass.expected new file mode 100644 index 000000000000..d6f419825425 --- /dev/null +++ b/tests/expected/quantifiers/assert_with_forall_pass.expected @@ -0,0 +1,4 @@ +- Status: SUCCESS\ +- Description: "assertion with forall"\ + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/quantifiers/assert_with_forall_pass.rs b/tests/expected/quantifiers/assert_with_forall_pass.rs new file mode 100644 index 000000000000..5ebf73e7c0ee --- /dev/null +++ b/tests/expected/quantifiers/assert_with_forall_pass.rs @@ -0,0 +1,9 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[kani::proof] +fn forall_assert_harness() { + let j = kani::any(); + kani::assume(j > 5); + kani::assert(kani::forall!(|i in (2,5)| i < j ), "assertion with forall"); +} diff --git a/tests/expected/quantifiers/assume_with_exists_fail.expected b/tests/expected/quantifiers/assume_with_exists_fail.expected new file mode 100644 index 000000000000..794397c227e8 --- /dev/null +++ b/tests/expected/quantifiers/assume_with_exists_fail.expected @@ -0,0 +1,4 @@ +- Status: FAILURE\ +- Description: "assume with exists"\ + +VERIFICATION:- FAILED diff --git a/tests/expected/quantifiers/assume_with_exists_fail.rs b/tests/expected/quantifiers/assume_with_exists_fail.rs new file mode 100644 index 000000000000..f6df36a6942d --- /dev/null +++ b/tests/expected/quantifiers/assume_with_exists_fail.rs @@ -0,0 +1,9 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[kani::proof] +fn exists_assume_harness() { + let j = kani::any(); + kani::assume(kani::exists!(|i in (2,4)| i == j)); + kani::assert(j == 3, "assume with exists"); +} diff --git a/tests/expected/quantifiers/contracts_fail.expected b/tests/expected/quantifiers/contracts_fail.expected new file mode 100644 index 000000000000..1d67c28486c1 --- /dev/null +++ b/tests/expected/quantifiers/contracts_fail.expected @@ -0,0 +1,11 @@ +- Status: FAILURE\ +- Description: "|ret| +{ + unsafe{ + let ptr_x = xs.as_ptr(); let ptr_y = ys.as_ptr(); + kani::forall!(| k in (0, 8)| *ptr_x.wrapping_byte_offset(k as isize) + == *ptr_y.wrapping_byte_offset(k as isize)) + } +}" + +VERIFICATION:- FAILED diff --git a/tests/expected/quantifiers/contracts_fail.rs b/tests/expected/quantifiers/contracts_fail.rs new file mode 100644 index 000000000000..a1b87ebc8633 --- /dev/null +++ b/tests/expected/quantifiers/contracts_fail.rs @@ -0,0 +1,24 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +/// Copy only first 7 elements and left the last one unchanged. +#[kani::ensures(|ret| { unsafe{ + let ptr_x = xs.as_ptr(); + let ptr_y = ys.as_ptr(); + kani::forall!(| k in (0, 8)| *ptr_x.wrapping_byte_offset(k as isize) == *ptr_y.wrapping_byte_offset(k as isize))}})] +#[kani::modifies(ys)] +pub fn copy(xs: &mut [u8; 8], ys: &mut [u8; 8]) { + let mut i = 0; + while i < 7 { + ys[i] = xs[i]; + i = i + 1; + } +} + +#[kani::proof_for_contract(copy)] +fn copy_harness() { + let mut xs: [u8; 8] = kani::any(); + let mut ys: [u8; 8] = kani::any(); + copy(&mut xs, &mut ys); +} diff --git a/tests/expected/quantifiers/quantifier_with_no_external_variable.expected b/tests/expected/quantifiers/quantifier_with_no_external_variable.expected new file mode 100644 index 000000000000..0c27dc76ad41 --- /dev/null +++ b/tests/expected/quantifiers/quantifier_with_no_external_variable.expected @@ -0,0 +1,5 @@ +- Status: SUCCESS\ +- Description: "assertion failed: quan" + +VERIFICATION:- SUCCESSFUL + diff --git a/tests/expected/quantifiers/quantifier_with_no_external_variable.rs b/tests/expected/quantifiers/quantifier_with_no_external_variable.rs new file mode 100644 index 000000000000..dabf4d9f2137 --- /dev/null +++ b/tests/expected/quantifiers/quantifier_with_no_external_variable.rs @@ -0,0 +1,10 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +/// Quantifier with no external variable in the closure + +#[kani::proof] +fn test() { + let quan = kani::exists!(|j in (0, 100)| j == 0); + assert!(quan); +} diff --git a/tests/expected/uninit/intrinsics/expected b/tests/expected/uninit/intrinsics/expected index d19d694df75e..e2ca608475a1 100644 --- a/tests/expected/uninit/intrinsics/expected +++ b/tests/expected/uninit/intrinsics/expected @@ -1,7 +1,3 @@ -std::ptr::read::>.unsupported_construct.\ - - Status: FAILURE\ - - Description: "Interaction between raw pointers and unions is not yet supported." - check_typed_swap_nonoverlapping.safety_check.\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*mut u8`" diff --git a/tests/kani/DynTrait/upcast.rs b/tests/kani/DynTrait/upcast.rs new file mode 100644 index 000000000000..68618d2cdc9a --- /dev/null +++ b/tests/kani/DynTrait/upcast.rs @@ -0,0 +1,29 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Check that upcasting to a supertrait works correctly. + +trait SubTrait: SuperTrait2 + SuperTrait1 {} + +impl SubTrait for T {} + +trait SuperTrait1 { + fn trigger(&self, _old: &()) {} +} + +trait SuperTrait2 { + #[allow(unused)] + fn func(&self) {} +} + +#[derive(Clone, Copy, Default)] +struct Struct; + +impl SuperTrait1 for Struct {} +impl SuperTrait2 for Struct {} + +#[kani::proof] +fn main() { + let val: &dyn SubTrait = &Struct; + (val as &dyn SuperTrait1).trigger(&()); +} diff --git a/tests/kani/FunctionContracts/modify_slice_elem.rs b/tests/kani/FunctionContracts/modify_slice_elem_fixme.rs similarity index 84% rename from tests/kani/FunctionContracts/modify_slice_elem.rs rename to tests/kani/FunctionContracts/modify_slice_elem_fixme.rs index 50df13b45af3..385db9983d88 100644 --- a/tests/kani/FunctionContracts/modify_slice_elem.rs +++ b/tests/kani/FunctionContracts/modify_slice_elem_fixme.rs @@ -2,6 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Check that Kani correctly verifies the contract that modifies slices. //! +//! This started failing with the 2025-04-05 toolchain upgrade +//! Tracking issue: https://github.com/model-checking/kani/issues/4029 +//! //! Note that this test used to crash while parsing the annotations. // kani-flags: -Zfunction-contracts extern crate kani; diff --git a/tests/kani/FunctionContracts/multiple_inherent_impls.rs b/tests/kani/FunctionContracts/multiple_inherent_impls.rs index cdd25337cd36..76ee51d99590 100644 --- a/tests/kani/FunctionContracts/multiple_inherent_impls.rs +++ b/tests/kani/FunctionContracts/multiple_inherent_impls.rs @@ -26,3 +26,37 @@ fn verify_unchecked_mul_ambiguous_path() { let x: NonZero = NonZero(-1); x.unchecked_mul(-2); } + +// Test that the resolution still works if the function in question is nested inside multiple modules, +// i.e. the absolute path to the function can be arbitrarily long. +// As long as the generic arguments and function name match, resolution should succeed. +// This mimics the actual structure of NonZero relative to its harnesses in the standard library. +pub mod num { + pub mod negative { + pub struct NegativeNumber(pub T); + + impl NegativeNumber { + #[kani::requires(self.0.checked_mul(x).is_some())] + pub fn unchecked_mul(self, x: i32) -> i32 { + self.0 * x + } + } + + impl NegativeNumber { + #[kani::requires(self.0.checked_mul(x).is_some())] + pub fn unchecked_mul(self, x: i16) -> i16 { + self.0 * x + } + } + } +} + +mod verify { + use crate::num::negative::*; + + #[kani::proof_for_contract(NegativeNumber::::unchecked_mul)] + fn verify_unchecked_mul_ambiguous_path() { + let x: NegativeNumber = NegativeNumber(-1); + x.unchecked_mul(-2); + } +} diff --git a/tests/kani/Quantifiers/array.rs b/tests/kani/Quantifiers/array.rs new file mode 100644 index 000000000000..b59eccd614fb --- /dev/null +++ b/tests/kani/Quantifiers/array.rs @@ -0,0 +1,33 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[kani::proof] +fn vec_assert_forall_harness() { + let v = vec![10 as u8; 128]; + let ptr = v.as_ptr(); + unsafe { + kani::assert(kani::forall!(|i in (0,128)| *ptr.wrapping_byte_offset(i as isize) == 10), ""); + } +} + +#[kani::proof] +fn slice_assume_forall_harness() { + let arr: [u8; 8] = kani::any(); + let ptr = arr.as_ptr(); + unsafe { + kani::assume(kani::forall!(|i in (0,arr.len())| *ptr.wrapping_byte_offset(i as isize) < 8)); + } + kani::assert(arr[0] < 8, ""); +} + +#[kani::proof] +fn slice_assume_sorted_harness() { + let arr: [u8; 12] = kani::any(); + let ptr = arr.as_ptr(); + unsafe { + kani::assume( + kani::forall!(|i in (0,arr.len()-1)| *ptr.wrapping_byte_offset(i as isize) < *ptr.wrapping_byte_offset((i+1) as isize)), + ); + } + kani::assert(arr[0] < arr[1], ""); +} diff --git a/tests/kani/Quantifiers/contracts.rs b/tests/kani/Quantifiers/contracts.rs new file mode 100644 index 000000000000..d8e36c194774 --- /dev/null +++ b/tests/kani/Quantifiers/contracts.rs @@ -0,0 +1,43 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#[kani::requires(i==0)] +#[kani::ensures(|ret| { + unsafe{ + let ptr = arr.as_ptr(); kani::forall!(| k in (0, 8)| *ptr.wrapping_byte_offset(k as isize) == 0)}})] +#[kani::modifies(arr)] +pub fn set_zero(arr: &mut [u8; 8], mut i: usize) -> usize { + while i < 8 { + arr[i] = 0; + i = i + 1; + } + i +} + +#[kani::proof_for_contract(set_zero)] +fn set_zero_harness() { + let mut arr: [u8; 8] = kani::any(); + let i: usize = 0; + let _j = set_zero(&mut arr, i); +} + +#[kani::ensures(|ret| { + unsafe{ + let ptr_x = xs.as_ptr(); + let ptr_y = ys.as_ptr(); kani::forall!(| k in (0, 8)| *ptr_x.wrapping_byte_offset(k as isize) == *ptr_y.wrapping_byte_offset(k as isize))}})] +#[kani::modifies(ys)] +pub fn copy(xs: &mut [u8; 8], ys: &mut [u8; 8]) { + let mut i = 0; + while i < 8 { + ys[i] = xs[i]; + i = i + 1; + } +} + +#[kani::proof_for_contract(copy)] +fn copy_harness() { + let mut xs: [u8; 8] = kani::any(); + let mut ys: [u8; 8] = kani::any(); + copy(&mut xs, &mut ys); +} diff --git a/tests/kani/Quantifiers/even.rs b/tests/kani/Quantifiers/even.rs new file mode 100644 index 000000000000..e7f74440d85a --- /dev/null +++ b/tests/kani/Quantifiers/even.rs @@ -0,0 +1,9 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[kani::proof] +fn quantifier_even_harness() { + let j: usize = kani::any(); + kani::assume(j % 2 == 0 && j < 2000); + kani::assert(kani::exists!(|i in (0, 1000)| i + i == j), ""); +} diff --git a/tests/kani/Quantifiers/from_raw_part_fixme.rs b/tests/kani/Quantifiers/from_raw_part_fixme.rs new file mode 100644 index 000000000000..2f06161847bf --- /dev/null +++ b/tests/kani/Quantifiers/from_raw_part_fixme.rs @@ -0,0 +1,45 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! FIXME: + +use std::mem; + +#[kani::proof] +fn main() { + let original_v = vec![kani::any::(); 3]; + let v = original_v.clone(); + let v_len = v.len(); + let v_ptr = v.as_ptr(); + unsafe { + kani::assume( + kani::forall!(|i in (0,v_len) | *v_ptr.wrapping_byte_offset(4*i as isize) < 5), + ); + } + + // Prevent running `v`'s destructor so we are in complete control + // of the allocation. + let mut v = mem::ManuallyDrop::new(v); + + // Pull out the various important pieces of information about `v` + let p = v.as_mut_ptr(); + let len = v.len(); + let cap = v.capacity(); + + unsafe { + // Overwrite memory + for i in 0..len { + *p.add(i) += 1; + if i == 1 { + *p.add(i) = 0; + } + } + + // Put everything back together into a Vec + let rebuilt = Vec::from_raw_parts(p, len, cap); + let rebuilt_ptr = v.as_ptr(); + assert!( + kani::exists!(| i in (0, len) | *rebuilt_ptr.wrapping_byte_offset(4*i as isize) == 0) + ); + } +} diff --git a/tests/kani/Quantifiers/no_array.rs b/tests/kani/Quantifiers/no_array.rs new file mode 100644 index 000000000000..9681e4ccf0fa --- /dev/null +++ b/tests/kani/Quantifiers/no_array.rs @@ -0,0 +1,48 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[kani::proof] +fn forall_assert_harness() { + let j = kani::any(); + kani::assume(j > 5); + kani::assert(kani::forall!(|i in (2,5)| i < j ), ""); +} + +#[kani::proof] +fn forall_assume_harness() { + let j = kani::any(); + kani::assume(kani::forall!(|i in (2,5)| i < j)); + kani::assert(j > 4, ""); +} + +fn comp(x: usize, y: usize) -> bool { + x > y +} + +#[kani::proof] +fn forall_function_harness() { + let j = kani::any(); + kani::assume(j > 5); + kani::assert(kani::forall!(|i in (2,5)| comp(j, i) ), ""); +} + +#[kani::proof] +fn exists_assert_harness() { + let j = kani::any(); + kani::assume(j > 2); + kani::assert(kani::exists!(|i in (2,5)| i < j ), ""); +} + +#[kani::proof] +fn exists_assume_harness() { + let j = kani::any(); + kani::assume(kani::exists!(|i in (2,4)| i == j)); + kani::assert(j == 3 || j == 2, ""); +} + +#[kani::proof] +fn exists_function_harness() { + let j = kani::any(); + kani::assume(j > 2); + kani::assert(kani::exists!(|i in (2,5)| comp(j, i) ), ""); +} diff --git a/tests/kani/SIMD/simd_float_ops_fixme.rs b/tests/kani/SIMD/simd_float_ops.rs similarity index 64% rename from tests/kani/SIMD/simd_float_ops_fixme.rs rename to tests/kani/SIMD/simd_float_ops.rs index 6b53d95d554a..dbb6d77260b6 100644 --- a/tests/kani/SIMD/simd_float_ops_fixme.rs +++ b/tests/kani/SIMD/simd_float_ops.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Ensure we can handle SIMD defined in the standard library -//! FIXME: #![allow(non_camel_case_types)] #![feature(repr_simd, core_intrinsics, portable_simd)] use std::intrinsics::simd::simd_add; @@ -10,7 +9,7 @@ use std::simd::f32x4; #[repr(simd)] #[derive(Clone, PartialEq, kani::Arbitrary)] -pub struct f32x2(f32, f32); +pub struct f32x2([f32; 2]); impl f32x2 { fn as_array(&self) -> &[f32; 2] { @@ -20,16 +19,22 @@ impl f32x2 { #[kani::proof] fn check_sum() { - let a = f32x2(0.0, 0.0); + let a = f32x2([0.0, 0.0]); let b = kani::any::(); - let sum = unsafe { simd_add(a.clone(), b) }; - assert_eq!(sum.as_array(), a.as_array()); + kani::assume(b.as_array()[0].is_normal()); + kani::assume(b.as_array()[1].is_normal()); + let sum = unsafe { simd_add(a.clone(), b.clone()) }; + assert_eq!(sum.as_array(), b.as_array()); } #[kani::proof] fn check_sum_portable() { let a = f32x4::splat(0.0); let b = f32x4::from_array(kani::any()); + kani::assume(b.as_array()[0].is_normal()); + kani::assume(b.as_array()[1].is_normal()); + kani::assume(b.as_array()[2].is_normal()); + kani::assume(b.as_array()[3].is_normal()); // Cannot compare them directly: https://github.com/model-checking/kani/issues/2632 assert_eq!((a + b).as_array(), b.as_array()); } diff --git a/tests/expected/llbc/basic0/expected b/tests/llbc/basic0/expected similarity index 100% rename from tests/expected/llbc/basic0/expected rename to tests/llbc/basic0/expected diff --git a/tests/expected/llbc/basic0/test.rs b/tests/llbc/basic0/test.rs similarity index 100% rename from tests/expected/llbc/basic0/test.rs rename to tests/llbc/basic0/test.rs diff --git a/tests/expected/llbc/basic1/expected b/tests/llbc/basic1/expected similarity index 100% rename from tests/expected/llbc/basic1/expected rename to tests/llbc/basic1/expected diff --git a/tests/expected/llbc/basic1/test.rs b/tests/llbc/basic1/test.rs similarity index 100% rename from tests/expected/llbc/basic1/test.rs rename to tests/llbc/basic1/test.rs diff --git a/tests/expected/llbc/enum/expected b/tests/llbc/enum/expected similarity index 100% rename from tests/expected/llbc/enum/expected rename to tests/llbc/enum/expected diff --git a/tests/expected/llbc/enum/test.rs b/tests/llbc/enum/test.rs similarity index 100% rename from tests/expected/llbc/enum/test.rs rename to tests/llbc/enum/test.rs diff --git a/tests/expected/llbc/generic/expected b/tests/llbc/generic/expected similarity index 100% rename from tests/expected/llbc/generic/expected rename to tests/llbc/generic/expected diff --git a/tests/expected/llbc/generic/test.rs b/tests/llbc/generic/test.rs similarity index 100% rename from tests/expected/llbc/generic/test.rs rename to tests/llbc/generic/test.rs diff --git a/tests/expected/llbc/option/expected b/tests/llbc/option/expected similarity index 100% rename from tests/expected/llbc/option/expected rename to tests/llbc/option/expected diff --git a/tests/expected/llbc/option/test.rs b/tests/llbc/option/test.rs similarity index 100% rename from tests/expected/llbc/option/test.rs rename to tests/llbc/option/test.rs diff --git a/tests/expected/llbc/projection/expected b/tests/llbc/projection/expected similarity index 100% rename from tests/expected/llbc/projection/expected rename to tests/llbc/projection/expected diff --git a/tests/expected/llbc/projection/test.rs b/tests/llbc/projection/test.rs similarity index 100% rename from tests/expected/llbc/projection/test.rs rename to tests/llbc/projection/test.rs diff --git a/tests/expected/llbc/struct/expected b/tests/llbc/struct/expected similarity index 100% rename from tests/expected/llbc/struct/expected rename to tests/llbc/struct/expected diff --git a/tests/expected/llbc/struct/test.rs b/tests/llbc/struct/test.rs similarity index 100% rename from tests/expected/llbc/struct/test.rs rename to tests/llbc/struct/test.rs diff --git a/tests/expected/llbc/traitimpl/expected b/tests/llbc/traitimpl/expected similarity index 100% rename from tests/expected/llbc/traitimpl/expected rename to tests/llbc/traitimpl/expected diff --git a/tests/expected/llbc/traitimpl/test.rs b/tests/llbc/traitimpl/test.rs similarity index 100% rename from tests/expected/llbc/traitimpl/test.rs rename to tests/llbc/traitimpl/test.rs diff --git a/tests/expected/llbc/tuple/expected b/tests/llbc/tuple/expected similarity index 100% rename from tests/expected/llbc/tuple/expected rename to tests/llbc/tuple/expected diff --git a/tests/expected/llbc/tuple/test.rs b/tests/llbc/tuple/test.rs similarity index 100% rename from tests/expected/llbc/tuple/test.rs rename to tests/llbc/tuple/test.rs diff --git a/tests/perf/s2n-quic b/tests/perf/s2n-quic index d0aff821d596..22434aa54178 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit d0aff821d596f279c2fe3af1cfb946174196c257 +Subproject commit 22434aa541781e42924bcf9856cd435250150344 diff --git a/tests/perf/smol_str/src/lib.rs b/tests/perf/smol_str/src/lib.rs index 7fe771c1fc07..01f1881bad03 100644 --- a/tests/perf/smol_str/src/lib.rs +++ b/tests/perf/smol_str/src/lib.rs @@ -5,9 +5,11 @@ //! #[kani::proof] -#[kani::unwind(4)] +#[kani::unwind(13)] fn check_new() { - let data: [char; 3] = kani::any(); - let input: String = data.iter().collect(); + let data: [u8; 12] = kani::any(); + let res = String::from_utf8(data.into()); + kani::assume(res.is_ok()); + let input: String = res.unwrap(); smol_str::SmolStr::new(&input); } diff --git a/tests/script-based-pre/cargo_autoharness_contracts/config.yml b/tests/script-based-pre/cargo_autoharness_contracts/config.yml index 38bf8ee6c38c..8b42bc3cbd18 100644 --- a/tests/script-based-pre/cargo_autoharness_contracts/config.yml +++ b/tests/script-based-pre/cargo_autoharness_contracts/config.yml @@ -2,3 +2,4 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT script: contracts.sh expected: contracts.expected +exit_code: 1 \ No newline at end of file diff --git a/tests/script-based-pre/cargo_autoharness_contracts/contracts.expected b/tests/script-based-pre/cargo_autoharness_contracts/contracts.expected index 7f3b92ffef86..2afedc9e0b0a 100644 --- a/tests/script-based-pre/cargo_autoharness_contracts/contracts.expected +++ b/tests/script-based-pre/cargo_autoharness_contracts/contracts.expected @@ -1,20 +1,23 @@ -Kani generated automatic harnesses for 5 function(s): -+--------------------------------+ -| Selected Function | -+================================+ -| should_fail::max | -|--------------------------------| -| should_pass::div | -|--------------------------------| -| should_pass::has_loop_contract | -|--------------------------------| -| should_pass::has_recursion_gcd | -|--------------------------------| -| should_pass::unchecked_mul | -+--------------------------------+ +Kani generated automatic harnesses for 7 function(s): ++-----------------------------+---------------------------------------------+ +| Crate | Selected Function | ++===========================================================================+ +| cargo_autoharness_contracts | should_fail::max | +|-----------------------------+---------------------------------------------| +| cargo_autoharness_contracts | should_pass::alignment::Alignment | +|-----------------------------+---------------------------------------------| +| cargo_autoharness_contracts | should_pass::alignment::Alignment::as_usize | +|-----------------------------+---------------------------------------------| +| cargo_autoharness_contracts | should_pass::div | +|-----------------------------+---------------------------------------------| +| cargo_autoharness_contracts | should_pass::has_loop_contract | +|-----------------------------+---------------------------------------------| +| cargo_autoharness_contracts | should_pass::has_recursion_gcd | +|-----------------------------+---------------------------------------------| +| cargo_autoharness_contracts | should_pass::unchecked_mul | ++-----------------------------+---------------------------------------------+ Skipped Functions: None. Kani generated automatic harnesses for all functions in the available crate(s). - Autoharness: Checking function should_fail::max's contract against all possible inputs... assertion\ - Status: FAILURE\ @@ -37,23 +40,37 @@ arithmetic_overflow\ - Status: SUCCESS\ - Description: "attempt to compute `unchecked_mul` which would overflow" +Autoharness: Checking function should_pass::alignment::Alignment::as_usize's contract against all possible inputs... + +should_pass::alignment::Alignment::as_usize\ + - Status: SUCCESS\ + - Description: "Rust intrinsic assumption failed" + +should_pass::alignment::Alignment::as_usize\ + - Status: SUCCESS\ + - Description: "|result| result.is_power_of_two()" + Manual Harness Summary: No proof harnesses (functions with #[kani::proof]) were found to verify. Autoharness Summary: -+--------------------------------+-----------------------------+---------------------+ -| Selected Function | Kind of Automatic Harness | Verification Result | -+====================================================================================+ -| should_pass::div | #[kani::proof_for_contract] | Success | -|--------------------------------+-----------------------------+---------------------| -| should_pass::has_loop_contract | #[kani::proof] | Success | -|--------------------------------+-----------------------------+---------------------| -| should_pass::has_recursion_gcd | #[kani::proof_for_contract] | Success | -|--------------------------------+-----------------------------+---------------------| -| should_pass::unchecked_mul | #[kani::proof_for_contract] | Success | -|--------------------------------+-----------------------------+---------------------| -| should_fail::max | #[kani::proof_for_contract] | Failure | -+--------------------------------+-----------------------------+---------------------+ ++-----------------------------+---------------------------------------------+-----------------------------+---------------------+ +| Crate | Selected Function | Kind of Automatic Harness | Verification Result | ++===============================================================================================================================+ +| cargo_autoharness_contracts | should_pass::alignment::Alignment | #[kani::proof] | Success | +|-----------------------------+---------------------------------------------+-----------------------------+---------------------| +| cargo_autoharness_contracts | should_pass::alignment::Alignment::as_usize | #[kani::proof_for_contract] | Success | +|-----------------------------+---------------------------------------------+-----------------------------+---------------------| +| cargo_autoharness_contracts | should_pass::div | #[kani::proof_for_contract] | Success | +|-----------------------------+---------------------------------------------+-----------------------------+---------------------| +| cargo_autoharness_contracts | should_pass::has_loop_contract | #[kani::proof] | Success | +|-----------------------------+---------------------------------------------+-----------------------------+---------------------| +| cargo_autoharness_contracts | should_pass::has_recursion_gcd | #[kani::proof_for_contract] | Success | +|-----------------------------+---------------------------------------------+-----------------------------+---------------------| +| cargo_autoharness_contracts | should_pass::unchecked_mul | #[kani::proof_for_contract] | Success | +|-----------------------------+---------------------------------------------+-----------------------------+---------------------| +| cargo_autoharness_contracts | should_fail::max | #[kani::proof_for_contract] | Failure | ++-----------------------------+---------------------------------------------+-----------------------------+---------------------+ Note that `kani autoharness` sets default --harness-timeout of 60s and --default-unwind of 20. If verification failed because of timing out or too low of an unwinding bound, try passing larger values for these arguments (or, if possible, writing a loop contract). -Complete - 4 successfully verified functions, 1 failures, 5 total. +Complete - 6 successfully verified functions, 1 failures, 7 total. diff --git a/tests/script-based-pre/cargo_autoharness_contracts/contracts.sh b/tests/script-based-pre/cargo_autoharness_contracts/contracts.sh index d4c93fcd828d..db4a99f8be09 100755 --- a/tests/script-based-pre/cargo_autoharness_contracts/contracts.sh +++ b/tests/script-based-pre/cargo_autoharness_contracts/contracts.sh @@ -2,8 +2,4 @@ # Copyright Kani Contributors # SPDX-License-Identifier: Apache-2.0 OR MIT -cargo kani autoharness -Z autoharness -Z function-contracts -Z loop-contracts -# We expect verification to fail, so the above command will produce an exit status of 1 -# However, we don't want the test to fail because of that exit status; we only want it to fail if the expected file doesn't match -# So, exit with a status code of 0 explicitly. -exit 0; +cargo kani autoharness -Z autoharness diff --git a/tests/script-based-pre/cargo_autoharness_contracts/src/lib.rs b/tests/script-based-pre/cargo_autoharness_contracts/src/lib.rs index c66b3dab4fbe..39bd828c682d 100644 --- a/tests/script-based-pre/cargo_autoharness_contracts/src/lib.rs +++ b/tests/script-based-pre/cargo_autoharness_contracts/src/lib.rs @@ -46,6 +46,90 @@ mod should_pass { unsafe fn unchecked_mul(left: u8, rhs: u8) -> u8 { unsafe { left.unchecked_mul(rhs) } } + + // Check that we can create automatic harnesses for more complex situtations, i.e., + // functions with contracts that reference nested data structures that derive Arbitrary. + mod alignment { + // FIXME: Note that since this is a tuple struct, we generate an extra harness for the Alignment constructor, + // c.f. https://github.com/model-checking/kani/issues/3832#issuecomment-2730580836 + #[derive(kani::Arbitrary)] + pub struct Alignment(AlignmentEnum); + + #[derive(kani::Arbitrary)] + enum AlignmentEnum { + _Align1Shl0 = 1 << 0, + _Align1Shl1 = 1 << 1, + _Align1Shl2 = 1 << 2, + _Align1Shl3 = 1 << 3, + _Align1Shl4 = 1 << 4, + _Align1Shl5 = 1 << 5, + _Align1Shl6 = 1 << 6, + _Align1Shl7 = 1 << 7, + _Align1Shl8 = 1 << 8, + _Align1Shl9 = 1 << 9, + _Align1Shl10 = 1 << 10, + _Align1Shl11 = 1 << 11, + _Align1Shl12 = 1 << 12, + _Align1Shl13 = 1 << 13, + _Align1Shl14 = 1 << 14, + _Align1Shl15 = 1 << 15, + _Align1Shl16 = 1 << 16, + _Align1Shl17 = 1 << 17, + _Align1Shl18 = 1 << 18, + _Align1Shl19 = 1 << 19, + _Align1Shl20 = 1 << 20, + _Align1Shl21 = 1 << 21, + _Align1Shl22 = 1 << 22, + _Align1Shl23 = 1 << 23, + _Align1Shl24 = 1 << 24, + _Align1Shl25 = 1 << 25, + _Align1Shl26 = 1 << 26, + _Align1Shl27 = 1 << 27, + _Align1Shl28 = 1 << 28, + _Align1Shl29 = 1 << 29, + _Align1Shl30 = 1 << 30, + _Align1Shl31 = 1 << 31, + _Align1Shl32 = 1 << 32, + _Align1Shl33 = 1 << 33, + _Align1Shl34 = 1 << 34, + _Align1Shl35 = 1 << 35, + _Align1Shl36 = 1 << 36, + _Align1Shl37 = 1 << 37, + _Align1Shl38 = 1 << 38, + _Align1Shl39 = 1 << 39, + _Align1Shl40 = 1 << 40, + _Align1Shl41 = 1 << 41, + _Align1Shl42 = 1 << 42, + _Align1Shl43 = 1 << 43, + _Align1Shl44 = 1 << 44, + _Align1Shl45 = 1 << 45, + _Align1Shl46 = 1 << 46, + _Align1Shl47 = 1 << 47, + _Align1Shl48 = 1 << 48, + _Align1Shl49 = 1 << 49, + _Align1Shl50 = 1 << 50, + _Align1Shl51 = 1 << 51, + _Align1Shl52 = 1 << 52, + _Align1Shl53 = 1 << 53, + _Align1Shl54 = 1 << 54, + _Align1Shl55 = 1 << 55, + _Align1Shl56 = 1 << 56, + _Align1Shl57 = 1 << 57, + _Align1Shl58 = 1 << 58, + _Align1Shl59 = 1 << 59, + _Align1Shl60 = 1 << 60, + _Align1Shl61 = 1 << 61, + _Align1Shl62 = 1 << 62, + _Align1Shl63 = 1 << 63, + } + + impl Alignment { + #[kani::ensures(|result| result.is_power_of_two())] + pub fn as_usize(self) -> usize { + self.0 as usize + } + } + } } mod should_fail { diff --git a/tests/script-based-pre/cargo_autoharness_dependencies/dependencies.expected b/tests/script-based-pre/cargo_autoharness_dependencies/dependencies.expected index 553573b70d2b..aac45d7733d6 100644 --- a/tests/script-based-pre/cargo_autoharness_dependencies/dependencies.expected +++ b/tests/script-based-pre/cargo_autoharness_dependencies/dependencies.expected @@ -1,9 +1,9 @@ Kani generated automatic harnesses for 1 function(s): -+-------------------+ -| Selected Function | -+===================+ -| yes_harness | -+-------------------+ ++--------------------------------+-------------------+ +| Crate | Selected Function | ++====================================================+ +| cargo_autoharness_dependencies | yes_harness | ++--------------------------------+-------------------+ Skipped Functions: None. Kani generated automatic harnesses for all functions in the available crate(s). @@ -14,9 +14,9 @@ Manual Harness Summary: No proof harnesses (functions with #[kani::proof]) were found to verify. Autoharness Summary: -+-------------------+---------------------------+---------------------+ -| Selected Function | Kind of Automatic Harness | Verification Result | -+=====================================================================+ -| yes_harness | #[kani::proof] | Success | -+-------------------+---------------------------+---------------------+ ++--------------------------------+-------------------+---------------------------+---------------------+ +| Crate | Selected Function | Kind of Automatic Harness | Verification Result | ++======================================================================================================+ +| cargo_autoharness_dependencies | yes_harness | #[kani::proof] | Success | ++--------------------------------+-------------------+---------------------------+---------------------+ Complete - 1 successfully verified functions, 0 failures, 1 total. diff --git a/tests/script-based-pre/cargo_autoharness_dependencies/dependencies.sh b/tests/script-based-pre/cargo_autoharness_dependencies/dependencies.sh index 34e177be9134..0e0f76e871ea 100755 --- a/tests/script-based-pre/cargo_autoharness_dependencies/dependencies.sh +++ b/tests/script-based-pre/cargo_autoharness_dependencies/dependencies.sh @@ -2,8 +2,4 @@ # Copyright Kani Contributors # SPDX-License-Identifier: Apache-2.0 OR MIT -cargo kani autoharness -Z autoharness -# We expect verification to fail, so the above command will produce an exit status of 1 -# However, we don't want the test to fail because of that exit status; we only want it to fail if the expected file doesn't match -# So, exit with a status code of 0 explicitly. -exit 0; +cargo kani autoharness -Z autoharness \ No newline at end of file diff --git a/tests/script-based-pre/cargo_autoharness_exclude/exclude.expected b/tests/script-based-pre/cargo_autoharness_exclude/exclude.expected index 1b0085fbcf28..9c006d210262 100644 --- a/tests/script-based-pre/cargo_autoharness_exclude/exclude.expected +++ b/tests/script-based-pre/cargo_autoharness_exclude/exclude.expected @@ -1,20 +1,19 @@ Kani generated automatic harnesses for 1 function(s): -+-------------------+ -| Selected Function | -+===================+ -| include::simple | -+-------------------+ ++---------------------------+-------------------+ +| Crate | Selected Function | ++===============================================+ +| cargo_autoharness_include | include::simple | ++---------------------------+-------------------+ Kani did not generate automatic harnesses for 2 function(s). If you believe that the provided reason is incorrect and Kani should have generated an automatic harness, please comment on this issue: https://github.com/model-checking/kani/issues/3832 -+------------------+--------------------------------+ -| Skipped Function | Reason for Skipping | -+===================================================+ -| excluded::simple | Did not match provided filters | -|------------------+--------------------------------| -| include::generic | Generic Function | -+------------------+--------------------------------+ - ++---------------------------+------------------+--------------------------------+ +| Crate | Skipped Function | Reason for Skipping | ++===============================================================================+ +| cargo_autoharness_include | excluded::simple | Did not match provided filters | +|---------------------------+------------------+--------------------------------| +| cargo_autoharness_include | include::generic | Generic Function | ++---------------------------+------------------+--------------------------------+ Autoharness: Checking function include::simple against all possible inputs... VERIFICATION:- SUCCESSFUL @@ -22,9 +21,9 @@ Manual Harness Summary: No proof harnesses (functions with #[kani::proof]) were found to verify. Autoharness Summary: -+-------------------+---------------------------+---------------------+ -| Selected Function | Kind of Automatic Harness | Verification Result | -+=====================================================================+ -| include::simple | #[kani::proof] | Success | -+-------------------+---------------------------+---------------------+ ++---------------------------+-------------------+---------------------------+---------------------+ +| Crate | Selected Function | Kind of Automatic Harness | Verification Result | ++=================================================================================================+ +| cargo_autoharness_include | include::simple | #[kani::proof] | Success | ++---------------------------+-------------------+---------------------------+---------------------+ Complete - 1 successfully verified functions, 0 failures, 1 total. diff --git a/tests/script-based-pre/cargo_autoharness_exclude/exclude.sh b/tests/script-based-pre/cargo_autoharness_exclude/exclude.sh index 481a8b73f5ee..a18bd9ad4e29 100755 --- a/tests/script-based-pre/cargo_autoharness_exclude/exclude.sh +++ b/tests/script-based-pre/cargo_autoharness_exclude/exclude.sh @@ -2,4 +2,4 @@ # Copyright Kani Contributors # SPDX-License-Identifier: Apache-2.0 OR MIT -cargo kani autoharness -Z autoharness --exclude-function exclude +cargo kani autoharness -Z autoharness --exclude-pattern exclude diff --git a/tests/script-based-pre/cargo_autoharness_exclude/src/lib.rs b/tests/script-based-pre/cargo_autoharness_exclude/src/lib.rs index 7757239126c0..39676ed697ee 100644 --- a/tests/script-based-pre/cargo_autoharness_exclude/src/lib.rs +++ b/tests/script-based-pre/cargo_autoharness_exclude/src/lib.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // // Test that the automatic harness generation feature selects functions correctly -// when --exclude-function is provided. +// when --exclude-pattern is provided. mod include { fn simple(x: u8, _y: u16) -> u8 { diff --git a/tests/script-based-pre/cargo_autoharness_filter/filter.expected b/tests/script-based-pre/cargo_autoharness_filter/filter.expected index a7bc89c2116c..f00cd9c79404 100644 --- a/tests/script-based-pre/cargo_autoharness_filter/filter.expected +++ b/tests/script-based-pre/cargo_autoharness_filter/filter.expected @@ -1,113 +1,113 @@ Kani generated automatic harnesses for 42 function(s): -+----------------------------------------------+ -| Selected Function | -+==============================================+ -| yes_harness::empty_body | -|----------------------------------------------| -| yes_harness::f_array | -|----------------------------------------------| -| yes_harness::f_bool | -|----------------------------------------------| -| yes_harness::f_char | -|----------------------------------------------| -| yes_harness::f_derives_arbitrary | -|----------------------------------------------| -| yes_harness::f_f128 | -|----------------------------------------------| -| yes_harness::f_f16 | -|----------------------------------------------| -| yes_harness::f_f32 | -|----------------------------------------------| -| yes_harness::f_f64 | -|----------------------------------------------| -| yes_harness::f_i128 | -|----------------------------------------------| -| yes_harness::f_i16 | -|----------------------------------------------| -| yes_harness::f_i32 | -|----------------------------------------------| -| yes_harness::f_i64 | -|----------------------------------------------| -| yes_harness::f_i8 | -|----------------------------------------------| -| yes_harness::f_isize | -|----------------------------------------------| -| yes_harness::f_manually_implements_arbitrary | -|----------------------------------------------| -| yes_harness::f_maybe_uninit | -|----------------------------------------------| -| yes_harness::f_multiple_args | -|----------------------------------------------| -| yes_harness::f_nonzero_i128 | -|----------------------------------------------| -| yes_harness::f_nonzero_i16 | -|----------------------------------------------| -| yes_harness::f_nonzero_i32 | -|----------------------------------------------| -| yes_harness::f_nonzero_i64 | -|----------------------------------------------| -| yes_harness::f_nonzero_i8 | -|----------------------------------------------| -| yes_harness::f_nonzero_isize | -|----------------------------------------------| -| yes_harness::f_nonzero_u128 | -|----------------------------------------------| -| yes_harness::f_nonzero_u16 | -|----------------------------------------------| -| yes_harness::f_nonzero_u32 | -|----------------------------------------------| -| yes_harness::f_nonzero_u64 | -|----------------------------------------------| -| yes_harness::f_nonzero_u8 | -|----------------------------------------------| -| yes_harness::f_nonzero_usize | -|----------------------------------------------| -| yes_harness::f_option | -|----------------------------------------------| -| yes_harness::f_phantom_data | -|----------------------------------------------| -| yes_harness::f_phantom_pinned | -|----------------------------------------------| -| yes_harness::f_result | -|----------------------------------------------| -| yes_harness::f_tuple | -|----------------------------------------------| -| yes_harness::f_u128 | -|----------------------------------------------| -| yes_harness::f_u16 | -|----------------------------------------------| -| yes_harness::f_u32 | -|----------------------------------------------| -| yes_harness::f_u64 | -|----------------------------------------------| -| yes_harness::f_u8 | -|----------------------------------------------| -| yes_harness::f_unsupported_return_type | -|----------------------------------------------| -| yes_harness::f_usize | -+----------------------------------------------+ ++--------------------------+----------------------------------------------+ +| Crate | Selected Function | ++=========================================================================+ +| cargo_autoharness_filter | yes_harness::empty_body | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_array | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_bool | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_char | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_derives_arbitrary | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_f128 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_f16 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_f32 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_f64 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_i128 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_i16 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_i32 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_i64 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_i8 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_isize | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_manually_implements_arbitrary | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_maybe_uninit | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_multiple_args | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_i128 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_i16 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_i32 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_i64 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_i8 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_isize | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_u128 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_u16 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_u32 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_u64 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_u8 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_usize | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_option | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_phantom_data | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_phantom_pinned | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_result | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_tuple | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_u128 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_u16 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_u32 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_u64 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_u8 | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_unsupported_return_type | +|--------------------------+----------------------------------------------| +| cargo_autoharness_filter | yes_harness::f_usize | ++--------------------------+----------------------------------------------+ Kani did not generate automatic harnesses for 8 function(s). If you believe that the provided reason is incorrect and Kani should have generated an automatic harness, please comment on this issue: https://github.com/model-checking/kani/issues/3832 -+----------------------------------------+------------------------------------------------------------------------------+ -| Skipped Function | Reason for Skipping | -+=======================================================================================================================+ -| no_harness::doesnt_implement_arbitrary | Missing Arbitrary implementation for argument(s) x: DoesntImplementArbitrary | -|----------------------------------------+------------------------------------------------------------------------------| -| no_harness::unsupported_const_pointer | Missing Arbitrary implementation for argument(s) _y: *const i32 | -|----------------------------------------+------------------------------------------------------------------------------| -| no_harness::unsupported_generic | Generic Function | -|----------------------------------------+------------------------------------------------------------------------------| -| no_harness::unsupported_mut_pointer | Missing Arbitrary implementation for argument(s) _y: *mut i32 | -|----------------------------------------+------------------------------------------------------------------------------| -| no_harness::unsupported_no_arg_name | Missing Arbitrary implementation for argument(s) _: &() | -|----------------------------------------+------------------------------------------------------------------------------| -| no_harness::unsupported_ref | Missing Arbitrary implementation for argument(s) _y: &i32 | -|----------------------------------------+------------------------------------------------------------------------------| -| no_harness::unsupported_slice | Missing Arbitrary implementation for argument(s) _y: &[u8] | -|----------------------------------------+------------------------------------------------------------------------------| -| no_harness::unsupported_vec | Missing Arbitrary implementation for argument(s) _y: std::vec::Vec | -+----------------------------------------+------------------------------------------------------------------------------+ ++--------------------------+----------------------------------------+------------------------------------------------------------------------------+ +| Crate | Skipped Function | Reason for Skipping | ++==================================================================================================================================================+ +| cargo_autoharness_filter | no_harness::doesnt_implement_arbitrary | Missing Arbitrary implementation for argument(s) x: DoesntImplementArbitrary | +|--------------------------+----------------------------------------+------------------------------------------------------------------------------| +| cargo_autoharness_filter | no_harness::unsupported_const_pointer | Missing Arbitrary implementation for argument(s) _y: *const i32 | +|--------------------------+----------------------------------------+------------------------------------------------------------------------------| +| cargo_autoharness_filter | no_harness::unsupported_generic | Generic Function | +|--------------------------+----------------------------------------+------------------------------------------------------------------------------| +| cargo_autoharness_filter | no_harness::unsupported_mut_pointer | Missing Arbitrary implementation for argument(s) _y: *mut i32 | +|--------------------------+----------------------------------------+------------------------------------------------------------------------------| +| cargo_autoharness_filter | no_harness::unsupported_no_arg_name | Missing Arbitrary implementation for argument(s) _: &() | +|--------------------------+----------------------------------------+------------------------------------------------------------------------------| +| cargo_autoharness_filter | no_harness::unsupported_ref | Missing Arbitrary implementation for argument(s) _y: &i32 | +|--------------------------+----------------------------------------+------------------------------------------------------------------------------| +| cargo_autoharness_filter | no_harness::unsupported_slice | Missing Arbitrary implementation for argument(s) _y: &[u8] | +|--------------------------+----------------------------------------+------------------------------------------------------------------------------| +| cargo_autoharness_filter | no_harness::unsupported_vec | Missing Arbitrary implementation for argument(s) _y: std::vec::Vec | ++--------------------------+----------------------------------------+------------------------------------------------------------------------------+ Autoharness: Checking function yes_harness::f_tuple against all possible inputs... Autoharness: Checking function yes_harness::f_maybe_uninit against all possible inputs... @@ -156,91 +156,91 @@ Manual Harness Summary: No proof harnesses (functions with #[kani::proof]) were found to verify. Autoharness Summary: -+----------------------------------------------+---------------------------+---------------------+ -| Selected Function | Kind of Automatic Harness | Verification Result | -+================================================================================================+ -| yes_harness::empty_body | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_array | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_bool | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_char | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_derives_arbitrary | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_f128 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_f16 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_f32 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_f64 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_i128 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_i16 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_i32 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_i64 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_i8 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_isize | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_manually_implements_arbitrary | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_maybe_uninit | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_multiple_args | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_nonzero_i128 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_nonzero_i16 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_nonzero_i32 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_nonzero_i64 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_nonzero_i8 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_nonzero_isize | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_nonzero_u128 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_nonzero_u16 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_nonzero_u32 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_nonzero_u64 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_nonzero_u8 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_nonzero_usize | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_option | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_phantom_data | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_phantom_pinned | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_result | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_tuple | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_u128 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_u16 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_u32 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_u64 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_u8 | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_unsupported_return_type | #[kani::proof] | Success | -|----------------------------------------------+---------------------------+---------------------| -| yes_harness::f_usize | #[kani::proof] | Success | -+----------------------------------------------+---------------------------+---------------------+ ++--------------------------+----------------------------------------------+---------------------------+---------------------+ +| Crate | Selected Function | Kind of Automatic Harness | Verification Result | ++===========================================================================================================================+ +| cargo_autoharness_filter | yes_harness::empty_body | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_array | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_bool | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_char | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_derives_arbitrary | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_f128 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_f16 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_f32 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_f64 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_i128 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_i16 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_i32 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_i64 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_i8 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_isize | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_manually_implements_arbitrary | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_maybe_uninit | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_multiple_args | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_i128 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_i16 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_i32 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_i64 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_i8 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_isize | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_u128 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_u16 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_u32 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_u64 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_u8 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_nonzero_usize | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_option | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_phantom_data | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_phantom_pinned | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_result | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_tuple | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_u128 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_u16 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_u32 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_u64 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_u8 | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_unsupported_return_type | #[kani::proof] | Success | +|--------------------------+----------------------------------------------+---------------------------+---------------------| +| cargo_autoharness_filter | yes_harness::f_usize | #[kani::proof] | Success | ++--------------------------+----------------------------------------------+---------------------------+---------------------+ Complete - 42 successfully verified functions, 0 failures, 42 total. diff --git a/tests/script-based-pre/cargo_autoharness_harnesses_fail/config.yml b/tests/script-based-pre/cargo_autoharness_harnesses_fail/config.yml index f5b3400d03bf..a165769407e4 100644 --- a/tests/script-based-pre/cargo_autoharness_harnesses_fail/config.yml +++ b/tests/script-based-pre/cargo_autoharness_harnesses_fail/config.yml @@ -2,3 +2,4 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT script: harnesses_fail.sh expected: harnesses_fail.expected +exit_code: 1 \ No newline at end of file diff --git a/tests/script-based-pre/cargo_autoharness_harnesses_fail/harnesses_fail.expected b/tests/script-based-pre/cargo_autoharness_harnesses_fail/harnesses_fail.expected index d9969a0f38c9..b139d5777928 100644 --- a/tests/script-based-pre/cargo_autoharness_harnesses_fail/harnesses_fail.expected +++ b/tests/script-based-pre/cargo_autoharness_harnesses_fail/harnesses_fail.expected @@ -1,17 +1,17 @@ Kani generated automatic harnesses for 5 function(s): -+-------------------------+ -| Selected Function | -+=========================+ -| integer_overflow | -|-------------------------| -| oob_safe_array_access | -|-------------------------| -| oob_unsafe_array_access | -|-------------------------| -| panic | -|-------------------------| -| unchecked_mul | -+-------------------------+ ++----------------------------------+-------------------------+ +| Crate | Selected Function | ++============================================================+ +| cargo_autoharness_harnesses_fail | integer_overflow | +|----------------------------------+-------------------------| +| cargo_autoharness_harnesses_fail | oob_safe_array_access | +|----------------------------------+-------------------------| +| cargo_autoharness_harnesses_fail | oob_unsafe_array_access | +|----------------------------------+-------------------------| +| cargo_autoharness_harnesses_fail | panic | +|----------------------------------+-------------------------| +| cargo_autoharness_harnesses_fail | unchecked_mul | ++----------------------------------+-------------------------+ Skipped Functions: None. Kani generated automatic harnesses for all functions in the available crate(s). @@ -73,19 +73,19 @@ Verification failed for - oob_safe_array_access_harness Complete - 0 successfully verified harnesses, 5 failures, 5 total. Autoharness Summary: -+-------------------------+---------------------------+---------------------+ -| Selected Function | Kind of Automatic Harness | Verification Result | -+===========================================================================+ -| integer_overflow | #[kani::proof] | Failure | -|-------------------------+---------------------------+---------------------| -| oob_safe_array_access | #[kani::proof] | Failure | -|-------------------------+---------------------------+---------------------| -| oob_unsafe_array_access | #[kani::proof] | Failure | -|-------------------------+---------------------------+---------------------| -| panic | #[kani::proof] | Failure | -|-------------------------+---------------------------+---------------------| -| unchecked_mul | #[kani::proof] | Failure | -+-------------------------+---------------------------+---------------------+ ++----------------------------------+-------------------------+---------------------------+---------------------+ +| Crate | Selected Function | Kind of Automatic Harness | Verification Result | ++==============================================================================================================+ +| cargo_autoharness_harnesses_fail | integer_overflow | #[kani::proof] | Failure | +|----------------------------------+-------------------------+---------------------------+---------------------| +| cargo_autoharness_harnesses_fail | oob_safe_array_access | #[kani::proof] | Failure | +|----------------------------------+-------------------------+---------------------------+---------------------| +| cargo_autoharness_harnesses_fail | oob_unsafe_array_access | #[kani::proof] | Failure | +|----------------------------------+-------------------------+---------------------------+---------------------| +| cargo_autoharness_harnesses_fail | panic | #[kani::proof] | Failure | +|----------------------------------+-------------------------+---------------------------+---------------------| +| cargo_autoharness_harnesses_fail | unchecked_mul | #[kani::proof] | Failure | ++----------------------------------+-------------------------+---------------------------+---------------------+ Note that `kani autoharness` sets default --harness-timeout of 60s and --default-unwind of 20. If verification failed because of timing out or too low of an unwinding bound, try passing larger values for these arguments (or, if possible, writing a loop contract). Complete - 0 successfully verified functions, 5 failures, 5 total. diff --git a/tests/script-based-pre/cargo_autoharness_harnesses_fail/harnesses_fail.sh b/tests/script-based-pre/cargo_autoharness_harnesses_fail/harnesses_fail.sh index 34e177be9134..0e0f76e871ea 100755 --- a/tests/script-based-pre/cargo_autoharness_harnesses_fail/harnesses_fail.sh +++ b/tests/script-based-pre/cargo_autoharness_harnesses_fail/harnesses_fail.sh @@ -2,8 +2,4 @@ # Copyright Kani Contributors # SPDX-License-Identifier: Apache-2.0 OR MIT -cargo kani autoharness -Z autoharness -# We expect verification to fail, so the above command will produce an exit status of 1 -# However, we don't want the test to fail because of that exit status; we only want it to fail if the expected file doesn't match -# So, exit with a status code of 0 explicitly. -exit 0; +cargo kani autoharness -Z autoharness \ No newline at end of file diff --git a/tests/script-based-pre/cargo_autoharness_include/include.expected b/tests/script-based-pre/cargo_autoharness_include/include.expected index 1b0085fbcf28..9c006d210262 100644 --- a/tests/script-based-pre/cargo_autoharness_include/include.expected +++ b/tests/script-based-pre/cargo_autoharness_include/include.expected @@ -1,20 +1,19 @@ Kani generated automatic harnesses for 1 function(s): -+-------------------+ -| Selected Function | -+===================+ -| include::simple | -+-------------------+ ++---------------------------+-------------------+ +| Crate | Selected Function | ++===============================================+ +| cargo_autoharness_include | include::simple | ++---------------------------+-------------------+ Kani did not generate automatic harnesses for 2 function(s). If you believe that the provided reason is incorrect and Kani should have generated an automatic harness, please comment on this issue: https://github.com/model-checking/kani/issues/3832 -+------------------+--------------------------------+ -| Skipped Function | Reason for Skipping | -+===================================================+ -| excluded::simple | Did not match provided filters | -|------------------+--------------------------------| -| include::generic | Generic Function | -+------------------+--------------------------------+ - ++---------------------------+------------------+--------------------------------+ +| Crate | Skipped Function | Reason for Skipping | ++===============================================================================+ +| cargo_autoharness_include | excluded::simple | Did not match provided filters | +|---------------------------+------------------+--------------------------------| +| cargo_autoharness_include | include::generic | Generic Function | ++---------------------------+------------------+--------------------------------+ Autoharness: Checking function include::simple against all possible inputs... VERIFICATION:- SUCCESSFUL @@ -22,9 +21,9 @@ Manual Harness Summary: No proof harnesses (functions with #[kani::proof]) were found to verify. Autoharness Summary: -+-------------------+---------------------------+---------------------+ -| Selected Function | Kind of Automatic Harness | Verification Result | -+=====================================================================+ -| include::simple | #[kani::proof] | Success | -+-------------------+---------------------------+---------------------+ ++---------------------------+-------------------+---------------------------+---------------------+ +| Crate | Selected Function | Kind of Automatic Harness | Verification Result | ++=================================================================================================+ +| cargo_autoharness_include | include::simple | #[kani::proof] | Success | ++---------------------------+-------------------+---------------------------+---------------------+ Complete - 1 successfully verified functions, 0 failures, 1 total. diff --git a/tests/script-based-pre/cargo_autoharness_include/include.sh b/tests/script-based-pre/cargo_autoharness_include/include.sh index 7879013c5717..a731584349bf 100755 --- a/tests/script-based-pre/cargo_autoharness_include/include.sh +++ b/tests/script-based-pre/cargo_autoharness_include/include.sh @@ -2,4 +2,4 @@ # Copyright Kani Contributors # SPDX-License-Identifier: Apache-2.0 OR MIT -cargo kani autoharness -Z autoharness --include-function include +cargo kani autoharness -Z autoharness --include-pattern cargo_autoharness_include::include diff --git a/tests/script-based-pre/cargo_autoharness_include/src/lib.rs b/tests/script-based-pre/cargo_autoharness_include/src/lib.rs index 68b30bf7afe8..135f86f76874 100644 --- a/tests/script-based-pre/cargo_autoharness_include/src/lib.rs +++ b/tests/script-based-pre/cargo_autoharness_include/src/lib.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // // Test that the automatic harness generation feature selects functions correctly -// when --include-function is provided. +// when --include-pattern is provided. // Each function inside this module matches the filter. mod include { diff --git a/tests/script-based-pre/cargo_autoharness_list/list.expected b/tests/script-based-pre/cargo_autoharness_list/list.expected index 4c9feb73d184..5837278d0f00 100644 --- a/tests/script-based-pre/cargo_autoharness_list/list.expected +++ b/tests/script-based-pre/cargo_autoharness_list/list.expected @@ -1,26 +1,32 @@ Kani generated automatic harnesses for 3 function(s): -+---------------------------+ -| Selected Function | -+===========================+ -| f_u8 | -|---------------------------| -| has_recursion_gcd | -|---------------------------| -| verify::has_recursion_gcd | -+---------------------------+ ++------------------------+---------------------------+ +| Crate | Selected Function | ++====================================================+ +| cargo_autoharness_list | f_u8 | +|------------------------+---------------------------| +| cargo_autoharness_list | has_recursion_gcd | +|------------------------+---------------------------| +| cargo_autoharness_list | verify::has_recursion_gcd | ++------------------------+---------------------------+ Skipped Functions: None. Kani generated automatic harnesses for all functions in the available crate(s). Contracts: -+-------+---------------------------+-----------------------------------------------------------------------------+ -| | Function | Contract Harnesses (#[kani::proof_for_contract]) | -+=================================================================================================================+ -| | has_recursion_gcd | my_harness, my_harness_2, kani::internal::automatic_harness | -|-------+---------------------------+-----------------------------------------------------------------------------| -| | verify::has_recursion_gcd | verify::my_harness, verify::my_harness_2, kani::internal::automatic_harness | -|-------+---------------------------+-----------------------------------------------------------------------------| -| Total | 2 | 6 | -+-------+---------------------------+-----------------------------------------------------------------------------+ ++-------+------------------------+---------------------------+-----------------------------------------------------------------------------+ +| | Crate | Function | Contract Harnesses (#[kani::proof_for_contract]) | ++==========================================================================================================================================+ +| | cargo_autoharness_list | has_recursion_gcd | my_harness, my_harness_2, kani::internal::automatic_harness | +|-------+------------------------+---------------------------+-----------------------------------------------------------------------------| +| | cargo_autoharness_list | verify::has_recursion_gcd | verify::my_harness, verify::my_harness_2, kani::internal::automatic_harness | +|-------+------------------------+---------------------------+-----------------------------------------------------------------------------| +| Total | | 2 | 6 | ++-------+------------------------+---------------------------+-----------------------------------------------------------------------------+ Standard Harnesses (#[kani::proof]): -1. f_u8 ++-------+------------------------+---------+ +| | Crate | Harness | ++==========================================+ +| | cargo_autoharness_list | f_u8 | +|-------+------------------------+---------| +| Total | | 1 | ++-------+------------------------+---------+ diff --git a/tests/script-based-pre/cargo_autoharness_list/list.sh b/tests/script-based-pre/cargo_autoharness_list/list.sh index 7deb9a0667cc..bf02eeb0276b 100755 --- a/tests/script-based-pre/cargo_autoharness_list/list.sh +++ b/tests/script-based-pre/cargo_autoharness_list/list.sh @@ -2,4 +2,4 @@ # Copyright Kani Contributors # SPDX-License-Identifier: Apache-2.0 OR MIT -cargo kani autoharness -Z autoharness --list -Z list -Z function-contracts +cargo kani autoharness -Z autoharness --list -Z list diff --git a/tests/script-based-pre/cargo_autoharness_termination_timeout/config.yml b/tests/script-based-pre/cargo_autoharness_termination_timeout/config.yml index b6fab7133fd6..6098e8a6cdf3 100644 --- a/tests/script-based-pre/cargo_autoharness_termination_timeout/config.yml +++ b/tests/script-based-pre/cargo_autoharness_termination_timeout/config.yml @@ -2,3 +2,4 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT script: termination_timeout.sh expected: termination_timeout.expected +exit_code: 1 \ No newline at end of file diff --git a/tests/script-based-pre/cargo_autoharness_termination_timeout/termination_timeout.expected b/tests/script-based-pre/cargo_autoharness_termination_timeout/termination_timeout.expected index 1729bc2b823c..26fd6606f31a 100644 --- a/tests/script-based-pre/cargo_autoharness_termination_timeout/termination_timeout.expected +++ b/tests/script-based-pre/cargo_autoharness_termination_timeout/termination_timeout.expected @@ -1,9 +1,9 @@ Kani generated automatic harnesses for 1 function(s): -+-----------------------+ -| Selected Function | -+=======================+ -| check_harness_timeout | -+-----------------------+ ++---------------------------------------+-----------------------+ +| Crate | Selected Function | ++===============================================================+ +| cargo_autoharness_termination_timeout | check_harness_timeout | ++---------------------------------------+-----------------------+ Skipped Functions: None. Kani generated automatic harnesses for all functions in the available crate(s). Autoharness: Checking function check_harness_timeout against all possible inputs... @@ -15,10 +15,10 @@ Manual Harness Summary: No proof harnesses (functions with #[kani::proof]) were found to verify. Autoharness Summary: -+-----------------------+---------------------------+---------------------+ -| Selected Function | Kind of Automatic Harness | Verification Result | -+=========================================================================+ -| check_harness_timeout | #[kani::proof] | Failure | -+-----------------------+---------------------------+---------------------+ ++---------------------------------------+-----------------------+---------------------------+---------------------+ +| Crate | Selected Function | Kind of Automatic Harness | Verification Result | ++=================================================================================================================+ +| cargo_autoharness_termination_timeout | check_harness_timeout | #[kani::proof] | Failure | ++---------------------------------------+-----------------------+---------------------------+---------------------+ Note that `kani autoharness` sets default --harness-timeout of 60s and --default-unwind of 20. If verification failed because of timing out or too low of an unwinding bound, try passing larger values for these arguments (or, if possible, writing a loop contract). diff --git a/tests/script-based-pre/cargo_autoharness_termination_timeout/termination_timeout.sh b/tests/script-based-pre/cargo_autoharness_termination_timeout/termination_timeout.sh index 65f949b65ad3..db4a99f8be09 100755 --- a/tests/script-based-pre/cargo_autoharness_termination_timeout/termination_timeout.sh +++ b/tests/script-based-pre/cargo_autoharness_termination_timeout/termination_timeout.sh @@ -2,4 +2,4 @@ # Copyright Kani Contributors # SPDX-License-Identifier: Apache-2.0 OR MIT -cargo kani autoharness -Z autoharness -Z function-contracts +cargo kani autoharness -Z autoharness diff --git a/tests/script-based-pre/cargo_autoharness_termination_unwind/config.yml b/tests/script-based-pre/cargo_autoharness_termination_unwind/config.yml index 148335b104cc..9a1a7065a88f 100644 --- a/tests/script-based-pre/cargo_autoharness_termination_unwind/config.yml +++ b/tests/script-based-pre/cargo_autoharness_termination_unwind/config.yml @@ -2,3 +2,4 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT script: termination_unwind.sh expected: termination_unwind.expected +exit_code: 1 \ No newline at end of file diff --git a/tests/script-based-pre/cargo_autoharness_termination_unwind/termination_unwind.expected b/tests/script-based-pre/cargo_autoharness_termination_unwind/termination_unwind.expected index 1e198bb02988..6d0c229e436a 100644 --- a/tests/script-based-pre/cargo_autoharness_termination_unwind/termination_unwind.expected +++ b/tests/script-based-pre/cargo_autoharness_termination_unwind/termination_unwind.expected @@ -1,11 +1,11 @@ Kani generated automatic harnesses for 2 function(s): -+-------------------+ -| Selected Function | -+===================+ -| gcd_recursion | -|-------------------| -| infinite_loop | -+-------------------+ ++--------------------------------------+-------------------+ +| Crate | Selected Function | ++==========================================================+ +| cargo_autoharness_termination_unwind | gcd_recursion | +|--------------------------------------+-------------------| +| cargo_autoharness_termination_unwind | infinite_loop | ++--------------------------------------+-------------------+ Skipped Functions: None. Kani generated automatic harnesses for all functions in the available crate(s). Autoharness: Checking function gcd_recursion against all possible inputs... @@ -38,12 +38,12 @@ Manual Harness Summary: No proof harnesses (functions with #[kani::proof]) were found to verify. Autoharness Summary: -+-------------------+---------------------------+---------------------+ -| Selected Function | Kind of Automatic Harness | Verification Result | -+=====================================================================+ -| gcd_recursion | #[kani::proof] | Failure | -|-------------------+---------------------------+---------------------| -| infinite_loop | #[kani::proof] | Failure | -+-------------------+---------------------------+---------------------+ ++--------------------------------------+-------------------+---------------------------+---------------------+ +| Crate | Selected Function | Kind of Automatic Harness | Verification Result | ++============================================================================================================+ +| cargo_autoharness_termination_unwind | gcd_recursion | #[kani::proof] | Failure | +|--------------------------------------+-------------------+---------------------------+---------------------| +| cargo_autoharness_termination_unwind | infinite_loop | #[kani::proof] | Failure | ++--------------------------------------+-------------------+---------------------------+---------------------+ Note that `kani autoharness` sets default --harness-timeout of 60s and --default-unwind of 20. If verification failed because of timing out or too low of an unwinding bound, try passing larger values for these arguments (or, if possible, writing a loop contract). diff --git a/tests/script-based-pre/cargo_autoharness_termination_unwind/termination_unwind.sh b/tests/script-based-pre/cargo_autoharness_termination_unwind/termination_unwind.sh index 78df727def41..b197c0b1e077 100755 --- a/tests/script-based-pre/cargo_autoharness_termination_unwind/termination_unwind.sh +++ b/tests/script-based-pre/cargo_autoharness_termination_unwind/termination_unwind.sh @@ -4,4 +4,4 @@ # Set the timeout to 5m to ensure that the gcd_recursion test gets killed because of the unwind bound # and not because CBMC times out. -cargo kani autoharness -Z autoharness -Z function-contracts --harness-timeout 5m -Z unstable-options +cargo kani autoharness -Z autoharness --harness-timeout 5m -Z unstable-options diff --git a/tests/script-based-pre/cargo_autoharness_type_invariant/Cargo.toml b/tests/script-based-pre/cargo_autoharness_type_invariant/Cargo.toml new file mode 100644 index 000000000000..abcecba4b6c2 --- /dev/null +++ b/tests/script-based-pre/cargo_autoharness_type_invariant/Cargo.toml @@ -0,0 +1,10 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +[package] +name = "cargo_autoharness_type_invariant" +version = "0.1.0" +edition = "2024" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(kani)'] } diff --git a/tests/script-based-pre/cargo_autoharness_type_invariant/config.yml b/tests/script-based-pre/cargo_autoharness_type_invariant/config.yml new file mode 100644 index 000000000000..7366397e2ad0 --- /dev/null +++ b/tests/script-based-pre/cargo_autoharness_type_invariant/config.yml @@ -0,0 +1,5 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +script: type-invariant.sh +expected: type-invariant.expected +exit_code: 1 \ No newline at end of file diff --git a/tests/script-based-pre/cargo_autoharness_type_invariant/src/lib.rs b/tests/script-based-pre/cargo_autoharness_type_invariant/src/lib.rs new file mode 100644 index 000000000000..4aca2eef9193 --- /dev/null +++ b/tests/script-based-pre/cargo_autoharness_type_invariant/src/lib.rs @@ -0,0 +1,112 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// Test that the autoharness subcommand respects the invariants specified in the type's kani::any() implementation. +// In other words, test that autoharness is actually finding and using the appropriate kani::any() implementation, +// rather than letting CBMC generate nondetermistic values for the type. +// In this example, we check that the methods that take `Duration` as an argument generate Durations that respect the type invariant that nanos < NANOS_PER_SEC. +// See the "TEST NOTE" inline comments for how we specifically check this. + +// Simplified code from https://github.com/model-checking/verify-rust-std/blob/3f4234a19211e677d51df3061db67477b29af171/library/core/src/time.rs#L1 + +use kani::Invariant; + +const NANOS_PER_SEC: u32 = 1_000_000_000; + +#[derive(kani::Arbitrary)] +#[derive(Clone, Copy)] +pub struct Nanoseconds(u32); + +#[derive(Clone, Copy)] +#[derive(kani::Invariant)] +pub struct Duration { + secs: u64, + nanos: Nanoseconds, +} + +impl kani::Invariant for Nanoseconds { + fn is_safe(&self) -> bool { + self.as_inner() < NANOS_PER_SEC + } +} + +impl Nanoseconds { + #[kani::requires(val < NANOS_PER_SEC)] + #[kani::ensures(|nano| nano.is_safe())] + pub const unsafe fn new_unchecked(val: u32) -> Self { + // SAFETY: caller promises that val < NANOS_PER_SEC + Self(val) + } + + pub const fn as_inner(self) -> u32 { + // SAFETY: This is a transparent wrapper, so unwrapping it is sound + unsafe { core::mem::transmute(self) } + } +} + +impl kani::Arbitrary for Duration { + fn any() -> Self { + let d = Duration { secs: kani::any(), nanos: kani::any() }; + kani::assume(d.is_safe()); + d + } +} + +impl Duration { + // TEST NOTE: the automatic harness for this method fails because it can panic. + #[kani::ensures(|duration| duration.is_safe())] + pub const fn new(secs: u64, nanos: u32) -> Duration { + if nanos < NANOS_PER_SEC { + // SAFETY: nanos < NANOS_PER_SEC, therefore nanos is within the valid range + Duration { secs, nanos: unsafe { Nanoseconds::new_unchecked(nanos) } } + } else { + let secs = secs + .checked_add((nanos / NANOS_PER_SEC) as u64) + .expect("overflow in Duration::new"); + let nanos = nanos % NANOS_PER_SEC; + // SAFETY: nanos % NANOS_PER_SEC < NANOS_PER_SEC, therefore nanos is within the valid range + Duration { secs, nanos: unsafe { Nanoseconds::new_unchecked(nanos) } } + } + } + + pub const fn abs_diff(self, other: Duration) -> Duration { + if let Some(res) = self.checked_sub(other) { res } else { other.checked_sub(self).unwrap() } + } + + #[kani::ensures(|duration| duration.is_none() || duration.unwrap().is_safe())] + pub const fn checked_add(self, rhs: Duration) -> Option { + if let Some(mut secs) = self.secs.checked_add(rhs.secs) { + // TEST NOTE: this addition doesn't overflow iff `self` and `rhs` respect the Duration type invariant + let mut nanos = self.nanos.as_inner() + rhs.nanos.as_inner(); + if nanos >= NANOS_PER_SEC { + nanos -= NANOS_PER_SEC; + if let Some(new_secs) = secs.checked_add(1) { + secs = new_secs; + } else { + return None; + } + } + Some(Duration::new(secs, nanos)) + } else { + None + } + } + + #[kani::ensures(|duration| duration.is_none() || duration.unwrap().is_safe())] + pub const fn checked_sub(self, rhs: Duration) -> Option { + if let Some(mut secs) = self.secs.checked_sub(rhs.secs) { + let nanos = if self.nanos.as_inner() >= rhs.nanos.as_inner() { + self.nanos.as_inner() - rhs.nanos.as_inner() + } else if let Some(sub_secs) = secs.checked_sub(1) { + secs = sub_secs; + // TEST NOTE: this arithmetic doesn't overflow iff `self` and `rhs` respect the Duration type invariant + self.nanos.as_inner() + NANOS_PER_SEC - rhs.nanos.as_inner() + } else { + return None; + }; + Some(Duration::new(secs, nanos)) + } else { + None + } + } +} diff --git a/tests/script-based-pre/cargo_autoharness_type_invariant/type-invariant.expected b/tests/script-based-pre/cargo_autoharness_type_invariant/type-invariant.expected new file mode 100644 index 000000000000..cc8e9b32ca14 --- /dev/null +++ b/tests/script-based-pre/cargo_autoharness_type_invariant/type-invariant.expected @@ -0,0 +1,110 @@ +Kani generated automatic harnesses for 7 function(s): ++----------------------------------+----------------------------+ +| Crate | Selected Function | ++===============================================================+ +| cargo_autoharness_type_invariant | Duration::abs_diff | +|----------------------------------+----------------------------| +| cargo_autoharness_type_invariant | Duration::checked_add | +|----------------------------------+----------------------------| +| cargo_autoharness_type_invariant | Duration::checked_sub | +|----------------------------------+----------------------------| +| cargo_autoharness_type_invariant | Duration::new | +|----------------------------------+----------------------------| +| cargo_autoharness_type_invariant | Nanoseconds | +|----------------------------------+----------------------------| +| cargo_autoharness_type_invariant | Nanoseconds::as_inner | +|----------------------------------+----------------------------| +| cargo_autoharness_type_invariant | Nanoseconds::new_unchecked | ++----------------------------------+----------------------------+ + +Autoharness: Checking function Nanoseconds::new_unchecked's contract against all possible inputs... +Nanoseconds::new_unchecked\ + - Status: SUCCESS\ + - Description: "|nano| nano.is_safe()" + +Autoharness: Checking function Duration::checked_sub's contract against all possible inputs... +Duration::checked_sub\ + - Status: SUCCESS\ + - Description: "attempt to add with overflow" + +Duration::checked_sub\ + - Status: SUCCESS\ + - Description: "attempt to subtract with overflow" + +Nanoseconds::new_unchecked\ + - Status: SUCCESS\ + - Description: "val < NANOS_PER_SEC" + +Duration::checked_sub\ + - Status: SUCCESS\ + - Description: "|duration| duration.is_none() || duration.unwrap().is_safe()" + +Duration::new\ + - Status: SUCCESS\ + - Description: "|duration| duration.is_safe()" + +Autoharness: Checking function Duration::checked_add's contract against all possible inputs... +Nanoseconds::new_unchecked\ + - Status: SUCCESS\ + - Description: "val < NANOS_PER_SEC" + +Duration::new\ + - Status: SUCCESS\ + - Description: "|duration| duration.is_safe()" + +Duration::checked_add\ + - Status: SUCCESS\ + - Description: "|duration| duration.is_none() || duration.unwrap().is_safe()" + +Duration::checked_add\ + - Status: SUCCESS\ + - Description: "attempt to add with overflow" + +Duration::checked_add\ + - Status: SUCCESS\ + - Description: "attempt to subtract with overflow" + +Autoharness: Checking function Duration::abs_diff against all possible inputs... +Duration::checked_sub\ + - Status: SUCCESS\ + - Description: "attempt to subtract with overflow" + +Duration::checked_sub\ + - Status: SUCCESS\ + - Description: "attempt to subtract with overflow" + +Autoharness: Checking function Duration::new's contract against all possible inputs... +Nanoseconds::new_unchecked\ + - Status: SUCCESS\ + - Description: "val < NANOS_PER_SEC" + +Duration::new\ + - Status: SUCCESS\ + - Description: "|duration| duration.is_safe()" + +std::option::expect_failed\ + - Status: FAILURE\ + - Description: "This is a placeholder message; Kani doesn't support message formatted at runtime" + + +Autoharness Summary: ++----------------------------------+----------------------------+-----------------------------+---------------------+ +| Crate | Selected Function | Kind of Automatic Harness | Verification Result | ++===================================================================================================================+ +| cargo_autoharness_type_invariant | Duration::abs_diff | #[kani::proof] | Success | +|----------------------------------+----------------------------+-----------------------------+---------------------| +| cargo_autoharness_type_invariant | Duration::checked_add | #[kani::proof_for_contract] | Success | +|----------------------------------+----------------------------+-----------------------------+---------------------| +| cargo_autoharness_type_invariant | Duration::checked_sub | #[kani::proof_for_contract] | Success | +|----------------------------------+----------------------------+-----------------------------+---------------------| +| cargo_autoharness_type_invariant | Nanoseconds | #[kani::proof] | Success | +|----------------------------------+----------------------------+-----------------------------+---------------------| +| cargo_autoharness_type_invariant | Nanoseconds::as_inner | #[kani::proof] | Success | +|----------------------------------+----------------------------+-----------------------------+---------------------| +| cargo_autoharness_type_invariant | Nanoseconds::new_unchecked | #[kani::proof_for_contract] | Success | +|----------------------------------+----------------------------+-----------------------------+---------------------| +| cargo_autoharness_type_invariant | Duration::new | #[kani::proof_for_contract] | Failure | ++----------------------------------+----------------------------+-----------------------------+---------------------+ +Note that `kani autoharness` sets default --harness-timeout of 60s and --default-unwind of 20. +If verification failed because of timing out or too low of an unwinding bound, try passing larger values for these arguments (or, if possible, writing a loop contract). +Complete - 6 successfully verified functions, 1 failures, 7 total. diff --git a/tests/script-based-pre/cargo_autoharness_type_invariant/type-invariant.sh b/tests/script-based-pre/cargo_autoharness_type_invariant/type-invariant.sh new file mode 100755 index 000000000000..db4a99f8be09 --- /dev/null +++ b/tests/script-based-pre/cargo_autoharness_type_invariant/type-invariant.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +cargo kani autoharness -Z autoharness diff --git a/tests/script-based-pre/cargo_list_md/list.expected b/tests/script-based-pre/cargo_list_md/list.expected index 9e913345e664..fcaf72a14d00 100644 --- a/tests/script-based-pre/cargo_list_md/list.expected +++ b/tests/script-based-pre/cargo_list_md/list.expected @@ -1,14 +1,16 @@ Contracts: -| | Function | Contract Harnesses (#[kani::proof_for_contract]) | -| ----- | ----------------------------- | -------------------------------------------------------------- | -| | example::implementation::bar | example::verify::check_bar | -| | example::implementation::foo | example::verify::check_foo_u32, example::verify::check_foo_u64 | -| | example::implementation::func | example::verify::check_func | -| | example::prep::parse | NONE | -| Total | 4 | 4 | - +| | Crate | Function | Contract Harnesses (#[kani::proof_for_contract]) | +| ----- | ----- | ----------------------------- | -------------------------------------------------------------- | +| | lib | example::implementation::bar | example::verify::check_bar | +| | lib | example::implementation::foo | example::verify::check_foo_u32, example::verify::check_foo_u64 | +| | lib | example::implementation::func | example::verify::check_func | +| | lib | example::prep::parse | NONE | +| Total | | 4 | 4 | Standard Harnesses (#[kani::proof]): -1. standard_harnesses::example::verify::check_modify -2. standard_harnesses::example::verify::check_new \ No newline at end of file +| | Crate | Harness | +| ----- | ----- | ------------------------------------------------- | +| | lib | standard_harnesses::example::verify::check_modify | +| | lib | standard_harnesses::example::verify::check_new | +| Total | | 2 | diff --git a/tests/script-based-pre/kani_autoharness_exclude_precedence/config.yml b/tests/script-based-pre/kani_autoharness_exclude_precedence/config.yml new file mode 100644 index 000000000000..16a0fe88bccd --- /dev/null +++ b/tests/script-based-pre/kani_autoharness_exclude_precedence/config.yml @@ -0,0 +1,3 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +script: precedence.sh diff --git a/tests/script-based-pre/kani_autoharness_exclude_precedence/precedence.sh b/tests/script-based-pre/kani_autoharness_exclude_precedence/precedence.sh new file mode 100755 index 000000000000..38b92b88c740 --- /dev/null +++ b/tests/script-based-pre/kani_autoharness_exclude_precedence/precedence.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +# Test that --include-pattern and --exclude-pattern work as expected when provided together. + +set -e + +# Define all function paths +FUNCTIONS=( + "foo::foo_function" + "foo::bar::bar_function" + "foo::bar::foo_bar_function" + "foo::baz::foo_baz_function" + "other::regular_function" + "other::with_bar_name" + "foo_top_level" + "bar_top_level" +) + +# Check if a function appears in the "Selected Function" table +check_selected() { + local output="$1" + local function_name="$2" + if echo "$output" | grep -q "| $function_name *|"; then + return 0 + else + return 1 + fi +} + +# Check if a function appears in the "Skipped Function" table +check_skipped() { + local output="$1" + local function_name="$2" + if echo "$output" | grep -q "| $function_name *|.*Did not match provided filters"; then + return 0 + else + return 1 + fi +} + +# Check that the warning message gets printed for the patterns that are mutually exclusive (no functions get selected) +check_warning() { + local output="$1" + local should_warn="$2" + local warning_present=$(echo "$output" | grep -c "warning: Include pattern" || true) + + if [ "$should_warn" = true ] && [ "$warning_present" -eq 0 ]; then + echo "ERROR: expected printed warning about conflicting --include-pattern and --exclude-pattern flags" + return 1 + elif [ "$should_warn" = false ] && [ "$warning_present" -gt 0 ]; then + echo "ERROR: Got unexpected warning message" + return 1 + fi + return 0 +} + + +# Helper function to verify functions against include/exclude patterns +verify_functions() { + local output="$1" + local include_pattern="$2" + local exclude_pattern="$3" + + for func in "${FUNCTIONS[@]}"; do + # If the function name matches the include pattern and not the exclude pattern, it should be selected + if echo "$func" | grep -q "$include_pattern" && ! echo "$func" | grep -q "$exclude_pattern"; then + if ! check_selected "$output" "$func"; then + echo "ERROR: Expected $func to be selected" + exit 1 + fi + # Otherwise, it should be skipped + else + if ! check_skipped "$output" "$func"; then + echo "ERROR: Expected $func to be skipped" + exit 1 + fi + fi + done +} + +# Test cases +test_cases=( + "include 'foo' exclude 'foo::bar'" + "include 'foo' exclude 'bar'" + "include 'foo::bar' exclude 'bar'" + "include 'foo' exclude 'foo'" +) + +include_patterns=( + "foo" + "foo" + "foo::bar" + "foo" +) + +exclude_patterns=( + "foo::bar" + "bar" + "bar" + "foo" +) + +# Whether each test case should produce a warning about no functions being selected +should_warn=( + false + false + true + true +) + +for i in "${!test_cases[@]}"; do + echo "Testing: ${test_cases[$i]}" + output=$(kani autoharness -Z autoharness src/lib.rs --include-pattern "${include_patterns[$i]}" --exclude-pattern "${exclude_patterns[$i]}" --only-codegen) + echo "$output" + + if ! check_warning "$output" "${should_warn[$i]}"; then + exit 1 + fi + + verify_functions "$output" "${include_patterns[$i]}" "${exclude_patterns[$i]}" +done + +echo "All tests passed!" \ No newline at end of file diff --git a/tests/script-based-pre/kani_autoharness_exclude_precedence/src/lib.rs b/tests/script-based-pre/kani_autoharness_exclude_precedence/src/lib.rs new file mode 100644 index 000000000000..8fb3d7957ae3 --- /dev/null +++ b/tests/script-based-pre/kani_autoharness_exclude_precedence/src/lib.rs @@ -0,0 +1,26 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +mod foo { + fn foo_function() {} + + mod bar { + fn bar_function() {} + + fn foo_bar_function() {} + } + + mod baz { + fn foo_baz_function() {} + } +} + +mod other { + fn regular_function() {} + + fn with_bar_name() {} +} + +fn foo_top_level() {} + +fn bar_top_level() {} diff --git a/tests/script-based-pre/kani_list_md/list.expected b/tests/script-based-pre/kani_list_md/list.expected index eb4ca335d678..1184443beaa1 100644 --- a/tests/script-based-pre/kani_list_md/list.expected +++ b/tests/script-based-pre/kani_list_md/list.expected @@ -1,14 +1,16 @@ Contracts: -| | Function | Contract Harnesses (#[kani::proof_for_contract]) | -| ----- | ----------------------------- | -------------------------------------------------------------- | -| | example::implementation::bar | example::verify::check_bar | -| | example::implementation::foo | example::verify::check_foo_u32, example::verify::check_foo_u64 | -| | example::implementation::func | example::verify::check_func | -| | example::prep::parse | NONE | -| Total | 4 | 4 | - +| | Crate | Function | Contract Harnesses (#[kani::proof_for_contract]) | +| ----- | ----- | ----------------------------- | -------------------------------------------------------------- | +| | lib | example::implementation::bar | example::verify::check_bar | +| | lib | example::implementation::foo | example::verify::check_foo_u32, example::verify::check_foo_u64 | +| | lib | example::implementation::func | example::verify::check_func | +| | lib | example::prep::parse | NONE | +| Total | | 4 | 4 | Standard Harnesses (#[kani::proof]): -1. example::verify::check_modify -2. example::verify::check_new \ No newline at end of file +| | Crate | Harness | +| ----- | ----- | ----------------------------- | +| | lib | example::verify::check_modify | +| | lib | example::verify::check_new | +| Total | | 2 | diff --git a/tests/script-based-pre/no_codegen/Cargo.toml b/tests/script-based-pre/no_codegen/Cargo.toml new file mode 100644 index 000000000000..330c69766800 --- /dev/null +++ b/tests/script-based-pre/no_codegen/Cargo.toml @@ -0,0 +1,9 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +[package] +name = "no_codegen" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/tests/script-based-pre/no_codegen/config.yml b/tests/script-based-pre/no_codegen/config.yml new file mode 100644 index 000000000000..cd1a72e5f000 --- /dev/null +++ b/tests/script-based-pre/no_codegen/config.yml @@ -0,0 +1,5 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +script: run.sh +expected: expected +exit_code: 0 diff --git a/tests/script-based-pre/no_codegen/expected b/tests/script-based-pre/no_codegen/expected new file mode 100644 index 000000000000..609978a7272c --- /dev/null +++ b/tests/script-based-pre/no_codegen/expected @@ -0,0 +1 @@ +info: Compilation succeeded up until codegen. Skipping codegen because of `--no-codegen` option. Rerun without `--no-codegen` to perform codegen. diff --git a/tests/script-based-pre/no_codegen/run.sh b/tests/script-based-pre/no_codegen/run.sh new file mode 100755 index 000000000000..01a6a5e2787b --- /dev/null +++ b/tests/script-based-pre/no_codegen/run.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +rm -rf target + +# Test the behavior of the `--no-codegen` option +cargo kani --no-codegen -Zunstable-options + +# Ensure no goto binaries (*.out) are generated +[[ -z $(find target -name "*.out") ]] || { + echo "ERROR: Found goto binaries (*.out) in target directory" + exit 1 +} diff --git a/tests/script-based-pre/no_codegen/src/main.rs b/tests/script-based-pre/no_codegen/src/main.rs new file mode 100644 index 000000000000..7757baddc225 --- /dev/null +++ b/tests/script-based-pre/no_codegen/src/main.rs @@ -0,0 +1,14 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This test checks the behavior of Kani's `--no-codegen` option + +#[kani::proof] +fn main() { + let x: u8 = kani::any(); + if x < 100 { + assert!(x < 101); + } else { + assert!(x > 99); + } +} diff --git a/tests/script-based-pre/no_codegen_error/Cargo.toml b/tests/script-based-pre/no_codegen_error/Cargo.toml new file mode 100644 index 000000000000..b44424869d11 --- /dev/null +++ b/tests/script-based-pre/no_codegen_error/Cargo.toml @@ -0,0 +1,9 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +[package] +name = "no_codegen_error" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/tests/script-based-pre/no_codegen_error/config.yml b/tests/script-based-pre/no_codegen_error/config.yml new file mode 100644 index 000000000000..482747bf1e99 --- /dev/null +++ b/tests/script-based-pre/no_codegen_error/config.yml @@ -0,0 +1,5 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +script: run.sh +expected: expected +exit_code: 1 diff --git a/tests/script-based-pre/no_codegen_error/expected b/tests/script-based-pre/no_codegen_error/expected new file mode 100644 index 000000000000..09089fc5b16b --- /dev/null +++ b/tests/script-based-pre/no_codegen_error/expected @@ -0,0 +1 @@ +could not compile `no_codegen_error` diff --git a/tests/script-based-pre/no_codegen_error/run.sh b/tests/script-based-pre/no_codegen_error/run.sh new file mode 100755 index 000000000000..c00403549aa7 --- /dev/null +++ b/tests/script-based-pre/no_codegen_error/run.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +rm -rf target + +# Test the behavior of the `--no-codegen` option +cargo kani --no-codegen -Zunstable-options diff --git a/tests/script-based-pre/no_codegen_error/src/main.rs b/tests/script-based-pre/no_codegen_error/src/main.rs new file mode 100644 index 000000000000..2afa9b9e747b --- /dev/null +++ b/tests/script-based-pre/no_codegen_error/src/main.rs @@ -0,0 +1,13 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This test checks the behavior of Kani's `--no-codegen` option when the crate +//! has compilation errors + +#[kani::proof] +fn main() { + let x: i32 = 5; + // Error: different types + let y: u32 = x; + assert_eq!(y, 5); +} diff --git a/tests/script-based-pre/tool-scanner/scanner-test.expected b/tests/script-based-pre/tool-scanner/scanner-test.expected index f55a883434ee..67bd14fc0e46 100644 --- a/tests/script-based-pre/tool-scanner/scanner-test.expected +++ b/tests/script-based-pre/tool-scanner/scanner-test.expected @@ -1,6 +1,18 @@ 5 test_scan_fn_loops.csv -19 test_scan_functions.csv +20 test_scan_functions.csv 5 test_scan_input_tys.csv -16 test_scan_overall.csv +19 test_scan_overall.csv 3 test_scan_recursion.csv -5 test_scan_unsafe_ops.csv +9 test_scan_unsafe_distance.csv +6 test_scan_unsafe_ops.csv + +Unsafe Distance Results +call_external;0 +next_id;0 +external_function;0 +raw_to_ref;0 +with_for_loop;1 +check_outer_coercion;1 +with_iterator;2 +current_id;0 +generic;0 diff --git a/tests/script-based-pre/tool-scanner/scanner-test.sh b/tests/script-based-pre/tool-scanner/scanner-test.sh index 2cd5a33a3f8e..468bb61dcf0d 100755 --- a/tests/script-based-pre/tool-scanner/scanner-test.sh +++ b/tests/script-based-pre/tool-scanner/scanner-test.sh @@ -16,5 +16,11 @@ pushd $OUT_DIR cargo run -p scanner test.rs --crate-type lib wc -l *csv +# How to intepret these results: +# - If the function is "truly safe," i.e., there's no unsafe in its call graph, it will not show up in the output at all. +# - Otherwise, the count should match the rules described in scanner::call_graph::OverallStats::unsafe_distance. +echo "Unsafe Distance Results" +cat test_scan_unsafe_distance.csv + popd rm -rf ${OUT_DIR} diff --git a/tests/script-based-pre/tool-scanner/test.rs b/tests/script-based-pre/tool-scanner/test.rs index f6a141f2a708..be45a653d55e 100644 --- a/tests/script-based-pre/tool-scanner/test.rs +++ b/tests/script-based-pre/tool-scanner/test.rs @@ -102,3 +102,12 @@ pub fn start_recursion() { pub fn not_recursive() { let _ = ok(); } + +extern "C" { + fn external_function(); +} + +/// Ensure scanner finds unsafe calls to external functions. +pub fn call_external() { + unsafe { external_function() }; +} diff --git a/tools/benchcomp/benchcomp/visualizers/__init__.py b/tools/benchcomp/benchcomp/visualizers/__init__.py index 865386900639..8963e444e7a4 100644 --- a/tools/benchcomp/benchcomp/visualizers/__init__.py +++ b/tools/benchcomp/benchcomp/visualizers/__init__.py @@ -265,12 +265,17 @@ def _get_template(): Scatterplot axis ranges are {{ d["scaled_metrics"][metric]["min_value"] }} (bottom/left) to {{ d["scaled_metrics"][metric]["max_value"] }} (top/right). {% endif -%} +
Breakdown by harness + | Benchmark | {% for variant in d["variants"][metric] %} {{ variant }} |{% endfor %} | --- |{% for variant in d["variants"][metric] %} --- |{% endfor -%} {% for bench_name, bench_variants in benchmarks.items () %} | {{ bench_name }} {% for variant in d["variants"][metric] -%} | {{ bench_variants[variant] }} {% endfor %}| {%- endfor %} + +
+ {% endfor -%} """) diff --git a/tools/benchcomp/test/test_regression.py b/tools/benchcomp/test/test_regression.py index ccf2259f7f0b..00256aa891ab 100644 --- a/tools/benchcomp/test/test_regression.py +++ b/tools/benchcomp/test/test_regression.py @@ -477,18 +477,28 @@ def test_markdown_results_table(self): ``` Scatterplot axis ranges are 5 (bottom/left) to 10 (top/right). +
Breakdown by harness + | Benchmark | variant_1 | variant_2 | ratio | | --- | --- | --- | --- | | bench_1 | 5 | 10 | **2.0** | | bench_2 | 10 | 5 | 0.5 | +
+ + ## success +
Breakdown by harness + | Benchmark | variant_1 | variant_2 | notes | | --- | --- | --- | --- | | bench_1 | True | True | | | bench_2 | True | False | regressed | | bench_3 | False | True | newly passing | + +
+ """)) diff --git a/tools/build-kani/Cargo.toml b/tools/build-kani/Cargo.toml index 23632adc48ec..9c8decbab8ea 100644 --- a/tools/build-kani/Cargo.toml +++ b/tools/build-kani/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "build-kani" -version = "0.60.0" +version = "0.62.0" edition = "2021" description = "Builds Kani, Sysroot and release bundle." license = "MIT OR Apache-2.0" diff --git a/tools/build-kani/src/main.rs b/tools/build-kani/src/main.rs index 3ff017e37509..1e37cba165a0 100644 --- a/tools/build-kani/src/main.rs +++ b/tools/build-kani/src/main.rs @@ -24,7 +24,7 @@ fn main() -> Result<()> { parser::Commands::BuildDev(build_parser) => { let bin_folder = &build_bin(&build_parser.args)?; if !build_parser.skip_libs { - build_lib(&bin_folder)?; + build_lib(bin_folder)?; } Ok(()) } @@ -105,8 +105,8 @@ fn bundle_kani(dir: &Path) -> Result<()> { // 4. Pre-compiled library files cp_dir(&kani_sysroot_lib(), dir)?; - cp_dir(&kani_playback_lib().parent().unwrap(), dir)?; - cp_dir(&kani_no_core_lib().parent().unwrap(), dir)?; + cp_dir(kani_playback_lib().parent().unwrap(), dir)?; + cp_dir(kani_no_core_lib().parent().unwrap(), dir)?; // 5. Record the exact toolchain and rustc version we use std::fs::write(dir.join("rust-toolchain-version"), env!("RUSTUP_TOOLCHAIN"))?; diff --git a/tools/build-kani/src/sysroot.rs b/tools/build-kani/src/sysroot.rs index 139dce794f13..bb8708a14f68 100644 --- a/tools/build-kani/src/sysroot.rs +++ b/tools/build-kani/src/sysroot.rs @@ -180,13 +180,13 @@ fn copy_artifacts(artifacts: &[Artifact], sysroot_lib: &Path, copy_std: bool) -> fs::create_dir_all(sysroot_lib)?; // Copy Kani libraries into sysroot top folder. - copy_libs(&artifacts, &sysroot_lib, &is_kani_lib); + copy_libs(artifacts, sysroot_lib, &is_kani_lib); // Copy standard libraries into rustlib//lib/ folder. if copy_std { let std_path = path_buf!(&sysroot_lib, "rustlib", build_target(), "lib"); - fs::create_dir_all(&std_path).expect(&format!("Failed to create {std_path:?}")); - copy_libs(&artifacts, &std_path, &is_std_lib); + fs::create_dir_all(&std_path).unwrap_or_else(|_| panic!("Failed to create {std_path:?}")); + copy_libs(artifacts, &std_path, &is_std_lib); } Ok(()) } diff --git a/tools/compile-timer/Cargo.toml b/tools/compile-timer/Cargo.toml new file mode 100644 index 000000000000..f826d25f842c --- /dev/null +++ b/tools/compile-timer/Cargo.toml @@ -0,0 +1,24 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +[package] +name = "compile-timer" +version = "0.1.0" +edition = "2024" +license = "MIT OR Apache-2.0" + +[dependencies] +clap = { version = "4.5.40", features = ["derive"] } +serde = {version = "1.0.219", features = ["derive"]} +serde_json = "1.0.140" + +[[bin]] +name = "compile-timer" +path = "src/compile-timer.rs" + +[[bin]] +name = "compile-analyzer" +path = "src/compile-analyzer.rs" + +[lints] +workspace = true \ No newline at end of file diff --git a/tools/compile-timer/README.md b/tools/compile-timer/README.md new file mode 100644 index 000000000000..de9821f91e88 --- /dev/null +++ b/tools/compile-timer/README.md @@ -0,0 +1,19 @@ +# Compile-Timer +This is a simple script for timing the Kani compiler's end-to-end performance on crates. + +## Setup +You can run it by first compiling Kani (with `cargo build-dev --release` in the project root), then building this script (with `cargo build --release` in this `compile-timer` directory). This will build new `compile-timer` & `compile-analyzer` binaries in `kani/target/release`. + +## Recording Compiler Times with `compile-timer` +After doing that, you should make sure you have Kani on your $PATH (see instructions [here](https://model-checking.github.io/kani/build-from-source.html#adding-kani-to-your-path)) after which you can run `compile-timer --out-path [OUT_JSON_FILE]` in any directory to profile the compiler's performance on it. + +By default, the script recursively goes into directories and will use `cargo kani` to profile any Rust projects it encounters (which it determines by looking for a `Cargo.toml`). You can tell it to ignore specific subtrees by passing in the `--ignore [DIR_NAME]` flag. + +## Visualizing Compiler Times with `compile-analyzer` +`compile-timer` itself will have some debug output including each individual run's time and aggregates for each crate. + +`compile-analyzer` is specifically for comparing performance across multiple commits. + +Once you've run `compile-timer` on both commits, you can run `compile-analyzer --path-pre [FIRST_JSON_FILE] --path-post [SECOND_JSON_FILE]` to see the change in performance going from the first to second commit. + +By default, `compile-analyzer` will just print to the console, but if you specify the `--only-markdown` option, it's output will be formatted for GitHub flavored markdown (as is useful in CI). \ No newline at end of file diff --git a/tools/compile-timer/src/common.rs b/tools/compile-timer/src/common.rs new file mode 100644 index 000000000000..b65ed0de79e8 --- /dev/null +++ b/tools/compile-timer/src/common.rs @@ -0,0 +1,61 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +#![allow(dead_code)] +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; +use std::time::Duration; + +#[derive(Debug, Serialize, Deserialize)] +pub struct AggrResult { + pub krate: PathBuf, + pub krate_trimmed_path: String, + /// the stats for only the 25th-75th percentile of runs on this crate, i.e., the interquartile range + pub iqr_stats: Stats, + /// the stats for all runs on this crate + full_stats: Stats, +} + +pub fn krate_trimmed_path(krate: &Path) -> String { + format!( + "{:?}", + krate + .canonicalize() + .unwrap() + .strip_prefix(std::env::current_dir().unwrap().parent().unwrap()) + .unwrap() + ) +} + +impl AggrResult { + pub fn new(krate: PathBuf, iqr_stats: Stats, full_stats: Stats) -> Self { + AggrResult { krate_trimmed_path: krate_trimmed_path(&krate), krate, iqr_stats, full_stats } + } + + pub fn full_std_dev(&self) -> Duration { + self.full_stats.std_dev + } + + pub fn iqr(&self) -> Duration { + self.iqr_stats.range.1 - self.iqr_stats.range.0 + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Stats { + pub avg: Duration, + pub std_dev: Duration, + pub range: (Duration, Duration), +} + +/// Sum the IQR averages and IQR standard deviations respectively for all crates timed. +pub fn aggregate_aggregates(info: &[AggrResult]) -> (Duration, Duration) { + for i in info { + println!("krate {:?} -- {:?}", i.krate, i.iqr_stats.avg); + } + + (info.iter().map(|i| i.iqr_stats.avg).sum(), info.iter().map(|i| i.iqr_stats.std_dev).sum()) +} + +pub fn fraction_of_duration(dur: Duration, frac: f64) -> Duration { + Duration::from_nanos(((dur.as_nanos() as f64) * frac) as u64) +} diff --git a/tools/compile-timer/src/compile-analyzer.rs b/tools/compile-timer/src/compile-analyzer.rs new file mode 100644 index 000000000000..416065131734 --- /dev/null +++ b/tools/compile-timer/src/compile-analyzer.rs @@ -0,0 +1,240 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +#![allow(dead_code)] +use crate::common::{AggrResult, fraction_of_duration}; +use clap::Parser; +use serde_json::Deserializer; +use std::{cmp::max, fs::File, io, path::PathBuf, time::Duration}; +mod common; + +// Constants for detecting 'significant' regressions + +/// The fractional of a sample's standard deviation that it can regress by +/// without being considered a significant regression. +const FRAC_STD_DEV_THRESHOLD: f64 = 2.0; // In this case, 2x the average std deviation. + +/// The fractional amount a run can regress by without it being considered a significant regression. +const FRAC_ABSOLUTE_THRESHOLD: f64 = 0.05; // In this case, 5% of the initial time. + +#[derive(Debug, Parser)] +#[command(version, about, long_about = None)] +struct AnalyzerArgs { + #[arg(short, long, value_name = "FILE")] + path_pre: PathBuf, + + #[arg(short, long, value_name = "FILE")] + path_post: PathBuf, + + #[arg(short, long)] + /// The test suite name to display as part of the output's title + suite_name: Option, + + /// Output results in markdown format + #[arg(short, long)] + only_markdown: bool, +} + +fn main() { + let args = AnalyzerArgs::parse(); + + let (pre_file, post_file) = try_read_files(&args).unwrap(); + + let (pre_ser, post_ser) = + (Deserializer::from_reader(pre_file), Deserializer::from_reader(post_file)); + + let pre_results = pre_ser.into_iter::().collect::>(); + let post_results = post_ser.into_iter::().collect::>(); + + let mut results = pre_results + .into_iter() + .filter_map(Result::ok) + .zip(post_results.into_iter().filter_map(Result::ok)) + .collect::>(); + + sort_results(&mut results); + + if args.only_markdown { + print_markdown(results.as_slice(), args.suite_name); + } else { + print_to_terminal(results.as_slice()); + } +} + +/// Sort results based on percentage change, with high magnitude regressions first, then low +/// magnitude regressions, low magnitude improvements and finally high magnitude improvements. +fn sort_results(results: &mut [(AggrResult, AggrResult)]) { + results.sort_by_key(|a| { + -(signed_percent_diff(&a.0.iqr_stats.avg, &a.1.iqr_stats.avg).abs() * 1000_f64) as i64 + }); +} + +/// Print results in a markdown format (for GitHub actions). +fn print_markdown(results: &[(AggrResult, AggrResult)], suite_name: Option) { + let suite_text = if let Some(suite_name) = suite_name { + format!(" (`{suite_name}` suite)") + } else { + "".to_string() + }; + println!("# Compiletime Results{suite_text}"); + let total_pre = results.iter().map(|i| i.0.iqr_stats.avg).sum(); + let total_post = results.iter().map(|i| i.1.iqr_stats.avg).sum(); + println!( + "### *on the whole: {:.2?} → {:.2?} —* {}", + total_pre, + total_post, + diff_string(total_pre, total_post) + ); + // Note that we have to call the fourth column "heterogeneousness" because the color-formatted + // diff will cut off if the column isn't wide enough for it, so verbosity is required. + println!( + "| test crate | old compile time | new compile time | heterogeneousness (percentage difference) | verdict |" + ); + println!("| - | - | - | - | - |"); + let regressions = results + .iter() + .map(|(pre_res, post_res)| { + assert!(pre_res.krate_trimmed_path == post_res.krate_trimmed_path); + let pre_time = pre_res.iqr_stats.avg; + let post_time = post_res.iqr_stats.avg; + + let verdict = verdict_on_change(pre_res, post_res); + // emphasize output of crate name if it had a suspected regression + let emph = if verdict.is_regression() { "**" } else { "" }; + println!( + "| {emph}{}{emph} | {:.2?} | {:.2?} | {} | {:?} |", + pre_res.krate_trimmed_path, + pre_time, + post_time, + diff_string(pre_time, post_time), + verdict + ); + (&pre_res.krate_trimmed_path, verdict) + }) + .filter_map( + |(krate, verdict)| if verdict.is_regression() { Some(krate.clone()) } else { None }, + ) + .collect::>(); + + let footnote_number = 1; + println!( + "\n[^{footnote_number}]: threshold: max({FRAC_STD_DEV_THRESHOLD} x std_dev, {FRAC_ABSOLUTE_THRESHOLD} x initial_time)." + ); + + if regressions.is_empty() { + println!("## No suspected regressions[^{footnote_number}]!"); + } else { + println!( + "## Failing because of {} suspected regressions[^{footnote_number}]:", + regressions.len() + ); + println!("{}", regressions.join(", ")); + std::process::exit(1); + } +} + +/// Print results for a terminal output. +fn print_to_terminal(results: &[(AggrResult, AggrResult)]) { + let krate_column_len = results + .iter() + .map(|(a, b)| max(a.krate_trimmed_path.len(), b.krate_trimmed_path.len())) + .max() + .unwrap(); + + for (pre_res, post_res) in results { + assert!(pre_res.krate == post_res.krate); + let pre_time = pre_res.iqr_stats.avg; + let post_time = post_res.iqr_stats.avg; + + let change_dir = if post_time > pre_time { + "↑" + } else if post_time == pre_time { + "-" + } else { + "↓" + }; + let change_amount = (pre_time.abs_diff(post_time).as_micros() as f64 + / post_time.as_micros() as f64) + * 100_f64; + + println!( + "krate {:krate_column_len$} -- [{:.2?} => {:.2?} ({change_dir}{change_amount:5.2}%)] {:?}", + pre_res.krate_trimmed_path, + pre_time, + post_time, + verdict_on_change(pre_res, post_res) + ); + } +} + +/// Classify a change into a [Verdict], determining whether it was an improvement, regression, +/// or likely just noise based on provided thresholds. +fn verdict_on_change(pre: &AggrResult, post: &AggrResult) -> Verdict { + let (pre_time, post_time) = (pre.iqr_stats.avg, post.iqr_stats.avg); + + if post_time.abs_diff(pre_time) < fraction_of_duration(pre_time, FRAC_ABSOLUTE_THRESHOLD) { + return Verdict::ProbablyNoise(NoiseExplanation::SmallPercentageChange); + } + + let avg_std_dev = (pre.full_std_dev() + post.full_std_dev()) / 2; + if post_time.abs_diff(pre_time) < fraction_of_duration(avg_std_dev, FRAC_STD_DEV_THRESHOLD) { + return Verdict::ProbablyNoise(NoiseExplanation::SmallComparedToStdDevOf(avg_std_dev)); + } + + if pre.iqr_stats.avg > post.iqr_stats.avg { + return Verdict::Improved; + } + + Verdict::PotentialRegression { sample_std_dev: avg_std_dev } +} + +fn signed_percent_diff(pre: &Duration, post: &Duration) -> f64 { + let change_amount = (pre.abs_diff(*post).as_micros() as f64 / pre.as_micros() as f64) * 100_f64; + if post < pre { -change_amount } else { change_amount } +} + +fn diff_string(pre: Duration, post: Duration) -> String { + let change_dir = if post > pre { + "$\\color{red}\\textsf{↑ " + } else if post == pre { + "$\\color{black}\\textsf{- " + } else { + "$\\color{green}\\textsf{↓ " + }; + let change_amount = signed_percent_diff(&pre, &post).abs(); + format!("{change_dir}{:.2?} ({change_amount:.2}\\\\%)}}$", pre.abs_diff(post)) +} + +#[derive(Debug)] +enum Verdict { + /// This crate now compiles faster! + Improved, + /// This crate compiled slower, but likely because of OS noise. + ProbablyNoise(NoiseExplanation), + /// This crate compiled slower, potentially indicating a true performance regression. + PotentialRegression { sample_std_dev: std::time::Duration }, +} + +#[derive(Debug)] +/// The reason a regression was flagged as likely noise rather than a true performance regression. +enum NoiseExplanation { + /// The increase in compile time is so small compared to the + /// sample's standard deviation (< [FRAC_STD_DEV_THRESHOLD] * std_dev) + /// that it is probably just sampling noise. + SmallComparedToStdDevOf(std::time::Duration), + /// The percentage increase in compile time is so small (< [FRAC_ABSOLUTE_THRESHOLD]), + /// the difference is likely insignificant. + SmallPercentageChange, +} + +impl Verdict { + fn is_regression(&self) -> bool { + matches!(self, Verdict::PotentialRegression { sample_std_dev: _ }) + } +} + +fn try_read_files(c: &AnalyzerArgs) -> io::Result<(File, File)> { + io::Result::Ok(( + File::open(c.path_pre.canonicalize()?)?, + File::open(c.path_post.canonicalize()?)?, + )) +} diff --git a/tools/compile-timer/src/compile-timer.rs b/tools/compile-timer/src/compile-timer.rs new file mode 100644 index 000000000000..4c7799d40ff4 --- /dev/null +++ b/tools/compile-timer/src/compile-timer.rs @@ -0,0 +1,184 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#![feature(exit_status_error)] + +use crate::common::{AggrResult, Stats, aggregate_aggregates, krate_trimmed_path}; +use clap::Parser; +use serde::Serialize; +use std::fs::File; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::time::Duration; +mod common; + +#[derive(Debug, Parser)] +#[command(version, about, long_about = None)] +struct TimerArgs { + /// Sets a custom config file + #[arg(short, long, value_name = "FILE")] + out_path: PathBuf, + + /// Ignore compiling the current directory + #[arg(short, long)] + skip_current: bool, + + /// The paths of additional paths to visit beyond the current subtree + #[arg(short, long, value_name = "DIR")] + also_visit: Vec, + + /// The names or paths of files to ignore + #[arg(short, long)] + ignore: Vec, +} + +/// The number of untimed runs to do before starting timed runs. +/// We need at least one warm-up run to make sure crates are fetched & cached in +/// the local `.cargo/registry` folder. Otherwise the first run will be significantly slower. +const WARMUP_RUNS: usize = 1; +const TIMED_RUNS: usize = 10; + +fn main() { + let args = TimerArgs::parse(); + + let (mut to_visit, mut res) = (vec![], vec![]); + to_visit.extend(args.also_visit.into_iter().rev()); + if !args.skip_current { + let current_directory = std::env::current_dir().expect("should be run in a directory"); + to_visit.push(current_directory); + } + + let mut out_ser = serde_json::Serializer::pretty(File::create(&args.out_path).unwrap()); + let run_start = std::time::Instant::now(); + + // recursively visit subdirectories to time the compiler on all rust projects + while let Some(next) = to_visit.pop() { + let next = next.canonicalize().unwrap(); + let path_to_toml = next.join("Cargo.toml"); + + if path_to_toml.exists() && path_to_toml.is_file() { + // in rust crate so we want to profile it + println!("[!] profiling in {}", krate_trimmed_path(&next)); + let new_res = profile_on_crate(&next); + new_res.serialize(&mut out_ser).unwrap(); + res.push(new_res); + } else { + // we want want to recur and visit all directories that aren't explicitly ignored + to_visit.extend(std::fs::read_dir(next).unwrap().filter_map(|entry| { + if let Ok(entry) = entry { + let path = entry.path(); + if path.is_dir() && !args.ignore.iter().any(|ignored| path.ends_with(ignored)) { + return Some(path); + } + } + None + })); + } + } + + println!("[!] total info is {:?}", aggregate_aggregates(&res)); + + print!("\t [*] run took {:?}", run_start.elapsed()); +} + +/// Profile a crate by running a certain number of untimed warmup runs and then +/// a certain number of timed runs, returning aggregates of the timing results. +fn profile_on_crate(absolute_path: &std::path::PathBuf) -> AggrResult { + let _warmup_results = (0..WARMUP_RUNS) + .map(|i| { + print!("\t[*] running warmup {}/{WARMUP_RUNS}", i + 1); + let _ = std::io::stdout().flush(); + let res = run_command_in(absolute_path); + println!(" -- {res:?}"); + res + }) + .collect::>(); + + let timed_results = (0..TIMED_RUNS) + .map(|i| { + print!("\t[*] running timed run {}/{TIMED_RUNS}", i + 1); + let _ = std::io::stdout().flush(); + let res = run_command_in(absolute_path); + println!(" -- {res:?}"); + res + }) + .collect::>(); + + let aggr = aggregate_results(absolute_path, &timed_results); + println!("\t[!] results for {absolute_path:?} are in! {aggr:?}"); + + aggr +} + +type RunResult = Duration; +/// Run `cargo kani` in a crate and parse out the compiler timing info outputted +/// by the `TIME_COMPILER` environment variable. +fn run_command_in(absolute_path: &Path) -> RunResult { + // `cargo clean` first to ensure the compiler is fully run again + let _ = Command::new("cargo") + .current_dir(absolute_path) + .arg("clean") + .stdout(Stdio::null()) + .output() + .expect("cargo clean should succeed"); + + // do the actual compiler run (using `--only-codegen` to save time) + let kani_output = Command::new("cargo") + .current_dir(absolute_path) + .arg("kani") + .arg("--only-codegen") + .env("TIME_COMPILER", "true") + .output() + .expect("cargo kani should succeed"); + + // parse the output bytes into a string + let out_str = String::from_utf8(kani_output.stdout).expect("utf8 conversion should succeed"); + + if !kani_output.status.success() { + println!( + "the `TIME_COMPILER=true cargo kani --only-codegen` command failed in {absolute_path:?} with output -- {out_str:?}" + ); + panic!("cargo kani command failed"); + } + + // parse that string for the compiler build information + // and if it's built multiple times (which could happen in a workspace with multiple crates), + // we just sum up the total time + out_str.split("\n").filter(|line| line.starts_with("BUILT")).map(extract_duration).sum() +} + +fn extract_duration(s: &str) -> Duration { + let s = &s[s.find("IN").unwrap_or(0)..]; + let micros = s.chars().filter(|c| c.is_ascii_digit()).collect::().parse().ok().unwrap(); + + Duration::from_micros(micros) +} + +fn aggregate_results(path: &Path, results: &[Duration]) -> AggrResult { + assert!(results.len() == TIMED_RUNS); + + // sort and calculate the subset of times in the interquartile range + let mut sorted = results.to_vec(); + sorted.sort(); + let iqr_bounds = (results.len() / 4, results.len() * 3 / 4); + let iqr_durations = sorted + .into_iter() + .enumerate() + .filter_map(|(i, v)| if i >= iqr_bounds.0 && i <= iqr_bounds.1 { Some(v) } else { None }) + .collect::>(); + + AggrResult::new(path.to_path_buf(), result_stats(&iqr_durations), result_stats(results)) +} + +/// Record the stats from a subset slice of timing runs. +fn result_stats(results: &[Duration]) -> Stats { + let avg = results.iter().sum::() / results.len().try_into().unwrap(); + let range = (*results.iter().min().unwrap(), *results.iter().max().unwrap()); + + let deviations = results.iter().map(|x| x.abs_diff(avg).as_micros().pow(2)).sum::(); + let std_dev = + Duration::from_micros((deviations / results.len() as u128).isqrt().try_into().unwrap()); + + Stats { avg, std_dev, range } +} diff --git a/tools/compiletest/src/header.rs b/tools/compiletest/src/header.rs index f49d4f64ce47..06f5637b35c4 100644 --- a/tools/compiletest/src/header.rs +++ b/tools/compiletest/src/header.rs @@ -24,6 +24,12 @@ pub struct TestProps { pub kani_panic_step: Option, } +impl Default for TestProps { + fn default() -> Self { + Self::new() + } +} + impl TestProps { pub fn new() -> Self { TestProps { diff --git a/tools/compiletest/src/main.rs b/tools/compiletest/src/main.rs index c0e599b725cf..8c0a2dd0b08d 100644 --- a/tools/compiletest/src/main.rs +++ b/tools/compiletest/src/main.rs @@ -125,9 +125,9 @@ pub fn parse_config(args: Vec) -> Config { match m.opt_str(nm) { Some(s) => PathBuf::from(&s), None => { - let mut root_folder = top_level().expect( - format!("Cannot find root directory. Please provide --{nm} option.").as_str(), - ); + let mut root_folder = top_level().unwrap_or_else(|| { + panic!("Cannot find root directory. Please provide --{nm} option.") + }); default.iter().for_each(|f| root_folder.push(f)); root_folder } @@ -147,9 +147,9 @@ pub fn parse_config(args: Vec) -> Config { let run_ignored = matches.opt_present("ignored"); let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode"); let timeout = matches.opt_str("timeout").map(|val| { - Duration::from_secs(u64::from_str(&val).expect(&format!( - "Unexpected timeout format. Expected a positive number but found {val}" - ))) + Duration::from_secs(u64::from_str(&val).unwrap_or_else(|_| { + panic!("Unexpected timeout format. Expected a positive number but found {val}") + })) }); Config { diff --git a/tools/compiletest/src/runtest.rs b/tools/compiletest/src/runtest.rs index d31e895eab6f..3467b092bb76 100644 --- a/tools/compiletest/src/runtest.rs +++ b/tools/compiletest/src/runtest.rs @@ -476,8 +476,9 @@ impl TestCx<'_> { (None, _) => { /* Test passed. Do nothing*/ } (Some(_), true) => { // Fix output but still fail the test so users know which ones were updated - fs::write(expected_path, output) - .expect(&format!("Failed to update file {}", expected_path.display())); + fs::write(expected_path, output).unwrap_or_else(|_| { + panic!("Failed to update file {}", expected_path.display()) + }); self.fatal_proc_rec( &format!("updated `{}` file, please review", expected_path.display()), proc_res, diff --git a/tools/kani-cov/src/args.rs b/tools/kani-cov/src/args.rs index c83b3516f388..11cfd5d7a73a 100644 --- a/tools/kani-cov/src/args.rs +++ b/tools/kani-cov/src/args.rs @@ -131,9 +131,9 @@ pub fn validate_args(args: &Args) -> Result<()> { } match args.command.as_ref().unwrap() { - Subcommand::Merge(merge_args) => merge::validate_merge_args(&merge_args)?, - Subcommand::Summary(summary_args) => summary::validate_summary_args(&summary_args)?, - Subcommand::Report(report_args) => report::validate_report_args(&report_args)?, + Subcommand::Merge(merge_args) => merge::validate_merge_args(merge_args)?, + Subcommand::Summary(summary_args) => summary::validate_summary_args(summary_args)?, + Subcommand::Report(report_args) => report::validate_report_args(report_args)?, }; Ok(()) diff --git a/tools/kani-cov/src/merge.rs b/tools/kani-cov/src/merge.rs index 2e8452393fb0..06a78d8f3845 100644 --- a/tools/kani-cov/src/merge.rs +++ b/tools/kani-cov/src/merge.rs @@ -45,11 +45,11 @@ fn parse_raw_results(paths: &Vec) -> Result> { let mut raw_results = Vec::with_capacity(paths.len()); for path in paths { let filename = path.to_string_lossy(); - let file = File::open(path).expect(&format!("could not open file {filename}")); + let file = File::open(path).unwrap_or_else(|_| panic!("could not open file {filename}")); let reader = BufReader::new(file); let result = serde_json::from_reader(reader) - .expect(&format!("could not deserialize file {filename}")); + .unwrap_or_else(|_| panic!("could not deserialize file {filename}")); raw_results.push(result); } Ok(raw_results) diff --git a/tools/scanner/src/analysis.rs b/tools/scanner/src/analysis.rs index 1c3907ae1918..08bcc7c04b04 100644 --- a/tools/scanner/src/analysis.rs +++ b/tools/scanner/src/analysis.rs @@ -1,19 +1,22 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -//! Provide different static analysis to be performed in the crate under compilation +//! Provide passes that perform intra-function analysis on the crate under compilation use crate::info; use csv::WriterBuilder; use graph_cycles::Cycles; use petgraph::graph::Graph; +use rustc_middle::ty::TyCtxt; +use rustc_smir::rustc_internal; use serde::{Serialize, Serializer, ser::SerializeStruct}; use stable_mir::mir::mono::Instance; use stable_mir::mir::visit::{Location, PlaceContext, PlaceRef}; use stable_mir::mir::{ - BasicBlock, Body, MirVisitor, Mutability, ProjectionElem, Safety, Terminator, TerminatorKind, + BasicBlock, Body, CastKind, MirVisitor, Mutability, NonDivergingIntrinsic, ProjectionElem, + Rvalue, Safety, Statement, StatementKind, Terminator, TerminatorKind, }; -use stable_mir::ty::{AdtDef, AdtKind, FnDef, GenericArgs, MirConst, RigidTy, Ty, TyKind}; +use stable_mir::ty::{Abi, AdtDef, AdtKind, FnDef, GenericArgs, MirConst, RigidTy, Ty, TyKind}; use stable_mir::visitor::{Visitable, Visitor}; use stable_mir::{CrateDef, CrateItem}; use std::collections::{HashMap, HashSet}; @@ -23,7 +26,7 @@ use std::path::{Path, PathBuf}; #[derive(Clone, Debug)] pub struct OverallStats { /// The key and value of each counter. - counters: Vec<(&'static str, usize)>, + pub counters: Vec<(&'static str, usize)>, /// TODO: Group stats per function. fn_stats: HashMap, } @@ -35,6 +38,7 @@ struct FnStats { has_unsafe_ops: Option, has_unsupported_input: Option, has_loop_or_iterator: Option, + is_public: Option, } impl FnStats { @@ -45,10 +49,17 @@ impl FnStats { has_unsafe_ops: None, has_unsupported_input: None, has_loop_or_iterator: None, + is_public: None, } } } +impl Default for OverallStats { + fn default() -> Self { + Self::new() + } +} + impl OverallStats { pub fn new() -> OverallStats { let all_items = stable_mir::all_local_items(); @@ -61,12 +72,12 @@ impl OverallStats { } pub fn store_csv(&self, base_path: PathBuf, file_stem: &str) { - let filename = format!("{}_overall", file_stem); + let filename = format!("{file_stem}_overall"); let mut out_path = base_path.parent().map_or(PathBuf::default(), Path::to_path_buf); out_path.set_file_name(filename); dump_csv(out_path, &self.counters); - let filename = format!("{}_functions", file_stem); + let filename = format!("{file_stem}_functions"); let mut out_path = base_path.parent().map_or(PathBuf::default(), Path::to_path_buf); out_path.set_file_name(filename); dump_csv(out_path, &self.fn_stats.values().collect::>()); @@ -222,34 +233,57 @@ impl OverallStats { .map(|def| { ( def.name(), - if recursions.recursive_fns.contains(&def) { "recursive" } else { "" }, + if recursions.recursive_fns.contains(def) { "recursive" } else { "" }, ) }) .collect::>(), ); } + + /// Iterate over all functions defined in this crate and log public vs private + pub fn public_fns(&mut self, tcx: &TyCtxt) { + let all_items = stable_mir::all_local_items(); + let (public_fns, private_fns) = all_items + .into_iter() + .filter_map(|item| { + let kind = item.ty().kind(); + if !kind.is_fn() { + return None; + }; + let int_def_id = rustc_internal::internal(*tcx, item.def_id()); + let is_public = tcx.visibility(int_def_id).is_public() + || tcx.visibility(int_def_id).is_visible_locally(); + self.fn_stats.get_mut(&item).unwrap().is_public = Some(is_public); + Some((item, is_public)) + }) + .partition::, _>(|(_, is_public)| *is_public); + self.counters.extend_from_slice(&[ + ("public_fns", public_fns.len()), + ("private_fns", private_fns.len()), + ]); + } } macro_rules! fn_props { ($(#[$attr:meta])* - struct $name:ident { + $vis:vis struct $name:ident { $( $(#[$prop_attr:meta])* $prop:ident, )+ }) => { #[derive(Debug)] - struct $name { + $vis struct $name { fn_name: String, $($(#[$prop_attr])* $prop: usize,)+ } impl $name { - const fn num_props() -> usize { + $vis const fn num_props() -> usize { [$(stringify!($prop),)+].len() } - fn new(fn_name: String) -> Self { + $vis fn new(fn_name: String) -> Self { Self { fn_name, $($prop: 0,)+} } } @@ -369,7 +403,7 @@ impl Visitor for TypeVisitor<'_> { } } -fn dump_csv(mut out_path: PathBuf, data: &[T]) { +pub(crate) fn dump_csv(mut out_path: PathBuf, data: &[T]) { out_path.set_extension("csv"); info(format!("Write file: {out_path:?}")); let mut writer = WriterBuilder::new().delimiter(b';').from_path(&out_path).unwrap(); @@ -379,17 +413,23 @@ fn dump_csv(mut out_path: PathBuf, data: &[T]) { } fn_props! { - struct FnUnsafeOperations { + pub struct FnUnsafeOperations { inline_assembly, /// Dereference a raw pointer. /// This is also counted when we access a static variable since it gets translated to a raw pointer. unsafe_dereference, - /// Call an unsafe function or method. + /// Call an unsafe function or method including C-FFI. unsafe_call, /// Access or modify a mutable static variable. unsafe_static_access, /// Access fields of unions. unsafe_union_access, + /// Invoke external functions (this is a subset of `unsafe_call`. + extern_call, + /// Transmute operations. + transmute, + /// Cast raw pointer to reference. + unsafe_cast, } } @@ -419,9 +459,19 @@ impl MirVisitor for BodyVisitor<'_> { fn visit_terminator(&mut self, term: &Terminator, location: Location) { match &term.kind { TerminatorKind::Call { func, .. } => { - let fn_sig = func.ty(self.body.locals()).unwrap().kind().fn_sig().unwrap(); - if fn_sig.value.safety == Safety::Unsafe { + let TyKind::RigidTy(RigidTy::FnDef(fn_def, _)) = + func.ty(self.body.locals()).unwrap().kind() + else { + return self.super_terminator(term, location); + }; + let fn_sig = fn_def.fn_sig().skip_binder(); + if fn_sig.safety == Safety::Unsafe { self.props.unsafe_call += 1; + if !matches!(fn_sig.abi, Abi::Rust | Abi::RustCold | Abi::RustCall) + && !fn_def.has_body() + { + self.props.extern_call += 1; + } } } TerminatorKind::InlineAsm { .. } => self.props.inline_assembly += 1, @@ -430,6 +480,34 @@ impl MirVisitor for BodyVisitor<'_> { self.super_terminator(term, location) } + fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) { + if let Rvalue::Cast(cast_kind, operand, ty) = rvalue { + match cast_kind { + CastKind::Transmute => { + self.props.transmute += 1; + } + _ => { + let operand_ty = operand.ty(self.body.locals()).unwrap(); + if ty.kind().is_ref() && operand_ty.kind().is_raw_ptr() { + self.props.unsafe_cast += 1; + } + } + } + }; + self.super_rvalue(rvalue, location); + } + + fn visit_statement(&mut self, stmt: &Statement, location: Location) { + if matches!( + &stmt.kind, + StatementKind::Intrinsic(NonDivergingIntrinsic::CopyNonOverlapping(_)) + ) { + // Treat this as invoking the copy intrinsic. + self.props.unsafe_call += 1; + } + self.super_statement(stmt, location) + } + fn visit_projection_elem( &mut self, place: PlaceRef, @@ -674,9 +752,9 @@ impl Recursion { } } -struct FnCallVisitor<'a> { - body: &'a Body, - fns: Vec, +pub struct FnCallVisitor<'a> { + pub body: &'a Body, + pub fns: Vec, } impl MirVisitor for FnCallVisitor<'_> { diff --git a/tools/scanner/src/call_graph.rs b/tools/scanner/src/call_graph.rs new file mode 100644 index 000000000000..a8fa3baa3b4f --- /dev/null +++ b/tools/scanner/src/call_graph.rs @@ -0,0 +1,110 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Provide passes that perform inter-function analysis on the crate under compilation. +//! +//! This module also includes a `CallGraph` structure to help the analysis. +//! For now, we build the CallGraph as part of the pass, but as we add more analysis, +//! the call-graph could be reused by different analysis. + +use crate::analysis::{FnCallVisitor, FnUnsafeOperations, OverallStats}; +use stable_mir::mir::{MirVisitor, Safety}; +use stable_mir::ty::{FnDef, RigidTy, Ty, TyKind}; +use stable_mir::{CrateDef, CrateDefType}; +use std::collections::hash_map::Entry; +use std::collections::{HashMap, VecDeque}; +use std::hash::{Hash, Hasher}; +use std::path::PathBuf; + +impl OverallStats { + /// Iterate over all functions defined in this crate and log any unsafe operation. + /// Distance indicates how many degrees of separation the function has to unsafe code, if any? + /// - `None` if this function is indeed safe. + /// - 0 if this function contains unsafe code (including invoking unsafe fns). + /// - 1 if this function calls a safe abstraction. + /// - 2+ if this function calls other functions that call safe abstractions. + pub fn unsafe_distance(&mut self, filename: PathBuf) { + let all_items = stable_mir::all_local_items(); + let mut queue = + all_items.into_iter().filter_map(|item| Node::try_new(item.ty())).collect::>(); + // Build call graph + let mut call_graph = CallGraph::default(); + while let Some(node) = queue.pop() { + if let Entry::Vacant(e) = call_graph.nodes.entry(node.def) { + e.insert(node); + let Some(body) = node.def.body() else { + continue; + }; + let mut visitor = FnCallVisitor { body: &body, fns: vec![] }; + visitor.visit_body(&body); + queue.extend(visitor.fns.iter().map(|def| Node::try_new(def.ty()).unwrap())); + for callee in &visitor.fns { + call_graph.rev_edges.entry(*callee).or_default().push(node.def) + } + call_graph.edges.insert(node.def, visitor.fns); + } + } + + // Calculate the distance between unsafe functions and functions with unsafe operation. + let mut queue = call_graph + .nodes + .values() + .filter_map(|node| node.has_unsafe.then_some((node.def, 0))) + .collect::>(); + let mut visited: HashMap = HashMap::from_iter(queue.iter().cloned()); + while let Some(current) = queue.pop_front() { + for caller in call_graph.rev_edges.entry(current.0).or_default() { + if !visited.contains_key(caller) { + let distance = current.1 + 1; + visited.insert(*caller, distance); + queue.push_back((*caller, distance)) + } + } + } + let krate = stable_mir::local_crate(); + let transitive_unsafe = visited + .into_iter() + .filter_map(|(def, distance)| (def.krate() == krate).then_some((def.name(), distance))) + .collect::>(); + self.counters.push(("transitive_unsafe", transitive_unsafe.len())); + crate::analysis::dump_csv(filename, &transitive_unsafe); + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +struct Node { + def: FnDef, + is_unsafe: bool, + has_unsafe: bool, +} + +impl Node { + fn try_new(ty: Ty) -> Option { + let kind = ty.kind(); + let TyKind::RigidTy(RigidTy::FnDef(def, _)) = kind else { + return None; + }; + let has_unsafe = if let Some(body) = def.body() { + let unsafe_ops = FnUnsafeOperations::new(def.name()).collect(&body); + unsafe_ops.has_unsafe() + } else { + true + }; + let fn_sig = kind.fn_sig().unwrap(); + let is_unsafe = fn_sig.skip_binder().safety == Safety::Unsafe; + Some(Node { def, is_unsafe, has_unsafe }) + } +} + +impl Hash for Node { + fn hash(&self, state: &mut H) { + self.def.hash(state) + } +} + +#[derive(Default, Debug)] +struct CallGraph { + nodes: HashMap, + edges: HashMap>, + rev_edges: HashMap>, +} diff --git a/tools/scanner/src/lib.rs b/tools/scanner/src/lib.rs index 7f9555781ccf..83ab1207ecfa 100644 --- a/tools/scanner/src/lib.rs +++ b/tools/scanner/src/lib.rs @@ -10,7 +10,8 @@ #![feature(rustc_private)] -mod analysis; +pub mod analysis; +pub mod call_graph; extern crate rustc_driver; extern crate rustc_interface; @@ -23,7 +24,7 @@ extern crate stable_mir; use crate::analysis::OverallStats; use rustc_middle::ty::TyCtxt; use rustc_session::config::OutputType; -use rustc_smir::{run_with_tcx, rustc_internal}; +use rustc_smir::run_with_tcx; use stable_mir::CompilerError; use std::ops::ControlFlow; use std::path::{Path, PathBuf}; @@ -42,7 +43,7 @@ pub fn run_all(rustc_args: Vec, verbose: bool) -> ExitCode { /// Executes a compilation and run the analysis that were requested. pub fn run_analyses(rustc_args: Vec, analyses: &[Analysis], verbose: bool) -> ExitCode { VERBOSE.store(verbose, Ordering::Relaxed); - let result = run_with_tcx!(rustc_args, |tcx| analyze_crate(tcx, analyses)); + let result = run_with_tcx!(&rustc_args, |tcx| analyze_crate(tcx, analyses)); if result.is_ok() || matches!(result, Err(CompilerError::Skipped)) { ExitCode::SUCCESS } else { @@ -65,16 +66,23 @@ pub enum Analysis { FnLoops, /// Collect information about recursion via direct calls. Recursion, + /// Collect information about transitive usage of unsafe. + UnsafeDistance, + /// Collect information about function visibility. + PublicFns, } fn info(msg: String) { if VERBOSE.load(Ordering::Relaxed) { - eprintln!("[INFO] {}", msg); + eprintln!("[INFO] {msg}"); } } /// This function invoke the required analyses in the given order. fn analyze_crate(tcx: TyCtxt, analyses: &[Analysis]) -> ControlFlow<()> { + if stable_mir::local_crate().name == "build_script_build" { + return ControlFlow::Continue(()); + } let object_file = tcx.output_filenames(()).path(OutputType::Object); let base_path = object_file.as_path().to_path_buf(); // Use name for now to make it more friendly. Change to base_path.file_stem() to avoid conflict. @@ -96,6 +104,8 @@ fn analyze_crate(tcx: TyCtxt, analyses: &[Analysis]) -> ControlFlow<()> { Analysis::UnsafeOps => crate_stats.unsafe_operations(out_path), Analysis::FnLoops => crate_stats.loops(out_path), Analysis::Recursion => crate_stats.recursion(out_path), + Analysis::UnsafeDistance => crate_stats.unsafe_distance(out_path), + Analysis::PublicFns => crate_stats.public_fns(&tcx), } } crate_stats.store_csv(base_path, &file_stem);