diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7635b0adccf5..84fd2d144fff 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,35 +1,11 @@ - -### Description of changes: - -Describe Kani's current behavior and how your code changes that behavior. If there are no issues this PR is resolving, explain why this change is necessary. - -### Resolved issues: +> Please ensure your PR description includes the following: +> 1. A description of how your changes improve Kani. +> 2. Some context on the problem you are solving. +> 3. A list of issues that are resolved by this PR. +> 4. If you had to perform any manual test, please describe them. +> +> **Make sure you remove this list from the final PR description.** Resolves #ISSUE-NUMBER -### Related RFC: - - -Optional #ISSUE-NUMBER. - -### Call-outs: - - - -### Testing: - -* How is this change tested? - -* Is this a refactor change? - -### Checklist -- [ ] Each commit message has a non-empty body, explaining why the change was made -- [ ] Methods or procedures are documented -- [ ] Regression or unit tests are included, or existing tests cover the modified code -- [ ] My PR is restricted to a single feature or bugfix - By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. diff --git a/.github/actions/build-bundle/action.yml b/.github/actions/build-bundle/action.yml new file mode 100644 index 000000000000..8ecefd545e22 --- /dev/null +++ b/.github/actions/build-bundle/action.yml @@ -0,0 +1,92 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +name: Build bundle +description: "Build the Kani bundle for the current architecture and OS. The inputs must match the worker architecture." +inputs: + arch: + description: "Current architecture tuple" + os: + description: "Current operating system" +outputs: + version: + description: "The bundle version (latest or =crate_version)" + value: ${{ steps.set_output.outputs.version }} + crate_version: + description: "The current crate version" + value: ${{ steps.set_output.outputs.crate_version }} + bundle: + description: "The bundle file name" + value: ${{ steps.set_output.outputs.bundle }} + package: + description: "The package file name" + value: ${{ steps.set_output.outputs.package }} +runs: + using: composite + steps: + - name: Export crate version + shell: bash + run: | + echo "CRATE_VERSION=$(cargo pkgid | cut -d@ -f2)" >> $GITHUB_ENV + + - name: Export tag version + shell: bash + if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/kani-') }} + run: | + # GITHUB_REF is refs/tags/kani-0.1.0 + echo "VERSION=$(echo ${{ github.ref }} | cut -d "-" -f 2)" >> $GITHUB_ENV + + - name: Check Version + shell: bash + if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/kani-') }} + run: | + # Validate git tag & Cargo.toml are in sync on version number + if [[ ${{ env.CRATE_VERSION }} != ${{ env.VERSION }} ]]; then + echo "Git tag ${{env.VERSION}} did not match crate version ${{env.CRATE_VERSION}}" + exit 1 + fi + + - name: Export latest version + shell: bash + if: ${{ !startsWith(github.ref, 'refs/tags/kani-') }} + run: | + echo "VERSION=latest" >> $GITHUB_ENV + + - name: Build bundle + shell: bash + run: | + cargo bundle -- ${{ env.VERSION }} + echo "BUNDLE=kani-${{ env.VERSION }}-${{ inputs.arch }}.tar.gz" >> $GITHUB_ENV + + - name: Build package + shell: bash + run: | + cargo package -p kani-verifier + mv target/package/kani-verifier-${{ env.CRATE_VERSION }}.crate ${{ inputs.os }}-kani-verifier.crate + echo "PKG=${{ inputs.os }}-kani-verifier.crate" >> $GITHUB_ENV + + - name: Upload bundle + uses: actions/upload-artifact@v3 + with: + name: ${{ env.BUNDLE }} + path: ${{ env.BUNDLE }} + if-no-files-found: error + # Aggressively short retention: we don't really need these + retention-days: 3 + + - name: Upload kani-verifier pkg + uses: actions/upload-artifact@v3 + with: + name: ${{ env.PKG }} + path: ${{ env.PKG }} + if-no-files-found: error + # Aggressively short retention: we don't really need these + retention-days: 3 + + - name: Export output + shell: bash + id: set_output + run: | + echo "version=${{ env.VERSION }}" >> ${GITHUB_OUTPUT} + echo "crate_version=${{ env.CRATE_VERSION }}" >> ${GITHUB_OUTPUT} + echo "bundle=${{ env.BUNDLE }}" >> ${GITHUB_OUTPUT} + echo "package=${{ env.PKG }}" >> ${GITHUB_OUTPUT} diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 940a8436da05..4adc7fd5f1e2 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -1,6 +1,7 @@ # Copyright Kani Contributors # SPDX-License-Identifier: Apache-2.0 OR MIT name: Setup Kani Dependencies +description: "Setup the machine to run Kani. Install rustup, dependencies and sync submodules." inputs: os: description: In which Operating System is this running @@ -12,12 +13,24 @@ inputs: runs: using: composite steps: - - name: Install dependencies - run: cd ${{ inputs.kani_dir }} && ./scripts/setup/${{ inputs.os }}/install_deps.sh + - name: Remove unnecessary software to free up disk space + if: contains(fromJSON('["ubuntu-20.04","ubuntu-22.04"]'), inputs.os) shell: bash + run: | + # inspired by https://github.com/easimon/maximize-build-space/blob/master/action.yml + df -h + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /usr/local/.ghcup + df -h - name: Install Rust toolchain - run: cd ${{ inputs.kani_dir }} && ./scripts/setup/install_rustup.sh + run: | + cd ${{ inputs.kani_dir }} + ./scripts/setup/install_rustup.sh + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + shell: bash + + - name: Install dependencies + run: cd ${{ inputs.kani_dir }} && ./scripts/setup/${{ inputs.os }}/install_deps.sh shell: bash - name: Update submodules diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000000..16dceb3366d9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,21 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" + groups: + cargo: + update-types: + - "minor" + - "patch" diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000000..1195ee4a14d2 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,13 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# Configuration for auto-labeling PRs +# +# Note that we enable dot, so "**" matches all files in a folder + +Z-BenchCI: +- any: + - changed-files: + - 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'] diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index bf852429459c..3ae9d192f376 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -16,7 +16,7 @@ jobs: audit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v1 with: arguments: --all-features --workspace diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml new file mode 100644 index 000000000000..ad0bbf81f2c2 --- /dev/null +++ b/.github/workflows/bench.yml @@ -0,0 +1,86 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# Run performance benchmarks comparing two different Kani versions. +# This workflow takes much longer than other workflows, so we don't run it by default. +# This workflow will run when: +# - Changes are pushed to 'main'. +# - Triggered by another workflow +name: Kani Performance Benchmarks +on: + push: + branches: + - 'main' + workflow_call: + +jobs: + perf-benchcomp: + runs-on: ubuntu-20.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-20.04 + kani_dir: old + + - name: Set up Kani Dependencies (new variant) + uses: ./new/.github/actions/setup + with: + os: ubuntu-20.04 + kani_dir: new + + - name: Build Kani (new variant) + run: pushd new && cargo build-dev + + - name: Build Kani (old variant) + run: pushd old && cargo build-dev + + - name: Copy benchmarks from new to old + run: rm -rf ./old/tests/perf ; cp -r ./new/tests/perf ./old/tests/ + + - name: Run benchcomp + run: | + new/tools/benchcomp/bin/benchcomp \ + --config new/tools/benchcomp/configs/perf-regression.yaml \ + run + new/tools/benchcomp/bin/benchcomp \ + --config new/tools/benchcomp/configs/perf-regression.yaml \ + collate + + - name: Perf Regression Results Table + run: | + new/tools/benchcomp/bin/benchcomp \ + --config new/tools/benchcomp/configs/perf-regression.yaml \ + visualize --only dump_markdown_results_table >> "$GITHUB_STEP_SUMMARY" + + - name: Run other visualizations + run: | + new/tools/benchcomp/bin/benchcomp \ + --config new/tools/benchcomp/configs/perf-regression.yaml \ + visualize --except dump_markdown_results_table diff --git a/.github/workflows/cbmc-latest.yml b/.github/workflows/cbmc-latest.yml index a53f8d38b899..9cb8082ae4b7 100644 --- a/.github/workflows/cbmc-latest.yml +++ b/.github/workflows/cbmc-latest.yml @@ -19,10 +19,10 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [macos-11, ubuntu-20.04, ubuntu-22.04] + os: [macos-12, ubuntu-20.04, ubuntu-22.04] steps: - name: Checkout Kani under "kani" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: kani @@ -37,7 +37,7 @@ jobs: run: cargo build-dev - name: Checkout CBMC under "cbmc" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: diffblue/cbmc path: cbmc @@ -58,7 +58,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout Kani under "kani" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: kani @@ -73,7 +73,7 @@ jobs: run: cargo build-dev -- --release - name: Checkout CBMC under "cbmc" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: diffblue/cbmc path: cbmc diff --git a/.github/workflows/cbmc-update.yml b/.github/workflows/cbmc-update.yml new file mode 100644 index 000000000000..e5f33e935e09 --- /dev/null +++ b/.github/workflows/cbmc-update.yml @@ -0,0 +1,85 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +name: Attempt CBMC update + +on: + schedule: + - cron: "30 4 * * Mon" # Run this every Monday at 04:30 UTC + workflow_dispatch: # Allow manual dispatching for a custom branch / tag. + +permissions: + checks: write + contents: write + issues: write + pull-requests: write + +jobs: + create-cargo-update-pr: + runs-on: ubuntu-22.04 + steps: + - name: Checkout Kani + uses: actions/checkout@v4 + + - name: Setup Kani Dependencies + uses: ./.github/actions/setup + with: + os: ubuntu-22.04 + + - name: Compare CBMC versions and determine next step + env: + GH_TOKEN: ${{ github.token }} + run: | + grep ^CBMC_VERSION kani-dependencies >> $GITHUB_ENV + CBMC_LATEST=$(gh -R diffblue/cbmc release list | grep Latest | awk '{print $1}' | cut -f2 -d-) + echo "CBMC_LATEST=$CBMC_LATEST" >> $GITHUB_ENV + # check whether the version has changed at all + if [ x$CBMC_LATEST = x$CBMC_VERSION ] ; then + echo "next_step=none" >> $GITHUB_ENV + # check whether we already have an existing issue for a failing + # upgrade + elif gh issue list -S \ + "CBMC upgrade to $CBMC_LATEST failed" \ + --json number,title | grep title ; then + echo "next_step=none" >> $GITHUB_ENV + # check whether we already have a branch (and thus: a PR) for a + # successful upgrade + elif ! git ls-remote --exit-code origin cbmc-$CBMC_LATEST ; then + CBMC_LATEST_MAJOR=$(echo $CBMC_LATEST | cut -f1 -d.) + CBMC_LATEST_MINOR=$(echo $CBMC_LATEST | cut -f2 -d.) + sed -i "s/^CBMC_MAJOR=.*/CBMC_MAJOR=\"$CBMC_MAJOR\"/" kani-dependencies + sed -i "s/^CBMC_MINOR=.*/CBMC_MINOR=\"$CBMC_MINOR\"/" kani-dependencies + sed -i "s/^CBMC_VERSION=.*/CBMC_VERSION=\"$CBMC_LATEST\"/" kani-dependencies + git diff + if ! ./scripts/kani-regression.sh ; then + echo "next_step=create_issue" >> $GITHUB_ENV + else + echo "next_step=create_pr" >> $GITHUB_ENV + fi + # we already have a PR, nothing to be done + else + echo "next_step=none" >> $GITHUB_ENV + fi + + - name: Create Pull Request + if: ${{ env.next_step == 'create_pr' }} + uses: peter-evans/create-pull-request@v5 + with: + commit-message: Upgrade CBMC from ${{ env.CBMC_VERSION }} to ${{ env.CBMC_LATEST }} + branch: cbmc-${{ env.CBMC_LATEST }} + delete-branch: true + title: 'Automatic upgrade of CBMC from ${{ env.CBMC_VERSION }} to ${{ env.CBMC_LATEST }}' + body: > + Upgrade CBMC to its latest release. + + - name: Create Issue + if: ${{ env.next_step == 'create_issue' }} + uses: dacbd/create-issue-action@main + with: + token: ${{ github.token }} + title: 'CBMC upgrade to ${{ env.CBMC_LATEST }} failed' + body: > + Updating CBMC from ${{ evn.CBMC_VERSION }} to ${{ env.CBMC_LATEST }} failed. + + The failed automated run + [can be found here.](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) diff --git a/.github/workflows/extra_jobs.yml b/.github/workflows/extra_jobs.yml new file mode 100644 index 000000000000..5d92f3fff53c --- /dev/null +++ b/.github/workflows/extra_jobs.yml @@ -0,0 +1,49 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# Workflow that execute jobs based on the files that were changed or some label configuration. +# +# The first job in this workflow will auto label the PR, while the following jobs will conditionally +# run according to the auto-label result. +# +# This workflow runs on `pull_request_target` because the labeler needs extra write permission. +# Thus, we keep this job minimal, and the only actions used are from the same verified publisher. +# +# Other jobs should not require extra permissions, so be careful when adding new jobs to not propagate write +# permissions. +# +# Note that this also means that the workflow version run is the one currently in `main`, +# not the one from the PR. This is only relevant if a PR is changing this workflow. +# +# See for more details. + +name: Kani Extra +on: pull_request_target + +jobs: + # Keep this job minimal since it requires extra permission + auto-label: + name: Auto Label + permissions: + contents: read + pull-requests: write + outputs: + all-labels: ${{ steps.labeler.outputs.all-labels }} + new-labels: ${{ steps.labeler.outputs.new-labels }} + runs-on: ubuntu-latest + steps: + - name: Checkout Kani + uses: actions/checkout@v4 + + - name: Label PR + id: labeler + uses: actions/labeler@v5 + with: + dot: true + + verification-bench: + name: Verification Benchmarks + needs: auto-label + permissions: {} + if: contains(needs.auto-label.outputs.all-labels, 'Z-BenchCI') + uses: ./.github/workflows/bench.yml diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml index 6a8267b09231..5ab7dcb1b9c3 100644 --- a/.github/workflows/format-check.yml +++ b/.github/workflows/format-check.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Kani - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Execute copyright check run: ./scripts/ci/run-copyright-check.sh @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout Kani - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Kani Dependencies uses: ./.github/actions/setup diff --git a/.github/workflows/kani-m1.yml b/.github/workflows/kani-m1.yml new file mode 100644 index 000000000000..36f2b615c4aa --- /dev/null +++ b/.github/workflows/kani-m1.yml @@ -0,0 +1,30 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +# Run the regression job on Apple M1 only on commits to `main` +name: Kani CI M1 +on: + push: + branches: + - 'main' + +env: + RUST_BACKTRACE: 1 + +jobs: + regression: + runs-on: macos-13-xlarge + steps: + - name: Checkout Kani + uses: actions/checkout@v4 + + - name: Setup Kani Dependencies + uses: ./.github/actions/setup + with: + os: macos-13-xlarge + + - name: Build Kani + run: cargo build-dev + + - name: Execute Kani regression + run: ./scripts/kani-regression.sh diff --git a/.github/workflows/kani.yml b/.github/workflows/kani.yml index 81403735e1cd..e866eaf635de 100644 --- a/.github/workflows/kani.yml +++ b/.github/workflows/kani.yml @@ -17,10 +17,10 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [macos-11, ubuntu-20.04, ubuntu-22.04] + os: [macos-12, ubuntu-20.04, ubuntu-22.04] steps: - name: Checkout Kani - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Kani Dependencies uses: ./.github/actions/setup @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout Kani - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Kani Dependencies uses: ./.github/actions/setup @@ -58,7 +58,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout Kani - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install benchcomp dependencies run: | @@ -81,7 +81,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout Kani - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Kani Dependencies uses: ./.github/actions/setup @@ -93,6 +93,8 @@ jobs: - name: Execute Kani performance tests run: ./scripts/kani-perf.sh + env: + RUST_TEST_THREADS: 1 bookrunner: runs-on: ubuntu-20.04 @@ -100,7 +102,7 @@ jobs: contents: write steps: - name: Checkout Kani - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Kani Dependencies uses: ./.github/actions/setup @@ -142,192 +144,3 @@ jobs: branch: gh-pages folder: docs/book/ single-commit: true - - releasebundle-macos: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [macos-11] - include: - - os: macos-11 - artifact: kani-latest-x86_64-apple-darwin.tar.gz - steps: - - name: Checkout Kani - uses: actions/checkout@v3 - - - name: Setup Kani Dependencies - uses: ./.github/actions/setup - with: - os: ${{ matrix.os }} - - - name: Build release bundle - run: | - cargo bundle -- latest - cargo package -p kani-verifier - - # We can't run macos in a container, so we can only test locally. - # Hopefully any dependency issues won't be unique to macos. - - name: Local install test - if: ${{ matrix.os == 'macos-11' }} - run: | - cargo install --path ./target/package/kani-verifier-*[^e] - cargo-kani setup --use-local-bundle ./${{ matrix.artifact }} - (cd tests/cargo-kani/simple-lib && cargo kani) - (cd tests/cargo-kani/simple-visualize && cargo kani) - (cd tests/cargo-kani/build-rs-works && cargo kani) - - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - name: ${{ matrix.artifact }} - path: ${{ matrix.artifact }} - if-no-files-found: error - # Aggressively short retention: we don't really need these - retention-days: 3 - - releasebundle-ubuntu: - runs-on: ubuntu-20.04 - container: - image: ubuntu:18.04 - steps: - # This is required before checkout because the container does not - # have Git installed, so cannot run checkout action. The checkout - # action requires Git >=2.18, so use the Git maintainers' PPA. - - name: Install system dependencies - run: | - apt-get update - apt-get install -y software-properties-common apt-utils - add-apt-repository ppa:git-core/ppa - apt-get update - apt-get install -y \ - build-essential bash-completion curl lsb-release sudo g++ gcc flex \ - bison make patch git - curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused -fsSL \ - https://get.docker.com -o /tmp/install-docker.sh - bash /tmp/install-docker.sh - - - name: Checkout Kani - uses: actions/checkout@v3 - - - name: Setup Kani Dependencies - uses: ./.github/actions/setup - with: - os: ubuntu-18.04 - - - name: Build release bundle - run: | - PATH=/github/home/.cargo/bin:$PATH cargo bundle -- latest - PATH=/github/home/.cargo/bin:$PATH cargo package -p kani-verifier - - # -v flag: Use docker socket from host as we're running docker-in-docker - - name: Build container test - run: | - for tag in 20-04 20-04-alt 18-04; do - >&2 echo "Building test container for ${tag}" - docker build -t kani-$tag -f scripts/ci/Dockerfile.bundle-test-ubuntu-$tag . - done - - - name: Run installed tests - run: | - for tag in kani-20-04 kani-20-04-alt kani-18-04; do - for dir in simple-lib simple-visualize build-rs-works simple-kissat; do - >&2 echo "Tag $tag: running test $dir" - docker run -v /var/run/docker.sock:/var/run/docker.sock \ - -w /tmp/kani/tests/cargo-kani/$dir $tag cargo kani - done - docker run -v /var/run/docker.sock:/var/run/docker.sock \ - $tag cargo-kani setup \ - --use-local-bundle ./kani-latest-x86_64-unknown-linux-gnu.tar.gz - done - - # While the above test OS issues, now try testing with nightly as - # default: - docker run -v /var/run/docker.sock:/var/run/docker.sock \ - -w /tmp/kani/tests/cargo-kani/simple-lib kani-20-04 \ - bash -c "rustup default nightly && cargo kani" - - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - name: kani-latest-x86_64-unknown-linux-gnu.tar.gz - path: kani-latest-x86_64-unknown-linux-gnu.tar.gz - if-no-files-found: error - # Aggressively short retention: we don't really need these - retention-days: 3 - - perf-benchcomp: - runs-on: ubuntu-20.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" - # We want to compare with $NEW_REF~. But we can't know what - # that ref actually is until we clone the repository, so for - # now make it equal to $NEW_REF - echo "OLD_REF=${{ github.event.after }}" | tee -a "$GITHUB_ENV" - - - name: Save pull request HEAD and target to environment variables - if: ${{ github.event_name == 'pull_request' }} - 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@v3 - with: - path: ./old - ref: ${{ env.OLD_REF }} - fetch-depth: 2 - - - name: Check out HEAD~ of push event as 'old' variant - if: ${{ github.event_name == 'push' }} - run: pushd old && git checkout "${NEW_REF}^" - - - name: Check out Kani (new variant) - uses: actions/checkout@v3 - with: - path: ./new - ref: ${{ env.NEW_REF }} - - - name: Set up Kani Dependencies (old variant) - uses: ./old/.github/actions/setup - with: - os: ubuntu-20.04 - kani_dir: old - - - name: Set up Kani Dependencies (new variant) - uses: ./new/.github/actions/setup - with: - os: ubuntu-20.04 - kani_dir: new - - - name: Build Kani (new variant) - run: pushd new && cargo build-dev - - - name: Build Kani (old variant) - run: pushd old && cargo build-dev - - - name: Copy benchmarks from new to old - run: rm -rf ./old/tests/perf ; cp -r ./new/tests/perf ./old/tests/ - - - name: Run benchcomp - run: | - new/tools/benchcomp/bin/benchcomp \ - --config new/tools/benchcomp/configs/perf-regression.yaml \ - run - new/tools/benchcomp/bin/benchcomp \ - --config new/tools/benchcomp/configs/perf-regression.yaml \ - collate - - - name: Perf Regression Results Table - run: | - new/tools/benchcomp/bin/benchcomp \ - --config new/tools/benchcomp/configs/perf-regression.yaml \ - visualize --only dump_markdown_results_table >> "$GITHUB_STEP_SUMMARY" - - - name: Run other visualizations - run: | - new/tools/benchcomp/bin/benchcomp \ - --config new/tools/benchcomp/configs/perf-regression.yaml \ - visualize --except dump_markdown_results_table diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f6647e4bd68..e753320c158f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,109 +1,92 @@ # Copyright Kani Contributors # SPDX-License-Identifier: Apache-2.0 OR MIT -name: Release +# +# This workflow will build, and, optionally, release Kani bundles in this order. +# +# The release will create a draft release and upload the bundles to it, and it will only run when we push a new +# release tag (i.e.: tag named `kani-*`). +name: Release Bundle on: + pull_request: push: + branches: + - 'main' tags: - kani-* +env: + RUST_BACKTRACE: 1 + jobs: - Release: - name: Release - runs-on: ubuntu-20.04 + build_bundle_macos: + name: BuildBundle-MacOs + runs-on: macos-12 permissions: contents: write outputs: - version: ${{ steps.versioning.outputs.version }} - upload_url: ${{ steps.create_release.outputs.upload_url }} + version: ${{ steps.bundle.outputs.version }} + bundle: ${{ steps.bundle.outputs.bundle }} + package: ${{ steps.bundle.outputs.package }} + crate_version: ${{ steps.bundle.outputs.crate_version }} steps: - name: Checkout code - uses: actions/checkout@v3 - - - name: Get version - run: | - # pkgid is something like file:///home/ubuntu/kani#kani-verifier:0.1.0 - echo "CRATE_VERSION=$(cargo pkgid | cut -d@ -f2)" >> $GITHUB_ENV - # GITHUB_REF is refs/tags/kani-0.1.0 - echo "TAG_VERSION=$(echo ${{ github.ref }} | cut -d "-" -f 2)" >> $GITHUB_ENV - # Note that the above env vars get set for future steps, not this one - - name: Version Check - id: versioning - run: | - # Output for upload scripts to see - echo "version=${{ env.TAG_VERSION }}" >> $GITHUB_OUTPUT - # Validate git tag & Cargo.toml are in sync on version number - if [[ ${{ env.CRATE_VERSION }} != ${{ env.TAG_VERSION }} ]]; then - echo "Git tag ${{env.TAG_VERSION}} did not match crate version ${{env.CRATE_VERSION}}" - exit 1 - fi - - - name: Create release - id: create_release - uses: ncipollo/release-action@v1.12.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - name: kani-${{ env.TAG_VERSION }} - tag: kani-${{ env.TAG_VERSION }} - body: | - Kani Rust verifier release bundle version ${{ env.TAG_VERSION }}. - draft: true - - MacOs-Bundle: - name: MacOs-Bundle - needs: Release - runs-on: macos-11 - permissions: - contents: write - steps: - - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Kani Dependencies uses: ./.github/actions/setup with: - os: macos-11 - - - name: Build release bundle - run: | - cargo bundle + os: macos-12 - - name: Upload artifact - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build bundle + id: bundle + uses: ./.github/actions/build-bundle with: - upload_url: ${{ needs.Release.outputs.upload_url }} - asset_path: kani-${{ needs.Release.outputs.version }}-x86_64-apple-darwin.tar.gz - asset_name: kani-${{ needs.Release.outputs.version }}-x86_64-apple-darwin.tar.gz - asset_content_type: application/gzip - - Linux-Bundle: - name: Linux-Bundle - needs: Release + os: macos-12 + arch: x86_64-apple-darwin + + build_bundle_linux: + name: BuildBundle-Linux runs-on: ubuntu-20.04 + outputs: + # The bundle version (latest or the version to be released) + version: ${{ steps.bundle.outputs.version }} + bundle: ${{ steps.bundle.outputs.bundle }} + package: ${{ steps.bundle.outputs.package }} + crate_version: ${{ steps.bundle.outputs.crate_version }} container: + # Build using ubuntu 18 due to compatibility issues with older OS. image: ubuntu:18.04 - permissions: - contents: write + volumes: + - /usr/local:/mnt/host-local steps: + + - name: Free up docker disk space + run: | + # inspired by https://github.com/easimon/maximize-build-space/blob/master/action.yml + df -h + rm -r /mnt/host-local/lib/android /mnt/host-local/.ghcup + df -h + # This is required before checkout because the container does not - # have Git installed, so cannot run checkout action. The checkout - # action requires Git >=2.18, so use the Git maintainers' PPA. + # have Git installed, so cannot run checkout action. + # The checkout action requires Git >=2.18 and python 3.7, so use the Git maintainers' PPA. + # and the "deadsnakes" PPA, as the default version of python on ubuntu 22.04 is Python 3.10 - name: Install system dependencies run: | apt-get update apt-get install -y software-properties-common apt-utils add-apt-repository ppa:git-core/ppa + add-apt-repository ppa:deadsnakes/ppa apt-get update apt-get install -y \ build-essential bash-completion curl lsb-release sudo g++ gcc flex \ - bison make patch git - curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused -fsSL \ - https://get.docker.com -o /tmp/install-docker.sh - bash /tmp/install-docker.sh + bison make patch git python3.7 python3.7-dev python3.7-distutils + update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 1 + curl -s https://bootstrap.pypa.io/get-pip.py -o get-pip.py + python3 get-pip.py --force-reinstall + rm get-pip.py - - name: Checkout code + - name: Checkout Kani uses: actions/checkout@v3 - name: Setup Kani Dependencies @@ -111,23 +94,161 @@ jobs: with: os: ubuntu-18.04 - - name: Build release bundle + - name: Build bundle + id: bundle + uses: ./.github/actions/build-bundle + with: + os: linux + arch: x86_64-unknown-linux-gnu + + test_bundle: + name: TestBundle + needs: [build_bundle_macos, build_bundle_linux] + strategy: + matrix: + os: [macos-12, ubuntu-20.04, ubuntu-22.04] + include: + # Stores the output of the previous job conditional to the OS + - prev_job: ${{ needs.build_bundle_linux.outputs }} + - os: macos-12 + prev_job: ${{ needs.build_bundle_macos.outputs }} + runs-on: ${{ matrix.os }} + steps: + - name: Download bundle + uses: actions/download-artifact@v3 + with: + name: ${{ matrix.prev_job.bundle }} + + - name: Download kani-verifier crate + uses: actions/download-artifact@v3 + with: + name: ${{ matrix.prev_job.package }} + + - name: Check download run: | - PATH=/github/home/.cargo/bin:$PATH cargo bundle + ls -lh . - - name: Upload artifact - uses: actions/upload-release-asset@v1 + - name: Install from bundle + run: | + tar zxvf ${{ matrix.prev_job.package }} + cargo install --locked --path kani-verifier-${{ matrix.prev_job.crate_version }} + cargo kani setup --use-local-bundle ./${{ matrix.prev_job.bundle }} + + - name: Checkout tests + uses: actions/checkout@v4 + + - name: Run tests + # TODO: Customize compiletest to run custom kani. For now, just run a few cargo kani tests. + run: | + for dir in simple-lib simple-visualize build-rs-works simple-kissat; do + >&2 echo "Running test $dir" + pushd tests/cargo-kani/$dir + cargo kani + popd + done + + # This job will run tests for platforms that don't have a respective GitHub worker. + # For now, we only test for Ubuntu-18.04 so we don't bother using matrix to configure the platform. + test_alt_platform: + name: TestAlternativePlatforms + needs: [build_bundle_linux] + runs-on: ubuntu-22.04 + env: + PKG: ${{ needs.build_bundle_linux.outputs.package }} + BUNDLE: ${{ needs.build_bundle_linux.outputs.bundle }} + VERSION: ${{ needs.build_bundle_linux.outputs.crate_version }} + KANI_SRC: ./kani_src + steps: + - name: Checkout Kani + uses: actions/checkout@v4 + with: + path: ${{ env.KANI_SRC }} + + - name: Download bundle + uses: actions/download-artifact@v3 + with: + name: ${{ env.BUNDLE }} + + - name: Download kani-verifier crate + uses: actions/download-artifact@v3 + with: + name: ${{ env.PKG }} + + - name: Build container test + run: | + docker build -t kani-18-04 -f ${{ env.KANI_SRC }}/scripts/ci/Dockerfile.bundle-test-ubuntu-18-04 . + + - name: Run installed tests + run: | + for dir in simple-lib simple-visualize build-rs-works simple-kissat; do + >&2 echo "Running test $dir" + docker run -v /var/run/docker.sock:/var/run/docker.sock \ + -w /tmp/kani/tests/cargo-kani/$dir kani-18-04 cargo kani + done + + # While the above test OS issues, now try testing with nightly as + # default: + docker run -v /var/run/docker.sock:/var/run/docker.sock \ + -w /tmp/kani/tests/cargo-kani/simple-lib kani-18-04 \ + bash -c "rustup default nightly && cargo kani" + + kani_release: + if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/kani-') }} + name: Release + runs-on: ubuntu-20.04 + needs: [build_bundle_macos, build_bundle_linux, test_bundle, test_alt_platform] + permissions: + contents: write + outputs: + version: ${{ steps.versioning.outputs.version }} + upload_url: ${{ steps.create_release.outputs.upload_url }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get version + run: | + # pkgid is something like file:///home/ubuntu/kani#kani-verifier:0.1.0 + echo "CRATE_VERSION=$(cargo pkgid | cut -d@ -f2)" >> $GITHUB_ENV + # GITHUB_REF is something like refs/tags/kani-0.1.0 + echo "TAG_VERSION=$(echo ${{ github.ref }} | cut -d "-" -f 2)" >> $GITHUB_ENV + + - name: Check Version + id: versioning + run: | + # Validate git tag & Cargo.toml are in sync on version number + if [[ ${{ env.CRATE_VERSION }} != ${{ env.TAG_VERSION }} ]]; then + echo "Git tag ${{env.TAG_VERSION}} did not match crate version ${{env.CRATE_VERSION}}" + exit 1 + fi + echo "version=${{ env.TAG_VERSION }}" >> $GITHUB_OUTPUT + + - name: Download MacOS bundle + uses: actions/download-artifact@v3 + with: + name: ${{ needs.build_bundle_macos.outputs.bundle }} + + - name: Download Linux bundle + uses: actions/download-artifact@v3 + with: + name: ${{ needs.build_bundle_linux.outputs.bundle }} + + - name: Create release + id: create_release + uses: ncipollo/release-action@v1.13.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - upload_url: ${{ needs.Release.outputs.upload_url }} - asset_path: kani-${{ needs.Release.outputs.version }}-x86_64-unknown-linux-gnu.tar.gz - asset_name: kani-${{ needs.Release.outputs.version }}-x86_64-unknown-linux-gnu.tar.gz - asset_content_type: application/gzip - - Package-Docker: - name: 'Package Docker' - needs: Release + name: kani-${{ env.TAG_VERSION }} + tag: kani-${{ env.TAG_VERSION }} + artifacts: "${{ needs.build_bundle_linux.outputs.bundle }},${{ needs.build_bundle_macos.outputs.bundle }}" + body: | + Kani Rust verifier release bundle version ${{ env.TAG_VERSION }}. + draft: true + + package_docker: + name: Package Docker + needs: kani_release runs-on: ubuntu-20.04 permissions: contents: write @@ -137,7 +258,7 @@ jobs: target: x86_64-unknown-linux-gnu steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Kani Dependencies uses: ./.github/actions/setup @@ -150,7 +271,7 @@ jobs: cargo package -p kani-verifier - name: 'Login to GitHub Container Registry' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -163,18 +284,18 @@ jobs: OWNER: '${{ github.repository_owner }}' - name: Build and push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: context: . file: scripts/ci/Dockerfile.bundle-release-20-04 push: true github-token: ${{ secrets.GITHUB_TOKEN }} tags: | - ghcr.io/${{ env.OWNER_LC }}/kani-${{ env.os }}:${{ needs.Release.outputs.version }} + ghcr.io/${{ env.OWNER_LC }}/kani-${{ env.os }}:${{ needs.kani_release.outputs.version }} ghcr.io/${{ env.OWNER_LC }}/kani-${{ env.os }}:latest labels: | org.opencontainers.image.source=${{ github.repositoryUrl }} - org.opencontainers.image.version=${{ needs.Release.outputs.version }} + org.opencontainers.image.version=${{ needs.kani_release.outputs.version }} org.opencontainers.image.licenses=Apache-2.0 OR MIT # This check will not work until #1655 is completed. diff --git a/.github/workflows/slow-tests.yml b/.github/workflows/slow-tests.yml index 94e6fb8e742c..b177b6d5f400 100644 --- a/.github/workflows/slow-tests.yml +++ b/.github/workflows/slow-tests.yml @@ -18,10 +18,10 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [macos-11, ubuntu-20.04, ubuntu-22.04] + os: [macos-12, ubuntu-20.04, ubuntu-22.04] steps: - name: Checkout Kani - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Kani Dependencies uses: ./.github/actions/setup diff --git a/.github/workflows/toolchain-upgrade.yml b/.github/workflows/toolchain-upgrade.yml index bc9527f62837..32266375cb5f 100644 --- a/.github/workflows/toolchain-upgrade.yml +++ b/.github/workflows/toolchain-upgrade.yml @@ -8,22 +8,39 @@ on: - cron: "30 2 * * *" # Run this every day at 02:30 UTC workflow_dispatch: # Allow manual dispatching for a custom branch / tag. +permissions: + checks: write + contents: write + issues: write + pull-requests: write + jobs: create-toolchain-pr: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout Kani - uses: actions/checkout@v3 + uses: actions/checkout@v4 + + - name: Setup Kani Dependencies + uses: ./.github/actions/setup + with: + os: ubuntu-22.04 - name: Update toolchain config + env: + GH_TOKEN: ${{ github.token }} run: | current_toolchain_date=$(grep ^channel rust-toolchain.toml | sed 's/.*nightly-\(.*\)"/\1/') echo "current_toolchain_date=$current_toolchain_date" >> $GITHUB_ENV current_toolchain_epoch=$(date --date $current_toolchain_date +%s) next_toolchain_date=$(date --date "@$(($current_toolchain_epoch + 86400))" +%Y-%m-%d) echo "next_toolchain_date=$next_toolchain_date" >> $GITHUB_ENV - if ! git ls-remote --exit-code origin toolchain-$next_toolchain_date ; then - echo "branch_exists=false" >> $GITHUB_ENV + if gh issue list -S \ + "Toolchain upgrade to nightly-$next_toolchain_date failed" \ + --json number,title | grep title ; then + echo "next_step=none" >> $GITHUB_ENV + elif ! git ls-remote --exit-code origin toolchain-$next_toolchain_date ; then + echo "next_step=create_pr" >> $GITHUB_ENV sed -i "/^channel/ s/$current_toolchain_date/$next_toolchain_date/" rust-toolchain.toml git diff git clone --filter=tree:0 https://github.com/rust-lang/rust rust.git @@ -39,11 +56,18 @@ jobs: echo "$EOF" >> $GITHUB_ENV cd .. rm -rf rust.git + if ! cargo build-dev ; then + echo "next_step=create_issue" >> $GITHUB_ENV + else + if ! ./scripts/kani-regression.sh ; then + echo "next_step=create_issue" >> $GITHUB_ENV + fi + fi else - echo "branch_exists=true" >> $GITHUB_ENV + echo "next_step=none" >> $GITHUB_ENV fi - name: Create Pull Request - if: ${{ env.branch_exists == 'false' }} + if: ${{ env.next_step == 'create_pr' }} uses: peter-evans/create-pull-request@v5 with: commit-message: Upgrade Rust toolchain to nightly-${{ env.next_toolchain_date }} @@ -61,4 +85,24 @@ jobs: https://github.com/rust-lang/rust/commit/${{ env.next_toolchain_hash }}. The log for this commit range is: + ${{ env.git_log }} + - name: Create Issue + if: ${{ env.next_step == 'create_issue' }} + uses: dacbd/create-issue-action@main + with: + token: ${{ github.token }} + title: 'Toolchain upgrade to nightly-${{ env.next_toolchain_date }} failed' + body: > + Updating Rust toolchain from nightly-${{ env.current_toolchain_date }} to + nightly-${{ env.next_toolchain_date }} requires source changes. + + The failed automated run + [can be found here.](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + + Please review the changes at + https://github.com/rust-lang/rust from + https://github.com/rust-lang/rust/commit/${{ env.current_toolchain_hash }} up to + https://github.com/rust-lang/rust/commit/${{ env.next_toolchain_hash }}. The log + for this commit range is: + ${{ env.git_log }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 659057b2a987..f2c80bdf1a54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,148 @@ 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.42.0] + +### What's Changed + +* Build CBMC from source and install as package on non-x86_64 by @bennofs in https://github.com/model-checking/kani/pull/2877 and https://github.com/model-checking/kani/pull/2878 +* Emit suggestions and an explanation when CBMC runs out of memory by @JustusAdam in https://github.com/model-checking/kani/pull/2885 +* Rust toolchain upgraded to `nightly-2023-11-28` by @celinval + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.41.0...kani-0.42.0 + +## [0.41.0] + +### Breaking Changes + +* Set minimum python to 3.7 in docker container and release action by @remi-delmas-3000 in https://github.com/model-checking/kani/pull/2879 +* Delete `any_slice` which has been deprecated since Kani 0.38.0. by @zhassan-aws in https://github.com/model-checking/kani/pull/2860 + +### What's Changed + +* Make `cover` const by @jswrenn in https://github.com/model-checking/kani/pull/2867 +* Change `expect()` from taking formatted strings to use `unwrap_or_else()` by @matthiaskrgr in https://github.com/model-checking/kani/pull/2865 +* Fix setup for `aarch64-unknown-linux-gnu` platform by @adpaco-aws in https://github.com/model-checking/kani/pull/2864 +* Do not override `std` library during playback by @celinval in https://github.com/model-checking/kani/pull/2852 +* Rust toolchain upgraded to `nightly-2023-11-11` by @zhassan-aws + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.40.0...kani-0.41.0 + +## [0.40.0] + +### What's Changed + +* Ease setup in Amazon Linux 2 by @adpaco-aws in https://github.com/model-checking/kani/pull/2833 +* Propagate backend options into goto-synthesizer by @qinheping in https://github.com/model-checking/kani/pull/2643 +* Update CBMC version to 5.95.1 by @adpaco-aws in https://github.com/model-checking/kani/pull/2844 +* Rust toolchain upgraded to `nightly-2023-10-31` by @jaisnan @adpaco-aws + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.39.0...kani-0.40.0 + +## [0.39.0] + +### What's Changed + +* Limit --exclude to workspace packages by @tautschnig in https://github.com/model-checking/kani/pull/2808 +* Fix panic warning and add arbitrary Duration by @celinval in https://github.com/model-checking/kani/pull/2820 +* Update CBMC version to 5.94 by @celinval in https://github.com/model-checking/kani/pull/2821 +* Rust toolchain upgraded to `nightly-2023-10-17` by @celinval @tautschnig + +**Full Changelog**: +https://github.com/model-checking/kani/compare/kani-0.38.0...kani-0.39.0 + +## [0.38.0] + +### Major Changes + +* Deprecate `any_slice` by @zhassan-aws in https://github.com/model-checking/kani/pull/2789 + +### What's Changed + +* Provide better error message for invalid stubs by @JustusAdam in https://github.com/model-checking/kani/pull/2787 +* Simple Stubbing with Contracts by @JustusAdam in https://github.com/model-checking/kani/pull/2746 +* Avoid mismatch when generating structs that represent scalar data but also include ZSTs by @adpaco-aws in https://github.com/model-checking/kani/pull/2794 +* Prevent kani crash during setup for first time by @jaisnan in https://github.com/model-checking/kani/pull/2799 +* Create concrete playback temp files in source directory by @tautschnig in https://github.com/model-checking/kani/pull/2804 +* Bump CBMC version by @zhassan-aws in https://github.com/model-checking/kani/pull/2796 +* Update Rust toolchain to 2023-09-23 by @tautschnig in https://github.com/model-checking/kani/pull/2806 + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.37.0...kani-0.38.0 + +## [0.37.0] + +### Major Changes + +* Delete obsolete stubs for `Vec` and related options by @zhassan-aws in https://github.com/model-checking/kani/pull/2770 +* Add support for the ARM64 Linux platform by @adpaco-aws in https://github.com/model-checking/kani/pull/2757 + +### What's Changed + +* Function Contracts: Support for defining and checking `requires` and `ensures` clauses by @JustusAdam in https://github.com/model-checking/kani/pull/2655 +* Force `any_vec` capacity to match length by @celinval in https://github.com/model-checking/kani/pull/2765 +* Fix expected value for `pref_align_of` under aarch64/macos by @remi-delmas-3000 in https://github.com/model-checking/kani/pull/2782 +* Bump CBMC version to 5.92.0 by @zhassan-aws in https://github.com/model-checking/kani/pull/2771 +* Upgrade to Kissat 3.1.1 by @zhassan-aws in https://github.com/model-checking/kani/pull/2756 +* Rust toolchain upgraded to `nightly-2023-09-19` by @remi-delmas-3000 @tautschnig + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.36.0...kani-0.37.0 + +## [0.36.0] + +### What's Changed + +* Enable `-Z stubbing` and error out instead of ignoring stub by @celinval in https://github.com/model-checking/kani/pull/2678 +* Enable concrete playback for failure of UB checks by @zhassan-aws in https://github.com/model-checking/kani/pull/2727 +* Bump CBMC version to 5.91.0 by @adpaco-aws in https://github.com/model-checking/kani/pull/2733 +* Rust toolchain upgraded to `nightly-2023-09-06` by @celinval @jaisnan @adpaco-aws + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.35.0...kani-0.36.0 + +## [0.35.0] + +### What's Changed + +* Add support to `simd_bitmask` by @celinval in https://github.com/model-checking/kani/pull/2677 +* Add integer overflow checking for `simd_div` and `simd_rem` by @reisnera in https://github.com/model-checking/kani/pull/2645 +* Bump CBMC version by @zhassan-aws in https://github.com/model-checking/kani/pull/2702 +* Upgrade Rust toolchain to 2023-08-19 by @zhassan-aws in https://github.com/model-checking/kani/pull/2696 + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.34.0...kani-0.35.0 + +## [0.34.0] + +### Breaking Changes +* Change default solver to CaDiCaL by @celinval in https://github.com/model-checking/kani/pull/2557 +By default, Kani will now run CBMC with CaDiCaL, since this solver has outperformed Minisat in most of our benchmarks. +User's should still be able to select Minisat (or a different solver) either by using `#[solver]` harness attribute, +or by passing `--solver=` command line option. + +### What's Changed + +* Allow specifying the scheduling strategy in #[kani_proof] for async functions by @fzaiser in https://github.com/model-checking/kani/pull/1661 +* Support for stubbing out foreign functions by @feliperodri in https://github.com/model-checking/kani/pull/2658 +* Coverage reporting without a need for cbmc-viewer by @adpaco-aws in https://github.com/model-checking/kani/pull/2609 +* Add support to array-based SIMD by @celinval in https://github.com/model-checking/kani/pull/2633 +* Add unchecked/SIMD bitshift checks and disable CBMC flag by @reisnera in https://github.com/model-checking/kani/pull/2630 +* Fix codegen of constant byte slices to address spurious verification failures by @zhassan in https://github.com/model-checking/kani/pull/2663 +* Bump CBMC to v5.89.0 by @remi-delmas-3000 in https://github.com/model-checking/kani/pull/2662 +* Update Rust toolchain to nightly 2023-08-04 by @remi-delmas-3000 in https://github.com/model-checking/kani/pull/2661 + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.33.0...kani-0.34.0 + +## [0.33.0] + +### What's Changed +* Add support for sysconf by @feliperodri in https://github.com/model-checking/kani/pull/2557 +* Print Kani version by @adpaco-aws in https://github.com/model-checking/kani/pull/2619 +* Upgrade Rust toolchain to nightly-2023-07-01 by @qinheping in https://github.com/model-checking/kani/pull/2616 +* Bump CBMC version to 5.88.1 by @zhassan-aws in https://github.com/model-checking/kani/pull/2623 + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.32.0...kani-0.33.0 + ## [0.32.0] -## What's Changed +### What's Changed * Add kani::spawn and an executor to the Kani library by @fzaiser in https://github.com/model-checking/kani/pull/1659 * Add "kani" configuration key to enable conditional compilation in build scripts by @celinval in https://github.com/model-checking/kani/pull/2297 @@ -20,7 +159,7 @@ This file was introduced starting Kani 0.23.0, so it only contains changes from ## [0.31.0] -## What's Changed +### What's Changed * Add `--exact` flag by @jaisnan in https://github.com/model-checking/kani/pull/2527 * Build the verification libraries using Kani compiler by @celinval in https://github.com/model-checking/kani/pull/2534 * Verify all Kani attributes in all crate items upfront by @celinval in https://github.com/model-checking/kani/pull/2536 @@ -31,7 +170,7 @@ This file was introduced starting Kani 0.23.0, so it only contains changes from ## [0.30.0] -## What's Changed +### What's Changed * Remove --harness requirement from stubbing by @celinval in https://github.com/model-checking/kani/pull/2495 * Add target selection for cargo kani by @celinval in https://github.com/model-checking/kani/pull/2507 * Generate Multiple playback harnesses when multiple crashes exist in a single harness. by @YoshikiTakashima in https://github.com/model-checking/kani/pull/2496 diff --git a/Cargo.lock b/Cargo.lock index 0847cd2f318a..7b6edfc8320c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" dependencies = [ "getrandom", "once_cell", @@ -25,39 +25,38 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] [[package]] name = "anstream" -version = "0.3.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] @@ -73,9 +72,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -83,9 +82,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "autocfg" @@ -101,9 +100,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "bookrunner" @@ -120,7 +119,7 @@ dependencies = [ [[package]] name = "build-kani" -version = "0.32.0" +version = "0.42.0" dependencies = [ "anyhow", "cargo_metadata", @@ -130,27 +129,27 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.4" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +checksum = "e34637b3140142bdf929fb439e8aa4ebad7651ebf7b1080b3930aa16ac1459ff" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" -version = "0.15.4" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", @@ -160,12 +159,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - [[package]] name = "cfg-if" version = "1.0.0" @@ -174,45 +167,43 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.3.11" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.3.11" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" dependencies = [ "anstream", "anstyle", "clap_lex", - "once_cell", "strsim", ] [[package]] name = "clap_derive" -version = "4.3.2" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.39", ] [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "colorchoice" @@ -222,9 +213,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "comfy-table" -version = "6.2.0" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e959d788268e3bf9d35ace83e81b124190378e4c91c9067524675e33394b8ba" +checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686" dependencies = [ "crossterm", "strum", @@ -264,7 +255,7 @@ dependencies = [ [[package]] name = "cprover_bindings" -version = "0.32.0" +version = "0.42.0" dependencies = [ "lazy_static", "linear-map", @@ -277,16 +268,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - [[package]] name = "crossbeam-deque" version = "0.8.3" @@ -322,17 +303,14 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.26.1" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "crossterm_winapi", "libc", - "mio", "parking_lot", - "signal-hook", - "signal-hook-mio", "winapi", ] @@ -347,9 +325,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "encode_unicode" @@ -359,30 +337,25 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "equivalent" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "fastrand" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "getopts" @@ -395,9 +368,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", @@ -421,9 +394,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -431,12 +404,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" - [[package]] name = "home" version = "0.5.5" @@ -448,50 +415,39 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.0", -] - -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys 0.48.0", + "hashbrown 0.14.3", ] [[package]] name = "itertools" -version = "0.10.5" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "kani" -version = "0.32.0" +version = "0.42.0" dependencies = [ "kani_macros", ] [[package]] name = "kani-compiler" -version = "0.32.0" +version = "0.42.0" dependencies = [ "clap", "cprover_bindings", @@ -512,7 +468,7 @@ dependencies = [ [[package]] name = "kani-driver" -version = "0.32.0" +version = "0.42.0" dependencies = [ "anyhow", "cargo_metadata", @@ -531,6 +487,7 @@ dependencies = [ "serde_json", "strum", "strum_macros", + "tempfile", "toml", "tracing", "tracing-subscriber", @@ -539,7 +496,7 @@ dependencies = [ [[package]] name = "kani-verifier" -version = "0.32.0" +version = "0.42.0" dependencies = [ "anyhow", "home", @@ -548,18 +505,19 @@ dependencies = [ [[package]] name = "kani_macros" -version = "0.32.0" +version = "0.42.0" dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.39", ] [[package]] name = "kani_metadata" -version = "0.32.0" +version = "0.42.0" dependencies = [ + "clap", "cprover_bindings", "serde", "strum", @@ -574,9 +532,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "linear-map" @@ -590,15 +548,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -606,9 +564,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "matchers" @@ -621,9 +579,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" @@ -640,18 +598,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2145869435ace5ea6ea3d35f59be559317ec9a0d04e1812d5f185a87b6d36f1a" -[[package]] -name = "mio" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.48.0", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -664,9 +610,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" dependencies = [ "num-bigint", "num-complex", @@ -678,9 +624,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", @@ -689,9 +635,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" dependencies = [ "num-traits", ] @@ -731,28 +677,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "os_info" @@ -782,15 +718,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.1", + "windows-targets 0.48.5", ] [[package]] @@ -801,9 +737,9 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "ppv-lite86" @@ -837,9 +773,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.64" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -857,9 +793,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.29" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -896,9 +832,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ "either", "rayon-core", @@ -906,35 +842,33 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.9.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.2", - "regex-syntax 0.7.3", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", ] [[package]] @@ -948,13 +882,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.3", + "regex-syntax 0.8.2", ] [[package]] @@ -965,9 +899,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustc-demangle" @@ -984,11 +918,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.3" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", @@ -997,15 +931,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" @@ -1018,44 +952,44 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.17" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.171" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.171" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.39", ] [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1064,27 +998,27 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" dependencies = [ "serde", ] [[package]] name = "serde_test" -version = "1.0.171" +version = "1.0.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6480a2f4e1449ec9757ea143362ad5cea79bc7f1cb7711c06e1c5d03b6b5a3a" +checksum = "5a2f49ace1498612d14f7e0b8245519584db8299541dfe31a06374a828d620ab" dependencies = [ "serde", ] [[package]] name = "serde_yaml" -version = "0.9.22" +version = "0.9.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "452e67b9c20c37fa79df53201dc03839651086ed9bbe92b3ca585ca9fdaa7d85" +checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" dependencies = [ "indexmap", "itoa", @@ -1095,9 +1029,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -1108,45 +1042,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" -[[package]] -name = "signal-hook" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "std" -version = "0.32.0" +version = "0.42.0" dependencies = [ "kani", ] @@ -1170,21 +1074,21 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" [[package]] name = "strum_macros" -version = "0.24.3" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 1.0.109", + "syn 2.0.39", ] [[package]] @@ -1194,39 +1098,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", - "quote", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.25" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "thiserror" -version = "1.0.43" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.43" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.39", ] [[package]] @@ -1241,9 +1157,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" dependencies = [ "serde", "serde_spanned", @@ -1253,18 +1169,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.12" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ "indexmap", "serde", @@ -1275,11 +1191,10 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1287,20 +1202,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.39", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -1308,12 +1223,12 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] @@ -1329,9 +1244,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -1351,30 +1266,30 @@ dependencies = [ [[package]] name = "unicase" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unsafe-libyaml" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" [[package]] name = "utf8parse" @@ -1405,9 +1320,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -1421,13 +1336,15 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "which" -version = "4.4.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -1448,9 +1365,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -1476,7 +1393,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.1", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -1496,17 +1422,32 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -1517,9 +1458,15 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" @@ -1529,9 +1476,15 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" @@ -1541,9 +1494,15 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" @@ -1553,9 +1512,15 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" @@ -1565,9 +1530,15 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" @@ -1577,9 +1548,15 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" @@ -1589,15 +1566,21 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.4.9" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index bb0dffcfaf1f..911375d4e0e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-verifier" -version = "0.32.0" +version = "0.42.0" edition = "2021" description = "A bit-precise model checker for Rust." readme = "README.md" @@ -67,4 +67,6 @@ exclude = [ "tests/slow", "tests/assess-scan-test-scaffold", "tests/script-based-pre", + "tests/script-based-pre/build-cache-bin/target/new_dep", + "tests/script-based-pre/build-cache-dirty/target/new_dep", ] diff --git a/cprover_bindings/Cargo.toml b/cprover_bindings/Cargo.toml index 938b93606fd2..96b0192f71fb 100644 --- a/cprover_bindings/Cargo.toml +++ b/cprover_bindings/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "cprover_bindings" -version = "0.32.0" +version = "0.42.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/cprover_bindings/src/goto_program/builtin.rs b/cprover_bindings/src/goto_program/builtin.rs index 258691b9ade4..438bc2eea1e9 100644 --- a/cprover_bindings/src/goto_program/builtin.rs +++ b/cprover_bindings/src/goto_program/builtin.rs @@ -61,6 +61,7 @@ pub enum BuiltinFn { Sinf, Sqrt, Sqrtf, + Sysconf, Trunc, Truncf, Unlink, @@ -124,6 +125,7 @@ impl ToString for BuiltinFn { Sinf => "sinf", Sqrt => "sqrt", Sqrtf => "sqrtf", + Sysconf => "sysconf", Trunc => "trunc", Truncf => "truncf", Unlink => "unlink", @@ -188,6 +190,7 @@ impl BuiltinFn { Sinf => vec![Type::float()], Sqrt => vec![Type::double()], Sqrtf => vec![Type::float()], + Sysconf => vec![Type::c_int()], Trunc => vec![Type::double()], Truncf => vec![Type::float()], Unlink => vec![Type::c_char().to_pointer()], @@ -251,6 +254,7 @@ impl BuiltinFn { Sinf => Type::float(), Sqrt => Type::double(), Sqrtf => Type::float(), + Sysconf => Type::c_long_int(), Trunc => Type::double(), Truncf => Type::float(), Unlink => Type::c_int(), @@ -314,6 +318,7 @@ impl BuiltinFn { Sinf, Sqrt, Sqrtf, + Sysconf, Trunc, Truncf, Unlink, @@ -324,7 +329,7 @@ impl BuiltinFn { /// Converters: build symbols and expressions from Builtins impl BuiltinFn { pub fn as_symbol(&self) -> Symbol { - Symbol::builtin_function(&self.to_string(), self.param_types(), self.return_type()) + Symbol::builtin_function(self.to_string(), self.param_types(), self.return_type()) } pub fn as_expr(&self) -> Expr { diff --git a/cprover_bindings/src/goto_program/expr.rs b/cprover_bindings/src/goto_program/expr.rs index 3a6da89239b5..b6ad7a041d11 100644 --- a/cprover_bindings/src/goto_program/expr.rs +++ b/cprover_bindings/src/goto_program/expr.rs @@ -627,6 +627,34 @@ impl Expr { expr!(IntConstant(i), typ) } + pub fn ssize_constant(i: i128, symbol_table: &SymbolTable) -> Self { + match symbol_table.machine_model().pointer_width { + 32 => { + let val = BigInt::from(i as i32); + expr!(IntConstant(val), Type::ssize_t()) + } + 64 => { + let val = BigInt::from(i as i64); + expr!(IntConstant(val), Type::ssize_t()) + } + i => unreachable!("Expected 32 or 64 bits pointer width, but got `{i}`"), + } + } + + pub fn size_constant(i: u128, symbol_table: &SymbolTable) -> Self { + match symbol_table.machine_model().pointer_width { + 32 => { + let val = BigInt::from(i as u32); + expr!(IntConstant(val), Type::size_t()) + } + 64 => { + let val = BigInt::from(i as u64); + expr!(IntConstant(val), Type::size_t()) + } + i => unreachable!("Expected 32 or 64 bits pointer width, but got `{i}`"), + } + } + pub fn typecheck_call(function: &Expr, arguments: &[Expr]) -> bool { // For variadic functions, all named arguments must match the type of their formal param. // Extra arguments (e.g the ... args) can have any type. @@ -1410,7 +1438,7 @@ impl Expr { ArithmeticOverflowResult { result, overflowed } } - /// Uses CBMC's [binop]-with-overflow operation that performs a single arithmetic + /// Uses CBMC's \[binop\]-with-overflow operation that performs a single arithmetic /// operation /// `struct (T, bool) overflow(binop, self, e)` where `T` is the type of `self` /// Pseudocode: diff --git a/cprover_bindings/src/goto_program/typ.rs b/cprover_bindings/src/goto_program/typ.rs index a038b8dee779..9d1649b99cd1 100644 --- a/cprover_bindings/src/goto_program/typ.rs +++ b/cprover_bindings/src/goto_program/typ.rs @@ -27,7 +27,7 @@ pub enum Type { Bool, /// `typ x : width`. e.g. `unsigned int x: 3`. CBitField { typ: Box, width: u64 }, - /// Machine dependent integers: `bool`, `char`, `int`, `size_t`, etc. + /// Machine dependent integers: `bool`, `char`, `int`, `long int`, `size_t`, etc. CInteger(CIntType), /// `return_type x(parameters)` Code { parameters: Vec, return_type: Box }, @@ -83,6 +83,8 @@ pub enum CIntType { Char, /// `int` Int, + /// `long int` + LongInt, /// `size_t` SizeT, /// `ssize_t` @@ -232,6 +234,7 @@ impl CIntType { CIntType::Bool => st.machine_model().bool_width, CIntType::Char => st.machine_model().char_width, CIntType::Int => st.machine_model().int_width, + CIntType::LongInt => st.machine_model().long_int_width, CIntType::SizeT => st.machine_model().pointer_width, CIntType::SSizeT => st.machine_model().pointer_width, } @@ -287,6 +290,7 @@ impl Type { CInteger(CIntType::Bool) => Some(mm.bool_width), CInteger(CIntType::Char) => Some(mm.char_width), CInteger(CIntType::Int) => Some(mm.int_width), + CInteger(CIntType::LongInt) => Some(mm.long_int_width), Signedbv { width } | Unsignedbv { width } => Some(*width), _ => None, } @@ -450,6 +454,14 @@ impl Type { } } + pub fn is_long_int(&self) -> bool { + let concrete = self.unwrap_typedef(); + match concrete { + Type::CInteger(CIntType::LongInt) => true, + _ => false, + } + } + pub fn is_c_size_t(&self) -> bool { let concrete = self.unwrap_typedef(); match concrete { @@ -637,7 +649,10 @@ impl Type { pub fn is_signed(&self, mm: &MachineModel) -> bool { let concrete = self.unwrap_typedef(); match concrete { - CInteger(CIntType::Int) | CInteger(CIntType::SSizeT) | Signedbv { .. } => true, + CInteger(CIntType::Int) + | CInteger(CIntType::LongInt) + | CInteger(CIntType::SSizeT) + | Signedbv { .. } => true, CInteger(CIntType::Char) => !mm.char_is_unsigned, _ => false, } @@ -963,6 +978,10 @@ impl Type { CInteger(CIntType::Int) } + pub fn c_long_int() -> Self { + CInteger(CIntType::LongInt) + } + pub fn c_size_t() -> Self { CInteger(CIntType::SizeT) } @@ -1471,6 +1490,7 @@ mod type_tests { assert_eq!(type_def.is_empty(), src_type.is_empty()); assert_eq!(type_def.is_double(), src_type.is_double()); assert_eq!(type_def.is_bool(), src_type.is_bool()); + assert_eq!(type_def.is_long_int(), src_type.is_long_int()); assert_eq!(type_def.is_array(), src_type.is_array()); assert_eq!(type_def.is_array_like(), src_type.is_array_like()); assert_eq!(type_def.is_union(), src_type.is_union()); diff --git a/cprover_bindings/src/irep/to_irep.rs b/cprover_bindings/src/irep/to_irep.rs index 70810e403c54..41c501896cd1 100644 --- a/cprover_bindings/src/irep/to_irep.rs +++ b/cprover_bindings/src/irep/to_irep.rs @@ -509,16 +509,16 @@ impl goto_program::Symbol { SymbolValues::None => Irep::nil(), }, location: self.location.to_irep(mm), - /// Unique identifier, same as key in symbol table `foo::x` + // Unique identifier, same as key in symbol table `foo::x` name: self.name, - /// Only used by verilog + // Only used by verilog module: self.module.unwrap_or("".into()), - /// Local identifier `x` + // Local identifier `x` base_name: self.base_name.unwrap_or("".into()), - /// Almost always the same as base_name, but with name mangling can be relevant + // Almost always the same as `base_name`, but with name mangling can be relevant pretty_name: self.pretty_name.unwrap_or("".into()), - /// Currently set to C. Consider creating a "rust" mode and using it in cbmc - /// https://github.com/model-checking/kani/issues/1 + // Currently set to C. Consider creating a "rust" mode and using it in cbmc + // https://github.com/model-checking/kani/issues/1 mode: self.mode.to_string().into(), // global properties @@ -589,6 +589,11 @@ impl ToIrep for Type { sub: vec![], named_sub: linear_map![(IrepId::Width, Irep::just_int_id(mm.int_width),)], }, + Type::CInteger(CIntType::LongInt) => Irep { + id: IrepId::Signedbv, + sub: vec![], + named_sub: linear_map![(IrepId::Width, Irep::just_int_id(mm.long_int_width),)], + }, Type::CInteger(CIntType::SizeT) => Irep { id: IrepId::Unsignedbv, sub: vec![], diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index d4609a87c23b..69be21a07ff5 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -26,6 +26,7 @@ - [Coding conventions](./conventions.md) - [Working with CBMC](./cbmc-hacks.md) - [Working with `rustc`](./rustc-hacks.md) + - [Migrating to StableMIR](./stable-mir.md) - [Command cheat sheets](./cheat-sheets.md) - [cargo kani assess](./dev-assess.md) - [Testing](./testing.md) diff --git a/docs/src/build-from-source.md b/docs/src/build-from-source.md index cb71264e3df2..01860f5f37e4 100644 --- a/docs/src/build-from-source.md +++ b/docs/src/build-from-source.md @@ -13,7 +13,7 @@ In general, the following dependencies are required to build Kani from source. 1. Cargo installed via [rustup](https://rustup.rs/) 2. [CBMC](https://github.com/diffblue/cbmc) (latest release) 3. [CBMC Viewer](https://github.com/awslabs/aws-viewer-for-cbmc) (latest release) -4. [Kissat](https://github.com/arminbiere/kissat) (Release 3.0.0) +4. [Kissat](https://github.com/arminbiere/kissat) (Release 3.1.1) Kani has been tested in [Ubuntu](#install-dependencies-on-ubuntu) and [macOS](##install-dependencies-on-macos) platforms. diff --git a/docs/src/dev-documentation.md b/docs/src/dev-documentation.md index 639b7125ae78..c3fe857cf0b6 100644 --- a/docs/src/dev-documentation.md +++ b/docs/src/dev-documentation.md @@ -14,6 +14,7 @@ developers (including external contributors): 3. [Development setup recommendations for working with `cbmc`](./cbmc-hacks.md). 4. [Development setup recommendations for working with `rustc`](./rustc-hacks.md). 5. [Guide for testing in Kani](./testing.md). + 6. [Transition to StableMIR](./stable-mir.md). > **NOTE**: The developer documentation is intended for Kani developers and not users. At present, the project is under heavy development and some items diff --git a/docs/src/install-guide.md b/docs/src/install-guide.md index 7f5bd0a7f232..776209987ba1 100644 --- a/docs/src/install-guide.md +++ b/docs/src/install-guide.md @@ -14,7 +14,7 @@ GitHub CI workflows, see [GitHub CI Action](./install-github-ci.md). The following must already be installed: -* **Python version 3.6 or newer** and the package installer `pip`. +* **Python version 3.7 or newer** and the package installer `pip`. * Rust 1.58 or newer installed via `rustup`. * `ctags` is required for Kani's `--visualize` option to work correctly. [Universal ctags](https://ctags.io/) is recommended. diff --git a/docs/src/performance-comparisons.md b/docs/src/performance-comparisons.md index 3bc430ac2954..0e99f41c9928 100644 --- a/docs/src/performance-comparisons.md +++ b/docs/src/performance-comparisons.md @@ -33,7 +33,7 @@ git worktree add old $(git describe --tags --abbrev=0) tools/benchcomp/bin/benchcomp --config tools/benchcomp/configs/perf-regression.yaml ``` -This uses the [`perf-regression.yaml` configuration file](https://github.com/model-checking/kani/tree/main/tools/benchcomp/benchcomp/configs/perf-regression.yaml) that we use in continuous integration. +This uses the [`perf-regression.yaml` configuration file](https://github.com/model-checking/kani/blob/main/tools/benchcomp/configs/perf-regression.yaml) that we use in continuous integration. After running the suite twice, the configuration file terminates `benchcomp` with a return code of 1 if any of the benchmarks regressed on metrics such as `success` (a boolean), `solver_runtime`, and `number_vccs` (numerical). Additionally, the config file directs benchcomp to print out a Markdown table that GitHub's CI summary page renders in to a table. diff --git a/docs/src/reference/attributes.md b/docs/src/reference/attributes.md index bb7823eb8116..bd8b80cbbdaa 100644 --- a/docs/src/reference/attributes.md +++ b/docs/src/reference/attributes.md @@ -203,8 +203,8 @@ VERIFICATION:- SUCCESSFUL This may change the verification time required to verify a harness. At present, `` can be one of: - - `minisat` (default): [MiniSat](http://minisat.se/). - - `cadical`: [CaDiCaL](https://github.com/arminbiere/cadical). + - `minisat`: [MiniSat](http://minisat.se/). + - `cadical` (default): [CaDiCaL](https://github.com/arminbiere/cadical). - `kissat`: [kissat](https://github.com/arminbiere/kissat). - `bin=""`: A custom solver binary, `""`, that must be in path. @@ -226,6 +226,11 @@ fn check() { Changing the solver may result in different verification times depending on the harness. +Note that the default solver may vary depending on Kani's version. +We highly recommend users to annotate their harnesses if the choice of solver +has a major impact on performance, even if the solver used is the current +default one. + ## `#[kani::stub(, )]` **Replaces the function/method with name with the function/method with name during compilation** diff --git a/docs/src/rust-feature-support/intrinsics.md b/docs/src/rust-feature-support/intrinsics.md index 73cb23aea34b..0705d3b00f1b 100644 --- a/docs/src/rust-feature-support/intrinsics.md +++ b/docs/src/rust-feature-support/intrinsics.md @@ -258,7 +258,7 @@ Name | Support | Notes | --- | --- | --- | `simd_add` | Yes | | `simd_and` | Yes | | -`simd_div` | Yes | Doesn't check for overflow cases [#1970](https://github.com/model-checking/kani/issues/1970) | +`simd_div` | Yes | | `simd_eq` | Yes | | `simd_extract` | Yes | | `simd_ge` | Yes | | @@ -269,9 +269,9 @@ Name | Support | Notes | `simd_mul` | Yes | | `simd_ne` | Yes | | `simd_or` | Yes | | -`simd_rem` | Yes | Doesn't check for overflow cases [#1970](https://github.com/model-checking/kani/issues/1970) | -`simd_shl` | Yes | Doesn't check for overflow cases [#1963](https://github.com/model-checking/kani/issues/1963) | -`simd_shr` | Yes | Doesn't check for overflow cases [#1963](https://github.com/model-checking/kani/issues/1963) | +`simd_rem` | Yes | Doesn't check for floating point overflow [#2669](https://github.com/model-checking/kani/issues/2669) | +`simd_shl` | Yes | | +`simd_shr` | Yes | | `simd_shuffle*` | Yes | | `simd_sub` | Yes | | `simd_xor` | Yes | | diff --git a/docs/src/stable-mir.md b/docs/src/stable-mir.md new file mode 100644 index 000000000000..79e63a98cba1 --- /dev/null +++ b/docs/src/stable-mir.md @@ -0,0 +1,94 @@ +# Transition to StableMIR + +We have partnered with the Rust compiler team in the initiative to introduce stable +APIs to the compiler that can be used by third-party tools, which is known as the +[Stable MIR Project](https://github.com/rust-lang/project-stable-mir), or just StableMIR. +This means that we are starting to use the new APIs introduced by this project as is, +despite them not being stable yet. + +### StableMIR APIs + +For now, the StableMIR APIs are exposed as a crate in the compiler named `stable_mir`. +This crate includes the definition of structures and methods to be stabilized, +which are expected to become the stable APIs in the compiler. +To reduce the migration burden, these APIs are somewhat close to the original compiler interfaces. +However, some changes have been made to make these APIs cleaner and easier to use. + +For example: +1. The usage of the compiler context (aka `TyCtxt`) is transparent to the user. + The StableMIR implementation caches this context in a thread local variable, + and retrieves it whenever necessary. + - Because of that, code that uses the StableMIR has to be invoked inside a `run` call. +2. The `DefId` has been specialized into multiple types, + making its usage less error prone. E.g.: + `FnDef` represents the definition of a function, + while `StaticDef` is the definition of a static variable. + - Note that the same `DefId` may be mapped to different definitions according to its context. + For example, an `InstanceDef` and a `FnDef` may represent the same function definition. +3. Methods that used to be exposed as part of `TyCtxt` are now part of a type. + Example, the function `TyCtxt.instance_mir` is now `Instance::body`. +4. There is no need for explicit instantiation (monomorphization) of items from an`Instance::body`. + This method already instantiates all types and resolves all constants before converting + it to stable APIs. + + +### Performance + +Since the new APIs require converting internal data to a stable representation, +the APIs were also designed to avoid needless conversions, +and to allow extra information to be retrieved on demand. + +For example, `Ty` is just an identifier, while `TyKind` is a structure that can be retrieved via `Ty::kind` method. +The `TyKind` is a more structured object, thus, +it is only generated when the `kind` method is invoked. +Since this translation is not cached, +many of the functions that the rust compiler used to expose in `Ty`, +is now only part of `TyKind`. +The reason being that there is no cache for the `TyKind`, +and users should do the caching themselves to avoid needless translations. + +From our initial experiments with the transition of the reachability algorithm to use StableMIR, +there is a small penalty of using StableMIR over internal rust compiler APIs. +However, they are still fairly efficient and it did not impact the overall compilation time. + +### Interface with internal APIs + +To reduce the burden of migrating to StableMIR, +and to allow StableMIR to be used together with internal APIs, +there are two helpful methods to convert StableMIR constructs to internal rustc and back: + - `rustc_internal::internal()`: Convert a Stable item into an internal one. + - `rustc_internal::stable()`: Convert an internal item into a Stable one. + +Both of these methods are inside `rustc_smir` crate in the `rustc_internal` +module inside the compiler. +Note that there is no plan to stabilize any of these methods, +and there's also no guarantee on its support and coverage. + +The conversion is not implemented for all items, and some conversions may be incomplete. +Please proceed with caution when using these methods. + +Besides that, do not invoke any other `rustc_smir` methods, except for `run`. +This crate's methods are not meant to be invoked externally. +Note that, the method `run` will also eventually be replaced by a Stable driver. + +### Creating and modifying StableMIR items + +For now, StableMIR should only be used to get information from the compiler. +Do not try to create or modify items directly, as it may not work. +This may result in incorrect behavior or an internal compiler error (ICE). + +## Naming conventions in Kani + +As we adopt StableMIR, we would like to introduce a few conventions to make it easier to maintain the code. +Whenever there is a name conflict, for example, `Ty` or `codegen_ty`, +use a suffix to indicate which API you are using. +`Stable` for StableMIR and `Internal` for `rustc` internal APIs. + +A module should either default its naming to Stable APIs or Internal APIs. +I.e.: Modules that have been migrated to StableMIR don't need to add the `Stable` suffix to stable items. +While those that haven't been migrated, should add `Stable`, but no `Internal` is needed. + +For example, the `codegen::typ` module will likely include methods: + +`codegen_ty(&mut self, Ty)` and `codegen_ty_stable(&mut, TyStable)` to handle +internal and stable APIs. diff --git a/docs/src/tutorial-first-steps.md b/docs/src/tutorial-first-steps.md index c6d5cbf306c1..57e70edb96dd 100644 --- a/docs/src/tutorial-first-steps.md +++ b/docs/src/tutorial-first-steps.md @@ -66,7 +66,7 @@ A trace is a record of exactly how execution proceeded, including concrete choic To get a trace for a failing check in Kani, run: ``` -cargo kani --visualize +cargo kani --visualize --enable-unstable ``` This command runs Kani and generates an HTML report that includes a trace. diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index f23fc40e4b8c..a9e4c974c6eb 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -3,24 +3,24 @@ [package] name = "kani-compiler" -version = "0.32.0" +version = "0.42.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false [dependencies] cbmc = { path = "../cprover_bindings", package = "cprover_bindings", optional = true } -clap = { version = "4.1.3", features = ["cargo"] } +clap = { version = "4.4.11", features = ["derive", "cargo"] } home = "0.5" -itertools = "0.10" +itertools = "0.12" kani_metadata = {path = "../kani_metadata"} lazy_static = "1.4.0" num = { version = "0.4.0", optional = true } regex = "1.7.0" serde = { version = "1", optional = true } serde_json = "1" -strum = "0.24.0" -strum_macros = "0.24.0" +strum = "0.25.0" +strum_macros = "0.25.2" shell-words = "1.0.0" tracing = {version = "0.1", features = ["max_level_trace", "release_max_level_debug"]} tracing-subscriber = {version = "0.3.8", features = ["env-filter", "json", "fmt"]} diff --git a/kani-compiler/src/args.rs b/kani-compiler/src/args.rs new file mode 100644 index 000000000000..b174795b7259 --- /dev/null +++ b/kani-compiler/src/args.rs @@ -0,0 +1,74 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use strum_macros::{AsRefStr, EnumString, EnumVariantNames}; +use tracing_subscriber::filter::Directive; + +#[derive(Debug, Default, Clone, Copy, AsRefStr, EnumString, EnumVariantNames, PartialEq, Eq)] +#[strum(serialize_all = "snake_case")] +pub enum ReachabilityType { + /// Start the cross-crate reachability analysis from all harnesses in the local crate. + Harnesses, + /// Don't perform any reachability analysis. This will skip codegen for this crate. + #[default] + None, + /// Start the cross-crate reachability analysis from all public functions in the local crate. + PubFns, + /// Start the cross-crate reachability analysis from all *test* (i.e. `#[test]`) harnesses in the local crate. + Tests, +} + +/// Command line arguments that this instance of the compiler run was called +/// with. Usually stored in and accessible via [`crate::kani_queries::QueryDb`]. +#[derive(Debug, Default, Clone, clap::Parser)] +pub struct Arguments { + /// Option name used to enable assertion reachability checks. + #[clap(long = "assertion-reach-checks")] + pub check_assertion_reachability: bool, + /// Option name used to enable coverage checks. + #[clap(long = "coverage-checks")] + pub check_coverage: bool, + /// Option name used to dump function pointer restrictions. + #[clap(long = "restrict-vtable-fn-ptrs")] + pub emit_vtable_restrictions: bool, + /// Option name used to use json pretty-print for output files. + #[clap(long = "pretty-json-files")] + pub output_pretty_json: bool, + /// Option used for suppressing global ASM error. + #[clap(long)] + pub ignore_global_asm: bool, + #[clap(long)] + /// Option used to write JSON symbol tables instead of GOTO binaries. + /// + /// When set, instructs the compiler to produce the symbol table for CBMC in JSON format and use symtab2gb. + pub write_json_symtab: bool, + /// Option name used to select which reachability analysis to perform. + #[clap(long = "reachability", default_value = "none")] + pub reachability_analysis: ReachabilityType, + #[clap(long = "enable-stubbing")] + pub stubbing_enabled: bool, + /// Option name used to define unstable features. + #[clap(short = 'Z', long = "unstable")] + pub unstable_features: Vec, + #[clap(long)] + /// Option used for building standard library. + /// + /// Flag that indicates that we are currently building the standard library. + /// Note that `kani` library will not be available if this is `true`. + pub build_std: bool, + #[clap(long)] + /// Option name used to set log level. + pub log_level: Option, + #[clap(long)] + /// Option name used to set the log output to a json file. + pub json_output: bool, + #[clap(long, conflicts_with = "json_output")] + /// Option name used to force logger to use color output. This doesn't work with --json-output. + pub color_output: bool, + #[clap(long)] + /// Pass the kani version to the compiler to ensure cache coherence. + check_version: Option, + #[clap(long)] + /// A legacy flag that is now ignored. + goto_c: bool, +} diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs index bcf452602930..9ca725b5a268 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs @@ -22,6 +22,7 @@ use crate::codegen_cprover_gotoc::GotocCtx; use cbmc::goto_program::{Expr, Location, Stmt, Type}; use cbmc::InternedString; use rustc_span::Span; +use stable_mir::ty::Span as SpanStable; use std::convert::AsRef; use strum_macros::{AsRefStr, EnumString}; use tracing::debug; @@ -29,7 +30,7 @@ use tracing::debug; /// Classifies the type of CBMC `assert`, as different assertions can have different semantics (e.g. cover) /// /// Each property class should justify its existence with a note about the special handling it recieves. -#[derive(Debug, Clone, EnumString, AsRefStr)] +#[derive(Debug, Clone, EnumString, AsRefStr, PartialEq)] #[strum(serialize_all = "snake_case")] pub enum PropertyClass { /// Overflow panics that can be generated by Intrisics. @@ -45,6 +46,19 @@ pub enum PropertyClass { /// /// SPECIAL BEHAVIOR: "Errors" for this type of assertion just mean "reachable" not failure. Cover, + /// The class of checks used for code coverage instrumentation. Only needed + /// when working on coverage-related features. + /// + /// Do not mistake with `Cover`, they are different: + /// - `CodeCoverage` checks have a fixed condition (`false`) and description. + /// - `CodeCoverage` checks are filtered out from verification results and + /// postprocessed to build coverage reports. + /// - `Cover` checks can be added by users (using the `kani::cover` macro), + /// while `CodeCoverage` checks are not exposed to users (i.e., they are + /// automatically added if running with the coverage option). + /// + /// SPECIAL BEHAVIOR: "Errors" for this type of assertion just mean "reachable" not failure. + CodeCoverage, /// Ordinary (Rust) assertions and panics. /// /// SPECIAL BEHAVIOR: These assertion failures should be observable during normal execution of Rust code. @@ -125,8 +139,8 @@ impl<'tcx> GotocCtx<'tcx> { } /// Generate code to cover the given condition at the current location - pub fn codegen_cover(&self, cond: Expr, msg: &str, span: Option) -> Stmt { - let loc = self.codegen_caller_span(&span); + pub fn codegen_cover(&self, cond: Expr, msg: &str, span: SpanStable) -> Stmt { + let loc = self.codegen_caller_span_stable(span); // Should use Stmt::cover, but currently this doesn't work with CBMC // unless it is run with '--cover cover' (see // https://github.com/diffblue/cbmc/issues/6613). So for now use @@ -134,6 +148,21 @@ impl<'tcx> GotocCtx<'tcx> { self.codegen_assert(cond.not(), PropertyClass::Cover, msg, loc) } + /// Generate a cover statement for code coverage reports. + pub fn codegen_coverage(&self, span: Span) -> Stmt { + let loc = self.codegen_caller_span(&Some(span)); + // Should use Stmt::cover, but currently this doesn't work with CBMC + // unless it is run with '--cover cover' (see + // https://github.com/diffblue/cbmc/issues/6613). So for now use + // `assert(false)`. + self.codegen_assert( + Expr::bool_false(), + PropertyClass::CodeCoverage, + "code coverage for location", + loc, + ) + } + // The above represent the basic operations we can perform w.r.t. assert/assume/cover // Below are various helper functions for constructing the above more easily. @@ -144,13 +173,9 @@ impl<'tcx> GotocCtx<'tcx> { /// reachability check. /// If reachability checks are disabled, the function returns the message /// unmodified and an empty (skip) statement. - pub fn codegen_reachability_check( - &mut self, - msg: String, - span: Option, - ) -> (String, Stmt) { - let loc = self.codegen_caller_span(&span); - if self.queries.check_assertion_reachability { + pub fn codegen_reachability_check(&mut self, msg: String, span: SpanStable) -> (String, Stmt) { + let loc = self.codegen_caller_span_stable(span); + if self.queries.args().check_assertion_reachability { // Generate a unique ID for the assert let assert_id = self.next_check_id(); // Also add the unique ID as a prefix to the assert message so that it can be @@ -196,7 +221,7 @@ impl<'tcx> GotocCtx<'tcx> { } /// Kani hooks function calls to `panic` and calls this intead. - pub fn codegen_panic(&self, span: Option, fargs: Vec) -> Stmt { + pub fn codegen_panic(&self, span: SpanStable, fargs: Vec) -> Stmt { // CBMC requires that the argument to the assertion must be a string constant. // If there is one in the MIR, use it; otherwise, explain that we can't. assert!(!fargs.is_empty(), "Panic requires a string message"); @@ -204,7 +229,8 @@ impl<'tcx> GotocCtx<'tcx> { "This is a placeholder message; Kani doesn't support message formatted at runtime", )); - self.codegen_fatal_error(PropertyClass::Assertion, &msg, span) + let loc = self.codegen_caller_span_stable(span); + self.codegen_assert_assume_false(PropertyClass::Assertion, &msg, loc) } /// Kani does not currently support all MIR constructs. diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/block.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/block.rs index b015be5b401c..11cdf47f4ed2 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/block.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/block.rs @@ -3,8 +3,13 @@ use crate::codegen_cprover_gotoc::GotocCtx; use rustc_middle::mir::{BasicBlock, BasicBlockData}; +use stable_mir::mir::BasicBlockIdx; use tracing::debug; +pub fn bb_label(bb: BasicBlockIdx) -> String { + format!("bb{bb}") +} + impl<'tcx> GotocCtx<'tcx> { /// Generates Goto-C for a basic block. /// @@ -14,29 +19,57 @@ impl<'tcx> GotocCtx<'tcx> { /// `self.current_fn_mut().push_onto_block(...)` pub fn codegen_block(&mut self, bb: BasicBlock, bbd: &BasicBlockData<'tcx>) { debug!(?bb, "Codegen basicblock"); - self.current_fn_mut().set_current_bb(bb); let label: String = self.current_fn().find_label(&bb); + let check_coverage = self.queries.args().check_coverage; // the first statement should be labelled. if there is no statements, then the // terminator should be labelled. match bbd.statements.len() { 0 => { let term = bbd.terminator(); let tcode = self.codegen_terminator(term); - self.current_fn_mut().push_onto_block(tcode.with_label(label)); + // When checking coverage, the `coverage` check should be + // labelled instead. + if check_coverage { + let span = term.source_info.span; + let cover = self.codegen_coverage(span); + self.current_fn_mut().push_onto_block(cover.with_label(label)); + self.current_fn_mut().push_onto_block(tcode); + } else { + self.current_fn_mut().push_onto_block(tcode.with_label(label)); + } } _ => { let stmt = &bbd.statements[0]; let scode = self.codegen_statement(stmt); - self.current_fn_mut().push_onto_block(scode.with_label(label)); + // When checking coverage, the `coverage` check should be + // labelled instead. + if check_coverage { + let span = stmt.source_info.span; + let cover = self.codegen_coverage(span); + self.current_fn_mut().push_onto_block(cover.with_label(label)); + self.current_fn_mut().push_onto_block(scode); + } else { + self.current_fn_mut().push_onto_block(scode.with_label(label)); + } for s in &bbd.statements[1..] { + if check_coverage { + let span = s.source_info.span; + let cover = self.codegen_coverage(span); + self.current_fn_mut().push_onto_block(cover); + } let stmt = self.codegen_statement(s); self.current_fn_mut().push_onto_block(stmt); } - let term = self.codegen_terminator(bbd.terminator()); - self.current_fn_mut().push_onto_block(term); + let term = bbd.terminator(); + if check_coverage { + let span = term.source_info.span; + let cover = self.codegen_coverage(span); + self.current_fn_mut().push_onto_block(cover); + } + let tcode = self.codegen_terminator(term); + self.current_fn_mut().push_onto_block(tcode); } } - self.current_fn_mut().reset_current_bb(); } } diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/foreign_function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/foreign_function.rs index a102fb5fcd7b..617eeb8af506 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/foreign_function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/foreign_function.rs @@ -16,7 +16,9 @@ use cbmc::goto_program::{Expr, Location, Stmt, Symbol, Type}; use cbmc::{InternString, InternedString}; use lazy_static::lazy_static; use rustc_middle::ty::Instance; +use rustc_smir::rustc_internal; use rustc_target::abi::call::Conv; +use stable_mir::mir::mono::Instance as InstanceStable; use tracing::{debug, trace}; lazy_static! { @@ -44,8 +46,9 @@ impl<'tcx> GotocCtx<'tcx> { /// /// For other foreign items, we declare a shim and add to the list of foreign shims to be /// handled later. - pub fn codegen_foreign_fn(&mut self, instance: Instance<'tcx>) -> &Symbol { + pub fn codegen_foreign_fn(&mut self, instance: InstanceStable) -> &Symbol { debug!(?instance, "codegen_foreign_function"); + let instance = rustc_internal::internal(instance); let fn_name = self.symbol_name(instance).intern(); if self.symbol_table.contains(fn_name) { // Symbol has been added (either a built-in CBMC function or a Rust allocation function). @@ -89,7 +92,7 @@ impl<'tcx> GotocCtx<'tcx> { /// Checks whether C-FFI has been enabled or not. /// When enabled, we blindly encode the function type as is. fn is_cffi_enabled(&self) -> bool { - self.queries.unstable_features.contains(&"c-ffi".to_string()) + self.queries.args().unstable_features.contains(&"c-ffi".to_string()) } /// Generate code for a foreign function shim. @@ -110,16 +113,15 @@ impl<'tcx> GotocCtx<'tcx> { .args .iter() .enumerate() - .filter_map(|(idx, arg)| { - (!arg.is_ignore()).then(|| { - let arg_name = format!("{fn_name}::param_{idx}"); - let base_name = format!("param_{idx}"); - let arg_type = self.codegen_ty(arg.layout.ty); - let sym = Symbol::variable(&arg_name, &base_name, arg_type.clone(), loc) - .with_is_parameter(true); - self.symbol_table.insert(sym); - arg_type.as_parameter(Some(arg_name.into()), Some(base_name.into())) - }) + .filter(|&(_, arg)| (!arg.is_ignore())) + .map(|(idx, arg)| { + let arg_name = format!("{fn_name}::param_{idx}"); + let base_name = format!("param_{idx}"); + let arg_type = self.codegen_ty(arg.layout.ty); + let sym = Symbol::variable(&arg_name, &base_name, arg_type.clone(), loc) + .with_is_parameter(true); + self.symbol_table.insert(sym); + arg_type.as_parameter(Some(arg_name.into()), Some(base_name.into())) }) .collect(); let ret_type = self.codegen_ty(fn_abi.ret.layout.ty); diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs index 800ec8219e40..f3d79c7f06c5 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs @@ -28,7 +28,7 @@ impl<'tcx> GotocCtx<'tcx> { /// - 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) { - let mir = self.current_fn().mir(); + let mir = self.current_fn().body_internal(); let ldecls = mir.local_decls(); let num_args = self.get_params_size(); ldecls.indices().enumerate().for_each(|(idx, lc)| { @@ -76,7 +76,7 @@ impl<'tcx> GotocCtx<'tcx> { debug!("Double codegen of {:?}", old_sym); } else { assert!(old_sym.is_function()); - let mir = self.current_fn().mir(); + let mir = self.current_fn().body_internal(); self.print_instance(instance, mir); self.codegen_function_prelude(); self.codegen_declare_variables(); @@ -94,7 +94,7 @@ impl<'tcx> GotocCtx<'tcx> { /// Codegen changes required due to the function ABI. /// We currently untuple arguments for RustCall ABI where the `spread_arg` is set. fn codegen_function_prelude(&mut self) { - let mir = self.current_fn().mir(); + let mir = self.current_fn().body_internal(); if let Some(spread_arg) = mir.spread_arg { self.codegen_spread_arg(mir, spread_arg); } @@ -187,7 +187,7 @@ impl<'tcx> GotocCtx<'tcx> { let tupe = sig.inputs().last().unwrap(); let args = match tupe.kind() { - ty::Tuple(substs) => *substs, + ty::Tuple(args) => *args, _ => unreachable!("a function's spread argument must be a tuple"), }; let starting_idx = sig.inputs().len(); @@ -228,7 +228,7 @@ impl<'tcx> GotocCtx<'tcx> { debug!(krate = self.current_fn().krate().as_str()); debug!(is_std = self.current_fn().is_std()); self.ensure(&self.current_fn().name(), |ctx, fname| { - let mir = ctx.current_fn().mir(); + let mir = ctx.current_fn().body_internal(); Symbol::function( fname, ctx.fn_typ(), diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs index 4bd5566f9d7c..88aecf947975 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs @@ -4,6 +4,7 @@ use super::typ::{self, pointee_type}; use super::PropertyClass; use crate::codegen_cprover_gotoc::GotocCtx; +use crate::unwrap_or_return_codegen_unimplemented_stmt; use cbmc::goto_program::{ ArithmeticOverflowResult, BinaryOperator, BuiltinFn, Expr, Location, Stmt, Type, }; @@ -11,6 +12,7 @@ use rustc_middle::mir::{BasicBlock, Operand, Place}; use rustc_middle::ty::layout::{LayoutOf, ValidityRequirement}; use rustc_middle::ty::{self, Ty}; use rustc_middle::ty::{Instance, InstanceDef}; +use rustc_smir::rustc_internal; use rustc_span::Span; use tracing::debug; @@ -188,7 +190,7 @@ impl<'tcx> GotocCtx<'tcx> { let a = fargs.remove(0); let b = fargs.remove(0); let div_does_not_overflow = self.div_does_not_overflow(a.clone(), b.clone()); - let div_overflow_check = self.codegen_assert( + let div_overflow_check = self.codegen_assert_assume( div_does_not_overflow, PropertyClass::ArithmeticOverflow, format!("attempt to compute {} which would overflow", intrinsic).as_str(), @@ -232,20 +234,20 @@ impl<'tcx> GotocCtx<'tcx> { // Intrinsics which encode a value known during compilation macro_rules! codegen_intrinsic_const { () => {{ - let value = self - .tcx - .const_eval_instance(ty::ParamEnv::reveal_all(), instance, span) - .unwrap(); + let place = rustc_internal::stable(p); + let place_ty = self.place_ty_stable(&place); + let stable_instance = rustc_internal::stable(instance); + let alloc = stable_instance.try_const_eval(place_ty).unwrap(); // We assume that the intrinsic has type checked at this point, so // we can use the place type as the expression type. - let e = self.codegen_const_value(value, self.place_ty(p), span.as_ref()); - self.codegen_expr_to_place(p, e) + let e = self.codegen_allocation(&alloc, place_ty, rustc_internal::stable(span)); + self.codegen_expr_to_place_stable(&place, e) }}; } macro_rules! codegen_size_align { ($which: ident) => {{ - let tp_ty = instance.substs.type_at(0); + let tp_ty = instance.args.type_at(0); let arg = fargs.remove(0); let size_align = self.size_and_align_of_dst(tp_ty, arg); self.codegen_expr_to_place(p, size_align.$which) @@ -408,6 +410,7 @@ impl<'tcx> GotocCtx<'tcx> { ), "ceilf32" => codegen_simple_intrinsic!(Ceilf), "ceilf64" => codegen_simple_intrinsic!(Ceil), + "compare_bytes" => self.codegen_compare_bytes(fargs, p, loc), "copy" => self.codegen_copy(intrinsic, false, fargs, farg_types, Some(p), loc), "copy_nonoverlapping" => unreachable!( "Expected `core::intrinsics::unreachable` to be handled by `StatementKind::CopyNonOverlapping`" @@ -422,7 +425,7 @@ impl<'tcx> GotocCtx<'tcx> { "cttz" => codegen_count_intrinsic!(cttz, true), "cttz_nonzero" => codegen_count_intrinsic!(cttz, false), "discriminant_value" => { - let ty = instance.substs.type_at(0); + let ty = instance.args.type_at(0); let e = self.codegen_get_discriminant(fargs.remove(0).dereference(), ty, ret_ty); self.codegen_expr_to_place(p, e) } @@ -509,9 +512,11 @@ impl<'tcx> GotocCtx<'tcx> { loc, ), "simd_and" => codegen_intrinsic_binop!(bitand), - // TODO: `simd_div` and `simd_rem` don't check for overflow cases. - // - "simd_div" => codegen_intrinsic_binop!(div), + // TODO: `simd_rem` doesn't check for overflow cases for floating point operands. + // + "simd_div" | "simd_rem" => { + self.codegen_simd_div_with_overflow(fargs, intrinsic, p, loc) + } "simd_eq" => self.codegen_simd_cmp(Expr::vector_eq, fargs, p, span, farg_types, ret_ty), "simd_extract" => { self.codegen_intrinsic_simd_extract(fargs, p, farg_types, ret_ty, span) @@ -535,19 +540,8 @@ impl<'tcx> GotocCtx<'tcx> { self.codegen_simd_cmp(Expr::vector_neq, fargs, p, span, farg_types, ret_ty) } "simd_or" => codegen_intrinsic_binop!(bitor), - // TODO: `simd_div` and `simd_rem` don't check for overflow cases. - // - "simd_rem" => codegen_intrinsic_binop!(rem), - // TODO: `simd_shl` and `simd_shr` don't check overflow cases. - // - "simd_shl" => codegen_intrinsic_binop!(shl), - "simd_shr" => { - if fargs[0].typ().base_type().unwrap().is_signed(self.symbol_table.machine_model()) - { - codegen_intrinsic_binop!(ashr) - } else { - codegen_intrinsic_binop!(lshr) - } + "simd_shl" | "simd_shr" => { + self.codegen_simd_shift_with_distance_check(fargs, intrinsic, p, loc) } // "simd_shuffle#" => handled in an `if` preceding this match "simd_sub" => self.codegen_simd_op_with_overflow( @@ -772,7 +766,7 @@ impl<'tcx> GotocCtx<'tcx> { intrinsic: &str, span: Option, ) -> Stmt { - let ty = instance.substs.type_at(0); + let ty = instance.args.type_at(0); let layout = self.layout_of(ty); // Note: We follow the pattern seen in `codegen_panic_intrinsic` from `rustc_codegen_ssa` // https://github.com/rust-lang/rust/blob/master/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -995,6 +989,42 @@ impl<'tcx> GotocCtx<'tcx> { Stmt::block(vec![src_align_check, dst_align_check, overflow_check, copy_expr], loc) } + /// This is an intrinsic that was added in + /// that is essentially the + /// same as memcmp: it compares two slices up to the specified length. + /// The implementation is the same as the hook for `memcmp`. + pub fn codegen_compare_bytes( + &mut self, + mut fargs: Vec, + p: &Place<'tcx>, + loc: Location, + ) -> Stmt { + let lhs = fargs.remove(0).cast_to(Type::void_pointer()); + let rhs = fargs.remove(0).cast_to(Type::void_pointer()); + let len = fargs.remove(0); + let (len_var, len_decl) = self.decl_temp_variable(len.typ().clone(), Some(len), loc); + let (lhs_var, lhs_decl) = self.decl_temp_variable(lhs.typ().clone(), Some(lhs), loc); + let (rhs_var, rhs_decl) = self.decl_temp_variable(rhs.typ().clone(), Some(rhs), loc); + let is_len_zero = len_var.clone().is_zero(); + // We have to ensure that the pointers are valid even if we're comparing zero bytes. + // According to Rust's current definition (see https://github.com/model-checking/kani/issues/1489), + // this means they have to be non-null and aligned. + // But alignment is automatically satisfied because `memcmp` takes `*const u8` pointers. + let is_lhs_ok = lhs_var.clone().is_nonnull(); + let is_rhs_ok = rhs_var.clone().is_nonnull(); + let should_skip_pointer_checks = is_len_zero.and(is_lhs_ok).and(is_rhs_ok); + let place_expr = + unwrap_or_return_codegen_unimplemented_stmt!(self, self.codegen_place(&p)).goto_expr; + let res = should_skip_pointer_checks.ternary( + Expr::int_constant(0, place_expr.typ().clone()), // zero bytes are always equal (as long as pointers are nonnull and aligned) + BuiltinFn::Memcmp + .call(vec![lhs_var, rhs_var, len_var], loc) + .cast_to(place_expr.typ().clone()), + ); + let code = place_expr.assign(res, loc).with_location(loc); + Stmt::block(vec![len_decl, lhs_decl, rhs_decl, code], loc) + } + // In some contexts (e.g., compilation-time evaluation), // `ptr_guaranteed_cmp` compares two pointers and returns: // * 2 if the result is unknown. @@ -1042,7 +1072,7 @@ impl<'tcx> GotocCtx<'tcx> { let offset = fargs.remove(0); // Check that computing `offset` in bytes would not overflow - let ty = self.monomorphize(instance.substs.type_at(0)); + let ty = self.monomorphize(instance.args.type_at(0)); let (offset_bytes, bytes_overflow_check) = self.count_in_bytes(offset.clone(), ty, Type::ssize_t(), intrinsic, loc); @@ -1192,7 +1222,7 @@ impl<'tcx> GotocCtx<'tcx> { p: &Place<'tcx>, loc: Location, ) -> Stmt { - let ty = self.monomorphize(instance.substs.type_at(0)); + let ty = self.monomorphize(instance.args.type_at(0)); let dst = fargs.remove(0).cast_to(Type::void_pointer()); let val = fargs.remove(0).cast_to(Type::void_pointer()); let layout = self.layout_of(ty); @@ -1522,6 +1552,39 @@ impl<'tcx> GotocCtx<'tcx> { self.codegen_expr_to_place(p, e) } + /// Codegen for `simd_div` and `simd_rem` intrinsics. + /// This checks for overflow in signed integer division (i.e. when dividing the minimum integer + /// for the type by -1). Overflow checks on floating point division are handled by CBMC, as is + /// division by zero for both integers and floats. + fn codegen_simd_div_with_overflow( + &mut self, + fargs: Vec, + intrinsic: &str, + p: &Place<'tcx>, + loc: Location, + ) -> Stmt { + let op_fun = match intrinsic { + "simd_div" => Expr::div, + "simd_rem" => Expr::rem, + _ => unreachable!("expected simd_div or simd_rem"), + }; + let base_type = fargs[0].typ().base_type().unwrap().clone(); + if base_type.is_integer() && base_type.is_signed(self.symbol_table.machine_model()) { + let min_int_expr = base_type.min_int_expr(self.symbol_table.machine_model()); + let negative_one = Expr::int_constant(-1, base_type); + self.codegen_simd_op_with_overflow( + op_fun, + |a, b| a.eq(min_int_expr.clone()).and(b.eq(negative_one.clone())), + fargs, + intrinsic, + p, + loc, + ) + } else { + self.binop(p, fargs, op_fun) + } + } + /// Intrinsics which encode a SIMD arithmetic operation with overflow check. /// We expand the overflow check because CBMC overflow operations don't accept array as /// argument. @@ -1557,7 +1620,76 @@ impl<'tcx> GotocCtx<'tcx> { ); let res = op_fun(a, b); let expr_place = self.codegen_expr_to_place(p, res); - Stmt::block(vec![expr_place, check_stmt], loc) + Stmt::block(vec![check_stmt, expr_place], loc) + } + + /// Intrinsics which encode a SIMD bitshift. + /// Also checks for valid shift distance. Shifts on an integer of type T are UB if shift + /// distance < 0 or >= T::BITS. + fn codegen_simd_shift_with_distance_check( + &mut self, + mut fargs: Vec, + intrinsic: &str, + p: &Place<'tcx>, + loc: Location, + ) -> Stmt { + let values = fargs.remove(0); + let distances = fargs.remove(0); + + let values_len = values.typ().len().unwrap(); + let distances_len = distances.typ().len().unwrap(); + assert_eq!(values_len, distances_len, "expected same length vectors"); + + let value_type = values.typ().base_type().unwrap(); + let distance_type = distances.typ().base_type().unwrap(); + let value_width = value_type.sizeof_in_bits(&self.symbol_table); + let value_width_expr = Expr::int_constant(value_width, distance_type.clone()); + let distance_is_signed = distance_type.is_signed(self.symbol_table.machine_model()); + + let mut excessive_check = Expr::bool_false(); + let mut negative_check = Expr::bool_false(); + for i in 0..distances_len { + let index = Expr::int_constant(i, Type::ssize_t()); + let distance = distances.clone().index_array(index); + let excessive_distance_cond = distance.clone().ge(value_width_expr.clone()); + excessive_check = excessive_check.or(excessive_distance_cond); + if distance_is_signed { + let negative_distance_cond = distance.is_negative(); + negative_check = negative_check.or(negative_distance_cond); + } + } + let excessive_check_stmt = self.codegen_assert_assume( + excessive_check.not(), + PropertyClass::ArithmeticOverflow, + format!("attempt {intrinsic} with excessive shift distance").as_str(), + loc, + ); + + let op_fun = match intrinsic { + "simd_shl" => Expr::shl, + "simd_shr" => { + if distance_is_signed { + Expr::ashr + } else { + Expr::lshr + } + } + _ => unreachable!("expected a simd shift intrinsic"), + }; + let res = op_fun(values, distances); + let expr_place = self.codegen_expr_to_place(p, res); + + if distance_is_signed { + let negative_check_stmt = self.codegen_assert_assume( + negative_check.not(), + PropertyClass::ArithmeticOverflow, + format!("attempt {intrinsic} with negative shift distance").as_str(), + loc, + ); + Stmt::block(vec![excessive_check_stmt, negative_check_stmt, expr_place], loc) + } else { + Stmt::block(vec![excessive_check_stmt, expr_place], loc) + } } /// `simd_shuffle` constructs a new vector from the elements of two input @@ -1598,7 +1730,7 @@ impl<'tcx> GotocCtx<'tcx> { // [u32; n]: translated wrapped in a struct let indexes = fargs.remove(0); - let (_, vec_subtype) = rust_arg_types[0].simd_size_and_type(self.tcx); + let (in_type_len, vec_subtype) = rust_arg_types[0].simd_size_and_type(self.tcx); let (ret_type_len, ret_type_subtype) = rust_ret_type.simd_size_and_type(self.tcx); if ret_type_len != n { let err_msg = format!( @@ -1618,7 +1750,7 @@ impl<'tcx> GotocCtx<'tcx> { // An unsigned type here causes an invariant violation in CBMC. // Issue: https://github.com/diffblue/cbmc/issues/6298 let st_rep = Type::ssize_t(); - let n_rep = Expr::int_constant(n, st_rep.clone()); + let n_rep = Expr::int_constant(in_type_len, st_rep.clone()); // P = indexes.expanded_map(v -> if v < N then vec1[v] else vec2[v-N]) let elems = (0..n) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/mod.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/mod.rs index 4dbd0385f62c..938a784765e0 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/mod.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/mod.rs @@ -17,7 +17,9 @@ mod statement; mod static_var; // Visible for all codegen module. +mod ty_stable; pub(super) mod typ; pub use assert::PropertyClass; +pub use block::bb_label; pub use typ::TypeExt; diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs index 23211f022b23..0f2e0ad609f4 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs @@ -1,24 +1,31 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT +use crate::codegen_cprover_gotoc::codegen::ty_stable::StableConverter; use crate::codegen_cprover_gotoc::utils::slice_fat_ptr; use crate::codegen_cprover_gotoc::GotocCtx; use crate::unwrap_or_return_codegen_unimplemented; -use cbmc::btree_string_map; use cbmc::goto_program::{DatatypeComponent, Expr, ExprValue, Location, Stmt, Symbol, Type}; -use rustc_ast::ast::Mutability; -use rustc_middle::mir::interpret::{ - read_target_uint, AllocId, Allocation, ConstValue, GlobalAlloc, Scalar, -}; -use rustc_middle::mir::{Constant, ConstantKind, Operand, UnevaluatedConst}; -use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::{self, Const, ConstKind, FloatTy, Instance, IntTy, Ty, Uint, UintTy}; +use rustc_middle::mir::Operand as OperandInternal; +use rustc_middle::ty::{Const as ConstInternal, Instance as InstanceInternal}; +use rustc_smir::rustc_internal; use rustc_span::def_id::DefId; -use rustc_span::Span; -use rustc_target::abi::{Size, TagEncoding, Variants}; +use rustc_span::Span as SpanInternal; +use stable_mir::mir::alloc::{AllocId, GlobalAlloc}; +use stable_mir::mir::mono::{Instance, StaticDef}; +use stable_mir::mir::Operand; +use stable_mir::ty::{ + Allocation, Const, ConstantKind, FloatTy, FnDef, GenericArgs, IntTy, RigidTy, Size, Span, Ty, + TyKind, UintTy, +}; +use stable_mir::CrateDef; use tracing::{debug, trace}; +#[derive(Clone, Debug)] enum AllocData<'a> { - Bytes(&'a [u8]), + /// The data is represented as a slice of optional bytes, where None represents uninitialized + /// bytes. + Bytes(&'a [Option]), + /// The allocation has been translated to an expression. Expr(Expr), } @@ -28,143 +35,219 @@ impl<'tcx> GotocCtx<'tcx> { /// A MIR operand is either a constant (literal or `const` declaration) or a place /// (being moved or copied for this operation). /// An "operand" in MIR is the argument to an "Rvalue" (and is also used by some statements.) - pub fn codegen_operand(&mut self, o: &Operand<'tcx>) -> Expr { - trace!(operand=?o, "codegen_operand"); - match o { - Operand::Copy(d) | Operand::Move(d) => + pub fn codegen_operand(&mut self, operand: &OperandInternal<'tcx>) -> Expr { + self.codegen_operand_stable(&StableConverter::convert_operand(self, operand.clone())) + } + + pub fn codegen_operand_stable(&mut self, operand: &Operand) -> Expr { + trace!(?operand, "codegen_operand"); + match operand { + Operand::Copy(place) | Operand::Move(place) => // TODO: move is an opportunity to poison/nondet the original memory. { let projection = - unwrap_or_return_codegen_unimplemented!(self, self.codegen_place(d)); + unwrap_or_return_codegen_unimplemented!(self, self.codegen_place_stable(place)); // If the operand itself is a Dynamic (like when passing a boxed closure), // we need to pull off the fat pointer. In that case, the rustc kind() on // both the operand and the inner type are Dynamic. // Consider moving this check elsewhere in: // https://github.com/model-checking/kani/issues/277 - match self.operand_ty(o).kind() { - ty::Dynamic(..) => projection.fat_ptr_goto_expr.unwrap(), + match self.operand_ty_stable(operand).kind() { + TyKind::RigidTy(RigidTy::Dynamic(..)) => projection.fat_ptr_goto_expr.unwrap(), _ => projection.goto_expr, } } - Operand::Constant(c) => self.codegen_constant(c), - } - } - - /// Generate a goto expression from a MIR constant operand. - /// - /// There are three possibile constants: - /// 1. `Ty` means e.g. that it's a const generic parameter. (See `codegen_const`) - /// 2. `Val` means it's a constant value of various kinds. (See `codegen_const_value`) - /// 3. `Unevaluated` means we need to run the interpreter, to get a `ConstValue`. (See `codegen_const_unevaluated`) - fn codegen_constant(&mut self, c: &Constant<'tcx>) -> Expr { - trace!(constant=?c, "codegen_constant"); - let span = Some(&c.span); - match self.monomorphize(c.literal) { - ConstantKind::Ty(ct) => self.codegen_const(ct, span), - ConstantKind::Val(val, ty) => self.codegen_const_value(val, ty, span), - ConstantKind::Unevaluated(unevaluated, ty) => { - self.codegen_const_unevaluated(unevaluated, ty, span) + Operand::Constant(constant) => { + self.codegen_const(&constant.literal, Some(constant.span)) } } } - /// Runs the interpreter to get a `ConstValue`, then call `codegen_const_value` - fn codegen_const_unevaluated( + pub fn codegen_const_internal( &mut self, - unevaluated: UnevaluatedConst<'tcx>, - ty: Ty<'tcx>, - span: Option<&Span>, + constant: ConstInternal<'tcx>, + span: Option, ) -> Expr { - debug!(?unevaluated, "codegen_const_unevaluated"); - let const_val = - self.tcx.const_eval_resolve(ty::ParamEnv::reveal_all(), unevaluated, None).unwrap(); - self.codegen_const_value(const_val, ty, span) + let stable_const = StableConverter::convert_constant(self, constant); + let stable_span = rustc_internal::stable(span); + self.codegen_const(&stable_const, stable_span) } - /// Generate a goto expression from a MIR `Const`. + /// Generate a goto expression that represents a constant. /// - /// `Const` are special constant values that (only?) come from the type system, - /// and consequently only need monomorphization to produce a value. - /// - /// Not to be confused with the more general MIR `Constant` which may need interpretation. - pub fn codegen_const(&mut self, lit: Const<'tcx>, span: Option<&Span>) -> Expr { - debug!("found literal: {:?}", lit); - let lit = self.monomorphize(lit); - - match lit.kind() { - // A `ConstantKind::Ty(ConstKind::Unevaluated)` should no longer show up - // and should be a `ConstantKind::Unevaluated` instead (and thus handled - // at the level of `codegen_constant` instead of `codegen_const`.) - ConstKind::Unevaluated(_) => unreachable!(), - - ConstKind::Value(valtree) => { - let value = self.tcx.valtree_to_const_val((lit.ty(), valtree)); - debug!("The literal was a ConstValue {:?}", value); - self.codegen_const_value(value, lit.ty(), span) + /// There are two possible constants included in the body of an instance: + /// - Allocated: It will have its byte representation already defined. We try to eagerly + /// generate code for it as simple literals or constants if possible. Otherwise, we create + /// a memory allocation for them and access them indirectly. + /// - ZeroSized: These are ZST constants and they just need to match the right type. + fn codegen_const(&mut self, constant: &Const, span: Option) -> Expr { + trace!(?constant, "codegen_constant"); + match constant.kind() { + ConstantKind::Allocated(alloc) => self.codegen_allocation(alloc, constant.ty(), span), + ConstantKind::ZeroSized => { + let lit_ty = constant.ty(); + match lit_ty.kind() { + // Rust "function items" (not closures, not function pointers, see `codegen_fndef`) + TyKind::RigidTy(RigidTy::FnDef(def, args)) => { + self.codegen_fndef(def, &args, span) + } + _ => Expr::init_unit(self.codegen_ty_stable(lit_ty), &self.symbol_table), + } } - _ => { - unreachable!( - "monomorphized item shouldn't have this constant value: {:?}", - lit.kind() - ) + ConstantKind::Param(..) | ConstantKind::Unevaluated(..) => { + unreachable!() } } } - /// Generate a goto expression from a MIR `ConstValue`. + pub fn codegen_allocation(&mut self, alloc: &Allocation, ty: Ty, span: Option) -> Expr { + // First try to generate the constant without allocating memory. + let expr = self.try_codegen_constant(alloc, ty, span).unwrap_or_else(|| { + debug!("codegen_allocation try_fail"); + let mem_var = self.codegen_const_allocation(alloc, None); + mem_var + .cast_to(Type::unsigned_int(8).to_pointer()) + .cast_to(self.codegen_ty_stable(ty).to_pointer()) + .dereference() + }); + debug!(?expr, ?alloc, ?ty, "codegen_allocation"); + expr + } + + /// Before allocating space for a constant, try to generate a simple expression. /// - /// A `ConstValue` is the result of evaluation of a constant (of various original forms). - /// All forms of constant code generation ultimately land here, where we have an actual value - /// that we now just need to translate based on its kind. - pub fn codegen_const_value( + /// Generate an expression for a constant too small/simple to require an `Allocation` such as: + /// 1. integers + /// 2. ZST, or transparent structs of one (scalar) value + /// 3. enums that don't carry data + /// 4. unit, tuples (may be multi-ary!), or size-0 arrays + /// 5. pointers to an allocation + fn try_codegen_constant( &mut self, - v: ConstValue<'tcx>, - lit_ty: Ty<'tcx>, - span: Option<&Span>, - ) -> Expr { - trace!(val=?v, ?lit_ty, "codegen_const_value"); - match v { - ConstValue::Scalar(s) => self.codegen_scalar(s, lit_ty, span), - ConstValue::Slice { data, start, end } => { - self.codegen_slice_value(v, lit_ty, span, data.inner(), start, end) + alloc: &Allocation, + ty: Ty, + span: Option, + ) -> Option { + debug!(?alloc, ?ty, "try_codegen_constant"); + match ty.kind() { + TyKind::RigidTy(RigidTy::Int(it)) => { + let val = alloc.read_int().unwrap(); + Some(match it { + IntTy::Isize => Expr::ssize_constant(val, &self.symbol_table), + IntTy::I8 => Expr::int_constant(val as i8, Type::signed_int(8)), + IntTy::I16 => Expr::int_constant(val as i16, Type::signed_int(16)), + IntTy::I32 => Expr::int_constant(val as i32, Type::signed_int(32)), + IntTy::I64 => Expr::int_constant(val as i64, Type::signed_int(64)), + IntTy::I128 => Expr::int_constant(val, Type::signed_int(128)), + }) + } + TyKind::RigidTy(RigidTy::Uint(it)) => { + let val = alloc.read_uint().unwrap(); + Some(match it { + UintTy::Usize => Expr::size_constant(val, &self.symbol_table), + UintTy::U8 => Expr::int_constant(val as u8, Type::unsigned_int(8)), + UintTy::U16 => Expr::int_constant(val as u16, Type::unsigned_int(16)), + UintTy::U32 => Expr::int_constant(val as u32, Type::unsigned_int(32)), + UintTy::U64 => Expr::int_constant(val as u64, Type::unsigned_int(64)), + UintTy::U128 => Expr::int_constant(val, Type::unsigned_int(128)), + }) + } + TyKind::RigidTy(RigidTy::Bool) => { + Some(Expr::c_bool_constant(alloc.read_bool().unwrap())) + } + TyKind::RigidTy(RigidTy::Char) => { + Some(Expr::int_constant(alloc.read_int().unwrap(), Type::signed_int(32))) + } + TyKind::RigidTy(RigidTy::Float(k)) => + // rustc uses a sophisticated format for floating points that is hard to get f32/f64 from. + // Instead, we use integers with the right width to represent the bit pattern. + { + match k { + FloatTy::F32 => Some(Expr::float_constant_from_bitpattern( + alloc.read_uint().unwrap() as u32, + )), + FloatTy::F64 => Some(Expr::double_constant_from_bitpattern( + alloc.read_uint().unwrap() as u64, + )), + } + } + TyKind::RigidTy(RigidTy::RawPtr(inner_ty, _)) + | TyKind::RigidTy(RigidTy::Ref(_, inner_ty, _)) => { + Some(self.codegen_const_ptr(alloc, ty, inner_ty, span)) + } + TyKind::RigidTy(RigidTy::Adt(adt, args)) if adt.kind().is_struct() => { + // Structs only have one variant. + let variant = adt.variants_iter().next().unwrap(); + // There must be at least one field associated with the scalar data. + // Any additional fields correspond to ZSTs. + let field_types: Vec<_> = + variant.fields().iter().map(|f| f.ty_with_args(&args)).collect(); + // Check that there is a single non-ZST field. + let non_zst_types: Vec<_> = + field_types.iter().filter(|t| !self.is_zst_stable(**t)).collect(); + debug!(len=?non_zst_types.len(), "non_zst_types"); + if non_zst_types.len() == 1 { + // Only try to directly expand the constant if only one field has data. + // We could eventually expand this, but keep it simple for now. See: + // https://github.com/model-checking/kani/issues/2936 + let overall_type = self.codegen_ty_stable(ty); + let field_values: Vec = field_types + .iter() + .map(|t| { + if self.is_zst_stable(*t) { + Some(Expr::init_unit( + self.codegen_ty_stable(*t), + &self.symbol_table, + )) + } else { + self.try_codegen_constant(alloc, *t, span) + } + }) + .collect::>>()?; + Some(Expr::struct_expr_from_values( + overall_type, + field_values, + &self.symbol_table, + )) + } else { + // Structures with more than one non-ZST element are handled with an extra + // allocation. + None + } } - ConstValue::ByRef { alloc, offset } => { - debug!("ConstValue by ref {:?} {:?}", alloc, offset); - let mem_var = self.codegen_const_allocation(alloc.inner(), None); - mem_var - .cast_to(Type::unsigned_int(8).to_pointer()) - .plus(Expr::int_constant(offset.bytes(), Type::unsigned_int(64))) - .cast_to(self.codegen_ty(lit_ty).to_pointer()) - .dereference() + TyKind::RigidTy(RigidTy::Tuple(tys)) if tys.len() == 1 => { + let overall_t = self.codegen_ty_stable(ty); + let inner_expr = self.try_codegen_constant(alloc, tys[0], span)?; + Some(inner_expr.transmute_to(overall_t, &self.symbol_table)) } - ConstValue::ZeroSized => match lit_ty.kind() { - // Rust "function items" (not closures, not function pointers, see `codegen_fndef`) - ty::FnDef(d, substs) => self.codegen_fndef(*d, substs, span), - _ => Expr::init_unit(self.codegen_ty(lit_ty), &self.symbol_table), - }, + // Everything else we encode as an allocation. + _ => None, } } - /// Generate a goto expression from a MIR `ConstValue::Slice`. - /// - /// A constant slice is an internal reference to another constant allocation. - fn codegen_slice_value( + fn codegen_const_ptr( &mut self, - v: ConstValue<'tcx>, - lit_ty: Ty<'tcx>, - span: Option<&Span>, - data: &'tcx Allocation, - start: usize, - end: usize, + alloc: &Allocation, + ty: Ty, + inner_ty: Ty, + span: Option, ) -> Expr { - if let ty::Ref(_, ref_ty, _) = lit_ty.kind() { - match ref_ty.kind() { - ty::Str => { + debug!(?ty, ?alloc, "codegen_const_ptr"); + if self.use_fat_pointer_stable(inner_ty) { + match inner_ty.kind() { + TyKind::RigidTy(RigidTy::Str) => { // a string literal - // These seem to always start at 0 - assert_eq!(start, 0); // Create a static variable that holds its value - let mem_var = self.codegen_const_allocation(data, None); + assert_eq!( + alloc.provenance.ptrs.len(), + 1, + "Expected `&str` to point to a str buffer" + ); + let alloc_id = alloc.provenance.ptrs[0].1.0; + let GlobalAlloc::Memory(data) = GlobalAlloc::from(alloc_id) else { + unreachable!() + }; + let mem_var = self.codegen_const_allocation(&data, None); // Extract identifier for static variable. // codegen_allocation_auto_imm_name returns the *address* of @@ -178,281 +261,59 @@ impl<'tcx> GotocCtx<'tcx> { }; // Extract the actual string literal - let slice = data.inspect_with_uninit_and_ptr_outside_interpreter(start..end); - let s = ::std::str::from_utf8(slice).expect("non utf8 str from miri"); + let bytes = data.raw_bytes().unwrap(); + let s = ::std::str::from_utf8(&bytes).expect("non utf8 str from mir"); // Store the identifier to the string literal in the goto context self.str_literals.insert(*ident, s.into()); // Codegen as a fat pointer let data_expr = mem_var.cast_to(Type::unsigned_int(8).to_pointer()); - let len_expr = Expr::int_constant(end - start, Type::size_t()); - return slice_fat_ptr( - self.codegen_ty(lit_ty), + let len_expr = Expr::int_constant(bytes.len(), Type::size_t()); + slice_fat_ptr( + self.codegen_ty_stable(ty), data_expr, len_expr, &self.symbol_table, - ); - } - ty::Slice(slice_ty) => { - if let Uint(UintTy::U8) = slice_ty.kind() { - // The case where we have a slice of u8 is easy enough: make an array of u8 - let slice = - data.inspect_with_uninit_and_ptr_outside_interpreter(start..end); - let vec_of_bytes: Vec = slice - .iter() - .map(|b| Expr::int_constant(*b, Type::unsigned_int(8))) - .collect(); - let len = vec_of_bytes.len(); - let array_expr = - Expr::array_expr(Type::unsigned_int(8).array_of(len), vec_of_bytes); - let data_expr = array_expr.array_to_ptr(); - let len_expr = Expr::int_constant(len, Type::size_t()); - return slice_fat_ptr( - self.codegen_ty(lit_ty), - data_expr, - len_expr, - &self.symbol_table, - ); - } else { - // TODO: Handle cases with other types such as tuples and larger integers. - let loc = self.codegen_span_option(span.cloned()); - let typ = self.codegen_ty(lit_ty); - let operation_name = format!("Constant slice for type {slice_ty}"); - return self.codegen_unimplemented_expr( - &operation_name, - typ, - loc, - "https://github.com/model-checking/kani/issues/1339", - ); - } + ) } - ty::Adt(def, _) if Some(def.did()) == self.tcx.lang_items().c_str() => { - // TODO: Handle CString - // - let loc = self.codegen_span_option(span.cloned()); - let typ = self.codegen_ty(lit_ty); - let operation_name = "C string literal"; - return self.codegen_unimplemented_expr( - &operation_name, - typ, - loc, - "https://github.com/model-checking/kani/issues/2549", + TyKind::RigidTy(RigidTy::Slice(inner_ty)) => { + // Create a static variable that holds its value + assert_eq!( + alloc.provenance.ptrs.len(), + 1, + "Expected `&[T]` to point to a single buffer" ); + let alloc_id = alloc.provenance.ptrs[0].1.0; + let GlobalAlloc::Memory(data) = GlobalAlloc::from(alloc_id) else { + unreachable!() + }; + let mem_var = self.codegen_const_allocation(&data, None); + let inner_typ = self.codegen_ty_stable(inner_ty); + let len = data.bytes.len() / inner_typ.sizeof(&self.symbol_table) as usize; + let data_expr = mem_var.cast_to(inner_typ.to_pointer()); + let len_expr = Expr::int_constant(len, Type::size_t()); + slice_fat_ptr( + self.codegen_ty_stable(ty), + data_expr, + len_expr, + &self.symbol_table, + ) } - _ => {} - } - } - unimplemented!("\nv {:?}\nlit_ty {:?}\nspan {:?}", v, lit_ty.kind(), span); - } - - /// Generate a goto expression from a MIR `ConstValue::Scalar`. - /// - /// A `Scalar` is a constant too small/simple to require an `Allocation` such as: - /// 1. integers - /// 2. ZST, or transparent structs of one (scalar) value - /// 3. enums that don't carry data - /// 4. unit, tuples (may be multi-ary!), or size-0 arrays - /// 5. pointers to an allocation - fn codegen_scalar(&mut self, s: Scalar, ty: Ty<'tcx>, span: Option<&Span>) -> Expr { - debug!(scalar=?s, ?ty, kind=?ty.kind(), ?span, "codegen_scalar"); - match (s, &ty.kind()) { - (Scalar::Int(_), ty::Int(it)) => match it { - IntTy::I8 => Expr::int_constant(s.to_i8().unwrap(), Type::signed_int(8)), - IntTy::I16 => Expr::int_constant(s.to_i16().unwrap(), Type::signed_int(16)), - IntTy::I32 => Expr::int_constant(s.to_i32().unwrap(), Type::signed_int(32)), - IntTy::I64 => Expr::int_constant(s.to_i64().unwrap(), Type::signed_int(64)), - IntTy::I128 => Expr::int_constant(s.to_i128().unwrap(), Type::signed_int(128)), - IntTy::Isize => { - Expr::int_constant(s.to_target_isize(self).unwrap(), Type::ssize_t()) - } - }, - (Scalar::Int(_), ty::Uint(it)) => match it { - UintTy::U8 => Expr::int_constant(s.to_u8().unwrap(), Type::unsigned_int(8)), - UintTy::U16 => Expr::int_constant(s.to_u16().unwrap(), Type::unsigned_int(16)), - UintTy::U32 => Expr::int_constant(s.to_u32().unwrap(), Type::unsigned_int(32)), - UintTy::U64 => Expr::int_constant(s.to_u64().unwrap(), Type::unsigned_int(64)), - UintTy::U128 => Expr::int_constant(s.to_u128().unwrap(), Type::unsigned_int(128)), - UintTy::Usize => { - Expr::int_constant(s.to_target_usize(self).unwrap(), Type::size_t()) - } - }, - (Scalar::Int(_), ty::Bool) => Expr::c_bool_constant(s.to_bool().unwrap()), - (Scalar::Int(_), ty::Char) => { - Expr::int_constant(s.to_i32().unwrap(), Type::signed_int(32)) - } - (Scalar::Int(_), ty::Float(k)) => - // rustc uses a sophisticated format for floating points that is hard to get f32/f64 from. - // Instead, we use integers with the right width to represent the bit pattern. - { - match k { - FloatTy::F32 => Expr::float_constant_from_bitpattern(s.to_u32().unwrap()), - FloatTy::F64 => Expr::double_constant_from_bitpattern(s.to_u64().unwrap()), - } - } - (Scalar::Int(..), ty::FnDef(..)) => { - // This was removed here: https://github.com/rust-lang/rust/pull/98957. - unreachable!("ZST is no longer represented as a scalar") - } - (Scalar::Int(_), ty::RawPtr(tm)) => { - Expr::int_constant(s.to_u64().unwrap(), Type::unsigned_int(64)) - .cast_to(self.codegen_ty(tm.ty).to_pointer()) - } - // TODO: Removing this doesn't cause any regressions to fail. - // We need a regression for this case. - (Scalar::Int(int), ty::Ref(_, ty, _)) => { - if int.is_null() { - self.codegen_ty(*ty).to_pointer().null() - } else { - unreachable!() - } - } - (Scalar::Int(_), ty::Adt(adt, subst)) => { - if adt.is_struct() || adt.is_union() { - // in this case, we must have a one variant ADT. there are two cases - let variant = &adt.variants().raw[0]; - // if there is no field, then it's just a ZST - if variant.fields.is_empty() { - if adt.is_struct() { - let overall_t = self.codegen_ty(ty); - Expr::struct_expr_from_values(overall_t, vec![], &self.symbol_table) - } else { - unimplemented!() - } - } else { - // otherwise, there is just one field, which is stored as the scalar data - let field = &variant.fields[0usize.into()]; - let fty = field.ty(self.tcx, subst); - - let overall_t = self.codegen_ty(ty); - if adt.is_struct() { - self.codegen_single_variant_single_field(s, span, overall_t, fty) - } else { - unimplemented!() - } - } - } else { - // if it's an enum - let layout = self.layout_of(ty); - let overall_t = self.codegen_ty(ty); - match &layout.variants { - Variants::Single { index } => { - // here we must have one variant - let variant = &adt.variants()[*index]; - - match variant.fields.len() { - 0 => Expr::struct_expr_from_values( - overall_t, - vec![], - &self.symbol_table, - ), - 1 => { - let fty = variant.fields[0usize.into()].ty(self.tcx, subst); - self.codegen_single_variant_single_field( - s, span, overall_t, fty, - ) - } - _ => unreachable!(), - } - } - Variants::Multiple { tag_encoding, tag_field, .. } => match tag_encoding { - TagEncoding::Niche { .. } => { - let niche_offset = layout.fields.offset(*tag_field); - assert_eq!( - niche_offset, - Size::ZERO, - "nonzero offset for niche in scalar" - ); - let discr_ty = self.codegen_enum_discr_typ(ty); - let niche_val = self.codegen_scalar(s, discr_ty, span); - let result_type = self.codegen_ty(ty); - let niche_type = niche_val.typ().clone(); - assert_eq!( - niche_type.sizeof_in_bits(&self.symbol_table), - result_type.sizeof_in_bits(&self.symbol_table), - "niche type and enum have different size in scalar" - ); - niche_val.transmute_to(result_type, &self.symbol_table) - } - - TagEncoding::Direct => { - // then the scalar field stores the discriminant - let discr_ty = self.codegen_enum_discr_typ(ty); - let init = self.codegen_scalar(s, discr_ty, span); - let cgt = self.codegen_ty(ty); - let fields = - cgt.get_non_empty_components(&self.symbol_table).unwrap(); - // TagEncoding::Direct makes a constant with a tag but no data. - // Check our understanding that that the Enum must have one field, - // which is the tag, and no data field. - assert_eq!( - fields.len(), - 1, - "TagEncoding::Direct encountered for enum with non-empty variants" - ); - assert_eq!( - fields[0].name().to_string(), - "case", - "Unexpected field in enum/generator. Please report your failing case at https://github.com/model-checking/kani/issues/1465" - ); - Expr::struct_expr_with_nondet_fields( - cgt, - btree_string_map![("case", init)], - &self.symbol_table, - ) - } - }, - } - } - } - (Scalar::Int(int), ty::Tuple(_)) => { - // A ScalarInt has a u128-typed data field, so the result can never be larger than - // that and the conversion to a uint (of an actual size that may be smaller than - // 128 bits) will succeed. - let int_u128 = int.try_to_uint(int.size()).ok().unwrap(); - let overall_t = self.codegen_ty(ty); - let expr_int = Expr::int_constant( - int_u128, - Type::unsigned_int(overall_t.sizeof_in_bits(&self.symbol_table)), - ); - expr_int.transmute_to(overall_t, &self.symbol_table) + _ => unreachable!("{inner_ty:?}"), } - (_, ty::Array(_, _)) => { - let typ = self.codegen_ty(ty); - // we must have zero size array here - Expr::struct_expr_from_values( - typ.clone(), - vec![Expr::array_expr(typ, vec![])], - &self.symbol_table, - ) - } - (Scalar::Ptr(ptr, _size), _) => { - let res_t = self.codegen_ty(ty); - let (alloc_id, offset) = ptr.into_parts(); - self.codegen_alloc_pointer(res_t, alloc_id, offset, span) - } - _ => unimplemented!(), - } - } - - /// A private helper for `codegen_scalar`. Many "scalars" are more complex types, but get treated as scalars - /// because they only have one (small) field. We still translated them as struct types, however. - fn codegen_single_variant_single_field( - &mut self, - s: Scalar, - span: Option<&Span>, - overall_t: Type, - fty: Ty<'tcx>, - ) -> Expr { - if fty.is_unit() { - // TODO: It's not clear if this case is reachable. It's not covered by our test suite at least. - Expr::struct_expr_from_values(overall_t, vec![], &self.symbol_table) + } else if !alloc.provenance.ptrs.is_empty() { + // Codegen the provenance pointer. + trace!("codegen_const_ptr with_prov"); + let ptr = alloc.provenance.ptrs[0]; + let alloc_id = ptr.1.0; + let typ = self.codegen_ty_stable(ty); + self.codegen_alloc_pointer(typ, alloc_id, ptr.0, span) } else { - Expr::struct_expr_from_values( - overall_t, - vec![self.codegen_scalar(s, fty, span)], - &self.symbol_table, - ) + // If there's no provenance, just codegen the pointer address. + trace!("codegen_const_ptr no_prov"); + let expr = Expr::size_constant(alloc.read_uint().unwrap(), &self.symbol_table); + expr.cast_to(self.codegen_ty_stable(ty)) } } @@ -463,34 +324,37 @@ impl<'tcx> GotocCtx<'tcx> { res_t: Type, alloc_id: AllocId, offset: Size, - span: Option<&Span>, + span: Option, ) -> Expr { - let base_addr = match self.tcx.global_alloc(alloc_id) { + debug!(?res_t, ?alloc_id, "codegen_alloc_pointer"); + let base_addr = match GlobalAlloc::from(alloc_id) { GlobalAlloc::Function(instance) => { // We want to return the function pointer (not to be confused with function item) self.codegen_func_expr(instance, span).address_of() } - GlobalAlloc::Static(def_id) => self.codegen_static_pointer(def_id, false), + GlobalAlloc::Static(def) => self.codegen_static_pointer(def), GlobalAlloc::Memory(alloc) => { // Full (mangled) crate name added so that allocations from different // crates do not conflict. The name alone is insufficient because Rust // allows different versions of the same crate to be used. let name = format!("{}::{alloc_id:?}", self.full_crate_name()); - self.codegen_const_allocation(alloc.inner(), Some(name)) + self.codegen_const_allocation(&alloc, Some(name)) } - GlobalAlloc::VTable(ty, trait_ref) => { + alloc @ GlobalAlloc::VTable(..) => { // This is similar to GlobalAlloc::Memory but the type is opaque to rust and it // requires a bit more logic to get information about the allocation. - let alloc_id = self.tcx.vtable_allocation((ty, trait_ref)); - let alloc = self.tcx.global_alloc(alloc_id).unwrap_memory(); + let vtable_alloc_id = alloc.vtable_allocation().unwrap(); + let GlobalAlloc::Memory(alloc) = GlobalAlloc::from(vtable_alloc_id) else { + unreachable!() + }; let name = format!("{}::{alloc_id:?}", self.full_crate_name()); - self.codegen_const_allocation(alloc.inner(), Some(name)) + self.codegen_const_allocation(&alloc, Some(name)) } }; assert!(res_t.is_pointer() || res_t.is_transparent_type(&self.symbol_table)); let offset_addr = base_addr .cast_to(Type::unsigned_int(8).to_pointer()) - .plus(Expr::int_constant(offset.bytes(), Type::unsigned_int(64))); + .plus(Expr::int_constant(offset, Type::unsigned_int(64))); // In some cases, Rust uses a transparent type here. Convert the pointer to an rvalue // of the type expected. https://github.com/model-checking/kani/issues/822 @@ -505,13 +369,24 @@ impl<'tcx> GotocCtx<'tcx> { } } - /// Generate a goto expression for a pointer to a static or thread-local variable. + /// Generate a goto expression for a pointer to a static. /// /// These are not initialized here, see `codegen_static`. - pub fn codegen_static_pointer(&mut self, def_id: DefId, is_thread_local: bool) -> Expr { - let instance = Instance::mono(self.tcx, def_id); + fn codegen_static_pointer(&mut self, def: StaticDef) -> Expr { + self.codegen_instance_pointer(Instance::from(def), false) + } - let sym = self.ensure(&self.symbol_name(instance), |ctx, name| { + /// Generate a goto expression for a pointer to a thread-local variable. + /// + /// These are not initialized here, see `codegen_static`. + pub fn codegen_thread_local_pointer(&mut self, def_id: DefId) -> Expr { + let instance = rustc_internal::stable(InstanceInternal::mono(self.tcx, def_id)); + self.codegen_instance_pointer(instance, true) + } + + /// Generate a goto expression for a pointer to a static or thread-local variable. + fn codegen_instance_pointer(&mut self, instance: Instance, is_thread_local: bool) -> Expr { + let sym = self.ensure(&instance.mangled_name(), |ctx, name| { // Rust has a notion of "extern static" variables. These are in an "extern" block, // and so aren't initialized in the current codegen unit. For example (from std): // extern "C" { @@ -527,14 +402,14 @@ impl<'tcx> GotocCtx<'tcx> { // 1. If they are `is_extern` they are nondet-initialized. // 2. If they are `!is_extern`, they are zero-initialized. // So we recognize a Rust "extern" declaration and pass that information along. - let is_extern = ctx.tcx.is_foreign_item(def_id); + let is_extern = instance.is_foreign_item(); - let span = ctx.tcx.def_span(def_id); + let span = instance.def.span(); Symbol::static_variable( name.to_string(), name.to_string(), - ctx.codegen_ty(instance.ty(ctx.tcx, ty::ParamEnv::reveal_all())), - ctx.codegen_span(&span), + ctx.codegen_ty_stable(instance.ty()), + ctx.codegen_span_stable(span), ) .with_is_extern(is_extern) .with_is_thread_local(is_thread_local) @@ -549,20 +424,18 @@ impl<'tcx> GotocCtx<'tcx> { /// /// These constants can be named constants which are declared by the user, or constant values /// used scattered throughout the source - fn codegen_const_allocation(&mut self, alloc: &'tcx Allocation, name: Option) -> Expr { + fn codegen_const_allocation(&mut self, alloc: &Allocation, name: Option) -> Expr { debug!(?name, "codegen_const_allocation"); - assert_eq!( - alloc.mutability, - Mutability::Not, - "Expected constant allocation for `{name:?}`, but got a mutable instead" - ); - if !self.alloc_map.contains_key(&alloc) { - let name = if let Some(name) = name { name } else { self.next_global_name() }; - self.codegen_alloc_in_memory(alloc, name); - } + let alloc_name = match self.alloc_map.get(alloc) { + None => { + let alloc_name = if let Some(name) = name { name } else { self.next_global_name() }; + self.codegen_alloc_in_memory(alloc.clone(), alloc_name.clone()); + alloc_name + } + Some(name) => name.clone(), + }; - let mem_place = - self.symbol_table.lookup(&self.alloc_map.get(&alloc).unwrap()).unwrap().to_expr(); + let mem_place = self.symbol_table.lookup(alloc_name).unwrap().to_expr(); mem_place.address_of() } @@ -571,8 +444,8 @@ impl<'tcx> GotocCtx<'tcx> { /// /// This function is ultimately responsible for creating new statically initialized global variables /// in our goto binaries. - pub fn codegen_alloc_in_memory(&mut self, alloc: &'tcx Allocation, name: String) { - debug!("codegen_alloc_in_memory name: {}", name); + pub fn codegen_alloc_in_memory(&mut self, alloc: Allocation, name: String) { + debug!(?alloc, ?name, "codegen_alloc_in_memory"); let struct_name = &format!("{name}::struct"); // The declaration of a static variable may have one type and the constant initializer for @@ -580,16 +453,17 @@ impl<'tcx> GotocCtx<'tcx> { // initializers. For example, for a boolean static variable, the variable will have type // CBool and the initializer will be a single byte (a one-character array) representing the // bit pattern for the boolean value. - let alloc_typ_ref = self.ensure_struct(&struct_name, &struct_name, |ctx, _| { - ctx.codegen_allocation_data(alloc) + let alloc_data = self.codegen_allocation_data(&alloc); + let alloc_typ_ref = self.ensure_struct(struct_name, struct_name, |_, _| { + alloc_data .iter() .enumerate() .map(|(i, d)| match d { AllocData::Bytes(bytes) => DatatypeComponent::field( - &i.to_string(), + i.to_string(), Type::unsigned_int(8).array_of(bytes.len()), ), - AllocData::Expr(e) => DatatypeComponent::field(&i.to_string(), e.typ().clone()), + AllocData::Expr(e) => DatatypeComponent::field(i.to_string(), e.typ().clone()), }) .collect() }); @@ -609,7 +483,6 @@ impl<'tcx> GotocCtx<'tcx> { // Assign the initial value `val` to `var` via an intermediate `temp_var` to allow for // transmuting the allocation type to the global static variable type. - let alloc_data = self.codegen_allocation_data(alloc); let val = Expr::struct_expr_from_values( alloc_typ_ref.clone(), alloc_data @@ -619,7 +492,9 @@ impl<'tcx> GotocCtx<'tcx> { Type::unsigned_int(8).array_of(bytes.len()), bytes .iter() - .map(|b| Expr::int_constant(*b, Type::unsigned_int(8))) + // We should consider adding a poison / undet where we have none + // This mimics the behaviour before StableMIR though. + .map(|b| Expr::int_constant(b.unwrap_or(0), Type::unsigned_int(8))) .collect(), ), AllocData::Expr(e) => e.clone(), @@ -641,44 +516,34 @@ impl<'tcx> GotocCtx<'tcx> { self.alloc_map.insert(alloc, name); } - /// This is an internal helper function for `codegen_alloc_in_memory` and you should understand - /// it by starting there. + /// This is an internal helper function for `codegen_alloc_in_memory`. /// /// We codegen global statics as their own unique struct types, and this creates a field-by-field /// representation of what those fields should be initialized with. /// (A field is either bytes, or initialized with an expression.) - fn codegen_allocation_data(&mut self, alloc: &'tcx Allocation) -> Vec> { - let mut alloc_vals = Vec::with_capacity(alloc.provenance().ptrs().len() + 1); - let pointer_size = - Size::from_bytes(self.symbol_table.machine_model().pointer_width_in_bytes()); + fn codegen_allocation_data<'a>(&mut self, alloc: &'a Allocation) -> Vec> { + let mut alloc_vals = Vec::with_capacity(alloc.provenance.ptrs.len() + 1); + let pointer_size = self.symbol_table.machine_model().pointer_width_in_bytes(); - let mut next_offset = Size::ZERO; - for &(offset, alloc_id) in alloc.provenance().ptrs().iter() { + let mut next_offset = 0; + for &(offset, prov) in alloc.provenance.ptrs.iter() { if offset > next_offset { - let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter( - next_offset.bytes_usize()..offset.bytes_usize(), - ); + let bytes = &alloc.bytes[next_offset..offset]; alloc_vals.push(AllocData::Bytes(bytes)); } - let ptr_offset = { - let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter( - offset.bytes_usize()..(offset + pointer_size).bytes_usize(), - ); - read_target_uint(self.tcx.sess.target.options.endian, bytes) - } - .unwrap(); + let ptr_offset = { alloc.read_partial_uint(offset..(offset + pointer_size)).unwrap() }; alloc_vals.push(AllocData::Expr(self.codegen_alloc_pointer( Type::signed_int(8).to_pointer(), - alloc_id, - Size::from_bytes(ptr_offset), + prov.0, + ptr_offset.try_into().unwrap(), None, ))); next_offset = offset + pointer_size; } - if alloc.len() >= next_offset.bytes_usize() { - let range = next_offset.bytes_usize()..alloc.len(); - let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(range); + if alloc.bytes.len() >= next_offset { + let range = next_offset..alloc.bytes.len(); + let bytes = &alloc.bytes[range]; alloc_vals.push(AllocData::Bytes(bytes)); } @@ -696,14 +561,8 @@ impl<'tcx> GotocCtx<'tcx> { /// function types. /// /// See - pub fn codegen_fndef( - &mut self, - d: DefId, - substs: ty::subst::SubstsRef<'tcx>, - span: Option<&Span>, - ) -> Expr { - let instance = - Instance::resolve(self.tcx, ty::ParamEnv::reveal_all(), d, substs).unwrap().unwrap(); + pub fn codegen_fndef(&mut self, def: FnDef, args: &GenericArgs, span: Option) -> Expr { + let instance = Instance::resolve(def, args).unwrap(); self.codegen_fn_item(instance, span) } @@ -713,18 +572,18 @@ impl<'tcx> GotocCtx<'tcx> { /// because the symbol should have the type. The problem is that the type in the symbol table /// sometimes subtly differs from the type that codegen_function_sig returns. /// This is tracked in . - fn codegen_func_symbol(&mut self, instance: Instance<'tcx>) -> (&Symbol, Type) { - let funct = self.codegen_function_sig(self.fn_sig_of_instance(instance)); - let sym = if self.tcx.is_foreign_item(instance.def_id()) { + fn codegen_func_symbol(&mut self, instance: Instance) -> (&Symbol, Type) { + let funct = self.codegen_function_sig(self.fn_sig_of_instance_stable(instance)); + let sym = if instance.is_foreign_item() { // Get the symbol that represents a foreign instance. self.codegen_foreign_fn(instance) } else { // All non-foreign functions should've been declared beforehand. trace!(func=?instance, "codegen_func_symbol"); - let func = self.symbol_name(instance); + let func = self.symbol_name_stable(instance); self.symbol_table - .lookup(func) - .expect("Function `{func}` should've been declared before usage") + .lookup(&func) + .unwrap_or_else(|| panic!("Function `{func}` should've been declared before usage")) }; (sym, funct) } @@ -734,30 +593,39 @@ impl<'tcx> GotocCtx<'tcx> { /// Note: In general with this `Expr` you should immediately either `.address_of()` or `.call(...)`. /// /// This should not be used where Rust expects a "function item" (See `codegen_fn_item`) - pub fn codegen_func_expr(&mut self, instance: Instance<'tcx>, span: Option<&Span>) -> Expr { - let (func_symbol, func_typ) = self.codegen_func_symbol(instance); + pub fn codegen_func_expr_internal( + &mut self, + instance: InstanceInternal<'tcx>, + span: Option<&SpanInternal>, + ) -> Expr { + let (func_symbol, func_typ) = self.codegen_func_symbol(rustc_internal::stable(instance)); Expr::symbol_expression(func_symbol.name, func_typ) .with_location(self.codegen_span_option(span.cloned())) } + pub fn codegen_func_expr(&mut self, instance: Instance, span: Option) -> Expr { + let (func_symbol, func_typ) = self.codegen_func_symbol(instance); + Expr::symbol_expression(func_symbol.name, func_typ) + .with_location(self.codegen_span_option_stable(span)) + } + /// Generate a goto expression referencing the singleton value for a MIR "function item". /// /// For a given function instance, generate a ZST struct and return a singleton reference to that. /// This is the Rust "function item". See /// This is not the function pointer, for that use `codegen_func_expr`. - fn codegen_fn_item(&mut self, instance: Instance<'tcx>, span: Option<&Span>) -> Expr { + fn codegen_fn_item(&mut self, instance: Instance, span: Option) -> Expr { let (func_symbol, _) = self.codegen_func_symbol(instance); let mangled_name = func_symbol.name; - let fn_item_struct_ty = self.codegen_fndef_type(instance); + let fn_item_struct_ty = self.codegen_fndef_type_stable(instance); // This zero-sized object that a function name refers to in Rust is globally unique, so we create such a global object. let fn_singleton_name = format!("{mangled_name}::FnDefSingleton"); - let fn_singleton = self.ensure_global_var( + self.ensure_global_var( &fn_singleton_name, false, fn_item_struct_ty, - Location::none(), + self.codegen_span_option_stable(span), |_, _| None, // zero-sized, so no initialization necessary - ); - fn_singleton.with_location(self.codegen_span_option(span.cloned())) + ) } } diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/place.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/place.rs index 95fadc7bbaf2..beb35d469391 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/place.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/place.rs @@ -6,28 +6,32 @@ //! in [GotocCtx::codegen_place] below. use super::typ::TypeExt; -use crate::codegen_cprover_gotoc::codegen::typ::{pointee_type, std_pointee_type}; +use crate::codegen_cprover_gotoc::codegen::ty_stable::{pointee_type, StableConverter}; +use crate::codegen_cprover_gotoc::codegen::typ::{ + pointee_type as pointee_type_internal, std_pointee_type, +}; use crate::codegen_cprover_gotoc::utils::{dynamic_fat_ptr, slice_fat_ptr}; use crate::codegen_cprover_gotoc::GotocCtx; use crate::unwrap_or_return_codegen_unimplemented; use cbmc::goto_program::{Expr, Location, Type}; -use rustc_abi::FieldIdx; -use rustc_hir::Mutability; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::{ - mir::{Local, Place, ProjectionElem}, - ty::{self, Ty, TypeAndMut, VariantDef}, + mir::{Local as LocalInternal, Place as PlaceInternal}, + ty::Ty as TyInternal, }; -use rustc_target::abi::{TagEncoding, VariantIdx, Variants}; +use rustc_smir::rustc_internal; +use rustc_target::abi::{TagEncoding, Variants}; +use stable_mir::mir::{FieldIdx, Local, Mutability, Place, ProjectionElem}; +use stable_mir::ty::{RigidTy, Ty, TyKind, VariantDef, VariantIdx}; use tracing::{debug, trace, warn}; /// A projection in Kani can either be to a type (the normal case), /// or a variant in the case of a downcast. -#[derive(Debug)] -pub enum TypeOrVariant<'tcx> { - Type(Ty<'tcx>), - Variant(&'tcx VariantDef), - GeneratorVariant(VariantIdx), +#[derive(Copy, Clone, Debug)] +pub enum TypeOrVariant { + Type(Ty), + Variant(VariantDef), + CoroutineVariant(VariantIdx), } /// A struct for storing the data for passing to `codegen_unimplemented` @@ -56,33 +60,31 @@ impl UnimplementedData { /// Relevent information about a projected place (i.e. an lvalue). #[derive(Debug)] -pub struct ProjectedPlace<'tcx> { +pub struct ProjectedPlace { /// The goto expression that represents the lvalue pub goto_expr: Expr, /// The MIR type of that expression. Normally a type, but can be a variant following a downcast. - /// Invariant: guaranteed to be monomorphized by the type constructor - pub mir_typ_or_variant: TypeOrVariant<'tcx>, + pub mir_typ_or_variant: TypeOrVariant, /// If a fat pointer was traversed during the projection, it is stored here. /// This is useful if we need to use any of its fields, for e.g. to generate a rvalue ref /// or to implement the `length` operation. pub fat_ptr_goto_expr: Option, /// The MIR type of the visited fat pointer, if one was traversed during the projection. - /// Invariant: guaranteed to be monomorphized by the type constructor - pub fat_ptr_mir_typ: Option>, + pub fat_ptr_mir_typ: Option, } /// Getters #[allow(dead_code)] -impl<'tcx> ProjectedPlace<'tcx> { +impl ProjectedPlace { pub fn goto_expr(&self) -> &Expr { &self.goto_expr } - pub fn mir_typ_or_variant(&self) -> &TypeOrVariant<'tcx> { + pub fn mir_typ_or_variant(&self) -> &TypeOrVariant { &self.mir_typ_or_variant } - pub fn mir_typ(&self) -> Ty<'tcx> { + pub fn mir_typ(&self) -> Ty { self.mir_typ_or_variant.expect_type() } @@ -90,29 +92,29 @@ impl<'tcx> ProjectedPlace<'tcx> { &self.fat_ptr_goto_expr } - pub fn fat_ptr_mir_typ(&self) -> &Option> { + pub fn fat_ptr_mir_typ(&self) -> &Option { &self.fat_ptr_mir_typ } } /// Constructor -impl<'tcx> ProjectedPlace<'tcx> { +impl ProjectedPlace { fn check_expr_typ_mismatch( expr: &Expr, - typ: &TypeOrVariant<'tcx>, - ctx: &mut GotocCtx<'tcx>, + typ: &TypeOrVariant, + ctx: &mut GotocCtx, ) -> Option<(Type, Type)> { match typ { TypeOrVariant::Type(t) => { let expr_ty = expr.typ().clone(); - let type_from_mir = ctx.codegen_ty(*t); + let type_from_mir = ctx.codegen_ty_stable(*t); if expr_ty != type_from_mir { match t.kind() { // Slice references (`&[T]`) store raw pointers to the element type `T` // due to pointer decay. They are fat pointers with the following repr: // SliceRef { data: *T, len: usize }. // In those cases, the projection will yield a pointer type. - ty::Slice(..) | ty::Str + TyKind::RigidTy(RigidTy::Slice(..)) | TyKind::RigidTy(RigidTy::Str) if expr_ty.is_pointer() && expr_ty.base_type() == type_from_mir.base_type() => { @@ -120,7 +122,7 @@ impl<'tcx> ProjectedPlace<'tcx> { } // TODO: Do we really need this? // https://github.com/model-checking/kani/issues/1092 - ty::Dynamic(..) + TyKind::RigidTy(RigidTy::Dynamic(..)) if expr_ty.is_pointer() && *expr_ty.base_type().unwrap() == type_from_mir => { @@ -133,32 +135,39 @@ impl<'tcx> ProjectedPlace<'tcx> { } } // TODO: handle Variant https://github.com/model-checking/kani/issues/448 - TypeOrVariant::Variant(_) | TypeOrVariant::GeneratorVariant(_) => None, + TypeOrVariant::Variant(_) | TypeOrVariant::CoroutineVariant(_) => None, } } fn check_fat_ptr_typ( fat_ptr: &Option, - fat_ptr_typ: &Option>, - ctx: &mut GotocCtx<'tcx>, + fat_ptr_typ: &Option, + ctx: &mut GotocCtx, ) -> bool { if let Some(fat_ptr) = fat_ptr { fat_ptr.typ().is_rust_fat_ptr(&ctx.symbol_table) - && fat_ptr.typ() == &ctx.codegen_ty(fat_ptr_typ.unwrap()) + && fat_ptr.typ() == &ctx.codegen_ty_stable(fat_ptr_typ.unwrap()) } else { true } } + pub fn try_new_internal<'tcx>( + goto_expr: Expr, + ty: TyInternal<'tcx>, + ctx: &mut GotocCtx<'tcx>, + ) -> Result { + let ty = ctx.monomorphize(ty); + Self::try_new(goto_expr, TypeOrVariant::Type(rustc_internal::stable(ty)), None, None, ctx) + } + pub fn try_new( goto_expr: Expr, - mir_typ_or_variant: TypeOrVariant<'tcx>, + mir_typ_or_variant: TypeOrVariant, fat_ptr_goto_expr: Option, - fat_ptr_mir_typ: Option>, - ctx: &mut GotocCtx<'tcx>, + fat_ptr_mir_typ: Option, + ctx: &mut GotocCtx, ) -> Result { - let mir_typ_or_variant = mir_typ_or_variant.monomorphize(ctx); - let fat_ptr_mir_typ = fat_ptr_mir_typ.map(|t| ctx.monomorphize(t)); if let Some(fat_ptr) = &fat_ptr_goto_expr { assert!( fat_ptr.typ().is_rust_fat_ptr(&ctx.symbol_table), @@ -167,9 +176,6 @@ impl<'tcx> ProjectedPlace<'tcx> { ctx.current_fn().readable_name() ); } - // TODO: these assertions fail on a few regressions. Figure out why. - // I think it may have to do with boxed fat pointers. - // https://github.com/model-checking/kani/issues/277 if let Some((expr_ty, ty_from_mir)) = Self::check_expr_typ_mismatch(&goto_expr, &mir_typ_or_variant, ctx) { @@ -202,108 +208,151 @@ impl<'tcx> ProjectedPlace<'tcx> { } } -impl<'tcx> TypeOrVariant<'tcx> { - pub fn monomorphize(self, ctx: &GotocCtx<'tcx>) -> Self { - match self { - TypeOrVariant::Type(t) => TypeOrVariant::Type(ctx.monomorphize(t)), - TypeOrVariant::Variant(_) | TypeOrVariant::GeneratorVariant(_) => self, - } - } -} - -impl<'tcx> TypeOrVariant<'tcx> { - pub fn expect_type(&self) -> Ty<'tcx> { +impl TypeOrVariant { + pub fn expect_type(&self) -> Ty { match self { TypeOrVariant::Type(t) => *t, TypeOrVariant::Variant(v) => panic!("expect a type but variant is found: {v:?}"), - TypeOrVariant::GeneratorVariant(v) => { - panic!("expect a type but generator variant is found: {v:?}") + TypeOrVariant::CoroutineVariant(v) => { + panic!("expect a type but coroutine variant is found: {v:?}") } } } #[allow(dead_code)] - pub fn expect_variant(&self) -> &'tcx VariantDef { + pub fn expect_variant(&self) -> &VariantDef { match self { TypeOrVariant::Type(t) => panic!("expect a variant but type is found: {t:?}"), TypeOrVariant::Variant(v) => v, - TypeOrVariant::GeneratorVariant(v) => { - panic!("expect a variant but generator variant found {v:?}") + TypeOrVariant::CoroutineVariant(v) => { + panic!("expect a variant but coroutine variant found {v:?}") } } } } impl<'tcx> GotocCtx<'tcx> { + /// Codegen field access for types that allow direct field projection. + /// + /// I.e.: Algebraic data types, closures, and coroutines. + /// + /// Other composite types such as array only support index projection. fn codegen_field( &mut self, - res: Expr, - t: TypeOrVariant<'tcx>, - f: &FieldIdx, + parent_expr: Expr, + parent_ty_or_var: TypeOrVariant, + field_idx: FieldIdx, + field_ty_or_var: TypeOrVariant, ) -> Result { - match t { - TypeOrVariant::Type(t) => { - match t.kind() { - ty::Alias(..) - | ty::Bool - | ty::Char - | ty::Int(_) - | ty::Uint(_) - | ty::Float(_) - | ty::FnPtr(_) - | ty::Never - | ty::FnDef(..) - | ty::GeneratorWitness(..) - | ty::Foreign(..) - | ty::Dynamic(..) - | ty::Bound(..) - | ty::Placeholder(..) - | ty::Param(_) - | ty::Infer(_) - | ty::Error(_) => unreachable!("type {:?} does not have a field", t), - ty::Tuple(_) => { - Ok(res.member(&Self::tuple_fld_name(f.index()), &self.symbol_table)) + match parent_ty_or_var { + TypeOrVariant::Type(parent_ty) => { + match parent_ty.kind() { + TyKind::Alias(..) + | TyKind::RigidTy(RigidTy::Bool) + | TyKind::RigidTy(RigidTy::Char) + | TyKind::RigidTy(RigidTy::Int(_)) + | TyKind::RigidTy(RigidTy::Uint(_)) + | TyKind::RigidTy(RigidTy::Float(_)) + | TyKind::RigidTy(RigidTy::FnPtr(_)) + | TyKind::RigidTy(RigidTy::Never) + | TyKind::RigidTy(RigidTy::FnDef(..)) + | TyKind::RigidTy(RigidTy::CoroutineWitness(..)) + | TyKind::RigidTy(RigidTy::Foreign(..)) + | TyKind::RigidTy(RigidTy::Dynamic(..)) + | TyKind::Bound(..) + | TyKind::Param(..) => { + unreachable!("type {parent_ty:?} does not have a field") } - ty::Adt(def, _) if def.repr().simd() => { - // this is a SIMD vector - the index represents one - // of the elements, so we want to index as an array - // Example: - // pub struct i64x2(i64, i64); - // fn main() { - // let v = i64x2(1, 2); - // assert!(v.0 == 1); // refers to the first i64 - // assert!(v.1 == 2); - // } - let size_index = Expr::int_constant(f.index(), Type::size_t()); - Ok(res.index_array(size_index)) + TyKind::RigidTy(RigidTy::Tuple(_)) => { + Ok(parent_expr.member(Self::tuple_fld_name(field_idx), &self.symbol_table)) + } + TyKind::RigidTy(RigidTy::Adt(def, _)) + if rustc_internal::internal(def).repr().simd() => + { + Ok(self.codegen_simd_field( + parent_expr, + field_idx, + field_ty_or_var.expect_type(), + )) } // if we fall here, then we are handling either a struct or a union - ty::Adt(def, _) => { - let field = &def.variants().raw[0].fields[*f]; - Ok(res.member(&field.name.to_string(), &self.symbol_table)) + TyKind::RigidTy(RigidTy::Adt(def, _)) => { + let fields = def.variants_iter().next().unwrap().fields(); + let field = &fields[field_idx]; + Ok(parent_expr.member(field.name.to_string(), &self.symbol_table)) + } + TyKind::RigidTy(RigidTy::Closure(..)) => { + Ok(parent_expr.member(field_idx.to_string(), &self.symbol_table)) } - ty::Closure(..) => Ok(res.member(&f.index().to_string(), &self.symbol_table)), - ty::Generator(..) => { - let field_name = self.generator_field_name(f.as_usize()); - Ok(res + TyKind::RigidTy(RigidTy::Coroutine(..)) => { + let field_name = self.coroutine_field_name(field_idx); + Ok(parent_expr .member("direct_fields", &self.symbol_table) .member(field_name, &self.symbol_table)) } - _ => unimplemented!(), + TyKind::RigidTy(RigidTy::Str) + | TyKind::RigidTy(RigidTy::Array(_, _)) + | TyKind::RigidTy(RigidTy::Slice(_)) + | TyKind::RigidTy(RigidTy::RawPtr(..)) + | TyKind::RigidTy(RigidTy::Ref(_, _, _)) => { + unreachable!( + "element of {parent_ty:?} is not accessed via field projection" + ) + } } } // if we fall here, then we are handling an enum - TypeOrVariant::Variant(v) => { - let field = &v.fields[*f]; - Ok(res.member(&field.name.to_string(), &self.symbol_table)) + TypeOrVariant::Variant(parent_var) => { + let fields = parent_var.fields(); + let field = &fields[field_idx]; + Ok(parent_expr.member(field.name.to_string(), &self.symbol_table)) } - TypeOrVariant::GeneratorVariant(_var_idx) => { - let field_name = self.generator_field_name(f.index()); - Ok(res.member(field_name, &self.symbol_table)) + TypeOrVariant::CoroutineVariant(_var_idx) => { + let field_name = self.coroutine_field_name(field_idx); + Ok(parent_expr.member(field_name, &self.symbol_table)) } } } + /// This is a SIMD vector, which has 2 possible internal representations: + /// 1- Multi-field representation (original and currently deprecated) + /// In this case, a field is one lane (i.e.: one element) + /// Example: + /// ```ignore + /// pub struct i64x2(i64, i64); + /// fn main() { + /// let v = i64x2(1, 2); + /// assert!(v.0 == 1); // refers to the first i64 + /// assert!(v.1 == 2); + /// } + /// ``` + /// 2- Array-based representation + /// In this case, the projection refers to the entire array. + /// ```ignore + /// pub struct i64x2([i64; 2]); + /// fn main() { + /// let v = i64x2([1, 2]); + /// assert!(v.0 == [1, 2]); // refers to the entire array + /// } + /// ``` + /// * Note that projection inside SIMD structs may eventually become illegal. + /// See thread. + /// + /// Since the goto representation for both is the same, we use the expected type to decide + /// what to return. + fn codegen_simd_field(&mut self, parent_expr: Expr, field_idx: FieldIdx, field_ty: Ty) -> Expr { + if matches!(field_ty.kind(), TyKind::RigidTy(RigidTy::Array { .. })) { + // Array based + assert_eq!(field_idx, 0); + let field_typ = self.codegen_ty_stable(field_ty); + parent_expr.reinterpret_cast(field_typ) + } else { + // Return the given field. + let index_expr = Expr::int_constant(field_idx, Type::size_t()); + parent_expr.index_array(index_expr) + } + } + /// If a local is a function definition, ignore the local variable name and /// generate a function call based on the def id. /// @@ -316,18 +365,22 @@ impl<'tcx> GotocCtx<'tcx> { /// a named variable. /// /// Recursively finds the actual FnDef from a pointer or box. - fn codegen_local_fndef(&mut self, ty: ty::Ty<'tcx>) -> Option { + fn codegen_local_fndef(&mut self, ty: Ty) -> Option { match ty.kind() { // A local that is itself a FnDef, like Fn::call_once - ty::FnDef(defid, substs) => Some(self.codegen_fndef(*defid, substs, None)), + TyKind::RigidTy(RigidTy::FnDef(def, args)) => { + Some(self.codegen_fndef(def, &args, None)) + } // A local can be pointer to a FnDef, like Fn::call and Fn::call_mut - ty::RawPtr(inner) => self - .codegen_local_fndef(inner.ty) + TyKind::RigidTy(RigidTy::RawPtr(inner, _)) => self + .codegen_local_fndef(inner) .map(|f| if f.can_take_address_of() { f.address_of() } else { f }), // A local can be a boxed function pointer - ty::Adt(def, _) if def.is_box() => { - let boxed_ty = self.codegen_ty(ty); - self.codegen_local_fndef(ty.boxed_ty()) + TyKind::RigidTy(RigidTy::Adt(def, args)) if def.is_box() => { + let boxed_ty = self.codegen_ty_stable(ty); + // The type of `T` for `Box` can be derived from the first definition args. + let inner_ty = args.0[0].ty().unwrap(); + self.codegen_local_fndef(*inner_ty) .map(|f| self.box_value(f.address_of(), boxed_ty)) } _ => None, @@ -336,14 +389,15 @@ impl<'tcx> GotocCtx<'tcx> { /// Codegen for a local fn codegen_local(&mut self, l: Local) -> Expr { + let local_ty = self.local_ty_stable(l); // Check if the local is a function definition (see comment above) - if let Some(fn_def) = self.codegen_local_fndef(self.local_ty(l)) { + if let Some(fn_def) = self.codegen_local_fndef(local_ty) { return fn_def; } // Otherwise, simply look up the local by the var name. - let vname = self.codegen_var_name(&l); - Expr::symbol_expression(vname, self.codegen_ty(self.local_ty(l))) + let vname = self.codegen_var_name(&LocalInternal::from(l)); + Expr::symbol_expression(vname, self.codegen_ty_stable(local_ty)) } /// A projection is an operation that translates an lvalue to another lvalue. @@ -353,22 +407,26 @@ impl<'tcx> GotocCtx<'tcx> { /// the return value is the expression after. fn codegen_projection( &mut self, - before: Result, UnimplementedData>, - proj: ProjectionElem>, - ) -> Result, UnimplementedData> { + before: Result, + proj: &ProjectionElem, + ) -> Result { let before = before?; + trace!(?before, ?proj, "codegen_projection"); match proj { ProjectionElem::Deref => { - trace!(?before, ?proj, "codegen_projection"); let base_type = before.mir_typ(); - let inner_goto_expr = if base_type.is_box() { + let inner_goto_expr = if is_box(base_type) { self.deref_box(before.goto_expr) } else { before.goto_expr }; - let inner_mir_typ = std_pointee_type(base_type).unwrap(); - let (fat_ptr_mir_typ, fat_ptr_goto_expr) = if self.use_thin_pointer(inner_mir_typ) { + let inner_mir_typ_internal = + std_pointee_type(rustc_internal::internal(base_type)).unwrap(); + let inner_mir_typ = rustc_internal::stable(inner_mir_typ_internal); + let (fat_ptr_mir_typ, fat_ptr_goto_expr) = if self + .use_thin_pointer(inner_mir_typ_internal) + { (before.fat_ptr_mir_typ, before.fat_ptr_goto_expr) } else { (Some(before.mir_typ_or_variant.expect_type()), Some(inner_goto_expr.clone())) @@ -384,7 +442,9 @@ impl<'tcx> GotocCtx<'tcx> { pointee_type(fat_ptr_mir_typ.unwrap()).unwrap().kind(), ); assert!( - self.use_fat_pointer(pointee_type(fat_ptr_mir_typ.unwrap()).unwrap()), + self.use_fat_pointer(rustc_internal::internal( + pointee_type(fat_ptr_mir_typ.unwrap()).unwrap() + )), "Unexpected type: {:?} -- {:?}", fat_ptr.typ(), fat_ptr_mir_typ, @@ -392,10 +452,14 @@ impl<'tcx> GotocCtx<'tcx> { }; let expr = match inner_mir_typ.kind() { - ty::Slice(_) | ty::Str | ty::Dynamic(..) => { + TyKind::RigidTy(RigidTy::Slice(_)) + | TyKind::RigidTy(RigidTy::Str) + | TyKind::RigidTy(RigidTy::Dynamic(..)) => { inner_goto_expr.member("data", &self.symbol_table) } - ty::Adt(..) if self.is_unsized(inner_mir_typ) => { + TyKind::RigidTy(RigidTy::Adt(..)) + if self.is_unsized(inner_mir_typ_internal) => + { // in tests/kani/Strings/os_str_reduced.rs, we see // ``` // p.projection = [ @@ -414,7 +478,7 @@ impl<'tcx> GotocCtx<'tcx> { .member("data", &self.symbol_table) // In the case of a vtable fat pointer, this data member is a void pointer, // so ensure the pointer has the correct type before dereferencing it. - .cast_to(self.codegen_ty(inner_mir_typ).to_pointer()) + .cast_to(self.codegen_ty_stable(inner_mir_typ).to_pointer()) .dereference() } _ => inner_goto_expr.dereference(), @@ -422,9 +486,10 @@ impl<'tcx> GotocCtx<'tcx> { let typ = TypeOrVariant::Type(inner_mir_typ); ProjectedPlace::try_new(expr, typ, fat_ptr_goto_expr, fat_ptr_mir_typ, self) } - ProjectionElem::Field(f, t) => { - let typ = TypeOrVariant::Type(t); - let expr = self.codegen_field(before.goto_expr, before.mir_typ_or_variant, &f)?; + ProjectionElem::Field(idx, ty) => { + let typ = TypeOrVariant::Type(*ty); + let expr = + self.codegen_field(before.goto_expr, before.mir_typ_or_variant, *idx, typ)?; ProjectedPlace::try_new( expr, typ, @@ -435,14 +500,17 @@ impl<'tcx> GotocCtx<'tcx> { } ProjectionElem::Index(i) => { let base_type = before.mir_typ(); - let idxe = self.codegen_local(i); + let idxe = self.codegen_local(*i); let typ = match base_type.kind() { - ty::Array(elemt, _) | ty::Slice(elemt) => TypeOrVariant::Type(*elemt), + TyKind::RigidTy(RigidTy::Array(elemt, _)) + | TyKind::RigidTy(RigidTy::Slice(elemt)) => TypeOrVariant::Type(elemt), _ => unreachable!("must index an array"), }; let expr = match base_type.kind() { - ty::Array(..) => self.codegen_idx_array(before.goto_expr, idxe), - ty::Slice(..) => before.goto_expr.index(idxe), + TyKind::RigidTy(RigidTy::Array(..)) => { + self.codegen_idx_array(before.goto_expr, idxe) + } + TyKind::RigidTy(RigidTy::Slice(..)) => before.goto_expr.index(idxe), _ => unreachable!("must index an array"), }; ProjectedPlace::try_new( @@ -454,7 +522,7 @@ impl<'tcx> GotocCtx<'tcx> { ) } ProjectionElem::ConstantIndex { offset, min_length, from_end } => { - self.codegen_constant_index(before, offset, min_length, from_end) + self.codegen_constant_index(before, *offset, *min_length, *from_end) } // Best effort to codegen subslice projection. // Full support to be added in @@ -462,16 +530,16 @@ impl<'tcx> GotocCtx<'tcx> { ProjectionElem::Subslice { from, to, from_end } => { // https://rust-lang.github.io/rfcs/2359-subslice-pattern-syntax.html match before.mir_typ().kind() { - ty::Array(ty, len) => { - let len = len.kind().try_to_target_usize(self.tcx).unwrap(); - let subarray_len = if from_end { + TyKind::RigidTy(RigidTy::Array(ty, len)) => { + let len = len.eval_target_usize().unwrap(); + let subarray_len = if *from_end { // `to` counts from the end of the array len - to - from } else { to - from }; - let typ = self.tcx.mk_array(*ty, subarray_len); - let goto_typ = self.codegen_ty(typ); + let typ = Ty::try_new_array(ty, subarray_len).unwrap(); + let goto_typ = self.codegen_ty_stable(typ); // unimplemented Err(UnimplementedData::new( "Sub-array binding", @@ -480,8 +548,8 @@ impl<'tcx> GotocCtx<'tcx> { *before.goto_expr.location(), )) } - ty::Slice(elemt) => { - let len = if from_end { + TyKind::RigidTy(RigidTy::Slice(_)) => { + let len = if *from_end { let olen = before .fat_ptr_goto_expr .clone() @@ -492,12 +560,11 @@ impl<'tcx> GotocCtx<'tcx> { } else { Expr::int_constant(to - from, Type::size_t()) }; - let typ = self.tcx.mk_slice(*elemt); - let typ_and_mut = TypeAndMut { ty: typ, mutbl: Mutability::Mut }; - let ptr_typ = self.tcx.mk_ptr(typ_and_mut); - let goto_type = self.codegen_ty(ptr_typ); + let typ = before.mir_typ(); + let ptr_typ = Ty::new_ptr(typ, Mutability::Not); + let goto_type = self.codegen_ty_stable(ptr_typ); - let index = Expr::int_constant(from, Type::ssize_t()); + let index = Expr::int_constant(*from, Type::ssize_t()); let from_elem = before.goto_expr.index(index); let data = from_elem.address_of(); let fat_ptr = slice_fat_ptr(goto_type, data, len, &self.symbol_table); @@ -512,28 +579,33 @@ impl<'tcx> GotocCtx<'tcx> { _ => unreachable!("must be array or slice"), } } - ProjectionElem::Downcast(_, idx) => { + ProjectionElem::Downcast(idx) => { // downcast converts a variable of an enum type to one of its discriminated cases - let t = before.mir_typ(); - let (case_name, type_or_variant) = match t.kind() { - ty::Adt(def, _) => { - let variant = def.variant(idx); - (variant.name.as_str().into(), TypeOrVariant::Variant(variant)) + let ty = before.mir_typ(); + let ty_kind = ty.kind(); + let (case_name, type_or_variant) = match &ty_kind { + TyKind::RigidTy(RigidTy::Adt(def, _)) => { + let variant = def.variant(*idx).unwrap(); + (variant.name().into(), TypeOrVariant::Variant(variant)) } - ty::Generator(..) => { - (self.generator_variant_name(idx), TypeOrVariant::GeneratorVariant(idx)) + TyKind::RigidTy(RigidTy::Coroutine(..)) => { + let idx_internal = rustc_internal::internal(idx); + ( + self.coroutine_variant_name(idx_internal), + TypeOrVariant::CoroutineVariant(*idx), + ) } _ => unreachable!( - "cannot downcast {:?} to a variant (only enums and generators can)", - &t.kind() + "cannot downcast {:?} to a variant (only enums and coroutines can)", + &ty.kind() ), }; - let layout = self.layout_of(t); + let layout = self.layout_of(rustc_internal::internal(ty)); let expr = match &layout.variants { Variants::Single { .. } => before.goto_expr, Variants::Multiple { tag_encoding, .. } => match tag_encoding { TagEncoding::Direct => { - let cases = if t.is_generator() { + let cases = if is_coroutine(ty_kind) { before.goto_expr } else { before.goto_expr.member("cases", &self.symbol_table) @@ -553,13 +625,15 @@ impl<'tcx> GotocCtx<'tcx> { self, ) } - ProjectionElem::OpaqueCast(ty) => ProjectedPlace::try_new( - before.goto_expr.cast_to(self.codegen_ty(ty)), - TypeOrVariant::Type(ty), - before.fat_ptr_goto_expr, - before.fat_ptr_mir_typ, - self, - ), + ProjectionElem::OpaqueCast(ty) | ProjectionElem::Subtype(ty) => { + ProjectedPlace::try_new( + before.goto_expr.cast_to(self.codegen_ty_stable(*ty)), + TypeOrVariant::Type(*ty), + before.fat_ptr_goto_expr, + before.fat_ptr_mir_typ, + self, + ) + } } } @@ -569,13 +643,13 @@ impl<'tcx> GotocCtx<'tcx> { /// build the fat pointer from there. /// - For `*(Wrapper)` where `T: Unsized`, the projection's `goto_expr` returns an object, /// and we need to take it's address and build the fat pointer. - pub fn codegen_place_ref(&mut self, place: &Place<'tcx>) -> Expr { + pub fn codegen_place_ref(&mut self, place: &PlaceInternal<'tcx>) -> Expr { let place_ty = self.place_ty(place); let projection = unwrap_or_return_codegen_unimplemented!(self, self.codegen_place(place)); if self.use_thin_pointer(place_ty) { // Just return the address of the place dereferenced. projection.goto_expr.address_of() - } else if place_ty == pointee_type(self.local_ty(place.local)).unwrap() { + } else if place_ty == pointee_type_internal(self.local_ty(place.local)).unwrap() { // Just return the fat pointer if this is a simple &(*local). projection.fat_ptr_goto_expr.unwrap() } else { @@ -600,17 +674,17 @@ impl<'tcx> GotocCtx<'tcx> { /// This function follows the MIR projection to get the final useable lvalue. /// If it passes through a fat pointer along the way, it stores info about it, /// which can be useful in reconstructing fat pointer operations. - pub fn codegen_place( + pub fn codegen_place_stable( &mut self, - p: &Place<'tcx>, - ) -> Result, UnimplementedData> { - debug!(place=?p, "codegen_place"); - let initial_expr = self.codegen_local(p.local); - let initial_typ = TypeOrVariant::Type(self.local_ty(p.local)); + place: &Place, + ) -> Result { + debug!(?place, "codegen_place"); + let initial_expr = self.codegen_local(place.local); + let initial_typ = TypeOrVariant::Type(self.local_ty_stable(place.local)); debug!(?initial_typ, ?initial_expr, "codegen_place"); let initial_projection = ProjectedPlace::try_new(initial_expr, initial_typ, None, None, self); - let result = p + let result = place .projection .iter() .fold(initial_projection, |accum, proj| self.codegen_projection(accum, proj)); @@ -618,22 +692,29 @@ impl<'tcx> GotocCtx<'tcx> { Err(data) => Err(UnimplementedData::new( &data.operation, &data.bug_url, - self.codegen_ty(self.place_ty(p)), + self.codegen_ty_stable(self.place_ty_stable(place)), data.loc, )), _ => result, } } + pub fn codegen_place( + &mut self, + place: &PlaceInternal<'tcx>, + ) -> Result { + self.codegen_place_stable(&StableConverter::convert_place(self, *place)) + } + /// Given a projection, generate an lvalue that represents the given variant index. pub fn codegen_variant_lvalue( &mut self, - initial_projection: ProjectedPlace<'tcx>, + initial_projection: ProjectedPlace, variant_idx: VariantIdx, - ) -> ProjectedPlace<'tcx> { + ) -> ProjectedPlace { debug!(?initial_projection, ?variant_idx, "codegen_variant_lvalue"); - let downcast = ProjectionElem::Downcast(None, variant_idx); - self.codegen_projection(Ok(initial_projection), downcast).unwrap() + let downcast = ProjectionElem::Downcast(variant_idx); + self.codegen_projection(Ok(initial_projection), &downcast).unwrap() } // https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/enum.ProjectionElem.html @@ -646,20 +727,20 @@ impl<'tcx> GotocCtx<'tcx> { // [_, _, .._, _, X] => { offset: 1, min_length: 4, from_end: true }, fn codegen_constant_index( &mut self, - before: ProjectedPlace<'tcx>, + before: ProjectedPlace, offset: u64, min_length: u64, from_end: bool, - ) -> Result, UnimplementedData> { + ) -> Result { match before.mir_typ().kind() { //TODO, ask on zulip if we can ever have from_end here? - ty::Array(elemt, length) => { - let length = length.kind().try_to_target_usize(self.tcx).unwrap(); + TyKind::RigidTy(RigidTy::Array(elemt, length)) => { + let length = length.eval_target_usize().unwrap(); assert!(length >= min_length); let idx = if from_end { length - offset } else { offset }; let idxe = Expr::int_constant(idx, Type::ssize_t()); let expr = self.codegen_idx_array(before.goto_expr, idxe); - let typ = TypeOrVariant::Type(*elemt); + let typ = TypeOrVariant::Type(elemt); ProjectedPlace::try_new( expr, typ, @@ -668,7 +749,7 @@ impl<'tcx> GotocCtx<'tcx> { self, ) } - ty::Slice(elemt) => { + TyKind::RigidTy(RigidTy::Slice(elemt)) => { let offset_e = Expr::int_constant(offset, Type::size_t()); //TODO, should we assert min_length? Or is that already handled by the typechecker? let idxe = if from_end { @@ -679,7 +760,7 @@ impl<'tcx> GotocCtx<'tcx> { offset_e }; let expr = before.goto_expr.plus(idxe).dereference(); - let typ = TypeOrVariant::Type(*elemt); + let typ = TypeOrVariant::Type(elemt); ProjectedPlace::try_new( expr, typ, @@ -700,6 +781,14 @@ impl<'tcx> GotocCtx<'tcx> { } } +fn is_box(ty: Ty) -> bool { + matches!(ty.kind(), TyKind::RigidTy(RigidTy::Adt(def, _)) if def.is_box()) +} + +fn is_coroutine(ty_kind: TyKind) -> bool { + matches!(ty_kind, TyKind::RigidTy(RigidTy::Coroutine(..))) +} + /// Extract the data pointer from a projection. /// The return type of the projection is not consistent today, so we need to specialize the /// behavior in order to get a consistent expression that represents a pointer to the projected diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs index 22b26a3489b4..697a7ba83d2d 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use super::typ::pointee_type; -use crate::codegen_cprover_gotoc::codegen::place::{ProjectedPlace, TypeOrVariant}; +use crate::codegen_cprover_gotoc::codegen::place::ProjectedPlace; use crate::codegen_cprover_gotoc::codegen::PropertyClass; use crate::codegen_cprover_gotoc::utils::{dynamic_fat_ptr, slice_fat_ptr}; use crate::codegen_cprover_gotoc::{GotocCtx, VtableCtx}; @@ -11,19 +11,19 @@ use crate::kani_middle::coercion::{ }; use crate::unwrap_or_return_codegen_unimplemented; use cbmc::goto_program::{ - arithmetic_overflow_result_type, BinaryOperator, Expr, Location, Stmt, Symbol, Type, + arithmetic_overflow_result_type, BinaryOperator, Expr, Location, Stmt, Type, ARITH_OVERFLOW_OVERFLOWED_FIELD, ARITH_OVERFLOW_RESULT_FIELD, }; use cbmc::MachineModel; use cbmc::{btree_string_map, InternString, InternedString}; use num::bigint::BigInt; -use rustc_abi::FieldIdx; use rustc_index::IndexVec; use rustc_middle::mir::{AggregateKind, BinOp, CastKind, NullOp, Operand, Place, Rvalue, UnOp}; -use rustc_middle::ty::adjustment::PointerCast; +use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::{self, Instance, IntTy, Ty, TyCtxt, UintTy, VtblEntry}; -use rustc_target::abi::{FieldsShape, Size, TagEncoding, VariantIdx, Variants}; +use rustc_smir::rustc_internal; +use rustc_target::abi::{FieldIdx, FieldsShape, Size, TagEncoding, VariantIdx, Variants}; use std::collections::BTreeMap; use tracing::{debug, trace, warn}; @@ -116,6 +116,14 @@ impl<'tcx> GotocCtx<'tcx> { BinOp::BitXor => ce1.bitxor(ce2), BinOp::Div => ce1.div(ce2), BinOp::Rem => ce1.rem(ce2), + BinOp::ShlUnchecked => ce1.shl(ce2), + BinOp::ShrUnchecked => { + if self.operand_ty(e1).is_signed() { + ce1.ashr(ce2) + } else { + ce1.lshr(ce2) + } + } _ => unreachable!("Unexpected {:?}", op), } } @@ -154,7 +162,7 @@ impl<'tcx> GotocCtx<'tcx> { fn codegen_rvalue_len(&mut self, p: &Place<'tcx>) -> Expr { let pt = self.place_ty(p); match pt.kind() { - ty::Array(_, sz) => self.codegen_const(*sz, None), + ty::Array(_, sz) => self.codegen_const_internal(*sz, None), ty::Slice(_) => unwrap_or_return_codegen_unimplemented!(self, self.codegen_place(p)) .fat_ptr_goto_expr .unwrap() @@ -343,11 +351,14 @@ impl<'tcx> GotocCtx<'tcx> { BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Shl | BinOp::Shr => { self.codegen_scalar_binop(op, e1, e2) } - // We currently rely on CBMC's UB checks for shift which isn't always accurate. - // We should implement the checks ourselves. - // See https://github.com/model-checking/kani/issues/2374 - BinOp::ShlUnchecked => self.codegen_scalar_binop(&BinOp::Shl, e1, e2), - BinOp::ShrUnchecked => self.codegen_scalar_binop(&BinOp::Shr, e1, e2), + BinOp::ShlUnchecked | BinOp::ShrUnchecked => { + let result = self.codegen_unchecked_scalar_binop(op, e1, e2); + let check = self.check_unchecked_shift_distance(e1, e2, loc); + Expr::statement_expression( + vec![check, result.clone().as_stmt(loc)], + result.typ().clone(), + ) + } BinOp::AddUnchecked | BinOp::MulUnchecked | BinOp::SubUnchecked => { self.codegen_binop_with_overflow_check(op, e1, e2, loc) } @@ -455,8 +466,41 @@ impl<'tcx> GotocCtx<'tcx> { } } - /// Create an initializer for a generator struct. - fn codegen_rvalue_generator( + /// Check for valid unchecked shift distance. + /// Shifts on an integer of type T are UB if shift distance < 0 or >= T::BITS. + fn check_unchecked_shift_distance( + &mut self, + value: &Operand<'tcx>, + distance: &Operand<'tcx>, + loc: Location, + ) -> Stmt { + let value_expr = self.codegen_operand(value); + let distance_expr = self.codegen_operand(distance); + let value_width = value_expr.typ().sizeof_in_bits(&self.symbol_table); + let value_width_expr = Expr::int_constant(value_width, distance_expr.typ().clone()); + + let excessive_distance_check = self.codegen_assert_assume( + distance_expr.clone().lt(value_width_expr), + PropertyClass::ArithmeticOverflow, + "attempt to shift by excessive shift distance", + loc, + ); + + if distance_expr.typ().is_signed(self.symbol_table.machine_model()) { + let negative_distance_check = self.codegen_assert_assume( + distance_expr.is_non_negative(), + PropertyClass::ArithmeticOverflow, + "attempt to shift by negative distance", + loc, + ); + Stmt::block(vec![negative_distance_check, excessive_distance_check], loc) + } else { + excessive_distance_check + } + } + + /// Create an initializer for a coroutine struct. + fn codegen_rvalue_coroutine( &mut self, operands: &IndexVec>, ty: Ty<'tcx>, @@ -465,7 +509,7 @@ impl<'tcx> GotocCtx<'tcx> { let discriminant_field = match &layout.variants { Variants::Multiple { tag_encoding: TagEncoding::Direct, tag_field, .. } => tag_field, _ => unreachable!( - "Expected generators to have multiple variants and direct encoding, but found: {layout:?}" + "Expected coroutines to have multiple variants and direct encoding, but found: {layout:?}" ), }; let overall_t = self.codegen_ty(ty); @@ -509,15 +553,10 @@ impl<'tcx> GotocCtx<'tcx> { stmts.push(decl); if !operands.is_empty() { // 2- Initialize the members of the temporary variant. - let initial_projection = ProjectedPlace::try_new( - temp_var.clone(), - TypeOrVariant::Type(res_ty), - None, - None, - self, - ) - .unwrap(); - let variant_proj = self.codegen_variant_lvalue(initial_projection, variant_index); + let initial_projection = + ProjectedPlace::try_new_internal(temp_var.clone(), res_ty, self).unwrap(); + let variant_proj = self + .codegen_variant_lvalue(initial_projection, rustc_internal::stable(variant_index)); let variant_expr = variant_proj.goto_expr.clone(); let layout = self.layout_of(res_ty); let fields = match &layout.variants { @@ -582,24 +621,28 @@ impl<'tcx> GotocCtx<'tcx> { AggregateKind::Adt(_, _, _, _, _) if res_ty.is_simd() => { let typ = self.codegen_ty(res_ty); let layout = self.layout_of(res_ty); - let vector_element_type = typ.base_type().unwrap().clone(); - Expr::vector_expr( - typ, - layout - .fields - .index_by_increasing_offset() - .map(|idx| { - let cgo = self.codegen_operand(&operands[idx.into()]); - // The input operand might actually be a one-element array, as seen - // when running assess on firecracker. - if *cgo.typ() == vector_element_type { - cgo - } else { - cgo.transmute_to(vector_element_type.clone(), &self.symbol_table) - } - }) - .collect(), - ) + trace!(shape=?layout.fields, "codegen_rvalue_aggregate"); + assert!(operands.len() > 0, "SIMD vector cannot be empty"); + if operands.len() == 1 { + let data = self.codegen_operand(&operands[0u32.into()]); + if data.typ().is_array() { + // Array-based SIMD representation. + data.transmute_to(typ, &self.symbol_table) + } else { + // Multi field-based representation with one field. + Expr::vector_expr(typ, vec![data]) + } + } else { + // Multi field SIMD representation. + Expr::vector_expr( + typ, + layout + .fields + .index_by_increasing_offset() + .map(|idx| self.codegen_operand(&operands[idx.into()])) + .collect(), + ) + } } AggregateKind::Adt(_, variant_index, ..) if res_ty.is_enum() => { self.codegen_rvalue_enum_aggregate(variant_index, operands, res_ty, loc) @@ -617,7 +660,7 @@ impl<'tcx> GotocCtx<'tcx> { &self.symbol_table, ) } - AggregateKind::Generator(_, _, _) => self.codegen_rvalue_generator(&operands, res_ty), + AggregateKind::Coroutine(_, _, _) => self.codegen_rvalue_coroutine(&operands, res_ty), } } @@ -658,7 +701,7 @@ impl<'tcx> GotocCtx<'tcx> { "https://github.com/model-checking/kani/issues/1784", ) } - Rvalue::Cast(CastKind::Pointer(k), e, t) => { + Rvalue::Cast(CastKind::PointerCoercion(k), e, t) => { let t = self.monomorphize(*t); self.codegen_pointer_cast(k, e, t, loc) } @@ -680,7 +723,7 @@ impl<'tcx> GotocCtx<'tcx> { .with_size_of_annotation(self.codegen_ty(t)), NullOp::AlignOf => Expr::int_constant(layout.align.abi.bytes(), Type::size_t()), NullOp::OffsetOf(fields) => Expr::int_constant( - layout.offset_of_subfield(self, fields.iter().map(|f| f.index())).bytes(), + layout.offset_of_subfield(self, fields.iter()).bytes(), Type::size_t(), ), } @@ -690,7 +733,7 @@ impl<'tcx> GotocCtx<'tcx> { // See https://github.com/rust-lang/compiler-team/issues/460 for more details. let operand = self.codegen_operand(operand); let t = self.monomorphize(*content_ty); - let box_ty = self.tcx.mk_box(t); + let box_ty = Ty::new_box(self.tcx, t); let box_ty = self.codegen_ty(box_ty); let cbmc_t = self.codegen_ty(t); let box_contents = operand.cast_to(cbmc_t.to_pointer()); @@ -718,7 +761,7 @@ impl<'tcx> GotocCtx<'tcx> { Rvalue::ThreadLocalRef(def_id) => { // Since Kani is single-threaded, we treat a thread local like a static variable: self.store_concurrent_construct("thread local (replaced by static variable)", loc); - self.codegen_static_pointer(*def_id, true) + self.codegen_thread_local_pointer(*def_id) } // A CopyForDeref is equivalent to a read from a place at the codegen level. // https://github.com/rust-lang/rust/blob/1673f1450eeaf4a5452e086db0fe2ae274a0144f/compiler/rustc_middle/src/mir/syntax.rs#L1055 @@ -737,8 +780,8 @@ impl<'tcx> GotocCtx<'tcx> { ), "discriminant field (`case`) only exists for multiple variants and direct encoding" ); - let expr = if ty.is_generator() { - // Generators are translated somewhat differently from enums (see [`GotoCtx::codegen_ty_generator`]). + let expr = if ty.is_coroutine() { + // Coroutines are translated somewhat differently from enums (see [`GotoCtx::codegen_ty_coroutine`]). // As a consequence, the discriminant is accessed as `.direct_fields.case` instead of just `.case`. place.member("direct_fields", &self.symbol_table) } else { @@ -943,48 +986,44 @@ impl<'tcx> GotocCtx<'tcx> { } /// "Pointer casts" are particular kinds of pointer-to-pointer casts. - /// See the [`PointerCast`] type for specifics. + /// See the [`PointerCoercion`] type for specifics. /// Note that this does not include all casts involving pointers, /// many of which are instead handled by [`Self::codegen_misc_cast`] instead. fn codegen_pointer_cast( &mut self, - k: &PointerCast, + k: &PointerCoercion, operand: &Operand<'tcx>, t: Ty<'tcx>, loc: Location, ) -> Expr { debug!(cast=?k, op=?operand, ?loc, "codegen_pointer_cast"); match k { - PointerCast::ReifyFnPointer => match self.operand_ty(operand).kind() { - ty::FnDef(def_id, substs) => { + PointerCoercion::ReifyFnPointer => match self.operand_ty(operand).kind() { + ty::FnDef(def_id, args) => { let instance = - Instance::resolve(self.tcx, ty::ParamEnv::reveal_all(), *def_id, substs) + Instance::resolve(self.tcx, ty::ParamEnv::reveal_all(), *def_id, args) .unwrap() .unwrap(); // We need to handle this case in a special way because `codegen_operand` compiles FnDefs to dummy structs. // (cf. the function documentation) - self.codegen_func_expr(instance, None).address_of() + self.codegen_func_expr_internal(instance, None).address_of() } _ => unreachable!(), }, - PointerCast::UnsafeFnPointer => self.codegen_operand(operand), - PointerCast::ClosureFnPointer(_) => { - if let ty::Closure(def_id, substs) = self.operand_ty(operand).kind() { - let instance = Instance::resolve_closure( - self.tcx, - *def_id, - substs, - ty::ClosureKind::FnOnce, - ) - .expect("failed to normalize and resolve closure during codegen") - .polymorphize(self.tcx); - self.codegen_func_expr(instance, None).address_of() + PointerCoercion::UnsafeFnPointer => self.codegen_operand(operand), + PointerCoercion::ClosureFnPointer(_) => { + if let ty::Closure(def_id, args) = self.operand_ty(operand).kind() { + let instance = + Instance::resolve_closure(self.tcx, *def_id, args, ty::ClosureKind::FnOnce) + .expect("failed to normalize and resolve closure during codegen") + .polymorphize(self.tcx); + self.codegen_func_expr_internal(instance, None).address_of() } else { unreachable!("{:?} cannot be cast to a fn ptr", operand) } } - PointerCast::MutToConstPointer => self.codegen_operand(operand), - PointerCast::ArrayToPointer => { + PointerCoercion::MutToConstPointer => self.codegen_operand(operand), + PointerCoercion::ArrayToPointer => { // TODO: I am not sure whether it is correct or not. // // some reasoning is as follows. @@ -1006,7 +1045,7 @@ impl<'tcx> GotocCtx<'tcx> { _ => unreachable!(), } } - PointerCast::Unsize => { + PointerCoercion::Unsize => { let src_goto_expr = self.codegen_operand(operand); let src_mir_type = self.operand_ty(operand); let dst_mir_type = t; @@ -1126,7 +1165,7 @@ impl<'tcx> GotocCtx<'tcx> { idx: usize, ) -> Expr { debug!(?instance, typ=?t, %idx, "codegen_vtable_method_field"); - let vtable_field_name = self.vtable_field_name(instance.def_id(), idx); + let vtable_field_name = self.vtable_field_name(idx); let vtable_type = Type::struct_tag(self.vtable_name(t)); let field_type = vtable_type.lookup_field_type(vtable_field_name, &self.symbol_table).unwrap(); @@ -1189,34 +1228,10 @@ impl<'tcx> GotocCtx<'tcx> { .address_of() .cast_to(trait_fn_ty) } else { - // We skip an entire submodule of the standard library, so drop is missing - // for it. Build and insert a function that just calls an unimplemented block - // to maintain soundness. - let drop_sym_name = format!("drop_unimplemented_{}", self.symbol_name(drop_instance)); - let pretty_name = - format!("drop_unimplemented<{}>", self.readable_instance_name(drop_instance)); - let drop_sym = self.ensure(&drop_sym_name, |ctx, name| { - // Function body - let unimplemented = ctx.codegen_unimplemented_stmt( - format!("drop_in_place for {drop_instance}").as_str(), - Location::none(), - "https://github.com/model-checking/kani/issues/281", - ); - - // Declare symbol for the single, self parameter - let param_typ = ctx.codegen_ty(trait_ty).to_pointer(); - let param_sym = ctx.gen_function_parameter(0, &drop_sym_name, param_typ); - - // Build and insert the function itself - Symbol::function( - name, - Type::code(vec![param_sym.to_function_parameter()], Type::empty()), - Some(Stmt::block(vec![unimplemented], Location::none())), - pretty_name, - Location::none(), - ) - }); - drop_sym.to_expr().address_of().cast_to(trait_fn_ty) + unreachable!( + "Missing drop implementation for {}", + self.readable_instance_name(drop_instance) + ); } } @@ -1369,7 +1384,7 @@ impl<'tcx> GotocCtx<'tcx> { (ty::Array(src_elt_type, src_elt_count), ty::Slice(dst_elt_type)) => { // Cast to a slice fat pointer. assert_eq!(src_elt_type, dst_elt_type); - let dst_goto_len = self.codegen_const(*src_elt_count, None); + let dst_goto_len = self.codegen_const_internal(*src_elt_count, None); let src_pointee_ty = pointee_type(coerce_info.src_ty).unwrap(); let dst_data_expr = if src_pointee_ty.is_array() { src_goto_expr.cast_to(self.codegen_ty(*src_elt_type).to_pointer()) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/span.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/span.rs index 72c9e258c14d..ac312f1580a9 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/span.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/span.rs @@ -3,16 +3,23 @@ //! MIR Span related functions -use crate::{codegen_cprover_gotoc::GotocCtx, kani_middle::SourceLocation}; +use crate::codegen_cprover_gotoc::GotocCtx; use cbmc::goto_program::Location; -use rustc_middle::mir::{Local, VarDebugInfo, VarDebugInfoContents}; +use rustc_middle::mir::{Local, VarDebugInfoContents}; +use rustc_smir::rustc_internal; use rustc_span::Span; +use stable_mir::mir::VarDebugInfo; +use stable_mir::ty::Span as SpanStable; impl<'tcx> GotocCtx<'tcx> { pub fn codegen_span(&self, sp: &Span) -> Location { - let loc = SourceLocation::new(self.tcx, sp); + self.codegen_span_stable(rustc_internal::stable(sp)) + } + + pub fn codegen_span_stable(&self, sp: SpanStable) -> Location { + let loc = sp.get_lines(); Location::new( - loc.filename, + sp.get_filename().to_string(), self.current_fn.as_ref().map(|x| x.readable_name().to_string()), loc.start_line, Some(loc.start_col), @@ -21,6 +28,14 @@ impl<'tcx> GotocCtx<'tcx> { ) } + pub fn codegen_span_option_stable(&self, sp: Option) -> Location { + sp.map_or(Location::none(), |span| self.codegen_span_stable(span)) + } + + pub fn codegen_caller_span_stable(&self, sp: SpanStable) -> Location { + self.codegen_caller_span(&Some(rustc_internal::internal(sp))) + } + /// Get the location of the caller. This will attempt to reach the macro caller. /// This function uses rustc_span methods designed to returns span for the macro which /// originally caused the expansion to happen. @@ -38,22 +53,12 @@ impl<'tcx> GotocCtx<'tcx> { sp.map_or(Location::none(), |x| self.codegen_span(&x)) } - pub fn find_debug_info(&self, l: &Local) -> Option<&VarDebugInfo<'tcx>> { - self.current_fn().mir().var_debug_info.iter().find(|info| match info.value { - VarDebugInfoContents::Place(p) => p.local == *l && p.projection.len() == 0, - VarDebugInfoContents::Const(_) => false, - // This variant was added in - // https://github.com/rust-lang/rust/pull/102570 and is concerned - // with a scalar replacement of aggregates (SROA) MIR optimization - // that is only enabled with `--mir-opt-level=3` or higher. - // TODO: create a test and figure out if we should return debug info - // for this case: - // https://github.com/model-checking/kani/issues/1933 - VarDebugInfoContents::Composite { .. } => { - // Fail in debug mode to determine if we ever hit this case - debug_assert!(false, "Unhandled VarDebugInfoContents::Composite"); - false - } - }) + pub fn find_debug_info(&self, l: &Local) -> Option { + rustc_internal::stable(self.current_fn().body_internal().var_debug_info.iter().find( + |info| match info.value { + VarDebugInfoContents::Place(p) => p.local == *l && p.projection.len() == 0, + VarDebugInfoContents::Const(_) => false, + }, + )) } } diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs index fbabe7e112cd..30a27f231d08 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs @@ -6,7 +6,6 @@ use super::PropertyClass; use crate::codegen_cprover_gotoc::{GotocCtx, VtableCtx}; use crate::unwrap_or_return_codegen_unimplemented_stmt; use cbmc::goto_program::{Expr, Location, Stmt, Type}; -use rustc_hir::def_id::DefId; use rustc_middle::mir; use rustc_middle::mir::{ AssertKind, BasicBlock, NonDivergingIntrinsic, Operand, Place, Statement, StatementKind, @@ -15,9 +14,11 @@ use rustc_middle::mir::{ use rustc_middle::ty; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::{Instance, InstanceDef, Ty}; +use rustc_smir::rustc_internal; use rustc_span::Span; use rustc_target::abi::VariantIdx; use rustc_target::abi::{FieldsShape, Primitive, TagEncoding, Variants}; +use stable_mir::mir::Place as PlaceStable; use tracing::{debug, debug_span, trace}; impl<'tcx> GotocCtx<'tcx> { @@ -118,13 +119,13 @@ impl<'tcx> GotocCtx<'tcx> { // because we don't want to raise the warning during compilation. // These operations will normally be codegen'd but normally be unreachable // since we make use of `-C unwind=abort`. - TerminatorKind::Resume => self.codegen_mimic_unimplemented( + TerminatorKind::UnwindResume => self.codegen_mimic_unimplemented( "TerminatorKind::Resume", loc, "https://github.com/model-checking/kani/issues/692", ), - TerminatorKind::Terminate => self.codegen_mimic_unimplemented( - "TerminatorKind::Terminate", + TerminatorKind::UnwindTerminate(_) => self.codegen_mimic_unimplemented( + "TerminatorKind::UnwindTerminate", loc, "https://github.com/model-checking/kani/issues/692", ), @@ -175,8 +176,10 @@ impl<'tcx> GotocCtx<'tcx> { msg.description() }; - let (msg_str, reach_stmt) = - self.codegen_reachability_check(msg.to_owned(), Some(term.source_info.span)); + let (msg_str, reach_stmt) = self.codegen_reachability_check( + msg.to_owned(), + rustc_internal::stable(term.source_info.span), + ); Stmt::block( vec![ @@ -195,7 +198,7 @@ impl<'tcx> GotocCtx<'tcx> { TerminatorKind::FalseEdge { .. } | TerminatorKind::FalseUnwind { .. } => { unreachable!("drop elaboration removes these TerminatorKind") } - TerminatorKind::Yield { .. } | TerminatorKind::GeneratorDrop => { + TerminatorKind::Yield { .. } | TerminatorKind::CoroutineDrop => { unreachable!("we should not hit these cases") // why? } TerminatorKind::InlineAsm { .. } => self.codegen_unimplemented_stmt( @@ -344,7 +347,7 @@ impl<'tcx> GotocCtx<'tcx> { // Non-virtual, direct drop_in_place call assert!(!matches!(drop_instance.def, InstanceDef::Virtual(_, _))); - let func = self.codegen_func_expr(drop_instance, None); + let func = self.codegen_func_expr_internal(drop_instance, None); // The only argument should be a self reference let args = vec![place_ref]; @@ -531,8 +534,16 @@ impl<'tcx> GotocCtx<'tcx> { self.codegen_untupled_args(instance, &mut fargs, args.last()); } - if let Some(hk) = self.hooks.hook_applies(self.tcx, instance) { - return hk.handle(self, instance, fargs, *destination, *target, Some(span)); + let stable_instance = rustc_internal::stable(instance); + if let Some(hk) = self.hooks.hook_applies(self.tcx, stable_instance) { + return hk.handle( + self, + stable_instance, + fargs, + rustc_internal::stable(destination), + target.map(BasicBlock::as_usize), + rustc_internal::stable(span), + ); } let mut stmts: Vec = match instance.def { @@ -541,16 +552,9 @@ impl<'tcx> GotocCtx<'tcx> { return Stmt::goto(self.current_fn().find_label(&target.unwrap()), loc); } // Handle a virtual function call via a vtable lookup - InstanceDef::Virtual(def_id, idx) => { + InstanceDef::Virtual(_, idx) => { let self_ty = self.operand_ty(&args[0]); - self.codegen_virtual_funcall( - self_ty, - def_id, - idx, - destination, - &mut fargs, - loc, - ) + self.codegen_virtual_funcall(self_ty, idx, destination, &mut fargs, loc) } // Normal, non-virtual function calls InstanceDef::Item(..) @@ -564,7 +568,7 @@ impl<'tcx> GotocCtx<'tcx> { | InstanceDef::CloneShim(..) => { // We need to handle FnDef items in a special way because `codegen_operand` compiles them to dummy structs. // (cf. the function documentation) - let func_exp = self.codegen_func_expr(instance, None); + let func_exp = self.codegen_func_expr_internal(instance, None); vec![ self.codegen_expr_to_place(destination, func_exp.call(fargs)) .with_location(loc), @@ -623,13 +627,12 @@ impl<'tcx> GotocCtx<'tcx> { fn codegen_virtual_funcall( &mut self, self_ty: Ty<'tcx>, - def_id: DefId, idx: usize, place: &Place<'tcx>, fargs: &mut [Expr], loc: Location, ) -> Vec { - let vtable_field_name = self.vtable_field_name(def_id, idx); + let vtable_field_name = self.vtable_field_name(idx); trace!(?self_ty, ?place, ?vtable_field_name, "codegen_virtual_funcall"); debug!(?fargs, "codegen_virtual_funcall"); @@ -695,6 +698,16 @@ impl<'tcx> GotocCtx<'tcx> { /// A MIR [Place] is an L-value (i.e. the LHS of an assignment). /// /// In Kani, we slightly optimize the special case for Unit and don't assign anything. + pub(crate) fn codegen_expr_to_place_stable(&mut self, p: &PlaceStable, e: Expr) -> Stmt { + if e.typ().is_unit() { + e.as_stmt(Location::none()) + } else { + unwrap_or_return_codegen_unimplemented_stmt!(self, self.codegen_place_stable(p)) + .goto_expr + .assign(e, Location::none()) + } + } + pub(crate) fn codegen_expr_to_place(&mut self, p: &Place<'tcx>, e: Expr) -> Stmt { if self.place_ty(p).is_unit() { e.as_stmt(Location::none()) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/static_var.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/static_var.rs index e71bea1f2066..cf4c6173637b 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/static_var.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/static_var.rs @@ -5,9 +5,8 @@ use crate::codegen_cprover_gotoc::GotocCtx; use cbmc::goto_program::Symbol; -use rustc_hir::def_id::DefId; -use rustc_middle::mir::mono::MonoItem; -use rustc_middle::ty::{subst::InternalSubsts, Instance}; +use stable_mir::mir::mono::{Instance, StaticDef}; +use stable_mir::CrateDef; use tracing::debug; impl<'tcx> GotocCtx<'tcx> { @@ -16,24 +15,24 @@ impl<'tcx> GotocCtx<'tcx> { /// Note that each static variable have their own location in memory. Per Rust documentation: /// "statics declare global variables. These represent a memory address." /// Source: - pub fn codegen_static(&mut self, def_id: DefId, item: MonoItem<'tcx>) { + pub fn codegen_static(&mut self, def: StaticDef) { debug!("codegen_static"); - let alloc = self.tcx.eval_static_initializer(def_id).unwrap(); - let symbol_name = item.symbol_name(self.tcx).to_string(); - self.codegen_alloc_in_memory(alloc.inner(), symbol_name); + let alloc = def.eval_initializer().unwrap(); + let symbol_name = Instance::from(def).mangled_name(); + self.codegen_alloc_in_memory(alloc, symbol_name); } /// Mutates the Goto-C symbol table to add a forward-declaration of the static variable. - pub fn declare_static(&mut self, def_id: DefId, item: MonoItem<'tcx>) { + pub fn declare_static(&mut self, def: StaticDef) { + let instance = Instance::from(def); // Unique mangled monomorphized name. - let symbol_name = item.symbol_name(self.tcx).to_string(); + let symbol_name = instance.mangled_name(); // Pretty name which may include function name. - let pretty_name = Instance::new(def_id, InternalSubsts::empty()).to_string(); - debug!(?symbol_name, ?pretty_name, "declare_static {}", item); + let pretty_name = instance.name(); + debug!(?def, ?symbol_name, ?pretty_name, "declare_static"); - let typ = self.codegen_ty(self.tcx.type_of(def_id).subst_identity()); - let span = self.tcx.def_span(def_id); - let location = self.codegen_span(&span); + let typ = self.codegen_ty_stable(instance.ty()); + let location = self.codegen_span_stable(def.span()); let symbol = Symbol::static_variable(symbol_name.clone(), symbol_name, typ, location) .with_is_hidden(false) // Static items are always user defined. .with_pretty_name(pretty_name); diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/ty_stable.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/ty_stable.rs new file mode 100644 index 000000000000..5c9c235d3164 --- /dev/null +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/ty_stable.rs @@ -0,0 +1,128 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Stable functions involving type manipulation. +//! +//! This may for now invoke functions that use internal Rust compiler APIs. +//! While we migrate to stable APIs, this module will contain stable versions of functions from +//! `typ.rs`. + +use crate::codegen_cprover_gotoc::GotocCtx; +use cbmc::goto_program::Type; +use rustc_middle::mir; +use rustc_middle::mir::visit::{MutVisitor, NonUseContext, PlaceContext}; +use rustc_middle::mir::{Operand as OperandInternal, Place as PlaceInternal}; +use rustc_middle::ty::{self, Const as ConstInternal, Ty as TyInternal, TyCtxt}; +use rustc_smir::rustc_internal; +use stable_mir::mir::mono::Instance; +use stable_mir::mir::{Local, Operand, Place}; +use stable_mir::ty::{Const, RigidTy, Ty, TyKind}; + +impl<'tcx> GotocCtx<'tcx> { + pub fn place_ty_stable(&self, place: &Place) -> Ty { + place.ty(self.current_fn().body().locals()).unwrap() + } + + pub fn codegen_ty_stable(&mut self, ty: Ty) -> Type { + self.codegen_ty(rustc_internal::internal(ty)) + } + + pub fn local_ty_stable(&mut self, local: Local) -> Ty { + self.current_fn().body().local_decl(local).unwrap().ty + } + + pub fn operand_ty_stable(&mut self, operand: &Operand) -> Ty { + operand.ty(self.current_fn().body().locals()).unwrap() + } + + pub fn is_zst_stable(&self, ty: Ty) -> bool { + self.is_zst(rustc_internal::internal(ty)) + } + + pub fn codegen_fndef_type_stable(&mut self, instance: Instance) -> Type { + let func = self.symbol_name_stable(instance); + self.ensure_struct( + format!("{func}::FnDefStruct"), + format!("{}::FnDefStruct", instance.name()), + |_, _| vec![], + ) + } + + pub fn fn_sig_of_instance_stable(&self, instance: Instance) -> ty::PolyFnSig<'tcx> { + self.fn_sig_of_instance(rustc_internal::internal(instance)) + } + + pub fn use_fat_pointer_stable(&self, pointer_ty: Ty) -> bool { + self.use_fat_pointer(rustc_internal::internal(pointer_ty)) + } +} +/// If given type is a Ref / Raw ref, return the pointee type. +pub fn pointee_type(mir_type: Ty) -> Option { + match mir_type.kind() { + TyKind::RigidTy(RigidTy::Ref(_, pointee_type, _)) => Some(pointee_type), + TyKind::RigidTy(RigidTy::RawPtr(ty, ..)) => Some(ty), + _ => None, + } +} + +/// Convert internal rustc's structs into StableMIR ones. +/// +/// The body of a StableMIR instance already comes monomorphized, which is different from rustc's +/// internal representation. To allow us to migrate parts of the code generation stage with +/// smaller PRs, we have to instantiate rustc's components when converting them to stable. +/// +/// Once we finish migrating the entire function code generation, we can remove this code. +pub struct StableConverter<'a, 'tcx> { + gcx: &'a GotocCtx<'tcx>, +} + +impl<'a, 'tcx> StableConverter<'a, 'tcx> { + pub fn convert_place(gcx: &'a GotocCtx<'tcx>, mut place: PlaceInternal<'tcx>) -> Place { + let mut converter = StableConverter { gcx }; + converter.visit_place( + &mut place, + PlaceContext::NonUse(NonUseContext::VarDebugInfo), + mir::Location::START, + ); + rustc_internal::stable(place) + } + + pub fn convert_operand(gcx: &'a GotocCtx<'tcx>, mut operand: OperandInternal<'tcx>) -> Operand { + let mut converter = StableConverter { gcx }; + converter.visit_operand(&mut operand, mir::Location::START); + rustc_internal::stable(operand) + } + + pub fn convert_constant(gcx: &'a GotocCtx<'tcx>, mut constant: ConstInternal<'tcx>) -> Const { + let mut converter = StableConverter { gcx }; + converter.visit_ty_const(&mut constant, mir::Location::START); + rustc_internal::stable(constant) + } +} + +impl<'a, 'tcx> MutVisitor<'tcx> for StableConverter<'a, 'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.gcx.tcx + } + + fn visit_ty(&mut self, ty: &mut TyInternal<'tcx>, _: mir::visit::TyContext) { + *ty = self.gcx.monomorphize(*ty); + } + + fn visit_ty_const(&mut self, ct: &mut ty::Const<'tcx>, _location: mir::Location) { + *ct = self.gcx.monomorphize(*ct); + } + + fn visit_constant(&mut self, constant: &mut mir::ConstOperand<'tcx>, location: mir::Location) { + let const_ = self.gcx.monomorphize(constant.const_); + let val = match const_.eval(self.gcx.tcx, ty::ParamEnv::reveal_all(), None) { + Ok(v) => v, + Err(mir::interpret::ErrorHandled::Reported(..)) => return, + Err(mir::interpret::ErrorHandled::TooGeneric(..)) => { + unreachable!("Failed to evaluate instance constant: {:?}", const_) + } + }; + let ty = constant.ty(); + constant.const_ = mir::Const::Val(val, ty); + self.super_constant(constant, location); + } +} diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs index 69da5f1c7ad1..c8c0765a1fd8 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs @@ -12,16 +12,17 @@ use rustc_middle::mir::{HasLocalDecls, Local, Operand, Place, Rvalue}; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::print::FmtPrinter; -use rustc_middle::ty::subst::InternalSubsts; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{ - self, AdtDef, Const, FloatTy, GeneratorSubsts, Instance, IntTy, PolyFnSig, Ty, TyCtxt, TyKind, + self, AdtDef, Const, CoroutineArgs, FloatTy, Instance, IntTy, PolyFnSig, Ty, TyCtxt, TyKind, UintTy, VariantDef, VtblEntry, }; use rustc_middle::ty::{List, TypeFoldable}; +use rustc_smir::rustc_internal; use rustc_span::def_id::DefId; use rustc_target::abi::{ - Abi::Vector, FieldsShape, Integer, LayoutS, Primitive, Size, TagEncoding, TyAndLayout, - VariantIdx, Variants, + Abi::Vector, FieldIdx, FieldsShape, Integer, LayoutS, Primitive, Size, TagEncoding, + TyAndLayout, VariantIdx, Variants, }; use rustc_target::spec::abi::Abi; use std::iter; @@ -157,6 +158,7 @@ impl<'tcx> GotocCtx<'tcx> { cbmc::goto_program::CIntType::Bool => "bool", cbmc::goto_program::CIntType::Char => "char", cbmc::goto_program::CIntType::Int => "int", + cbmc::goto_program::CIntType::LongInt => "long int", cbmc::goto_program::CIntType::SizeT => "size_t", cbmc::goto_program::CIntType::SSizeT => "ssize_t", }; @@ -262,7 +264,7 @@ impl<'tcx> GotocCtx<'tcx> { let fn_sig = sig.skip_binder(); if let Some((tupe, prev_args)) = fn_sig.inputs().split_last() { let args = match *tupe.kind() { - ty::Tuple(substs) => substs, + ty::Tuple(args) => args, _ => unreachable!("the final argument of a closure must be a tuple"), }; @@ -287,14 +289,10 @@ impl<'tcx> GotocCtx<'tcx> { sig } - fn closure_sig( - &self, - def_id: DefId, - substs: ty::subst::SubstsRef<'tcx>, - ) -> ty::PolyFnSig<'tcx> { - let sig = self.monomorphize(substs.as_closure().sig()); + fn closure_sig(&self, def_id: DefId, args: ty::GenericArgsRef<'tcx>) -> ty::PolyFnSig<'tcx> { + let sig = self.monomorphize(args.as_closure().sig()); - // In addition to `def_id` and `substs`, we need to provide the kind of region `env_region` + // In addition to `def_id` and `args`, we need to provide the kind of region `env_region` // in `closure_env_ty`, which we can build from the bound variables as follows let bound_vars = self.tcx.mk_bound_variable_kinds_from_iter( sig.bound_vars().iter().chain(iter::once(ty::BoundVariableKind::Region(ty::BrEnv))), @@ -303,8 +301,8 @@ impl<'tcx> GotocCtx<'tcx> { var: ty::BoundVar::from_usize(bound_vars.len() - 1), kind: ty::BoundRegionKind::BrEnv, }; - let env_region = ty::Region::new_late_bound(self.tcx, ty::INNERMOST, br); - let env_ty = self.tcx.closure_env_ty(def_id, substs, env_region).unwrap(); + let env_region = ty::Region::new_bound(self.tcx, ty::INNERMOST, br); + let env_ty = self.tcx.closure_env_ty(def_id, args, env_region).unwrap(); let sig = sig.skip_binder(); @@ -329,41 +327,41 @@ impl<'tcx> GotocCtx<'tcx> { // Adapted from `fn_sig_for_fn_abi` in // https://github.com/rust-lang/rust/blob/739d68a76e35b22341d9930bb6338bf202ba05ba/compiler/rustc_ty_utils/src/abi.rs#L88 // Code duplication tracked here: https://github.com/model-checking/kani/issues/1365 - fn generator_sig( + fn coroutine_sig( &self, did: &DefId, ty: Ty<'tcx>, - substs: ty::subst::SubstsRef<'tcx>, + args: ty::GenericArgsRef<'tcx>, ) -> ty::PolyFnSig<'tcx> { - let sig = substs.as_generator().poly_sig(); + let sig = args.as_coroutine().sig(); - let bound_vars = self.tcx.mk_bound_variable_kinds_from_iter( - sig.bound_vars().iter().chain(iter::once(ty::BoundVariableKind::Region(ty::BrEnv))), - ); + let bound_vars = self.tcx.mk_bound_variable_kinds_from_iter(iter::once( + ty::BoundVariableKind::Region(ty::BrEnv), + )); let br = ty::BoundRegion { var: ty::BoundVar::from_usize(bound_vars.len() - 1), kind: ty::BoundRegionKind::BrEnv, }; - let env_region = ty::ReLateBound(ty::INNERMOST, br); - let env_ty = self.tcx.mk_mut_ref(ty::Region::new_from_kind(self.tcx, env_region), ty); + let env_region = ty::ReBound(ty::INNERMOST, br); + let env_ty = Ty::new_mut_ref(self.tcx, ty::Region::new_from_kind(self.tcx, env_region), ty); let pin_did = self.tcx.require_lang_item(LangItem::Pin, None); let pin_adt_ref = self.tcx.adt_def(pin_did); - let pin_substs = self.tcx.mk_substs(&[env_ty.into()]); - let env_ty = self.tcx.mk_adt(pin_adt_ref, pin_substs); + let pin_args = self.tcx.mk_args(&[env_ty.into()]); + let env_ty = Ty::new_adt(self.tcx, pin_adt_ref, pin_args); - let sig = sig.skip_binder(); - // The `FnSig` and the `ret_ty` here is for a generators main - // `Generator::resume(...) -> GeneratorState` function in case we - // have an ordinary generator, or the `Future::poll(...) -> Poll` - // function in case this is a special generator backing an async construct. + // The `FnSig` and the `ret_ty` here is for a coroutines main + // `coroutine::resume(...) -> CoroutineState` function in case we + // have an ordinary coroutine, or the `Future::poll(...) -> Poll` + // function in case this is a special coroutine backing an async construct. let tcx = self.tcx; - let (resume_ty, ret_ty) = if tcx.generator_is_async(*did) { + let (resume_ty, ret_ty) = if tcx.coroutine_is_async(*did) { // The signature should be `Future::poll(_, &mut Context<'_>) -> Poll` let poll_did = tcx.require_lang_item(LangItem::Poll, None); let poll_adt_ref = tcx.adt_def(poll_did); - let poll_substs = tcx.mk_substs(&[sig.return_ty.into()]); - let ret_ty = tcx.mk_adt(poll_adt_ref, poll_substs); + let poll_args = tcx.mk_args(&[sig.return_ty.into()]); + // TODO figure out where this one went + let ret_ty = Ty::new_adt(tcx, poll_adt_ref, poll_args); // We have to replace the `ResumeTy` that is used for type and borrow checking // with `&mut Context<'_>` which is used in codegen. @@ -376,15 +374,15 @@ impl<'tcx> GotocCtx<'tcx> { panic!("expected `ResumeTy`, found `{:?}`", sig.resume_ty); }; } - let context_mut_ref = tcx.mk_task_context(); + let context_mut_ref = Ty::new_task_context(tcx); (context_mut_ref, ret_ty) } else { - // The signature should be `Generator::resume(_, Resume) -> GeneratorState` - let state_did = tcx.require_lang_item(LangItem::GeneratorState, None); + // The signature should be `Coroutine::resume(_, Resume) -> CoroutineState` + let state_did = tcx.require_lang_item(LangItem::CoroutineState, None); let state_adt_ref = tcx.adt_def(state_did); - let state_substs = tcx.mk_substs(&[sig.yield_ty.into(), sig.return_ty.into()]); - let ret_ty = tcx.mk_adt(state_adt_ref, state_substs); + let state_args = tcx.mk_args(&[sig.yield_ty.into(), sig.return_ty.into()]); + let ret_ty = Ty::new_adt(tcx, state_adt_ref, state_args); (sig.resume_ty, ret_ty) }; @@ -413,7 +411,7 @@ impl<'tcx> GotocCtx<'tcx> { } sig } - ty::Generator(did, substs, _) => self.generator_sig(did, fntyp, substs), + ty::Coroutine(did, args, _) => self.coroutine_sig(did, fntyp, args), _ => unreachable!("Can't get function signature of type: {:?}", fntyp), }) } @@ -426,7 +424,7 @@ impl<'tcx> GotocCtx<'tcx> { { // Instance is Some(..) only when current codegen unit is a function. if let Some(current_fn) = &self.current_fn { - current_fn.instance().subst_mir_and_normalize_erasing_regions( + current_fn.instance().instantiate_mir_and_normalize_erasing_regions( self.tcx, ty::ParamEnv::reveal_all(), ty::EarlyBinder::bind(value), @@ -439,19 +437,19 @@ impl<'tcx> GotocCtx<'tcx> { } pub fn local_ty(&self, l: Local) -> Ty<'tcx> { - self.monomorphize(self.current_fn().mir().local_decls()[l].ty) + self.monomorphize(self.current_fn().body_internal().local_decls()[l].ty) } pub fn rvalue_ty(&self, rv: &Rvalue<'tcx>) -> Ty<'tcx> { - self.monomorphize(rv.ty(self.current_fn().mir().local_decls(), self.tcx)) + self.monomorphize(rv.ty(self.current_fn().body_internal().local_decls(), self.tcx)) } pub fn operand_ty(&self, o: &Operand<'tcx>) -> Ty<'tcx> { - self.monomorphize(o.ty(self.current_fn().mir().local_decls(), self.tcx)) + self.monomorphize(o.ty(self.current_fn().body_internal().local_decls(), self.tcx)) } pub fn place_ty(&self, p: &Place<'tcx>) -> Ty<'tcx> { - self.monomorphize(p.ty(self.current_fn().mir().local_decls(), self.tcx).ty) + self.monomorphize(p.ty(self.current_fn().body_internal().local_decls(), self.tcx).ty) } /// Is the MIR type a zero-sized type. @@ -484,7 +482,7 @@ impl<'tcx> GotocCtx<'tcx> { let fn_ptr = fn_ty.to_pointer(); // vtable field name, i.e., 3_vol (idx_method) - let vtable_field_name = self.vtable_field_name(instance.def_id(), idx); + let vtable_field_name = self.vtable_field_name(idx); DatatypeComponent::field(vtable_field_name, fn_ptr) } @@ -634,16 +632,17 @@ impl<'tcx> GotocCtx<'tcx> { } pub fn ty_pretty_name(&self, t: Ty<'tcx>) -> InternedString { + use crate::rustc_middle::ty::print::Print; use rustc_hir::def::Namespace; - use rustc_middle::ty::print::Printer; - let printer = FmtPrinter::new(self.tcx, Namespace::TypeNS); + let mut printer = FmtPrinter::new(self.tcx, Namespace::TypeNS); // Monomorphizing the type ensures we get a cannonical form for dynamic trait // objects with auto traits, such as: // StructTag("tag-std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync)>") } // StructTag("tag-std::boxed::Box") } let t = self.monomorphize(t); - with_no_trimmed_paths!(printer.print_type(t).unwrap().into_buffer()).intern() + t.print(&mut printer).unwrap(); + with_no_trimmed_paths!(printer.into_buffer()).intern() } pub fn ty_mangled_name(&self, t: Ty<'tcx>) -> InternedString { @@ -659,7 +658,7 @@ impl<'tcx> GotocCtx<'tcx> { // linked C libraries // https://github.com/model-checking/kani/issues/450 match t.kind() { - TyKind::Adt(def, substs) if substs.is_empty() && def.repr().c() => { + TyKind::Adt(def, args) if args.is_empty() && def.repr().c() => { // For non-generic #[repr(C)] types, use the literal path instead of mangling it. self.tcx.def_path_str(def.did()).intern() } @@ -682,7 +681,7 @@ impl<'tcx> GotocCtx<'tcx> { } fn codegen_ty_raw_array(&mut self, elem_ty: Ty<'tcx>, len: Const<'tcx>) -> Type { - let size = self.codegen_const(len, None).int_constant_value().unwrap(); + let size = self.codegen_const_internal(len, None).int_constant_value().unwrap(); let elemt = self.codegen_ty(elem_ty); elemt.array_of(size) } @@ -783,16 +782,16 @@ impl<'tcx> GotocCtx<'tcx> { ty::Slice(e) => self.codegen_ty(*e).flexible_array_of(), ty::Str => Type::unsigned_int(8).flexible_array_of(), ty::Ref(_, t, _) | ty::RawPtr(ty::TypeAndMut { ty: t, .. }) => self.codegen_ty_ref(*t), - ty::FnDef(def_id, substs) => { + ty::FnDef(def_id, args) => { let instance = - Instance::resolve(self.tcx, ty::ParamEnv::reveal_all(), *def_id, substs) + Instance::resolve(self.tcx, ty::ParamEnv::reveal_all(), *def_id, args) .unwrap() .unwrap(); self.codegen_fndef_type(instance) } ty::FnPtr(sig) => self.codegen_function_sig(*sig).to_pointer(), ty::Closure(_, subst) => self.codegen_ty_closure(ty, subst), - ty::Generator(..) => self.codegen_ty_generator(ty), + ty::Coroutine(..) => self.codegen_ty_coroutine(ty), ty::Never => self.ensure_struct(NEVER_TYPE_EMPTY_STRUCT_NAME, "!", |_, _| vec![]), ty::Tuple(ts) => { if ts.is_empty() { @@ -815,11 +814,7 @@ impl<'tcx> GotocCtx<'tcx> { ty::Bound(_, _) | ty::Param(_) => unreachable!("monomorphization bug"), // type checking remnants which shouldn't be reachable - ty::GeneratorWitness(_) - | ty::GeneratorWitnessMIR(_, _) - | ty::Infer(_) - | ty::Placeholder(_) - | ty::Error(_) => { + ty::CoroutineWitness(_, _) | ty::Infer(_) | ty::Placeholder(_) | ty::Error(_) => { unreachable!("remnants of type checking") } } @@ -866,7 +861,7 @@ impl<'tcx> GotocCtx<'tcx> { // We need to pad to the next offset let padding_size = next_offset - current_offset; let name = format!("$pad{idx}"); - Some(DatatypeComponent::padding(&name, padding_size.bits())) + Some(DatatypeComponent::padding(name, padding_size.bits())) } else { None } @@ -876,7 +871,7 @@ impl<'tcx> GotocCtx<'tcx> { fn codegen_alignment_padding( &self, size: Size, - layout: &LayoutS, + layout: &LayoutS, idx: usize, ) -> Option { let align = Size::from_bits(layout.align.abi.bits()); @@ -901,7 +896,7 @@ impl<'tcx> GotocCtx<'tcx> { fn codegen_struct_fields( &mut self, flds: Vec<(String, Ty<'tcx>)>, - layout: &LayoutS, + layout: &LayoutS, initial_offset: Size, ) -> Vec { match &layout.fields { @@ -981,15 +976,15 @@ impl<'tcx> GotocCtx<'tcx> { /// A closure is a struct of all its environments. That is, a closure is /// just a tuple with a unique type identifier, so that Fn related traits /// can find its impl. - fn codegen_ty_closure(&mut self, t: Ty<'tcx>, substs: ty::subst::SubstsRef<'tcx>) -> Type { + fn codegen_ty_closure(&mut self, t: Ty<'tcx>, args: ty::GenericArgsRef<'tcx>) -> Type { self.ensure_struct(self.ty_mangled_name(t), self.ty_pretty_name(t), |ctx, _| { - ctx.codegen_ty_tuple_like(t, substs.as_closure().upvar_tys().collect()) + ctx.codegen_ty_tuple_like(t, args.as_closure().upvar_tys().to_vec()) }) } - /// Translate a generator type similarly to an enum with a variant for each suspend point. + /// Translate a coroutine type similarly to an enum with a variant for each suspend point. /// - /// Consider the following generator: + /// Consider the following coroutine: /// ``` /// || { /// let a = true; @@ -1001,13 +996,13 @@ impl<'tcx> GotocCtx<'tcx> { /// ``` /// /// Rustc compiles this to something similar to the following enum (but there are differences, see below!), - /// as described at the top of : + /// as described at the top of : /// /// ```ignore - /// enum GeneratorEnum { - /// Unresumed, // initial state of the generator - /// Returned, // generator has returned - /// Panicked, // generator has panicked + /// enum CoroutineEnum { + /// Unresumed, // initial state of the coroutine + /// Returned, // coroutine has returned + /// Panicked, // coroutine has panicked /// Suspend0 { b: &bool, a: bool }, // state after suspending (`yield`ing) for the first time /// Suspend1, // state after suspending (`yield`ing) for the second time /// } @@ -1020,12 +1015,12 @@ impl<'tcx> GotocCtx<'tcx> { /// This means that we CANNOT use the enum translation, which would be roughly as follows: /// /// ```ignore - /// struct GeneratorEnum { + /// struct CoroutineEnum { /// int case; // discriminant - /// union GeneratorEnum-union cases; // variant + /// union CoroutineEnum-union cases; // variant /// } /// - /// union GeneratorEnum-union { + /// union CoroutineEnum-union { /// struct Unresumed variant0; /// struct Returned variant1; /// // ... @@ -1035,10 +1030,10 @@ impl<'tcx> GotocCtx<'tcx> { /// Instead, we use the following translation: /// /// ```ignore - /// union GeneratorEnum { + /// union CoroutineEnum { /// struct DirectFields direct_fields; - /// struct Unresumed generator_variant_Unresumed; - /// struct Returned generator_variant_Returned; + /// struct Unresumed coroutine_variant_Unresumed; + /// struct Returned coroutine_variant_Returned; /// // ... /// } /// @@ -1055,17 +1050,17 @@ impl<'tcx> GotocCtx<'tcx> { /// // ... /// /// struct Suspend0 { - /// bool *generator_field_0; // variable b in the generator code above + /// bool *coroutine_field_0; // variable b in the coroutine code above /// // padding (for char case in DirectFields) - /// bool generator_field_1; // variable a in the generator code above + /// bool coroutine_field_1; // variable a in the coroutine code above /// } /// ``` /// - /// Of course, if the generator has any other top-level/direct fields, they'd be included in the `DirectFields` struct as well. - fn codegen_ty_generator(&mut self, ty: Ty<'tcx>) -> Type { - let generator_name = self.ty_mangled_name(ty); + /// Of course, if the coroutine has any other top-level/direct fields, they'd be included in the `DirectFields` struct as well. + fn codegen_ty_coroutine(&mut self, ty: Ty<'tcx>) -> Type { + let coroutine_name = self.ty_mangled_name(ty); let pretty_name = self.ty_pretty_name(ty); - debug!(?pretty_name, "codeged_ty_generator"); + debug!(?pretty_name, "codeged_ty_coroutine"); self.ensure_union(self.ty_mangled_name(ty), pretty_name, |ctx, _| { let type_and_layout = ctx.layout_of(ty); let (discriminant_field, variants) = match &type_and_layout.variants { @@ -1075,13 +1070,13 @@ impl<'tcx> GotocCtx<'tcx> { variants, .. } => (tag_field, variants), - _ => unreachable!("Generators have more than one variant and use direct encoding"), + _ => unreachable!("Coroutines have more than one variant and use direct encoding"), }; // generate a struct for the direct fields of the layout (fields that don't occur in the variants) let direct_fields = DatatypeComponent::Field { name: "direct_fields".into(), - typ: ctx.codegen_generator_variant_struct( - generator_name, + typ: ctx.codegen_coroutine_variant_struct( + coroutine_name, pretty_name, type_and_layout, "DirectFields".into(), @@ -1090,11 +1085,11 @@ impl<'tcx> GotocCtx<'tcx> { }; let mut fields = vec![direct_fields]; for var_idx in variants.indices() { - let variant_name = GeneratorSubsts::variant_name(var_idx).into(); + let variant_name = CoroutineArgs::variant_name(var_idx).into(); fields.push(DatatypeComponent::Field { - name: ctx.generator_variant_name(var_idx), - typ: ctx.codegen_generator_variant_struct( - generator_name, + name: ctx.coroutine_variant_name(var_idx), + typ: ctx.codegen_coroutine_variant_struct( + coroutine_name, pretty_name, type_and_layout.for_variant(ctx, var_idx), variant_name, @@ -1106,22 +1101,22 @@ impl<'tcx> GotocCtx<'tcx> { }) } - /// Generates a struct for a variant of the generator. + /// Generates a struct for a variant of the coroutine. /// - /// The field `discriminant_field` should be `Some(idx)` when generating the variant for the direct (top-[evel) fields of the generator. + /// The field `discriminant_field` should be `Some(idx)` when generating the variant for the direct (top-[evel) fields of the coroutine. /// Then the field with the index `idx` will be treated as the discriminant and will be given a special name to work with the rest of the code. - /// The field `discriminant_field` should be `None` when generating an actual variant of the generator because those don't contain the discriminant as a field. - fn codegen_generator_variant_struct( + /// The field `discriminant_field` should be `None` when generating an actual variant of the coroutine because those don't contain the discriminant as a field. + fn codegen_coroutine_variant_struct( &mut self, - generator_name: InternedString, - pretty_generator_name: InternedString, + coroutine_name: InternedString, + pretty_coroutine_name: InternedString, type_and_layout: TyAndLayout<'tcx, Ty<'tcx>>, variant_name: InternedString, discriminant_field: Option, ) -> Type { - let struct_name = format!("{generator_name}::{variant_name}"); - let pretty_struct_name = format!("{pretty_generator_name}::{variant_name}"); - debug!(?pretty_struct_name, "codeged_generator_variant_struct"); + let struct_name = format!("{coroutine_name}::{variant_name}"); + let pretty_struct_name = format!("{pretty_coroutine_name}::{variant_name}"); + debug!(?pretty_struct_name, "codeged_coroutine_variant_struct"); self.ensure_struct(struct_name, pretty_struct_name, |ctx, _| { let mut offset = Size::ZERO; let mut fields = vec![]; @@ -1131,7 +1126,7 @@ impl<'tcx> GotocCtx<'tcx> { let field_name = if Some(idx) == discriminant_field { "case".into() } else { - ctx.generator_field_name(idx) + ctx.coroutine_field_name(idx) }; let field_ty = type_and_layout.field(ctx, idx).ty; let field_offset = type_and_layout.fields.offset(idx); @@ -1154,12 +1149,12 @@ impl<'tcx> GotocCtx<'tcx> { }) } - pub fn generator_variant_name(&self, var_idx: VariantIdx) -> InternedString { - format!("generator_variant_{}", GeneratorSubsts::variant_name(var_idx)).into() + pub fn coroutine_variant_name(&self, var_idx: VariantIdx) -> InternedString { + format!("coroutine_variant_{}", CoroutineArgs::variant_name(var_idx)).into() } - pub fn generator_field_name(&self, field_idx: usize) -> InternedString { - format!("generator_field_{field_idx}").into() + pub fn coroutine_field_name(&self, field_idx: usize) -> InternedString { + format!("coroutine_field_{field_idx}").into() } /// Codegen "fat pointers" to the given `pointee_type`. These are pointers with metadata. @@ -1236,7 +1231,7 @@ impl<'tcx> GotocCtx<'tcx> { | ty::Closure(..) | ty::Float(_) | ty::Foreign(_) - | ty::Generator(..) + | ty::Coroutine(..) | ty::Int(_) | ty::RawPtr(_) | ty::Ref(..) @@ -1256,8 +1251,7 @@ impl<'tcx> GotocCtx<'tcx> { // For soundness, hold off on generating them till we have test-cases. ty::Bound(_, _) => todo!("{:?} {:?}", pointee_type, pointee_type.kind()), ty::Error(_) => todo!("{:?} {:?}", pointee_type, pointee_type.kind()), - ty::GeneratorWitness(_) => todo!("{:?} {:?}", pointee_type, pointee_type.kind()), - ty::GeneratorWitnessMIR(_, _) => todo!("{:?} {:?}", pointee_type, pointee_type.kind()), + ty::CoroutineWitness(_, _) => todo!("{:?} {:?}", pointee_type, pointee_type.kind()), ty::Infer(_) => todo!("{:?} {:?}", pointee_type, pointee_type.kind()), ty::Param(_) => todo!("{:?} {:?}", pointee_type, pointee_type.kind()), ty::Placeholder(_) => todo!("{:?} {:?}", pointee_type, pointee_type.kind()), @@ -1330,12 +1324,7 @@ impl<'tcx> GotocCtx<'tcx> { /// /// For details, see pub fn codegen_fndef_type(&mut self, instance: Instance<'tcx>) -> Type { - let func = self.symbol_name(instance); - self.ensure_struct( - format!("{func}::FnDefStruct"), - format!("{}::FnDefStruct", self.readable_instance_name(instance)), - |_, _| vec![], - ) + self.codegen_fndef_type_stable(rustc_internal::stable(instance)) } /// codegen for struct @@ -1345,7 +1334,7 @@ impl<'tcx> GotocCtx<'tcx> { &mut self, ty: Ty<'tcx>, def: &'tcx AdtDef, - subst: &'tcx InternalSubsts<'tcx>, + subst: &'tcx GenericArgsRef<'tcx>, ) -> Type { self.ensure_struct(self.ty_mangled_name(ty), self.ty_pretty_name(ty), |ctx, _| { let variant = &def.variants().raw[0]; @@ -1358,8 +1347,8 @@ impl<'tcx> GotocCtx<'tcx> { fn codegen_variant_struct_fields( &mut self, variant: &VariantDef, - subst: &'tcx InternalSubsts<'tcx>, - layout: &LayoutS, + subst: &'tcx GenericArgsRef<'tcx>, + layout: &LayoutS, initial_offset: Size, ) -> Vec { let flds: Vec<_> = @@ -1372,7 +1361,7 @@ impl<'tcx> GotocCtx<'tcx> { &mut self, ty: Ty<'tcx>, def: &'tcx AdtDef, - subst: &'tcx InternalSubsts<'tcx>, + subst: &'tcx GenericArgsRef<'tcx>, ) -> Type { self.ensure_union(self.ty_mangled_name(ty), self.ty_pretty_name(ty), |ctx, _| { def.variants().raw[0] @@ -1380,7 +1369,7 @@ impl<'tcx> GotocCtx<'tcx> { .iter() .map(|f| { DatatypeComponent::field( - &f.name.to_string(), + f.name.to_string(), ctx.codegen_ty(f.ty(ctx.tcx, subst)), ) }) @@ -1424,7 +1413,7 @@ impl<'tcx> GotocCtx<'tcx> { &mut self, ty: Ty<'tcx>, adtdef: &'tcx AdtDef, - subst: &'tcx InternalSubsts<'tcx>, + subst: &'tcx GenericArgsRef<'tcx>, ) -> Type { let pretty_name = self.ty_pretty_name(ty); // variants appearing in source code (in source code order) @@ -1448,9 +1437,9 @@ impl<'tcx> GotocCtx<'tcx> { }) } Variants::Multiple { tag_encoding, variants, tag_field, .. } => { - // Contrary to generators, currently enums have only one field (the discriminant), the rest are in the variants: + // Contrary to coroutines, currently enums have only one field (the discriminant), the rest are in the variants: assert!(layout.fields.count() <= 1); - // Contrary to generators, the discriminant is the first (and only) field for enums: + // Contrary to coroutines, the discriminant is the first (and only) field for enums: assert_eq!(*tag_field, 0); match tag_encoding { TagEncoding::Direct => { @@ -1527,8 +1516,8 @@ impl<'tcx> GotocCtx<'tcx> { &mut self, ty: Ty<'tcx>, adtdef: &'tcx AdtDef, - subst: &'tcx InternalSubsts<'tcx>, - variants: &IndexVec, + subst: &'tcx GenericArgsRef<'tcx>, + variants: &IndexVec>, ) -> Type { let non_zst_count = variants.iter().filter(|layout| layout.size.bytes() > 0).count(); let mangled_name = self.ty_mangled_name(ty); @@ -1547,7 +1536,7 @@ impl<'tcx> GotocCtx<'tcx> { pub(crate) fn variant_min_offset( &self, - variants: &IndexVec, + variants: &IndexVec>, ) -> Option { variants .iter() @@ -1608,9 +1597,10 @@ impl<'tcx> GotocCtx<'tcx> { Primitive::F32 => self.tcx.types.f32, Primitive::F64 => self.tcx.types.f64, - Primitive::Pointer(_) => { - self.tcx.mk_ptr(ty::TypeAndMut { ty: self.tcx.types.u8, mutbl: Mutability::Not }) - } + Primitive::Pointer(_) => Ty::new_ptr( + self.tcx, + ty::TypeAndMut { ty: self.tcx.types.u8, mutbl: Mutability::Not }, + ), } } @@ -1630,8 +1620,8 @@ impl<'tcx> GotocCtx<'tcx> { name: InternedString, pretty_name: InternedString, def: &'tcx AdtDef, - subst: &'tcx InternalSubsts<'tcx>, - layouts: &IndexVec, + subst: &'tcx GenericArgsRef<'tcx>, + layouts: &IndexVec>, initial_offset: Size, ) -> Vec { def.variants() @@ -1642,7 +1632,7 @@ impl<'tcx> GotocCtx<'tcx> { None } else { Some(DatatypeComponent::field( - &case.name.to_string(), + case.name.to_string(), self.codegen_enum_case_struct( name, pretty_name, @@ -1662,8 +1652,8 @@ impl<'tcx> GotocCtx<'tcx> { name: InternedString, pretty_name: InternedString, case: &VariantDef, - subst: &'tcx InternalSubsts<'tcx>, - variant: &LayoutS, + subst: &'tcx GenericArgsRef<'tcx>, + variant: &LayoutS, initial_offset: Size, ) -> Type { let case_name = format!("{name}::{}", case.name); @@ -1716,7 +1706,7 @@ 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) = self.current_fn().mir().spread_arg { + if let Some(spread) = self.current_fn().body_internal().spread_arg { if lc.index() >= spread.index() { let (name, _) = self.codegen_spread_arg_name(&lc); ident = name; @@ -1771,7 +1761,7 @@ impl<'tcx> GotocCtx<'tcx> { // Codegen the type replacing the non-zst field. let new_name = self.ty_mangled_name(*curr).to_string() + "::WithDataPtr"; let new_pretty_name = format!("{}::WithDataPtr", self.ty_pretty_name(*curr)); - if let ty::Adt(adt_def, adt_substs) = curr.kind() { + if let ty::Adt(adt_def, adt_args) = curr.kind() { let fields = &adt_def.variants().get(VariantIdx::from_u32(0)).unwrap().fields; self.ensure_struct(new_name, new_pretty_name, |ctx, s_name| { let fields_shape = ctx.layout_of(*curr).layout.fields(); @@ -1780,7 +1770,7 @@ impl<'tcx> GotocCtx<'tcx> { .map(|idx| { let idx = idx.into(); let name = fields[idx].name.to_string().intern(); - let field_ty = fields[idx].ty(ctx.tcx, adt_substs); + let field_ty = fields[idx].ty(ctx.tcx, adt_args); let typ = if !ctx.is_zst(field_ty) { last_type.clone() } else { @@ -1811,11 +1801,11 @@ impl<'tcx> GotocCtx<'tcx> { } let mut typ = struct_type; - while let ty::Adt(adt_def, adt_substs) = typ.kind() { + while let ty::Adt(adt_def, adt_args) = typ.kind() { assert_eq!(adt_def.variants().len(), 1, "Expected a single-variant ADT. Found {typ:?}"); let fields = &adt_def.variants().get(VariantIdx::from_u32(0)).unwrap().fields; let last_field = fields.last_index().expect("Trait should be the last element."); - typ = fields[last_field].ty(self.tcx, adt_substs); + typ = fields[last_field].ty(self.tcx, adt_args); } if typ.is_trait() { Some(typ) } else { None } } @@ -1858,7 +1848,7 @@ impl<'tcx> GotocCtx<'tcx> { type Item = (String, Ty<'tcx>); fn next(&mut self) -> Option { - if let ty::Adt(adt_def, adt_substs) = self.curr.kind() { + if let ty::Adt(adt_def, adt_args) = self.curr.kind() { assert_eq!( adt_def.variants().len(), 1, @@ -1869,8 +1859,8 @@ impl<'tcx> GotocCtx<'tcx> { let fields = &adt_def.variants().get(VariantIdx::from_u32(0)).unwrap().fields; let mut non_zsts = fields .iter() - .filter(|field| !ctx.is_zst(field.ty(ctx.tcx, adt_substs))) - .map(|non_zst| (non_zst.name.to_string(), non_zst.ty(ctx.tcx, adt_substs))); + .filter(|field| !ctx.is_zst(field.ty(ctx.tcx, adt_args))) + .map(|non_zst| (non_zst.name.to_string(), non_zst.ty(ctx.tcx, adt_args))); let (name, next) = non_zsts.next().expect("Expected one non-zst field."); self.curr = next; assert!(non_zsts.next().is_none(), "Expected only one non-zst field."); diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index a89e5035f2a2..5d27375de54f 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -3,6 +3,7 @@ //! This file contains the code necessary to interface with the compiler backend +use crate::args::ReachabilityType; use crate::codegen_cprover_gotoc::GotocCtx; use crate::kani_middle::analysis; use crate::kani_middle::attributes::is_test_harness_description; @@ -12,7 +13,7 @@ use crate::kani_middle::reachability::{ collect_reachable_items, filter_const_crate_items, filter_crate_items, }; use crate::kani_middle::{check_reachable_items, dump_mir_items}; -use crate::kani_queries::{QueryDb, ReachabilityType}; +use crate::kani_queries::QueryDb; use cbmc::goto_program::Location; use cbmc::irep::goto_binary_serde::write_goto_binary_file; use cbmc::RoundingMode; @@ -32,18 +33,20 @@ use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_errors::{ErrorGuaranteed, DEFAULT_LOCALE_RESOURCE}; use rustc_hir::def_id::LOCAL_CRATE; use rustc_hir::definitions::DefPathHash; +use rustc_metadata::creader::MetadataLoaderDyn; use rustc_metadata::fs::{emit_wrapper_file, METADATA_FILENAME}; use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::mir::mono::MonoItem; -use rustc_middle::query::{ExternProviders, Providers}; use rustc_middle::ty::TyCtxt; +use rustc_middle::util::Providers; use rustc_session::config::{CrateType, OutputFilenames, OutputType}; -use rustc_session::cstore::MetadataLoaderDyn; use rustc_session::output::out_filename; use rustc_session::Session; +use rustc_smir::rustc_internal; use rustc_target::abi::Endian; use rustc_target::spec::PanicStrategy; +use stable_mir::mir::mono::MonoItem as MonoItemStable; use std::any::Any; use std::collections::BTreeMap; use std::collections::HashSet; @@ -109,8 +112,11 @@ impl GotocCodegenBackend { ); } MonoItem::Static(def_id) => { + let MonoItemStable::Static(def) = rustc_internal::stable(item) else { + unreachable!() + }; gcx.call_with_panic_debug_info( - |ctx| ctx.declare_static(def_id, *item), + |ctx| ctx.declare_static(def), format!("declare_static: {def_id:?}"), def_id, ); @@ -134,8 +140,11 @@ impl GotocCodegenBackend { ); } MonoItem::Static(def_id) => { + let MonoItemStable::Static(def) = rustc_internal::stable(item) else { + unreachable!() + }; gcx.call_with_panic_debug_info( - |ctx| ctx.codegen_static(def_id, *item), + |ctx| ctx.codegen_static(def), format!("codegen_static: {def_id:?}"), def_id, ); @@ -164,9 +173,9 @@ impl GotocCodegenBackend { // 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().output_pretty_json; + let pretty = self.queries.lock().unwrap().args().output_pretty_json; write_file(&symtab_goto, ArtifactType::PrettyNameMap, &pretty_name_map, pretty); - if gcx.queries.write_json_symtab { + if gcx.queries.args().write_json_symtab { write_file(&symtab_goto, ArtifactType::SymTab, &gcx.symbol_table, pretty); symbol_table_to_gotoc(&tcx, &symtab_goto); } else { @@ -192,10 +201,6 @@ impl CodegenBackend for GotocCodegenBackend { provide::provide(providers, &self.queries.lock().unwrap()); } - fn provide_extern(&self, providers: &mut ExternProviders) { - provide::provide_extern(providers); - } - fn print_version(&self) { println!("Kani-goto version: {}", env!("CARGO_PKG_VERSION")); } @@ -211,105 +216,115 @@ impl CodegenBackend for GotocCodegenBackend { rustc_metadata: EncodedMetadata, _need_metadata_module: bool, ) -> Box { - super::utils::init(); - - // Queries shouldn't change today once codegen starts. - let queries = self.queries.lock().unwrap().clone(); - check_target(tcx.sess); - check_options(tcx.sess); - - // Codegen all items that need to be processed according to the selected reachability mode: - // - // - Harnesses: Generate one model per local harnesses (marked with `kani::proof` attribute). - // - Tests: Generate one model per test harnesses. - // - PubFns: Generate code for all reachable logic starting from the local public functions. - // - None: Don't generate code. This is used to compile dependencies. - let base_filename = tcx.output_filenames(()).output_path(OutputType::Object); - let reachability = queries.reachability_analysis; - let mut results = GotoCodegenResults::new(tcx, reachability); - match reachability { - ReachabilityType::Harnesses => { - // Cross-crate collecting of all items that are reachable from the crate harnesses. - let harnesses = queries.target_harnesses(); - let mut items: HashSet = HashSet::with_capacity(harnesses.len()); - items.extend(harnesses.into_iter()); - let harnesses = - filter_crate_items(tcx, |_, def_id| items.contains(&tcx.def_path_hash(def_id))); - for harness in harnesses { - let model_path = - queries.harness_model_path(&tcx.def_path_hash(harness.def_id())).unwrap(); + let ret_val = rustc_internal::run(tcx, || { + super::utils::init(); + + // Queries shouldn't change today once codegen starts. + let queries = self.queries.lock().unwrap().clone(); + check_target(tcx.sess); + check_options(tcx.sess); + + // Codegen all items that need to be processed according to the selected reachability mode: + // + // - Harnesses: Generate one model per local harnesses (marked with `kani::proof` attribute). + // - Tests: Generate one model per test harnesses. + // - PubFns: Generate code for all reachable logic starting from the local public functions. + // - None: Don't generate code. This is used to compile dependencies. + let base_filename = tcx.output_filenames(()).output_path(OutputType::Object); + let reachability = queries.args().reachability_analysis; + let mut results = GotoCodegenResults::new(tcx, reachability); + match reachability { + ReachabilityType::Harnesses => { + // Cross-crate collecting of all items that are reachable from the crate harnesses. + let harnesses = queries.target_harnesses(); + let mut items: HashSet = HashSet::with_capacity(harnesses.len()); + items.extend(harnesses); + let harnesses = filter_crate_items(tcx, |_, def_id| { + items.contains(&tcx.def_path_hash(def_id)) + }); + for harness in harnesses { + let model_path = queries + .harness_model_path(&tcx.def_path_hash(harness.def_id())) + .unwrap(); + let (gcx, items) = + self.codegen_items(tcx, &[harness], model_path, &results.machine_model); + results.extend(gcx, items, None); + } + } + ReachabilityType::Tests => { + // We're iterating over crate items here, so what we have to codegen is the "test description" containing the + // test closure that we want to execute + // TODO: Refactor this code so we can guarantee that the pair (test_fn, test_desc) actually match. + let mut descriptions = vec![]; + let harnesses = filter_const_crate_items(tcx, |_, def_id| { + if is_test_harness_description(tcx, def_id) { + descriptions.push(def_id); + true + } else { + false + } + }); + // Codegen still takes a considerable amount, thus, we only generate one model for + // all harnesses and copy them for each harness. + // We will be able to remove this once we optimize all calls to CBMC utilities. + // https://github.com/model-checking/kani/issues/1971 + let model_path = base_filename.with_extension(ArtifactType::SymTabGoto); let (gcx, items) = - self.codegen_items(tcx, &[harness], model_path, &results.machine_model); + self.codegen_items(tcx, &harnesses, &model_path, &results.machine_model); results.extend(gcx, items, None); - } - } - ReachabilityType::Tests => { - // We're iterating over crate items here, so what we have to codegen is the "test description" containing the - // test closure that we want to execute - // TODO: Refactor this code so we can guarantee that the pair (test_fn, test_desc) actually match. - let mut descriptions = vec![]; - let harnesses = filter_const_crate_items(tcx, |_, def_id| { - if is_test_harness_description(tcx, def_id) { - descriptions.push(def_id); - true - } else { - false + + 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 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() + )); + results.harnesses.push(metadata); } - }); - // Codegen still takes a considerable amount, thus, we only generate one model for - // all harnesses and copy them for each harness. - // We will be able to remove this once we optimize all calls to CBMC utilities. - // https://github.com/model-checking/kani/issues/1971 - let model_path = base_filename.with_extension(ArtifactType::SymTabGoto); - let (gcx, items) = - self.codegen_items(tcx, &harnesses, &model_path, &results.machine_model); - results.extend(gcx, items, None); - - 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 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() - )); - results.harnesses.push(metadata); + } + ReachabilityType::None => {} + ReachabilityType::PubFns => { + let entry_fn = tcx.entry_fn(()).map(|(id, _)| id); + let local_reachable = filter_crate_items(tcx, |_, def_id| { + (tcx.is_reachable_non_generic(def_id) && tcx.def_kind(def_id).is_fn_like()) + || entry_fn == Some(def_id) + }); + let model_path = base_filename.with_extension(ArtifactType::SymTabGoto); + let (gcx, items) = self.codegen_items( + tcx, + &local_reachable, + &model_path, + &results.machine_model, + ); + results.extend(gcx, items, None); } } - ReachabilityType::None => {} - ReachabilityType::PubFns => { - let entry_fn = tcx.entry_fn(()).map(|(id, _)| id); - let local_reachable = filter_crate_items(tcx, |_, def_id| { - (tcx.is_reachable_non_generic(def_id) && tcx.def_kind(def_id).is_fn_like()) - || entry_fn == Some(def_id) - }); - let model_path = base_filename.with_extension(ArtifactType::SymTabGoto); - let (gcx, items) = - self.codegen_items(tcx, &local_reachable, &model_path, &results.machine_model); - results.extend(gcx, items, None); - } - } - if reachability != ReachabilityType::None { - // Print compilation report. - results.print_report(tcx); - - if reachability != ReachabilityType::Harnesses { - // In a workspace, cargo seems to be using the same file prefix to build a crate that is - // a package lib and also a dependency of another package. - // 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, - ArtifactType::Metadata, - &results.generate_metadata(), - queries.output_pretty_json, - ); + if reachability != ReachabilityType::None { + // Print compilation report. + results.print_report(tcx); + + if reachability != ReachabilityType::Harnesses { + // In a workspace, cargo seems to be using the same file prefix to build a crate that is + // a package lib and also a dependency of another package. + // 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, + ArtifactType::Metadata, + &results.generate_metadata(), + queries.args().output_pretty_json, + ); + } } - } - codegen_results(tcx, rustc_metadata, &results.machine_model) + codegen_results(tcx, rustc_metadata, &results.machine_model) + }); + ret_val.unwrap() } fn join_codegen( @@ -321,7 +336,7 @@ impl CodegenBackend for GotocCodegenBackend { match ongoing_codegen.downcast::<(CodegenResults, FxIndexMap)>() { Ok(val) => Ok(*val), - Err(val) => panic!("unexpected error: {:?}", val.type_id()), + Err(val) => panic!("unexpected error: {:?}", (*val).type_id()), } } @@ -345,7 +360,7 @@ impl CodegenBackend for GotocCodegenBackend { codegen_results: CodegenResults, outputs: &OutputFilenames, ) -> Result<(), ErrorGuaranteed> { - let requested_crate_types = sess.crate_types(); + let requested_crate_types = &codegen_results.crate_info.crate_types; for crate_type in requested_crate_types { let out_fname = out_filename( sess, @@ -386,17 +401,23 @@ fn check_target(session: &Session) { // The requirement below is needed to build a valid CBMC machine model // in function `machine_model_from_session` from // src/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs - let is_linux_target = session.target.llvm_target == "x86_64-unknown-linux-gnu"; + let is_x86_64_linux_target = session.target.llvm_target == "x86_64-unknown-linux-gnu"; + let is_arm64_linux_target = session.target.llvm_target == "aarch64-unknown-linux-gnu"; // Comparison with `x86_64-apple-darwin` does not work well because the LLVM // target may become `x86_64-apple-macosx10.7.0` (or similar) and fail let is_x86_64_darwin_target = session.target.llvm_target.starts_with("x86_64-apple-"); // looking for `arm64-apple-*` let is_arm64_darwin_target = session.target.llvm_target.starts_with("arm64-apple-"); - if !is_linux_target && !is_x86_64_darwin_target && !is_arm64_darwin_target { + if !is_x86_64_linux_target + && !is_arm64_linux_target + && !is_x86_64_darwin_target + && !is_arm64_darwin_target + { let err_msg = format!( - "Kani requires the target platform to be `x86_64-unknown-linux-gnu` or \ - `x86_64-apple-*` or `arm64-apple-*`, but it is {}", + "Kani requires the target platform to be `x86_64-unknown-linux-gnu`, \ + `aarch64-unknown-linux-gnu`, `x86_64-apple-*` or `arm64-apple-*`, but \ + it is {}", &session.target.llvm_target ); session.err(err_msg); @@ -571,9 +592,9 @@ impl<'tcx> GotoCodegenResults<'tcx> { metadata: Option, ) { let mut items = items; - self.harnesses.extend(metadata.into_iter()); - self.concurrent_constructs.extend(gcx.concurrent_constructs.into_iter()); - self.unsupported_constructs.extend(gcx.unsupported_constructs.into_iter()); + self.harnesses.extend(metadata); + self.concurrent_constructs.extend(gcx.concurrent_constructs); + self.unsupported_constructs.extend(gcx.unsupported_constructs); self.items.append(&mut items); } @@ -622,6 +643,7 @@ fn new_machine_model(sess: &Session) -> MachineModel { // `check_target` from src/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs // and error if it is not any of the ones we expect. let architecture = &sess.target.arch; + let os = &sess.target.os; let pointer_width = sess.target.pointer_width.into(); // The model assumes the following values for session options: @@ -689,12 +711,18 @@ fn new_machine_model(sess: &Session) -> MachineModel { let double_width = 64; let float_width = 32; let int_width = 32; - let long_double_width = 64; + let long_double_width = match os.as_ref() { + "linux" => 128, + _ => 64, + }; let long_int_width = 64; let long_long_int_width = 64; let short_int_width = 16; let single_width = 32; - let wchar_t_is_unsigned = false; + // https://developer.arm.com/documentation/dui0491/i/Compiler-Command-line-Options/--signed-chars----unsigned-chars + // https://www.arm.linux.org.uk/docs/faqs/signedchar.php + // https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms + let wchar_t_is_unsigned = matches!(os.as_ref(), "linux"); let wchar_t_width = 32; MachineModel { diff --git a/kani-compiler/src/codegen_cprover_gotoc/context/current_fn.rs b/kani-compiler/src/codegen_cprover_gotoc/context/current_fn.rs index 1c503cadd3b3..e39d44743c5f 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/context/current_fn.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/context/current_fn.rs @@ -3,26 +3,26 @@ use crate::codegen_cprover_gotoc::GotocCtx; use cbmc::goto_program::Stmt; -use rustc_middle::mir::BasicBlock; -use rustc_middle::mir::Body; -use rustc_middle::ty::Instance; -use rustc_middle::ty::PolyFnSig; +use rustc_middle::mir::{BasicBlock, Body as InternalBody}; +use rustc_middle::ty::{Instance as InternalInstance, PolyFnSig}; +use rustc_smir::rustc_internal; +use stable_mir::mir::mono::Instance; +use stable_mir::mir::Body; +use stable_mir::CrateDef; /// This structure represents useful data about the function we are currently compiling. #[derive(Debug)] pub struct CurrentFnCtx<'tcx> { /// The GOTO block we are compiling into block: Vec, - /// The current MIR basic block - current_bb: Option, /// The codegen instance for the current function - instance: Instance<'tcx>, + instance: Instance, /// The crate this function is from krate: String, - /// The goto labels for all blocks - labels: Vec, - /// The mir for the current instance - mir: &'tcx Body<'tcx>, + /// The MIR for the current instance. This is using the internal representation. + mir: &'tcx InternalBody<'tcx>, + /// The MIR for the current instance. + body: Body, /// The symbol name of the current function name: String, /// A human readable pretty name for the current function @@ -35,17 +35,21 @@ pub struct CurrentFnCtx<'tcx> { /// Constructor impl<'tcx> CurrentFnCtx<'tcx> { - pub fn new(instance: Instance<'tcx>, gcx: &GotocCtx<'tcx>, labels: Vec) -> Self { + pub fn new(instance: Instance, gcx: &GotocCtx<'tcx>) -> Self { + let internal_instance = rustc_internal::internal(instance); + let body = instance.body().unwrap(); + let readable_name = instance.name(); + let name = + if &readable_name == "main" { readable_name.clone() } else { instance.mangled_name() }; Self { block: vec![], - current_bb: None, instance, - krate: gcx.get_crate(instance), - labels, - mir: gcx.tcx.instance_mir(instance.def), - name: gcx.symbol_name(instance), - readable_name: gcx.readable_instance_name(instance), - sig: gcx.fn_sig_of_instance(instance), + mir: gcx.tcx.instance_mir(internal_instance.def), + krate: instance.def.krate().name, + body, + name, + readable_name, + sig: gcx.fn_sig_of_instance(internal_instance), temp_var_counter: 0, } } @@ -67,21 +71,13 @@ impl<'tcx> CurrentFnCtx<'tcx> { pub fn push_onto_block(&mut self, s: Stmt) { self.block.push(s) } - - pub fn reset_current_bb(&mut self) { - self.current_bb = None; - } - - pub fn set_current_bb(&mut self, bb: BasicBlock) { - self.current_bb = Some(bb); - } } /// Getters impl<'tcx> CurrentFnCtx<'tcx> { /// The function we are currently compiling - pub fn instance(&self) -> Instance<'tcx> { - self.instance + pub fn instance(&self) -> InternalInstance<'tcx> { + rustc_internal::internal(self.instance) } /// The crate that function came from @@ -89,8 +85,8 @@ impl<'tcx> CurrentFnCtx<'tcx> { self.krate.to_string() } - /// The MIR for the function we are currently compiling - pub fn mir(&self) -> &'tcx Body<'tcx> { + /// The internal MIR for the function we are currently compiling using internal APIs. + pub fn body_internal(&self) -> &'tcx InternalBody<'tcx> { self.mir } @@ -108,6 +104,11 @@ impl<'tcx> CurrentFnCtx<'tcx> { pub fn sig(&self) -> PolyFnSig<'tcx> { self.sig } + + /// The body of the function. + pub fn body(&self) -> &Body { + &self.body + } } /// Utility functions @@ -118,6 +119,6 @@ impl CurrentFnCtx<'_> { } pub fn find_label(&self, bb: &BasicBlock) -> String { - self.labels[bb.index()].clone() + format!("{bb:?}") } } 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 dd43c96f27ea..61861827df96 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs @@ -24,16 +24,18 @@ use cbmc::utils::aggr_tag; use cbmc::{InternedString, MachineModel}; use kani_metadata::HarnessMetadata; use rustc_data_structures::fx::FxHashMap; -use rustc_middle::mir::interpret::Allocation; use rustc_middle::span_bug; use rustc_middle::ty::layout::{ FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasParamEnv, HasTyCtxt, LayoutError, LayoutOfHelpers, TyAndLayout, }; use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; -use rustc_span::source_map::{respan, Span}; +use rustc_smir::rustc_internal; +use rustc_span::source_map::respan; +use rustc_span::Span; use rustc_target::abi::call::FnAbi; use rustc_target::abi::{HasDataLayout, TargetDataLayout}; +use stable_mir::ty::Allocation; pub struct GotocCtx<'tcx> { /// the typing context @@ -43,13 +45,13 @@ pub struct GotocCtx<'tcx> { pub queries: QueryDb, /// the generated symbol table for gotoc pub symbol_table: SymbolTable, - pub hooks: GotocHooks<'tcx>, + pub hooks: GotocHooks, /// the full crate name, including versioning info pub full_crate_name: String, /// a global counter for generating unique names for global variables pub global_var_count: u64, /// map a global allocation to a name in the symbol table - pub alloc_map: FxHashMap<&'tcx Allocation, String>, + pub alloc_map: FxHashMap, /// map (trait, method) pairs to possible implementations pub vtable_ctx: VtableCtx, pub current_fn: Option>, @@ -78,7 +80,7 @@ impl<'tcx> GotocCtx<'tcx> { ) -> GotocCtx<'tcx> { let fhks = fn_hooks(); let symbol_table = SymbolTable::new(machine_model.clone()); - let emit_vtable_restrictions = queries.emit_vtable_restrictions; + let emit_vtable_restrictions = queries.args().emit_vtable_restrictions; GotocCtx { tcx, queries, @@ -137,11 +139,6 @@ impl<'tcx> GotocCtx<'tcx> { self.gen_stack_variable(c, fname, "var", t, Location::none(), false) } - // Generate a Symbol Expression representing a function parameter from the MIR - pub fn gen_function_parameter(&mut self, c: u64, fname: &str, t: Type) -> Symbol { - self.gen_stack_variable(c, fname, "var", t, Location::none(), true) - } - /// Given a counter `c` a function name `fname, and a prefix `prefix`, generates a new function local variable /// It is an error to reuse an existing `c`, `fname` `prefix` tuple. fn gen_stack_variable( @@ -302,16 +299,7 @@ impl<'tcx> GotocCtx<'tcx> { /// Mutators impl<'tcx> GotocCtx<'tcx> { pub fn set_current_fn(&mut self, instance: Instance<'tcx>) { - self.current_fn = Some(CurrentFnCtx::new( - instance, - self, - self.tcx - .instance_mir(instance.def) - .basic_blocks - .indices() - .map(|bb| format!("{bb:?}")) - .collect(), - )); + self.current_fn = Some(CurrentFnCtx::new(rustc_internal::stable(instance), self)); } pub fn reset_current_fn(&mut self) { diff --git a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs index 5254394ccbc8..b914c46c0f6c 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs @@ -8,36 +8,37 @@ //! It would be too nasty if we spread around these sort of undocumented hooks in place, so //! this module addresses this issue. -use crate::codegen_cprover_gotoc::codegen::PropertyClass; +use crate::codegen_cprover_gotoc::codegen::{bb_label, PropertyClass}; use crate::codegen_cprover_gotoc::GotocCtx; use crate::unwrap_or_return_codegen_unimplemented_stmt; use cbmc::goto_program::{BuiltinFn, Expr, Location, Stmt, Type}; -use rustc_middle::mir::{BasicBlock, Place}; -use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::{Instance, TyCtxt}; -use rustc_span::Span; +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::Span, CrateDef}; use std::rc::Rc; use tracing::debug; -pub trait GotocHook<'tcx> { +pub trait GotocHook { /// if the hook applies, it means the codegen would do something special to it - fn hook_applies(&self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> bool; + fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool; /// the handler for codegen fn handle( &self, - tcx: &mut GotocCtx<'tcx>, - instance: Instance<'tcx>, + gcx: &mut GotocCtx, + instance: Instance, fargs: Vec, - assign_to: Place<'tcx>, - target: Option, - span: Option, + assign_to: Place, + target: Option, + span: Span, ) -> Stmt; } fn matches_function(tcx: TyCtxt, instance: Instance, attr_name: &str) -> bool { let attr_sym = rustc_span::symbol::Symbol::intern(attr_name); if let Some(attr_id) = tcx.all_diagnostic_items(()).name_to_id.get(&attr_sym) { - if instance.def.def_id() == *attr_id { + if rustc_internal::internal(instance.def.def_id()) == *attr_id { debug!("matched: {:?} {:?}", attr_id, attr_sym); return true; } @@ -54,34 +55,34 @@ fn matches_function(tcx: TyCtxt, instance: Instance, attr_name: &str) -> bool { /// /// for more details. struct Cover; -impl<'tcx> GotocHook<'tcx> for Cover { - fn hook_applies(&self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> bool { +impl GotocHook for Cover { + fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool { matches_function(tcx, instance, "KaniCover") } fn handle( &self, - tcx: &mut GotocCtx<'tcx>, - _instance: Instance<'tcx>, + gcx: &mut GotocCtx, + _instance: Instance, mut fargs: Vec, - _assign_to: Place<'tcx>, - target: Option, - span: Option, + _assign_to: Place, + target: Option, + span: Span, ) -> Stmt { assert_eq!(fargs.len(), 2); let cond = fargs.remove(0).cast_to(Type::bool()); let msg = fargs.remove(0); - let msg = tcx.extract_const_message(&msg).unwrap(); + let msg = gcx.extract_const_message(&msg).unwrap(); let target = target.unwrap(); - let caller_loc = tcx.codegen_caller_span(&span); + let caller_loc = gcx.codegen_caller_span_stable(span); - let (msg, reach_stmt) = tcx.codegen_reachability_check(msg, span); + let (msg, reach_stmt) = gcx.codegen_reachability_check(msg, span); Stmt::block( vec![ reach_stmt, - tcx.codegen_cover(cond, &msg, span), - Stmt::goto(tcx.current_fn().find_label(&target), caller_loc), + gcx.codegen_cover(cond, &msg, span), + Stmt::goto(bb_label(target), caller_loc), ], caller_loc, ) @@ -89,69 +90,63 @@ impl<'tcx> GotocHook<'tcx> for Cover { } struct Assume; -impl<'tcx> GotocHook<'tcx> for Assume { - fn hook_applies(&self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> bool { +impl GotocHook for Assume { + fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool { matches_function(tcx, instance, "KaniAssume") } fn handle( &self, - tcx: &mut GotocCtx<'tcx>, - _instance: Instance<'tcx>, + gcx: &mut GotocCtx, + _instance: Instance, mut fargs: Vec, - _assign_to: Place<'tcx>, - target: Option, - span: Option, + _assign_to: Place, + target: Option, + span: Span, ) -> Stmt { assert_eq!(fargs.len(), 1); let cond = fargs.remove(0).cast_to(Type::bool()); let target = target.unwrap(); - let loc = tcx.codegen_span_option(span); + let loc = gcx.codegen_span_stable(span); - Stmt::block( - vec![ - tcx.codegen_assume(cond, loc), - Stmt::goto(tcx.current_fn().find_label(&target), loc), - ], - loc, - ) + Stmt::block(vec![gcx.codegen_assume(cond, loc), Stmt::goto(bb_label(target), loc)], loc) } } struct Assert; -impl<'tcx> GotocHook<'tcx> for Assert { - fn hook_applies(&self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> bool { +impl GotocHook for Assert { + fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool { matches_function(tcx, instance, "KaniAssert") } fn handle( &self, - tcx: &mut GotocCtx<'tcx>, - _instance: Instance<'tcx>, + gcx: &mut GotocCtx, + _instance: Instance, mut fargs: Vec, - _assign_to: Place<'tcx>, - target: Option, - span: Option, + _assign_to: Place, + target: Option, + span: Span, ) -> Stmt { assert_eq!(fargs.len(), 2); let cond = fargs.remove(0).cast_to(Type::bool()); let msg = fargs.remove(0); - let msg = tcx.extract_const_message(&msg).unwrap(); + let msg = gcx.extract_const_message(&msg).unwrap(); let target = target.unwrap(); - let caller_loc = tcx.codegen_caller_span(&span); + let caller_loc = gcx.codegen_caller_span_stable(span); - let (msg, reach_stmt) = tcx.codegen_reachability_check(msg, span); + let (msg, reach_stmt) = gcx.codegen_reachability_check(msg, span); // Since `cond` might have side effects, assign it to a temporary // variable so that it's evaluated once, then assert and assume it // TODO: I don't think `cond` can have side effects, this is MIR, it's going to be temps - let (tmp, decl) = tcx.decl_temp_variable(cond.typ().clone(), Some(cond), caller_loc); + let (tmp, decl) = gcx.decl_temp_variable(cond.typ().clone(), Some(cond), caller_loc); Stmt::block( vec![ reach_stmt, decl, - tcx.codegen_assert_assume(tmp, PropertyClass::Assertion, &msg, caller_loc), - Stmt::goto(tcx.current_fn().find_label(&target), caller_loc), + gcx.codegen_assert_assume(tmp, PropertyClass::Assertion, &msg, caller_loc), + Stmt::goto(bb_label(target), caller_loc), ], caller_loc, ) @@ -160,34 +155,36 @@ impl<'tcx> GotocHook<'tcx> for Assert { struct Nondet; -impl<'tcx> GotocHook<'tcx> for Nondet { - fn hook_applies(&self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> bool { +impl GotocHook for Nondet { + fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool { matches_function(tcx, instance, "KaniAnyRaw") } fn handle( &self, - tcx: &mut GotocCtx<'tcx>, - _instance: Instance<'tcx>, + gcx: &mut GotocCtx, + _instance: Instance, fargs: Vec, - assign_to: Place<'tcx>, - target: Option, - span: Option, + assign_to: Place, + target: Option, + span: Span, ) -> Stmt { assert!(fargs.is_empty()); - let loc = tcx.codegen_span_option(span); + let loc = gcx.codegen_span_stable(span); let target = target.unwrap(); - let pt = tcx.place_ty(&assign_to); - if pt.is_unit() { - Stmt::goto(tcx.current_fn().find_label(&target), loc) + let pt = gcx.place_ty_stable(&assign_to); + if pt.kind().is_unit() { + Stmt::goto(bb_label(target), loc) } else { - let pe = - unwrap_or_return_codegen_unimplemented_stmt!(tcx, tcx.codegen_place(&assign_to)) - .goto_expr; + let pe = unwrap_or_return_codegen_unimplemented_stmt!( + gcx, + gcx.codegen_place_stable(&assign_to) + ) + .goto_expr; Stmt::block( vec![ - pe.assign(tcx.codegen_ty(pt).nondet(), loc), - Stmt::goto(tcx.current_fn().find_label(&target), loc), + pe.assign(gcx.codegen_ty_stable(pt).nondet(), loc), + Stmt::goto(bb_label(target), loc), ], loc, ) @@ -197,11 +194,11 @@ impl<'tcx> GotocHook<'tcx> for Nondet { struct Panic; -impl<'tcx> GotocHook<'tcx> for Panic { - fn hook_applies(&self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> bool { - let def_id = instance.def.def_id(); +impl GotocHook for Panic { + fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool { + let def_id = rustc_internal::internal(instance.def.def_id()); Some(def_id) == tcx.lang_items().panic_fn() - || Some(def_id) == tcx.lang_items().panic_display() + || tcx.has_attr(def_id, rustc_span::sym::rustc_const_panic_str) || Some(def_id) == tcx.lang_items().panic_fmt() || Some(def_id) == tcx.lang_items().begin_panic_fn() || matches_function(tcx, instance, "KaniPanic") @@ -209,50 +206,53 @@ impl<'tcx> GotocHook<'tcx> for Panic { fn handle( &self, - tcx: &mut GotocCtx<'tcx>, - _instance: Instance<'tcx>, + gcx: &mut GotocCtx, + _instance: Instance, fargs: Vec, - _assign_to: Place<'tcx>, - _target: Option, - span: Option, + _assign_to: Place, + _target: Option, + span: Span, ) -> Stmt { - tcx.codegen_panic(span, fargs) + gcx.codegen_panic(span, fargs) } } struct RustAlloc; // Removing this hook causes regression failures. // https://github.com/model-checking/kani/issues/1170 -impl<'tcx> GotocHook<'tcx> for RustAlloc { - fn hook_applies(&self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> bool { - let full_name = with_no_trimmed_paths!(tcx.def_path_str(instance.def_id())); +impl GotocHook for RustAlloc { + fn hook_applies(&self, _tcx: TyCtxt, instance: Instance) -> bool { + let full_name = instance.name(); full_name == "alloc::alloc::exchange_malloc" } fn handle( &self, - tcx: &mut GotocCtx<'tcx>, - instance: Instance<'tcx>, + gcx: &mut GotocCtx, + instance: Instance, mut fargs: Vec, - assign_to: Place<'tcx>, - target: Option, - span: Option, + assign_to: Place, + target: Option, + span: Span, ) -> Stmt { debug!(?instance, "Replace allocation"); - let loc = tcx.codegen_span_option(span); + let loc = gcx.codegen_span_stable(span); let target = target.unwrap(); let size = fargs.remove(0); Stmt::block( vec![ - unwrap_or_return_codegen_unimplemented_stmt!(tcx, tcx.codegen_place(&assign_to)) - .goto_expr - .assign( - BuiltinFn::Malloc - .call(vec![size], loc) - .cast_to(Type::unsigned_int(8).to_pointer()), - loc, - ), - Stmt::goto(tcx.current_fn().find_label(&target), Location::none()), + unwrap_or_return_codegen_unimplemented_stmt!( + gcx, + gcx.codegen_place_stable(&assign_to) + ) + .goto_expr + .assign( + BuiltinFn::Malloc + .call(vec![size], loc) + .cast_to(Type::unsigned_int(8).to_pointer()), + loc, + ), + Stmt::goto(bb_label(target), Location::none()), ], Location::none(), ) @@ -271,30 +271,30 @@ impl<'tcx> GotocHook<'tcx> for RustAlloc { /// ``` pub struct MemCmp; -impl<'tcx> GotocHook<'tcx> for MemCmp { - fn hook_applies(&self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> bool { - let name = with_no_trimmed_paths!(tcx.def_path_str(instance.def_id())); +impl GotocHook for MemCmp { + fn hook_applies(&self, _tcx: TyCtxt, instance: Instance) -> bool { + let name = instance.name(); name == "core::slice::cmp::memcmp" || name == "std::slice::cmp::memcmp" } fn handle( &self, - tcx: &mut GotocCtx<'tcx>, - instance: Instance<'tcx>, + gcx: &mut GotocCtx, + instance: Instance, mut fargs: Vec, - assign_to: Place<'tcx>, - target: Option, - span: Option, + assign_to: Place, + target: Option, + span: Span, ) -> Stmt { - let loc = tcx.codegen_span_option(span); + let loc = gcx.codegen_span_stable(span); let target = target.unwrap(); let first = fargs.remove(0); let second = fargs.remove(0); let count = fargs.remove(0); - let (count_var, count_decl) = tcx.decl_temp_variable(count.typ().clone(), Some(count), loc); - let (first_var, first_decl) = tcx.decl_temp_variable(first.typ().clone(), Some(first), loc); + let (count_var, count_decl) = gcx.decl_temp_variable(count.typ().clone(), Some(count), loc); + let (first_var, first_decl) = gcx.decl_temp_variable(first.typ().clone(), Some(first), loc); let (second_var, second_decl) = - tcx.decl_temp_variable(second.typ().clone(), Some(second), loc); + gcx.decl_temp_variable(second.typ().clone(), Some(second), loc); let is_count_zero = count_var.clone().is_zero(); // We have to ensure that the pointers are valid even if we're comparing zero bytes. // According to Rust's current definition (see https://github.com/model-checking/kani/issues/1489), @@ -304,28 +304,68 @@ impl<'tcx> GotocHook<'tcx> for MemCmp { let is_second_ok = second_var.clone().is_nonnull(); let should_skip_pointer_checks = is_count_zero.and(is_first_ok).and(is_second_ok); let place_expr = - unwrap_or_return_codegen_unimplemented_stmt!(tcx, tcx.codegen_place(&assign_to)) + unwrap_or_return_codegen_unimplemented_stmt!(gcx, gcx.codegen_place_stable(&assign_to)) .goto_expr; let rhs = should_skip_pointer_checks.ternary( Expr::int_constant(0, place_expr.typ().clone()), // zero bytes are always equal (as long as pointers are nonnull and aligned) - tcx.codegen_func_expr(instance, span.as_ref()) + gcx.codegen_func_expr(instance, Some(span)) .call(vec![first_var, second_var, count_var]), ); let code = place_expr.assign(rhs, loc).with_location(loc); Stmt::block( - vec![ - count_decl, - first_decl, - second_decl, - code, - Stmt::goto(tcx.current_fn().find_label(&target), loc), - ], + vec![count_decl, first_decl, second_decl, code, Stmt::goto(bb_label(target), loc)], loc, ) } } -pub fn fn_hooks<'tcx>() -> GotocHooks<'tcx> { +/// A builtin that is essentially a C-style dereference operation, creating an +/// unsafe shallow copy. Importantly either this copy or the original needs to +/// be `mem::forget`en or a double-free will occur. +/// +/// Takes in a `&T` reference and returns a `T` (like clone would but without +/// cloning). Breaks ownership rules and is only used in the context of function +/// contracts where we can structurally guarantee the use is safe. +struct UntrackedDeref; + +impl GotocHook for UntrackedDeref { + fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool { + matches_function(tcx, instance, "KaniUntrackedDeref") + } + + fn handle( + &self, + gcx: &mut GotocCtx, + _instance: Instance, + mut fargs: Vec, + assign_to: Place, + _target: Option, + span: Span, + ) -> Stmt { + assert_eq!( + fargs.len(), + 1, + "Invariant broken. `untracked_deref` should only be given one argument. \ + This function should only be called from code generated by kani macros, \ + as such this is likely a code-generation error." + ); + let loc = gcx.codegen_span_stable(span); + Stmt::block( + vec![Stmt::assign( + unwrap_or_return_codegen_unimplemented_stmt!( + gcx, + gcx.codegen_place_stable(&assign_to) + ) + .goto_expr, + fargs.pop().unwrap().dereference(), + loc, + )], + loc, + ) + } +} + +pub fn fn_hooks() -> GotocHooks { GotocHooks { hooks: vec![ Rc::new(Panic), @@ -335,20 +375,17 @@ pub fn fn_hooks<'tcx>() -> GotocHooks<'tcx> { Rc::new(Nondet), Rc::new(RustAlloc), Rc::new(MemCmp), + Rc::new(UntrackedDeref), ], } } -pub struct GotocHooks<'tcx> { - hooks: Vec + 'tcx>>, +pub struct GotocHooks { + hooks: Vec>, } -impl<'tcx> GotocHooks<'tcx> { - pub fn hook_applies( - &self, - tcx: TyCtxt<'tcx>, - instance: Instance<'tcx>, - ) -> Option + 'tcx>> { +impl GotocHooks { + pub fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> Option> { for h in &self.hooks { if h.hook_applies(tcx, instance) { return Some(h.clone()); diff --git a/kani-compiler/src/codegen_cprover_gotoc/utils/names.rs b/kani-compiler/src/codegen_cprover_gotoc/utils/names.rs index a2c3ea3518e9..587eef72a645 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/utils/names.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/utils/names.rs @@ -5,12 +5,12 @@ use crate::codegen_cprover_gotoc::GotocCtx; use cbmc::InternedString; -use rustc_hir::def_id::DefId; use rustc_hir::def_id::LOCAL_CRATE; use rustc_middle::mir::mono::CodegenUnitNameBuilder; use rustc_middle::mir::Local; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{Instance, TyCtxt}; +use stable_mir::mir::mono::Instance as InstanceStable; use tracing::debug; impl<'tcx> GotocCtx<'tcx> { @@ -22,7 +22,7 @@ impl<'tcx> GotocCtx<'tcx> { pub fn codegen_var_base_name(&self, l: &Local) -> String { match self.find_debug_info(l) { None => format!("var_{}", l.index()), - Some(info) => format!("{}", info.name), + Some(info) => info.name, } } @@ -54,9 +54,7 @@ impl<'tcx> GotocCtx<'tcx> { /// A human readable name in Rust for reference, should not be used as a key. pub fn readable_instance_name(&self, instance: Instance<'tcx>) -> String { - with_no_trimmed_paths!( - self.tcx.def_path_str_with_substs(instance.def_id(), instance.substs) - ) + with_no_trimmed_paths!(self.tcx.def_path_str_with_args(instance.def_id(), instance.args)) } /// The actual function name used in the symbol table @@ -77,6 +75,15 @@ impl<'tcx> GotocCtx<'tcx> { if pretty == "main" { pretty } else { llvm_mangled } } + /// Return the mangled name to be used in the symbol table. + /// + /// We special case main function in order to support `--function main`. + // TODO: Get rid of this: https://github.com/model-checking/kani/issues/2129 + pub fn symbol_name_stable(&self, instance: InstanceStable) -> String { + let pretty = instance.name(); + if pretty == "main" { pretty } else { instance.mangled_name() } + } + /// The name for a tuple field pub fn tuple_fld_name(n: usize) -> String { format!("{n}") @@ -85,12 +92,8 @@ impl<'tcx> GotocCtx<'tcx> { /// The name for the struct field on a vtable for a given function. Because generic /// functions can share the same name, we need to use the index of the entry in the /// vtable. This is the same index that will be passed in virtual function calls as - /// InstanceDef::Virtual(def_id, idx). We could use solely the index as a key into - /// the vtable struct, but we add the method name for debugging readability. - /// Example: 3_vol - pub fn vtable_field_name(&self, _def_id: DefId, idx: usize) -> InternedString { - // format!("{}_{}", idx, with_no_trimmed_paths!(|| self.tcx.item_name(def_id))) - // TODO: use def_id https://github.com/model-checking/kani/issues/364 + /// InstanceDef::Virtual(def_id, idx). + pub fn vtable_field_name(&self, idx: usize) -> InternedString { idx.to_string().into() } diff --git a/kani-compiler/src/codegen_cprover_gotoc/utils/utils.rs b/kani-compiler/src/codegen_cprover_gotoc/utils/utils.rs index f565c6b869f9..413d1cb12ca6 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/utils/utils.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/utils/utils.rs @@ -6,7 +6,7 @@ use crate::codegen_cprover_gotoc::GotocCtx; use cbmc::goto_program::{Expr, ExprValue, Location, SymbolTable, Type}; use cbmc::{btree_string_map, InternedString}; use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::{Instance, Ty}; +use rustc_middle::ty::Ty; use tracing::debug; // Should move into rvalue @@ -86,13 +86,6 @@ impl<'tcx> GotocCtx<'tcx> { /// Members traverse path to get to the raw pointer of a box (b.0.pointer.pointer). const RAW_PTR_FROM_BOX: [&str; 3] = ["0", "pointer", "pointer"]; -impl<'tcx> GotocCtx<'tcx> { - /// Given an "instance" find the crate it came from - pub fn get_crate(&self, instance: Instance<'tcx>) -> String { - self.tcx.crate_name(instance.def_id().krate).to_string() - } -} - impl<'tcx> GotocCtx<'tcx> { /// Dereference a boxed type `std::boxed::Box` to get a `*T`. /// diff --git a/kani-compiler/src/kani_compiler.rs b/kani-compiler/src/kani_compiler.rs index cdb17b8cd0ac..fc04b68ec747 100644 --- a/kani-compiler/src/kani_compiler.rs +++ b/kani-compiler/src/kani_compiler.rs @@ -15,6 +15,7 @@ //! in order to apply the stubs. For the subsequent runs, we add the stub configuration to //! `-C llvm-args`. +use crate::args::{Arguments, ReachabilityType}; #[cfg(feature = "cprover")] use crate::codegen_cprover_gotoc::GotocCodegenBackend; use crate::kani_middle::attributes::is_proof_harness; @@ -22,9 +23,9 @@ use crate::kani_middle::check_crate_items; use crate::kani_middle::metadata::gen_proof_metadata; use crate::kani_middle::reachability::filter_crate_items; use crate::kani_middle::stubbing::{self, harness_stub_map}; -use crate::kani_queries::{QueryDb, ReachabilityType}; -use crate::parser::{self, KaniCompilerParser}; +use crate::kani_queries::QueryDb; use crate::session::init_session; +use clap::Parser; use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata}; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_driver::{Callbacks, Compilation, RunCompiler}; @@ -33,11 +34,13 @@ use rustc_hir::definitions::DefPathHash; use rustc_interface::Config; use rustc_middle::ty::TyCtxt; use rustc_session::config::{ErrorOutputType, OutputType}; +use rustc_smir::rustc_internal; use rustc_span::ErrorGuaranteed; use std::collections::{BTreeMap, HashMap}; use std::fs::File; use std::io::BufWriter; use std::mem; +use std::path::{Path, PathBuf}; use std::process::ExitCode; use std::sync::{Arc, Mutex}; use tracing::debug; @@ -70,12 +73,21 @@ type HarnessId = DefPathHash; /// A set of stubs. type Stubs = BTreeMap; -#[derive(Debug)] +#[derive(Clone, Debug)] struct HarnessInfo { pub metadata: HarnessMetadata, pub stub_map: Stubs, } +/// Store some relevant information about the crate compilation. +#[derive(Clone, Debug)] +struct CrateInfo { + /// The name of the crate being compiled. + pub name: String, + /// The metadata output path that shall be generated as part of the crate compilation. + pub output_path: PathBuf, +} + /// Represents the current compilation stage. /// /// The Kani compiler may run the Rust compiler multiple times since stubbing has to be applied @@ -84,20 +96,28 @@ struct HarnessInfo { /// - We always start in the [CompilationStage::Init]. /// - After [CompilationStage::Init] we transition to either /// - [CompilationStage::CodegenNoStubs] on a regular crate compilation, this will follow Init. -/// - [CompilationStage::Done], running the compiler to gather information, such as `--version` -/// will skip code generation completely, and there is no work to be done. +/// - [CompilationStage::CompilationSkipped], running the compiler to gather information, such as +/// `--version` will skip code generation completely, and there is no work to be done. /// - After the [CompilationStage::CodegenNoStubs], we transition to either /// - [CompilationStage::CodegenWithStubs] when there is at least one harness with stubs. /// - [CompilationStage::Done] where there is no harness left to process. /// - The [CompilationStage::CodegenWithStubs] can last multiple Rust compiler runs. Once there is /// no harness left, we move to [CompilationStage::Done]. +/// - The final stages are either [CompilationStage::Done] or [CompilationStage::CompilationSkipped]. +/// - [CompilationStage::Done] represents the final state of the compiler after a successful +/// compilation. The crate metadata is stored here (even if no codegen was actually performed). +/// - [CompilationStage::CompilationSkipped] no compilation was actually performed. +/// No work needs to be done. +/// - Note: In a scenario where the compilation fails, the compiler will exit immediately, +/// independent on the stage. Any artifact produced shouldn't be used. /// I.e.: /// ```dot /// graph CompilationStage { -/// Init -> {CodegenNoStubs, Done} +/// Init -> {CodegenNoStubs, CompilationSkipped} /// CodegenNoStubs -> {CodegenStubs, Done} /// // Loop up to N harnesses times. /// CodegenStubs -> {CodegenStubs, Done} +/// CompilationSkipped /// Done /// } /// ``` @@ -107,11 +127,14 @@ enum CompilationStage { /// Initial state that the compiler is always instantiated with. /// In this stage, we initialize the Query and collect all harnesses. Init, + /// State where the compiler ran but didn't actually compile anything (e.g.: --version). + CompilationSkipped, /// Stage where the compiler will perform codegen of all harnesses that don't use stub. CodegenNoStubs { target_harnesses: Vec, next_harnesses: Vec>, all_harnesses: HashMap, + crate_info: CrateInfo, }, /// Stage where the compiler will codegen harnesses that use stub, one group at a time. /// The harnesses at this stage are grouped according to the stubs they are using. For now, @@ -120,18 +143,17 @@ enum CompilationStage { target_harnesses: Vec, next_harnesses: Vec>, all_harnesses: HashMap, + crate_info: CrateInfo, + }, + Done { + metadata: Option<(KaniMetadata, CrateInfo)>, }, - Done, } impl CompilationStage { pub fn is_init(&self) -> bool { matches!(self, CompilationStage::Init) } - - pub fn is_done(&self) -> bool { - matches!(self, CompilationStage::Done) - } } /// This object controls the compiler behavior. @@ -159,7 +181,7 @@ impl KaniCompiler { /// Since harnesses may have different attributes that affect compilation, Kani compiler can /// actually invoke the rust compiler multiple times. pub fn run(&mut self, orig_args: Vec) -> Result<(), ErrorGuaranteed> { - while !self.stage.is_done() { + loop { debug!(next=?self.stage, "run"); match &self.stage { CompilationStage::Init => { @@ -177,14 +199,28 @@ impl KaniCompiler { args.push(extra_arg); self.run_compilation_session(&args)?; } - CompilationStage::Done => { - unreachable!("There's nothing to be done here.") + CompilationStage::Done { metadata: Some((kani_metadata, crate_info)) } => { + // Only store metadata for harnesses for now. + // TODO: This should only skip None. + // https://github.com/model-checking/kani/issues/2493 + if self.queries.lock().unwrap().args().reachability_analysis + == ReachabilityType::Harnesses + { + // Store metadata file. + // We delay storing the metadata so we can include information collected + // during codegen. + self.store_metadata(&kani_metadata, &crate_info.output_path); + } + return Ok(()); + } + CompilationStage::Done { metadata: None } + | CompilationStage::CompilationSkipped => { + return Ok(()); } }; self.next_stage(); } - Ok(()) } /// Set up the next compilation stage after a `rustc` run. @@ -192,22 +228,35 @@ impl KaniCompiler { self.stage = match &mut self.stage { CompilationStage::Init => { // This may occur when user passes arguments like --version or --help. - CompilationStage::Done + CompilationStage::Done { metadata: None } + } + CompilationStage::CodegenNoStubs { + next_harnesses, all_harnesses, crate_info, .. } - CompilationStage::CodegenNoStubs { next_harnesses, all_harnesses, .. } - | CompilationStage::CodegenWithStubs { next_harnesses, all_harnesses, .. } => { + | CompilationStage::CodegenWithStubs { + next_harnesses, + all_harnesses, + crate_info, + .. + } => { if let Some(target_harnesses) = next_harnesses.pop() { assert!(!target_harnesses.is_empty(), "expected at least one target harness"); CompilationStage::CodegenWithStubs { target_harnesses, next_harnesses: mem::take(next_harnesses), all_harnesses: mem::take(all_harnesses), + crate_info: crate_info.clone(), } } else { - CompilationStage::Done + CompilationStage::Done { + metadata: Some(( + generate_metadata(&crate_info, all_harnesses), + crate_info.clone(), + )), + } } } - CompilationStage::Done => { + CompilationStage::Done { .. } | CompilationStage::CompilationSkipped => { unreachable!() } }; @@ -224,7 +273,12 @@ impl KaniCompiler { /// Gather and process all harnesses from this crate that shall be compiled. fn process_harnesses(&self, tcx: TyCtxt) -> CompilationStage { - if self.queries.lock().unwrap().reachability_analysis == ReachabilityType::Harnesses { + let crate_info = CrateInfo { + name: tcx.crate_name(LOCAL_CRATE).as_str().into(), + output_path: metadata_output_path(tcx), + }; + if self.queries.lock().unwrap().args().reachability_analysis == ReachabilityType::Harnesses + { let base_filename = tcx.output_filenames(()).output_path(OutputType::Object); let harnesses = filter_crate_items(tcx, |_, def_id| is_proof_harness(tcx, def_id)); let all_harnesses = harnesses @@ -239,7 +293,7 @@ impl KaniCompiler { .collect::>(); let (no_stubs, with_stubs): (Vec<_>, Vec<_>) = - if self.queries.lock().unwrap().stubbing_enabled { + if self.queries.lock().unwrap().args().stubbing_enabled { // Partition harnesses that don't have stub with the ones with stub. all_harnesses .keys() @@ -249,14 +303,13 @@ impl KaniCompiler { // Generate code without stubs. (all_harnesses.keys().cloned().collect(), vec![]) }; - // Store metadata file. - self.store_metadata(tcx, &all_harnesses); - // Even if no_stubs is empty we still need to store metadata. + // Even if no_stubs is empty we still need to store rustc metadata. CompilationStage::CodegenNoStubs { target_harnesses: no_stubs, next_harnesses: group_by_stubs(with_stubs, &all_harnesses), all_harnesses, + crate_info, } } else { // Leave other reachability type handling as is for now. @@ -264,6 +317,7 @@ impl KaniCompiler { target_harnesses: vec![], next_harnesses: vec![], all_harnesses: HashMap::default(), + crate_info, } } } @@ -290,29 +344,18 @@ impl KaniCompiler { .collect(); Compilation::Continue } - CompilationStage::Init | CompilationStage::Done => unreachable!(), + CompilationStage::Init + | CompilationStage::Done { .. } + | CompilationStage::CompilationSkipped => unreachable!(), } } /// Write the metadata to a file - fn store_metadata(&self, tcx: TyCtxt, all_harnesses: &HashMap) { - let (proof_harnesses, test_harnesses) = all_harnesses - .values() - .map(|info| &info.metadata) - .cloned() - .partition(|md| md.attributes.proof); - let metadata = KaniMetadata { - crate_name: tcx.crate_name(LOCAL_CRATE).as_str().into(), - proof_harnesses, - unsupported_features: vec![], - test_harnesses, - }; - let mut filename = tcx.output_filenames(()).output_path(OutputType::Object); - filename.set_extension(ArtifactType::Metadata); + fn store_metadata(&self, metadata: &KaniMetadata, filename: &Path) { debug!(?filename, "write_metadata"); - let out_file = File::create(&filename).unwrap(); + let out_file = File::create(filename).unwrap(); let writer = BufWriter::new(out_file); - if self.queries.lock().unwrap().output_pretty_json { + if self.queries.lock().unwrap().args().output_pretty_json { serde_json::to_writer_pretty(writer, &metadata).unwrap(); } else { serde_json::to_writer(writer, &metadata).unwrap(); @@ -339,30 +382,13 @@ impl Callbacks for KaniCompiler { if self.stage.is_init() { let mut args = vec!["kani-compiler".to_string()]; args.extend(config.opts.cg.llvm_args.iter().cloned()); - let matches = parser::parser().get_matches_from(&args); - init_session( - &matches, - matches!(config.opts.error_format, ErrorOutputType::Json { .. }), - ); + let args = Arguments::parse_from(args); + init_session(&args, matches!(config.opts.error_format, ErrorOutputType::Json { .. })); // Configure queries. let queries = &mut (*self.queries.lock().unwrap()); - queries.emit_vtable_restrictions = matches.get_flag(parser::RESTRICT_FN_PTRS); - queries.check_assertion_reachability = matches.get_flag(parser::ASSERTION_REACH_CHECKS); - queries.output_pretty_json = matches.get_flag(parser::PRETTY_OUTPUT_FILES); - queries.ignore_global_asm = matches.get_flag(parser::IGNORE_GLOBAL_ASM); - queries.write_json_symtab = - cfg!(feature = "write_json_symtab") || matches.get_flag(parser::WRITE_JSON_SYMTAB); - queries.reachability_analysis = matches.reachability_type(); - - if let Some(features) = matches.get_many::(parser::UNSTABLE_FEATURE) { - queries.unstable_features = features.cloned().collect::>(); - } - if matches.get_flag(parser::ENABLE_STUBBING) - && queries.reachability_analysis == ReachabilityType::Harnesses - { - queries.stubbing_enabled = true; - } + queries.set_args(args); + debug!(?queries, "config end"); } } @@ -375,19 +401,46 @@ impl Callbacks for KaniCompiler { ) -> Compilation { if self.stage.is_init() { self.stage = rustc_queries.global_ctxt().unwrap().enter(|tcx| { - check_crate_items(tcx, self.queries.lock().unwrap().ignore_global_asm); - self.process_harnesses(tcx) - }); + rustc_internal::run(tcx, || { + check_crate_items(tcx, self.queries.lock().unwrap().args().ignore_global_asm); + self.process_harnesses(tcx) + }) + .unwrap() + }) } self.prepare_codegen() } } +/// Generate [KaniMetadata] for the target crate. +fn generate_metadata( + crate_info: &CrateInfo, + all_harnesses: &HashMap, +) -> KaniMetadata { + let (proof_harnesses, test_harnesses) = all_harnesses + .values() + .map(|info| &info.metadata) + .cloned() + .partition(|md| md.attributes.proof); + KaniMetadata { + crate_name: crate_info.name.clone(), + proof_harnesses, + unsupported_features: vec![], + test_harnesses, + } +} + +/// Extract the filename for the metadata file. +fn metadata_output_path(tcx: TyCtxt) -> PathBuf { + let mut filename = tcx.output_filenames(()).output_path(OutputType::Object); + filename.set_extension(ArtifactType::Metadata); + filename +} + #[cfg(test)] mod tests { - use super::{HarnessInfo, Stubs}; - use crate::kani_compiler::{group_by_stubs, HarnessId}; + use super::*; use kani_metadata::{HarnessAttributes, HarnessMetadata}; use rustc_data_structures::fingerprint::Fingerprint; use rustc_hir::definitions::DefPathHash; @@ -400,12 +453,12 @@ mod tests { DefPathHash(Fingerprint::new(id, 0)) } - fn mock_metadata() -> HarnessMetadata { + fn mock_metadata(name: String, krate: String) -> HarnessMetadata { HarnessMetadata { - pretty_name: String::from("dummy"), - mangled_name: String::from("dummy"), - crate_name: String::from("dummy"), - original_file: String::from("dummy"), + pretty_name: name.clone(), + mangled_name: name.clone(), + original_file: format!("{}.rs", krate), + crate_name: krate, original_start_line: 10, original_end_line: 20, goto_file: None, @@ -414,7 +467,7 @@ mod tests { } fn mock_info_with_stubs(stub_map: Stubs) -> HarnessInfo { - HarnessInfo { metadata: mock_metadata(), stub_map } + HarnessInfo { metadata: mock_metadata("dummy".to_string(), "crate".to_string()), stub_map } } #[test] @@ -454,4 +507,67 @@ mod tests { ); assert!(grouped.contains(&vec![harness_2])); } + + #[test] + fn test_generate_metadata() { + // Mock inputs. + let name = "my_crate".to_string(); + let crate_info = CrateInfo { name: name.clone(), output_path: PathBuf::default() }; + + let mut info = mock_info_with_stubs(Stubs::default()); + info.metadata.attributes.proof = true; + let id = mock_next_id(); + let all_harnesses = HashMap::from([(id, info.clone())]); + + // Call generate metadata. + let metadata = generate_metadata(&crate_info, &all_harnesses); + + // Check output. + assert_eq!(metadata.crate_name, name); + assert_eq!(metadata.proof_harnesses.len(), 1); + assert_eq!(*metadata.proof_harnesses.first().unwrap(), info.metadata); + } + + #[test] + fn test_generate_empty_metadata() { + // Mock inputs. + let name = "my_crate".to_string(); + let crate_info = CrateInfo { name: name.clone(), output_path: PathBuf::default() }; + let all_harnesses = HashMap::new(); + + // Call generate metadata. + let metadata = generate_metadata(&crate_info, &all_harnesses); + + // Check output. + assert_eq!(metadata.crate_name, name); + assert_eq!(metadata.proof_harnesses.len(), 0); + } + + #[test] + fn test_generate_metadata_with_multiple_harness() { + // Mock inputs. + let krate = "my_crate".to_string(); + let crate_info = CrateInfo { name: krate.clone(), output_path: PathBuf::default() }; + + let harnesses = ["h1", "h2", "h3"]; + let infos = harnesses.map(|harness| { + let mut metadata = mock_metadata(harness.to_string(), krate.clone()); + metadata.attributes.proof = true; + (mock_next_id(), HarnessInfo { stub_map: Stubs::default(), metadata }) + }); + let all_harnesses = HashMap::from(infos.clone()); + + // Call generate metadata. + let metadata = generate_metadata(&crate_info, &all_harnesses); + + // Check output. + assert_eq!(metadata.crate_name, krate); + assert_eq!(metadata.proof_harnesses.len(), infos.len()); + assert!( + metadata + .proof_harnesses + .iter() + .all(|harness| harnesses.contains(&&*harness.pretty_name)) + ); + } } diff --git a/kani-compiler/src/kani_middle/analysis.rs b/kani-compiler/src/kani_middle/analysis.rs index ee0b8e769bc7..b23fb99f83ff 100644 --- a/kani-compiler/src/kani_middle/analysis.rs +++ b/kani-compiler/src/kani_middle/analysis.rs @@ -7,10 +7,13 @@ //! This module will perform all the analyses requested. Callers are responsible for selecting //! when the cost of these analyses are worth it. -use rustc_middle::mir::mono::MonoItem; -use rustc_middle::mir::visit::Visitor as MirVisitor; -use rustc_middle::mir::{Location, Rvalue, Statement, StatementKind, Terminator, TerminatorKind}; +use rustc_middle::mir::mono::MonoItem as InternalMonoItem; use rustc_middle::ty::TyCtxt; +use rustc_smir::rustc_internal; +use stable_mir::mir::mono::MonoItem; +use stable_mir::mir::{ + visit::Location, MirVisitor, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, +}; use std::collections::HashMap; use std::fmt::Display; @@ -20,19 +23,14 @@ use std::fmt::Display; /// - Number of items per type (Function / Constant / Shims) /// - Number of instructions per type. /// - Total number of MIR instructions. -pub fn print_stats<'tcx>(tcx: TyCtxt<'tcx>, items: &[MonoItem<'tcx>]) { +pub fn print_stats<'tcx>(_tcx: TyCtxt<'tcx>, items: &[InternalMonoItem<'tcx>]) { + let items: Vec = items.iter().map(rustc_internal::stable).collect(); let item_types = items.iter().collect::(); let visitor = items .iter() - .filter_map(|&mono| { - if let MonoItem::Fn(instance) = mono { - Some(tcx.instance_mir(instance.def)) - } else { - None - } - }) + .filter_map(|mono| if let MonoItem::Fn(instance) = mono { Some(instance) } else { None }) .fold(StatsVisitor::default(), |mut visitor, body| { - visitor.visit_body(body); + visitor.visit_body(&body.body().unwrap()); visitor }); eprintln!("====== Reachability Analysis Result ======="); @@ -54,20 +52,20 @@ struct StatsVisitor { exprs: Counter, } -impl<'tcx> MirVisitor<'tcx> for StatsVisitor { - fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { +impl MirVisitor for StatsVisitor { + fn visit_statement(&mut self, statement: &Statement, location: Location) { self.stmts.add(statement); // Also visit the type of expression. self.super_statement(statement, location); } - fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, _location: Location) { + fn visit_terminator(&mut self, terminator: &Terminator, _location: Location) { self.stmts.add(terminator); // Stop here since we don't care today about the information inside the terminator. // self.super_terminator(terminator, location); } - fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, _location: Location) { + fn visit_rvalue(&mut self, rvalue: &Rvalue, _location: Location) { self.exprs.add(rvalue); // Stop here since we don't care today about the information inside the rvalue. // self.super_rvalue(rvalue, location); @@ -115,8 +113,8 @@ impl> FromIterator for Counter { #[derive(Debug, Eq, Hash, PartialEq)] struct Key(pub &'static str); -impl<'tcx> From<&MonoItem<'tcx>> for Key { - fn from(value: &MonoItem) -> Self { +impl From<&MonoItem> for Key { + fn from(value: &stable_mir::mir::mono::MonoItem) -> Self { match value { MonoItem::Fn(_) => Key("function"), MonoItem::GlobalAsm(_) => Key("global assembly"), @@ -125,18 +123,18 @@ impl<'tcx> From<&MonoItem<'tcx>> for Key { } } -impl<'tcx> From<&Statement<'tcx>> for Key { - fn from(value: &Statement<'tcx>) -> Self { +impl From<&Statement> for Key { + fn from(value: &Statement) -> Self { match value.kind { - StatementKind::Assign(_) => Key("Assign"), + StatementKind::Assign(..) => Key("Assign"), StatementKind::Deinit(_) => Key("Deinit"), StatementKind::Intrinsic(_) => Key("Intrinsic"), StatementKind::SetDiscriminant { .. } => Key("SetDiscriminant"), // For now, we don't care about the ones below. - StatementKind::AscribeUserType(_, _) + StatementKind::AscribeUserType { .. } | StatementKind::Coverage(_) | StatementKind::ConstEvalCounter - | StatementKind::FakeRead(_) + | StatementKind::FakeRead(..) | StatementKind::Nop | StatementKind::PlaceMention(_) | StatementKind::Retag(_, _) @@ -146,29 +144,25 @@ impl<'tcx> From<&Statement<'tcx>> for Key { } } -impl<'tcx> From<&Terminator<'tcx>> for Key { - fn from(value: &Terminator<'tcx>) -> Self { +impl From<&Terminator> for Key { + fn from(value: &Terminator) -> Self { match value.kind { + TerminatorKind::Abort => Key("Abort"), TerminatorKind::Assert { .. } => Key("Assert"), TerminatorKind::Call { .. } => Key("Call"), TerminatorKind::Drop { .. } => Key("Drop"), - TerminatorKind::GeneratorDrop => Key("GeneratorDrop"), TerminatorKind::Goto { .. } => Key("Goto"), - TerminatorKind::FalseEdge { .. } => Key("FalseEdge"), - TerminatorKind::FalseUnwind { .. } => Key("FalseUnwind"), TerminatorKind::InlineAsm { .. } => Key("InlineAsm"), - TerminatorKind::Resume => Key("Resume"), + TerminatorKind::Resume { .. } => Key("Resume"), TerminatorKind::Return => Key("Return"), TerminatorKind::SwitchInt { .. } => Key("SwitchInt"), - TerminatorKind::Terminate => Key("Terminate"), TerminatorKind::Unreachable => Key("Unreachable"), - TerminatorKind::Yield { .. } => Key("Yield"), } } } -impl<'tcx> From<&Rvalue<'tcx>> for Key { - fn from(value: &Rvalue<'tcx>) -> Self { +impl From<&Rvalue> for Key { + fn from(value: &Rvalue) -> Self { match value { Rvalue::Use(_) => Key("Use"), Rvalue::Repeat(_, _) => Key("Repeat"), @@ -177,8 +171,8 @@ impl<'tcx> From<&Rvalue<'tcx>> for Key { Rvalue::AddressOf(_, _) => Key("AddressOf"), Rvalue::Len(_) => Key("Len"), Rvalue::Cast(_, _, _) => Key("Cast"), - Rvalue::BinaryOp(_, _) => Key("BinaryOp"), - Rvalue::CheckedBinaryOp(_, _) => Key("CheckedBinaryOp"), + Rvalue::BinaryOp(..) => Key("BinaryOp"), + Rvalue::CheckedBinaryOp(..) => Key("CheckedBinaryOp"), Rvalue::NullaryOp(_, _) => Key("NullaryOp"), Rvalue::UnaryOp(_, _) => Key("UnaryOp"), Rvalue::Discriminant(_) => Key("Discriminant"), diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index 6ee5136e12e5..2c9c6d7ee54c 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -5,17 +5,21 @@ use std::collections::BTreeMap; use kani_metadata::{CbmcSolver, HarnessAttributes, Stub}; -use rustc_ast::{attr, AttrKind, Attribute, LitKind, MetaItem, MetaItemKind, NestedMetaItem}; +use rustc_ast::{ + attr, AttrArgs, AttrArgsEq, AttrKind, Attribute, ExprKind, LitKind, MetaItem, MetaItemKind, + NestedMetaItem, +}; use rustc_errors::ErrorGuaranteed; use rustc_hir::{def::DefKind, def_id::DefId}; use rustc_middle::ty::{Instance, TyCtxt, TyKind}; -use rustc_span::Span; +use rustc_session::Session; +use rustc_span::{Span, Symbol}; use std::str::FromStr; use strum_macros::{AsRefStr, EnumString}; use tracing::{debug, trace}; -use super::resolve; +use super::resolve::{self, resolve_fn, ResolveError}; #[derive(Debug, Clone, Copy, AsRefStr, EnumString, PartialEq, Eq, PartialOrd, Ord)] #[strum(serialize_all = "snake_case")] @@ -27,6 +31,23 @@ enum KaniAttributeKind { /// Attribute used to mark unstable APIs. Unstable, Unwind, + /// A sound [`Self::Stub`] that replaces a function by a stub generated from + /// its contract. + StubVerified, + /// A harness, similar to [`Self::Proof`], but for checking a function + /// contract, e.g. the contract check is substituted for the target function + /// before the the verification runs. + ProofForContract, + /// Attribute on a function with a contract that identifies the code + /// implementing the check for this contract. + CheckedWith, + /// Internal attribute of the contracts implementation that identifies the + /// name of the function which was generated as the sound stub from the + /// contract of this function. + ReplacedWith, + /// Attribute on a function that was auto-generated from expanding a + /// function contract. + IsContractGenerated, } impl KaniAttributeKind { @@ -37,66 +58,463 @@ impl KaniAttributeKind { | KaniAttributeKind::ShouldPanic | KaniAttributeKind::Solver | KaniAttributeKind::Stub + | KaniAttributeKind::ProofForContract + | KaniAttributeKind::StubVerified | KaniAttributeKind::Unwind => true, - KaniAttributeKind::Unstable => false, + KaniAttributeKind::Unstable + | KaniAttributeKind::ReplacedWith + | KaniAttributeKind::CheckedWith + | KaniAttributeKind::IsContractGenerated => false, } } + + /// Is this an "active" function contract attribute? This means it is + /// part of the function contract interface *and* it implies that a contract + /// will be used (stubbed or checked) in some way, thus requiring that the + /// user activate the unstable feature. + /// + /// If we find an "inactive" contract attribute we chose not to error, + /// because it wouldn't have any effect anyway. + pub fn demands_function_contract_use(self) -> bool { + matches!(self, KaniAttributeKind::ProofForContract) + } + + /// Would this attribute be placed on a function as part of a function + /// contract. E.g. created by `requires`, `ensures`. + pub fn is_function_contract(self) -> bool { + use KaniAttributeKind::*; + matches!(self, CheckedWith | IsContractGenerated) + } +} + +/// Bundles together common data used when evaluating the attributes of a given +/// function. +#[derive(Clone)] +pub struct KaniAttributes<'tcx> { + /// Rustc type context/queries + tcx: TyCtxt<'tcx>, + /// The function which these attributes decorate. + item: DefId, + /// All attributes we found in raw format. + map: BTreeMap>, } -/// Check that all attributes assigned to an item is valid. -/// Errors will be added to the session. Invoke self.tcx.sess.abort_if_errors() to terminate -/// the session and emit all errors found. -pub(super) fn check_attributes(tcx: TyCtxt, def_id: DefId) { - let attributes = extract_kani_attributes(tcx, def_id); +impl<'tcx> std::fmt::Debug for KaniAttributes<'tcx> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KaniAttributes") + .field("item", &self.tcx.def_path_debug_str(self.item)) + .field("map", &self.map) + .finish() + } +} - // Check that all attributes are correctly used and well formed. - let is_harness = attributes.contains_key(&KaniAttributeKind::Proof); - for (kind, attrs) in attributes { - if !is_harness && kind.is_harness_only() { - tcx.sess.span_err( - attrs[0].span, - format!( +impl<'tcx> KaniAttributes<'tcx> { + /// Perform preliminary parsing and checking for the attributes on this + /// function + pub fn for_item(tcx: TyCtxt<'tcx>, def_id: DefId) -> Self { + let all_attributes = tcx.get_attrs_unchecked(def_id); + let map = all_attributes.iter().fold( + >>::default(), + |mut result, attribute| { + // Get the string the appears after "kanitool::" in each attribute string. + // Ex - "proof" | "unwind" etc. + if let Some(kind) = attr_kind(tcx, attribute) { + result.entry(kind).or_default().push(attribute) + } + result + }, + ); + Self { map, tcx, item: def_id } + } + + /// Expect that at most one attribute of this kind exists on the function + /// and return it. + fn expect_maybe_one(&self, kind: KaniAttributeKind) -> Option<&'tcx Attribute> { + match self.map.get(&kind)?.as_slice() { + [one] => Some(one), + _ => { + self.tcx.sess.err(format!( + "Too many {} attributes on {}, expected 0 or 1", + kind.as_ref(), + self.tcx.def_path_debug_str(self.item) + )); + None + } + } + } + + /// Parse, extract and resolve the target of `stub_verified(TARGET)`. The + /// returned `Symbol` and `DefId` are respectively the name and id of + /// `TARGET`. The `Span` is that of the contents of the attribute and used + /// for error reporting. + fn interpret_stub_verified_attribute( + &self, + ) -> Vec> { + self.map + .get(&KaniAttributeKind::StubVerified) + .map_or([].as_slice(), Vec::as_slice) + .iter() + .map(|attr| { + let name = expect_key_string_value(self.tcx.sess, attr)?; + let ok = self.resolve_sibling(name.as_str()).map_err(|e| { + self.tcx.sess.span_err( + attr.span, + format!("Failed to resolve replacement function {}: {e}", name.as_str()), + ) + })?; + Ok((name, ok, attr.span)) + }) + .collect() + } + + /// Parse and extract the `proof_for_contract(TARGET)` attribute. The + /// returned symbol and DefId are respectively the name and id of `TARGET`, + /// the span in the span for the attribute (contents). + fn interpret_the_for_contract_attribute( + &self, + ) -> Option> { + self.expect_maybe_one(KaniAttributeKind::ProofForContract).map(|target| { + let name = expect_key_string_value(self.tcx.sess, target)?; + self.resolve_sibling(name.as_str()).map(|ok| (name, ok, target.span)).map_err( + |resolve_err| { + self.tcx.sess.span_err( + target.span, + format!( + "Failed to resolve checking function {} because {resolve_err}", + name.as_str() + ), + ) + }, + ) + }) + } + + /// Extract the name of the sibling function this function's contract is + /// checked with (if any). + /// + /// `None` indicates this function does not use a contract, `Some(Err(_))` + /// indicates a contract does exist but an error occurred during resolution. + pub fn checked_with(&self) -> Option> { + self.expect_maybe_one(KaniAttributeKind::CheckedWith) + .map(|target| expect_key_string_value(self.tcx.sess, target)) + } + + /// Extract the name of the sibling function this function's contract is + /// stubbed as (if any). + /// + /// `None` indicates this function does not use a contract, `Some(Err(_))` + /// indicates a contract does exist but an error occurred during resolution. + pub fn replaced_with(&self) -> Option> { + self.expect_maybe_one(KaniAttributeKind::ReplacedWith) + .map(|target| expect_key_string_value(self.tcx.sess, target)) + } + + /// Resolve a function that is known to reside in the same module as the one + /// these attributes belong to (`self.item`). + fn resolve_sibling(&self, path_str: &str) -> Result> { + resolve_fn( + self.tcx, + self.tcx.parent_module_from_def_id(self.item.expect_local()).to_local_def_id(), + path_str, + ) + } + + /// Check that all attributes assigned to an item is valid. + /// Errors will be added to the session. Invoke self.tcx.sess.abort_if_errors() to terminate + /// the session and emit all errors found. + pub(super) fn check_attributes(&self) { + // Check that all attributes are correctly used and well formed. + let is_harness = self.is_harness(); + for (&kind, attrs) in self.map.iter() { + let local_error = |msg| self.tcx.sess.span_err(attrs[0].span, msg); + + if !is_harness && kind.is_harness_only() { + local_error(format!( "the `{}` attribute also requires the `#[kani::proof]` attribute", kind.as_ref() - ), - ); - } - match kind { - KaniAttributeKind::ShouldPanic => { - expect_single(tcx, kind, &attrs); - attrs.iter().for_each(|attr| { - expect_no_args(tcx, kind, attr); - }) - } - KaniAttributeKind::Solver => { - expect_single(tcx, kind, &attrs); - attrs.iter().for_each(|attr| { - parse_solver(tcx, attr); - }) + )); } - KaniAttributeKind::Stub => { - parse_stubs(tcx, def_id, attrs); + match kind { + KaniAttributeKind::ShouldPanic => { + 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); + attrs.iter().for_each(|attr| { + parse_solver(self.tcx, attr); + }) + } + KaniAttributeKind::Stub => { + parse_stubs(self.tcx, self.item, attrs); + } + KaniAttributeKind::Unwind => { + expect_single(self.tcx, kind, &attrs); + attrs.iter().for_each(|attr| { + parse_unwind(self.tcx, attr); + }) + } + KaniAttributeKind::Proof => { + if self.map.contains_key(&KaniAttributeKind::ProofForContract) { + local_error( + "`proof` and `proof_for_contract` may not be used on the same function.".to_string(), + ); + } + expect_single(self.tcx, kind, &attrs); + attrs.iter().for_each(|attr| self.check_proof_attribute(attr)) + } + KaniAttributeKind::Unstable => attrs.iter().for_each(|attr| { + let _ = UnstableAttribute::try_from(*attr).map_err(|err| err.report(self.tcx)); + }), + KaniAttributeKind::ProofForContract => { + if self.map.contains_key(&KaniAttributeKind::Proof) { + local_error( + "`proof` and `proof_for_contract` may not be used on the same function.".to_string(), + ); + } + expect_single(self.tcx, kind, &attrs); + } + KaniAttributeKind::StubVerified => { + expect_single(self.tcx, kind, &attrs); + } + KaniAttributeKind::CheckedWith | KaniAttributeKind::ReplacedWith => { + self.expect_maybe_one(kind) + .map(|attr| expect_key_string_value(&self.tcx.sess, attr)); + } + KaniAttributeKind::IsContractGenerated => { + // Ignored here because this is only used by the proc macros + // to communicate with one another. So by the time it gets + // here we don't care if it's valid or not. + } } - KaniAttributeKind::Unwind => { - expect_single(tcx, kind, &attrs); - attrs.iter().for_each(|attr| { - parse_unwind(tcx, attr); - }) + } + } + + /// Check that any unstable API has been enabled. Otherwise, emit an error. + /// + /// TODO: Improve error message by printing the span of the harness instead of the definition. + pub fn check_unstable_features(&self, enabled_features: &[String]) { + if !matches!(self.tcx.type_of(self.item).skip_binder().kind(), TyKind::FnDef(..)) { + // Skip closures since it shouldn't be possible to add an unstable attribute to them. + // We have to explicitly skip them though due to an issue with rustc: + // https://github.com/model-checking/kani/pull/2406#issuecomment-1534333862 + return; + } + + // If the `function-contracts` unstable feature is not enabled then no + // function should use any of those APIs. + if !enabled_features.iter().any(|feature| feature == "function-contracts") { + for kind in self.map.keys().copied().filter(|a| a.demands_function_contract_use()) { + let msg = format!( + "Using the {} attribute requires activating the unstable `function-contracts` feature", + kind.as_ref() + ); + if let Some(attr) = self.map.get(&kind).unwrap().first() { + self.tcx.sess.span_err(attr.span, msg); + } else { + self.tcx.sess.err(msg); + } } - KaniAttributeKind::Proof => { - expect_single(tcx, kind, &attrs); - attrs.iter().for_each(|attr| check_proof_attribute(tcx, def_id, attr)) + } + + if let Some(unstable_attrs) = self.map.get(&KaniAttributeKind::Unstable) { + for attr in unstable_attrs { + let unstable_attr = UnstableAttribute::try_from(*attr).unwrap(); + if !enabled_features.contains(&unstable_attr.feature) { + // Reached an unstable attribute that was not enabled. + self.report_unstable_forbidden(&unstable_attr); + } else { + debug!(enabled=?attr, def_id=?self.item, "check_unstable_features"); + } } - KaniAttributeKind::Unstable => attrs.iter().for_each(|attr| { - let _ = UnstableAttribute::try_from(*attr).map_err(|err| err.report(tcx)); - }), + } + } + + /// Report misusage of an unstable feature that was not enabled. + fn report_unstable_forbidden(&self, unstable_attr: &UnstableAttribute) -> ErrorGuaranteed { + let fn_name = self.tcx.def_path_str(self.item); + self.tcx + .sess + .struct_err(format!( + "Use of unstable feature `{}`: {}", + unstable_attr.feature, unstable_attr.reason + )) + .span_note( + self.tcx.def_span(self.item), + format!("the function `{fn_name}` is unstable:"), + ) + .note(format!("see issue {} for more information", unstable_attr.issue)) + .help(format!("use `-Z {}` to enable using this function.", unstable_attr.feature)) + .emit() + } + + /// Is this item a harness? (either `proof` or `proof_for_contract` + /// attribute are present) + fn is_harness(&self) -> bool { + self.map.contains_key(&KaniAttributeKind::Proof) + || self.map.contains_key(&KaniAttributeKind::ProofForContract) + } + + /// Extract harness attributes for a given `def_id`. + /// + /// We only extract attributes for harnesses that are local to the current crate. + /// Note that all attributes should be valid by now. + pub fn harness_attributes(&self) -> HarnessAttributes { + // Abort if not local. + if !self.item.is_local() { + panic!("Expected a local item, but got: {:?}", self.item); + }; + trace!(?self, "extract_harness_attributes"); + assert!(self.is_harness()); + self.map.iter().fold(HarnessAttributes::default(), |mut harness, (kind, attributes)| { + match kind { + KaniAttributeKind::ShouldPanic => harness.should_panic = true, + KaniAttributeKind::Solver => { + harness.solver = parse_solver(self.tcx, attributes[0]); + } + KaniAttributeKind::Stub => { + harness.stubs.extend_from_slice(&parse_stubs(self.tcx, self.item, attributes)); + } + KaniAttributeKind::Unwind => { + harness.unwind_value = parse_unwind(self.tcx, attributes[0]) + } + KaniAttributeKind::Proof => harness.proof = true, + KaniAttributeKind::ProofForContract => self.handle_proof_for_contract(&mut harness), + KaniAttributeKind::StubVerified => self.handle_stub_verified(&mut harness), + KaniAttributeKind::Unstable => { + // Internal attribute which shouldn't exist here. + unreachable!() + } + KaniAttributeKind::CheckedWith + | KaniAttributeKind::IsContractGenerated + | KaniAttributeKind::ReplacedWith => { + self.tcx.sess.span_err(self.tcx.def_span(self.item), format!("Contracts are not supported on harnesses. (Found the kani-internal contract attribute `{}`)", kind.as_ref())); + } + }; + harness + }) + } + + fn handle_proof_for_contract(&self, harness: &mut HarnessAttributes) { + let sess = self.tcx.sess; + let (name, id, span) = match self.interpret_the_for_contract_attribute() { + None => unreachable!( + "impossible, was asked to handle `proof_for_contract` but didn't find such an attribute." + ), + Some(Err(_)) => return, // This error was already emitted + Some(Ok(values)) => values, + }; + let Some(Ok(replacement_name)) = KaniAttributes::for_item(self.tcx, id).checked_with() + else { + sess.struct_span_err( + span, + format!( + "Failed to check contract: Function `{}` has no contract.", + self.item_name(), + ), + ) + .span_note(self.tcx.def_span(id), "Try adding a contract to this function.") + .emit(); + return; }; + harness.stubs.push(self.stub_for_relative_item(name, replacement_name)); + } + + fn handle_stub_verified(&self, harness: &mut HarnessAttributes) { + let sess = self.tcx.sess; + for contract in self.interpret_stub_verified_attribute() { + let Ok((name, def_id, span)) = contract else { + // This error has already been emitted so we can ignore it now. + // Later the session will fail anyway so we can just + // optimistically forge on and try to find more errors. + continue; + }; + let replacement_name = match KaniAttributes::for_item(self.tcx, def_id).replaced_with() + { + None => { + sess.struct_span_err( + span, + format!( + "Failed to generate verified stub: Function `{}` has no contract.", + self.item_name(), + ), + ) + .span_note( + self.tcx.def_span(def_id), + format!( + "Try adding a contract to this function or use the unsound `{}` attribute instead.", + KaniAttributeKind::Stub.as_ref(), + ) + ) + .emit(); + continue; + } + Some(Ok(replacement_name)) => replacement_name, + Some(Err(_)) => continue, + }; + harness.stubs.push(self.stub_for_relative_item(name, replacement_name)) + } + } + + fn item_name(&self) -> Symbol { + self.tcx.item_name(self.item) + } + + /// Check that if this item is tagged with a proof_attribute, it is a valid harness. + fn check_proof_attribute(&self, proof_attribute: &Attribute) { + let span = proof_attribute.span; + let tcx = self.tcx; + expect_no_args(tcx, KaniAttributeKind::Proof, proof_attribute); + if tcx.def_kind(self.item) != DefKind::Fn { + tcx.sess.span_err(span, "the `proof` attribute can only be applied to functions"); + } else if tcx.generics_of(self.item).requires_monomorphization(tcx) { + tcx.sess.span_err(span, "the `proof` attribute cannot be applied to generic functions"); + } else { + let instance = Instance::mono(tcx, self.item); + if !super::fn_abi(tcx, instance).args.is_empty() { + tcx.sess.span_err(span, "functions used as harnesses cannot have any arguments"); + } + } + } + + fn stub_for_relative_item(&self, anchor: Symbol, replacement: Symbol) -> Stub { + let local_id = self.item.expect_local(); + let current_module = self.tcx.parent_module_from_def_id(local_id); + let replace_str = replacement.as_str(); + let original_str = anchor.as_str(); + let replacement = original_str + .rsplit_once("::") + .map_or_else(|| replace_str.to_string(), |t| t.0.to_string() + "::" + replace_str); + resolve::resolve_fn(self.tcx, current_module.to_local_def_id(), &replacement).unwrap(); + Stub { original: original_str.to_string(), replacement } } } +/// An efficient check for the existence for a particular [`KaniAttributeKind`]. +/// Unlike querying [`KaniAttributes`] this method builds no new heap data +/// structures and has short circuiting. +fn has_kani_attribute bool>( + tcx: TyCtxt, + def_id: DefId, + predicate: F, +) -> bool { + tcx.get_attrs_unchecked(def_id).iter().filter_map(|a| attr_kind(tcx, a)).any(predicate) +} + +/// Test if this function was generated by expanding a contract attribute like +/// `requires` and `ensures`. +pub fn is_function_contract_generated(tcx: TyCtxt, def_id: DefId) -> bool { + has_kani_attribute(tcx, def_id, KaniAttributeKind::is_function_contract) +} + +/// Same as [`KaniAttributes::is_harness`] but more efficient because less +/// attribute parsing is performed. pub fn is_proof_harness(tcx: TyCtxt, def_id: DefId) -> bool { - let attributes = extract_kani_attributes(tcx, def_id); - attributes.contains_key(&KaniAttributeKind::Proof) + has_kani_attribute(tcx, def_id, |a| { + matches!(a, KaniAttributeKind::Proof | KaniAttributeKind::ProofForContract) + }) } /// Does this `def_id` have `#[rustc_test_marker]`? @@ -112,77 +530,43 @@ pub fn test_harness_name(tcx: TyCtxt, def_id: DefId) -> String { parse_str_value(&marker).unwrap() } -/// Extract harness attributes for a given `def_id`. -/// -/// We only extract attributes for harnesses that are local to the current crate. -/// Note that all attributes should be valid by now. -pub fn extract_harness_attributes(tcx: TyCtxt, def_id: DefId) -> HarnessAttributes { - // Abort if not local. - assert!(def_id.is_local(), "Expected a local item, but got: {def_id:?}"); - let attributes = extract_kani_attributes(tcx, def_id); - trace!(?def_id, ?attributes, "extract_harness_attributes"); - assert!(attributes.contains_key(&KaniAttributeKind::Proof)); - attributes.into_iter().fold(HarnessAttributes::default(), |mut harness, (kind, attributes)| { - match kind { - KaniAttributeKind::ShouldPanic => harness.should_panic = true, - KaniAttributeKind::Solver => { - harness.solver = parse_solver(tcx, attributes[0]); - } - KaniAttributeKind::Stub => { - harness.stubs = parse_stubs(tcx, def_id, attributes); - } - KaniAttributeKind::Unwind => harness.unwind_value = parse_unwind(tcx, attributes[0]), - KaniAttributeKind::Proof => harness.proof = true, - KaniAttributeKind::Unstable => { - // Internal attribute which shouldn't exist here. - unreachable!() - } - }; - harness - }) -} - -/// Check that any unstable API has been enabled. Otherwise, emit an error. -/// -/// TODO: Improve error message by printing the span of the harness instead of the definition. -pub fn check_unstable_features(tcx: TyCtxt, enabled_features: &[String], def_id: DefId) { - if !matches!(tcx.type_of(def_id).skip_binder().kind(), TyKind::FnDef(..)) { - // skip closures due to an issue with rustc. - // https://github.com/model-checking/kani/pull/2406#issuecomment-1534333862 - return; - } - let attributes = extract_kani_attributes(tcx, def_id); - if let Some(unstable_attrs) = attributes.get(&KaniAttributeKind::Unstable) { - for attr in unstable_attrs { - let unstable_attr = UnstableAttribute::try_from(*attr).unwrap(); - if !enabled_features.contains(&unstable_attr.feature) { - // Reached an unstable attribute that was not enabled. - report_unstable_forbidden(tcx, def_id, &unstable_attr); +/// Expect the contents of this attribute to be of the format #[attribute = +/// "value"] and return the `"value"`. +fn expect_key_string_value( + sess: &Session, + attr: &Attribute, +) -> Result { + let span = attr.span; + let AttrArgs::Eq(_, it) = &attr.get_normal_item().args else { + return Err(sess.span_err(span, "Expected attribute of the form #[attr = \"value\"]")); + }; + let maybe_str = match it { + AttrArgsEq::Ast(expr) => { + if let ExprKind::Lit(tok) = expr.kind { + match LitKind::from_token_lit(tok) { + Ok(l) => l.str(), + Err(err) => { + return Err(sess.span_err( + span, + format!("Invalid string literal on right hand side of `=` {err:?}"), + )); + } + } } else { - debug!(enabled=?attr, ?def_id, "check_unstable_features"); + return Err( + sess.span_err(span, "Expected literal string as right hand side of `=`") + ); } } + AttrArgsEq::Hir(lit) => lit.kind.str(), + }; + if let Some(str) = maybe_str { + Ok(str) + } else { + Err(sess.span_err(span, "Expected literal string as right hand side of `=`")) } } -/// Report misusage of an unstable feature that was not enabled. -fn report_unstable_forbidden( - tcx: TyCtxt, - def_id: DefId, - unstable_attr: &UnstableAttribute, -) -> ErrorGuaranteed { - let fn_name = tcx.def_path_str(def_id); - tcx.sess - .struct_err(format!( - "Use of unstable feature `{}`: {}", - unstable_attr.feature, unstable_attr.reason - )) - .span_note(tcx.def_span(def_id), format!("the function `{fn_name}` is unstable:")) - .note(format!("see issue {} for more information", unstable_attr.issue)) - .help(format!("use `-Z {}` to enable using this function.", unstable_attr.feature)) - .emit() -} - fn expect_single<'a>( tcx: TyCtxt, kind: KaniAttributeKind, @@ -200,38 +584,6 @@ fn expect_single<'a>( attr } -/// Check that if an item is tagged with a proof_attribute, it is a valid harness. -fn check_proof_attribute(tcx: TyCtxt, def_id: DefId, proof_attribute: &Attribute) { - let span = proof_attribute.span; - expect_no_args(tcx, KaniAttributeKind::Proof, proof_attribute); - if tcx.def_kind(def_id) != DefKind::Fn { - tcx.sess.span_err(span, "the `proof` attribute can only be applied to functions"); - } else if tcx.generics_of(def_id).requires_monomorphization(tcx) { - tcx.sess.span_err(span, "the `proof` attribute cannot be applied to generic functions"); - } else { - let instance = Instance::mono(tcx, def_id); - if !super::fn_abi(tcx, instance).args.is_empty() { - tcx.sess.span_err(span, "functions used as harnesses cannot have any arguments"); - } - } -} - -/// Partition all the attributes according to their kind. -fn extract_kani_attributes( - tcx: TyCtxt, - def_id: DefId, -) -> BTreeMap> { - let all_attributes = tcx.get_attrs_unchecked(def_id); - all_attributes.iter().fold(BTreeMap::default(), |mut result, attribute| { - // Get the string the appears after "kanitool::" in each attribute string. - // Ex - "proof" | "unwind" etc. - if let Some(kind) = attr_kind(tcx, attribute) { - result.entry(kind).or_default().push(attribute) - } - result - }) -} - /// Attribute used to mark a Kani lib API unstable. #[derive(Debug)] struct UnstableAttribute { @@ -328,10 +680,10 @@ fn parse_unwind(tcx: TyCtxt, attr: &Attribute) -> Option { } } -fn parse_stubs(tcx: TyCtxt, harness: DefId, attributes: Vec<&Attribute>) -> Vec { +fn parse_stubs(tcx: TyCtxt, harness: DefId, attributes: &[&Attribute]) -> Vec { let current_module = tcx.parent_module_from_def_id(harness.expect_local()); let check_resolve = |attr: &Attribute, name: &str| { - let result = resolve::resolve_fn(tcx, current_module, name); + let result = resolve::resolve_fn(tcx, current_module.to_local_def_id(), name); if let Err(err) = result { tcx.sess.span_err(attr.span, format!("failed to resolve `{name}`: {err}")); } diff --git a/kani-compiler/src/kani_middle/coercion.rs b/kani-compiler/src/kani_middle/coercion.rs index 42f3cfb0a702..0f4be88bc29e 100644 --- a/kani-compiler/src/kani_middle/coercion.rs +++ b/kani-compiler/src/kani_middle/coercion.rs @@ -174,7 +174,7 @@ impl<'tcx> Iterator for CoerceUnsizedIterator<'tcx> { let src_ty = self.src_ty.take().unwrap(); let dst_ty = self.dst_ty.take().unwrap(); let field = match (&src_ty.kind(), &dst_ty.kind()) { - (&ty::Adt(src_def, src_substs), &ty::Adt(dst_def, dst_substs)) => { + (&ty::Adt(src_def, src_args), &ty::Adt(dst_def, dst_args)) => { // Handle smart pointers by using CustomCoerceUnsized to find the field being // coerced. assert_eq!(src_def, dst_def); @@ -186,8 +186,8 @@ impl<'tcx> Iterator for CoerceUnsizedIterator<'tcx> { custom_coerce_unsize_info(self.tcx, src_ty, dst_ty); assert!(coerce_index.as_usize() < src_fields.len()); - self.src_ty = Some(src_fields[coerce_index].ty(self.tcx, src_substs)); - self.dst_ty = Some(dst_fields[coerce_index].ty(self.tcx, dst_substs)); + self.src_ty = Some(src_fields[coerce_index].ty(self.tcx, src_args)); + self.dst_ty = Some(dst_fields[coerce_index].ty(self.tcx, dst_args)); Some(src_fields[coerce_index].name) } _ => { @@ -213,11 +213,7 @@ fn custom_coerce_unsize_info<'tcx>( ) -> CustomCoerceUnsized { let def_id = tcx.require_lang_item(LangItem::CoerceUnsized, None); - let trait_ref = ty::Binder::dummy(TraitRef::new( - tcx, - def_id, - tcx.mk_substs_trait(source_ty, [target_ty.into()]), - )); + let trait_ref = TraitRef::new(tcx, def_id, tcx.mk_args_trait(source_ty, [target_ty.into()])); match tcx.codegen_select_candidate((ParamEnv::reveal_all(), trait_ref)) { Ok(ImplSource::UserDefined(ImplSourceUserDefinedData { impl_def_id, .. })) => { diff --git a/kani-compiler/src/kani_middle/intrinsics.rs b/kani-compiler/src/kani_middle/intrinsics.rs new file mode 100644 index 000000000000..c4785ac98b81 --- /dev/null +++ b/kani-compiler/src/kani_middle/intrinsics.rs @@ -0,0 +1,108 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! This module contains a MIR pass that replaces some intrinsics by rust intrinsics models as +//! well as validation logic that can only be added during monomorphization. +use rustc_index::IndexVec; +use rustc_middle::mir::{Body, Const as mirConst, ConstValue, Operand, TerminatorKind}; +use rustc_middle::mir::{Local, LocalDecl}; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::ty::{Const, GenericArgsRef}; +use rustc_span::symbol::{sym, Symbol}; +use tracing::{debug, trace}; + +pub struct ModelIntrinsics<'tcx> { + tcx: TyCtxt<'tcx>, + /// Local declarations of the function being transformed. + local_decls: IndexVec>, +} + +impl<'tcx> ModelIntrinsics<'tcx> { + /// Function that replace calls to some intrinsics that have a high level model in our library. + /// + /// For now, we only look at intrinsic calls, which are modelled by a terminator. + /// + /// However, this pass runs after lowering intrinsics, which may replace the terminator by + /// an intrinsic statement (non-diverging intrinsic). + pub fn run_pass(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { + ModelIntrinsics { tcx, local_decls: body.local_decls.clone() }.transform(body) + } + + pub fn transform(&self, body: &mut Body<'tcx>) { + for block in body.basic_blocks.as_mut() { + let terminator = block.terminator_mut(); + if let TerminatorKind::Call { func, args, .. } = &mut terminator.kind { + let func_ty = func.ty(&self.local_decls, self.tcx); + if let Some((intrinsic_name, generics)) = resolve_rust_intrinsic(self.tcx, func_ty) + { + trace!(?func, ?intrinsic_name, "run_pass"); + if intrinsic_name == sym::simd_bitmask { + self.replace_simd_bitmask(func, args, generics) + } + } + } + } + } + + /// Change the function call to use the stubbed version. + /// We only replace calls if we can ensure the input has simd representation. + fn replace_simd_bitmask( + &self, + func: &mut Operand<'tcx>, + args: &[Operand<'tcx>], + gen_args: GenericArgsRef<'tcx>, + ) { + assert_eq!(args.len(), 1); + let tcx = self.tcx; + let arg_ty = args[0].ty(&self.local_decls, tcx); + if arg_ty.is_simd() { + // Get the stub definition. + let stub_id = tcx.get_diagnostic_item(Symbol::intern("KaniModelSimdBitmask")).unwrap(); + debug!(?func, ?stub_id, "replace_simd_bitmask"); + + // Get SIMD information from the type. + let (len, elem_ty) = simd_len_and_type(tcx, arg_ty); + debug!(?len, ?elem_ty, "replace_simd_bitmask Ok"); + + // Increment the list of generic arguments since our stub also takes element type and len. + let mut new_gen_args = Vec::from_iter(gen_args.iter()); + new_gen_args.push(elem_ty.into()); + new_gen_args.push(len.into()); + + let Operand::Constant(fn_def) = func else { unreachable!() }; + fn_def.const_ = mirConst::from_value( + ConstValue::ZeroSized, + tcx.type_of(stub_id).instantiate(tcx, &new_gen_args), + ); + } else { + debug!(?arg_ty, "replace_simd_bitmask failed"); + } + } +} + +fn simd_len_and_type<'tcx>(tcx: TyCtxt<'tcx>, simd_ty: Ty<'tcx>) -> (Const<'tcx>, Ty<'tcx>) { + match simd_ty.kind() { + ty::Adt(def, args) => { + assert!(def.repr().simd(), "`simd_size_and_type` called on non-SIMD type"); + let variant = def.non_enum_variant(); + let f0_ty = variant.fields[0u32.into()].ty(tcx, args); + + match f0_ty.kind() { + ty::Array(elem_ty, len) => (*len, *elem_ty), + _ => (Const::from_target_usize(tcx, variant.fields.len() as u64), f0_ty), + } + } + _ => unreachable!("unexpected layout for simd type {simd_ty}"), + } +} + +fn resolve_rust_intrinsic<'tcx>( + tcx: TyCtxt<'tcx>, + func_ty: Ty<'tcx>, +) -> Option<(Symbol, GenericArgsRef<'tcx>)> { + if let ty::FnDef(def_id, args) = *func_ty.kind() { + if tcx.is_intrinsic(def_id) { + return Some((tcx.item_name(def_id), args)); + } + } + None +} diff --git a/kani-compiler/src/kani_middle/metadata.rs b/kani-compiler/src/kani_middle/metadata.rs index 506779001689..fbf69c2d4fa4 100644 --- a/kani-compiler/src/kani_middle/metadata.rs +++ b/kani-compiler/src/kani_middle/metadata.rs @@ -10,11 +10,11 @@ use kani_metadata::{ArtifactType, HarnessAttributes, HarnessMetadata}; use rustc_hir::def_id::DefId; use rustc_middle::ty::{Instance, InstanceDef, TyCtxt}; -use super::{attributes::extract_harness_attributes, SourceLocation}; +use super::{attributes::KaniAttributes, SourceLocation}; /// Create the harness metadata for a proof harness for a given function. pub fn gen_proof_metadata(tcx: TyCtxt, def_id: DefId, base_name: &Path) -> HarnessMetadata { - let attributes = extract_harness_attributes(tcx, def_id); + let attributes = KaniAttributes::for_item(tcx, def_id).harness_attributes(); let pretty_name = tcx.def_path_str(def_id); // Main function a special case in order to support `--function main` // TODO: Get rid of this: https://github.com/model-checking/kani/issues/2129 diff --git a/kani-compiler/src/kani_middle/mod.rs b/kani-compiler/src/kani_middle/mod.rs index ba235db26805..0aec0c448219 100644 --- a/kani-compiler/src/kani_middle/mod.rs +++ b/kani-compiler/src/kani_middle/mod.rs @@ -15,7 +15,7 @@ use rustc_middle::ty::layout::{ FnAbiError, FnAbiOf, FnAbiOfHelpers, FnAbiRequest, HasParamEnv, HasTyCtxt, LayoutError, LayoutOfHelpers, TyAndLayout, }; -use rustc_middle::ty::{self, Instance, InstanceDef, Ty, TyCtxt}; +use rustc_middle::ty::{self, Instance, InstanceDef, ParamEnv, Ty, TyCtxt}; use rustc_session::config::OutputType; use rustc_span::source_map::respan; use rustc_span::Span; @@ -25,11 +25,12 @@ use std::fs::File; use std::io::BufWriter; use std::io::Write; -use self::attributes::{check_attributes, check_unstable_features}; +use self::attributes::KaniAttributes; pub mod analysis; pub mod attributes; pub mod coercion; +mod intrinsics; pub mod metadata; pub mod provide; pub mod reachability; @@ -43,7 +44,7 @@ pub fn check_crate_items(tcx: TyCtxt, ignore_asm: bool) { let krate = tcx.crate_name(LOCAL_CRATE); for item in tcx.hir_crate_items(()).items() { let def_id = item.owner_id.def_id.to_def_id(); - check_attributes(tcx, def_id); + KaniAttributes::for_item(tcx, def_id).check_attributes(); if tcx.def_kind(def_id) == DefKind::GlobalAsm { if !ignore_asm { let error_msg = format!( @@ -65,20 +66,112 @@ pub fn check_crate_items(tcx: TyCtxt, ignore_asm: bool) { /// Check that all given items are supported and there's no misconfiguration. /// This method will exhaustively print any error / warning and it will abort at the end if any /// error was found. -pub fn check_reachable_items(tcx: TyCtxt, queries: &QueryDb, items: &[MonoItem]) { +pub fn check_reachable_items<'tcx>(tcx: TyCtxt<'tcx>, queries: &QueryDb, items: &[MonoItem<'tcx>]) { // Avoid printing the same error multiple times for different instantiations of the same item. let mut def_ids = HashSet::new(); for item in items.iter().filter(|i| matches!(i, MonoItem::Fn(..) | MonoItem::Static(..))) { let def_id = item.def_id(); if !def_ids.contains(&def_id) { // Check if any unstable attribute was reached. - check_unstable_features(tcx, &queries.unstable_features, def_id); + KaniAttributes::for_item(tcx, def_id) + .check_unstable_features(&queries.args().unstable_features); def_ids.insert(def_id); } + + // We don't short circuit here since this is a type check and can shake + // out differently depending on generic parameters. + if let MonoItem::Fn(instance) = item { + if attributes::is_function_contract_generated(tcx, instance.def_id()) { + check_is_contract_safe(tcx, *instance); + } + } } tcx.sess.abort_if_errors(); } +/// A basic check that ensures a function with a contract does not receive +/// mutable pointers in its input and does not return raw pointers of any kind. +/// +/// This is a temporary safety measure because contracts cannot yet reason +/// about the heap. +fn check_is_contract_safe<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) { + use ty::TypeVisitor; + struct NoMutPtr<'tcx> { + tcx: TyCtxt<'tcx>, + is_prohibited: fn(ty::Ty<'tcx>) -> bool, + /// Where (top level) did the type we're analyzing come from. Used for + /// composing error messages. + r#where: &'static str, + /// Adjective to describe the kind of pointer we're prohibiting. + /// Essentially `is_prohibited` but in English. + what: &'static str, + } + + impl<'tcx> TypeVisitor> for NoMutPtr<'tcx> { + fn visit_ty(&mut self, t: ty::Ty<'tcx>) -> std::ops::ControlFlow { + use ty::TypeSuperVisitable; + if (self.is_prohibited)(t) { + // TODO make this more user friendly + self.tcx.sess.err(format!("{} contains a {}pointer ({t:?}). This is prohibited for functions with contracts, as they cannot yet reason about the pointer behavior.", self.r#where, self.what)); + } + + // Rust's type visitor only recurses into type arguments, (e.g. + // `generics` in this match). This is enough for many types, but it + // won't look at the field types of structs or enums. So we override + // it here and do that ourselves. + // + // Since the field types also must contain in some form all the type + // arguments the visitor will see them as it inspects the fields and + // we don't need to call back to `super`. + if let ty::TyKind::Adt(adt_def, generics) = t.kind() { + for variant in adt_def.variants() { + for field in &variant.fields { + let ctrl = self.visit_ty(field.ty(self.tcx, generics)); + if ctrl.is_break() { + // Technically we can just ignore this because we + // know this case will never happen, but just to be + // safe. + return ctrl; + } + } + } + std::ops::ControlFlow::Continue(()) + } else { + // For every other type. + t.super_visit_with(self) + } + } + } + + fn is_raw_mutable_ptr(t: ty::Ty) -> bool { + matches!(t.kind(), ty::TyKind::RawPtr(tmut) if tmut.mutbl == rustc_ast::Mutability::Mut) + } + + let bound_fn_sig = instance.ty(tcx, ParamEnv::reveal_all()).fn_sig(tcx); + + for v in bound_fn_sig.bound_vars() { + if let ty::BoundVariableKind::Ty(t) = v { + tcx.sess.span_err( + tcx.def_span(instance.def_id()), + format!("Found a bound type variable {t:?} after monomorphization"), + ); + } + } + + let fn_typ = bound_fn_sig.skip_binder(); + + for (typ, (is_prohibited, r#where, what)) in fn_typ + .inputs() + .iter() + .copied() + .zip(std::iter::repeat((is_raw_mutable_ptr as fn(_) -> _, "This argument", "mutable "))) + .chain([(fn_typ.output(), (ty::Ty::is_unsafe_ptr as fn(_) -> _, "The return", ""))]) + { + let mut v = NoMutPtr { tcx, is_prohibited, r#where, what }; + v.visit_ty(typ); + } +} + /// Print MIR for the reachable items if the `--emit mir` option was provided to rustc. pub fn dump_mir_items(tcx: TyCtxt, items: &[MonoItem], output: &Path) { /// Convert MonoItem into a DefId. diff --git a/kani-compiler/src/kani_middle/provide.rs b/kani-compiler/src/kani_middle/provide.rs index bdca51f43129..3c7664d076d6 100644 --- a/kani-compiler/src/kani_middle/provide.rs +++ b/kani-compiler/src/kani_middle/provide.rs @@ -4,37 +4,40 @@ //! to run during code generation. For example, this can be used to hook up //! custom MIR transformations. +use crate::args::{Arguments, ReachabilityType}; +use crate::kani_middle::intrinsics::ModelIntrinsics; use crate::kani_middle::reachability::{collect_reachable_items, filter_crate_items}; use crate::kani_middle::stubbing; use crate::kani_queries::QueryDb; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_interface; -use rustc_middle::{ - mir::Body, - query::{queries, ExternProviders, Providers}, - ty::TyCtxt, -}; +use rustc_middle::util::Providers; +use rustc_middle::{mir::Body, query::queries, ty::TyCtxt}; /// Sets up rustc's query mechanism to apply Kani's custom queries to code from -/// the present crate. +/// a crate. pub fn provide(providers: &mut Providers, queries: &QueryDb) { - providers.optimized_mir = run_mir_passes; - if queries.stubbing_enabled { - providers.collect_and_partition_mono_items = collect_and_partition_mono_items; + let args = queries.args(); + if should_override(args) { + // Don't override queries if we are only compiling our dependencies. + providers.optimized_mir = run_mir_passes; + providers.extern_queries.optimized_mir = run_mir_passes_extern; + if args.stubbing_enabled { + // TODO: Check if there's at least one stub being applied. + providers.collect_and_partition_mono_items = collect_and_partition_mono_items; + } } } -/// Sets up rustc's query mechanism to apply Kani's custom queries to code from -/// external crates. -pub fn provide_extern(providers: &mut ExternProviders) { - providers.optimized_mir = run_mir_passes_extern; +fn should_override(args: &Arguments) -> bool { + args.reachability_analysis != ReachabilityType::None && !args.build_std } /// Returns the optimized code for the external function associated with `def_id` by /// running rustc's optimization passes followed by Kani-specific passes. fn run_mir_passes_extern(tcx: TyCtxt, def_id: DefId) -> &Body { tracing::debug!(?def_id, "run_mir_passes_extern"); - let body = (rustc_interface::DEFAULT_EXTERN_QUERY_PROVIDERS.optimized_mir)(tcx, def_id); + let body = (rustc_interface::DEFAULT_QUERY_PROVIDERS.extern_queries.optimized_mir)(tcx, def_id); run_kani_mir_passes(tcx, def_id, body) } @@ -55,7 +58,11 @@ fn run_kani_mir_passes<'tcx>( body: &'tcx Body<'tcx>, ) -> &'tcx Body<'tcx> { tracing::debug!(?def_id, "Run Kani transformation passes"); - stubbing::transform(tcx, def_id, body) + let mut transformed_body = stubbing::transform(tcx, def_id, body); + stubbing::transform_foreign_functions(tcx, &mut transformed_body); + // This should be applied after stubbing so user stubs take precedence. + ModelIntrinsics::run_pass(tcx, &mut transformed_body); + tcx.arena.alloc(transformed_body) } /// Runs a reachability analysis before running the default @@ -69,11 +76,14 @@ fn collect_and_partition_mono_items( tcx: TyCtxt, key: (), ) -> queries::collect_and_partition_mono_items::ProvidedValue { - let entry_fn = tcx.entry_fn(()).map(|(id, _)| id); - let local_reachable = filter_crate_items(tcx, |_, def_id| { - tcx.is_reachable_non_generic(def_id) || entry_fn == Some(def_id) - }); - // We do not actually need the value returned here. - collect_reachable_items(tcx, &local_reachable); + rustc_smir::rustc_internal::run(tcx, || { + let entry_fn = tcx.entry_fn(()).map(|(id, _)| id); + let local_reachable = filter_crate_items(tcx, |_, def_id| { + tcx.is_reachable_non_generic(def_id) || entry_fn == Some(def_id) + }); + // We do not actually need the value returned here. + collect_reachable_items(tcx, &local_reachable); + }) + .unwrap(); (rustc_interface::DEFAULT_QUERY_PROVIDERS.collect_and_partition_mono_items)(tcx, key) } diff --git a/kani-compiler/src/kani_middle/reachability.rs b/kani-compiler/src/kani_middle/reachability.rs index a3110426dc57..2a6bfbe8fc6b 100644 --- a/kani-compiler/src/kani_middle/reachability.rs +++ b/kani-compiler/src/kani_middle/reachability.rs @@ -13,40 +13,43 @@ //! - For every static, collect initializer and drop functions. //! //! We have kept this module agnostic of any Kani code in case we can contribute this back to rustc. -use tracing::{debug, debug_span, trace, warn}; +//! +//! Note that this is a copy of `reachability.rs` that uses StableMIR but the public APIs are still +//! kept with internal APIs. +use tracing::{debug, debug_span, trace}; use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; -use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; -use rustc_hir::ItemId; -use rustc_middle::mir::interpret::{AllocId, ConstValue, ErrorHandled, GlobalAlloc, Scalar}; -use rustc_middle::mir::mono::MonoItem; -use rustc_middle::mir::visit::Visitor as MirVisitor; -use rustc_middle::mir::{ - Body, CastKind, Constant, ConstantKind, Location, Rvalue, Terminator, TerminatorKind, -}; -use rustc_middle::span_bug; -use rustc_middle::ty::adjustment::PointerCast; -use rustc_middle::ty::{ - Closure, ClosureKind, ConstKind, EarlyBinder, Instance, InstanceDef, ParamEnv, Ty, TyCtxt, - TyKind, TypeFoldable, VtblEntry, +use rustc_middle::mir::mono::MonoItem as InternalMonoItem; +use rustc_middle::ty::{TyCtxt, VtblEntry}; +use rustc_smir::rustc_internal; +use stable_mir::mir::alloc::{AllocId, GlobalAlloc}; +use stable_mir::mir::mono::{Instance, InstanceKind, MonoItem, StaticDef}; +use stable_mir::mir::pretty::pretty_ty; +use stable_mir::mir::{ + visit::Location, Body, CastKind, Constant, MirVisitor, PointerCoercion, Rvalue, Terminator, + TerminatorKind, }; +use stable_mir::ty::{Allocation, ClosureKind, ConstantKind, RigidTy, Ty, TyKind}; +use stable_mir::CrateDef; +use stable_mir::{self, CrateItem}; use crate::kani_middle::coercion; -use crate::kani_middle::stubbing::get_stub; +use crate::kani_middle::coercion::CoercionBase; +use crate::kani_middle::stubbing::{get_stub, validate_instance}; /// Collect all reachable items starting from the given starting points. pub fn collect_reachable_items<'tcx>( tcx: TyCtxt<'tcx>, - starting_points: &[MonoItem<'tcx>], -) -> Vec> { + starting_points: &[InternalMonoItem<'tcx>], +) -> Vec> { // For each harness, collect items using the same collector. // I.e.: This will return any item that is reachable from one or more of the starting points. let mut collector = MonoItemsCollector::new(tcx); for item in starting_points { - collector.collect(*item); + collector.collect(rustc_internal::stable(item)); } #[cfg(debug_assertions)] @@ -56,99 +59,76 @@ pub fn collect_reachable_items<'tcx>( .unwrap_or_else(|e| tracing::error!("Failed to dump call graph: {e}")); tcx.sess.abort_if_errors(); - // Sort the result so code generation follows deterministic order. // This helps us to debug the code, but it also provides the user a good experience since the // order of the errors and warnings is stable. - let mut sorted_items: Vec<_> = collector.collected.into_iter().collect(); + let mut sorted_items: Vec<_> = + collector.collected.iter().map(rustc_internal::internal).collect(); sorted_items.sort_by_cached_key(|item| to_fingerprint(tcx, item)); sorted_items } /// Collect all (top-level) items in the crate that matches the given predicate. /// An item can only be a root if they are: non-generic Fn / Static / GlobalASM -pub fn filter_crate_items(tcx: TyCtxt, predicate: F) -> Vec +pub fn filter_crate_items(tcx: TyCtxt, predicate: F) -> Vec where F: Fn(TyCtxt, DefId) -> bool, { - let crate_items = tcx.hir_crate_items(()); + let crate_items = stable_mir::all_local_items(); // Filter regular items. - let root_items = crate_items.items().filter_map(|item| { - let def_id = item.owner_id.def_id.to_def_id(); - if !is_generic(tcx, def_id) && predicate(tcx, def_id) { - to_mono_root(tcx, item, def_id) - } else { - None - } - }); - - // Filter items from implementation blocks. - let impl_items = crate_items.impl_items().filter_map(|impl_item| { - let def_id = impl_item.owner_id.def_id.to_def_id(); - if matches!(tcx.def_kind(def_id), DefKind::AssocFn) - && !is_generic(tcx, def_id) - && predicate(tcx, def_id) - { - Some(MonoItem::Fn(Instance::mono(tcx, def_id))) - } else { - None - } - }); - root_items.chain(impl_items).collect() + crate_items + .iter() + .filter_map(|item| { + // Only collect monomorphic items. + Instance::try_from(*item).ok().and_then(|instance| { + let def_id = rustc_internal::internal(item); + predicate(tcx, def_id) + .then_some(InternalMonoItem::Fn(rustc_internal::internal(&instance))) + }) + }) + .collect::>() } /// Use a predicate to find `const` declarations, then extract all items reachable from them. /// /// Probably only specifically useful with a predicate to find `TestDescAndFn` const declarations from /// tests and extract the closures from them. -pub fn filter_const_crate_items(tcx: TyCtxt, mut predicate: F) -> Vec +pub fn filter_const_crate_items(tcx: TyCtxt, mut predicate: F) -> Vec where F: FnMut(TyCtxt, DefId) -> bool, { + let crate_items = stable_mir::all_local_items(); let mut roots = Vec::new(); - for hir_id in tcx.hir_crate_items(()).items() { - let def_id = hir_id.owner_id.def_id.to_def_id(); - let def_kind = tcx.def_kind(def_id); - if matches!(def_kind, DefKind::Const) && predicate(tcx, def_id) { - let instance = Instance::mono(tcx, def_id); - let body = tcx.instance_mir(InstanceDef::Item(def_id)); - let mut collector = - MonoItemsFnCollector { tcx, body, instance, collected: FxHashSet::default() }; - collector.visit_body(body); - - roots.extend(collector.collected); + // Filter regular items. + for item in crate_items { + // Only collect monomorphic items. + if let Ok(instance) = Instance::try_from(item) { + let def_id = rustc_internal::internal(&item); + if predicate(tcx, def_id) { + let body = instance.body().unwrap(); + let mut collector = MonoItemsFnCollector { + tcx, + body: &body, + collected: FxHashSet::default(), + instance: &instance, + }; + collector.visit_body(&body); + roots.extend(collector.collected.iter().map(rustc_internal::internal)); + } } } roots } -fn is_generic(tcx: TyCtxt, def_id: DefId) -> bool { - let generics = tcx.generics_of(def_id); - generics.requires_monomorphization(tcx) -} - -fn to_mono_root(tcx: TyCtxt, item_id: ItemId, def_id: DefId) -> Option { - let kind = tcx.def_kind(def_id); - match kind { - DefKind::Static(..) => Some(MonoItem::Static(def_id)), - DefKind::Fn => Some(MonoItem::Fn(Instance::mono(tcx, def_id))), - DefKind::GlobalAsm => Some(MonoItem::GlobalAsm(item_id)), - _ => { - debug!(?def_id, ?kind, "Ignored item. Not a root type."); - None - } - } -} - struct MonoItemsCollector<'tcx> { /// The compiler context. tcx: TyCtxt<'tcx>, /// Set of collected items used to avoid entering recursion loops. - collected: FxHashSet>, + collected: FxHashSet, /// Items enqueued for visiting. - queue: Vec>, + queue: Vec, #[cfg(debug_assertions)] - call_graph: debug::CallGraph<'tcx>, + call_graph: debug::CallGraph, } impl<'tcx> MonoItemsCollector<'tcx> { @@ -161,8 +141,9 @@ impl<'tcx> MonoItemsCollector<'tcx> { call_graph: debug::CallGraph::default(), } } + /// Collects all reachable items starting from the given root. - pub fn collect(&mut self, root: MonoItem<'tcx>) { + pub fn collect(&mut self, root: MonoItem) { debug!(?root, "collect"); self.queue.push(root); self.reachable_items(); @@ -173,12 +154,12 @@ impl<'tcx> MonoItemsCollector<'tcx> { fn reachable_items(&mut self) { while let Some(to_visit) = self.queue.pop() { if !self.collected.contains(&to_visit) { - self.collected.insert(to_visit); - let next_items = match to_visit { - MonoItem::Fn(instance) => self.visit_fn(instance), - MonoItem::Static(def_id) => self.visit_static(def_id), + self.collected.insert(to_visit.clone()); + let next_items = match &to_visit { + MonoItem::Fn(instance) => self.visit_fn(*instance), + MonoItem::Static(static_def) => self.visit_static(*static_def), MonoItem::GlobalAsm(_) => { - self.visit_asm(to_visit); + self.visit_asm(&to_visit); vec![] } }; @@ -192,141 +173,117 @@ impl<'tcx> MonoItemsCollector<'tcx> { } /// Visit a function and collect all mono-items reachable from its instructions. - fn visit_fn(&mut self, instance: Instance<'tcx>) -> Vec> { + fn visit_fn(&mut self, instance: Instance) -> Vec { let _guard = debug_span!("visit_fn", function=?instance).entered(); - let body = self.tcx.instance_mir(instance.def); - let mut collector = - MonoItemsFnCollector { tcx: self.tcx, collected: FxHashSet::default(), instance, body }; - collector.visit_body(body); - collector.collected.into_iter().collect() + if validate_instance(self.tcx, instance) { + let body = instance.body().unwrap(); + let mut collector = MonoItemsFnCollector { + tcx: self.tcx, + collected: FxHashSet::default(), + body: &body, + instance: &instance, + }; + collector.visit_body(&body); + collector.collected.into_iter().collect() + } else { + vec![] + } } /// Visit a static object and collect drop / initialization functions. - fn visit_static(&mut self, def_id: DefId) -> Vec> { - let _guard = debug_span!("visit_static", ?def_id).entered(); - let instance = Instance::mono(self.tcx, def_id); + fn visit_static(&mut self, def: StaticDef) -> Vec { + let _guard = debug_span!("visit_static", ?def).entered(); let mut next_items = vec![]; // Collect drop function. - let static_ty = instance.ty(self.tcx, ParamEnv::reveal_all()); - let instance = Instance::resolve_drop_in_place(self.tcx, static_ty); - next_items.push(MonoItem::Fn(instance.polymorphize(self.tcx))); + let static_ty = def.ty(); + let instance = Instance::resolve_drop_in_place(static_ty); + next_items.push(instance.into()); // Collect initialization. - let alloc = self.tcx.eval_static_initializer(def_id).unwrap(); - for id in alloc.inner().provenance().provenances() { - next_items.extend(collect_alloc_items(self.tcx, id).iter()); + let alloc = def.eval_initializer().unwrap(); + for (_, prov) in alloc.provenance.ptrs { + next_items.extend(collect_alloc_items(prov.0).into_iter()); } next_items } /// Visit global assembly and collect its item. - fn visit_asm(&mut self, item: MonoItem<'tcx>) { + fn visit_asm(&mut self, item: &MonoItem) { debug!(?item, "visit_asm"); } } struct MonoItemsFnCollector<'a, 'tcx> { tcx: TyCtxt<'tcx>, - collected: FxHashSet>, - instance: Instance<'tcx>, - body: &'a Body<'tcx>, + collected: FxHashSet, + body: &'a Body, + instance: &'a Instance, } impl<'a, 'tcx> MonoItemsFnCollector<'a, 'tcx> { - fn monomorphize(&self, value: T) -> T - where - T: TypeFoldable>, - { - trace!(instance=?self.instance, ?value, "monomorphize"); - self.instance.subst_mir_and_normalize_erasing_regions( - self.tcx, - ParamEnv::reveal_all(), - EarlyBinder::bind(value), - ) - } - /// Collect the implementation of all trait methods and its supertrait methods for the given /// concrete type. - fn collect_vtable_methods(&mut self, concrete_ty: Ty<'tcx>, trait_ty: Ty<'tcx>) { + fn collect_vtable_methods(&mut self, concrete_ty: Ty, trait_ty: Ty) { trace!(?concrete_ty, ?trait_ty, "collect_vtable_methods"); - assert!(!concrete_ty.is_trait(), "Expected a concrete type, but found: {concrete_ty:?}"); - assert!(trait_ty.is_trait(), "Expected a trait: {trait_ty:?}"); - if let TyKind::Dynamic(trait_list, ..) = trait_ty.kind() { + let concrete_kind = concrete_ty.kind(); + let trait_kind = trait_ty.kind(); + + assert!(!concrete_kind.is_trait(), "expected a concrete type, but found `{concrete_ty:?}`"); + assert!(trait_kind.is_trait(), "expected a trait `{trait_ty:?}`"); + if let Some(principal) = trait_kind.trait_principal() { // A trait object type can have multiple trait bounds but up to one non-auto-trait // bound. This non-auto-trait, named principal, is the only one that can have methods. // https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits - if let Some(principal) = trait_list.principal() { - let poly_trait_ref = principal.with_self_ty(self.tcx, concrete_ty); - - // Walk all methods of the trait, including those of its supertraits - let entries = self.tcx.vtable_entries(poly_trait_ref); - let methods = entries.iter().filter_map(|entry| match entry { - VtblEntry::MetadataAlign - | VtblEntry::MetadataDropInPlace - | VtblEntry::MetadataSize - | VtblEntry::Vacant => None, - VtblEntry::TraitVPtr(_) => { - // all super trait items already covered, so skip them. - None - } - VtblEntry::Method(instance) if should_codegen_locally(self.tcx, instance) => { - Some(MonoItem::Fn(instance.polymorphize(self.tcx))) - } - VtblEntry::Method(..) => None, - }); - trace!(methods=?methods.clone().collect::>(), "collect_vtable_methods"); - self.collected.extend(methods); - } + let poly_trait_ref = principal.with_self_ty(concrete_ty); + + // Walk all methods of the trait, including those of its supertraits + let entries = self.tcx.vtable_entries(rustc_internal::internal(&poly_trait_ref)); + let methods = entries.iter().filter_map(|entry| match entry { + VtblEntry::MetadataAlign + | VtblEntry::MetadataDropInPlace + | VtblEntry::MetadataSize + | VtblEntry::Vacant => None, + VtblEntry::TraitVPtr(_) => { + // all super trait items already covered, so skip them. + None + } + VtblEntry::Method(instance) => { + let instance = rustc_internal::stable(instance); + should_codegen_locally(&instance).then_some(MonoItem::Fn(instance)) + } + }); + trace!(methods=?methods.clone().collect::>(), "collect_vtable_methods"); + self.collected.extend(methods); } // Add the destructor for the concrete type. - let instance = Instance::resolve_drop_in_place(self.tcx, concrete_ty); + let instance = Instance::resolve_drop_in_place(concrete_ty); self.collect_instance(instance, false); } /// Collect an instance depending on how it is used (invoked directly or via fn_ptr). - fn collect_instance(&mut self, instance: Instance<'tcx>, is_direct_call: bool) { - let should_collect = match instance.def { - InstanceDef::Virtual(..) | InstanceDef::Intrinsic(_) => { + fn collect_instance(&mut self, instance: Instance, is_direct_call: bool) { + let should_collect = match instance.kind { + InstanceKind::Virtual { .. } | InstanceKind::Intrinsic => { // Instance definition has no body. assert!(is_direct_call, "Expected direct call {instance:?}"); false } - InstanceDef::DropGlue(_, None) => { - // Only need the glue if we are not calling it directly. - !is_direct_call - } - InstanceDef::CloneShim(..) - | InstanceDef::ClosureOnceShim { .. } - | InstanceDef::DropGlue(_, Some(_)) - | InstanceDef::FnPtrShim(..) - | InstanceDef::Item(..) - | InstanceDef::ReifyShim(..) - | InstanceDef::VTableShim(..) => true, - InstanceDef::ThreadLocalShim(_) | InstanceDef::FnPtrAddrShim(_, _) => true, + InstanceKind::Shim | InstanceKind::Item => true, }; - if should_collect && should_codegen_locally(self.tcx, &instance) { + if should_collect && should_codegen_locally(&instance) { trace!(?instance, "collect_instance"); - self.collected.insert(MonoItem::Fn(instance.polymorphize(self.tcx))); + self.collected.insert(instance.into()); } } /// Collect constant values represented by static variables. - fn collect_const_value(&mut self, value: ConstValue<'tcx>) { - debug!(?value, "collect_const_value"); - match value { - ConstValue::Scalar(Scalar::Ptr(ptr, _size)) => { - self.collected.extend(collect_alloc_items(self.tcx, ptr.provenance).iter()); - } - ConstValue::Slice { data: alloc, start: _, end: _ } - | ConstValue::ByRef { alloc, .. } => { - for id in alloc.inner().provenance().provenances() { - self.collected.extend(collect_alloc_items(self.tcx, id).iter()) - } - } - _ => {} + fn collect_allocation(&mut self, alloc: &Allocation) { + debug!(?alloc, "collect_allocation"); + for (_, id) in &alloc.provenance.ptrs { + self.collected.extend(collect_alloc_items(id.0).into_iter()) } } } @@ -346,68 +303,62 @@ impl<'a, 'tcx> MonoItemsFnCollector<'a, 'tcx> { /// 5. Drop glue. /// 6. Static Initialization /// This code has been mostly taken from `rustc_monomorphize::collector::MirNeighborCollector`. -impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { +impl<'a, 'tcx> MirVisitor for MonoItemsFnCollector<'a, 'tcx> { /// Collect the following: /// - Trait implementations when casting from concrete to dyn Trait. /// - Functions / Closures that have their address taken. /// - Thread Local. - fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) { trace!(rvalue=?*rvalue, "visit_rvalue"); match *rvalue { - Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), ref operand, target) => { + Rvalue::Cast( + CastKind::PointerCoercion(PointerCoercion::Unsize), + ref operand, + target, + ) => { // Check if the conversion include casting a concrete type to a trait type. // If so, collect items from the impl `Trait for Concrete {}`. - let target_ty = self.monomorphize(target); - let source_ty = self.monomorphize(operand.ty(self.body, self.tcx)); - let base_coercion = - coercion::extract_unsize_casting(self.tcx, source_ty, target_ty); - if !base_coercion.src_ty.is_trait() && base_coercion.dst_ty.is_trait() { - debug!(?base_coercion, "collect_vtable_methods"); - self.collect_vtable_methods(base_coercion.src_ty, base_coercion.dst_ty); + let target_ty = target; + let source_ty = operand.ty(self.body.locals()).unwrap(); + let (src_ty, dst_ty) = extract_unsize_coercion(self.tcx, source_ty, target_ty); + if !src_ty.kind().is_trait() && dst_ty.kind().is_trait() { + debug!(?src_ty, ?dst_ty, "collect_vtable_methods"); + self.collect_vtable_methods(src_ty, dst_ty); } } - Rvalue::Cast(CastKind::Pointer(PointerCast::ReifyFnPointer), ref operand, _) => { - let fn_ty = operand.ty(self.body, self.tcx); - let fn_ty = self.monomorphize(fn_ty); - if let TyKind::FnDef(def_id, substs) = *fn_ty.kind() { - let instance = Instance::resolve_for_fn_ptr( - self.tcx, - ParamEnv::reveal_all(), - def_id, - substs, - ) - .unwrap(); + Rvalue::Cast( + CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer), + ref operand, + _, + ) => { + let fn_kind = operand.ty(self.body.locals()).unwrap().kind(); + if let RigidTy::FnDef(fn_def, args) = fn_kind.rigid().unwrap() { + let instance = Instance::resolve_for_fn_ptr(*fn_def, args).unwrap(); self.collect_instance(instance, false); } else { - unreachable!("Expected FnDef type, but got: {:?}", fn_ty); + unreachable!("Expected FnDef type, but got: {:?}", fn_kind); } } - Rvalue::Cast(CastKind::Pointer(PointerCast::ClosureFnPointer(_)), ref operand, _) => { - let source_ty = operand.ty(self.body, self.tcx); - let source_ty = self.monomorphize(source_ty); - match *source_ty.kind() { - Closure(def_id, substs) => { - let instance = Instance::resolve_closure( - self.tcx, - def_id, - substs, - ClosureKind::FnOnce, - ) - .expect("failed to normalize and resolve closure during codegen"); + Rvalue::Cast( + CastKind::PointerCoercion(PointerCoercion::ClosureFnPointer(_)), + ref operand, + _, + ) => { + let source_ty = operand.ty(self.body.locals()).unwrap(); + match source_ty.kind().rigid().unwrap() { + RigidTy::Closure(def_id, args) => { + let instance = + Instance::resolve_closure(*def_id, args, ClosureKind::FnOnce) + .expect("failed to normalize and resolve closure during codegen"); self.collect_instance(instance, false); } _ => unreachable!("Unexpected type: {:?}", source_ty), } } - Rvalue::ThreadLocalRef(def_id) => { - assert!(self.tcx.is_thread_local_static(def_id)); - trace!(?def_id, "visit_rvalue thread_local"); - let instance = Instance::mono(self.tcx, def_id); - if should_codegen_locally(self.tcx, &instance) { - trace!("collecting thread-local static {:?}", def_id); - self.collected.insert(MonoItem::Static(def_id)); - } + Rvalue::ThreadLocalRef(item) => { + trace!(?item, "visit_rvalue thread_local"); + self.collected.insert(MonoItem::Static(StaticDef::try_from(item).unwrap())); } _ => { /* not interesting */ } } @@ -416,85 +367,65 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { } /// Collect constants that are represented as static variables. - fn visit_constant(&mut self, constant: &Constant<'tcx>, location: Location) { - let literal = self.monomorphize(constant.literal); - debug!(?constant, ?location, ?literal, "visit_constant"); - let val = match literal { - ConstantKind::Val(const_val, _) => const_val, - ConstantKind::Ty(ct) => match ct.kind() { - ConstKind::Value(v) => self.tcx.valtree_to_const_val((ct.ty(), v)), - ConstKind::Unevaluated(_) => unreachable!(), - // Nothing to do - ConstKind::Param(..) - | ConstKind::Infer(..) - | ConstKind::Error(..) - | ConstKind::Expr(..) => return, - - // Shouldn't happen - ConstKind::Placeholder(..) | ConstKind::Bound(..) => { - unreachable!("Unexpected constant type {:?} ({:?})", ct, ct.kind()) - } - }, - ConstantKind::Unevaluated(un_eval, _) => { - // Thread local fall into this category. - match self.tcx.const_eval_resolve(ParamEnv::reveal_all(), un_eval, None) { - // The `monomorphize` call should have evaluated that constant already. - Ok(const_val) => const_val, - Err(ErrorHandled::TooGeneric) => span_bug!( - self.body.source_info(location).span, - "Unexpected polymorphic constant: {:?}", - literal - ), - Err(error) => { - warn!(?error, "Error already reported"); - return; - } - } + fn visit_constant(&mut self, constant: &Constant, location: Location) { + debug!(?constant, ?location, literal=?constant.literal, "visit_constant"); + let allocation = match constant.literal.kind() { + ConstantKind::Allocated(allocation) => allocation, + ConstantKind::Unevaluated(_) => { + unreachable!("Instance with polymorphic constant: `{constant:?}`") + } + ConstantKind::Param(_) => unreachable!("Unexpected parameter constant: {constant:?}"), + ConstantKind::ZeroSized => { + // Nothing to do here. + return; } }; - self.collect_const_value(val); + self.collect_allocation(&allocation); } /// Collect function calls. - fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { + fn visit_terminator(&mut self, terminator: &Terminator, location: Location) { trace!(?terminator, ?location, "visit_terminator"); - let tcx = self.tcx; match terminator.kind { - TerminatorKind::Call { ref func, ref args, .. } => { - let callee_ty = func.ty(self.body, tcx); - let fn_ty = self.monomorphize(callee_ty); - if let TyKind::FnDef(def_id, substs) = *fn_ty.kind() { - let instance_opt = - Instance::resolve(self.tcx, ParamEnv::reveal_all(), def_id, substs) - .unwrap(); + TerminatorKind::Call { ref func, .. } => { + let fn_ty = func.ty(self.body.locals()).unwrap(); + if let TyKind::RigidTy(RigidTy::FnDef(fn_def, args)) = fn_ty.kind() { + let instance_opt = Instance::resolve(fn_def, &args).ok(); match instance_opt { None => { - let caller = tcx.def_path_str(self.instance.def_id()); - let callee = tcx.def_path_str(def_id); + let caller = CrateItem::try_from(*self.instance).unwrap().name(); + let callee = fn_def.name(); // Check if the current function has been stubbed. - if let Some(stub) = get_stub(tcx, self.instance.def_id()) { + if let Some(stub) = + get_stub(self.tcx, rustc_internal::internal(self.instance).def_id()) + { // During the MIR stubbing transformation, we do not // force type variables in the stub's signature to // implement the same traits as those in the // original function/method. A trait mismatch shows // up here, when we try to resolve a trait method - let generic_ty = args[0].ty(self.body, tcx).peel_refs(); - let receiver_ty = tcx.subst_and_normalize_erasing_regions( - substs, - ParamEnv::reveal_all(), - EarlyBinder::bind(generic_ty), - ); + + // FIXME: This assumes the type resolving the + // trait is the first argument, but that isn't + // necessarily true. It could be any argument or + // even the return type, for instance for a + // trait like `FromIterator`. + let receiver_ty = args.0[0].expect_ty(); let sep = callee.rfind("::").unwrap(); let trait_ = &callee[..sep]; - tcx.sess.span_err( - terminator.source_info.span, + self.tcx.sess.span_err( + rustc_internal::internal(terminator.span), format!( - "`{receiver_ty}` doesn't implement \ - `{trait_}`. The function `{caller}` \ + "`{}` doesn't implement \ + `{}`. The function `{}` \ cannot be stubbed by `{}` due to \ - generic bounds not being met.", - tcx.def_path_str(stub) + generic bounds not being met. Callee: {}", + pretty_ty(receiver_ty.kind()), + trait_, + caller, + self.tcx.def_path_str(stub), + callee, ), ); } else { @@ -505,22 +436,21 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { }; } else { assert!( - matches!(fn_ty.kind(), TyKind::FnPtr(..)), + matches!(fn_ty.kind().rigid(), Some(RigidTy::FnPtr(..))), "Unexpected type: {fn_ty:?}" ); } } TerminatorKind::Drop { ref place, .. } => { - let place_ty = place.ty(self.body, self.tcx).ty; - let place_mono_ty = self.monomorphize(place_ty); - let instance = Instance::resolve_drop_in_place(self.tcx, place_mono_ty); + let place_ty = place.ty(self.body.locals()).unwrap(); + let instance = Instance::resolve_drop_in_place(place_ty); self.collect_instance(instance, true); } TerminatorKind::InlineAsm { .. } => { // We don't support inline assembly. This shall be replaced by an unsupported // construct during codegen. } - TerminatorKind::Terminate { .. } | TerminatorKind::Assert { .. } => { + TerminatorKind::Abort { .. } | TerminatorKind::Assert { .. } => { // We generate code for this without invoking any lang item. } TerminatorKind::Goto { .. } @@ -528,21 +458,24 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { | TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::Unreachable => {} - TerminatorKind::GeneratorDrop - | TerminatorKind::Yield { .. } - | TerminatorKind::FalseEdge { .. } - | TerminatorKind::FalseUnwind { .. } => { - unreachable!("Unexpected at this MIR level") - } } self.super_terminator(terminator, location); } } +fn extract_unsize_coercion(tcx: TyCtxt, orig_ty: Ty, dst_trait: Ty) -> (Ty, Ty) { + let CoercionBase { src_ty, dst_ty } = coercion::extract_unsize_casting( + tcx, + rustc_internal::internal(orig_ty), + rustc_internal::internal(dst_trait), + ); + (rustc_internal::stable(src_ty), rustc_internal::stable(dst_ty)) +} + /// Convert a `MonoItem` into a stable `Fingerprint` which can be used as a stable hash across /// compilation sessions. This allow us to provide a stable deterministic order to codegen. -fn to_fingerprint(tcx: TyCtxt, item: &MonoItem) -> Fingerprint { +fn to_fingerprint(tcx: TyCtxt, item: &InternalMonoItem) -> Fingerprint { tcx.with_stable_hashing_context(|mut hcx| { let mut hasher = StableHasher::new(); item.hash_stable(&mut hcx, &mut hasher); @@ -551,52 +484,31 @@ fn to_fingerprint(tcx: TyCtxt, item: &MonoItem) -> Fingerprint { } /// Return whether we should include the item into codegen. -/// - We only skip foreign items. -/// -/// Note: Ideally, we should be able to assert that the MIR for non-foreign items are available via -/// call to `tcx.is_mir_available (def_id)`. -/// However, we found an issue where this function was returning `false` for a mutable static -/// item with constant initializer from an upstream crate. -/// See for an example. -fn should_codegen_locally<'tcx>(tcx: TyCtxt<'tcx>, instance: &Instance<'tcx>) -> bool { - if let Some(def_id) = instance.def.def_id_if_not_guaranteed_local_codegen() { - // We cannot codegen foreign items. - !tcx.is_foreign_item(def_id) - } else { - // This will include things like VTableShim and other stuff. See the method - // def_id_if_not_guaranteed_local_codegen for the full list. - true - } +fn should_codegen_locally(instance: &Instance) -> bool { + !instance.is_foreign_item() } -/// Scans the allocation type and collect static objects. -fn collect_alloc_items(tcx: TyCtxt, alloc_id: AllocId) -> Vec { - trace!(alloc=?tcx.global_alloc(alloc_id), ?alloc_id, "collect_alloc_items"); +fn collect_alloc_items(alloc_id: AllocId) -> Vec { + trace!(?alloc_id, "collect_alloc_items"); let mut items = vec![]; - match tcx.global_alloc(alloc_id) { - GlobalAlloc::Static(def_id) => { + match GlobalAlloc::from(alloc_id) { + GlobalAlloc::Static(def) => { // This differ from rustc's collector since rustc does not include static from // upstream crates. - assert!(!tcx.is_thread_local_static(def_id)); - let instance = Instance::mono(tcx, def_id); - should_codegen_locally(tcx, &instance).then(|| items.push(MonoItem::Static(def_id))); + let instance = Instance::try_from(CrateItem::from(def)).unwrap(); + should_codegen_locally(&instance).then(|| items.push(MonoItem::from(def))); } GlobalAlloc::Function(instance) => { - should_codegen_locally(tcx, &instance) - .then(|| items.push(MonoItem::Fn(instance.polymorphize(tcx)))); + should_codegen_locally(&instance).then(|| items.push(MonoItem::from(instance))); } GlobalAlloc::Memory(alloc) => { items.extend( - alloc - .inner() - .provenance() - .provenances() - .flat_map(|id| collect_alloc_items(tcx, id)), + alloc.provenance.ptrs.iter().flat_map(|(_, prov)| collect_alloc_items(prov.0)), ); } - GlobalAlloc::VTable(ty, trait_ref) => { - let vtable_id = tcx.vtable_allocation((ty, trait_ref)); - items.append(&mut collect_alloc_items(tcx, vtable_id)); + vtable_alloc @ GlobalAlloc::VTable(..) => { + let vtable_id = vtable_alloc.vtable_allocation().unwrap(); + items = collect_alloc_items(vtable_id); } }; items @@ -606,6 +518,7 @@ fn collect_alloc_items(tcx: TyCtxt, alloc_id: AllocId) -> Vec { mod debug { #![allow(dead_code)] + use std::fmt::{Display, Formatter}; use std::{ collections::{HashMap, HashSet}, fs::File, @@ -617,35 +530,39 @@ mod debug { use super::*; #[derive(Debug, Default)] - pub struct CallGraph<'tcx> { + pub struct CallGraph { // Nodes of the graph. - nodes: HashSet>, - edges: HashMap, Vec>>, - back_edges: HashMap, Vec>>, + nodes: HashSet, + edges: HashMap>, + back_edges: HashMap>, } - type Node<'tcx> = MonoItem<'tcx>; + #[derive(Clone, Debug, Eq, PartialEq, Hash)] + struct Node(pub MonoItem); - impl<'tcx> CallGraph<'tcx> { - pub fn add_node(&mut self, item: Node<'tcx>) { - self.nodes.insert(item); - self.edges.entry(item).or_default(); - self.back_edges.entry(item).or_default(); + impl CallGraph { + pub fn add_node(&mut self, item: MonoItem) { + let node = Node(item); + self.nodes.insert(node.clone()); + self.edges.entry(node.clone()).or_default(); + self.back_edges.entry(node).or_default(); } /// Add a new edge "from" -> "to". - pub fn add_edge(&mut self, from: Node<'tcx>, to: Node<'tcx>) { + pub fn add_edge(&mut self, from: MonoItem, to: MonoItem) { + let from_node = Node(from.clone()); + let to_node = Node(to.clone()); self.add_node(from); self.add_node(to); - self.edges.get_mut(&from).unwrap().push(to); - self.back_edges.get_mut(&to).unwrap().push(from); + self.edges.get_mut(&from_node).unwrap().push(to_node.clone()); + self.back_edges.get_mut(&to_node).unwrap().push(from_node); } /// Add multiple new edges for the "from" node. - pub fn add_edges(&mut self, from: Node<'tcx>, to: &[Node<'tcx>]) { - self.add_node(from); + pub fn add_edges(&mut self, from: MonoItem, to: &[MonoItem]) { + self.add_node(from.clone()); for item in to { - self.add_edge(from, *item); + self.add_edge(from.clone(), item.clone()); } } @@ -656,7 +573,7 @@ mod debug { debug!(?target, "dump_dot"); let outputs = tcx.output_filenames(()); let path = outputs.output_path(OutputType::Metadata).with_extension("dot"); - let out_file = File::create(&path)?; + let out_file = File::create(path)?; let mut writer = BufWriter::new(out_file); writeln!(writer, "digraph ReachabilityGraph {{")?; if target.is_empty() { @@ -690,7 +607,7 @@ mod debug { .iter() .filter(|item| item.to_string().contains(target)) .collect::>(); - let mut visited: HashSet<&MonoItem> = HashSet::default(); + let mut visited: HashSet<&Node> = HashSet::default(); tracing::info!(target=?queue, nodes=?self.nodes.len(), edges=?self.edges.len(), "dump_reason"); while let Some(to_visit) = queue.pop() { if !visited.contains(to_visit) { @@ -710,4 +627,14 @@ mod debug { Ok(()) } } + + impl Display for Node { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match &self.0 { + MonoItem::Fn(instance) => write!(f, "{}", instance.name()), + MonoItem::Static(def) => write!(f, "{}", def.name()), + MonoItem::GlobalAsm(asm) => write!(f, "{asm:?}"), + } + } + } } diff --git a/kani-compiler/src/kani_middle/resolve.rs b/kani-compiler/src/kani_middle/resolve.rs index 5c04089cd200..1d3b1a2c2f06 100644 --- a/kani-compiler/src/kani_middle/resolve.rs +++ b/kani-compiler/src/kani_middle/resolve.rs @@ -14,7 +14,7 @@ use std::fmt; use std::iter::Peekable; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX, LOCAL_CRATE}; +use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId, CRATE_DEF_INDEX, LOCAL_CRATE}; use rustc_hir::{ItemKind, UseKind}; use rustc_middle::ty::TyCtxt; use tracing::debug; @@ -90,6 +90,12 @@ pub enum ResolveError<'tcx> { UnexpectedType { tcx: TyCtxt<'tcx>, item: DefId, expected: &'static str }, } +impl<'tcx> fmt::Debug for ResolveError<'tcx> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + std::fmt::Display::fmt(self, f) + } +} + impl<'tcx> fmt::Display for ResolveError<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -153,8 +159,10 @@ fn resolve_prefix<'tcx>( ) -> Result> { debug!(?name, ?current_module, "resolve_prefix"); - // Split the string into segments separated by `::`. - let mut segments = name.split("::").map(str::to_string).peekable(); + // Split the string into segments separated by `::`. Trim the whitespace + // since path strings generated from macros sometimes add spaces around + // `::`. + let mut segments = name.split("::").map(|s| s.trim().to_string()).peekable(); assert!(segments.peek().is_some(), "expected identifier, found `{name}`"); // Resolve qualifiers `crate`, initial `::`, and `self`. The qualifier @@ -182,7 +190,7 @@ fn resolve_prefix<'tcx>( CRATE => { segments.next(); // Find the module at the root of the crate. - let current_module_hir_id = tcx.hir().local_def_id_to_hir_id(current_module); + let current_module_hir_id = tcx.local_def_id_to_hir_id(current_module); let crate_root = match tcx.hir().parent_iter(current_module_hir_id).last() { None => current_module, Some((hir_id, _)) => hir_id.owner.def_id, @@ -221,7 +229,7 @@ fn resolve_super<'tcx, I>( where I: Iterator, { - let current_module_hir_id = tcx.hir().local_def_id_to_hir_id(current_module); + let current_module_hir_id = tcx.local_def_id_to_hir_id(current_module); let mut parents = tcx.hir().parent_iter(current_module_hir_id); let mut base_module = current_module; while segments.next_if(|segment| segment == SUPER).is_some() { @@ -278,7 +286,7 @@ enum RelativeResolution { } /// Resolves a path relative to a local module. -fn resolve_relative(tcx: TyCtxt, current_module: LocalDefId, name: &str) -> RelativeResolution { +fn resolve_relative(tcx: TyCtxt, current_module: LocalModDefId, name: &str) -> RelativeResolution { debug!(?name, ?current_module, "resolve_relative"); let mut glob_imports = vec![]; @@ -318,7 +326,7 @@ fn resolve_in_module<'tcx>( ResolveError::MissingItem { tcx, base: current_module, unresolved: name.to_string() } }), Some(local_id) => { - let result = resolve_relative(tcx, local_id, name); + let result = resolve_relative(tcx, LocalModDefId::new_unchecked(local_id), name); match result { RelativeResolution::Found(def_id) => Ok(def_id), RelativeResolution::Globs(globs) => { @@ -372,7 +380,7 @@ fn resolve_in_glob_uses<'tcx>( fn resolve_in_glob_use(tcx: TyCtxt, res: &Res, name: &str) -> RelativeResolution { if let Res::Def(DefKind::Mod, def_id) = res { if let Some(local_id) = def_id.as_local() { - resolve_relative(tcx, local_id, name) + resolve_relative(tcx, LocalModDefId::new_unchecked(local_id), name) } else { resolve_in_foreign_module(tcx, *def_id, name) .map_or(RelativeResolution::Globs(vec![]), RelativeResolution::Found) diff --git a/kani-compiler/src/kani_middle/stubbing/annotations.rs b/kani-compiler/src/kani_middle/stubbing/annotations.rs index f164f8634b57..a8554cf9e2a2 100644 --- a/kani-compiler/src/kani_middle/stubbing/annotations.rs +++ b/kani-compiler/src/kani_middle/stubbing/annotations.rs @@ -18,7 +18,7 @@ fn stub_def_ids(tcx: TyCtxt, harness: LocalDefId, stub: &Stub) -> Option<(DefId, // Resolve the attribute arguments to `DefId`s let current_module = tcx.parent_module_from_def_id(harness); let resolve = |name: &str| -> Option { - let maybe_resolved = resolve_fn(tcx, current_module, name); + let maybe_resolved = resolve_fn(tcx, current_module.to_local_def_id(), name); match maybe_resolved { Ok(def_id) => { tracing::debug!(?def_id, "Resolved {name} to {}", tcx.def_path_str(def_id)); diff --git a/kani-compiler/src/kani_middle/stubbing/mod.rs b/kani-compiler/src/kani_middle/stubbing/mod.rs index d4a00f44cc01..6af0b442c8d4 100644 --- a/kani-compiler/src/kani_middle/stubbing/mod.rs +++ b/kani-compiler/src/kani_middle/stubbing/mod.rs @@ -6,12 +6,19 @@ mod annotations; mod transform; use std::collections::BTreeMap; +use tracing::{debug, trace}; +pub use self::transform::*; use kani_metadata::HarnessMetadata; use rustc_hir::def_id::DefId; use rustc_hir::definitions::DefPathHash; -use rustc_middle::ty::TyCtxt; -pub use transform::*; +use rustc_middle::mir::Const; +use rustc_middle::ty::{self, EarlyBinder, ParamEnv, TyCtxt, TypeFoldable}; +use rustc_smir::rustc_internal; +use stable_mir::mir::mono::Instance; +use stable_mir::mir::visit::{Location, MirVisitor}; +use stable_mir::mir::Constant; +use stable_mir::{CrateDef, CrateItem}; use self::annotations::update_stub_mapping; @@ -28,3 +35,84 @@ pub fn harness_stub_map( } stub_pairs } + +/// Validate that an instance body can be instantiated. +/// +/// Stubbing may cause an instance to not be correctly instantiated since we delay checking its +/// generic bounds. +/// +/// In stable MIR, trying to retrieve an `Instance::body()` will ICE if we cannot evaluate a +/// constant as expected. For now, use internal APIs to anticipate this issue. +pub fn validate_instance(tcx: TyCtxt, instance: Instance) -> bool { + let internal_instance = rustc_internal::internal(instance); + if get_stub(tcx, internal_instance.def_id()).is_some() { + debug!(?instance, "validate_instance"); + let item = CrateItem::try_from(instance).unwrap(); + let mut checker = StubConstChecker::new(tcx, internal_instance, item); + checker.visit_body(&item.body()); + checker.is_valid() + } else { + true + } +} + +struct StubConstChecker<'tcx> { + tcx: TyCtxt<'tcx>, + instance: ty::Instance<'tcx>, + source: CrateItem, + is_valid: bool, +} + +impl<'tcx> StubConstChecker<'tcx> { + fn new(tcx: TyCtxt<'tcx>, instance: ty::Instance<'tcx>, source: CrateItem) -> Self { + StubConstChecker { tcx, instance, is_valid: true, source } + } + fn monomorphize(&self, value: T) -> T + where + T: TypeFoldable>, + { + trace!(instance=?self.instance, ?value, "monomorphize"); + self.instance.instantiate_mir_and_normalize_erasing_regions( + self.tcx, + ParamEnv::reveal_all(), + EarlyBinder::bind(value), + ) + } + + fn is_valid(&self) -> bool { + self.is_valid + } +} + +impl<'tcx> MirVisitor for StubConstChecker<'tcx> { + /// Collect constants that are represented as static variables. + fn visit_constant(&mut self, constant: &Constant, location: Location) { + let const_ = self.monomorphize(rustc_internal::internal(&constant.literal)); + debug!(?constant, ?location, ?const_, "visit_constant"); + match const_ { + Const::Val(..) | Const::Ty(..) => {} + Const::Unevaluated(un_eval, _) => { + // Thread local fall into this category. + if self.tcx.const_eval_resolve(ParamEnv::reveal_all(), un_eval, None).is_err() { + // The `monomorphize` call should have evaluated that constant already. + let tcx = self.tcx; + let mono_const = &un_eval; + let implementor = match mono_const.args.as_slice() { + [one] => one.as_type().unwrap(), + _ => unreachable!(), + }; + let trait_ = tcx.trait_of_item(mono_const.def).unwrap(); + let msg = format!( + "Type `{implementor}` does not implement trait `{}`. \ + This is likely because `{}` is used as a stub but its \ + generic bounds are not being met.", + tcx.def_path_str(trait_), + self.source.name() + ); + tcx.sess.span_err(rustc_internal::internal(location.span()), msg); + self.is_valid = false; + } + } + }; + } +} diff --git a/kani-compiler/src/kani_middle/stubbing/transform.rs b/kani-compiler/src/kani_middle/stubbing/transform.rs index 2a164623ba24..a455a8d5e7ca 100644 --- a/kani-compiler/src/kani_middle/stubbing/transform.rs +++ b/kani-compiler/src/kani_middle/stubbing/transform.rs @@ -12,7 +12,13 @@ use lazy_static::lazy_static; use regex::Regex; use rustc_data_structures::fingerprint::Fingerprint; use rustc_hir::{def_id::DefId, definitions::DefPathHash}; -use rustc_middle::{mir::Body, ty::TyCtxt}; +use rustc_index::IndexVec; +use rustc_middle::mir::{ + visit::MutVisitor, Body, Const, ConstValue, Local, LocalDecl, Location, Operand, +}; +use rustc_middle::ty::{self, TyCtxt}; + +use tracing::debug; /// Returns the `DefId` of the stub for the function/method identified by the /// parameter `def_id`, and `None` if the function/method is not stubbed. @@ -23,18 +29,63 @@ pub fn get_stub(tcx: TyCtxt, def_id: DefId) -> Option { /// Returns the new body of a function/method if it has been stubbed out; /// otherwise, returns the old body. -pub fn transform<'tcx>( - tcx: TyCtxt<'tcx>, - def_id: DefId, - old_body: &'tcx Body<'tcx>, -) -> &'tcx Body<'tcx> { +pub fn transform<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, old_body: &'tcx Body<'tcx>) -> Body<'tcx> { if let Some(replacement) = get_stub(tcx, def_id) { + debug!( + original = tcx.def_path_debug_str(def_id), + replaced = tcx.def_path_debug_str(replacement), + "transform" + ); let new_body = tcx.optimized_mir(replacement).clone(); if check_compatibility(tcx, def_id, old_body, replacement, &new_body) { - return tcx.arena.alloc(new_body); + return new_body; + } + } + old_body.clone() +} + +/// Traverse `body` searching for calls to foreing functions and, whevever there is +/// a stub available, replace the call to the foreign function with a call +/// to its correspondent stub. This happens as a separate step because there is no +/// body available to foreign functions at this stage. +pub fn transform_foreign_functions<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { + if let Some(stub_map) = get_stub_mapping(tcx) { + let mut visitor = + ForeignFunctionTransformer { tcx, local_decls: body.clone().local_decls, stub_map }; + visitor.visit_body(body); + } +} + +struct ForeignFunctionTransformer<'tcx> { + /// The compiler context. + tcx: TyCtxt<'tcx>, + /// Local declarations of the callee function. Kani searches here for foreign functions. + local_decls: IndexVec>, + /// Map of functions/methods to their correspondent stubs. + stub_map: HashMap, +} + +impl<'tcx> MutVisitor<'tcx> for ForeignFunctionTransformer<'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn visit_operand(&mut self, operand: &mut Operand<'tcx>, _location: Location) { + let func_ty = operand.ty(&self.local_decls, self.tcx); + if let ty::FnDef(reachable_function, arguments) = *func_ty.kind() { + if self.tcx.is_foreign_item(reachable_function) { + if let Some(stub) = self.stub_map.get(&reachable_function) { + let Operand::Constant(function_definition) = operand else { + return; + }; + function_definition.const_ = Const::from_value( + ConstValue::ZeroSized, + self.tcx.type_of(stub).instantiate(self.tcx, arguments), + ); + } + } } } - old_body } /// Checks whether the stub is compatible with the original function/method: do diff --git a/kani-compiler/src/kani_queries/mod.rs b/kani-compiler/src/kani_queries/mod.rs index b8446bb7a023..7c716a5f38a5 100644 --- a/kani-compiler/src/kani_queries/mod.rs +++ b/kani-compiler/src/kani_queries/mod.rs @@ -8,35 +8,13 @@ use std::{ path::PathBuf, sync::{Arc, Mutex}, }; -use strum_macros::{AsRefStr, EnumString, EnumVariantNames}; - -#[derive(Debug, Default, Clone, Copy, AsRefStr, EnumString, EnumVariantNames, PartialEq, Eq)] -#[strum(serialize_all = "snake_case")] -pub enum ReachabilityType { - /// Start the cross-crate reachability analysis from all harnesses in the local crate. - Harnesses, - /// Don't perform any reachability analysis. This will skip codegen for this crate. - #[default] - None, - /// Start the cross-crate reachability analysis from all public functions in the local crate. - PubFns, - /// Start the cross-crate reachability analysis from all *test* (i.e. `#[test]`) harnesses in the local crate. - Tests, -} + +use crate::args::Arguments; /// This structure should only be used behind a synchronized reference or a snapshot. #[derive(Debug, Default, Clone)] pub struct QueryDb { - pub check_assertion_reachability: bool, - pub emit_vtable_restrictions: bool, - pub output_pretty_json: bool, - pub ignore_global_asm: bool, - /// When set, instructs the compiler to produce the symbol table for CBMC in JSON format and use symtab2gb. - pub write_json_symtab: bool, - pub reachability_analysis: ReachabilityType, - pub stubbing_enabled: bool, - pub unstable_features: Vec, - + args: Option, /// Information about all target harnesses. pub harnesses_info: HashMap, } @@ -55,4 +33,12 @@ impl QueryDb { pub fn harness_model_path(&self, harness: &DefPathHash) -> Option<&PathBuf> { self.harnesses_info.get(harness) } + + pub fn set_args(&mut self, args: Arguments) { + self.args = Some(args); + } + + pub fn args(&self) -> &Arguments { + self.args.as_ref().expect("Arguments have not been initialized") + } } diff --git a/kani-compiler/src/main.rs b/kani-compiler/src/main.rs index 448466ce8d64..4316d188c02d 100644 --- a/kani-compiler/src/main.rs +++ b/kani-compiler/src/main.rs @@ -6,7 +6,6 @@ //! //! Like miri, clippy, and other tools developed on the top of rustc, we rely on the //! rustc_private feature and a specific version of rustc. -#![deny(warnings)] #![feature(extern_types)] #![recursion_limit = "256"] #![feature(box_patterns)] @@ -27,17 +26,19 @@ extern crate rustc_interface; extern crate rustc_metadata; extern crate rustc_middle; extern crate rustc_session; +extern crate rustc_smir; extern crate rustc_span; extern crate rustc_target; +extern crate stable_mir; // We can't add this directly as a dependency because we need the version to match rustc extern crate tempfile; +mod args; #[cfg(feature = "cprover")] mod codegen_cprover_gotoc; mod kani_compiler; mod kani_middle; mod kani_queries; -mod parser; mod session; use rustc_driver::{RunCompiler, TimePassesCallbacks}; @@ -47,7 +48,7 @@ use std::process::ExitCode; /// Main function. Configure arguments and run the compiler. fn main() -> ExitCode { session::init_panic_hook(); - let (kani_compiler, rustc_args) = parser::is_kani_compiler(env::args().collect()); + let (kani_compiler, rustc_args) = is_kani_compiler(env::args().collect()); // Configure and run compiler. if kani_compiler { @@ -58,3 +59,27 @@ fn main() -> ExitCode { if compiler.run().is_err() { ExitCode::FAILURE } else { ExitCode::SUCCESS } } } + +/// Return whether we should run our flavour of the compiler, and which arguments to pass to rustc. +/// +/// We add a `--kani-compiler` argument to run the Kani version of the compiler, which needs to be +/// filtered out before passing the arguments to rustc. +/// +/// All other Kani arguments are today located inside `--llvm-args`. +pub fn is_kani_compiler(args: Vec) -> (bool, Vec) { + assert!(!args.is_empty(), "Arguments should always include executable name"); + const KANI_COMPILER: &str = "--kani-compiler"; + let mut has_kani_compiler = false; + let new_args = args + .into_iter() + .filter(|arg| { + if arg == KANI_COMPILER { + has_kani_compiler = true; + false + } else { + true + } + }) + .collect(); + (has_kani_compiler, new_args) +} diff --git a/kani-compiler/src/parser.rs b/kani-compiler/src/parser.rs deleted file mode 100644 index 2ff81a90bc7c..000000000000 --- a/kani-compiler/src/parser.rs +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -use crate::kani_queries::ReachabilityType; -use clap::{builder::PossibleValuesParser, command, Arg, ArgAction, ArgMatches, Command}; -use std::env; -use std::str::FromStr; -use strum::VariantNames as _; - -/// Option name used to set log level. -pub const LOG_LEVEL: &str = "log-level"; - -/// Option name used to enable goto-c compilation. -pub const GOTO_C: &str = "goto-c"; - -/// Option name used to override Kani library path. -pub const KANI_LIB: &str = "kani-lib"; - -/// Option name used to set the log output to a json file. -pub const JSON_OUTPUT: &str = "json-output"; - -/// Option name used to force logger to use color output. This doesn't work with --json-output. -pub const COLOR_OUTPUT: &str = "color-output"; - -/// Option name used to dump function pointer restrictions. -pub const RESTRICT_FN_PTRS: &str = "restrict-vtable-fn-ptrs"; - -/// Option name used to enable assertion reachability checks. -pub const ASSERTION_REACH_CHECKS: &str = "assertion-reach-checks"; - -/// Option name used to use json pretty-print for output files. -pub const PRETTY_OUTPUT_FILES: &str = "pretty-json-files"; - -/// Option used for suppressing global ASM error. -pub const IGNORE_GLOBAL_ASM: &str = "ignore-global-asm"; - -/// Option used to write JSON symbol tables instead of GOTO binaries. -pub const WRITE_JSON_SYMTAB: &str = "write-json-symtab"; - -/// Option name used to select which reachability analysis to perform. -pub const REACHABILITY: &str = "reachability"; - -/// Option name used to enable stubbing. -pub const ENABLE_STUBBING: &str = "enable-stubbing"; - -/// Option name used to define unstable features. -pub const UNSTABLE_FEATURE: &str = "unstable"; - -/// Configure command options for the Kani compiler. -pub fn parser() -> Command { - let app = command!() - .arg( - Arg::new(KANI_LIB) - .long(KANI_LIB) - .value_name("FOLDER_PATH") - .help("Sets the path to locate the kani library.") - .action(ArgAction::Set), - ) - .arg( - Arg::new(GOTO_C) - .long(GOTO_C) - .help("Enables compilation to goto-c intermediate representation.") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(LOG_LEVEL) - .long(LOG_LEVEL) - .value_parser(["error", "warn", "info", "debug", "trace"]) - .value_name("LOG_LEVEL") - .help( - "Sets the maximum log level to the value given. Use KANI_LOG for more granular \ - control.", - ) - .action(ArgAction::Set), - ) - .arg( - Arg::new(JSON_OUTPUT) - .long(JSON_OUTPUT) - .help("Print output including logs in json format.") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(COLOR_OUTPUT) - .long(COLOR_OUTPUT) - .help("Print output using colors.") - .conflicts_with(JSON_OUTPUT) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(RESTRICT_FN_PTRS) - .long(RESTRICT_FN_PTRS) - .help("Restrict the targets of virtual table function pointer calls.") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(ASSERTION_REACH_CHECKS) - .long(ASSERTION_REACH_CHECKS) - .help("Check the reachability of every assertion.") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(REACHABILITY) - .long(REACHABILITY) - .value_parser(PossibleValuesParser::new(ReachabilityType::VARIANTS)) - .required(false) - .default_value(ReachabilityType::None.as_ref()) - .help("Selects the type of reachability analysis to perform.") - .action(ArgAction::Set), - ) - .arg( - Arg::new(PRETTY_OUTPUT_FILES) - .long(PRETTY_OUTPUT_FILES) - .help("Output json files in a more human-readable format (with spaces).") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(IGNORE_GLOBAL_ASM) - .long(IGNORE_GLOBAL_ASM) - .help("Suppress error due to the existence of global_asm in a crate") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(WRITE_JSON_SYMTAB) - .long(WRITE_JSON_SYMTAB) - .help("Instruct the compiler to produce GotoC symbol tables in JSON format instead of GOTO binary format.") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(ENABLE_STUBBING) - .long(ENABLE_STUBBING) - .help("Instruct the compiler to perform stubbing.") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("check-version") - .long("check-version") - .action(ArgAction::Set) - .help("Pass the kani version to the compiler to ensure cache coherence."), - ) - .arg( - Arg::new(UNSTABLE_FEATURE) - .long(UNSTABLE_FEATURE) - .help("Enable an unstable feature") - .value_name("UNSTABLE_FEATURE") - .action(ArgAction::Append), - ); - app -} - -pub trait KaniCompilerParser { - fn reachability_type(&self) -> ReachabilityType; -} - -impl KaniCompilerParser for ArgMatches { - fn reachability_type(&self) -> ReachabilityType { - self.get_one::(REACHABILITY) - .map_or(ReachabilityType::None, |arg| ReachabilityType::from_str(arg).unwrap()) - } -} - -/// Return whether we should run our flavour of the compiler, and which arguments to pass to rustc. -/// -/// We add a `--kani-compiler` argument to run the Kani version of the compiler, which needs to be -/// filtered out before passing the arguments to rustc. -/// -/// All other Kani arguments are today located inside `--llvm-args`. -pub fn is_kani_compiler(args: Vec) -> (bool, Vec) { - assert!(!args.is_empty(), "Arguments should always include executable name"); - const KANI_COMPILER: &str = "--kani-compiler"; - let mut has_kani_compiler = false; - let new_args = args - .into_iter() - .filter(|arg| { - if arg == KANI_COMPILER { - has_kani_compiler = true; - false - } else { - true - } - }) - .collect(); - (has_kani_compiler, new_args) -} - -#[cfg(test)] -mod parser_test { - use super::*; - - #[test] - fn test_rustc_version() { - let args = vec!["kani-compiler", "-V"]; - let matches = parser().get_matches_from(args); - assert!(matches.get_flag("rustc-version")); - } - - #[test] - fn test_kani_flags() { - let args = vec!["kani-compiler", "--goto-c", "--kani-lib", "some/path"]; - let matches = parser().get_matches_from(args); - assert!(matches.get_flag("goto-c")); - assert_eq!(matches.get_one::("kani-lib"), Some(&"some/path".to_string())); - } - - #[test] - fn test_stubbing_flags() { - let args = vec!["kani-compiler", "--enable-stubbing"]; - let matches = parser().get_matches_from(args); - assert!(matches.get_flag("enable-stubbing")); - } - - #[test] - fn test_cargo_kani_hack_noop() { - let args = ["kani-compiler", "some/path"]; - let args = args.map(String::from); - let (is_kani, new_args) = is_kani_compiler(Vec::from(args.clone())); - assert_eq!(args.as_slice(), new_args.as_slice()); - assert!(!is_kani); - } - - #[test] - fn test_cargo_kani_hack_no_args() { - let args = ["kani_compiler", "some/path", "--kani-compiler"]; - let args = args.map(String::from); - let (is_kani, new_args) = is_kani_compiler(Vec::from(args.clone())); - assert_eq!(new_args.len(), 2, "New args should not include --kani-compiler"); - assert_eq!(new_args[0], args[0]); - assert_eq!(new_args[1], args[1]); - assert!(is_kani); - } -} diff --git a/kani-compiler/src/session.rs b/kani-compiler/src/session.rs index 6061554200dc..87d25f1e8fc3 100644 --- a/kani-compiler/src/session.rs +++ b/kani-compiler/src/session.rs @@ -3,17 +3,17 @@ //! Module used to configure a compiler session. -use crate::parser; -use clap::ArgMatches; +use crate::args::Arguments; use rustc_errors::{ emitter::Emitter, emitter::HumanReadableErrorType, fallback_fluent_bundle, json::JsonEmitter, ColorConfig, Diagnostic, TerminalUrl, }; +use rustc_session::config::ErrorOutputType; +use rustc_session::EarlyErrorHandler; use std::io::IsTerminal; use std::panic; -use std::str::FromStr; use std::sync::LazyLock; -use tracing_subscriber::{filter::Directive, layer::SubscriberExt, EnvFilter, Registry}; +use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry}; /// Environment variable used to control this session log tracing. const LOG_ENV_VAR: &str = "KANI_LOG"; @@ -67,11 +67,12 @@ static JSON_PANIC_HOOK: LazyLock) + Sync + Send }); /// Initialize compiler session. -pub fn init_session(args: &ArgMatches, json_hook: bool) { +pub fn init_session(args: &Arguments, json_hook: bool) { // Initialize the rustc logger using value from RUSTC_LOG. We keep the log control separate // because we cannot control the RUSTC log format unless if we match the exact tracing // version used by RUSTC. - rustc_driver::init_rustc_env_logger(); + let handler = EarlyErrorHandler::new(ErrorOutputType::default()); + rustc_driver::init_rustc_env_logger(&handler); // Install Kani panic hook. if json_hook { @@ -83,15 +84,15 @@ pub fn init_session(args: &ArgMatches, json_hook: bool) { } /// Initialize the logger using the KANI_LOG environment variable and the --log-level argument. -fn init_logger(args: &ArgMatches) { +fn init_logger(args: &Arguments) { let filter = EnvFilter::from_env(LOG_ENV_VAR); - let filter = if let Some(log_level) = args.get_one::(parser::LOG_LEVEL) { - filter.add_directive(Directive::from_str(log_level).unwrap()) + let filter = if let Some(log_level) = &args.log_level { + filter.add_directive(log_level.clone()) } else { filter }; - if args.get_flag(parser::JSON_OUTPUT) { + if args.json_output { json_logs(filter); } else { hier_logs(args, filter); @@ -106,8 +107,8 @@ fn json_logs(filter: EnvFilter) { } /// Configure global logger to use a hierarchical view. -fn hier_logs(args: &ArgMatches, filter: EnvFilter) { - let use_colors = std::io::stdout().is_terminal() || args.get_flag(parser::COLOR_OUTPUT); +fn hier_logs(args: &Arguments, filter: EnvFilter) { + let use_colors = std::io::stdout().is_terminal() || args.color_output; let subscriber = Registry::default().with(filter); let subscriber = subscriber.with( tracing_subscriber::fmt::layer() diff --git a/kani-dependencies b/kani-dependencies index 498182678d6f..200755839284 100644 --- a/kani-dependencies +++ b/kani-dependencies @@ -1,4 +1,10 @@ -CBMC_VERSION="5.87.0" +CBMC_MAJOR="5" +CBMC_MINOR="95" +CBMC_VERSION="5.95.1" + # If you update this version number, remember to bump it in `src/setup.rs` too +CBMC_VIEWER_MAJOR="3" +CBMC_VIEWER_MINOR="8" CBMC_VIEWER_VERSION="3.8" -KISSAT_VERSION="3.0.0" + +KISSAT_VERSION="3.1.1" diff --git a/kani-driver/Cargo.toml b/kani-driver/Cargo.toml index a4385206f83f..ca0161652ae4 100644 --- a/kani-driver/Cargo.toml +++ b/kani-driver/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-driver" -version = "0.32.0" +version = "0.42.0" edition = "2021" description = "Build a project with Kani and run all proof harnesses" license = "MIT OR Apache-2.0" @@ -13,26 +13,27 @@ publish = false [dependencies] kani_metadata = { path = "../kani_metadata" } -cargo_metadata = "0.15.0" +cargo_metadata = "0.18.0" anyhow = "1" console = "0.15.1" -once_cell = "1.13.0" +once_cell = "1.19.0" serde = { version = "1", features = ["derive"] } serde_json = "1" -clap = { version = "4.1.3", features = ["derive"] } +clap = { version = "4.4.11", features = ["derive"] } glob = "0.3" -toml = "0.7" +toml = "0.8" regex = "1.6" rustc-demangle = "0.1.21" pathdiff = "0.2.1" rayon = "1.5.3" -comfy-table = "6.0.0" -strum = {version = "0.24.0"} -strum_macros = {version = "0.24.0"} +comfy-table = "7.0.1" +strum = {version = "0.25.0"} +strum_macros = {version = "0.25.2"} +tempfile = "3" tracing = {version = "0.1", features = ["max_level_trace", "release_max_level_debug"]} tracing-subscriber = {version = "0.3.8", features = ["env-filter", "json", "fmt"]} rand = "0.8" -which = "4.4.0" +which = "5.0.0" # A good set of suggested dependencies can be found in rustup: # https://github.com/rust-lang/rustup/blob/master/Cargo.toml diff --git a/kani-driver/src/args/common.rs b/kani-driver/src/args/common.rs index 746482c6985e..d4ac0a61bd17 100644 --- a/kani-driver/src/args/common.rs +++ b/kani-driver/src/args/common.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Define arguments that should be common to all subcommands in Kani. use crate::args::ValidateArgs; -use clap::{error::Error, error::ErrorKind, ValueEnum}; +use clap::{error::Error, error::ErrorKind}; +pub use kani_metadata::{EnabledUnstableFeatures, UnstableFeature}; /// Common Kani arguments that we expect to be included in most subcommands. #[derive(Debug, clap::Args)] @@ -26,24 +27,8 @@ pub struct CommonArgs { pub dry_run: bool, /// Enable an unstable feature. - #[arg(short = 'Z', num_args(1), value_name = "UNSTABLE_FEATURE")] - pub unstable_features: Vec, -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum, strum_macros::Display)] -#[strum(serialize_all = "kebab-case")] -pub enum UnstableFeatures { - /// Allow replacing certain items with stubs (mocks). - /// See [RFC-0002](https://model-checking.github.io/kani/rfc/rfcs/0002-function-stubbing.html) - Stubbing, - /// Generate a C-like file equivalent to input program used for debugging purpose. - GenC, - /// Allow Kani to link against C code. - CFfi, - /// Enable concrete playback flow. - ConcretePlayback, - /// Enable Kani's unstable async library. - AsyncLib, + #[clap(flatten)] + pub unstable_features: EnabledUnstableFeatures, } impl ValidateArgs for CommonArgs { diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index 31cae95e2a61..6651f72db7c2 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -208,6 +208,7 @@ pub struct VerificationArgs { #[arg(long, requires("harnesses"))] pub unwind: Option, /// Specify the CBMC solver to use. Overrides the harness `solver` attribute. + /// If no solver is specified (with --solver or harness attribute), Kani will use CaDiCaL. #[arg(long, value_parser = CbmcSolverValueParser::new(CbmcSolver::VARIANTS))] pub solver: Option, /// Pass through directly to CBMC; must be the last flag. @@ -225,18 +226,6 @@ pub struct VerificationArgs { #[arg(short, long, hide = true, requires("enable_unstable"))] pub jobs: Option>, - // Hide option till https://github.com/model-checking/kani/issues/697 is - // fixed. - /// Use abstractions for the standard library. - /// This is an experimental feature and requires `--enable-unstable` to be used - #[arg(long, hide = true, requires("enable_unstable"))] - pub use_abs: bool, - // Hide option till https://github.com/model-checking/kani/issues/697 is - // fixed. - /// Choose abstraction for modules of standard library if available - #[arg(long, default_value = "std", ignore_case = true, hide = true, value_enum)] - pub abs_type: AbstractionType, - /// Enable extra pointer checks such as invalid pointers in relation operations and pointer /// arithmetic overflow. /// This feature is unstable and it may yield false counter examples. It requires @@ -298,7 +287,11 @@ pub struct VerificationArgs { requires("enable_unstable"), conflicts_with("concrete_playback") )] - pub enable_stubbing: bool, + enable_stubbing: bool, + + /// Enable Kani coverage output alongside verification result + #[arg(long, hide_short_help = true)] + pub coverage: bool, /// Arguments to pass down to Cargo #[command(flatten)] @@ -340,6 +333,18 @@ impl VerificationArgs { Some(Some(x)) => Some(x), // -j=x } } + + /// Are experimental function contracts enabled? + pub fn is_function_contracts_enabled(&self) -> bool { + self.common_args.unstable_features.contains(UnstableFeature::FunctionContracts) + } + + /// Is experimental stubbing enabled? + pub fn is_stubbing_enabled(&self) -> bool { + self.enable_stubbing + || self.common_args.unstable_features.contains(UnstableFeature::Stubbing) + || self.is_function_contracts_enabled() + } } #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)] @@ -357,32 +362,6 @@ pub enum OutputFormat { Old, } -#[derive(Clone, Debug, PartialEq, Eq, ValueEnum)] -pub enum AbstractionType { - Std, - Kani, - // Clap defaults to `c-ffi` - CFfi, - // Clap defaults to `no-back` - NoBack, -} -impl std::fmt::Display for AbstractionType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Std => f.write_str("std"), - Self::Kani => f.write_str("kani"), - Self::CFfi => f.write_str("c-ffi"), - Self::NoBack => f.write_str("no-back"), - } - } -} -#[cfg(test)] -impl AbstractionType { - pub fn variants() -> Vec<&'static str> { - vec!["std", "kani", "c-ffi", "no-back"] - } -} - #[derive(Debug, clap::Args)] pub struct CheckArgs { // Rust argument parsers (/clap) don't have the convenient '--flag' and '--no-flag' boolean pairs, so approximate @@ -443,12 +422,12 @@ impl CheckArgs { /// /// We currently define a bunch of cargo specific arguments as part of the overall arguments, /// however, they are invalid in the Kani standalone usage. Explicitly check them for now. -/// TODO: Remove this as part of https://github.com/model-checking/kani/issues/1831 +/// TODO: Remove this as part of 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 `{}` cannot be used with standalone Kani.", name), )) } else { Ok(()) @@ -474,7 +453,7 @@ impl ValidateArgs for StandaloneArgs { if !input.is_file() { return Err(Error::raw( ErrorKind::InvalidValue, - &format!( + format!( "Invalid argument: Input invalid. `{}` is not a regular file.", input.display() ), @@ -604,7 +583,7 @@ impl ValidateArgs for VerificationArgs { if out_dir.exists() && !out_dir.is_dir() { return Err(Error::raw( ErrorKind::InvalidValue, - &format!( + format!( "Invalid argument: `--target-dir` argument `{}` is not a directory", out_dir.display() ), @@ -612,8 +591,12 @@ impl ValidateArgs for VerificationArgs { } } + if self.enable_stubbing { + print_deprecated(&self.common_args, "--enable-stubbing", "-Z stubbing"); + } + if self.concrete_playback.is_some() - && !self.common_args.unstable_features.contains(&UnstableFeatures::ConcretePlayback) + && !self.common_args.unstable_features.contains(UnstableFeature::ConcretePlayback) { if self.common_args.enable_unstable { print_deprecated(&self.common_args, "--enable-unstable", "-Z concrete-playback"); @@ -627,7 +610,7 @@ impl ValidateArgs for VerificationArgs { } if !self.c_lib.is_empty() - && !self.common_args.unstable_features.contains(&UnstableFeatures::CFfi) + && !self.common_args.unstable_features.contains(UnstableFeature::CFfi) { if self.common_args.enable_unstable { print_deprecated(&self.common_args, "`--enable-unstable`", "-Z c-ffi"); @@ -640,6 +623,16 @@ impl ValidateArgs for VerificationArgs { } } + if self.coverage + && !self.common_args.unstable_features.contains(UnstableFeature::LineCoverage) + { + return Err(Error::raw( + ErrorKind::MissingRequiredArgument, + "The `--coverage` argument is unstable and requires `-Z \ + line-coverage` to be used.", + )); + } + Ok(()) } } @@ -787,18 +780,6 @@ mod tests { }; } - #[test] - fn check_abs_type() { - // Since we manually implemented this, consistency check it - for t in AbstractionType::variants() { - assert_eq!(t, format!("{}", AbstractionType::from_str(t, false).unwrap())); - } - check_opt!("--abs-type std", false, abs_type, AbstractionType::Std); - check_opt!("--abs-type kani", false, abs_type, AbstractionType::Kani); - check_opt!("--abs-type c-ffi", false, abs_type, AbstractionType::CFfi); - check_opt!("--abs-type no-back", false, abs_type, AbstractionType::NoBack); - } - #[test] fn check_dry_run_fails() { // We don't support --dry-run anymore but we print a friendly reminder for now. @@ -834,11 +815,6 @@ mod tests { StandaloneArgs::try_parse_from(args.split(' ')) } - #[test] - fn check_abs_unstable() { - check_unstable_flag!("--use-abs", use_abs); - } - #[test] fn check_restrict_vtable_unstable() { check_unstable_flag!("--restrict-vtable", restrict_vtable); diff --git a/kani-driver/src/args/playback_args.rs b/kani-driver/src/args/playback_args.rs index eabeaae72a9d..bdad446a1158 100644 --- a/kani-driver/src/args/playback_args.rs +++ b/kani-driver/src/args/playback_args.rs @@ -3,7 +3,7 @@ //! Implements the subcommand handling of the playback subcommand use crate::args::cargo::CargoTestArgs; -use crate::args::common::UnstableFeatures; +use crate::args::common::UnstableFeature; use crate::args::{CommonArgs, ValidateArgs}; use clap::error::ErrorKind; use clap::{Error, Parser, ValueEnum}; @@ -74,7 +74,7 @@ impl ValidateArgs for KaniPlaybackArgs { if !self.input.is_file() { return Err(Error::raw( ErrorKind::InvalidValue, - &format!( + format!( "Invalid argument: Input invalid. `{}` is not a regular file.", self.input.display() ), @@ -87,7 +87,7 @@ impl ValidateArgs for KaniPlaybackArgs { impl ValidateArgs for PlaybackArgs { fn validate(&self) -> Result<(), Error> { self.common_opts.validate()?; - if !self.common_opts.unstable_features.contains(&UnstableFeatures::ConcretePlayback) { + if !self.common_opts.unstable_features.contains(UnstableFeature::ConcretePlayback) { return Err(Error::raw( ErrorKind::MissingRequiredArgument, "The `playback` subcommand is unstable and requires `-Z concrete-playback` \ diff --git a/kani-driver/src/assess/metadata.rs b/kani-driver/src/assess/metadata.rs index 122261ce66bb..d555f1e4d309 100644 --- a/kani-driver/src/assess/metadata.rs +++ b/kani-driver/src/assess/metadata.rs @@ -88,7 +88,7 @@ pub struct SessionError { /// If given the argument to so do, write the assess metadata to the target file. pub(crate) fn write_metadata(args: &AssessArgs, metadata: AssessMetadata) -> Result<()> { if let Some(path) = &args.emit_metadata { - let out_file = File::create(&path)?; + let out_file = File::create(path)?; let writer = BufWriter::new(out_file); // use pretty for now to keep things readable and debuggable, but this should change eventually serde_json::to_writer_pretty(writer, &metadata)?; diff --git a/kani-driver/src/assess/scan.rs b/kani-driver/src/assess/scan.rs index 88bd8f643266..5e4dc81d7e2a 100644 --- a/kani-driver/src/assess/scan.rs +++ b/kani-driver/src/assess/scan.rs @@ -179,7 +179,7 @@ fn invoke_assess( // Additionally, this should be `--manifest-path` but `cargo kani` doesn't support that yet. cmd.arg("-p").arg(package); cmd.arg("--enable-unstable"); // This has to be after `-p` due to an argument parsing bug in kani-driver - cmd.args(&["assess", "--emit-metadata"]) + cmd.args(["assess", "--emit-metadata"]) .arg(outfile) .current_dir(dir) .stdout(log.try_clone()?) @@ -201,10 +201,16 @@ fn scan_cargo_projects(path: PathBuf, accumulator: &mut Vec) { return; } // Errors are silently skipped entirely here - let Ok(entries) = std::fs::read_dir(path) else { return; }; + let Ok(entries) = std::fs::read_dir(path) else { + return; + }; for entry in entries { - let Ok(entry) = entry else { continue; }; - let Ok(typ) = entry.file_type() else { continue; }; + let Ok(entry) = entry else { + continue; + }; + let Ok(typ) = entry.file_type() else { + continue; + }; // symlinks are not `is_dir()` if typ.is_dir() { scan_cargo_projects(entry.path(), accumulator) diff --git a/kani-driver/src/call_cargo.rs b/kani-driver/src/call_cargo.rs index 82186e96a44e..66166913c9cb 100644 --- a/kani-driver/src/call_cargo.rs +++ b/kani-driver/src/call_cargo.rs @@ -319,8 +319,14 @@ fn packages_to_verify<'b>( .map(|pkg_name| metadata.packages.iter().find(|pkg| pkg.name == *pkg_name).unwrap()) .collect() } else if !args.cargo.exclude.is_empty() { + // should be ensured by argument validation + assert!(args.cargo.workspace); validate_package_names(&args.cargo.exclude, &metadata.packages)?; - metadata.packages.iter().filter(|pkg| !args.cargo.exclude.contains(&pkg.name)).collect() + metadata + .workspace_packages() + .into_iter() + .filter(|pkg| !args.cargo.exclude.contains(&pkg.name)) + .collect() } else { match (args.cargo.workspace, metadata.root_package()) { (true, _) | (_, None) => metadata.workspace_packages(), @@ -346,8 +352,7 @@ fn map_kani_artifact(rustc_artifact: cargo_metadata::Artifact) -> Option String { + pub fn render( + &self, + output_format: &OutputFormat, + should_panic: bool, + coverage_mode: bool, + ) -> String { match &self.results { Ok(results) => { let status = self.status; let failed_properties = self.failed_properties; let show_checks = matches!(output_format, OutputFormat::Regular); - let mut result = - format_result(results, status, should_panic, failed_properties, show_checks); + + let mut result = if coverage_mode { + format_coverage(results, status, should_panic, failed_properties, show_checks) + } else { + format_result(results, status, should_panic, failed_properties, show_checks) + }; writeln!(result, "Verification Time: {}s", self.runtime.as_secs_f32()).unwrap(); result } Err(exit_status) => { let verification_result = console::style("FAILED").red(); + let explanation = if *exit_status == 137 { + "CBMC appears to have run out of memory. You may want to rerun your proof in \ + an environment with additional memory or use stubbing to reduce the size of the \ + code the verifier reasons about.\n" + } else { + "" + }; format!( - "\nCBMC failed with status {exit_status}\nVERIFICATION:- {verification_result}\n", + "\nCBMC failed with status {exit_status}\n\ + VERIFICATION:- {verification_result}\n\ + {explanation}", ) } } diff --git a/kani-driver/src/call_goto_cc.rs b/kani-driver/src/call_goto_cc.rs index 1d36bfccb744..b906c256904a 100644 --- a/kani-driver/src/call_goto_cc.rs +++ b/kani-driver/src/call_goto_cc.rs @@ -6,7 +6,6 @@ use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::process::Command; -use crate::args::AbstractionType; use crate::session::KaniSession; impl KaniSession { @@ -17,15 +16,6 @@ impl KaniSession { args.extend(inputs.iter().map(|x| x.clone().into_os_string())); args.extend(self.args.c_lib.iter().map(|x| x.clone().into_os_string())); - // Special case hack for handling the "c-ffi" abs-type - if self.args.use_abs && self.args.abs_type == AbstractionType::CFfi { - let vec = self.kani_c_stubs.join("vec/vec.c"); - let hashset = self.kani_c_stubs.join("hashset/hashset.c"); - - args.push(vec.into_os_string()); - args.push(hashset.into_os_string()); - } - // TODO think about this: kani_lib_c is just an empty c file. Maybe we could just // create such an empty file ourselves instead of having to look up this path. args.push(self.kani_lib_c.clone().into_os_string()); diff --git a/kani-driver/src/call_goto_synthesizer.rs b/kani-driver/src/call_goto_synthesizer.rs index 277201cabb18..9853123dc29a 100644 --- a/kani-driver/src/call_goto_synthesizer.rs +++ b/kani-driver/src/call_goto_synthesizer.rs @@ -35,7 +35,18 @@ impl KaniSession { output.to_owned().into_os_string(), // output ]; + // goto-synthesizer should take the same backend options as cbmc. + // Backend options include + // 1. solver options self.handle_solver_args(&harness_metadata.attributes.solver, &mut args)?; + // 2. object-bits option + if let Some(object_bits) = self.args.cbmc_object_bits() { + args.push("--object-bits".into()); + args.push(object_bits.to_string().into()); + } + // 3. and array-as-uninterpreted-functions options, which should be included + // in the cbmc_args. + args.extend(self.args.cbmc_args.iter().cloned()); let mut cmd = Command::new("goto-synthesizer"); cmd.args(args); diff --git a/kani-driver/src/call_single_file.rs b/kani-driver/src/call_single_file.rs index caf3dd7339a3..9b6e0598daa5 100644 --- a/kani-driver/src/call_single_file.rs +++ b/kani-driver/src/call_single_file.rs @@ -92,17 +92,15 @@ impl KaniSession { flags.push("--write-json-symtab".into()); } - if self.args.enable_stubbing { + if self.args.is_stubbing_enabled() { flags.push("--enable-stubbing".into()); } - flags.extend( - self.args - .common_args - .unstable_features - .iter() - .map(|feature| format!("--unstable={feature}")), - ); + if self.args.coverage { + flags.push("--coverage-checks".into()); + } + + flags.extend(self.args.common_args.unstable_features.as_arguments().map(str::to_string)); // This argument will select the Kani flavour of the compiler. It will be removed before // rustc driver is invoked. @@ -127,14 +125,6 @@ impl KaniSession { ] .map(OsString::from), ); - if self.args.use_abs { - flags.push("-Z".into()); - flags.push("force-unstable-if-unmarked=yes".into()); // ?? - flags.push("--cfg=use_abs".into()); - flags.push("--cfg".into()); - let abs_type = format!("abs_type={}", self.args.abs_type.to_string().to_lowercase()); - flags.push(abs_type.into()); - } if let Some(seed_opt) = self.args.randomize_layout { flags.push("-Z".into()); diff --git a/kani-driver/src/cbmc_output_parser.rs b/kani-driver/src/cbmc_output_parser.rs index 70e13b7dda06..1bb353d7d4c0 100644 --- a/kani-driver/src/cbmc_output_parser.rs +++ b/kani-driver/src/cbmc_output_parser.rs @@ -95,12 +95,18 @@ pub struct PropertyId { } impl Property { - const COVER_PROPERTY_CLASS: &str = "cover"; + const COVER_PROPERTY_CLASS: &'static str = "cover"; + const COVERAGE_PROPERTY_CLASS: &'static str = "code_coverage"; pub fn property_class(&self) -> String { self.property_id.class.clone() } + // Returns true if this is a code_coverage check + pub fn is_code_coverage_property(&self) -> bool { + self.property_id.class == Self::COVERAGE_PROPERTY_CLASS + } + /// Returns true if this is a cover property pub fn is_cover_property(&self) -> bool { self.property_id.class == Self::COVER_PROPERTY_CLASS @@ -322,11 +328,13 @@ impl std::fmt::Display for TraceData { #[serde(rename_all = "UPPERCASE")] pub enum CheckStatus { Failure, - Satisfied, // for cover properties only + Covered, // for `code_coverage` properties only + Satisfied, // for `cover` properties only Success, Undetermined, Unreachable, - Unsatisfiable, // for cover properties only + Uncovered, // for `code_coverage` properties only + Unsatisfiable, // for `cover` properties only } impl std::fmt::Display for CheckStatus { @@ -334,6 +342,8 @@ impl std::fmt::Display for CheckStatus { let check_str = match self { CheckStatus::Satisfied => style("SATISFIED").green(), CheckStatus::Success => style("SUCCESS").green(), + CheckStatus::Covered => style("COVERED").green(), + CheckStatus::Uncovered => style("UNCOVERED").red(), CheckStatus::Failure => style("FAILURE").red(), CheckStatus::Unreachable => style("UNREACHABLE").yellow(), CheckStatus::Undetermined => style("UNDETERMINED").yellow(), diff --git a/kani-driver/src/cbmc_property_renderer.rs b/kani-driver/src/cbmc_property_renderer.rs index 8da67dae7ab1..62ef748a1ad5 100644 --- a/kani-driver/src/cbmc_property_renderer.rs +++ b/kani-driver/src/cbmc_property_renderer.rs @@ -8,7 +8,8 @@ use console::style; use once_cell::sync::Lazy; use regex::Regex; use rustc_demangle::demangle; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; +use strum_macros::{AsRefStr, Display}; type CbmcAltDescriptions = HashMap<&'static str, Vec<(&'static str, Option<&'static str>)>>; @@ -149,6 +150,15 @@ static CBMC_ALT_DESCRIPTIONS: Lazy = Lazy::new(|| { map }); +#[derive(PartialEq, Eq, AsRefStr, Clone, Copy, Display)] +#[strum(serialize_all = "UPPERCASE")] +// The status of coverage reported by Kani +enum CoverageStatus { + Full, + Partial, + None, +} + const UNSUPPORTED_CONSTRUCT_DESC: &str = "is not currently supported by Kani"; const UNWINDING_ASSERT_DESC: &str = "unwinding assertion loop"; const UNWINDING_ASSERT_REC_DESC: &str = "recursion unwinding assertion"; @@ -421,6 +431,72 @@ pub fn format_result( result_str } +/// Separate checks into coverage and non-coverage based on property class and format them separately for --coverage. We report both verification and processed coverage +/// results +pub fn format_coverage( + properties: &[Property], + status: VerificationStatus, + should_panic: bool, + failed_properties: FailedProperties, + show_checks: bool, +) -> String { + let (coverage_checks, non_coverage_checks): (Vec, Vec) = + properties.iter().cloned().partition(|x| x.property_class() == "code_coverage"); + + let verification_output = + format_result(&non_coverage_checks, status, should_panic, failed_properties, show_checks); + let coverage_output = format_result_coverage(&coverage_checks); + let result = format!("{}\n{}", verification_output, coverage_output); + + result +} + +/// Generate coverage result from all coverage properties (i.e., checks with `code_coverage` property class). +/// Loops through each of the checks with the `code_coverage` property class on a line and gives: +/// - A status `FULL` if all checks pertaining to a line number are `COVERED` +/// - A status `NONE` if all checks related to a line are `UNCOVERED` +/// - Otherwise (i.e., if the line contains both) it reports `PARTIAL`. +/// +/// Used when the user requests coverage information with `--coverage`. +/// Output is tested through the `coverage-based` testing suite, not the regular +/// `expected` suite. +fn format_result_coverage(properties: &[Property]) -> String { + let mut formatted_output = String::new(); + formatted_output.push_str("\nCoverage Results:\n"); + + let mut coverage_results: BTreeMap> = + BTreeMap::default(); + for prop in properties { + let src = prop.source_location.clone(); + let file_entries = coverage_results.entry(src.file.unwrap()).or_default(); + let check_status = if prop.status == CheckStatus::Covered { + CoverageStatus::Full + } else { + CoverageStatus::None + }; + + // Create Map> + file_entries + .entry(src.line.unwrap().parse().unwrap()) + .and_modify(|line_status| { + if *line_status != check_status { + *line_status = CoverageStatus::Partial + } + }) + .or_insert(check_status); + } + + // Create formatted string that is returned to the user as output + for (file, checks) in coverage_results.iter() { + for (line_number, coverage_status) in checks { + formatted_output.push_str(&format!("{}, {}, {}\n", file, line_number, coverage_status)); + } + formatted_output.push('\n'); + } + + formatted_output +} + /// Attempts to build a message for a failed property with as much detailed /// information on the source location as possible. fn build_failure_message(description: String, trace: &Option>) -> String { @@ -456,10 +532,14 @@ fn build_failure_message(description: String, trace: &Option>) -> /// to `--object-bits` being too low. The message is edited to show Kani /// options. fn postprocess_error_message(message: ParserItem) -> ParserItem { - if let ParserItem::Message { ref message_text, message_type: _ } = message && message_text.contains("use the `--object-bits n` option") { + if let ParserItem::Message { ref message_text, message_type: _ } = message + && message_text.contains("use the `--object-bits n` option") + { ParserItem::Message { - message_text: message_text.replace("--object-bits ", "--enable-unstable --cbmc-args --object-bits "), - message_type: String::from("ERROR") } + message_text: message_text + .replace("--object-bits ", "--enable-unstable --cbmc-args --object-bits "), + message_type: String::from("ERROR"), + } } else { message } @@ -512,7 +592,8 @@ pub fn postprocess_result(properties: Vec, extra_ptr_checks: bool) -> let updated_properties = update_properties_with_reach_status(properties_filtered, has_fundamental_failures); - update_results_of_cover_checks(updated_properties) + let results_after_code_coverage = update_results_of_code_covererage_checks(updated_properties); + update_results_of_cover_checks(results_after_code_coverage) } /// Determines if there is property with status `FAILURE` and the given description @@ -542,7 +623,10 @@ fn modify_undefined_function_checks(mut properties: Vec) -> (Vec `UNCOVERED` +/// - `FAILURE` -> `COVERED` +/// Note that these statuses are intermediate statuses that aren't reported to +/// users but rather internally consumed and reported finally as `PARTIAL`, `FULL` +/// or `NONE` based on aggregated line coverage results. +fn update_results_of_code_covererage_checks(mut properties: Vec) -> Vec { + for prop in properties.iter_mut() { + if prop.is_code_coverage_property() { + prop.status = match prop.status { + CheckStatus::Success => CheckStatus::Uncovered, + CheckStatus::Failure => CheckStatus::Covered, + _ => unreachable!( + "status for coverage checks should be either `SUCCESS` or `FAILURE` prior to postprocessing" + ), + }; + } + } + properties +} + /// Update the results of cover properties. /// We encode cover(cond) as assert(!cond), so if the assertion /// fails, then the cover property is satisfied and vice versa. diff --git a/kani-driver/src/concrete_playback/playback.rs b/kani-driver/src/concrete_playback/playback.rs index bb060cea58bd..96ed30da904d 100644 --- a/kani-driver/src/concrete_playback/playback.rs +++ b/kani-driver/src/concrete_playback/playback.rs @@ -84,7 +84,7 @@ fn build_test(install: &InstallType, args: &KaniPlaybackArgs) -> Result rustc_args.push("--error-format=json".into()); } - let mut cmd = Command::new(&install.kani_compiler()?); + let mut cmd = Command::new(install.kani_compiler()?); cmd.args(rustc_args); session::run_terminal(&args.playback.common_opts, cmd)?; diff --git a/kani-driver/src/concrete_playback/test_generator.rs b/kani-driver/src/concrete_playback/test_generator.rs index b2e8fd17ff67..53c3a92dd94c 100644 --- a/kani-driver/src/concrete_playback/test_generator.rs +++ b/kani-driver/src/concrete_playback/test_generator.rs @@ -7,7 +7,6 @@ use crate::args::ConcretePlaybackMode; use crate::call_cbmc::VerificationResult; use crate::session::KaniSession; -use crate::util::tempfile::TempFile; use anyhow::{Context, Result}; use concrete_vals_extractor::{extract_harness_values, ConcreteVal}; use kani_metadata::HarnessMetadata; @@ -18,6 +17,7 @@ use std::hash::{Hash, Hasher}; use std::io::{BufRead, BufReader, Write}; use std::path::Path; use std::process::Command; +use tempfile::NamedTempFile; impl KaniSession { /// The main driver for generating concrete playback unit tests and adding them to source code. @@ -158,25 +158,28 @@ impl KaniSession { // Create temp file if !unit_tests.is_empty() { - let mut temp_file = TempFile::try_new("concrete_playback.tmp")?; + let source_basedir = Path::new(source_path).parent().unwrap_or(Path::new(".")); + let mut temp_file = NamedTempFile::with_prefix_in("concrete_playback", source_basedir)?; let mut curr_line_num = 0; // Use a buffered reader/writer to generate the unit test line by line - for line in source_reader.lines().flatten() { + for line in source_reader.lines().map_while(Result::ok) { curr_line_num += 1; - if let Some(temp_writer) = temp_file.writer.as_mut() { - writeln!(temp_writer, "{line}")?; - if curr_line_num == proof_harness_end_line { - for unit_test in unit_tests.iter() { - for unit_test_line in unit_test.code.iter() { - curr_line_num += 1; - writeln!(temp_writer, "{unit_test_line}")?; - } + writeln!(temp_file, "{line}")?; + if curr_line_num == proof_harness_end_line { + for unit_test in unit_tests.iter() { + for unit_test_line in unit_test.code.iter() { + curr_line_num += 1; + writeln!(temp_file, "{unit_test_line}")?; } } } } - temp_file.rename(source_path).expect("Could not rename file"); + + // Renames are usually automic, so we won't corrupt the user's source file during a + // crash; but first flush all updates to disk, which persist wouldn't take care of. + temp_file.as_file().sync_all()?; + temp_file.persist(source_path).expect("Could not rename file"); } Ok(!unit_tests.is_empty()) @@ -325,7 +328,7 @@ mod concrete_vals_extractor { result_items .iter() .filter(|prop| { - (prop.property_class() == "assertion" && prop.status == CheckStatus::Failure) + (prop.property_class() != "unwind" && prop.status == CheckStatus::Failure) || (prop.property_class() == "cover" && prop.status == CheckStatus::Satisfied) }) .map(|property| { @@ -380,10 +383,22 @@ mod concrete_vals_extractor { next_num.push(next_byte); } - return Some(ConcreteVal { - byte_arr: next_num, - interp_val: interp_concrete_val.to_string(), - }); + // In ARM64 Linux, CBMC will produce a character instead of a number for + // interpreted values because the char type is unsigned in that platform. + // For example, for the value `101` it will produce `'e'` instead of `101`. + // To correct this, we check if the value starts and ends with `'`, and + // convert the character into its ASCII value in that case. + let interp_val = { + let interp_val_str = interp_concrete_val.to_string(); + if interp_val_str.starts_with('\'') && interp_val_str.ends_with('\'') { + let interp_num = interp_val_str.chars().nth(1).unwrap() as u8; + interp_num.to_string() + } else { + interp_val_str + } + }; + + return Some(ConcreteVal { byte_arr: next_num, interp_val }); } } } diff --git a/kani-driver/src/harness_runner.rs b/kani-driver/src/harness_runner.rs index fdafa8a5c18b..2cd8bb7cbd70 100644 --- a/kani-driver/src/harness_runner.rs +++ b/kani-driver/src/harness_runner.rs @@ -4,14 +4,13 @@ use anyhow::{bail, Result}; use kani_metadata::{ArtifactType, HarnessMetadata}; use rayon::prelude::*; -use std::cmp::Ordering; use std::path::Path; use crate::args::OutputFormat; use crate::call_cbmc::{VerificationResult, VerificationStatus}; use crate::project::Project; use crate::session::KaniSession; -use crate::util::{error, warning}; +use crate::util::error; /// A HarnessRunner is responsible for checking all proof harnesses. The data in this structure represents /// "background information" that the controlling driver (e.g. cargo-kani or kani) computed. @@ -38,6 +37,8 @@ impl<'sess, 'pr> HarnessRunner<'sess, 'pr> { &self, harnesses: &'pr [&HarnessMetadata], ) -> Result>> { + self.check_stubbing(harnesses)?; + let sorted_harnesses = crate::metadata::sort_harnesses_by_loc(harnesses); let pool = { @@ -70,6 +71,33 @@ impl<'sess, 'pr> HarnessRunner<'sess, 'pr> { Ok(results) } + + /// Return an error if the user is trying to verify a harness with stubs without enabling the + /// experimental feature. + fn check_stubbing(&self, harnesses: &[&HarnessMetadata]) -> Result<()> { + if !self.sess.args.is_stubbing_enabled() { + let with_stubs: Vec<_> = harnesses + .iter() + .filter_map(|harness| { + (!harness.attributes.stubs.is_empty()).then_some(harness.pretty_name.as_str()) + }) + .collect(); + match with_stubs.as_slice() { + [] => { /* do nothing */ } + [harness] => bail!( + "Use of unstable feature 'stubbing' in harness `{}`.\n\ + To enable stubbing, pass option `-Z stubbing`", + harness + ), + harnesses => bail!( + "Use of unstable feature 'stubbing' in harnesses `{}`.\n\ + To enable stubbing, pass option `-Z stubbing`", + harnesses.join("`, `") + ), + } + } + Ok(()) + } } impl KaniSession { @@ -96,7 +124,11 @@ impl KaniSession { if !self.args.common_args.quiet && self.args.output_format != OutputFormat::Old { println!( "{}", - result.render(&self.args.output_format, harness.attributes.should_panic) + result.render( + &self.args.output_format, + harness.attributes.should_panic, + self.args.coverage + ) ); } self.gen_and_add_concrete_playback(harness, &mut result)?; @@ -104,33 +136,6 @@ impl KaniSession { } } - /// Prints a warning at the end of the verification if harness contained a stub but stubs were - /// not enabled. - fn stubbing_statuses(&self, results: &[HarnessResult]) { - if !self.args.enable_stubbing { - let ignored_stubs: Vec<_> = results - .iter() - .filter_map(|result| { - (!result.harness.attributes.stubs.is_empty()) - .then_some(result.harness.pretty_name.as_str()) - }) - .collect(); - match ignored_stubs.len().cmp(&1) { - Ordering::Equal => warning(&format!( - "harness `{}` contained stubs which were ignored.\n\ - To enable stubbing, pass options `--enable-unstable --enable-stubbing`", - ignored_stubs[0] - )), - Ordering::Greater => warning(&format!( - "harnesses `{}` contained stubs which were ignored.\n\ - To enable stubbing, pass options `--enable-unstable --enable-stubbing`", - ignored_stubs.join("`, `") - )), - Ordering::Less => {} - } - } - } - /// Concludes a session by printing a summary report and exiting the process with an /// error code (if applicable). /// @@ -191,8 +196,6 @@ impl KaniSession { } } - self.stubbing_statuses(results); - if failing > 0 { // Failure exit code without additional error message drop(self); diff --git a/kani-driver/src/main.rs b/kani-driver/src/main.rs index ff0a97cce053..1d2afa177ca9 100644 --- a/kani-driver/src/main.rs +++ b/kani-driver/src/main.rs @@ -14,6 +14,7 @@ use crate::args::StandaloneSubcommand; use crate::concrete_playback::playback::{playback_cargo, playback_standalone}; use crate::project::Project; use crate::session::KaniSession; +use crate::version::print_kani_version; use clap::Parser; use tracing::debug; @@ -35,12 +36,15 @@ mod metadata; mod project; mod session; mod util; +mod version; /// The main function for the `kani-driver`. /// The driver can be invoked via `cargo kani` and `kani` commands, which determines what kind of /// project should be verified. fn main() -> ExitCode { - let result = match determine_invocation_type(Vec::from_iter(std::env::args_os())) { + let invocation_type = determine_invocation_type(Vec::from_iter(std::env::args_os())); + + let result = match invocation_type { InvocationType::CargoKani(args) => cargokani_main(args), InvocationType::Standalone => standalone_main(), }; @@ -59,10 +63,15 @@ fn main() -> ExitCode { /// The main function for the `cargo kani` command. fn cargokani_main(input_args: Vec) -> Result<()> { let input_args = join_args(input_args)?; - let args = args::CargoKaniArgs::parse_from(input_args); + let args = args::CargoKaniArgs::parse_from(&input_args); check_is_valid(&args); + let session = session::KaniSession::new(args.verify_opts)?; + if !session.args.common_args.quiet { + print_kani_version(InvocationType::CargoKani(input_args)); + } + match args.command { Some(CargoKaniSubcommand::Assess(args)) => { return assess::run_assess(session, *args); @@ -91,6 +100,11 @@ fn standalone_main() -> Result<()> { } let session = session::KaniSession::new(args.verify_opts)?; + + if !session.args.common_args.quiet { + print_kani_version(InvocationType::Standalone); + } + let project = project::standalone_project(&args.input.unwrap(), &session)?; if session.args.only_codegen { Ok(()) } else { verify_project(project, session) } } @@ -116,7 +130,7 @@ enum InvocationType { /// Peeks at command line arguments to determine if we're being invoked as 'kani' or 'cargo-kani' fn determine_invocation_type(mut args: Vec) -> InvocationType { - let exe = util::executable_basename(&args.get(0)); + let exe = util::executable_basename(&args.first()); // Case 1: if 'kani' is our first real argument, then we're being invoked as cargo-kani // 'cargo kani ...' will cause cargo to run 'cargo-kani kani ...' preserving argv1 diff --git a/kani-driver/src/project.rs b/kani-driver/src/project.rs index c776a106ae47..df85ca151077 100644 --- a/kani-driver/src/project.rs +++ b/kani-driver/src/project.rs @@ -364,6 +364,6 @@ fn metadata_with_function( // Note: `out_dir` is already on canonical form, so no need to invoke `try_new()`. fn standalone_artifact(out_dir: &Path, crate_name: &String, typ: ArtifactType) -> Artifact { let mut path = out_dir.join(crate_name); - let _ = path.set_extension(&typ); + let _ = path.set_extension(typ); Artifact { path, typ } } diff --git a/kani-driver/src/session.rs b/kani-driver/src/session.rs index d0834cae2122..5b9b90854789 100644 --- a/kani-driver/src/session.rs +++ b/kani-driver/src/session.rs @@ -35,8 +35,6 @@ pub struct KaniSession { pub kani_compiler: PathBuf, /// The location we found 'kani_lib.c' pub kani_lib_c: PathBuf, - /// The location we found the Kani C stub .c files - pub kani_c_stubs: PathBuf, /// The temporary files we littered that need to be cleaned up at the end of execution pub temporaries: Mutex>, @@ -62,7 +60,6 @@ impl KaniSession { codegen_tests: false, kani_compiler: install.kani_compiler()?, kani_lib_c: install.kani_lib_c()?, - kani_c_stubs: install.kani_c_stubs()?, temporaries: Mutex::new(vec![]), }) } @@ -219,7 +216,7 @@ pub fn run_redirect( stdout.display() ); } - let output_file = std::fs::File::create(&stdout)?; + let output_file = std::fs::File::create(stdout)?; cmd.stdout(output_file); let program = cmd.get_program().to_string_lossy().to_string(); @@ -339,10 +336,6 @@ impl InstallType { self.base_path_with("library/kani/kani_lib.c") } - pub fn kani_c_stubs(&self) -> Result { - self.base_path_with("library/kani/stubs/C") - } - /// A common case is that our repo and release bundle have the same `subpath` fn base_path_with(&self, subpath: &str) -> Result { let path = match self { diff --git a/kani-driver/src/util.rs b/kani-driver/src/util.rs index a9800785f869..1e0957dc7726 100644 --- a/kani-driver/src/util.rs +++ b/kani-driver/src/util.rs @@ -14,84 +14,6 @@ use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::process::Command; -pub mod tempfile { - use std::{ - env, - fs::{self, rename, File}, - io::{BufWriter, Error, Write}, - path::PathBuf, - }; - - use crate::util; - use ::rand; - use anyhow::Context; - use rand::Rng; - - /// Handle a writable temporary file which will be deleted when the object is dropped. - /// To save the contents of the file, users can invoke `rename` which will move the file to - /// its new location and no further deletion will be performed. - pub struct TempFile { - pub file: File, - pub temp_path: PathBuf, - pub writer: Option>, - renamed: bool, - } - - impl TempFile { - /// Create a temp file - pub fn try_new(suffix_name: &str) -> Result { - let mut temp_path = env::temp_dir(); - - // Generate a unique name for the temporary directory - let hash: u32 = rand::thread_rng().gen(); - let file_name: &str = &format!("kani_tmp_{hash}_{suffix_name}"); - - temp_path.push(file_name); - let temp_file = File::create(&temp_path)?; - let writer = BufWriter::new(temp_file.try_clone()?); - - Ok(Self { file: temp_file, temp_path, writer: Some(writer), renamed: false }) - } - - /// Rename the temporary file to the new path, replacing the original file if the path points to a file that already exists. - pub fn rename(mut self, source_path: &str) -> Result<(), String> { - // flush here - self.writer.as_mut().unwrap().flush().unwrap(); - self.writer = None; - // Renames are usually automic, so we won't corrupt the user's source file during a crash. - rename(&self.temp_path, source_path) - .with_context(|| format!("Error renaming file {}", self.temp_path.display())) - .unwrap(); - self.renamed = true; - Ok(()) - } - } - - /// Ensure that the bufwriter is flushed and temp variables are dropped - /// everytime the tempfile is out of scope - /// note: the fields for the struct are dropped automatically by destructor - impl Drop for TempFile { - fn drop(&mut self) { - // if writer is not flushed, flush it - if self.writer.as_ref().is_some() { - // couldn't use ? as drop does not handle returns - if let Err(e) = self.writer.as_mut().unwrap().flush() { - util::warning( - format!("failed to flush {}: {e}", self.temp_path.display()).as_str(), - ); - } - self.writer = None; - } - - if !self.renamed { - if let Err(_e) = fs::remove_file(&self.temp_path.clone()) { - util::warning(&format!("Error removing file {}", self.temp_path.display())); - } - } - } - } -} - /// Replace an extension with another one, in a new PathBuf. (See tests for examples) pub fn alter_extension(path: &Path, ext: &str) -> PathBuf { path.with_extension(ext) diff --git a/kani-driver/src/version.rs b/kani-driver/src/version.rs new file mode 100644 index 000000000000..95d98b0d6d3e --- /dev/null +++ b/kani-driver/src/version.rs @@ -0,0 +1,31 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use crate::InvocationType; + +const KANI_RUST_VERIFIER: &str = "Kani Rust Verifier"; +/// We assume this is the same as the `kani-verifier` version, but we should +/// make sure it's enforced through CI: +/// +const KANI_VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// Print Kani version. At present, this is only release version information. +pub(crate) fn print_kani_version(invocation_type: InvocationType) { + let kani_version = kani_version_release(invocation_type); + // TODO: Print development version information. + // + println!("{kani_version}"); +} + +/// Print Kani release version as `Kani Rust Verifier ()` +/// where: +/// - `` is the `kani-verifier` version +/// - `` is `cargo plugin` if Kani was invoked with `cargo kani` or +/// `standalone` if it was invoked with `kani`. +fn kani_version_release(invocation_type: InvocationType) -> String { + let invocation_str = match invocation_type { + InvocationType::CargoKani(_) => "cargo plugin", + InvocationType::Standalone => "standalone", + }; + format!("{KANI_RUST_VERIFIER} {KANI_VERSION} ({invocation_str})") +} diff --git a/kani_metadata/Cargo.toml b/kani_metadata/Cargo.toml index 7b908e068fa5..3cb7fa7c605e 100644 --- a/kani_metadata/Cargo.toml +++ b/kani_metadata/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_metadata" -version = "0.32.0" +version = "0.42.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false @@ -13,5 +13,6 @@ publish = false [dependencies] serde = {version = "1", features = ["derive"]} cbmc = { path = "../cprover_bindings", package = "cprover_bindings" } -strum = "0.24.1" -strum_macros = "0.24.3" +strum = "0.25.0" +strum_macros = "0.25.2" +clap = { version = "4.4.11", features = ["derive"] } diff --git a/kani_metadata/src/artifact.rs b/kani_metadata/src/artifact.rs index 54ff7a025e19..078cad14f679 100644 --- a/kani_metadata/src/artifact.rs +++ b/kani_metadata/src/artifact.rs @@ -56,7 +56,7 @@ pub fn convert_type(path: &Path, from: ArtifactType, to: ArtifactType) -> PathBu match from { // Artifact types that has only one extension. ArtifactType::Goto => { - result.set_extension(&to); + result.set_extension(to); } // Artifact types that has two extensions. ArtifactType::Metadata @@ -66,7 +66,7 @@ pub fn convert_type(path: &Path, from: ArtifactType, to: ArtifactType) -> PathBu | ArtifactType::VTableRestriction | ArtifactType::PrettyNameMap => { result.set_extension(""); - result.set_extension(&to); + result.set_extension(to); } } result diff --git a/kani_metadata/src/harness.rs b/kani_metadata/src/harness.rs index 2aadc70e9468..2356f9bdf42f 100644 --- a/kani_metadata/src/harness.rs +++ b/kani_metadata/src/harness.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; /// We emit this structure for each annotated proof harness (`#[kani::proof]`) we find. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct HarnessMetadata { /// The fully qualified name the user gave to the function (i.e. includes the module path). pub pretty_name: String, @@ -27,7 +27,7 @@ pub struct HarnessMetadata { } /// The attributes added by the user to control how a harness is executed. -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] pub struct HarnessAttributes { /// Whether the harness has been annotated with proof. pub proof: bool, @@ -42,7 +42,7 @@ pub struct HarnessAttributes { } /// The stubbing type. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Stub { pub original: String, pub replacement: String, diff --git a/kani_metadata/src/lib.rs b/kani_metadata/src/lib.rs index 7da3cbb94121..fa5a8828b6be 100644 --- a/kani_metadata/src/lib.rs +++ b/kani_metadata/src/lib.rs @@ -1,6 +1,8 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT +extern crate clap; + use std::{collections::HashSet, path::PathBuf}; use serde::{Deserialize, Serialize}; @@ -13,8 +15,11 @@ pub use vtable::*; pub mod artifact; mod cbmc_solver; mod harness; +pub mod unstable; mod vtable; +pub use unstable::{EnabledUnstableFeatures, UnstableFeature}; + /// The structure of `.kani-metadata.json` files, which are emitted for each crate #[derive(Debug, Clone, Serialize, Deserialize)] pub struct KaniMetadata { diff --git a/kani_metadata/src/unstable.rs b/kani_metadata/src/unstable.rs new file mode 100644 index 000000000000..060508666735 --- /dev/null +++ b/kani_metadata/src/unstable.rs @@ -0,0 +1,121 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Unified handling of unstable-feature flags across Kani. +//! +//! The central types are [`UnstableFeature`], which describes a single feature +//! and is intended as a small, cheap enum that is [`Copy`]. +//! [`EnabledUnstableFeatures`] then describes which [`UnstableFeature`]s are +//! enabled. +//! +//! To check if a feature is enabled use [`EnabledUnstableFeatures::contains`]. +//! +//! ### Parsing +//! +//! [`EnabledUnstableFeatures`] is intended to be used with the [`clap`] +//! "derive" API. You can directly drop it into a command line arguments struct +//! like so: +//! +//! ``` +//! # use kani_metadata::unstable::*; +//! use clap::Parser; +//! +//! #[derive(Parser)] +//! struct MyCmdArgs { +//! // ... +//! #[clap(flatten)] +//! unstable: EnabledUnstableFeatures, +//! } +//! ``` +//! +//! Which will add the long form `--unstable feature-name` and short form `-Z +//! feature-name` options to your argument parser. +//! +//! **Note:** [`clap`] internally uses a unique name (string) to refer to each +//! argument or group, which is usually derived from the field name. +//! [`EnabledUnstableFeatures`] uses the internal name +//! `"enabled_unstable_features"` which may therefore not be used (as a field +//! name) in the embedding argument struct, e.g. `MyCmdArgs`. +//! +//! ### Reusing +//! +//! You can turn an [`UnstableFeature`] back into it's command line +//! representation. This should be done with +//! [`EnabledUnstableFeatures::as_arguments`], which returns an iterator that, +//! when passed to e.g. [`std::process::Command::args`] will enable those +//! features in the subsequent call. +//! +//! You can also serialize a single feature with +//! [`UnstableFeature::as_argument`]. +//! +//! Both of these methods return values that are ready for direct usage on the +//! command line, e.g one or more `-Z feature-name`. If you need only the +//! serialized name of the feature use [`UnstableFeature::as_ref`]. + +/// A single unstable feature. This is where you should register your own if you +/// are adding new ones, e.g. as part of the RFC process. +/// +/// For usage see the [module level documentation][self]. +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + clap::ValueEnum, + strum_macros::Display, + strum_macros::AsRefStr +)] +#[strum(serialize_all = "kebab-case")] +pub enum UnstableFeature { + /// Allow replacing certain items with stubs (mocks). + /// See [RFC-0002](https://model-checking.github.io/kani/rfc/rfcs/0002-function-stubbing.html) + Stubbing, + /// Generate a C-like file equivalent to input program used for debugging purpose. + GenC, + /// Allow Kani to link against C code. + CFfi, + /// Enable concrete playback flow. + ConcretePlayback, + /// Enable Kani's unstable async library. + AsyncLib, + /// Enable line coverage instrumentation/reports. + LineCoverage, + /// Enable function contracts [RFC 9](https://model-checking.github.io/kani/rfc/rfcs/0009-function-contracts.html) + FunctionContracts, +} + +impl UnstableFeature { + /// Serialize this feature into a format in which it can be passed on the + /// command line. Note that this already includes the `-Z` prefix, if you + /// require only the serialized feature name use [`Self::as_ref`]. + pub fn as_argument(&self) -> [&str; 2] { + ["-Z", self.as_ref()] + } +} + +/// An opaque collection of unstable features that is enabled on this session. +/// Used to unify handling of unstable command line arguments across the +/// compiler and the driver. +/// +/// For usage see the [module level documentation][self]. +#[derive(clap::Args, Debug)] +pub struct EnabledUnstableFeatures { + #[clap(short = 'Z', long = "unstable", num_args(1), value_name = "UNSTABLE_FEATURE")] + enabled_unstable_features: Vec, +} + +impl EnabledUnstableFeatures { + /// The preferred way to serialize these unstable features back into a + /// format that can be used as command line arguments fo an invocation of + /// e.g. the compiler. + /// + /// See also the [module level documentation][self]. + pub fn as_arguments(&self) -> impl Iterator { + self.enabled_unstable_features.iter().flat_map(|f| f.as_argument()) + } + + /// Is this feature enabled? + pub fn contains(&self, feature: UnstableFeature) -> bool { + self.enabled_unstable_features.contains(&feature) + } +} diff --git a/library/kani/Cargo.toml b/library/kani/Cargo.toml index 8d44bb760367..c3658b986d93 100644 --- a/library/kani/Cargo.toml +++ b/library/kani/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani" -version = "0.32.0" +version = "0.42.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani/src/arbitrary.rs b/library/kani/src/arbitrary.rs index 2d89e7084d36..938f3c30968f 100644 --- a/library/kani/src/arbitrary.rs +++ b/library/kani/src/arbitrary.rs @@ -170,3 +170,12 @@ where Box::new(T::any()) } } + +impl Arbitrary for std::time::Duration { + fn any() -> Self { + const NANOS_PER_SEC: u32 = 1_000_000_000; + let nanos = u32::any(); + crate::assume(nanos < NANOS_PER_SEC); + std::time::Duration::new(u64::any(), nanos) + } +} diff --git a/library/kani/src/contracts.rs b/library/kani/src/contracts.rs new file mode 100644 index 000000000000..a211a8ba905c --- /dev/null +++ b/library/kani/src/contracts.rs @@ -0,0 +1,192 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Kani implementation of function contracts. +//! +//! Function contracts are still under development. Using the APIs therefore +//! requires the unstable `-Zfunction-contracts` flag to be passed. You can join +//! the discussion on contract design by reading our +//! [RFC](https://model-checking.github.io/kani/rfc/rfcs/0009-function-contracts.html) +//! and [commenting on the tracking +//! issue](https://github.com/model-checking/kani/issues/2652). +//! +//! The function contract API is expressed as proc-macro attributes, and there +//! are two parts to it. +//! +//! 1. [Contract specification attributes](#specification-attributes-overview): +//! [`requires`][macro@requires] and [`ensures`][macro@ensures]. +//! 2. [Contract use attributes](#contract-use-attributes-overview): +//! [`proof_for_contract`][macro@proof_for_contract] and +//! [`stub_verified`][macro@stub_verified]. +//! +//! ## Step-by-step Guide +//! +//! Let us explore using a workflow involving contracts on the example of a +//! simple division function `my_div`: +//! +//! ``` +//! fn my_div(dividend: u32, divisor: u32) -> u32 { +//! dividend / divisor +//! } +//! ``` +//! +//! With the contract specification attributes we can specify the behavior of +//! this function declaratively. The [`requires`][macro@requires] attribute +//! allows us to declare constraints on what constitutes valid inputs to our +//! function. In this case we would want to disallow a divisor that is `0`. +//! +//! ```ignore +//! #[requires(divisor != 0)] +//! ``` +//! +//! This is called a precondition, because it is enforced before (pre-) the +//! function call. As you can see attribute has access to the functions +//! arguments. The condition itself is just regular Rust code. You can use any +//! Rust code, including calling functions and methods. However you may not +//! perform I/O (like [`println!`]) or mutate memory (like [`Vec::push`]). +//! +//! The [`ensures`][macro@ensures] attribute on the other hand lets us describe +//! the output value in terms of the inputs. You may be as (im)precise as you +//! like in the [`ensures`][macro@ensures] clause, depending on your needs. One +//! approximation of the result of division for instance could be this: +//! +//! ``` +//! #[ensures(result <= dividend)] +//! ``` +//! +//! This is called a postcondition and it also has access to the arguments and +//! is expressed in regular Rust code. The same restrictions apply as did for +//! [`requires`][macro@requires]. In addition to the arguments the postcondition +//! also has access to the value returned from the function in a variable called +//! `result`. +//! +//! You may combine as many [`requires`][macro@requires] and +//! [`ensures`][macro@ensures] attributes on a single function as you please. +//! They all get enforced (as if their conditions were `&&`ed together) and the +//! order does not matter. In our example putting them together looks like this: +//! +//! ``` +//! #[kani::requires(divisor != 0)] +//! #[kani::ensures(result <= dividend)] +//! fn my_div(dividend: u32, divisor: u32) -> u32 { +//! dividend / divisor +//! } +//! ``` +//! +//! Once we are finished specifying our contract we can ask Kani to check it's +//! validity. For this we need to provide a proof harness that exercises the +//! function. The harness is created like any other, e.g. as a test-like +//! function with inputs and using `kani::any` to create arbitrary values. +//! However we do not need to add any assertions or assumptions about the +//! inputs, Kani will use the pre- and postconditions we have specified for that +//! and we use the [`proof_for_contract`][macro@proof_for_contract] attribute +//! instead of [`proof`](crate::proof) and provide it with the path to the +//! function we want to check. +//! +//! ``` +//! #[kani::proof_for_contract(my_div)] +//! fn my_div_harness() { +//! my_div(kani::any(), kani::any()) } +//! ``` +//! +//! The harness is checked like any other by running `cargo kani` and can be +//! specifically selected with `--harness my_div_harness`. +//! +//! Once we have verified that our contract holds, we can use perhaps it's +//! coolest feature: verified stubbing. This allows us to use the conditions of +//! the contract *instead* of it's implementation. This can be very powerful for +//! expensive implementations (involving loops for instance). +//! +//! Verified stubbing is available to any harness via the +//! [`stub_verified`][macro@stub_verified] harness attribute. We must provide +//! the attribute with the path to the function to stub, but unlike with +//! [`stub`](crate::stub) we do not need to provide a function to replace with, +//! the contract will be used automatically. +//! +//! ``` +//! #[kani::proof] +//! #[kani::stub_verified(my_div)] +//! fn use_div() { +//! let v = vec![...]; +//! let some_idx = my_div(v.len() - 1, 3); +//! v[some_idx]; +//! } +//! ``` +//! +//! In this example the contract is sufficient to prove that the element access +//! in the last line cannot be out-of-bounds. +//! +//! ## Specification Attributes Overview +//! +//! There are currently two specification attributes available for describing +//! function behavior: [`requires`][macro@requires] for preconditions and +//! [`ensures`][macro@ensures] for postconditions. Both admit arbitrary Rust +//! expressions as their bodies which may also reference the function arguments +//! but must not mutate memory or perform I/O. The postcondition may +//! additionally reference the return value of the function as the variable +//! `result`. +//! +//! During verified stubbing the return value of a function with a contract is +//! replaced by a call to `kani::any`. As such the return value must implement +//! the `kani::Arbitrary` trait. +//! +//! In Kani, function contracts are optional. As such a function with at least +//! one specification attribute is considered to "have a contract" and any +//! absent specification type defaults to its most general interpretation +//! (`true`). All functions with not a single specification attribute are +//! considered "not to have a contract" and are ineligible for use as the target +//! of a [`proof_for_contract`][macro@proof_for_contract] of +//! [`stub_verified`][macro@stub_verified] attribute. +//! +//! ## Contract Use Attributes Overview +//! +//! Contract are used both to verify function behavior and to leverage the +//! verification result as a sound abstraction. +//! +//! Verifying function behavior currently requires the designation of at least +//! one checking harness with the +//! [`proof_for_contract`](macro@proof_for_contract) attribute. A harness may +//! only have one `proof_for_contract` attribute and it may not also have a +//! `proof` attribute. +//! +//! The checking harness is expected to set up the arguments that `foo` should +//! be called with and initialized any `static mut` globals that are reachable. +//! All of these should be initialized to as general value as possible, usually +//! achieved using `kani::any`. The harness must call e.g. `foo` at least once +//! and if `foo` has type parameters, only one instantiation of those parameters +//! is admissible. Violating either results in a compile error. +//! +//! If any inputs have special invariants you *can* use `kani::assume` to +//! enforce them but this may introduce unsoundness. In general all restrictions +//! on input parameters should be part of the [`requires`](macro@requires) +//! clause of the function contract. +//! +//! Once the contract has been verified it may be used as a verified stub. For +//! this the [`stub_verified`](macro@stub_verified) attribute is used. +//! `stub_verified` is a harness attribute, like +//! [`unwind`](macro@crate::unwind), meaning it is used on functions that are +//! annotated with [`proof`](macro@crate::proof). It may also be used on a +//! `proof_for_contract` proof. +//! +//! Unlike `proof_for_contract` multiple `stub_verified` attributes are allowed +//! on the same proof harness though they must target different functions. +//! +//! ## Inductive Verification +//! +//! Function contracts by default use inductive verification to efficiently +//! verify recursive functions. In inductive verification a recursive function +//! is executed once and every recursive call instead uses the contract +//! replacement. In this way many recursive calls can be checked with a +//! single verification pass. +//! +//! The downside of inductive verification is that the return value of a +//! contracted function must implement `kani::Arbitrary`. Due to restrictions to +//! code generation in proc macros, the contract macros cannot determine reliably +//! in all cases whether a given function with a contract is recursive. As a +//! result it conservatively sets up inductive verification for every function +//! and requires the `kani::Arbitrary` constraint for contract checks. +//! +//! If you feel strongly about this issue you can join the discussion on issue +//! [#2823](https://github.com/model-checking/kani/issues/2823) to enable +//! opt-out of inductive verification. +pub use super::{ensures, proof_for_contract, requires, stub_verified}; diff --git a/library/kani/src/futures.rs b/library/kani/src/futures.rs index cd3b9ad60597..637f24a80f6a 100644 --- a/library/kani/src/futures.rs +++ b/library/kani/src/futures.rs @@ -182,10 +182,12 @@ impl Future for JoinHandle { #[crate::unstable(feature = "async-lib", issue = 2559, reason = "experimental async support")] pub fn spawn + Sync + 'static>(fut: F) -> JoinHandle { unsafe { - GLOBAL_EXECUTOR - .as_mut() - .expect("`spawn` should only be called within `block_on_with_spawn`") - .spawn(fut) + if let Some(executor) = GLOBAL_EXECUTOR.as_mut() { + executor.spawn(fut) + } else { + // An explicit panic instead of `.expect(...)` has better location information in Kani's output + panic!("`spawn` should only be called within `block_on_with_spawn`") + } } } diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index 5ab09d69f9bb..d2959e548417 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -5,10 +5,17 @@ #![feature(register_tool)] #![register_tool(kanitool)] // Used for rustc_diagnostic_item. +// Note: We could use a kanitool attribute instead. #![feature(rustc_attrs)] // This is required for the optimized version of `any_array()` #![feature(generic_const_exprs)] #![allow(incomplete_features)] +// Used to model simd. +#![feature(repr_simd)] +// Features used for tests only. +#![cfg_attr(test, feature(platform_intrinsics, portable_simd))] +// Required for rustc_diagnostic_item +#![allow(internal_features)] pub mod arbitrary; #[cfg(feature = "concrete_playback")] @@ -18,6 +25,8 @@ pub mod slice; pub mod tuple; pub mod vec; +mod models; + pub use arbitrary::Arbitrary; #[cfg(feature = "concrete_playback")] pub use concrete_playback::concrete_playback_run; @@ -65,6 +74,33 @@ pub fn assume(cond: bool) { assert!(cond, "`kani::assume` should always hold"); } +/// `implies!(premise => conclusion)` means that if the `premise` is true, so +/// must be the `conclusion`. +/// +/// This simply expands to `!premise || conclusion` and is intended to be used +/// in function contracts to make them more readable, as the concept of an +/// implication is more natural to think about than its expansion. +/// +/// For further convenience multiple comma separated premises are allowed, and +/// are joined with `||` in the expansion. E.g. `implies!(a, b => c)` expands to +/// `!a || !b || c` and says that `c` is true if both `a` and `b` are true (see +/// also [Horn Clauses](https://en.wikipedia.org/wiki/Horn_clause)). +#[macro_export] +macro_rules! implies { + ($($premise:expr),+ => $conclusion:expr) => { + $(!$premise)||+ || ($conclusion) + }; +} + +/// A way to break the ownerhip rules. Only used by contracts where we can +/// guarantee it is done safely. +#[inline(never)] +#[doc(hidden)] +#[rustc_diagnostic_item = "KaniUntrackedDeref"] +pub fn untracked_deref(_: &T) -> T { + todo!() +} + /// Creates an assertion of the specified condition and message. /// /// # Example: @@ -110,7 +146,7 @@ pub const fn assert(cond: bool, msg: &'static str) { /// #[inline(never)] #[rustc_diagnostic_item = "KaniCover"] -pub fn cover(_cond: bool, _msg: &'static str) {} +pub const fn cover(_cond: bool, _msg: &'static str) {} /// This creates an symbolic *valid* value of type `T`. You can assign the return value of this /// function to a variable that you want to make symbolic. @@ -258,5 +294,7 @@ macro_rules! cover { }; } -/// Kani proc macros must be in a separate crate +// Kani proc macros must be in a separate crate pub use kani_macros::*; + +pub mod contracts; diff --git a/library/kani/src/models/mod.rs b/library/kani/src/models/mod.rs new file mode 100644 index 000000000000..194f220595c0 --- /dev/null +++ b/library/kani/src/models/mod.rs @@ -0,0 +1,230 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Contains definitions that Kani compiler may use to model functions that are not suitable for +//! verification or functions without a body, such as intrinsics. +//! +//! Note that these are models that Kani uses by default; thus, we keep them separate from stubs. + +// Definitions in this module are not meant to be visible to the end user, only the compiler. +#[allow(dead_code)] +mod intrinsics { + use std::fmt::Debug; + use std::mem::size_of; + + /// Similar definition to portable SIMD. + /// We cannot reuse theirs since TRUE and FALSE defs are private. + /// We leave this private today, since this is not necessarily a final solution, so we don't + /// want users relying on this. + /// Our definitions are also a bit more permissive to comply with the platform intrinsics. + pub(super) trait MaskElement: PartialEq + Debug { + const TRUE: Self; + const FALSE: Self; + } + + macro_rules! impl_element { + { $ty:ty } => { + impl MaskElement for $ty { + const TRUE: Self = -1; + const FALSE: Self = 0; + } + } + } + + macro_rules! impl_unsigned_element { + { $ty:ty } => { + impl MaskElement for $ty { + // Note that in the declaration of the intrinsic it is documented that the lane + // values should be -1 or 0: + // + // + // However, MIRI and the Rust compiler seems to accept unsigned values and they + // use their binary representation. Thus, that's what we use for now. + /// All bits are 1 which represents TRUE. + const TRUE: Self = <$ty>::MAX; + /// All bits are 0 which represents FALSE. + const FALSE: Self = 0; + } + } + } + + impl_element! { i8 } + impl_element! { i16 } + impl_element! { i32 } + impl_element! { i64 } + impl_element! { i128 } + impl_element! { isize } + + impl_unsigned_element! { u8 } + impl_unsigned_element! { u16 } + impl_unsigned_element! { u32 } + impl_unsigned_element! { u64 } + impl_unsigned_element! { u128 } + impl_unsigned_element! { usize } + + /// Calculate the minimum number of lanes to represent a mask + /// Logic similar to `bitmask_len` from `portable_simd`. + /// + pub(super) const fn mask_len(len: usize) -> usize { + (len + 7) / 8 + } + + #[cfg(target_endian = "little")] + unsafe fn simd_bitmask_impl(input: &[T; LANES]) -> [u8; mask_len(LANES)] + where + T: MaskElement, + { + let mut mask_array = [0; mask_len(LANES)]; + for lane in (0..input.len()).rev() { + let byte = lane / 8; + let mask = &mut mask_array[byte]; + let shift_mask = *mask << 1; + *mask = if input[lane] == T::TRUE { + shift_mask | 0x1 + } else { + assert_eq!(input[lane], T::FALSE, "Masks values should either be 0 or -1"); + shift_mask + }; + } + mask_array + } + + /// Stub for simd_bitmask. + /// + /// It will reduce a simd vector (TxN), into an integer of size S (in bits), where S >= N. + /// Each bit of the output will represent a lane from the input. A lane value of all 0's will be + /// translated to 1b0, while all 1's will be translated to 1b1. + /// + /// In order to be able to do this pragmatically, we take additional parameters that are filled + /// by the compiler. + #[rustc_diagnostic_item = "KaniModelSimdBitmask"] + pub(super) unsafe fn simd_bitmask(input: T) -> U + where + [u8; mask_len(LANES)]: Sized, + E: MaskElement, + { + // These checks are compiler sanity checks to ensure we are not doing anything invalid. + assert_eq!( + size_of::(), + size_of::<[u8; mask_len(LANES)]>(), + "Expected size of return type and mask lanes to match", + ); + assert_eq!( + size_of::(), + size_of::>(), + "Expected size of input and lanes to match", + ); + + let data = &*(&input as *const T as *const [E; LANES]); + let mask = simd_bitmask_impl(data); + (&mask as *const [u8; mask_len(LANES)] as *const U).read() + } + + /// Structure used for sanity check our parameters. + #[repr(simd)] + struct Simd([T; LANES]); +} + +#[cfg(test)] +mod test { + use super::intrinsics as kani_intrinsic; + use std::{fmt::Debug, simd::*}; + + extern "platform-intrinsic" { + fn simd_bitmask(x: T) -> U; + } + + /// Test that the `simd_bitmask` model is equivalent to the intrinsic for all true and all false + /// masks with lanes represented using i16. + #[test] + fn test_bitmask_i16() { + check_portable_bitmask::<_, i16, 16, u16>(mask16x16::splat(false)); + check_portable_bitmask::<_, i16, 16, u16>(mask16x16::splat(true)); + } + + /// Tests that the model correctly fails if an invalid value is given. + #[test] + #[should_panic(expected = "Masks values should either be 0 or -1")] + fn test_invalid_bitmask() { + let invalid_mask = unsafe { mask32x16::from_int_unchecked(i32x16::splat(10)) }; + assert_eq!( + unsafe { kani_intrinsic::simd_bitmask::<_, u16, i32, 16>(invalid_mask) }, + u16::MAX + ); + } + + /// Tests that the model correctly fails if the size parameter of the mask doesn't match the + /// expected number of bytes in the representation. + #[test] + #[should_panic(expected = "Expected size of return type and mask lanes to match")] + fn test_invalid_generics() { + let mask = mask32x16::splat(false); + assert_eq!(unsafe { kani_intrinsic::simd_bitmask::<_, u16, i32, 2>(mask) }, u16::MAX); + } + + /// Test that the `simd_bitmask` model is equivalent to the intrinsic for a few random values. + /// These values shouldn't be symmetric and ensure that we also handle endianness correctly. + #[test] + fn test_bitmask_i32() { + check_portable_bitmask::<_, i32, 8, u8>(mask32x8::from([ + true, true, false, true, false, false, false, true, + ])); + + check_portable_bitmask::<_, i32, 4, u8>(mask32x4::from([true, false, false, true])); + } + + #[repr(simd)] + #[derive(Clone, Debug)] + struct CustomMask([T; LANES]); + + /// Check that the bitmask model can handle odd size SIMD arrays. + /// Since the portable_simd restricts the number of lanes, we have to use our own custom SIMD. + #[test] + fn test_bitmask_odd_lanes() { + check_bitmask::<_, [u8; 3], i128, 23>(CustomMask([0i128; 23])); + check_bitmask::<_, [u8; 9], i128, 70>(CustomMask([-1i128; 70])); + } + + /// Compare the value returned by our model and the portable simd representation. + fn check_portable_bitmask(mask: Mask) + where + T: std::simd::MaskElement, + LaneCount: SupportedLaneCount, + E: kani_intrinsic::MaskElement, + [u8; kani_intrinsic::mask_len(LANES)]: Sized, + u64: From, + { + assert_eq!( + unsafe { u64::from(kani_intrinsic::simd_bitmask::<_, M, E, LANES>(mask.clone())) }, + mask.to_bitmask() + ); + } + + /// Compare the value returned by our model and the simd_bitmask intrinsic. + fn check_bitmask(mask: T) + where + T: Clone, + U: PartialEq + Debug, + E: kani_intrinsic::MaskElement, + [u8; kani_intrinsic::mask_len(LANES)]: Sized, + { + assert_eq!( + unsafe { kani_intrinsic::simd_bitmask::<_, U, E, LANES>(mask.clone()) }, + unsafe { simd_bitmask::(mask) } + ); + } + + /// Similar to portable simd_harness. + #[test] + fn check_mask_harness() { + // From array doesn't work either. Manually build [false, true, false, true] + let mut mask = mask32x4::splat(false); + mask.set(1, true); + mask.set(3, true); + let bitmask = mask.to_bitmask(); + assert_eq!(bitmask, 0b1010); + + let kani_mask = + unsafe { u64::from(kani_intrinsic::simd_bitmask::<_, u8, u32, 4>(mask.clone())) }; + assert_eq!(kani_mask, bitmask); + } +} diff --git a/library/kani/src/slice.rs b/library/kani/src/slice.rs index f06118a4b2f5..1b13de29d589 100644 --- a/library/kani/src/slice.rs +++ b/library/kani/src/slice.rs @@ -1,8 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::{any, assume, Arbitrary}; -use std::alloc::{alloc, dealloc, Layout}; -use std::ops::{Deref, DerefMut}; +use crate::{any, assume}; /// Given an array `arr` of length `LENGTH`, this function returns a **valid** /// slice of `arr` with non-deterministic start and end points. This is useful @@ -34,103 +32,3 @@ fn any_range() -> (usize, usize) { assume(from <= to); (from, to) } - -/// A struct that stores a slice of type `T` with a non-deterministic length -/// between `0..=MAX_SLICE_LENGTH` and with non-deterministic content. This is -/// useful in situations where one wants to verify that all slices with any -/// content and with a length up to `MAX_SLICE_LENGTH` satisfy a certain -/// property. Use `any_slice` to create an instance of this struct. -/// -/// # Example: -/// -/// ```rust -/// let slice: kani::slice::AnySlice = kani::slice::any_slice(); -/// foo(&slice); // where foo is a function that takes a slice and verifies a property about it -/// ``` -pub struct AnySlice { - layout: Layout, - ptr: *mut T, - slice_len: usize, -} - -impl AnySlice { - fn new() -> Self - where - T: Arbitrary, - { - let any_slice = AnySlice::::alloc_slice(); - unsafe { - let mut i = 0; - // Note: even though the guard `i < MAX_SLICE_LENGTH` is redundant - // since the assumption above guarantees that `slice_len` <= - // `MAX_SLICE_LENGTH`, without it, CBMC fails to infer the required - // unwind value, and requires specifying one, which is inconvenient. - // CBMC also fails to infer the unwinding if the loop is simply - // written as: - // for i in 0..slice_len { - // *(ptr as *mut T).add(i) = any(); - // } - while i < any_slice.slice_len && i < MAX_SLICE_LENGTH { - std::ptr::write(any_slice.ptr.add(i), any()); - i += 1; - } - } - any_slice - } - - fn alloc_slice() -> Self { - let slice_len = any(); - assume(slice_len <= MAX_SLICE_LENGTH); - let layout = Layout::array::(slice_len).unwrap(); - let ptr = if slice_len == 0 { std::ptr::null() } else { unsafe { alloc(layout) } }; - Self { layout, ptr: ptr as *mut T, slice_len } - } - - pub fn get_slice(&self) -> &[T] { - if self.slice_len == 0 { - &[] - } else { - unsafe { std::slice::from_raw_parts(self.ptr, self.slice_len) } - } - } - - pub fn get_slice_mut(&mut self) -> &mut [T] { - if self.slice_len == 0 { - &mut [] - } else { - unsafe { std::slice::from_raw_parts_mut(self.ptr, self.slice_len) } - } - } -} - -impl Drop for AnySlice { - fn drop(&mut self) { - if self.slice_len > 0 { - assert!(!self.ptr.is_null()); - unsafe { - dealloc(self.ptr as *mut u8, self.layout); - } - } - } -} - -impl Deref for AnySlice { - type Target = [T]; - - fn deref(&self) -> &Self::Target { - self.get_slice() - } -} - -impl DerefMut for AnySlice { - fn deref_mut(&mut self) -> &mut Self::Target { - self.get_slice_mut() - } -} - -pub fn any_slice() -> AnySlice -where - T: Arbitrary, -{ - AnySlice::::new() -} diff --git a/library/kani/src/vec.rs b/library/kani/src/vec.rs index 5deb90d0ec14..626d152f02d4 100644 --- a/library/kani/src/vec.rs +++ b/library/kani/src/vec.rs @@ -1,6 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::{any, assume, Arbitrary}; +use crate::{any, any_where, Arbitrary}; /// Generates an arbitrary vector whose length is at most MAX_LENGTH. pub fn any_vec() -> Vec @@ -8,12 +8,18 @@ where T: Arbitrary, [(); std::mem::size_of::<[T; MAX_LENGTH]>()]:, { - let mut v = exact_vec::(); - let real_length: usize = any(); - assume(real_length <= MAX_LENGTH); - unsafe { v.set_len(real_length) }; - - v + let real_length: usize = any_where(|sz| *sz <= MAX_LENGTH); + match real_length { + 0 => vec![], + exact if exact == MAX_LENGTH => exact_vec::(), + _ => { + let mut any_vec = exact_vec::(); + any_vec.truncate(real_length); + any_vec.shrink_to_fit(); + assert!(any_vec.capacity() == any_vec.len()); + any_vec + } + } } /// Generates an arbitrary vector that is exactly EXACT_LENGTH long. diff --git a/library/kani/stubs/C/hashset/hashset.c b/library/kani/stubs/C/hashset/hashset.c deleted file mode 100644 index e92affafb487..000000000000 --- a/library/kani/stubs/C/hashset/hashset.c +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -#include -#include -#include - -// This HashSet stub implementation is supposed to work with c_hashset.rs. -// Please refer to that file for an introduction to the idea of a HashSet and -// some other implemenntation details. Public methods defined in c_hashset.rs -// act as wrappers around methods implemented here. - -// As noted before, this HashSet implementation is specifically for inputs which -// are u16. The details below can be extended to larger sets if necessary. The -// domain of the output is i16. -// -// The hash function that we choose is the identity function. -// For all input x, hasher(x) = x. For our case, this satisfies all the -// requirements of an ideal hash function, it is 1:1 and there exists only one -// element in the input domain for each value in the output domain. -// -// An important thing to note here is that the hash function can be -// appropriately modified depending on the type of the input value which is -// stored in the hashset. As an example, if the HashSet needs to store a tuple -// of integers, say , the hash function can be modified to be: -// -// hash((x, y)) = prime * x + y; -// -// Although this value can be greater than the chosen output domain, the -// function is still sound if the value wraps around because it guarantees a -// unique output for a given pair of x and y. -// -// Another way to think about this problem could be through the lens of -// uninterpreted functions where : if x == y => f(x) == f(y). Exploring this can -// be future work. The idea would be to implement a HashSet similar to that seen -// in functional programming languages. -// -// For the purpose of a HashSet, we dont necessarily need a SENTINEL outside the -// range of the hashing function because of the way we design the HashSet -// operations. -const uint16_t SENTINEL = 1; - -uint16_t hasher(uint16_t value) { return value; } - -// We initialize all values of the domain to be 0 by initializing it with -// calloc. This lets us get around the problem of looping through all elements -// to initialize them individually with a special value. -// -// The domain array is to be interpreted such that -// if domain[index] != 0, value such that hash(value) = index is present. -// -// However, this logic does not work for the value 0. For this, we choose the -// SENTINEL value to initialize that element. -typedef struct { - uint16_t *domain; -} hashset; - -// Ideally, this approach is much more suitable if we can work with arrays of -// arbitrary size, specifically infinity. This would allow us to define hash -// functions for any type because the output domain can be considered to be -// infinite. However, CBMC currently does not handle unbounded arrays correctly. -// Please see: https://github.com/diffblue/cbmc/issues/6261. Even in that case, -// we might run into theoretical limitations of how solvers handle uninterpreted -// symbols such as unbounded arrays. For the case of this API, the consumer can -// request for an arbitrary number of HashSets which can be dynamically chosen. -// As a result, the solver cannot know apriori how many unbounded arrays it -// needs to initialize which might lead to errors. -// -// Firecracker uses HashSet (src/devices/src/virtio/vsock/unix/muxer.rs). -// But for working with u32s, we run into the problem that the entire domain -// cannot be allocated through malloc. We run into the error "array too large -// for flattening". For that reason, we choose to work with u16 to demonstrate -// the feasability of this approach. However, it should be extensible to other -// integer types. -// -// Returns: pointer to a hashset instance which tracks the domain memory. This -// pointer is used in later callbacks such as insert() and remove(). -hashset *hashset_new() -{ - hashset *set = ( hashset * )malloc(sizeof(hashset)); - // Initializes value all indexes to be 0, indicating that those elements are - // not present in the HashSet. - set->domain = calloc(UINT16_MAX, sizeof(uint16_t)); - // For 0, choose another value to achieve the same. - set->domain[ 0 ] = SENTINEL; - return set; -} - -// For insert, we need to first check if the value exists in the HashSet. If it -// does, we immediately return a 0 (false) value back. -// -// If it doesnt, then we mark that element of the domain array with the value to -// indicate that this element has been inserted. For element 0, we mark it with -// the SENTINEL. -// -// To check if a value exists, we simply check if domain[hash] != 0 and -// in the case of 0 if domain[0] != SENTINEL. -// -// Returns: an integer value 1 or 0. If the value is already present in the -// hashset, this function returns a 0. If the value is sucessfully inserted, we -// return a 1. -uint32_t hashset_insert(hashset *s, uint16_t value) -{ - uint16_t hash = hasher(value); - - if ((hash == 0 && s->domain[ hash ] != SENTINEL) || (hash != 0 && s->domain[ hash ] != 0)) { return 0; } - - s->domain[ hash ] = value; - return 1; -} - -// We perform a similar check here as described in hashset_insert(). We do not -// duplicate code so as to not compute the hash twice. This can be improved. -// -// Returns: an integer value 1 or 0. If the value is present in the hashset, -// this function returns a 1, otherwise 0. -uint32_t hashset_contains(hashset *s, uint16_t value) -{ - uint16_t hash = hasher(value); - - if ((hash == 0 && s->domain[ hash ] != SENTINEL) || (hash != 0 && s->domain[ hash ] != 0)) { return 1; } - - return 0; -} - -// We check if the element exists in the array. If it does not, we return a 0 -// (false) value back. If it does, we mark it with 0 and in the case of 0, we -// mark it with the SENTINEL and return 1. -// -// Returns: an integer value 1 or 0. If the value is not present in the hashset, -// this function returns a 0. If the value is sucessfully removed from the -// hashset, it returns a 1. -uint32_t hashset_remove(hashset *s, uint16_t value) -{ - uint16_t hash = hasher(value); - - if ((hash == 0 && s->domain[ hash ] == SENTINEL) || (hash != 0 && s->domain[ hash ] == 0)) { return 0; } - - if (hash == 0) { - s->domain[ hash ] = SENTINEL; - } else { - s->domain[ hash ] = 0; - } - - return 1; -} diff --git a/library/kani/stubs/C/vec/vec.c b/library/kani/stubs/C/vec/vec.c deleted file mode 100644 index ce0fb0433661..000000000000 --- a/library/kani/stubs/C/vec/vec.c +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -#include -#include -#include -#include - -// This Vector stub implementation is supposed to work with c_vec.rs. Please -// refer to that file for a detailed explanation about the workings of this -// abstraction. Public methods implemented in c_vec.rs act as wrappers around -// methods implemented here. - -// __CPROVER_max_malloc_size is dependent on the number of offset bits used to -// represent a pointer variable. By default, this is chosen to be 56, in which -// case the max_malloc_size is 2 ** (offset_bits - 1). We could go as far as to -// assign the default capacity to be the max_malloc_size but that would be overkill. -// Instead, we choose a high-enough value 2 ** 10. Another reason to do -// this is that it would be easier for the solver to reason about memory if multiple -// Vectors are initialized by the abstraction consumer. -// -// For larger array sizes such as 2 ** (31 - 1) we encounter "array size too large -// for flattening" error. -#define DEFAULT_CAPACITY 1024 -#define MAX_MALLOC_SIZE 18014398509481984 - -// A Vector is a dynamically growing array type with contiguous memory. We track -// allocated memory, the length of the Vector and the capacity of the -// allocation. - -// As can be seen from the pointer to mem (unint32_t*), we track memory in terms -// of words. The current implementation works only if the containing type is -// u32. This was specifically chosen due to a use case seen in the Firecracker -// codebase. This structure is used to communicate over the FFI boundary. -// Future work: -// Ideally, the pointer to memory would be uint8_t* - representing that we treat -// memory as an array of bytes. This would allow us to be generic over the type -// of the element contained in the Vector. In that case, we would have to treat -// every sizeof(T) bytes as an indivdual element and cast memory accordingly. -typedef struct { - uint32_t *mem; - size_t len; - size_t capacity; -} vec; - -// The grow operation resizes the vector and copies its original contents into a -// new allocation. This is one of the more expensive operations for the solver -// to reason about and one way to get around this problem is to use a large -// allocation size. We also implement sized_grow which takes a argument -// definining the minimum number of additional elements that need to be fit into -// the Vector memory. This aims to replicate behavior as seen in the Rust -// standard library where the size of the vector is decided based on the -// following equation: -// new_capacity = max(capacity * 2, capacity + additional). -// Please refer to method amortized_grow in raw_vec.rs in the Standard Library -// for additional information. -// The current implementation performance depends on CBMCs performance about -// reasoning about realloc. If CBMC does better, do would we in the case of -// this abstraction. -// -// One important callout to make here is that because we allocate a large enough -// buffer, we cant reason about buffer overflow bugs. This is because the -// allocated memory will (most-likely) always have enough space allocated after -// the required vec capacity. -// -// Future work: -// Ideally, we would like to get around the issue of resizing altogether since -// CBMC supports unbounded arrays. In that case, we would allocate memory of -// size infinity and work with that. For program verification, this would -// optimize a lot of operations since the solver does not really have to worry -// about the bounds of memory. The appropriate constant for capacity would be -// __CPROVER_constant_infinity_uint but this is currently blocked due to -// incorrect translation of the constant: https://github.com/diffblue/cbmc/issues/6261. -// -// Another way to approach this problem would be to implement optimizations in -// the realloc operation of CBMC. Rather than allocating a new memory block and -// copying over elements, we can track only the end pointer of the memory and -// shift it to track the new length. Since this behavior is that of the -// allocator, the consumer of the API is blind to it. -void vec_grow_exact(vec *v, size_t new_cap) -{ - uint32_t *new_mem = ( uint32_t * )realloc(v->mem, new_cap * sizeof(*v->mem)); - - v->mem = new_mem; - v->capacity = new_cap; -} - -void vec_grow(vec *v) -{ - size_t new_cap = v->capacity * 2; - if (new_cap > MAX_MALLOC_SIZE) { - // Panic if the new size requirement is greater than max size that can - // be allocated through malloc. - assert(0); - } - - vec_grow_exact(v, new_cap); -} - -void vec_sized_grow(vec *v, size_t additional) -{ - size_t min_cap = v->capacity + additional; - size_t grow_cap = v->capacity * 2; - - // This resembles the Rust Standard Library behavior - amortized_grow in - // alloc/raw_vec.rs - // - // Reference: https://doc.rust-lang.org/src/alloc/raw_vec.rs.html#421 - size_t new_cap = min_cap > grow_cap ? min_cap : grow_cap; - if (new_cap > MAX_MALLOC_SIZE) { - // Panic if the new size requirement is greater than max size that can - // be allocated through malloc. - assert(0); - } - - vec_grow_exact(v, new_cap); -} - -vec *vec_new() -{ - vec *v = ( vec * )malloc(sizeof(vec)); - // Default size is DEFAULT_CAPACITY. We compute the maximum number of - // elements to ensure that allocation size is aligned. - size_t max_elements = DEFAULT_CAPACITY / sizeof(*v->mem); - v->mem = ( uint32_t * )malloc(max_elements * sizeof(*v->mem)); - v->len = 0; - v->capacity = max_elements; - // Return a pointer to the allocated vec structure, which is used in future - // callbacks. - return v; -} - -vec *vec_with_capacity(size_t capacity) -{ - vec *v = ( vec * )malloc(sizeof(vec)); - if (capacity > MAX_MALLOC_SIZE) { - // Panic if the new size requirement is greater than max size that can - // be allocated through malloc. - assert(0); - } - - v->mem = ( uint32_t * )malloc(capacity * sizeof(*v->mem)); - v->len = 0; - v->capacity = capacity; - return v; -} - -void vec_push(vec *v, uint32_t elem) -{ - // If we have already reached capacity, resize the Vector before - // pushing in new elements. - if (v->len == v->capacity) { - // Ensure that we have capacity to hold atleast one more element - vec_sized_grow(v, 1); - } - - v->mem[ v->len ] = elem; - v->len += 1; -} - -uint32_t vec_pop(vec *v) -{ - assert(v->len > 0); - v->len -= 1; - - return v->mem[ v->len ]; -} - -void vec_append(vec *v1, vec *v2) -{ - // Reserve enough space before adding in new elements. - vec_sized_grow(v1, v2->len); - // Perform a memcpy of elements which is cheaper than pushing each element - // at once. - memcpy(v1->mem + v1->len, v2->mem, v2->len * sizeof(*v2->mem)); - v1->len = v1->len + v2->len; -} - -size_t vec_len(vec *v) { return v->len; } - -size_t vec_cap(vec *v) { return v->capacity; } - -void vec_free(vec *v) -{ - free(v->mem); - free(v); -} diff --git a/library/kani/stubs/README.md b/library/kani/stubs/README.md deleted file mode 100644 index c2972a8c9d8e..000000000000 --- a/library/kani/stubs/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Verification-friendly Vector stubs ----------- - - diff --git a/library/kani/stubs/Rust/hashset/c_hashset.rs b/library/kani/stubs/Rust/hashset/c_hashset.rs deleted file mode 100644 index 3c1e3f20015a..000000000000 --- a/library/kani/stubs/Rust/hashset/c_hashset.rs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -// NOTE: Code in this file and hashset.c is experimental and is meant to be a -// proof-of-concept implementation of the idea. It is unsound and might not work -// with all test cases. More details below. - -// CHashSet is an abstraction of the HashSet library. This is also implemented as -// a Rust frontend and a C backend (similar to CVec). HashSets are hard to reason -// about for verification tools due to the nature of hash functions. To that end, -// this program tries to implement a HashSet for u16 values. -// -// A typical hashset is defined using two main components: -// -// 1. Hash function which maps the values in the input domain to a set of values -// in an output domain. Ideally, the output domain is larger than the input domain -// to ensure that there are special values such as the SENTINEL value which cannot -// be generated through the hashing function. Hash functions are 1:1 injections - -// for a certain input, they will deterministically generate the same output value -// and that no other input will generate that hash value. -// For our case, we have implemented this idea for u16. This implies that -// the input domain is <0 .. u16::MAX>. The output domain is chosen to be -// i16 <-i16::MAX .. i16::MAX>. We can theoretically choose any output domain -// which can provide us with a special value such that it does not lie in the range -// of the hash function. -// -// 2. HashSets also have a map which allow us to check the existence of an element -// in amortized constant O(1) time. They use the hashed value as the key into the -// map to check if a truth value is present. For implementing HashMaps however, we -// need to store the mapped value at the hashed location. -// -// We implement this idea in hashset.c, please refer to that file for implementation -// specifics. - -// c_hashset consists of a pointer to the memory which tracks the hashset allocation -// in the C backend. This is used to exchange information over the FFI boundary. -// -// In theory, this can also be implemented purely in Rust. We chose to implement -// using the C-FFI to leverage CBMC constructs. -// -// But it important to note here that Kani currently does not support unbounded -// structures and arrays; -// Tracking issue: https://github.com/model-checking/kani/issues/311 -#[repr(C)] -pub struct c_hashset { - domain: *mut int16_t, -} - -// All of the functions below call into implementations defined in vec.c. -// -// For other related future work on how this interface can be automatically -// generated and made cleaner, please refer c_vec.rs. -extern "C" { - // Returns a pointer to a new c_hashset structure. - fn hashset_new() -> *mut c_hashset; - - // Inserts a new value in the hashset. If the value is already present, - // this function returns 0 else, returns 1. - fn hashset_insert(ptr: *mut c_hashset, value: uint16_t) -> uint32_t; - - // Checks if the value is contained in the hashset. Returns 1 if present, 0 - // otherwise. - fn hashset_contains(ptr: *mut c_hashset, value: uint16_t) -> uint32_t; - - // Removes a value from the hashset. If the value is not present, it returns 0 - // else 1. - fn hashset_remove(ptr: *mut c_hashset, value: uint16_t) -> uint32_t; -} - -// The HashSet interface exposed to the user only tracks the pointer to the -// low-level c_hashset structure. All methods defined on this structure act as -// wrappers and call into the C implementation. -// -// The implementation is currently not generic over the contained type. -pub struct HashSet { - ptr: *mut c_hashset, - _marker: PhantomData, -} - -// Wrapper methods which ensure that consumer code does not have to make calls -// to unsafe C-FFI functions. -impl HashSet { - pub fn new() -> Self { - unsafe { HashSet { ptr: hashset_new(), _marker: Default::default() } } - } - - pub fn insert(&mut self, value: uint16_t) -> bool { - unsafe { hashset_insert(self.ptr, value) != 0 } - } - - pub fn contains(&self, value: &uint16_t) -> bool { - unsafe { hashset_contains(self.ptr, *value) != 0 } - } - - pub fn remove(&mut self, value: uint16_t) -> bool { - unsafe { hashset_remove(self.ptr, value) != 0 } - } -} diff --git a/library/kani/stubs/Rust/vec/c_vec.rs b/library/kani/stubs/Rust/vec/c_vec.rs deleted file mode 100644 index 223e56529a9a..000000000000 --- a/library/kani/stubs/Rust/vec/c_vec.rs +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -mod utils; -use utils::libc::{int16_t, size_t, uint16_t, uint32_t}; - -use std::marker::PhantomData; - -// CVec is an abstraction of the Vector library which is implemented as a Rust-based -// frontend and a C based backend. All public facing methods here are implemented -// as wrappers around FFI functions which call into methods implemented in C. There -// were multiple reasons as to why this abstractions was conceived: -// -// 1. Reduce the cost of translation: Kani translates Rust code into equivalent -// gotoC representation which is used for verification with CBMC. However, this -// might introduce additional overhead due to nesting of calls, monomorphization, -// handling generics, etc. CVec gets around that issue by making direct calls -// to the C implementation. This is usually hard to reason about for most other -// frameworks since they are unable to handle unsafe code. But because of the -// way Kani works, it is almost zero cost to achieve this. -// -// 2. Leverage CBMC primitives: Some CBMC primitives cannot yet be correctly -// translated/handled by Kani, for instance: __CPROVER_constant_infinity_uint. If -// there are improvements in CBMC, a quick way to test their applicability in -// Kani would be to work with this abstraction and perform experiments. - -// A c_vec consists of a pointer to allocated memory, the capacity of the allocation and -// the length of the vector to be used in verification. This structure is also -// defined in c_vec and is used across the FFI boundary. -// -// An important callout to make here is that this structure is currently only defined -// to work with a Vec. This code was meant to experiment with and demonstrate -// the ability to work with CBMCs C interface. -#[repr(C)] -pub struct c_vec { - mem: *mut uint32_t, - len: size_t, - capacity: size_t, -} - -// All of the functions below call into implementations defined in vec.c -// -// We could also move to a more polished definition which is defined in a .h header -// file which is what this interface would need to care about. For now, the -// definition and the implementation reside in vec.c. -// -// Although these are defined manually, it might be worthwhile to look at projects -// such as cbindgen which can generate automatically generate headers for Rust -// code which exposes a public interface. For instance, we could define generic -// Vector representations and have the framework generate headers for us which we -// can then implement. In that case, we would have to implement the C backend in -// such a way that it can handle types of arbitrary sizes by casting memory blocks -// and Vector elements and treating them as such. -// Reference: https://github.com/eqrion/cbindgen -extern "C" { - // Returns pointer to a new c_vec structure. The default capacity of the allocated - // vec is (1073741824 / sizeof(u32)) at the maximum. - fn vec_new() -> *mut c_vec; - - // Returns pointer to a new c_vec structure. The capacity is provided as an - // argument. - fn vec_with_capacity(cap: size_t) -> *mut c_vec; - - // Pushes a new elements to the Vector. If there is not enough space to allocate - // the element, the Vector will resize itself. - fn vec_push(ptr: *mut c_vec, elem: uint32_t); - - // Pop an element out of the Vector. The wrapper function contains a check - // to ensure that we are not popping a value off of an empty Vector. - fn vec_pop(ptr: *mut c_vec) -> uint32_t; - - // Returns the current capacity of allocation. - fn vec_cap(ptr: *mut c_vec) -> size_t; - - // Returns the length of the Vector - fn vec_len(ptr: *mut c_vec) -> size_t; - - // Append Vector represented by ptr2 to ptr1. - fn vec_append(ptr1: *mut c_vec, ptr2: *mut c_vec); - - // Grow the allocated vector in size such that it accomodates atleast - // additional elements. This is similar in behavior to the implementation of - // the Rust Standard Library. Please refer to vec.c for more details. - fn vec_sized_grow(ptr: *mut c_vec, additional: size_t); - - // Free allocated memory for the Vec - fn vec_free(ptr: *mut c_vec); -} - -// The Vec interface which is exposed to the user only tracks the pointer to the -// low-level c_vec structure. All methods defined on this structure act as wrappers -// and call into the C implementation. -// -// The implementation is currently not generic over the contained type. -pub struct Vec { - ptr: *mut c_vec, - _marker: PhantomData, -} - -// Wrapper methods which ensure that consumer code does not have to make calls -// to unsafe C-FFI functions. -impl Vec { - pub fn ptr(&mut self) -> *mut c_vec { - return self.ptr; - } - - pub fn new() -> Self { - unsafe { Vec { ptr: vec_new(), _marker: Default::default() } } - } - - pub fn with_capacity(cap: usize) -> Self { - unsafe { Vec { ptr: vec_with_capacity(cap), _marker: Default::default() } } - } - - pub fn push(&mut self, elem: uint32_t) { - unsafe { - vec_push(self.ptr, elem); - } - } - - // Check if the length of the Vector is 0, in which case we return a None. - // Otherwise, we make a call to the vec_pop() function and wrap the result around - // a Some. - pub fn pop(&mut self) -> Option { - if self.len() == 0 { None } else { unsafe { Some(vec_pop(self.ptr)) } } - } - - pub fn append(&mut self, other: &mut Self) { - unsafe { - vec_append(self.ptr, other.ptr()); - } - } - - pub fn capacity(&self) -> usize { - unsafe { vec_cap(self.ptr) as usize } - } - - pub fn len(&self) -> usize { - unsafe { vec_len(self.ptr) as usize } - } - - pub fn reserve(&mut self, additional: usize) { - unsafe { - vec_sized_grow(self.ptr, additional); - } - } -} - -impl Drop for Vec { - // We have implemented Vec for u32 which does not have any drop semantics - // associated with it. We are only responsible for deallocating the space - // allocated on the C backend for the Vec and the c_vec structure. - // - // For elements of the Vector which need a custom drop, the ideal behavior - // here would be to pop each element from the Vector and call drop_in_place(). - // Refer: https://doc.rust-lang.org/std/ptr/fn.drop_in_place.html - fn drop(&mut self) { - unsafe { - vec_free(self.ptr); - } - } -} - -// Here, we define the kani_vec! macro which behaves similar to the vec! macro -// found in the std prelude. If we try to override the vec! macro, we get error: -// -// = note: `vec` could refer to a macro from prelude -// note: `vec` could also refer to the macro defined here -// -// Relevant Zulip stream: -// https://rust-lang.zulipchat.com/#narrow/stream/122651-general/topic/Override.20prelude.20macro -// -// The workaround for now is to define a new macro. kani_vec! will initialize a new -// Vec based on its definition in this file. We support two types of initialization -// expressions: -// -// [ elem; count] - initialize a Vector with element value `elem` occurring count times. -// [ elem1, elem2, ...] - initialize a Vector with elements elem1, elem2... -#[cfg(abs_type = "c-ffi")] -#[macro_export] -macro_rules! kani_vec { - ( $val:expr ; $count:expr ) => - ({ - let mut result = Vec::with_capacity($count); - let mut i: usize = 0; - while i < $count { - result.push($val); - i += 1; - } - result - }); - ( $( $xs:expr ),* ) => { - { - let mut result = Vec::new(); - $( - result.push($xs); - )* - result - } - }; -} diff --git a/library/kani/stubs/Rust/vec/kani_vec.rs b/library/kani/stubs/Rust/vec/kani_vec.rs deleted file mode 100644 index 1a9a10651ffc..000000000000 --- a/library/kani/stubs/Rust/vec/kani_vec.rs +++ /dev/null @@ -1,1100 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -mod utils; -use utils::libc; - -use std::cmp; -use std::convert::TryFrom; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::iter::FromIterator; -use std::mem; -use std::ops::{Deref, DerefMut, FnMut, Index, IndexMut}; -use std::ptr::{drop_in_place, read}; -use std::slice; - -// __CPROVER_max_malloc_size is dependent on the number of offset bits used to -// represent a pointer variable. By default, this is chosen to be 56, in which -// case the max_malloc_size is 2 ** (offset_bits - 1). We could go as far as to -// assign the default capacity to be the max_malloc_size but that would be overkill. -// Instead, we choose a high-enough value 2 ** 10. Another reason to do -// this is that it would be easier for the solver to reason about memory if multiple -// Vectors are initialized by the abstraction consumer. -// -// For larger array sizes such as 2 ** (31 - 1) we encounter "array size too large -// for flattening" error. -const DEFAULT_CAPACITY: usize = 1024; -const CBMC_MAX_MALLOC_SIZE: usize = 18014398509481984; - -// We choose a constant which will ensure that we dont allocate small vectors. -// Small vectors will lead to more resizing operations and hence slowdown in -// verification performance. It is possible for the consumer of this abstraction -// allocate small buffers, specifically using with_capacity() functions. But there -// are no guarantees made about the allocation once it is full. Even then, the -// user can then choose to shrink_to_fit() if they want to play around with -// tight bounds on the Vec capacity. -const MIN_NON_ZERO_CAP: usize = 1024; - -// KaniVec implements a fine-grained abstraction of the Vector library for Rust. -// It is aimed to provide a lot more functionality than the other two available -// abstractions - NoBackVec and CVec. KaniVec aims to implement close-to-complete -// compatibility with the Rust Standard Library (RSL) implementation. -// -// The goal of KaniVec is to implement basic operations of the Vec such as push(), -// pop(), append(), insert() in a much simpler way than it is done in the RSL. The -// intuition behind this idea is that with a simple trace, it would be much easier -// for verification techniques such as bounded model checking to reason about -// that piece of code. For that reason, we choose to directly drop down to libc -// functions for low-level operations so that they can be directly translated -// to CBMC primitives. That way, if CBMC performs better through some optimizations -// Kani would too. -// -// We first implement KaniRawVec, an auxiliary data structure which holds a pointer -// to allocated memory and the capacity of the allocation. This abstracts away -// all low-level memory resizing operations from the actual Vec data structure. -// It is also used later to implement KaniIter, an iterator for the KaniVec -// data structure. -// -// We then use KaniRawVec as a member of Vec (KaniVec) which is the interface exposed -// to the public. KaniVec aims to main close-to-complete compatibility with the -// RSL Vec implementation. -// -// An important future work direction here is to abstract other relevant -// data-structures such as Strings and HashMaps but implementing optimization -// for slices seems super crucial. Most customer code deals with slices since -// operations such as sort(), split(), get() which traditionally deal with linear -// data structures are implemented on the slice. The advantage of doing it so -// is that other data structures have to implement coercion to a slice and -// can then get all these methods for free. For instance, this is done for Vec, -// String, etc. Currently, we implement coercion to std::slice primitive type allowing -// us to take benefits of that implementation directly but we could get much better -// performance for real world code if we could develop abstractions for that -// as well. Initial intuitions are that it might be harder since those operations -// are typically not verification-friendly. -// -// Please note that this implementation has not been tested against ZSTs - Zero -// Sized Types and might show unsound behavior. - -// KaniRawVec consists of a pointer to allocated memory and another variable tracking -// the capacity of the allocation. -struct KaniRawVec { - ptr: *const T, - cap: usize, -} - -impl KaniRawVec { - fn new() -> Self { - let elem_size = mem::size_of::(); - // NOTE: Currently, this abstraction is not tested against code which has - // zero-sized types. - assert!(elem_size != 0); - // We choose to allocate a Vector of DEFAULT_CAPACITY which is chosen - // to be a very high value. This way, for most tests, the trace will not - // generate any resizing operations. - // - // An important callout to make here is that this however prevents us - // from finding buffer overflow bugs. As we always allocate large enough - // memory, there will always be enough space for writing data after the - // index crosses the length of the array. - let cap = DEFAULT_CAPACITY; - let ptr = unsafe { libc::malloc(cap * elem_size) as *mut T }; - KaniRawVec { ptr, cap } - } - - fn new_with_capacity(cap: usize) -> Self { - let elem_size = mem::size_of::(); - // In this case, allocate space for capacity elements as requested. - let ptr = unsafe { libc::malloc(cap * elem_size) as *mut T }; - KaniRawVec { ptr, cap } - } - - // Checks if the Vector needs to be resized to allocate additional more elements. - fn needs_to_grow(&self, len: usize, additional: usize) -> bool { - additional > self.cap - len - } - - // grow() and grow_exact() are functions which reallocate the memory to a larger - // allocation if we run out of space. These are typically called from wrappers - // such as reserve() and reserve_exact() from Vec. The semantics of both of these - // functions is similar to that implemented in the RSL. It is important to call - // out what they are. - // - // According to the RSL, the reserve() function is defined as: - // - // "Reserves capacity for at least additional more elements to be inserted in - // the given Vec. The collection may reserve more space to avoid frequent - // reallocations. After calling reserve, capacity will be greater than or - // equal to self.len() + additional. - // Does nothing if capacity is already sufficient." - // - // The important point to note here is that it is expected to reserve space - // for "atleast" additional more elements. Because of which, there cannot be - // any guarantees made about how much the exact capacity would be after the - // grow()/reserve() operation is performed. - // - // For the purpose of this implementation, we follow the specifics implemented - // in raw_vec.rs -> grow_amortized(). We choose: - // - // Reference: https://doc.rust-lang.org/src/alloc/raw_vec.rs.html#421 - // - // max ( current_capacity * 2 , current_length + additional ). - // This ensures exponential growth of the allocated memory and also reduces - // the number of resizing operations required. - // - // We also ensure that the new allocation is greater than a certain minimum - // that we want to deal with for verification. - fn grow(&mut self, len: usize, additional: usize) { - let elem_size = mem::size_of::(); - let req_cap = len + additional; - let grow_cap = self.cap * 2; - - let new_cap = if req_cap > grow_cap { req_cap } else { grow_cap }; - let new_cap = if MIN_NON_ZERO_CAP > new_cap { MIN_NON_ZERO_CAP } else { new_cap }; - // As per the definition of reserve() - assert!(new_cap * elem_size <= isize::MAX as usize); - unsafe { - self.ptr = libc::realloc(self.ptr as *mut libc::c_void, new_cap * elem_size) as *mut T; - } - self.cap = new_cap; - } - - fn reserve(&mut self, len: usize, additional: usize) { - if self.needs_to_grow(len, additional) { - self.grow(len, additional); - } - } - - // grow_exact() also poses interesting semantics for the case of our abstraction. - // According to the RSL: - // - // "Reserves the minimum capacity for exactly additional more elements to be inserted in the - // given Vec. After calling reserve_exact, capacity will be greater than or equal to - // self.len() + additional. Does nothing if the capacity is already sufficient. - // Note that the allocator may give the collection more space than it requests. Therefore, - // capacity can not be relied upon to be precisely minimal. Prefer reserve if future insertions - // are expected." - // - // As can be observed, the capacity cannot be relied upon to be precisely minimal. - // However, we try to model the RSL behavior as much as we can. Please refer to - // grow_exact() from kani_vec.rs for more details. - fn grow_exact(&mut self, len: usize, additional: usize) { - let elem_size = mem::size_of::(); - let req_cap = len + additional; - // The RSL implementation checks if we are growing beyond usize::MAX - // for ZSTs and panics. The idea is that if we need to grow for a ZST, - // that effectively means that something has gone wrong. - assert!(elem_size != 0); - unsafe { - self.ptr = libc::realloc(self.ptr as *mut libc::c_void, req_cap * elem_size) as *mut T; - } - self.cap = req_cap; - } - - fn reserve_exact(&mut self, len: usize, additional: usize) { - if self.needs_to_grow(len, additional) { - self.grow_exact(len, additional); - } - } - - // Reallocate memory such that the allocation size is equal to the exact - // requirement of the Vector. We try to model RSL behavior (refer raw_vec.rs - // shrink()) but according to the RSL: - // - // "It will drop down as close as possible to the length but the allocator - // may still inform the vector that there is space for a few more elements." - // - // Even in this case, no guarantees can be made to ensure that the capacity - // of the allocationa after shrinking would be exactly equal to the length. - fn shrink_to_fit(&mut self, len: usize) { - assert!(len <= self.cap); - let elem_size = mem::size_of::(); - unsafe { - self.ptr = libc::realloc(self.ptr as *mut libc::c_void, len * elem_size) as *mut T; - } - self.cap = len; - } - - fn capacity(&self) -> usize { - self.cap - } -} - -// Since we allocate memory manually, the Drop for KaniVec should ensure that we -// free that allocation. We drop to libc::free since we have a pointer to the memory -// that was allocated by libc::malloc / libc::realloc. -impl Drop for KaniRawVec { - fn drop(&mut self) { - unsafe { - libc::free(self.ptr as *mut _); - } - } -} - -// In theory, there is no need to track the Allocator here. However, the RSL -// implementation of the Vector is generic over the type of the Allocator that -// it takes. Also, many functions are part of impl Blocks which require that the -// Vec be generic over the type of the Allocator that it takes. -// -// We define an empty trait Allocator which shadows std::alloc::Allocator. -// -// We also define an empty KaniAllocator structure here which serves as the default type -// for the Vec data structure. The Vec implemented as part of the Rust Standard -// Library has the Global allocator as its default. -pub trait Allocator {} - -#[derive(Clone, Copy)] -pub struct KaniAllocator {} - -impl KaniAllocator { - pub fn new() -> Self { - KaniAllocator {} - } -} - -// Implement the Allocator trait -impl Allocator for KaniAllocator {} - -// This is the primary Vec abstraction that is exposed to the user. It has a -// KaniRawVec which tracks the underlying memory and values stored in the Vec. We -// also track the length and an allocator instance. -pub struct Vec { - buf: KaniRawVec, - len: usize, - allocator: A, -} - -// Impl block for helper functions. -impl Vec { - fn ptr(&self) -> *mut T { - self.buf.ptr as *mut T - } - - fn with_capacity_in(capacity: usize, allocator: A) -> Self { - Vec { buf: KaniRawVec::new_with_capacity(capacity), len: 0, allocator: allocator } - } -} - -impl Vec { - pub fn new() -> Self { - Vec { buf: KaniRawVec::new(), len: 0, allocator: KaniAllocator::new() } - } - - pub fn with_capacity(cap: usize) -> Self { - Self::with_capacity_in(cap, KaniAllocator::new()) - } - - // A lot of invariants here are not checked: - // * If the pointer was not allocated via a String/Vec, it is highly likely to be - // incorrect. - // * T needs to have the same and alignment as what ptr was allocated with. - // * length needs to be less than or equal to the capacity. - // * capacity needs to be capacity that the pointer was allocated with. - pub unsafe fn from_raw_parts(ptr: *mut T, length: usize, capacity: usize) -> Self { - // Assert that the alignment of T and the allocated pointer are the same. - assert_eq!(mem::align_of::(), mem::align_of_val(&ptr)); - // Assert that the length is less than or equal to the capacity - assert!(length <= capacity); - // We cannot check if the capacity of the memory pointer to by ptr is - // atleast "capacity", this is to be assumed. - let mut v = Vec { - buf: KaniRawVec::new_with_capacity(capacity), - len: 0, - allocator: KaniAllocator::new(), - }; - unsafe { - let mut curr_idx: isize = 0; - while curr_idx < length as isize { - // The push performed here is cheap as we have already allocated - // enough capacity to hold the data. - v.push_unsafe(read(ptr.offset(curr_idx))); - curr_idx += 1; - } - } - v - } -} - -impl Vec { - pub fn allocator(&self) -> &A { - &self.allocator - } - - pub fn push(&mut self, elem: T) { - // Check if the buffer needs to grow in size, call grow() in that case. - if self.len == self.capacity() { - self.buf.grow(self.len, 1); - } - - unsafe { - *self.ptr().offset(self.len as isize) = elem; - } - self.len += 1; - } - - pub fn push_unsafe(&mut self, elem: T) { - unsafe { - *self.ptr().offset(self.len as isize) = elem; - } - self.len += 1; - } - - // It is important to note that pop() does not trigger any changes in the - // underlying allocation capacity. - pub fn pop(&mut self) -> Option { - if self.len == 0 { - None - } else { - self.len -= 1; - unsafe { Some(read(self.ptr().offset(self.len as isize))) } - } - } - - pub fn insert(&mut self, index: usize, elem: T) { - assert!(index <= self.len); - - // Check if the buffer needs to grow in size, call grow() in that case. - if self.capacity() < (self.len + 1) { - self.buf.grow(self.len, 1); - } - - unsafe { - if index < self.len { - // Perform a memmove of all data from the index starting at idx - // to idx+1 to make space for the element to be inserted - libc::memmove( - self.ptr().offset(index as isize + 1) as *mut libc::c_void, - self.ptr().offset(index as isize) as *mut libc::c_void, - (self.len - index) * mem::size_of::(), - ); - } - *self.ptr().offset(index as isize) = elem; - self.len += 1; - } - } - - pub fn remove(&mut self, index: usize) -> T { - assert!(index < self.len); - - unsafe { - self.len -= 1; - let result = read(self.ptr().offset(index as isize)); - if self.len - index > 0 { - // Perform a memmove of all data from the index starting at idx + 1 - // to idx to occupy space created by the element which was removed. - libc::memmove( - self.ptr().offset(index as isize) as *mut libc::c_void, - self.ptr().offset(index as isize + 1) as *mut libc::c_void, - (self.len - index) * mem::size_of::(), - ); - } - result - } - } - - pub fn len(&self) -> usize { - self.len - } - - // Please refer to grow() and grow_exact() for more details() - pub fn reserve(&mut self, additional: usize) { - self.buf.reserve(self.len, additional); - } - - pub fn reserve_exact(&mut self, additional: usize) { - self.buf.reserve(self.len, additional); - } - - // The following safety guarantees must be satisfied: - // - // * new_len must be less than or equal to capacity(). - // * The elements at old_len..new_len must be initialized. - pub unsafe fn set_len(&mut self, new_len: usize) { - assert!(new_len <= self.capacity()); - - self.len = new_len; - } - - pub fn as_mut_ptr(&mut self) -> *mut T { - self.ptr() - } - - // This is possible as we implement the Deref coercion for Vec - pub fn as_slice(&self) -> &[T] { - self - } - - // This is possible as we implement the DerefMut coercion for Vec - pub fn as_mut_slice(&mut self) -> &mut [T] { - self - } - - pub fn as_ptr(&self) -> *const T { - self.buf.ptr - } - - // According to the RSL: - // - // "Shortens the vector, keeping the first len elements and dropping the rest. - // If len is greater than the vector’s current length, this has no effect. - // Note that this method has no effect on the allocated capacity of the vector." - pub fn truncate(&mut self, len: usize) { - unsafe { - if len > self.len { - return; - } - - // Call drop for elements which are truncated - let remaining_len = self.len - len; - while self.len != len { - self.len -= 1; - drop_in_place(self.as_mut_ptr().offset(self.len as isize)); - } - } - } - - // Clears the vector, removing all values. - // This method has no effect on the allocated capacity of the vector - pub fn clear(&mut self) { - self.truncate(0); - } - - // Removes an element from the Vector and returns it. The removed element is - // replaced by the last element of the Vector. This does not preserve ordering, - // but is O(1) - because we dont perform memory resizing operations. - pub fn swap_remove(&mut self, index: usize) -> T { - let len = self.len; - assert!(index < len); - - unsafe { - let last = read(self.as_ptr().add(len - 1)); - let hole = self.as_mut_ptr().add(index); - self.set_len(len - 1); - let prev_hole = read(hole); - *hole = last; - prev_hole - } - } - - // According to the RSL: - // "Returns the number of elements the vector can hold without reallocating." - // The API consumer cannot rely on the precision of this function. - pub fn capacity(&self) -> usize { - self.buf.capacity() - } - - // Splits the collection into two at the given index. - // - // Returns a newly allocated vector containing the elements in the range [at, len). After the - // call, the original vector will be left containing the elements [0, at) with its previous - // capacity unchanged. - pub fn split_off(&mut self, at: usize) -> Self - where - A: Clone, - { - assert!(at <= self.len); - - let other_len = self.len - at; - let mut other = Vec::with_capacity_in(other_len, self.allocator().clone()); - - unsafe { - // Copy all the elements from "at" till the end of the Vector through - // a memcpy which is much cheaper than remove() and push() - libc::memcpy( - other.as_mut_ptr() as *mut libc::c_void, - self.as_ptr().offset(at as isize) as *mut libc::c_void, - other_len * mem::size_of::(), - ); - - // Set length to point to end of array. - self.set_len(at); - other.set_len(other_len); - } - - other - } - - pub fn append(&mut self, other: &mut Vec) { - // Reserve enough space to reduce the number of resizing operations - self.reserve(other.len()); - unsafe { - libc::memmove( - self.as_ptr().offset(self.len as isize) as *mut libc::c_void, - other.as_ptr() as *mut libc::c_void, - other.len() * mem::size_of::(), - ); - self.len += other.len(); - other.set_len(0); - } - } - - // Resizes the Vec in-place so that len is equal to new_len. - // - // If new_len is greater than len, the Vec is extended by the difference, with each additional - // slot filled with the result of calling the closure f. The return values from f will end up - // in the Vec in the order they have been generated. - // - // If new_len is less than len, the Vec is simply truncated. - pub fn resize_with(&mut self, new_len: usize, f: F) - where - F: FnMut() -> T, - { - let len = self.len; - - if new_len > len { - let additional = new_len - len; - self.reserve(additional); - let mut closure = f; - for _ in 0..additional { - // This push is cheap as we have already reserved enough space. - self.push_unsafe(closure()); - } - } else { - self.truncate(new_len); - } - } - - // The semantics of shrink() and shrink_to_fit() are similar to that of reserve(). - // According to the RSL: - // - // "Shrinks the capacity of the vector as much as possible. - // It will drop down as close as possible to the length but the allocator may still inform the - // vector that there is space for a few more elements." - // - // There cannot be any guarantees made that the capacity will be changed - // to fit the length of the Vector exactly. - pub fn shrink_to_fit(&mut self) { - if self.capacity() > self.len { - self.buf.shrink_to_fit(self.len); - } - } - - // This is an experimental API. According to the RSL: - // - // "Shrinks the capacity of the vector with a lower bound. - // The capacity will remain at least as large as both the length and the supplied value. - // If the current capacity is less than the lower limit, this is a no-op." - pub fn shrink_to(&mut self, min_capacity: usize) { - if self.capacity() > min_capacity { - let max = if self.len > min_capacity { self.len } else { min_capacity }; - self.buf.shrink_to_fit(max); - } - } - - pub fn is_empty(&self) -> bool { - self.len == 0 - } - - pub fn new_in(alloc: A) -> Self { - Vec { buf: KaniRawVec::new(), len: 0, allocator: alloc } - } -} - -impl Vec { - // Resizes the Vec in-place so that len is equal to new_len. - // - // If new_len is greater than len, the Vec is extended by the difference, with each additional - // slot filled with value. If new_len is less than len, the Vec is simply truncated. - // - // This method requires T to implement Clone, in order to be able to clone the passed value. - pub fn resize(&mut self, new_len: usize, value: T) { - let len = self.len; - - if new_len > len { - let additional = new_len - len; - self.reserve(additional); - for _ in 0..additional { - // This push is cheap as we have already reserved enough space. - self.push_unsafe(value.clone()); - } - } else { - self.truncate(new_len); - } - } - - // Clones and appends all elements in a slice to the Vec. - // - // Iterates over the slice other, clones each element, and then appends it to this Vec. The - // other vector is traversed in-order. - pub fn extend_from_slice(&mut self, other: &[T]) { - let other_len = other.len(); - self.reserve(other_len); - for i in 0..other_len { - // This push is cheap as we have already reserved enough space. - self.push_unsafe(other[i].clone()); - } - } -} - -// Drop is codegen for most types, no need to perform any action here. -impl Drop for Vec { - fn drop(&mut self) {} -} - -// Trait implementations for Vec -// We try to implement all major traits for Vec which might be priority for -// our customers. -impl Default for Vec { - fn default() -> Self { - Vec::new() - } -} - -impl PartialEq for Vec { - fn eq(&self, other: &Self) -> bool { - if self.len != other.len() { - return false; - } - - for idx in 0..self.len { - if self[idx] != other[idx] { - return false; - } - } - - return true; - } -} - -// We implement the PartialEq trait for Vec with other slices by using a generic -// macro. As we implement the Deref coercion, we can perform self[index] and compare -// it with the RHS. -macro_rules! __impl_slice_eq1 { - ([$($vars:tt)*] $lhs:ty, $rhs:ty) => { - impl PartialEq<$rhs> for $lhs - where - T: PartialEq, A: Allocator - { - #[inline] - fn eq(&self, other: &$rhs) -> bool { self[..] == other[..] } - #[inline] - fn ne(&self, other: &$rhs) -> bool { self[..] != other[..] } - } - } -} - -__impl_slice_eq1! { [A] Vec, &[U] } -__impl_slice_eq1! { [A] Vec, &mut [U] } -__impl_slice_eq1! { [A] &[T], Vec } -__impl_slice_eq1! { [A] &mut [T], Vec } -__impl_slice_eq1! { [A, const N: usize] Vec, [U; N] } -__impl_slice_eq1! { [A, const N: usize] Vec, &[U; N] } - -// Coercion support into Deref allows us to benefit from operations on slice -// implemented in the standard library. Quoting the RSL: -// -// "Deref coercion is a convenience that Rust performs on arguments to functions -// and methods. Deref coercion works only on types that implement the Deref trait. -// Deref coercion converts such a type into a reference to another type. Deref coercion -// happens automatically when we pass a reference to a particular type’s value -// as an argument to a function or method that doesn’t match the parameter type -// in the function or method definition. A sequence of calls to the deref method -// converts the type we provided into the type the parameter needs." -// -// For our case, the deref coercion implemented here can convert a Vec into a -// primitive slice type. This allows us to benefit from methods implemented -// on the slice type such as sort(), split(), etc. -impl Deref for Vec { - type Target = [T]; - - fn deref(&self) -> &[T] { - unsafe { ::std::slice::from_raw_parts(self.ptr(), self.len) } - } -} - -impl DerefMut for Vec { - fn deref_mut(&mut self) -> &mut [T] { - unsafe { ::std::slice::from_raw_parts_mut(self.ptr() as *mut T, self.len) } - } -} - -// Clone -impl Clone for Vec { - fn clone(&self) -> Self { - let mut v = Self::with_capacity_in(self.len, self.allocator.clone()); - for idx in 0..self.len { - v.push_unsafe(self[idx].clone()); - } - v - } - - fn clone_from(&mut self, other: &Self) { - *self = other.clone(); - } -} - -// Hash -impl Hash for Vec { - fn hash(&self, state: &mut H) { - Hash::hash(&**self, state) - } -} - -// Index -impl, A: Allocator> Index for Vec { - type Output = I::Output; - - fn index(&self, index: I) -> &Self::Output { - Index::index(&**self, index) - } -} - -// IndexMut -impl, A: Allocator> IndexMut for Vec { - fn index_mut(&mut self, index: I) -> &mut Self::Output { - IndexMut::index_mut(&mut **self, index) - } -} - -// From the RSL: -// -// "Extend a collection with the contents of an iterator. -// Iterators produce a series of values, and collections can also be thought of -// as a series of values. The Extend trait bridges this gap, allowing you to -// extend a collection by including the contents of that iterator. When extending -// a collection with an already existing key, that entry is updated or, in the -// case of collections that permit multiple entries with equal keys, that -// entry is inserted." -// -// We cannot reserve space for the elements which are added as we dont know -// the size of the iterator. In this case, we perform sequential push operations. -// However because our underlying Vector grows exponential in size, we can be -// sure that we won't perform too many resizing operations. -impl Extend for Vec { - fn extend>(&mut self, iter: I) { - for elem in iter.into_iter() { - self.push(elem); - } - } -} - -impl<'a, T: Copy + 'a, A: Allocator + 'a> Extend<&'a T> for Vec { - fn extend>(&mut self, iter: I) { - for elem in iter.into_iter() { - self.push(*elem); - } - } -} - -impl PartialOrd for Vec { - fn partial_cmp(&self, other: &Self) -> Option { - PartialOrd::partial_cmp(&**self, &**other) - } -} - -impl Eq for Vec {} - -impl Ord for Vec { - fn cmp(&self, other: &Self) -> cmp::Ordering { - Ord::cmp(&**self, &**other) - } -} - -impl AsRef> for Vec { - fn as_ref(&self) -> &Vec { - self - } -} - -impl AsMut> for Vec { - fn as_mut(&mut self) -> &mut Vec { - self - } -} - -// AsRef to a slice is possible because we implement the Deref coercion. -impl AsRef<[T]> for Vec { - fn as_ref(&self) -> &[T] { - self - } -} - -// AsMut to a slice is possible because we implement the Deref coercion -impl AsMut<[T]> for Vec { - fn as_mut(&mut self) -> &mut [T] { - self - } -} - -// Debug -impl fmt::Debug for Vec { - // fmt implementation left empty since we dont care about debug messages - // and such in the verification case - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - Ok(()) - } -} - -// Create a new Vec from a slice reference -impl From<&[T]> for Vec { - fn from(s: &[T]) -> Vec { - let s_len = s.len(); - // Reserve space for atleast s.len() elements to avoid resizing - let mut v = Vec::with_capacity(s_len); - for i in 0..s_len { - // This push is cheap as we reserve enough space earlier. - v.push_unsafe(s[i].clone()); - } - v - } -} - -// Create a new Vec from a slice mut reference -impl From<&mut [T]> for Vec { - fn from(s: &mut [T]) -> Vec { - let s_len = s.len(); - // Reserve space for atleast s.len() elements to avoid resizing - let mut v = Vec::with_capacity(s_len); - for i in 0..s_len { - // This push is cheap as we reserve enough space earlier. - v.push_unsafe(s[i].clone()); - } - v - } -} - -// Create a new Vec from an array -impl From<[T; N]> for Vec { - fn from(s: [T; N]) -> Vec { - // Reserve space for atleast s.len() elements to avoid resizing - let mut v = Vec::with_capacity(s.len()); - for elem in s { - // This push is cheap as we reserve enough space earlier. - v.push_unsafe(elem); - } - v - } -} - -impl From<&str> for Vec { - fn from(s: &str) -> Vec { - From::from(s.as_bytes()) - } -} - -// Gets the entire contents of the `Vec` as an array, -// if its size exactly matches that of the requested array. -impl TryFrom> for [T; N] { - type Error = Vec; - - fn try_from(mut vec: Vec) -> Result<[T; N], Vec> { - if vec.len() != N { - return Err(vec); - } - - unsafe { - vec.set_len(0); - } - - let array = unsafe { read(vec.as_ptr() as *const [T; N]) }; - Ok(array) - } -} - -// We implement an IntoIterator for (Kani)Vec using a custom structure - -// KaniIter. For KaniIter, we implement KaniRawValIter as a member which stores -// raw pointers to the start and end of memory of the sequence. -struct KaniRawValIter { - start: *const T, - end: *const T, -} - -impl KaniRawValIter { - unsafe fn new(slice: &[T]) -> Self { - KaniRawValIter { - // The pointer to the slice marks its beginning - start: slice.as_ptr(), - end: if mem::size_of::() == 0 { - // Handle ZST (Zero-sized types) - ((slice.as_ptr() as usize) + slice.len()) as *const _ - } else if slice.len() == 0 { - // If the length of the slice is 0, the pointer to the slice also - // marks its end - slice.as_ptr() - } else { - // For the general case, compute offset from the start by counting - // slice.len() elements. - slice.as_ptr().offset(slice.len() as isize) - }, - } - } -} - -// An interface for dealing with iterators. -impl Iterator for KaniRawValIter { - type Item = T; - - // Yield the next element of the sequence. This method changes the internal - // state of the iterator. - fn next(&mut self) -> Option { - // If we have already reached the end, yield a None value. According to - // the documentation, individual implementations may or may not choose - // to return a Some() again at some point. In our case, we dont. - if self.start == self.end { - None - } else { - unsafe { - let result = read(self.start); - self.start = if mem::size_of::() == 0 { - // Handle ZSTs correctly - (self.start as usize + 1) as *const _ - } else { - // For the general case, offset increment the start by 1. - self.start.offset(1) - }; - Some(result) - } - } - } -} - -// An iterator able to yield elements from both ends. -// -// Something that implements DoubleEndedIterator has one extra capability over -// something that implements Iterator: the ability to also take Items from the back, -// as well as the front. -// -// once a DoubleEndedIterator returns None from a next_back(), calling it again -// may or may not ever return Some again -impl DoubleEndedIterator for KaniRawValIter { - fn next_back(&mut self) -> Option { - // If we have already consumed the iterator, return a None. According to - // the documentation, individual implementations may or may not choose - // to return a Some() again at some point. In our case, we dont. - if self.start == self.end { - None - } else { - unsafe { - self.end = if mem::size_of::() == 0 { - // Handle ZSTs - (self.end as usize - 1) as *const _ - } else { - // Offset decrement the end by 1 - self.end.offset(-1) - }; - // Read from end and wrap around a Some() - Some(read(self.end)) - } - } - } -} - -// KaniIntoIter contains a KaniRawVec and KaniRawValIter to track the Vector and -// the Iterator. This exposes a public interface which can be used with Vec. -pub struct KaniIntoIter { - _buf: KaniRawVec, - iter: KaniRawValIter, -} - -impl Iterator for KaniIntoIter { - type Item = T; - - fn next(&mut self) -> Option { - self.iter.next() - } -} - -impl DoubleEndedIterator for KaniIntoIter { - fn next_back(&mut self) -> Option { - self.iter.next_back() - } -} - -// Implement IntoIterator for Vec -// -// By implementing IntoIterator for a type, you define how it will be converted -// to an iterator. -impl IntoIterator for Vec { - type Item = T; - type IntoIter = KaniIntoIter; - - fn into_iter(self) -> KaniIntoIter { - unsafe { - let iter = KaniRawValIter::new(&self); - let buf = read(&self.buf); - // into_iter() takes self by value, and it consumes that collection. - // For that reason, we need to ensure that the destructor for the Vec - // is not called since that will free the underlying buffer. In that - // case, we need to take ownership of the data while making sure - // that the destructor is not called. mem::forget allows us to do - // that. We implement a Drop for KaniIntoIter to ensure that elements - // which were not yielded are dropped appropriately. - // - // For reference: https://doc.rust-lang.org/nomicon/vec-into-iter.html - mem::forget(self); - - KaniIntoIter { iter, _buf: buf } - } - } -} - -// FromIterator defines how a Vec will be created from an Iterator. -impl FromIterator for Vec { - fn from_iter>(iter: I) -> Vec { - let mut v = Vec::new(); - for elem in iter.into_iter() { - v.push_unsafe(elem); - } - v - } -} - -// IntoIterator defines how we can convert a Vec into a struct which implements -// Iterator. For our case, we choose std::Iter. -impl<'a, T, A: Allocator> IntoIterator for &'a Vec { - type Item = &'a T; - type IntoIter = slice::Iter<'a, T>; - - fn into_iter(self) -> slice::Iter<'a, T> { - self.iter() - } -} - -impl<'a, T, A: Allocator> IntoIterator for &'a mut Vec { - type Item = &'a mut T; - type IntoIter = slice::IterMut<'a, T>; - - fn into_iter(self) -> slice::IterMut<'a, T> { - self.iter_mut() - } -} - -// Here, we define the kani_vec! macro which behaves similar to the vec! macro -// found in the std prelude. If we try to override the vec! macro, we get error: -// -// = note: `vec` could refer to a macro from prelude -// note: `vec` could also refer to the macro defined here -// -// Relevant Zulip stream: -// https://rust-lang.zulipchat.com/#narrow/stream/122651-general/topic/Override.20prelude.20macro -// -// The workaround for now is to define a new macro. kani_vec! will initialize a new -// Vec based on its definition in this file. We support two types of initialization -// expressions: -// -// [ elem; count] - initialize a Vector with element value `elem` occurring count times. -// [ elem1, elem2, ...] - initialize a Vector with elements elem1, elem2... -#[cfg(abs_type = "kani")] -#[macro_export] -macro_rules! kani_vec { - ( $val:expr ; $count:expr ) => - ({ - // Reserve space for atleast $count elements to avoid resizing operations - let mut result = Vec::with_capacity($count); - let mut i: usize = 0; - while i < $count { - result.push($val); - i += 1; - } - result - }); - ( $( $xs:expr ),* ) => { - { - let mut result = Vec::new(); - $( - result.push($xs); - )* - result - } - }; -} diff --git a/library/kani/stubs/Rust/vec/noback_vec.rs b/library/kani/stubs/Rust/vec/noback_vec.rs deleted file mode 100644 index c7945ec591ae..000000000000 --- a/library/kani/stubs/Rust/vec/noback_vec.rs +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -use std::marker::PhantomData; -use std::mem; - -// NoBackVec implements an abstraction of the Vector library which tracks only -// the length of the vector. It does not contain a backing store which implies -// that writes only increment the length and all reads return a non-deterministic -// value. -// -// This abstraction is particularly effective for use cases where the customer -// code only cares about the length of the vector. All length queries are -// fast because the solver does not have to reason about the memory model at all. -// -// This abstraction has several limitations however. Since it does not model any -// memory, defining general methods which operate on the values of the vector is -// hard and in some cases, unsound. Please see the README.md for a more in-depth -// discussion of potential improvements to this abstraction. -// -// NOTE: It would also be difficult to soundly model a Vector where the contained -// type has a non-trivial drop method defined for it. - -// __CPROVER_max_malloc_size is dependent on the number of offset bits used to -// represent a pointer variable. By default, this is chosen to be 56, in which -// case the max_malloc_size is 2 ** (offset_bits - 1). We could go as far as to -// assign the default capacity to be the max_malloc_size but that would be overkill. -// Instead, we choose a high-enough value 2 ** (31 - 1). Another reason to do -// this is that it would be easier for the solver to reason about memory if multiple -// Vectors are initialized by the abstraction consumer. -const DEFAULT_CAPACITY: usize = 1073741824; -const MAX_MALLOC_SIZE: usize = 18014398509481984; - -// The Vec structure here models the length and the capacity. -pub struct Vec { - len: usize, - capacity: usize, - // We use a _marker variable since we want the Vector to be generic over type - // T. It is a zero-sized type which is used to mark things such that they act - // like they own a T. - _marker: PhantomData, -} - -impl Vec { - // The standard library Vec implementation calls reserve() to reserve - // space for an additional element -> self.reserve(1). However, the - // semantics of reserve() are ambiguous. reserve(num) allocates space for - // "atleast num more elements of the containing type". The operation can - // be found in function `grow_amortized()` in raw_vec.rs in the standard - // library. The logic for choosing a new value is: - // self.cap = max(self.cap * 2, self.len + additional) - // We try to implement similar semantics here. - fn grow(&mut self, additional: usize) { - let new_len = self.len + additional; - let grow_cap = self.capacity * 2; - let new_capacity = if new_len > grow_cap { new_len } else { grow_cap }; - - if new_capacity > MAX_MALLOC_SIZE { - panic!("Malloc failed to allocate enough memory"); - } - - self.capacity = new_capacity; - } -} - -impl Vec { - pub fn new() -> Vec { - // By default, we create a vector with a high default capacity. An - // important callout to make here is that it prevents us from discovering - // buffer-overflow bugs since we will (most-likely) always have enough - // space allocated additional to the required vec capacity. - // NOTE: This is however not a concern for this abstaction. - Vec { len: 0, capacity: DEFAULT_CAPACITY, _marker: Default::default() } - } - - // Even though we dont model any memory, we can soundly model the capacity - // of the allocation. - pub fn with_capacity(capacity: usize) -> Self { - Vec { len: 0, capacity: capacity, _marker: Default::default() } - } - - pub fn push(&mut self, elem: T) { - // Please refer to grow() for better understanding the semantics of reserve(). - if self.capacity == self.len { - self.reserve(1); - } - - assert!(self.capacity >= self.len); - // We only increment the length of the vector disregarding the actual - // element added to the Vector. - self.len += 1; - } - - // We check if there are any elements in the Vector. If not, we return a None - // otherwise we return a nondeterministic value since we dont track any concrete - // values in the Vector. - pub fn pop(&mut self) -> Option { - if self.len == 0 { - None - } else { - self.len -= 1; - Some(__nondet::()) - } - } - - pub fn append(&mut self, other: &mut Vec) { - let new_len = self.len + other.len; - // Please refer to grow() for better understanding the semantics of grow(). - if self.capacity < new_len { - self.reserve(other.len); - } - - assert!(self.capacity >= new_len); - // Drop all writes, increment the length of the Vector with the size - // of the Vector which is appended. - self.len = new_len; - } - - // At whichever position we insert the new element into, the overall effect on - // the abstraction is that the length increases by 1. - pub fn insert(&mut self, index: usize, elem: T) { - assert!(index <= self.len); - - self.len += 1; - } - - // We only care that the index we are removing from lies somewhere as part of - // the length of the Vector. The only effect on the abstraction is that the - // length decreases by 1. In the case that it is a valid removal, we return a - // nondeterministic value. - pub fn remove(&mut self, index: usize) -> T { - assert!(index < self.len); - - self.len -= 1; - __nondet::() - } - - pub fn extend(&mut self, iter: I) - where - I: Iterator, - { - // We first compute the length of the iterator. - let mut iter_len = 0; - for value in iter { - iter_len += 1; - } - - // Please refer to grow() for better understanding the semantics of grow(). - self.reserve(iter_len); - self.len += iter_len; - } - - pub fn len(&self) -> usize { - self.len - } - - pub fn capacity(&self) -> usize { - self.capacity - } - - // Please refer to grow() for better understanding the semantics of reserve(). - pub fn reserve(&mut self, additional: usize) { - self.grow(additional); - } -} - -// NoBackIter is a structure which implements Iterator suitable for NoBackVec. We -// only track the index values to the start and end of the iterator. -pub struct NoBackIter { - start: usize, - end: usize, - // Please refer to the NoBackvec definition to understand why PhantomData is used - // here. - _marker: PhantomData, -} - -impl NoBackIter { - pub fn new(len: usize) -> Self { - // By default, initialize the start to index 0 and end to the last index - // of the Vector. - NoBackIter { start: 0, end: len, _marker: Default::default() } - } -} - -impl Iterator for NoBackIter { - type Item = T; - - // Unless we are at the end of the array, return a nondeterministic value - // wrapped around a Some. - fn next(&mut self) -> Option { - if self.start == self.end { None } else { Some(__nondet::()) } - } - - fn size_hint(&self) -> (usize, Option) { - let len = self.end - self.start; - (len, Some(len)) - } -} - -impl IntoIterator for Vec { - type Item = T; - type IntoIter = NoBackIter; - - fn into_iter(self) -> NoBackIter { - NoBackIter::new(self.len()) - } -} - -// Here, we define the kani_vec! macro which behaves similar to the vec! macro -// found in the std prelude. If we try to override the vec! macro, we get error: -// -// = note: `vec` could refer to a macro from prelude -// note: `vec` could also refer to the macro defined here -// -// Relevant Zulip stream: -// https://rust-lang.zulipchat.com/#narrow/stream/122651-general/topic/Override.20prelude.20macro -// -// The workaround for now is to define a new macro. kani_vec! will initialize a new -// Vec based on its definition in this file. We support two types of initialization -// expressions: -// -// [ elem; count] - initialize a Vector with element value `elem` occurring count times. -// [ elem1, elem2, ...] - initialize a Vector with elements elem1, elem2... -#[cfg(abs_type = "no-back")] -#[macro_export] -macro_rules! kani_vec { - ( $val:expr ; $count:expr ) => - ({ - let mut result = Vec::new(); - let mut i: usize = 0; - while i < $count { - result.push($val); - i += 1; - } - result - }); - ( $( $xs:expr ),* ) => { - { - let mut result = Vec::new(); - $( - result.push($xs); - )* - result - } - }; -} diff --git a/library/kani/stubs/Rust/vec/utils.rs b/library/kani/stubs/Rust/vec/utils.rs deleted file mode 100644 index a014d99e2db7..000000000000 --- a/library/kani/stubs/Rust/vec/utils.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -// This file should contain imports and methods which can be used across the -// different abstractions. - -// We use methods from libc as they are directly translated into CBMC primitives. -// In which case, if CBMC does better by implementing any optimizations on these -// operations, Kani would do better too. -pub extern crate libc; - -// Currently, the way we handle non-determinism is to implement a __nondet::::() -// function which is stubbed to be `unimplemented!()`. However, at a later time -// it could be possible to implement a Nondet trait per type. This would with -// enum types such as Option where we could decide whether we want to return -// a None or a Some(Nondet). That method would likely end up in this file so -// that it can be used throughout. diff --git a/library/kani_macros/Cargo.toml b/library/kani_macros/Cargo.toml index 78b8fbb529da..dc36a6d16284 100644 --- a/library/kani_macros/Cargo.toml +++ b/library/kani_macros/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_macros" -version = "0.32.0" +version = "0.42.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false @@ -15,4 +15,4 @@ proc-macro = true proc-macro2 = "1.0" proc-macro-error = "1.0.4" quote = "1.0.20" -syn = { version = "2.0.18", features = ["full"] } +syn = { version = "2.0.18", features = ["full", "visit-mut", "visit"] } diff --git a/library/kani_macros/src/lib.rs b/library/kani_macros/src/lib.rs index 6cded7052369..89482a6266ca 100644 --- a/library/kani_macros/src/lib.rs +++ b/library/kani_macros/src/lib.rs @@ -7,6 +7,7 @@ // downstream crates to enable these features as well. // So we have to enable this on the commandline (see kani-rustc) with: // RUSTFLAGS="-Zcrate-attr=feature(register_tool) -Zcrate-attr=register_tool(kanitool)" +#![feature(proc_macro_diagnostic)] mod derive; @@ -22,7 +23,13 @@ use regular as attr_impl; /// Marks a Kani proof harness /// -/// For async harnesses, this will call [`kani::block_on`] (see its documentation for more information). +/// For async harnesses, this will call [`block_on`](https://model-checking.github.io/kani/crates/doc/kani/futures/fn.block_on.html) to drive the future to completion (see its documentation for more information). +/// +/// If you want to spawn tasks in an async harness, you have to pass a schedule to the `#[kani::proof]` attribute, +/// e.g. `#[kani::proof(schedule = kani::RoundRobin::default())]`. +/// +/// This will wrap the async function in a call to [`block_on_with_spawn`](https://model-checking.github.io/kani/crates/doc/kani/futures/fn.block_on_with_spawn.html) (see its documentation for more information). +#[proc_macro_error] #[proc_macro_attribute] pub fn proof(attr: TokenStream, item: TokenStream) -> TokenStream { attr_impl::proof(attr, item) @@ -46,7 +53,7 @@ pub fn should_panic(attr: TokenStream, item: TokenStream) -> TokenStream { } /// Set Loop unwind limit for proof harnesses -/// The attribute '#[kani::unwind(arg)]' can only be called alongside '#[kani::proof]'. +/// The attribute `#[kani::unwind(arg)]` can only be called alongside `#[kani::proof]`. /// arg - Takes in a integer value (u32) that represents the unwind value for the harness. #[proc_macro_attribute] pub fn unwind(attr: TokenStream, item: TokenStream) -> TokenStream { @@ -66,7 +73,8 @@ pub fn stub(attr: TokenStream, item: TokenStream) -> TokenStream { } /// Select the SAT solver to use with CBMC for this harness -/// The attribute `#[kani::solver(arg)]` can only be used alongside `#[kani::proof]`` +/// +/// The attribute `#[kani::solver(arg)]` can only be used alongside `#[kani::proof]`. /// /// arg - name of solver, e.g. kissat #[proc_macro_attribute] @@ -89,14 +97,91 @@ pub fn derive_arbitrary(item: TokenStream) -> TokenStream { derive::expand_derive_arbitrary(item) } +/// Add a precondition to this function. +/// +/// This is part of the function contract API, for more general information see +/// the [module-level documentation](../kani/contracts/index.html). +/// +/// The contents of the attribute is a condition over the input values to the +/// annotated function. All Rust syntax is supported, even calling other +/// functions, but the computations must be side effect free, e.g. it cannot +/// perform I/O or use mutable memory. +/// +/// Kani requires each function that uses a contract (this attribute or +/// [`ensures`][macro@ensures]) to have at least one designated +/// [`proof_for_contract`][macro@proof_for_contract] harness for checking the +/// contract. +#[proc_macro_attribute] +pub fn requires(attr: TokenStream, item: TokenStream) -> TokenStream { + attr_impl::requires(attr, item) +} + +/// Add a postcondition to this function. +/// +/// This is part of the function contract API, for more general information see +/// the [module-level documentation](../kani/contracts/index.html). +/// +/// The contents of the attribute is a condition over the input values to the +/// annotated function *and* its return value, accessible as a variable called +/// `result`. All Rust syntax is supported, even calling other functions, but +/// the computations must be side effect free, e.g. it cannot perform I/O or use +/// mutable memory. +/// +/// Kani requires each function that uses a contract (this attribute or +/// [`requires`][macro@requires]) to have at least one designated +/// [`proof_for_contract`][macro@proof_for_contract] harness for checking the +/// contract. +#[proc_macro_attribute] +pub fn ensures(attr: TokenStream, item: TokenStream) -> TokenStream { + attr_impl::ensures(attr, item) +} + +/// Designates this function as a harness to check a function contract. +/// +/// The argument to this macro is the relative path (e.g. `foo` or +/// `super::some_mod::foo` or `crate::SomeStruct::foo`) to the function, the +/// contract of which should be checked. +/// +/// This is part of the function contract API, for more general information see +/// the [module-level documentation](../kani/contracts/index.html). +#[proc_macro_attribute] +pub fn proof_for_contract(attr: TokenStream, item: TokenStream) -> TokenStream { + attr_impl::proof_for_contract(attr, item) +} + +/// `stub_verified(TARGET)` is a harness attribute (to be used on +/// [`proof`][macro@proof] or [`proof_for_contract`][macro@proof_for_contract] +/// function) that replaces all occurrences of `TARGET` reachable from this +/// harness with a stub generated from the contract on `TARGET`. +/// +/// The target of `stub_verified` *must* have a contract. More information about +/// how to specify a contract for your function can be found +/// [here](../contracts/index.html#specification-attributes-overview). +/// +/// You may use multiple `stub_verified` attributes on a single harness. +/// +/// This is part of the function contract API, for more general information see +/// the [module-level documentation](../kani/contracts/index.html). +#[proc_macro_attribute] +pub fn stub_verified(attr: TokenStream, item: TokenStream) -> TokenStream { + attr_impl::stub_verified(attr, item) +} + /// This module implements Kani attributes in a way that only Kani's compiler can understand. /// This code should only be activated when pre-building Kani's sysroot. #[cfg(kani_sysroot)] mod sysroot { + use proc_macro_error::{abort, abort_call_site}; + + mod contracts; + + pub use contracts::{ensures, proof_for_contract, requires, stub_verified}; + use super::*; use { quote::{format_ident, quote}, + syn::parse::{Parse, ParseStream}, syn::{parse_macro_input, ItemFn}, }; @@ -126,7 +211,31 @@ mod sysroot { }; } + struct ProofOptions { + schedule: Option, + } + + impl Parse for ProofOptions { + fn parse(input: ParseStream) -> syn::Result { + if input.is_empty() { + Ok(ProofOptions { schedule: None }) + } else { + let ident = input.parse::()?; + if ident != "schedule" { + abort_call_site!("`{}` is not a valid option for `#[kani::proof]`.", ident; + help = "did you mean `schedule`?"; + note = "for now, `schedule` is the only option for `#[kani::proof]`."; + ); + } + let _ = input.parse::()?; + let schedule = Some(input.parse::()?); + Ok(ProofOptions { schedule }) + } + } + } + pub fn proof(attr: TokenStream, item: TokenStream) -> TokenStream { + let proof_options = parse_macro_input!(attr as ProofOptions); let fn_item = parse_macro_input!(item as ItemFn); let attrs = fn_item.attrs; let vis = fn_item.vis; @@ -138,9 +247,13 @@ mod sysroot { #[kanitool::proof] ); - assert!(attr.is_empty(), "#[kani::proof] does not take any arguments currently"); - if sig.asyncness.is_none() { + if proof_options.schedule.is_some() { + abort_call_site!( + "`#[kani::proof(schedule = ...)]` can only be used with `async` functions."; + help = "did you mean to make this function `async`?"; + ); + } // Adds `#[kanitool::proof]` and other attributes quote!( #kani_attributes @@ -152,32 +265,44 @@ mod sysroot { // For async functions, it translates to a synchronous function that calls `kani::block_on`. // Specifically, it translates // ```ignore - // #[kani::async_proof] + // #[kani::proof] // #[attribute] // pub async fn harness() { ... } // ``` // to // ```ignore - // #[kani::proof] + // #[kanitool::proof] // #[attribute] // pub fn harness() { // async fn harness() { ... } // kani::block_on(harness()) + // // OR + // kani::spawnable_block_on(harness(), schedule) + // // where `schedule` was provided as an argument to `#[kani::proof]`. // } // ``` - assert!( - sig.inputs.is_empty(), - "#[kani::proof] cannot be applied to async functions that take inputs for now" - ); + if !sig.inputs.is_empty() { + abort!( + sig.inputs, + "`#[kani::proof]` cannot be applied to async functions that take arguments for now"; + help = "try removing the arguments"; + ); + } let mut modified_sig = sig.clone(); modified_sig.asyncness = None; let fn_name = &sig.ident; + let schedule = proof_options.schedule; + let block_on_call = if let Some(schedule) = schedule { + quote!(kani::block_on_with_spawn(#fn_name(), #schedule)) + } else { + quote!(kani::block_on(#fn_name())) + }; quote!( #kani_attributes #(#attrs)* #vis #modified_sig { #sig #body - kani::block_on(#fn_name()) + #block_on_call } ) .into() @@ -221,4 +346,8 @@ mod regular { no_op!(stub); no_op!(unstable); no_op!(unwind); + no_op!(requires); + no_op!(ensures); + no_op!(proof_for_contract); + no_op!(stub_verified); } diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs new file mode 100644 index 000000000000..a497e0ca8cee --- /dev/null +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -0,0 +1,1078 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Implementation of the function contracts code generation. +//! +//! The most exciting part is the handling of `requires` and `ensures`, the main +//! entry point to which is [`requires_ensures_main`]. Most of the code +//! generation for that is implemented on [`ContractConditionsHandler`] with +//! [`ContractFunctionState`] steering the code generation. The function state +//! implements a state machine in order to be able to handle multiple attributes +//! on the same function correctly. +//! +//! ## How the handling for `requires` and `ensures` works. +//! +//! Our aim is to generate a "check" function that can be used to verify the +//! validity of the contract and a "replace" function that can be used as a +//! stub, generated from the contract that can be used instead of the original +//! function. +//! +//! Let me first introduce the constraints which we are operating under to +//! explain why we need the somewhat involved state machine to achieve this. +//! +//! Proc-macros are expanded one-at-a-time, outside-in and they can also be +//! renamed. Meaning the user can do `use kani::requires as precondition` and +//! then use `precondition` everywhere. We want to support this functionality +//! instead of throwing a hard error but this means we cannot detect if a given +//! function has further contract attributes placed on it during any given +//! expansion. As a result every expansion needs to leave the code in a valid +//! state that could be used for all contract functionality but it must alow +//! further contract attributes to compose with what was already generated. In +//! addition we also want to make sure to support non-contract attributes on +//! functions with contracts. +//! +//! To this end we use a state machine. The initial state is an "untouched" +//! function with possibly multiple contract attributes, none of which have been +//! expanded. When we expand the first (outermost) `requires` or `ensures` +//! attribute on such a function we re-emit the function unchanged but we also +//! generate fresh "check" and "replace" functions that enforce the condition +//! carried by the attribute currently being expanded. We copy all additional +//! attributes from the original function to both the "check" and the "replace". +//! This allows us to deal both with renaming and also support non-contract +//! attributes. +//! +//! In addition to copying attributes we also add new marker attributes to +//! advance the state machine. The "check" function gets a +//! `kanitool::is_contract_generated(check)` attributes and analogous for +//! replace. The re-emitted original meanwhile is decorated with +//! `kanitool::checked_with(name_of_generated_check_function)` and an analogous +//! `kanittool::replaced_with` attribute also. The next contract attribute that +//! is expanded will detect the presence of these markers in the attributes of +//! the item and be able to determine their position in the state machine this +//! way. If the state is either a "check" or "replace" then the body of the +//! function is augmented with the additional conditions carried by the macro. +//! If the state is the "original" function, no changes are performed. +//! +//! We place marker attributes at the bottom of the attribute stack (innermost), +//! otherwise they would not be visible to the future macro expansions. +//! +//! Below you can see a graphical rendering where boxes are states and each +//! arrow represents the expansion of a `requires` or `ensures` macro. +//! +//! ```plain +//! │ Start +//! ▼ +//! ┌───────────┐ +//! │ Untouched │ +//! │ Function │ +//! └─────┬─────┘ +//! │ +//! Emit │ Generate + Copy Attributes +//! ┌─────────────────┴─────┬──────────┬─────────────────┐ +//! │ │ │ │ +//! │ │ │ │ +//! ▼ ▼ ▼ ▼ +//! ┌──────────┐ ┌───────────┐ ┌───────┐ ┌─────────┐ +//! │ Original │◄─┐ │ Recursion │ │ Check │◄─┐ │ Replace │◄─┐ +//! └──┬───────┘ │ │ Wrapper │ └───┬───┘ │ └────┬────┘ │ +//! │ │ Ignore └───────────┘ │ │ Augment │ │ Augment +//! └──────────┘ └──────┘ └───────┘ +//! +//! │ │ │ │ +//! └───────────────┘ └─────────────────────────────────────────────┘ +//! +//! Presence of Presence of +//! "checked_with" "is_contract_generated" +//! +//! State is detected via +//! ``` +//! +//! All named arguments of the annotated function are unsafely shallow-copied +//! with the `kani::untracked_deref` function to circumvent the borrow checker +//! for postconditions. The case where this is relevant is if you want to return +//! a mutable borrow from the function which means any immutable borrow in the +//! postcondition would be illegal. We must ensure that those copies are not +//! dropped (causing a double-free) so after the postconditions we call +//! `mem::forget` on each copy. +//! +//! ## Check function +//! +//! Generates a `_check_` function that assumes preconditions +//! and asserts postconditions. The check function is also marked as generated +//! with the `#[kanitool::is_contract_generated(check)]` attribute. +//! +//! Decorates the original function with `#[kanitool::checked_by = +//! "_check_"]`. +//! +//! The check function is a copy of the original function with preconditions +//! added before the body and postconditions after as well as injected before +//! every `return` (see [`PostconditionInjector`]). Attributes on the original +//! function are also copied to the check function. +//! +//! ## Replace Function +//! +//! As the mirror to that also generates a `_replace_` +//! function that asserts preconditions and assumes postconditions. The replace +//! function is also marked as generated with the +//! `#[kanitool::is_contract_generated(replace)]` attribute. +//! +//! Decorates the original function with `#[kanitool::replaced_by = +//! "_replace_"]`. +//! +//! The replace function has the same signature as the original function but its +//! body is replaced by `kani::any()`, which generates a non-deterministic +//! value. +//! +//! ## Inductive Verification +//! +//! To efficiently check recursive functions we verify them inductively. To +//! be able to do this we need both the check and replace functions we have seen +//! before. +//! +//! Inductive verification is comprised of a hypothesis and an induction step. +//! The hypothesis in this case is the replace function. It represents the +//! assumption that the contracts holds if the preconditions are satisfied. The +//! induction step is the check function, which ensures that the contract holds, +//! assuming the preconditions hold. +//! +//! Since the induction revolves around the recursive call we can simply set it +//! up upon entry into the body of the function under verification. We use a +//! global variable that tracks whether we are re-entering the function +//! recursively and starts off as `false`. On entry to the function we flip the +//! variable to `true` and dispatch to the check (induction step). If the check +//! recursively calls our function our re-entry tracker now reads `true` and we +//! dispatch to the replacement (application of induction hypothesis). Because +//! the replacement function only checks the conditions and does not perform +//! other computation we will only ever go "one recursion level deep", making +//! inductive verification very efficient. Once the check function returns we +//! flip the tracker variable back to `false` in case the function is called +//! more than once in its harness. +//! +//! To facilitate all this we generate a `_recursion_wrapper_` +//! function with the following shape: +//! +//! ```ignored +//! fn recursion_wrapper_...(fn args ...) { +//! static mut REENTRY: bool = false; +//! +//! if unsafe { REENTRY } { +//! call_replace(fn args...) +//! } else { +//! unsafe { reentry = true }; +//! let result = call_check(fn args...); +//! unsafe { reentry = false }; +//! result +//! } +//! } +//! ``` +//! +//! We register this function as `#[kanitool::checked_with = +//! "recursion_wrapper_..."]` instead of the check function. +//! +//! # Complete example +//! +//! ``` +//! #[kani::requires(divisor != 0)] +//! #[kani::ensures(result <= dividend)] +//! fn div(dividend: u32, divisor: u32) -> u32 { +//! dividend / divisor +//! } +//! ``` +//! +//! Turns into +//! +//! ``` +//! #[kanitool::checked_with = "div_recursion_wrapper_965916"] +//! #[kanitool::replaced_with = "div_replace_965916"] +//! fn div(dividend: u32, divisor: u32) -> u32 { dividend / divisor } +//! +//! #[allow(dead_code)] +//! #[allow(unused_variables)] +//! #[kanitool::is_contract_generated(check)] +//! fn div_check_965916(dividend: u32, divisor: u32) -> u32 { +//! let dividend_renamed = kani::untracked_deref(÷nd); +//! let divisor_renamed = kani::untracked_deref(&divisor); +//! let result = { kani::assume(divisor != 0); { dividend / divisor } }; +//! kani::assert(result <= dividend_renamed, "result <= dividend"); +//! std::mem::forget(dividend_renamed); +//! std::mem::forget(divisor_renamed); +//! result +//! } +//! +//! #[allow(dead_code)] +//! #[allow(unused_variables)] +//! #[kanitool::is_contract_generated(replace)] +//! fn div_replace_965916(dividend: u32, divisor: u32) -> u32 { +//! kani::assert(divisor != 0, "divisor != 0"); +//! let dividend_renamed = kani::untracked_deref(÷nd); +//! let divisor_renamed = kani::untracked_deref(&divisor); +//! let result = kani::any(); +//! kani::assume(result <= dividend_renamed, "result <= dividend"); +//! std::mem::forget(dividend_renamed); +//! std::mem::forget(divisor_renamed); +//! result +//! } +//! +//! #[allow(dead_code)] +//! #[allow(unused_variables)] +//! #[kanitool::is_contract_generated(recursion_wrapper)] +//! fn div_recursion_wrapper_965916(dividend: u32, divisor: u32) -> u32 { +//! static mut REENTRY: bool = false; +//! +//! if unsafe { REENTRY } { +//! div_replace_965916(dividend, divisor) +//! } else { +//! unsafe { reentry = true }; +//! let result = div_check_965916(dividend, divisor); +//! unsafe { reentry = false }; +//! result +//! } +//! } +//! ``` + +use proc_macro::{Diagnostic, TokenStream}; +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; +use quote::{quote, ToTokens}; +use std::{ + borrow::Cow, + collections::{HashMap, HashSet}, +}; +use syn::{ + parse_macro_input, spanned::Spanned, visit::Visit, visit_mut::VisitMut, Attribute, Expr, + ItemFn, PredicateType, ReturnType, Signature, TraitBound, TypeParamBound, WhereClause, +}; + +/// Create a unique hash for a token stream (basically a [`std::hash::Hash`] +/// impl for `proc_macro2::TokenStream`). +fn hash_of_token_stream(hasher: &mut H, stream: proc_macro2::TokenStream) { + use proc_macro2::TokenTree; + use std::hash::Hash; + for token in stream { + match token { + TokenTree::Ident(i) => i.hash(hasher), + TokenTree::Punct(p) => p.as_char().hash(hasher), + TokenTree::Group(g) => { + std::mem::discriminant(&g.delimiter()).hash(hasher); + hash_of_token_stream(hasher, g.stream()); + } + TokenTree::Literal(lit) => lit.to_string().hash(hasher), + } + } +} + +/// Hash this `TokenStream` and return an integer that is at most digits +/// long when hex formatted. +fn short_hash_of_token_stream(stream: &proc_macro::TokenStream) -> u64 { + use std::hash::Hasher; + let mut hasher = std::collections::hash_map::DefaultHasher::default(); + hash_of_token_stream(&mut hasher, proc_macro2::TokenStream::from(stream.clone())); + let long_hash = hasher.finish(); + long_hash % 0x1_000_000 // six hex digits +} + +/// Makes consistent names for a generated function which was created for +/// `purpose`, from an attribute that decorates `related_function` with the +/// hash `hash`. +fn identifier_for_generated_function(related_function: &ItemFn, purpose: &str, hash: u64) -> Ident { + let identifier = format!("{}_{purpose}_{hash:x}", related_function.sig.ident); + Ident::new(&identifier, proc_macro2::Span::mixed_site()) +} + +pub fn requires(attr: TokenStream, item: TokenStream) -> TokenStream { + requires_ensures_main(attr, item, true) +} + +pub fn ensures(attr: TokenStream, item: TokenStream) -> TokenStream { + requires_ensures_main(attr, item, false) +} + +/// Collect all named identifiers used in the argument patterns of a function. +struct ArgumentIdentCollector(HashSet); + +impl ArgumentIdentCollector { + fn new() -> Self { + Self(HashSet::new()) + } +} + +impl<'ast> Visit<'ast> for ArgumentIdentCollector { + fn visit_pat_ident(&mut self, i: &'ast syn::PatIdent) { + self.0.insert(i.ident.clone()); + syn::visit::visit_pat_ident(self, i) + } + fn visit_receiver(&mut self, _: &'ast syn::Receiver) { + self.0.insert(Ident::new("self", proc_macro2::Span::call_site())); + } +} + +/// Applies the contained renaming (key renamed to value) to every ident pattern +/// and ident expr visited. +struct Renamer<'a>(&'a HashMap); + +impl<'a> VisitMut for Renamer<'a> { + fn visit_expr_path_mut(&mut self, i: &mut syn::ExprPath) { + if i.path.segments.len() == 1 { + i.path + .segments + .first_mut() + .and_then(|p| self.0.get(&p.ident).map(|new| p.ident = new.clone())); + } + } + + /// This restores shadowing. Without this we would rename all ident + /// occurrences, but not rebinding location. This is because our + /// [`Self::visit_expr_path_mut`] is scope-unaware. + fn visit_pat_ident_mut(&mut self, i: &mut syn::PatIdent) { + if let Some(new) = self.0.get(&i.ident) { + i.ident = new.clone(); + } + } +} + +/// Does the provided path have the same chain of identifiers as `mtch` (match) +/// and no arguments anywhere? +/// +/// So for instance (using some pseudo-syntax for the [`syn::Path`]s) +/// `matches_path(std::vec::Vec, &["std", "vec", "Vec"]) == true` but +/// `matches_path(std::Vec::::contains, &["std", "Vec", "contains"]) != +/// true`. +/// +/// This is intended to be used to match the internal `kanitool` family of +/// attributes which we know to have a regular structure and no arguments. +fn matches_path(path: &syn::Path, mtch: &[E]) -> bool +where + Ident: std::cmp::PartialEq, +{ + path.segments.len() == mtch.len() + && path.segments.iter().all(|s| s.arguments.is_empty()) + && path.leading_colon.is_none() + && path.segments.iter().zip(mtch).all(|(actual, expected)| actual.ident == *expected) +} + +/// Classifies the state a function is in in the contract handling pipeline. +#[derive(Clone, Copy, PartialEq, Eq)] +enum ContractFunctionState { + /// This is the original code, re-emitted from a contract attribute. + Original, + /// This is the first time a contract attribute is evaluated on this + /// function. + Untouched, + /// This is a check function that was generated from a previous evaluation + /// of a contract attribute. + Check, + /// This is a replace function that was generated from a previous evaluation + /// of a contract attribute. + Replace, +} + +impl<'a> TryFrom<&'a syn::Attribute> for ContractFunctionState { + type Error = Option; + + /// Find out if this attribute could be describing a "contract handling" + /// state and if so return it. + fn try_from(attribute: &'a syn::Attribute) -> Result { + if let syn::Meta::List(lst) = &attribute.meta { + if matches_path(&lst.path, &["kanitool", "is_contract_generated"]) { + let ident = syn::parse2::(lst.tokens.clone()) + .map_err(|e| Some(lst.span().unwrap().error(format!("{e}"))))?; + let ident_str = ident.to_string(); + return match ident_str.as_str() { + "check" => Ok(Self::Check), + "replace" => Ok(Self::Replace), + _ => { + Err(Some(lst.span().unwrap().error("Expected `check` or `replace` ident"))) + } + }; + } + } + if let syn::Meta::NameValue(nv) = &attribute.meta { + if matches_path(&nv.path, &["kanitool", "checked_with"]) { + return Ok(ContractFunctionState::Original); + } + } + Err(None) + } +} + +impl ContractFunctionState { + // If we didn't find any other contract handling related attributes we + // assume this function has not been touched by a contract before. + fn from_attributes(attributes: &[syn::Attribute]) -> Self { + attributes + .iter() + .find_map(|attr| { + let state = ContractFunctionState::try_from(attr); + if let Err(Some(diag)) = state { + diag.emit(); + None + } else { + state.ok() + } + }) + .unwrap_or(ContractFunctionState::Untouched) + } + + /// Do we need to emit the `is_contract_generated` tag attribute on the + /// generated function(s)? + fn emit_tag_attr(self) -> bool { + matches!(self, ContractFunctionState::Untouched) + } +} + +/// A visitor which injects a copy of the token stream it holds before every +/// `return` expression. +/// +/// This is intended to be used with postconditions and for that purpose it also +/// performs a rewrite where the return value is first bound to `result` so the +/// postconditions can access it. +/// +/// # Example +/// +/// The expression `return x;` turns into +/// +/// ```rs +/// { // Always opens a new block +/// let result = x; +/// +/// return result; +/// } +/// ``` +struct PostconditionInjector(TokenStream2); + +impl VisitMut for PostconditionInjector { + /// We leave this empty to stop the recursion here. We don't want to look + /// inside the closure, because the return statements contained within are + /// for a different function. + fn visit_expr_closure_mut(&mut self, _: &mut syn::ExprClosure) {} + + fn visit_expr_mut(&mut self, i: &mut Expr) { + if let syn::Expr::Return(r) = i { + let tokens = self.0.clone(); + let mut output = TokenStream2::new(); + if let Some(expr) = &mut r.expr { + // In theory the return expression can contain itself a `return` + // so we need to recurse here. + self.visit_expr_mut(expr); + output.extend(quote!(let result = #expr;)); + *expr = Box::new(Expr::Verbatim(quote!(result))); + } + *i = syn::Expr::Verbatim(quote!({ + #output + #tokens + #i + })) + } else { + syn::visit_mut::visit_expr_mut(self, i) + } + } +} + +/// A supporting function for creating shallow, unsafe copies of the arguments +/// for the postconditions. +/// +/// This function: +/// - Collects all [`Ident`]s found in the argument patterns; +/// - Creates new names for them; +/// - Replaces all occurrences of those idents in `attrs` with the new names and; +/// - Returns the mapping of old names to new names. +fn rename_argument_occurrences(sig: &syn::Signature, attr: &mut Expr) -> HashMap { + let mut arg_ident_collector = ArgumentIdentCollector::new(); + arg_ident_collector.visit_signature(&sig); + + let mk_new_ident_for = |id: &Ident| Ident::new(&format!("{}_renamed", id), Span::mixed_site()); + let arg_idents = arg_ident_collector + .0 + .into_iter() + .map(|i| { + let new = mk_new_ident_for(&i); + (i, new) + }) + .collect::>(); + + let mut ident_rewriter = Renamer(&arg_idents); + ident_rewriter.visit_expr_mut(attr); + arg_idents +} + +/// The information needed to generate the bodies of check and replacement +/// functions that integrate the conditions from this contract attribute. +struct ContractConditionsHandler<'a> { + function_state: ContractFunctionState, + /// Information specific to the type of contract attribute we're expanding. + condition_type: ContractConditionsType, + /// The contents of the attribute. + attr: Expr, + /// Body of the function this attribute was found on. + annotated_fn: &'a ItemFn, + /// An unparsed, unmodified copy of `attr`, used in the error messages. + attr_copy: TokenStream2, + /// The stream to which we should write the generated code. + output: &'a mut TokenStream2, +} + +/// Information needed for generating check and replace handlers for different +/// contract attributes. +enum ContractConditionsType { + Requires, + Ensures { + /// Translation map from original argument names to names of the copies + /// we will be emitting. + argument_names: HashMap, + }, +} + +impl ContractConditionsType { + /// Constructs a [`Self::Ensures`] from the signature of the decorated + /// function and the contents of the decorating attribute. + /// + /// Renames the [`Ident`]s used in `attr` and stores the translation map in + /// `argument_names`. + fn new_ensures(sig: &Signature, attr: &mut Expr) -> Self { + let argument_names = rename_argument_occurrences(sig, attr); + ContractConditionsType::Ensures { argument_names } + } +} + +impl<'a> ContractConditionsHandler<'a> { + /// Initialize the handler. Constructs the required + /// [`ContractConditionsType`] depending on `is_requires`. + fn new( + function_state: ContractFunctionState, + is_requires: bool, + mut attr: Expr, + annotated_fn: &'a ItemFn, + attr_copy: TokenStream2, + output: &'a mut TokenStream2, + ) -> Self { + let condition_type = if is_requires { + ContractConditionsType::Requires + } else { + ContractConditionsType::new_ensures(&annotated_fn.sig, &mut attr) + }; + + Self { function_state, condition_type, attr, annotated_fn, attr_copy, output } + } + + /// Create the body of a check function. + /// + /// Wraps the conditions from this attribute around `self.body`. + fn make_check_body(&self) -> TokenStream2 { + let Self { attr, attr_copy, .. } = self; + let ItemFn { sig, block, .. } = self.annotated_fn; + let return_type = return_type_to_type(&sig.output); + + match &self.condition_type { + ContractConditionsType::Requires => quote!( + kani::assume(#attr); + #block + ), + ContractConditionsType::Ensures { argument_names } => { + let (arg_copies, copy_clean) = make_unsafe_argument_copies(&argument_names); + + // The code that enforces the postconditions and cleans up the shallow + // argument copies (with `mem::forget`). + let exec_postconditions = quote!( + kani::assert(#attr, stringify!(#attr_copy)); + #copy_clean + ); + + // We make a copy here because we'll modify it. Technically not + // necessary but could lead to weird results if + // `make_replace_body` were called after this if we modified in + // place. + let mut call = block.clone(); + let mut inject_conditions = PostconditionInjector(exec_postconditions.clone()); + inject_conditions.visit_block_mut(&mut call); + + quote!( + #arg_copies + let result : #return_type = #call; + #exec_postconditions + result + ) + } + } + } + + /// Create the body of a stub for this contract. + /// + /// Wraps the conditions from this attribute around a prior call. If + /// `use_nondet_result` is `true` we will use `kani::any()` to create a + /// result, otherwise whatever the `body` of our annotated function was. + /// + /// `use_nondet_result` will only be true if this is the first time we are + /// generating a replace function. + fn make_replace_body(&self, use_nondet_result: bool) -> TokenStream2 { + let Self { attr, attr_copy, .. } = self; + let ItemFn { sig, block, .. } = self.annotated_fn; + let call_to_prior = + if use_nondet_result { quote!(kani::any()) } else { block.to_token_stream() }; + let return_type = return_type_to_type(&sig.output); + + match &self.condition_type { + ContractConditionsType::Requires => quote!( + kani::assert(#attr, stringify!(#attr_copy)); + #call_to_prior + ), + ContractConditionsType::Ensures { argument_names } => { + let (arg_copies, copy_clean) = make_unsafe_argument_copies(&argument_names); + quote!( + #arg_copies + let result: #return_type = #call_to_prior; + kani::assume(#attr); + #copy_clean + result + ) + } + } + } + + /// Emit the check function into the output stream. + /// + /// See [`Self::make_check_body`] for the most interesting parts of this + /// function. + fn emit_check_function(&mut self, check_function_ident: Ident) { + self.emit_common_header(); + + if self.function_state.emit_tag_attr() { + // If it's the first time we also emit this marker. Again, order is + // important so this happens as the last emitted attribute. + self.output.extend(quote!(#[kanitool::is_contract_generated(check)])); + } + let body = self.make_check_body(); + let mut sig = self.annotated_fn.sig.clone(); + sig.ident = check_function_ident; + self.output.extend(quote!( + #sig { + #body + } + )) + } + + /// Emit the replace funtion into the output stream. + /// + /// See [`Self::make_replace_body`] for the most interesting parts of this + /// function. + fn emit_replace_function(&mut self, replace_function_ident: Ident, is_first_emit: bool) { + self.emit_common_header(); + + if self.function_state.emit_tag_attr() { + // If it's the first time we also emit this marker. Again, order is + // important so this happens as the last emitted attribute. + self.output.extend(quote!(#[kanitool::is_contract_generated(replace)])); + } + let mut sig = self.annotated_fn.sig.clone(); + if is_first_emit { + attach_require_kani_any(&mut sig); + } + let body = self.make_replace_body(is_first_emit); + sig.ident = replace_function_ident; + + // Finally emit the check function itself. + self.output.extend(quote!( + #sig { + #body + } + )); + } + + /// Emit attributes common to check or replace function into the output + /// stream. + fn emit_common_header(&mut self) { + if self.function_state.emit_tag_attr() { + self.output.extend(quote!( + #[allow(dead_code, unused_variables)] + )); + } + self.output.extend(self.annotated_fn.attrs.iter().flat_map(Attribute::to_token_stream)); + } +} + +/// If an explicit return type was provided it is returned, otherwise `()`. +fn return_type_to_type(return_type: &syn::ReturnType) -> Cow { + match return_type { + syn::ReturnType::Default => Cow::Owned(syn::Type::Tuple(syn::TypeTuple { + paren_token: syn::token::Paren::default(), + elems: Default::default(), + })), + syn::ReturnType::Type(_, typ) => Cow::Borrowed(typ.as_ref()), + } +} + +/// Looks complicated but does something very simple: attach a bound for +/// `kani::Arbitrary` on the return type to the provided signature. Pushes it +/// onto a preexisting where condition, initializing a new `where` condition if +/// it doesn't already exist. +/// +/// Very simple example: `fn foo() -> usize { .. }` would be rewritten `fn foo() +/// -> usize where usize: kani::Arbitrary { .. }`. +/// +/// This is called when we first emit a replace function. Later we can rely on +/// this bound already being present. +fn attach_require_kani_any(sig: &mut Signature) { + if matches!(sig.output, ReturnType::Default) { + // It's the default return type, e.g. `()` so we can skip adding the + // constraint. + return; + } + let return_ty = return_type_to_type(&sig.output); + let where_clause = sig.generics.where_clause.get_or_insert_with(|| WhereClause { + where_token: syn::Token![where](Span::call_site()), + predicates: Default::default(), + }); + + where_clause.predicates.push(syn::WherePredicate::Type(PredicateType { + lifetimes: None, + bounded_ty: return_ty.into_owned(), + colon_token: syn::Token![:](Span::call_site()), + bounds: [TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path: syn::Path { + leading_colon: None, + segments: [ + syn::PathSegment { + ident: Ident::new("kani", Span::call_site()), + arguments: syn::PathArguments::None, + }, + syn::PathSegment { + ident: Ident::new("Arbitrary", Span::call_site()), + arguments: syn::PathArguments::None, + }, + ] + .into_iter() + .collect(), + }, + })] + .into_iter() + .collect(), + })) +} + +/// We make shallow copies of the argument for the postconditions in both +/// `requires` and `ensures` clauses and later clean them up. +/// +/// This function creates the code necessary to both make the copies (first +/// tuple elem) and to clean them (second tuple elem). +fn make_unsafe_argument_copies( + renaming_map: &HashMap, +) -> (TokenStream2, TokenStream2) { + let arg_names = renaming_map.values(); + let also_arg_names = renaming_map.values(); + let arg_values = renaming_map.keys(); + ( + quote!(#(let #arg_names = kani::untracked_deref(&#arg_values);)*), + quote!(#(std::mem::forget(#also_arg_names);)*), + ) +} + +/// The main meat of handling requires/ensures contracts. +/// +/// See the [module level documentation][self] for a description of how the code +/// generation works. +fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: bool) -> TokenStream { + let attr_copy = TokenStream2::from(attr.clone()); + let attr = parse_macro_input!(attr as Expr); + + let mut output = proc_macro2::TokenStream::new(); + let item_stream_clone = item.clone(); + let item_fn = parse_macro_input!(item as ItemFn); + + let function_state = ContractFunctionState::from_attributes(&item_fn.attrs); + + if matches!(function_state, ContractFunctionState::Original) { + // If we're the original function that means we're *not* the first time + // that a contract attribute is handled on this function. This means + // there must exist a generated check function somewhere onto which the + // attributes have been copied and where they will be expanded into more + // checks. So we just return ourselves unchanged. + // + // Since this is the only function state case that doesn't need a + // handler to be constructed, we do this match early, separately. + return item_fn.into_token_stream().into(); + } + + let mut handler = ContractConditionsHandler::new( + function_state, + is_requires, + attr, + &item_fn, + attr_copy, + &mut output, + ); + + match function_state { + ContractFunctionState::Check => { + // The easy cases first: If we are on a check or replace function + // emit them again but with additional conditions layered on. + // + // Since we are already on the check function, it will have an + // appropriate, unique generated name which we are just going to + // pass on. + handler.emit_check_function(item_fn.sig.ident.clone()); + } + ContractFunctionState::Replace => { + // Analogous to above + handler.emit_replace_function(item_fn.sig.ident.clone(), false); + } + ContractFunctionState::Original => { + unreachable!("Impossible: This is handled via short circuiting earlier.") + } + ContractFunctionState::Untouched => { + // The complex case. We are the first time a contract is handled on this function, so + // we're responsible for + // + // 1. Generating a name for the check function + // 2. Emitting the original, unchanged item and register the check + // function on it via attribute + // 3. Renaming our item to the new name + // 4. And (minor point) adding #[allow(dead_code)] and + // #[allow(unused_variables)] to the check function attributes + + // We'll be using this to postfix the generated names for the "check" + // and "replace" functions. + let item_hash = short_hash_of_token_stream(&item_stream_clone); + + let check_fn_name = identifier_for_generated_function(&item_fn, "check", item_hash); + let replace_fn_name = identifier_for_generated_function(&item_fn, "replace", item_hash); + let recursion_wrapper_name = + identifier_for_generated_function(&item_fn, "recursion_wrapper", item_hash); + + // Constructing string literals explicitly here, because `stringify!` + // doesn't work. Let's say we have an identifier `check_fn` and we were + // to do `quote!(stringify!(check_fn))` to try to have it expand to + // `"check_fn"` in the generated code. Then when the next macro parses + // this it will *not* see the literal `"check_fn"` as you may expect but + // instead the *expression* `stringify!(check_fn)`. + let replace_fn_name_str = + syn::LitStr::new(&replace_fn_name.to_string(), Span::call_site()); + let recursion_wrapper_name_str = + syn::LitStr::new(&recursion_wrapper_name.to_string(), Span::call_site()); + + // The order of `attrs` and `kanitool::{checked_with, + // is_contract_generated}` is important here, because macros are + // expanded outside in. This way other contract annotations in `attrs` + // sees those attributes and can use them to determine + // `function_state`. + // + // The same care is taken when we emit check and replace functions. + // emit the check function. + let is_impl_fn = is_probably_impl_fn(&item_fn); + let ItemFn { attrs, vis, sig, block } = &item_fn; + handler.output.extend(quote!( + #(#attrs)* + #[kanitool::checked_with = #recursion_wrapper_name_str] + #[kanitool::replaced_with = #replace_fn_name_str] + #vis #sig { + #block + } + )); + + let mut wrapper_sig = sig.clone(); + attach_require_kani_any(&mut wrapper_sig); + wrapper_sig.ident = recursion_wrapper_name; + + let args = pats_to_idents(&mut wrapper_sig.inputs).collect::>(); + let also_args = args.iter(); + let (call_check, call_replace) = if is_impl_fn { + (quote!(Self::#check_fn_name), quote!(Self::#replace_fn_name)) + } else { + (quote!(#check_fn_name), quote!(#replace_fn_name)) + }; + + handler.output.extend(quote!( + #[allow(dead_code, unused_variables)] + #[kanitool::is_contract_generated(recursion_wrapper)] + #wrapper_sig { + static mut REENTRY: bool = false; + if unsafe { REENTRY } { + #call_replace(#(#args),*) + } else { + unsafe { REENTRY = true }; + let result = #call_check(#(#also_args),*); + unsafe { REENTRY = false }; + result + } + } + )); + + handler.emit_check_function(check_fn_name); + handler.emit_replace_function(replace_fn_name, true); + } + } + + output.into() +} + +/// Convert every use of a pattern in this signature to a simple, fresh, binding-only +/// argument ([`syn::PatIdent`]) and return the [`Ident`] that was generated. +fn pats_to_idents

( + sig: &mut syn::punctuated::Punctuated, +) -> impl Iterator + '_ { + sig.iter_mut().enumerate().map(|(i, arg)| match arg { + syn::FnArg::Receiver(_) => Ident::from(syn::Token![self](Span::call_site())), + syn::FnArg::Typed(syn::PatType { pat, .. }) => { + let ident = Ident::new(&format!("arg{i}"), Span::mixed_site()); + *pat.as_mut() = syn::Pat::Ident(syn::PatIdent { + attrs: vec![], + by_ref: None, + mutability: None, + ident: ident.clone(), + subpat: None, + }); + ident + } + }) +} + +/// The visitor used by [`is_probably_impl_fn`]. See function documentation for +/// more information. +struct SelfDetector(bool); + +impl<'ast> Visit<'ast> for SelfDetector { + fn visit_ident(&mut self, i: &'ast syn::Ident) { + self.0 |= i == &Ident::from(syn::Token![Self](Span::mixed_site())) + } + + fn visit_receiver(&mut self, _node: &'ast syn::Receiver) { + self.0 = true; + } +} + +/// Try to determine if this function is part of an `impl`. +/// +/// Detects *methods* by the presence of a receiver argument. Heuristically +/// detects *associated functions* by the use of `Self` anywhere. +/// +/// Why do we need this? It's because if we want to call this `fn`, or any other +/// `fn` we generate into the same context we need to use `foo()` or +/// `Self::foo()` respectively depending on whether this is a plain or +/// associated function or Rust will complain. For the contract machinery we +/// need to generate and then call various functions we generate as well as the +/// original contracted function and so we need to determine how to call them +/// correctly. +/// +/// We can only solve this heuristically. The fundamental problem with Rust +/// macros is that they only see the syntax that's given to them and no other +/// context. It is however that context (of an `impl` block) that definitively +/// determines whether the `fn` is a plain function or an associated function. +/// +/// The heuristic itself is flawed, but it's the best we can do. For instance +/// this is perfectly legal +/// +/// ``` +/// struct S; +/// impl S { +/// #[i_want_to_call_you] +/// fn helper(u: usize) -> bool { +/// u < 8 +/// } +/// } +/// ``` +/// +/// This function would have to be called `S::helper()` but to the +/// `#[i_want_to_call_you]` attribute this function looks just like a bare +/// function because it never mentions `self` or `Self`. While this is a rare +/// case, the following is much more likely and suffers from the same problem, +/// because we can't know that `Vec == Self`. +/// +/// ``` +/// impl Vec { +/// fn new() -> Vec { +/// Vec { cap: 0, buf: NonNull::dangling() } +/// } +/// } +/// ``` +/// +/// **Side note:** You may be tempted to suggest that we could try and parse +/// `syn::ImplItemFn` and distinguish that from `syn::ItemFn` to distinguish +/// associated function from plain functions. However parsing in an attribute +/// placed on *any* `fn` will always succeed for *both* `syn::ImplItemFn` and +/// `syn::ItemFn`, thus not letting us distinguish between the two. +fn is_probably_impl_fn(fun: &ItemFn) -> bool { + let mut self_detector = SelfDetector(false); + self_detector.visit_item_fn(fun); + self_detector.0 +} + +/// This is very similar to the kani_attribute macro, but it instead creates +/// key-value style attributes which I find a little easier to parse. +macro_rules! passthrough { + ($name:ident, $allow_dead_code:ident) => { + pub fn $name(attr: TokenStream, item: TokenStream) -> TokenStream { + let args = proc_macro2::TokenStream::from(attr); + let fn_item = proc_macro2::TokenStream::from(item); + let name = Ident::new(stringify!($name), proc_macro2::Span::call_site()); + let extra_attrs = if $allow_dead_code { + quote!(#[allow(dead_code)]) + } else { + quote!() + }; + quote!( + #extra_attrs + #[kanitool::#name = stringify!(#args)] + #fn_item + ) + .into() + } + } +} + +passthrough!(stub_verified, false); +passthrough!(proof_for_contract, true); + +#[cfg(test)] +mod test { + macro_rules! detect_impl_fn { + ($expect_pass:expr, $($tt:tt)*) => {{ + let syntax = stringify!($($tt)*); + let ast = syn::parse_str(syntax).unwrap(); + assert!($expect_pass == super::is_probably_impl_fn(&ast), + "Incorrect detection.\nExpected is_impl_fun: {}\nInput Expr; {}\nParsed: {:?}", + $expect_pass, + syntax, + ast + ); + }} + } + + #[test] + fn detect_impl_fn_by_receiver() { + detect_impl_fn!(true, fn self_by_ref(&self, u: usize) -> bool {}); + + detect_impl_fn!(true, fn self_by_self(self, u: usize) -> bool {}); + } + + #[test] + fn detect_impl_fn_by_self_ty() { + detect_impl_fn!(true, fn self_by_construct(u: usize) -> Self {}); + detect_impl_fn!(true, fn self_by_wrapped_construct(u: usize) -> Arc {}); + + detect_impl_fn!(true, fn self_by_other_arg(u: usize, slf: Self) {}); + + detect_impl_fn!(true, fn self_by_other_wrapped_arg(u: usize, slf: Vec) {}) + } + + #[test] + fn detect_impl_fn_by_qself() { + detect_impl_fn!( + true, + fn self_by_mention(u: usize) { + Self::other(u) + } + ); + } + + #[test] + fn detect_no_impl_fn() { + detect_impl_fn!( + false, + fn self_by_mention(u: usize) { + let self_name = 18; + let self_lit = "self"; + let self_lit = "Self"; + } + ); + } +} diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 3cad55b31167..d604ff6b9a47 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.32.0" +version = "0.42.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 907d98d280a2..92ff9216da95 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -15,9 +15,11 @@ pub use std::*; // Bind `core::assert` to a different name to avoid possible name conflicts if a // crate uses `extern crate std as core`. See // https://github.com/model-checking/kani/issues/1949 +#[cfg(not(feature = "concrete_playback"))] #[allow(unused_imports)] use core::assert as __kani__workaround_core_assert; +#[cfg(not(feature = "concrete_playback"))] // Override process calls with stubs. pub mod process; @@ -41,6 +43,7 @@ pub mod process; /// ``` /// the assert message will be: /// "The sum of {} and {} is {}", a, b, c +#[cfg(not(feature = "concrete_playback"))] #[macro_export] macro_rules! assert { ($cond:expr $(,)?) => { @@ -77,6 +80,7 @@ macro_rules! assert { // (see https://github.com/model-checking/kani/issues/13) // 3. Call kani::assert so that any instrumentation that it does (e.g. injecting // reachability checks) is done for assert_eq and assert_ne +#[cfg(not(feature = "concrete_playback"))] #[macro_export] macro_rules! assert_eq { ($left:expr, $right:expr $(,)?) => ({ @@ -89,6 +93,7 @@ macro_rules! assert_eq { }); } +#[cfg(not(feature = "concrete_playback"))] #[macro_export] macro_rules! assert_ne { ($left:expr, $right:expr $(,)?) => ({ @@ -102,16 +107,19 @@ macro_rules! assert_ne { } // Treat the debug assert macros same as non-debug ones +#[cfg(not(feature = "concrete_playback"))] #[macro_export] macro_rules! debug_assert { ($($x:tt)*) => ({ $crate::assert!($($x)*); }) } +#[cfg(not(feature = "concrete_playback"))] #[macro_export] macro_rules! debug_assert_eq { ($($x:tt)*) => ({ $crate::assert_eq!($($x)*); }) } +#[cfg(not(feature = "concrete_playback"))] #[macro_export] macro_rules! debug_assert_ne { ($($x:tt)*) => ({ $crate::assert_ne!($($x)*); }) @@ -119,28 +127,33 @@ macro_rules! debug_assert_ne { // Override the print macros to skip all the printing functionality (which // is not relevant for verification) +#[cfg(not(feature = "concrete_playback"))] #[macro_export] macro_rules! print { ($($x:tt)*) => {{ let _ = format_args!($($x)*); }}; } +#[cfg(not(feature = "concrete_playback"))] #[macro_export] macro_rules! eprint { ($($x:tt)*) => {{ let _ = format_args!($($x)*); }}; } +#[cfg(not(feature = "concrete_playback"))] #[macro_export] macro_rules! println { () => { }; ($($x:tt)*) => {{ let _ = format_args!($($x)*); }}; } +#[cfg(not(feature = "concrete_playback"))] #[macro_export] macro_rules! eprintln { () => { }; ($($x:tt)*) => {{ let _ = format_args!($($x)*); }}; } +#[cfg(not(feature = "concrete_playback"))] #[macro_export] macro_rules! unreachable { // The argument, if present, is a literal that represents the error message, i.e.: @@ -171,6 +184,7 @@ macro_rules! unreachable { stringify!($fmt, $($arg)*)))}}; } +#[cfg(not(feature = "concrete_playback"))] #[macro_export] macro_rules! panic { // No argument is given. @@ -180,7 +194,10 @@ macro_rules! panic { // The argument is a literal that represents the error message, i.e.: // `panic!("Error message")` ($msg:literal $(,)?) => ({ - kani::panic(concat!($msg)); + if false { + __kani__workaround_core_assert!(true, $msg); + } + kani::panic(concat!($msg)) }); // The argument is an expression, such as a variable. // ``` diff --git a/rfc/src/SUMMARY.md b/rfc/src/SUMMARY.md index a86f85ede4cf..f8d5dc5639f5 100644 --- a/rfc/src/SUMMARY.md +++ b/rfc/src/SUMMARY.md @@ -12,3 +12,6 @@ - [0004-loop-contract-synthesis](rfcs/0004-loop-contract-synthesis.md) - [0005-should-panic-attr](rfcs/0005-should-panic-attr.md) - [0006-unstable-api](rfcs/0006-unstable-api.md) +- [0007-global-conditions](rfcs/0007-global-conditions.md) +- [0008-line-coverage](rfcs/0008-line-coverage.md) +- [0009-function-contracts](rfcs/0009-function-contracts.md) diff --git a/rfc/src/intro.md b/rfc/src/intro.md index 300bab8b7966..c10b1486dfba 100644 --- a/rfc/src/intro.md +++ b/rfc/src/intro.md @@ -8,10 +8,10 @@ integrate feedback from developers and users on future changes to Kani, we decid You should create an RFC in one of two cases: -1. The change you are proposing would be a "one way door": e.g. a change to the public API, a new feature that would be difficult to modify once released, deprecating a feature, etc. +1. The change you are proposing would be a "one way door": e.g. a major change to the public API, a new feature that would be difficult to modify once released, etc. 2. The change you are making has a significant design component, and would benefit from a design review. -Bugs and smaller improvements to existing features do not require an RFC. +Bugs and improvements to existing features do not require an RFC. If you are in doubt, feel free to create a [feature request](https://github.com/model-checking/kani/issues/new?assignees=&labels=&template=feature_request.md) and discuss the next steps in the new issue. Your PR reviewer may also request an RFC if your change appears to fall into category 1 or 2. @@ -36,6 +36,9 @@ This is the overall workflow for the RFC process: 2. Start from a fork of the Kani repository. 3. Copy the template file ([`rfc/src/template.md`](./template.md)) to `rfc/src/rfcs/.md`. 4. Fill in the details according to the template instructions. + - For the first RFC version, we recommend that you leave the "Software Design" section empty. + - Focus on the user impact and user experience. + Include a few usage examples if possible. 5. Add a link to the new RFC inside [`rfc/src/SUMMARY.md`](https://github.com/model-checking/kani/blob/main/rfc/src/SUMMARY.md) 6. Submit a pull request. 2. Build consensus and integrate feedback. @@ -44,22 +47,24 @@ This is the overall workflow for the RFC process: 3. If the RFC is not approved, close the PR without merging. 3. Feature implementation. 1. Start implementing the new feature in your fork. - 2. It is OK to implement it incrementally over multiple PRs. Just ensure that every pull request has a testable - end-to-end flow and that it is properly tested. - 3. In the implementation stage, the feature should only be accessible if the user explicitly passes - `--enable-unstable` as an argument to Kani. - 4. Document how to use the feature. - 5. Keep the RFC up-to-date with the decisions you make during implementation. + 2. Create a new revision of the RFC to add details about the "Software Design". + 3. It is OK to implement the feature incrementally over multiple PRs. + Just ensure that every pull request has a testable end-to-end flow and that it is properly tested. + 4. In the implementation stage, the feature should only be accessible if the user explicitly passes + `-Z ` as an argument to Kani. + 5. Document how to use the feature. + 6. Keep the RFC up-to-date with the decisions you make during implementation. 4. Test and Gather Feedback. 1. Fix major issues related to the new feature. 2. Gather user feedback and make necessary adjustments. - 3. Add lots of tests. + 3. Resolve RFC open questions. + 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 `--enable-unstable` guard and that marks the RFC status as "STABLE". + 2. Create a new PR that removes the `-Z ` guard and that marks the RFC status as "STABLE". 1. Make sure the RFC reflects the final implementation and user experience. 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 that is no longer relevant. - 4. Close the tracking issue. \ No newline at end of file + 4. Close the tracking issue. diff --git a/rfc/src/rfcs/0008-line-coverage.md b/rfc/src/rfcs/0008-line-coverage.md new file mode 100644 index 000000000000..d2bedab8bdef --- /dev/null +++ b/rfc/src/rfcs/0008-line-coverage.md @@ -0,0 +1,171 @@ +- **Feature Name:** Line coverage (`line-coverage`) +- **Feature Request Issue:** +- **RFC PR:** +- **Status:** Unstable +- **Version:** 0 +- **Proof-of-concept:** (Kani) + (Kani VS Code Extension) + +------------------- + +## Summary + +Add verification-based line coverage reports to Kani. + +## User Impact + +Nowadays, users can't easily obtain verification-based coverage reports in Kani. +Generally speaking, coverage reports show which parts of the code under verification are covered and which are not. +Because of that, coverage is often seen as a great metric to determine the quality of a verification effort. + +Moreover, some users prefer using coverage information for harness development and debugging. +That's because coverage information provides users with more familiar way to interpret verification results. + +This RFC proposes adding a new option for verification-based line coverage reports to Kani. +As mentioned earlier, we expect users to employ this coverage-related option on several stages of a verification effort: + * **Learning:** New users are more familiar with coverage reports than property-based results. + * **Development:** Some users prefer coverage results to property-based results since they are easier to interpret. + * **CI Integration**: Users may want to enforce a minimum percentage of code coverage for new contributions. + * **Debugging:** Users may find coverage reports particularly helpful when inputs are over-constrained (missing some corner cases). + * **Evaluation:** Users can easily evaluate where and when more verification work is needed (some projects aim for 100% coverage). + +Moreover, adding this option directly to Kani, instead of relying on another tools, is likely to: + 1. Increase the speed of development + 2. Improve testing for coverage features + +Which translates into faster and more reliable coverage options for users. + +## User Experience + +The goal is for Kani to generate code coverage report per harness in a well established format, such as [LCOV](https://github.com/linux-test-project/lcov), and possibly a summary in the output. +For now, we will focus on an interim solution that will enable us to assess the results of [our instrumentation](#injection-of-coverage-checks) and enable integration with the Kani VS Code extension. + +### High-level changes + +For the first version, this experimental feature will report verification results along coverage reports. +Because of that, we'll add a new section `Coverage results` that shows coverage results for each individual harness. + +In the following, we describe an experimental output format. +Note that the final output format and overall UX is to be determined. + +### Experimental output format for coverage results + +The `Coverage results` section for each harness will produce coverage information in a CSV format as follows: +``` +, , +``` +where `` is either `FULL`, `PARTIAL` or `NONE`. + +As mentioned, this format is designed for evaluating the [native instrumentation-based design](#detailed-design) and is likely to be substituted with another well-established format as soon as possible. + +**Users are not expected to consume this output directly.** +Instead, coverage data is to be consumed by the [Kani VS Code extension](https://github.com/model-checking/kani-vscode-extension) and displayed as in [the VS Code Extension prototype](https://github.com/model-checking/kani-vscode-extension/pull/122). + +How to activate and display coverage information in the extension is out of scope for this RFC. +That said, a proof-of-concept implementation is available [here](https://github.com/model-checking/kani-vscode-extension/pull/122). + +## Detailed Design + +### Architecture + +We will add a new unstable `--coverage` verification option to Kani which will require `-Z line-coverage` until this feature is stabilized. +We will also add a new `--coverage-checks` option to `kani-compiler`, which will result in the injection of coverage checks before each Rust statement and terminator[^coverage-experiments]. +This option will be supplied by `kani-driver` when the `--coverage` option is selected. +These options will cause Kani to inject coverage checks during compilation and postprocess them to produce the coverage results sections described earlier. + +### Coverage Checks + +Coverage checks are a new class of checks similar to [`cover` checks](https://model-checking.github.io/kani/rfc/rfcs/0003-cover-statement.html). +The main difference is that users cannot directly interact with coverage checks (i.e., they cannot add or remove them manually). +Coverage checks are encoded as an `assert(false)` statement (to test reachability) with a fixed description. +In addition, coverage checks are: + * Hidden from verification results. + * Postprocessed to produce coverage results. + +In the following, we describe the injection and postprocessing procedures to generate coverage results. + +#### Injection of Coverage Checks + +The injection of coverage checks will be done while generating code for basic blocks. +This allows us to add one coverage check before each statement and terminator, which provides the most accurate results[^coverage-experiments]. +It's not completely clear how this compares to the coverage instrumentation done in the Rust compiler, but an exploration to use the compiler APIs revealed that they're quite similar[^coverage-api]. + +#### Postprocessing Coverage Checks + +The injection of coverage checks often results in one or more checks per line (assuming a well-formatted program). +We'll postprocess these checks so for each line + - if all checks are `SATISFIED`: return `FULL` + - if all checks are `UNSATISFIED`: return `NONE` + - otherwise: return `PARTIAL` + +We won't report coverage status for lines which don't include a coverage check. + +## Rationale and alternatives + +### Benefits from a native coverage solution + +Kani has relied on [`cbmc-viewer`](https://github.com/model-checking/cbmc-viewer) to report coverage information since the beginning. +In essence, `cbmc-viewer` consumes data from coverage-focused invocations of CBMC and produces an HTML report containing (1) coverage information and (2) counterexample traces. +Recently, there have been some issues with the coverage information reported by `cbmc-viewer` (e.g., [#2048](https://github.com/model-checking/kani/issues/2048) or [#1707](https://github.com/model-checking/kani/issues/1707)), forcing us to mark the `--visualize` option as unstable and disable coverage results in the reports (in [#2206](https://github.com/model-checking/kani/pull/2206)). + +However, it's possible for Kani to report coverage information without `cbmc-viewer`, as explained before. +This would give Kani control on both ends: + * **The instrumentation performed** on the program. Eventually, this would allow us to report more precise coverage information (maybe similar to [Rust's instrument-based code coverage](https://doc.rust-lang.org/rustc/instrument-coverage.html)). + * **The format of the coverage report** to be generated. Similarly, this would allow us to generate coverage data in different formats (see [#1706](https://github.com/model-checking/kani/issues/1706) for GCOV, or [#1777](https://github.com/model-checking/kani/issues/1777) for LCOV). While technically this is also doable from `cbmc-viewer`'s output, development is likely to be faster this way. + +#### Coverage through `cbmc-viewer` + +As an alternative, we could fix and use `cbmc-viewer` to report line coverage. + +Most of the issues with `cbmc-viewer` are generally due to: + 1. Missing locations due to non-propagation of locations in either Kani or CBMC. + 2. Differences in the definition of a basic block in CBMC and Rust's MIR. + 3. Scarce documentation for coverage-related options (i.e., `--cover

(P); + +impl<'a> kani::Arbitrary for ArbitraryPointer<&'a mut bool> { + fn any() -> Self { + ArbitraryPointer(unsafe { &mut B as &'a mut bool }) + } +} + +#[kani::ensures(true)] +fn allowed_mut_return_ref<'a>() -> ArbitraryPointer<&'a mut bool> { + ArbitraryPointer(unsafe { &mut B as &'a mut bool }) +} + +#[kani::proof_for_contract(allowed_mut_return_ref)] +fn allowed_mut_return_ref_harness() { + let _ = Box::new(()); + allowed_mut_return_ref(); +} diff --git a/tests/expected/function-contract/prohibit-pointers/allowed_ref.expected b/tests/expected/function-contract/prohibit-pointers/allowed_ref.expected new file mode 100644 index 000000000000..34c886c358cb --- /dev/null +++ b/tests/expected/function-contract/prohibit-pointers/allowed_ref.expected @@ -0,0 +1 @@ +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/prohibit-pointers/allowed_ref.rs b/tests/expected/function-contract/prohibit-pointers/allowed_ref.rs new file mode 100644 index 000000000000..3dd4145eff9c --- /dev/null +++ b/tests/expected/function-contract/prohibit-pointers/allowed_ref.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#![allow(unreachable_code, unused_variables)] + +#[kani::ensures(true)] +fn allowed_ref(t: &bool) {} + +#[kani::proof_for_contract(allowed_ref)] +fn allowed_ref_harness() { + let _ = Box::new(()); + let a = true; + allowed_ref(&a) +} diff --git a/tests/expected/function-contract/prohibit-pointers/hidden.expected b/tests/expected/function-contract/prohibit-pointers/hidden.expected new file mode 100644 index 000000000000..59e40d5aadc8 --- /dev/null +++ b/tests/expected/function-contract/prohibit-pointers/hidden.expected @@ -0,0 +1 @@ +error: This argument contains a mutable pointer (*mut u32). This is prohibited for functions with contracts, as they cannot yet reason about the pointer behavior. diff --git a/tests/expected/function-contract/prohibit-pointers/hidden.rs b/tests/expected/function-contract/prohibit-pointers/hidden.rs new file mode 100644 index 000000000000..9ca23fe6b2e1 --- /dev/null +++ b/tests/expected/function-contract/prohibit-pointers/hidden.rs @@ -0,0 +1,16 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#![allow(unreachable_code, unused_variables)] + +struct HidesAPointer(*mut u32); + +#[kani::ensures(true)] +fn hidden_pointer(h: HidesAPointer) {} + +#[kani::proof_for_contract(hidden_pointer)] +fn harness() { + let mut a = 0; + hidden_pointer(HidesAPointer(&mut a)) +} diff --git a/tests/expected/function-contract/prohibit-pointers/plain_pointer.expected b/tests/expected/function-contract/prohibit-pointers/plain_pointer.expected new file mode 100644 index 000000000000..916854aa3131 --- /dev/null +++ b/tests/expected/function-contract/prohibit-pointers/plain_pointer.expected @@ -0,0 +1 @@ +error: This argument contains a mutable pointer (*mut i32). This is prohibited for functions with contracts, as they cannot yet reason about the pointer behavior. diff --git a/tests/expected/function-contract/prohibit-pointers/plain_pointer.rs b/tests/expected/function-contract/prohibit-pointers/plain_pointer.rs new file mode 100644 index 000000000000..24e9d006a9c0 --- /dev/null +++ b/tests/expected/function-contract/prohibit-pointers/plain_pointer.rs @@ -0,0 +1,14 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#![allow(unreachable_code, unused_variables)] + +#[kani::ensures(true)] +fn plain_pointer(t: *mut i32) {} + +#[kani::proof_for_contract(plain_pointer)] +fn plain_ptr_harness() { + let mut a = 0; + plain_pointer(&mut a) +} diff --git a/tests/expected/function-contract/prohibit-pointers/return_pointer.expected b/tests/expected/function-contract/prohibit-pointers/return_pointer.expected new file mode 100644 index 000000000000..8f3689888d92 --- /dev/null +++ b/tests/expected/function-contract/prohibit-pointers/return_pointer.expected @@ -0,0 +1 @@ +error: The return contains a pointer (*const usize). This is prohibited for functions with contracts, as they cannot yet reason about the pointer behavior. diff --git a/tests/expected/function-contract/prohibit-pointers/return_pointer.rs b/tests/expected/function-contract/prohibit-pointers/return_pointer.rs new file mode 100644 index 000000000000..fc279667ad13 --- /dev/null +++ b/tests/expected/function-contract/prohibit-pointers/return_pointer.rs @@ -0,0 +1,25 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#![allow(unreachable_code, unused_variables)] + +/// This only exists so I can fake a [`kani::Arbitrary`] instance for `*const +/// usize`. +struct ArbitraryPointer

(P); + +impl kani::Arbitrary for ArbitraryPointer<*const usize> { + fn any() -> Self { + unreachable!() + } +} + +#[kani::ensures(true)] +fn return_pointer() -> ArbitraryPointer<*const usize> { + unreachable!() +} + +#[kani::proof_for_contract(return_pointer)] +fn return_ptr_harness() { + return_pointer(); +} diff --git a/tests/expected/function-contract/simple_ensures_fail.expected b/tests/expected/function-contract/simple_ensures_fail.expected new file mode 100644 index 000000000000..8e9b42d42640 --- /dev/null +++ b/tests/expected/function-contract/simple_ensures_fail.expected @@ -0,0 +1,8 @@ +assertion\ +- Status: FAILURE\ +- Description: "result == x"\ +in function max + +Failed Checks: result == x + +VERIFICATION:- FAILED diff --git a/tests/expected/function-contract/simple_ensures_fail.rs b/tests/expected/function-contract/simple_ensures_fail.rs new file mode 100644 index 000000000000..687853612dcc --- /dev/null +++ b/tests/expected/function-contract/simple_ensures_fail.rs @@ -0,0 +1,14 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#[kani::ensures(result == x)] +fn max(x: u32, y: u32) -> u32 { + if x > y { x } else { y } +} + +#[kani::proof_for_contract(max)] +fn max_harness() { + let _ = Box::new(9_usize); + max(7, 9); +} diff --git a/tests/expected/function-contract/simple_ensures_pass.expected b/tests/expected/function-contract/simple_ensures_pass.expected new file mode 100644 index 000000000000..5a7874964413 --- /dev/null +++ b/tests/expected/function-contract/simple_ensures_pass.expected @@ -0,0 +1,6 @@ +assertion\ +- Status: SUCCESS\ +- Description: "(result == x) | (result == y)"\ +in function max + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/simple_ensures_pass.rs b/tests/expected/function-contract/simple_ensures_pass.rs new file mode 100644 index 000000000000..2d36f5c96e88 --- /dev/null +++ b/tests/expected/function-contract/simple_ensures_pass.rs @@ -0,0 +1,14 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#[kani::ensures((result == x) | (result == y))] +fn max(x: u32, y: u32) -> u32 { + if x > y { x } else { y } +} + +#[kani::proof_for_contract(max)] +fn max_harness() { + let _ = Box::new(9_usize); + max(7, 6); +} diff --git a/tests/expected/function-contract/simple_replace_fail.expected b/tests/expected/function-contract/simple_replace_fail.expected new file mode 100644 index 000000000000..b6806befc22c --- /dev/null +++ b/tests/expected/function-contract/simple_replace_fail.expected @@ -0,0 +1,3 @@ +main.assertion\ +- Status: FAILURE\ +- Description: ""contract doesn't guarantee equality"" diff --git a/tests/expected/function-contract/simple_replace_fail.rs b/tests/expected/function-contract/simple_replace_fail.rs new file mode 100644 index 000000000000..33a531a3aef7 --- /dev/null +++ b/tests/expected/function-contract/simple_replace_fail.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#[kani::requires(divisor != 0)] +#[kani::ensures(result <= dividend)] +fn div(dividend: u32, divisor: u32) -> u32 { + dividend / divisor +} + +#[kani::proof] +#[kani::stub_verified(div)] +fn main() { + assert!(div(9, 4) == 2, "contract doesn't guarantee equality"); +} diff --git a/tests/expected/function-contract/simple_replace_pass.expected b/tests/expected/function-contract/simple_replace_pass.expected new file mode 100644 index 000000000000..e1fc78ca462f --- /dev/null +++ b/tests/expected/function-contract/simple_replace_pass.expected @@ -0,0 +1,9 @@ +div.assertion\ +- Status: SUCCESS\ +- Description: "divisor != 0" + +main.assertion\ +- Status: SUCCESS\ +- Description: ""contract guarantees smallness"" + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/simple_replace_pass.rs b/tests/expected/function-contract/simple_replace_pass.rs new file mode 100644 index 000000000000..0dcc6cd59fe3 --- /dev/null +++ b/tests/expected/function-contract/simple_replace_pass.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#[kani::requires(divisor != 0)] +#[kani::ensures(result <= dividend)] +fn div(dividend: u32, divisor: u32) -> u32 { + dividend / divisor +} + +#[kani::proof] +#[kani::stub_verified(div)] +fn main() { + assert!(div(9, 1) != 10, "contract guarantees smallness"); +} diff --git a/tests/expected/function-contract/type_annotation_needed.expected b/tests/expected/function-contract/type_annotation_needed.expected new file mode 100644 index 000000000000..34c886c358cb --- /dev/null +++ b/tests/expected/function-contract/type_annotation_needed.expected @@ -0,0 +1 @@ +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/type_annotation_needed.rs b/tests/expected/function-contract/type_annotation_needed.rs new file mode 100644 index 000000000000..09b20443d47b --- /dev/null +++ b/tests/expected/function-contract/type_annotation_needed.rs @@ -0,0 +1,14 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#[kani::ensures(result.is_some())] +fn or_default(opt: Option) -> Option { + opt.or(Some(T::default())) +} + +#[kani::proof_for_contract(or_default)] +fn harness() { + let input: Option = kani::any(); + or_default(input); +} diff --git a/tests/expected/function-stubbing-error/expected b/tests/expected/function-stubbing-error/expected new file mode 100644 index 000000000000..6adf1ad27631 --- /dev/null +++ b/tests/expected/function-stubbing-error/expected @@ -0,0 +1,2 @@ +error: Use of unstable feature 'stubbing' in harness `main`. +To enable stubbing, pass option `-Z stubbing` diff --git a/tests/expected/function-stubbing-warning/main.rs b/tests/expected/function-stubbing-error/main.rs similarity index 68% rename from tests/expected/function-stubbing-warning/main.rs rename to tests/expected/function-stubbing-error/main.rs index 4305f4d398be..4b206bfaee44 100644 --- a/tests/expected/function-stubbing-warning/main.rs +++ b/tests/expected/function-stubbing-error/main.rs @@ -3,8 +3,7 @@ // // kani-flags: --harness main // -//! This tests whether we issue a warning if a stub is specified for a harness -//! but stubbing is not enabled. +//! This tests whether we abort if a stub is specified for a harness but stubbing is not enabled. fn foo() -> u32 { 0 diff --git a/tests/expected/function-stubbing-warning/expected b/tests/expected/function-stubbing-warning/expected deleted file mode 100644 index 5a5f9f608542..000000000000 --- a/tests/expected/function-stubbing-warning/expected +++ /dev/null @@ -1,5 +0,0 @@ -Checking harness main... -Failed Checks: assertion failed: foo() == 42 - -warning: harness `main` contained stubs which were ignored.\ -To enable stubbing, pass options `--enable-unstable --enable-stubbing` diff --git a/tests/expected/generators/as_parameter/main.rs b/tests/expected/generators/as_parameter/main.rs deleted file mode 100644 index 513204e230ec..000000000000 --- a/tests/expected/generators/as_parameter/main.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -// Test that a generator can be passed as a parameter. -// (adapted from https://github.com/model-checking/kani/issues/1075) - -#![feature(generators, generator_trait)] - -use std::ops::{Generator, GeneratorState}; -use std::pin::Pin; - -fn foo + Unpin>(mut g: G) -where - ::Return: std::cmp::PartialEq, -{ - let res = Pin::new(&mut g).resume(()); - assert_eq!(res, GeneratorState::Yielded(1)); - let res2 = Pin::new(&mut g).resume(()); - assert_eq!(res2, GeneratorState::Complete(2)); -} - -#[kani::proof] -fn main() { - foo(|| { - yield 1; - return 2; - }); -} diff --git a/tests/expected/generators/expected b/tests/expected/generators/expected deleted file mode 100644 index b619dd8c009a..000000000000 --- a/tests/expected/generators/expected +++ /dev/null @@ -1,10 +0,0 @@ -SUCCESS\ -Description: "assertion failed: res == GeneratorState::Yielded(val)" - -SUCCESS\ -Description: "assertion failed: res == GeneratorState::Complete(!val)" - -UNREACHABLE\ -Description: "generator resumed after completion" - -VERIFICATION:- SUCCESSFUL \ No newline at end of file diff --git a/tests/expected/generators/main.rs b/tests/expected/generators/main.rs deleted file mode 100644 index b6dbce9363c5..000000000000 --- a/tests/expected/generators/main.rs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 OR MIT - -#![feature(generators, generator_trait)] - -use std::ops::{Generator, GeneratorState}; -use std::pin::Pin; - -#[kani::proof] -#[kani::unwind(2)] -fn main() { - let val: bool = kani::any(); - let mut generator = move || { - let x = val; - yield x; - return !x; - }; - - let res = Pin::new(&mut generator).resume(()); - assert_eq!(res, GeneratorState::Yielded(val)); - let res = Pin::new(&mut generator).resume(()); - assert_eq!(res, GeneratorState::Complete(!val)); -} diff --git a/tests/expected/intrinsics/ctpop-ice/expected b/tests/expected/intrinsics/ctpop-ice/expected index 25fbb79d9551..1ac989525f36 100644 --- a/tests/expected/intrinsics/ctpop-ice/expected +++ b/tests/expected/intrinsics/ctpop-ice/expected @@ -3,4 +3,4 @@ error: Type check failed for intrinsic `ctpop`: Expected integer type, found () 12 | let n = ctpop(()); | ^^^^^^^^^ -error: aborting due to previous error \ No newline at end of file +error: aborting due to 1 previous error \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-cmp-result-type-is-diff-size/expected b/tests/expected/intrinsics/simd-cmp-result-type-is-diff-size/expected index f6bf97116db3..b535e4699346 100644 --- a/tests/expected/intrinsics/simd-cmp-result-type-is-diff-size/expected +++ b/tests/expected/intrinsics/simd-cmp-result-type-is-diff-size/expected @@ -1,2 +1,2 @@ expected return type with length 2 (same as input type `u64x2`), found `u32x4` with length 4 -error: aborting due to previous error \ No newline at end of file +error: aborting due to 1 previous error \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-div-rem-overflow/expected b/tests/expected/intrinsics/simd-div-rem-overflow/expected new file mode 100644 index 000000000000..156854c7dcdc --- /dev/null +++ b/tests/expected/intrinsics/simd-div-rem-overflow/expected @@ -0,0 +1,8 @@ +FAILURE\ +attempt to compute simd_div which would overflow +UNREACHABLE\ +assertion failed: quotients.0 == quotients.1 +FAILURE\ +attempt to compute simd_rem which would overflow +UNREACHABLE\ +assertion failed: remainders.0 == remainders.1 \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-div-rem-overflow/main.rs b/tests/expected/intrinsics/simd-div-rem-overflow/main.rs new file mode 100644 index 000000000000..e7f2e9fbc48f --- /dev/null +++ b/tests/expected/intrinsics/simd-div-rem-overflow/main.rs @@ -0,0 +1,44 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// Checks that the `simd_div` and `simd_rem` intrinsics check for integer overflows. + +#![feature(repr_simd, platform_intrinsics)] + +#[repr(simd)] +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct i32x2(i32, i32); + +extern "platform-intrinsic" { + fn simd_div(x: T, y: T) -> T; + fn simd_rem(x: T, y: T) -> T; +} + +unsafe fn do_simd_div(dividends: i32x2, divisors: i32x2) -> i32x2 { + simd_div(dividends, divisors) +} + +unsafe fn do_simd_rem(dividends: i32x2, divisors: i32x2) -> i32x2 { + simd_rem(dividends, divisors) +} + +#[kani::proof] +fn test_simd_div_overflow() { + let dividend = i32::MIN; + let dividends = i32x2(dividend, dividend); + let divisor = -1; + let divisors = i32x2(divisor, divisor); + let quotients = unsafe { do_simd_div(dividends, divisors) }; + assert_eq!(quotients.0, quotients.1); +} + +#[kani::proof] +fn test_simd_rem_overflow() { + let dividend = i32::MIN; + let dividends = i32x2(dividend, dividend); + let divisor = -1; + let divisors = i32x2(divisor, divisor); + let remainders = unsafe { do_simd_rem(dividends, divisors) }; + assert_eq!(remainders.0, remainders.1); +} diff --git a/tests/expected/intrinsics/simd-extract-wrong-type/expected b/tests/expected/intrinsics/simd-extract-wrong-type/expected index 5d6a42b0542d..eb2c6e932803 100644 --- a/tests/expected/intrinsics/simd-extract-wrong-type/expected +++ b/tests/expected/intrinsics/simd-extract-wrong-type/expected @@ -1,2 +1,2 @@ expected return type `i64` (element of input `i64x2`), found `i32` -error: aborting due to previous error \ No newline at end of file +error: aborting due to 1 previous error \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-insert-wrong-type/expected b/tests/expected/intrinsics/simd-insert-wrong-type/expected index 8e2271a5ca57..c86b42bc89e4 100644 --- a/tests/expected/intrinsics/simd-insert-wrong-type/expected +++ b/tests/expected/intrinsics/simd-insert-wrong-type/expected @@ -1,2 +1,2 @@ expected inserted type `i64` (element of input `i64x2`), found `i32` -error: aborting due to previous error \ No newline at end of file +error: aborting due to 1 previous error \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-result-type-is-float/expected b/tests/expected/intrinsics/simd-result-type-is-float/expected index 26145ffed72c..991b6f37d384 100644 --- a/tests/expected/intrinsics/simd-result-type-is-float/expected +++ b/tests/expected/intrinsics/simd-result-type-is-float/expected @@ -1,2 +1,2 @@ expected return type with integer elements, found `f32x2` with non-integer `f32` -error: aborting due to previous error \ No newline at end of file +error: aborting due to 1 previous error \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-shl-operand-negative/expected b/tests/expected/intrinsics/simd-shl-operand-negative/expected deleted file mode 100644 index b437e2374575..000000000000 --- a/tests/expected/intrinsics/simd-shl-operand-negative/expected +++ /dev/null @@ -1,2 +0,0 @@ -FAILURE\ -shift operand is negative \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-shl-operand-negative/main.rs b/tests/expected/intrinsics/simd-shl-operand-negative/main.rs deleted file mode 100644 index 3d7969d8d4b6..000000000000 --- a/tests/expected/intrinsics/simd-shl-operand-negative/main.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -//! Checks that `simd_shl` returns a failure if the operand is negative. -//! Note: The "operand is negative" property isn't checked for `simd_shr`, only -//! for `simd_shl`. -#![feature(repr_simd, platform_intrinsics)] - -#[repr(simd)] -#[allow(non_camel_case_types)] -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct i32x2(i32, i32); - -extern "platform-intrinsic" { - fn simd_shl(x: T, y: T) -> T; -} - -#[kani::proof] -fn test_simd_shl() { - let value = kani::any(); - let values = i32x2(value, value); - let shift = kani::any(); - kani::assume(shift >= 0); - kani::assume(shift < 32); - let shifts = i32x2(shift, shift); - let _result = unsafe { simd_shl(values, shifts) }; -} diff --git a/tests/expected/intrinsics/simd-shl-shift-negative/expected b/tests/expected/intrinsics/simd-shl-shift-negative/expected index 0e4e9fadb811..c3b2e1184e06 100644 --- a/tests/expected/intrinsics/simd-shl-shift-negative/expected +++ b/tests/expected/intrinsics/simd-shl-shift-negative/expected @@ -1,2 +1,2 @@ FAILURE\ -shift distance is negative \ No newline at end of file +attempt simd_shl with negative shift distance \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-shl-shift-negative/main.rs b/tests/expected/intrinsics/simd-shl-shift-negative/main.rs index ebde0284a497..0c7116b30567 100644 --- a/tests/expected/intrinsics/simd-shl-shift-negative/main.rs +++ b/tests/expected/intrinsics/simd-shl-shift-negative/main.rs @@ -16,7 +16,6 @@ extern "platform-intrinsic" { #[kani::proof] fn test_simd_shl() { let value = kani::any(); - kani::assume(value >= 0); let values = i32x2(value, value); let shift = kani::any(); kani::assume(shift < 32); diff --git a/tests/expected/intrinsics/simd-shl-shift-too-large/expected b/tests/expected/intrinsics/simd-shl-shift-too-large/expected index 65d41d1518fc..2c50f584f06c 100644 --- a/tests/expected/intrinsics/simd-shl-shift-too-large/expected +++ b/tests/expected/intrinsics/simd-shl-shift-too-large/expected @@ -1,2 +1,2 @@ FAILURE\ -shift distance too large \ No newline at end of file +attempt simd_shl with excessive shift distance \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-shl-shift-too-large/main.rs b/tests/expected/intrinsics/simd-shl-shift-too-large/main.rs index e7cffe4ebb92..fff9aadf1900 100644 --- a/tests/expected/intrinsics/simd-shl-shift-too-large/main.rs +++ b/tests/expected/intrinsics/simd-shl-shift-too-large/main.rs @@ -16,7 +16,6 @@ extern "platform-intrinsic" { #[kani::proof] fn test_simd_shl() { let value = kani::any(); - kani::assume(value >= 0); let values = i32x2(value, value); let shift = kani::any(); kani::assume(shift >= 0); diff --git a/tests/expected/intrinsics/simd-shr-shift-negative/expected b/tests/expected/intrinsics/simd-shr-shift-negative/expected index 0e4e9fadb811..49a75a18308e 100644 --- a/tests/expected/intrinsics/simd-shr-shift-negative/expected +++ b/tests/expected/intrinsics/simd-shr-shift-negative/expected @@ -1,2 +1,2 @@ FAILURE\ -shift distance is negative \ No newline at end of file +attempt simd_shr with negative shift distance \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-shr-shift-negative/main.rs b/tests/expected/intrinsics/simd-shr-shift-negative/main.rs index ca8cbbc3e0d7..580f0337db25 100644 --- a/tests/expected/intrinsics/simd-shr-shift-negative/main.rs +++ b/tests/expected/intrinsics/simd-shr-shift-negative/main.rs @@ -16,7 +16,6 @@ extern "platform-intrinsic" { #[kani::proof] fn test_simd_shr() { let value = kani::any(); - kani::assume(value >= 0); let values = i32x2(value, value); let shift = kani::any(); kani::assume(shift < 32); diff --git a/tests/expected/intrinsics/simd-shr-shift-too-large/expected b/tests/expected/intrinsics/simd-shr-shift-too-large/expected index 65d41d1518fc..adc26e269699 100644 --- a/tests/expected/intrinsics/simd-shr-shift-too-large/expected +++ b/tests/expected/intrinsics/simd-shr-shift-too-large/expected @@ -1,2 +1,2 @@ FAILURE\ -shift distance too large \ No newline at end of file +attempt simd_shr with excessive shift distance \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-shr-shift-too-large/main.rs b/tests/expected/intrinsics/simd-shr-shift-too-large/main.rs index 87718ec48553..3d9cd5e0c919 100644 --- a/tests/expected/intrinsics/simd-shr-shift-too-large/main.rs +++ b/tests/expected/intrinsics/simd-shr-shift-too-large/main.rs @@ -16,7 +16,6 @@ extern "platform-intrinsic" { #[kani::proof] fn test_simd_shr() { let value = kani::any(); - kani::assume(value >= 0); let values = i32x2(value, value); let shift = kani::any(); kani::assume(shift >= 0); diff --git a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/expected b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/expected index bc85467ee413..6b6c4ad781fd 100644 --- a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/expected +++ b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/expected @@ -1,2 +1,2 @@ expected return type of length 4, found `i64x2` with length 2 -error: aborting due to previous error \ No newline at end of file +error: aborting due to 1 previous error \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/main.rs b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/main.rs index f18203d8a337..25796f6c22e7 100644 --- a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/main.rs +++ b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/main.rs @@ -16,7 +16,7 @@ pub struct i64x2(i64, i64); pub struct i64x4(i64, i64, i64, i64); extern "platform-intrinsic" { - fn simd_shuffle4(x: T, y: T, idx: [u32; 4]) -> U; + fn simd_shuffle(x: T, y: T, idx: I) -> U; } #[kani::proof] @@ -24,7 +24,7 @@ fn main() { let y = i64x2(0, 1); let z = i64x2(1, 2); const I: [u32; 4] = [1, 2, 1, 2]; - let x: i64x2 = unsafe { simd_shuffle4(y, z, I) }; + let x: i64x2 = unsafe { simd_shuffle(y, z, I) }; // ^^^^ The code above fails to type-check in Rust with the error: // ``` // error[E0511]: invalid monomorphization of `simd_shuffle4` intrinsic: expected return type of length 4, found `i64x2` with length 2 diff --git a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/expected b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/expected index 4bb75c754afb..9982de236965 100644 --- a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/expected +++ b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/expected @@ -1,2 +1,2 @@ expected return element type `i64` (element of input `i64x2`), found `f64x2` with element type `f64` -error: aborting due to previous error \ No newline at end of file +error: aborting due to 1 previous error \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/main.rs b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/main.rs index 78b2d0171013..6bdadae159f8 100644 --- a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/main.rs +++ b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/main.rs @@ -16,7 +16,7 @@ pub struct i64x2(i64, i64); pub struct f64x2(f64, f64); extern "platform-intrinsic" { - fn simd_shuffle2(x: T, y: T, idx: [u32; 2]) -> U; + fn simd_shuffle(x: T, y: T, idx: I) -> U; } #[kani::proof] @@ -24,7 +24,7 @@ fn main() { let y = i64x2(0, 1); let z = i64x2(1, 2); const I: [u32; 2] = [1, 2]; - let x: f64x2 = unsafe { simd_shuffle2(y, z, I) }; + let x: f64x2 = unsafe { simd_shuffle(y, z, I) }; // ^^^^ The code above fails to type-check in Rust with the error: // ``` // error[E0511]: invalid monomorphization of `simd_shuffle2` intrinsic: expected return element type `i64` (element of input `i64x2`), found `f64x2` with element type `f64` diff --git a/tests/expected/issue-2589/issue_2589.expected b/tests/expected/issue-2589/issue_2589.expected new file mode 100644 index 000000000000..0352a8933b6d --- /dev/null +++ b/tests/expected/issue-2589/issue_2589.expected @@ -0,0 +1 @@ +error: Type `std::string::String` does not implement trait `Dummy`. This is likely because `original` is used as a stub but its generic bounds are not being met. diff --git a/tests/expected/issue-2589/issue_2589.rs b/tests/expected/issue-2589/issue_2589.rs new file mode 100644 index 000000000000..a9442091c1e3 --- /dev/null +++ b/tests/expected/issue-2589/issue_2589.rs @@ -0,0 +1,21 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: -Z stubbing + +fn original() {} + +trait Dummy { + const TRUE: bool = true; +} + +fn stub() { + // Do nothing. + assert!(T::TRUE); +} + +#[kani::proof] +#[kani::stub(original, stub)] +fn check_mismatch() { + original::(); +} diff --git a/tests/expected/nondet-slice-i32-oob/main.rs b/tests/expected/nondet-slice-i32-oob/main.rs index 399304469960..9537bbc5de43 100644 --- a/tests/expected/nondet-slice-i32-oob/main.rs +++ b/tests/expected/nondet-slice-i32-oob/main.rs @@ -2,11 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // This test checks that Kani reports out-of-bound accesses on a non-det slice -// created using kani::slice::any_slice +// created using `kani::slice::any_slice_of_array` #[kani::proof] fn check_out_of_bounds() { - let bytes = kani::slice::any_slice::(); - let val = unsafe { *bytes.get_slice().as_ptr().offset(1) }; + let arr: [i32; 8] = kani::any(); + let bytes = kani::slice::any_slice_of_array(&arr); + let val = unsafe { *bytes.as_ptr().offset(1) }; assert_eq!(val - val, 0); } diff --git a/tests/expected/nondet-slice-len/expected b/tests/expected/nondet-slice-len/expected index 3a9f223e446c..629888bd74d6 100644 --- a/tests/expected/nondet-slice-len/expected +++ b/tests/expected/nondet-slice-len/expected @@ -1,5 +1,14 @@ -Failed Checks: assertion failed: s.len() != 0 -Failed Checks: assertion failed: s.len() != 1 -Failed Checks: assertion failed: s.len() != 2 -Failed Checks: assertion failed: s.len() != 3 -Failed Checks: assertion failed: s.len() != 4 +Status: SATISFIED\ +Description: "cover condition: s.len() == 0" + +Status: SATISFIED\ +Description: "cover condition: s.len() == 1" + +Status: SATISFIED\ +Description: "cover condition: s.len() == 2" + +Status: SATISFIED\ +Description: "cover condition: s.len() == 3" + +Status: SATISFIED\ +Description: "cover condition: s.len() == 4" diff --git a/tests/expected/nondet-slice-len/main.rs b/tests/expected/nondet-slice-len/main.rs index 842a181b54be..cf4cb55fc89f 100644 --- a/tests/expected/nondet-slice-len/main.rs +++ b/tests/expected/nondet-slice-len/main.rs @@ -1,15 +1,16 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// This test checks that non-det slices created using kani::slice::any_slice can +// This test checks that non-det slices created using `kani::slice::any_slice_of_array` // assume any length up the specified maximum #[kani::proof] fn check_possible_slice_lengths() { - let s = kani::slice::any_slice::(); - assert!(s.len() != 0); - assert!(s.len() != 1); - assert!(s.len() != 2); - assert!(s.len() != 3); - assert!(s.len() != 4); + let arr: [i32; 4] = kani::any(); + let s = kani::slice::any_slice_of_array(&arr); + 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); } diff --git a/tests/expected/nondet-slice-u8-oob/main.rs b/tests/expected/nondet-slice-u8-oob/main.rs index 26df22279c05..fcebe171f7bd 100644 --- a/tests/expected/nondet-slice-u8-oob/main.rs +++ b/tests/expected/nondet-slice-u8-oob/main.rs @@ -2,12 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // This test checks that Kani reports out-of-bound accesses on a non-det slice -// created using kani::slice::any_slice +// created using `kani::slice::any_slice_of_array` #[kani::proof] fn check_out_of_bounds() { - let mut bytes = kani::slice::any_slice::(); - let val = unsafe { *bytes.get_slice().as_ptr().add(4) }; + let arr: [u8; 5] = kani::any(); + let mut bytes = kani::slice::any_slice_of_array(&arr); + let val = unsafe { *bytes.as_ptr().add(4) }; kani::assume(val != 0); assert_ne!(val, 0); } diff --git a/tests/expected/panic/arg-error/expected b/tests/expected/panic/arg-error/expected index 95567d5f7efb..655a016bc38e 100644 --- a/tests/expected/panic/arg-error/expected +++ b/tests/expected/panic/arg-error/expected @@ -1,2 +1,2 @@ error: 1 positional argument in format string, but no arguments were given -error: aborting due to previous error +error: aborting due to 1 previous error diff --git a/tests/expected/slice_c_str/c_str.rs b/tests/expected/slice_c_str/c_str_fixme.rs similarity index 89% rename from tests/expected/slice_c_str/c_str.rs rename to tests/expected/slice_c_str/c_str_fixme.rs index 9fa2feedc6ea..894746772100 100644 --- a/tests/expected/slice_c_str/c_str.rs +++ b/tests/expected/slice_c_str/c_str_fixme.rs @@ -1,6 +1,6 @@ #![feature(rustc_private)] #![feature(c_str_literals)] - +//! FIXME: extern crate libc; use libc::c_char; diff --git a/tests/kani/ArithOperators/rem_float_fixme.rs b/tests/kani/ArithOperators/rem_float_fixme.rs new file mode 100644 index 000000000000..42bd1fc8e532 --- /dev/null +++ b/tests/kani/ArithOperators/rem_float_fixme.rs @@ -0,0 +1,13 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// Checks that the remainder operator works with floating point values (see issue #2669) + +#[kani::proof] +fn rem_float() { + let dividend = 0.5 * f32::from(kani::any::()); + let divisor = 0.5 * f32::from(kani::any::()); + kani::assume(divisor != 0.0); + let result = dividend % divisor; + assert!(result == 0.0 || result.is_normal()); +} diff --git a/tests/kani/AsyncAwait/spawn.rs b/tests/kani/AsyncAwait/spawn.rs index 3bef244ef547..76583f10d615 100644 --- a/tests/kani/AsyncAwait/spawn.rs +++ b/tests/kani/AsyncAwait/spawn.rs @@ -11,9 +11,24 @@ use std::sync::{ Arc, }; +#[kani::proof(schedule = kani::RoundRobin::default())] +#[kani::unwind(4)] +async fn round_robin_schedule() { + let x = Arc::new(AtomicI64::new(0)); // Surprisingly, Arc verified faster than Rc + let x2 = x.clone(); + let x3 = x2.clone(); + let handle = kani::spawn(async move { + x3.fetch_add(1, Ordering::Relaxed); + }); + kani::yield_now().await; + x2.fetch_add(1, Ordering::Relaxed); + handle.await; + assert_eq!(x.load(Ordering::Relaxed), 2); +} + #[kani::proof] #[kani::unwind(4)] -fn round_robin_schedule() { +fn round_robin_schedule_manual() { let x = Arc::new(AtomicI64::new(0)); // Surprisingly, Arc verified faster than Rc let x2 = x.clone(); kani::block_on_with_spawn( diff --git a/tests/kani/BitwiseShiftOperators/shift_neg_vals.rs b/tests/kani/BitwiseShiftOperators/shift_neg_vals.rs new file mode 100644 index 000000000000..288698a0fbee --- /dev/null +++ b/tests/kani/BitwiseShiftOperators/shift_neg_vals.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// Ensure that shifting negative values yields the expected results. + +#[kani::proof] +#[kani::unwind(5)] +fn sheck_shl() { + let val: i32 = kani::any(); + let dist: u8 = kani::any(); + kani::assume(dist < 32); + assert_eq!(val << dist, val.wrapping_mul(2_i32.wrapping_pow(dist.into()))); +} + +#[kani::proof] +#[kani::unwind(5)] +fn check_shr() { + let val: i32 = kani::any(); + let dist: u8 = kani::any(); + kani::assume(dist < 32); + let result = (val as i64).div_euclid(2_i64.pow(dist.into())); + assert_eq!(val >> dist, result as i32); +} diff --git a/tests/kani/ConstEval/slices.rs b/tests/kani/ConstEval/slices.rs new file mode 100644 index 000000000000..16d422efe172 --- /dev/null +++ b/tests/kani/ConstEval/slices.rs @@ -0,0 +1,18 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Check that we can properly handle tuple constants including the ones with some padding. +#[kani::proof] +fn point_slice() { + let point: &[(u8, u32)] = &[(0, u32::MAX), (u8::MAX, 0)]; + assert_eq!(point.len(), 2); + assert_eq!(point[0].0, 0); + assert_eq!(point[0].1, u32::MAX); +} + +#[kani::proof] +fn points() { + let point: &[(u8, u8)] = &[(0, u8::MAX), (10, 231)]; + assert_eq!(point.len(), 2); + assert_eq!(point[0].0, 0); + assert_eq!(point[0].1, u8::MAX); +} diff --git a/tests/kani/Generator/issue-1593.rs b/tests/kani/Coroutines/issue-1593.rs similarity index 90% rename from tests/kani/Generator/issue-1593.rs rename to tests/kani/Coroutines/issue-1593.rs index c1ee4a8ddf31..9028dadad3d3 100644 --- a/tests/kani/Generator/issue-1593.rs +++ b/tests/kani/Coroutines/issue-1593.rs @@ -4,7 +4,7 @@ // compile-flags: --edition 2018 // Regression test for https://github.com/model-checking/kani/issues/1593 -// The problem was that the size of a generator was wrong, which was discovered +// The problem was that the size of a coroutine was wrong, which was discovered // in the context of vtables. use std::sync::{ diff --git a/tests/kani/Generator/issue-2434.rs b/tests/kani/Coroutines/issue-2434.rs similarity index 100% rename from tests/kani/Generator/issue-2434.rs rename to tests/kani/Coroutines/issue-2434.rs diff --git a/tests/kani/Generator/main.rs b/tests/kani/Coroutines/main.rs similarity index 64% rename from tests/kani/Generator/main.rs rename to tests/kani/Coroutines/main.rs index c9f6e9f51db9..10d92571aaa6 100644 --- a/tests/kani/Generator/main.rs +++ b/tests/kani/Coroutines/main.rs @@ -1,11 +1,11 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// This tests that generators work, even with a non-() resume type. +// This tests that coroutines work, even with a non-() resume type. -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] -use std::ops::{Generator, GeneratorState}; +use std::ops::{Coroutine, CoroutineState}; use std::pin::Pin; #[kani::proof] @@ -18,6 +18,6 @@ fn main() { for _ in 0..2 { let val = kani::any(); let res = Pin::new(&mut add_one).resume(val); - assert_eq!(res, GeneratorState::Yielded(val.saturating_add(1))); + assert_eq!(res, CoroutineState::Yielded(val.saturating_add(1))); } } diff --git a/tests/kani/Generator/rustc-generator-tests/conditional-drop.rs b/tests/kani/Coroutines/rustc-coroutine-tests/conditional-drop.rs similarity index 91% rename from tests/kani/Generator/rustc-generator-tests/conditional-drop.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/conditional-drop.rs index 7cd1f792858d..81036a8f1238 100644 --- a/tests/kani/Generator/rustc-generator-tests/conditional-drop.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/conditional-drop.rs @@ -1,5 +1,5 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/conditional-drop.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/conditional-drop.rs // SPDX-License-Identifier: Apache-2.0 OR MIT @@ -11,9 +11,9 @@ // revisions: default nomiropt //[nomiropt]compile-flags: -Z mir-opt-level=0 -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] -use std::ops::Generator; +use std::ops::Coroutine; use std::pin::Pin; use std::sync::atomic::{AtomicUsize, Ordering}; diff --git a/tests/kani/Generator/rustc-generator-tests/control-flow.rs b/tests/kani/Coroutines/rustc-coroutine-tests/control-flow.rs similarity index 78% rename from tests/kani/Generator/rustc-generator-tests/control-flow.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/control-flow.rs index 726801f9420b..6e48b96e1d2a 100644 --- a/tests/kani/Generator/rustc-generator-tests/control-flow.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/control-flow.rs @@ -1,5 +1,5 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/control-flow.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/control-flow.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // @@ -11,20 +11,20 @@ // revisions: default nomiropt //[nomiropt]compile-flags: -Z mir-opt-level=0 -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] use std::marker::Unpin; -use std::ops::{Generator, GeneratorState}; +use std::ops::{Coroutine, CoroutineState}; use std::pin::Pin; fn finish(mut amt: usize, mut t: T) -> T::Return where - T: Generator<(), Yield = ()> + Unpin, + T: Coroutine<(), Yield = ()> + Unpin, { loop { match Pin::new(&mut t).resume(()) { - GeneratorState::Yielded(()) => amt = amt.checked_sub(1).unwrap(), - GeneratorState::Complete(ret) => { + CoroutineState::Yielded(()) => amt = amt.checked_sub(1).unwrap(), + CoroutineState::Complete(ret) => { assert_eq!(amt, 0); return ret; } diff --git a/tests/kani/Generator/rustc-generator-tests/env-drop.rs b/tests/kani/Coroutines/rustc-coroutine-tests/env-drop.rs similarity index 91% rename from tests/kani/Generator/rustc-generator-tests/env-drop.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/env-drop.rs index 47a3f4a08fa5..a6420aca283b 100644 --- a/tests/kani/Generator/rustc-generator-tests/env-drop.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/env-drop.rs @@ -1,5 +1,5 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/env-drop.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/env-drop.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // @@ -11,9 +11,9 @@ // revisions: default nomiropt //[nomiropt]compile-flags: -Z mir-opt-level=0 -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] -use std::ops::Generator; +use std::ops::Coroutine; use std::pin::Pin; use std::sync::atomic::{AtomicUsize, Ordering}; diff --git a/tests/kani/Generator/rustc-generator-tests/iterator-count.rs b/tests/kani/Coroutines/rustc-coroutine-tests/iterator-count.rs similarity index 65% rename from tests/kani/Generator/rustc-generator-tests/iterator-count.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/iterator-count.rs index f4a7378cfad0..caa2efec216f 100644 --- a/tests/kani/Generator/rustc-generator-tests/iterator-count.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/iterator-count.rs @@ -1,5 +1,5 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/iterator-count.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/iterator-count.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // @@ -8,28 +8,28 @@ // run-pass -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] use std::marker::Unpin; -use std::ops::{Generator, GeneratorState}; +use std::ops::{Coroutine, CoroutineState}; use std::pin::Pin; struct W(T); -// This impl isn't safe in general, but the generator used in this test is movable +// This impl isn't safe in general, but the coroutine used in this test is movable // so it won't cause problems. -impl + Unpin> Iterator for W { +impl + Unpin> Iterator for W { type Item = T::Yield; fn next(&mut self) -> Option { match Pin::new(&mut self.0).resume(()) { - GeneratorState::Complete(..) => None, - GeneratorState::Yielded(v) => Some(v), + CoroutineState::Complete(..) => None, + CoroutineState::Yielded(v) => Some(v), } } } -fn test() -> impl Generator<(), Return = (), Yield = u8> + Unpin { +fn test() -> impl Coroutine<(), Return = (), Yield = u8> + Unpin { || { for i in 1..6 { yield i diff --git a/tests/kani/Generator/rustc-generator-tests/live-upvar-across-yield.rs b/tests/kani/Coroutines/rustc-coroutine-tests/live-upvar-across-yield.rs similarity index 76% rename from tests/kani/Generator/rustc-generator-tests/live-upvar-across-yield.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/live-upvar-across-yield.rs index df1f0c0eb3db..9bce8679464f 100644 --- a/tests/kani/Generator/rustc-generator-tests/live-upvar-across-yield.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/live-upvar-across-yield.rs @@ -1,5 +1,5 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/live-upvar-across-yield.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/live-upvar-across-yield.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // @@ -8,9 +8,9 @@ // run-pass -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] -use std::ops::Generator; +use std::ops::Coroutine; use std::pin::Pin; #[kani::proof] diff --git a/tests/kani/Generator/rustc-generator-tests/moved-locals-size.rs b/tests/kani/Coroutines/rustc-coroutine-tests/moved-locals-size.rs similarity index 81% rename from tests/kani/Generator/rustc-generator-tests/moved-locals-size.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/moved-locals-size.rs index 77cdd6ada955..d63d34427ca6 100644 --- a/tests/kani/Generator/rustc-generator-tests/moved-locals-size.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/moved-locals-size.rs @@ -1,13 +1,13 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/size-moved-locals.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/size-moved-locals.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // // Modifications Copyright Kani Contributors // See GitHub history for details. -//! Tests the size of generators -//! Note that the size of generators can depend on the panic strategy. +//! Tests the size of coroutines +//! Note that the size of coroutines can depend on the panic strategy. //! This is the case here (see the bottom of the file). //! In particular, running rustc with default options on this file will fail an assertion. //! Since Kani uses "panic=abort", you need to run rustc with `-C panic=abort` @@ -20,7 +20,7 @@ // `complex` below.) // // The exact sizes here can change (we'd like to know when they do). What we -// don't want to see is the `complex` generator size being upwards of 2048 bytes +// don't want to see is the `complex` coroutine size being upwards of 2048 bytes // (which would indicate it is reserving space for two copies of Foo.) // // See issue https://github.com/rust-lang/rust/issues/59123 for a full explanation. @@ -29,9 +29,9 @@ // ignore-wasm32 issue #62807 // ignore-asmjs issue #62807 -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] -use std::ops::Generator; +use std::ops::Coroutine; const FOO_SIZE: usize = 1024; struct Foo([u8; FOO_SIZE]); @@ -40,7 +40,7 @@ impl Drop for Foo { fn drop(&mut self) {} } -fn move_before_yield() -> impl Generator { +fn move_before_yield() -> impl Coroutine { static || { let first = Foo([0; FOO_SIZE]); let _second = first; @@ -51,7 +51,7 @@ fn move_before_yield() -> impl Generator { fn noop() {} -fn move_before_yield_with_noop() -> impl Generator { +fn move_before_yield_with_noop() -> impl Coroutine { static || { let first = Foo([0; FOO_SIZE]); noop(); @@ -63,7 +63,7 @@ fn move_before_yield_with_noop() -> impl Generator { // Today we don't have NRVO (we allocate space for both `first` and `second`,) // but we can overlap `first` with `_third`. -fn overlap_move_points() -> impl Generator { +fn overlap_move_points() -> impl Coroutine { static || { let first = Foo([0; FOO_SIZE]); yield; @@ -74,7 +74,7 @@ fn overlap_move_points() -> impl Generator { } } -fn overlap_x_and_y() -> impl Generator { +fn overlap_x_and_y() -> impl Coroutine { static || { let x = Foo([0; FOO_SIZE]); yield; diff --git a/tests/kani/Generator/rustc-generator-tests/moved-locals.rs b/tests/kani/Coroutines/rustc-coroutine-tests/moved-locals.rs similarity index 52% rename from tests/kani/Generator/rustc-generator-tests/moved-locals.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/moved-locals.rs index d5a9469be4e7..b3b6c4cc6767 100644 --- a/tests/kani/Generator/rustc-generator-tests/moved-locals.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/moved-locals.rs @@ -1,5 +1,5 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/size-moved-locals.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/size-moved-locals.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // @@ -12,7 +12,7 @@ // `complex` below.) // // The exact sizes here can change (we'd like to know when they do). What we -// don't want to see is the `complex` generator size being upwards of 2048 bytes +// don't want to see is the `complex` coroutine size being upwards of 2048 bytes // (which would indicate it is reserving space for two copies of Foo.) // // See issue #59123 for a full explanation. @@ -21,9 +21,9 @@ // ignore-wasm32 issue #62807 // ignore-asmjs issue #62807 -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] -use std::ops::{Generator, GeneratorState}; +use std::ops::{Coroutine, CoroutineState}; use std::pin::Pin; const FOO_SIZE: usize = 128; @@ -33,7 +33,7 @@ impl Drop for Foo { fn drop(&mut self) {} } -fn move_before_yield() -> impl Generator { +fn move_before_yield() -> impl Coroutine { static || { let first = Foo([0; FOO_SIZE]); let _second = first; @@ -44,7 +44,7 @@ fn move_before_yield() -> impl Generator { fn noop() {} -fn move_before_yield_with_noop() -> impl Generator { +fn move_before_yield_with_noop() -> impl Coroutine { static || { let first = Foo([0; FOO_SIZE]); noop(); @@ -56,7 +56,7 @@ fn move_before_yield_with_noop() -> impl Generator { // Today we don't have NRVO (we allocate space for both `first` and `second`,) // but we can overlap `first` with `_third`. -fn overlap_move_points() -> impl Generator { +fn overlap_move_points() -> impl Coroutine { static || { let first = Foo([0; FOO_SIZE]); yield; @@ -67,7 +67,7 @@ fn overlap_move_points() -> impl Generator { } } -fn overlap_x_and_y() -> impl Generator { +fn overlap_x_and_y() -> impl Coroutine { static || { let x = Foo([0; FOO_SIZE]); yield; @@ -80,55 +80,55 @@ fn overlap_x_and_y() -> impl Generator { #[kani::proof] fn main() { - let mut generator = move_before_yield(); + let mut coroutine = move_before_yield(); assert_eq!( - unsafe { Pin::new_unchecked(&mut generator) }.resume(()), - GeneratorState::Yielded(()) + unsafe { Pin::new_unchecked(&mut coroutine) }.resume(()), + CoroutineState::Yielded(()) ); assert_eq!( - unsafe { Pin::new_unchecked(&mut generator) }.resume(()), - GeneratorState::Complete(()) + unsafe { Pin::new_unchecked(&mut coroutine) }.resume(()), + CoroutineState::Complete(()) ); - let mut generator = move_before_yield_with_noop(); + let mut coroutine = move_before_yield_with_noop(); assert_eq!( - unsafe { Pin::new_unchecked(&mut generator) }.resume(()), - GeneratorState::Yielded(()) + unsafe { Pin::new_unchecked(&mut coroutine) }.resume(()), + CoroutineState::Yielded(()) ); assert_eq!( - unsafe { Pin::new_unchecked(&mut generator) }.resume(()), - GeneratorState::Complete(()) + unsafe { Pin::new_unchecked(&mut coroutine) }.resume(()), + CoroutineState::Complete(()) ); - let mut generator = overlap_move_points(); + let mut coroutine = overlap_move_points(); assert_eq!( - unsafe { Pin::new_unchecked(&mut generator) }.resume(()), - GeneratorState::Yielded(()) + unsafe { Pin::new_unchecked(&mut coroutine) }.resume(()), + CoroutineState::Yielded(()) ); assert_eq!( - unsafe { Pin::new_unchecked(&mut generator) }.resume(()), - GeneratorState::Yielded(()) + unsafe { Pin::new_unchecked(&mut coroutine) }.resume(()), + CoroutineState::Yielded(()) ); assert_eq!( - unsafe { Pin::new_unchecked(&mut generator) }.resume(()), - GeneratorState::Yielded(()) + unsafe { Pin::new_unchecked(&mut coroutine) }.resume(()), + CoroutineState::Yielded(()) ); assert_eq!( - unsafe { Pin::new_unchecked(&mut generator) }.resume(()), - GeneratorState::Complete(()) + unsafe { Pin::new_unchecked(&mut coroutine) }.resume(()), + CoroutineState::Complete(()) ); - let mut generator = overlap_x_and_y(); + let mut coroutine = overlap_x_and_y(); assert_eq!( - unsafe { Pin::new_unchecked(&mut generator) }.resume(()), - GeneratorState::Yielded(()) + unsafe { Pin::new_unchecked(&mut coroutine) }.resume(()), + CoroutineState::Yielded(()) ); assert_eq!( - unsafe { Pin::new_unchecked(&mut generator) }.resume(()), - GeneratorState::Yielded(()) + unsafe { Pin::new_unchecked(&mut coroutine) }.resume(()), + CoroutineState::Yielded(()) ); assert_eq!( - unsafe { Pin::new_unchecked(&mut generator) }.resume(()), - GeneratorState::Complete(()) + unsafe { Pin::new_unchecked(&mut coroutine) }.resume(()), + CoroutineState::Complete(()) ); } diff --git a/tests/kani/Generator/rustc-generator-tests/nested-generators.rs b/tests/kani/Coroutines/rustc-coroutine-tests/nested-generators.rs similarity index 55% rename from tests/kani/Generator/rustc-generator-tests/nested-generators.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/nested-generators.rs index 31ab9a47daa6..0d770380e2b9 100644 --- a/tests/kani/Generator/rustc-generator-tests/nested-generators.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/nested-generators.rs @@ -1,5 +1,5 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/nested-generators.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/nested-coroutines.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // @@ -8,20 +8,20 @@ // run-pass -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] -use std::ops::{Generator, GeneratorState}; +use std::ops::{Coroutine, CoroutineState}; use std::pin::Pin; #[kani::proof] fn main() { - let _generator = || { - let mut sub_generator = || { + let _coroutine = || { + let mut sub_coroutine = || { yield 2; }; - match Pin::new(&mut sub_generator).resume(()) { - GeneratorState::Yielded(x) => { + match Pin::new(&mut sub_coroutine).resume(()) { + CoroutineState::Yielded(x) => { yield x; } _ => panic!(), diff --git a/tests/kani/Generator/rustc-generator-tests/niche-in-generator-size.rs b/tests/kani/Coroutines/rustc-coroutine-tests/niche-in-generator-size.rs similarity index 68% rename from tests/kani/Generator/rustc-generator-tests/niche-in-generator-size.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/niche-in-generator-size.rs index 6796fd3a1a26..5de21166c318 100644 --- a/tests/kani/Generator/rustc-generator-tests/niche-in-generator-size.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/niche-in-generator-size.rs @@ -1,16 +1,16 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/niche-in-generator.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/niche-in-coroutine.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // // Modifications Copyright Kani Contributors // See GitHub history for details. -// Test that niche finding works with captured generator upvars. +// Test that niche finding works with captured coroutine upvars. // run-pass -#![feature(generators)] +#![feature(coroutines)] use std::mem::size_of_val; @@ -24,6 +24,6 @@ fn main() { take(x); }; - // FIXME: size of generators does not work reliably (https://github.com/model-checking/kani/issues/1395) + // FIXME: size of coroutines does not work reliably (https://github.com/model-checking/kani/issues/1395) assert_eq!(size_of_val(&gen1), size_of_val(&Some(gen1))); } diff --git a/tests/kani/Generator/rustc-generator-tests/niche-in-generator.rs b/tests/kani/Coroutines/rustc-coroutine-tests/niche-in-generator.rs similarity index 55% rename from tests/kani/Generator/rustc-generator-tests/niche-in-generator.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/niche-in-generator.rs index 6e09b0967032..170a356fb318 100644 --- a/tests/kani/Generator/rustc-generator-tests/niche-in-generator.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/niche-in-generator.rs @@ -1,18 +1,18 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/niche-in-generator.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/niche-in-coroutine.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // // Modifications Copyright Kani Contributors // See GitHub history for details. -// Test that niche finding works with captured generator upvars. +// Test that niche finding works with captured coroutine upvars. // run-pass -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] -use std::ops::{Generator, GeneratorState}; +use std::ops::{Coroutine, CoroutineState}; use std::pin::Pin; use std::mem::size_of_val; @@ -27,6 +27,6 @@ fn main() { take(x); }; - assert_eq!(Pin::new(&mut gen1).resume(()), GeneratorState::Yielded(())); - assert_eq!(Pin::new(&mut gen1).resume(()), GeneratorState::Complete(())); + assert_eq!(Pin::new(&mut gen1).resume(()), CoroutineState::Yielded(())); + assert_eq!(Pin::new(&mut gen1).resume(()), CoroutineState::Complete(())); } diff --git a/tests/kani/Generator/rustc-generator-tests/overlap-locals-size.rs b/tests/kani/Coroutines/rustc-coroutine-tests/overlap-locals-size.rs similarity index 84% rename from tests/kani/Generator/rustc-generator-tests/overlap-locals-size.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/overlap-locals-size.rs index 6043d9024744..d22ed53f8b60 100644 --- a/tests/kani/Generator/rustc-generator-tests/overlap-locals-size.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/overlap-locals-size.rs @@ -1,5 +1,5 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/overlap-locals.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/overlap-locals.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // @@ -8,7 +8,7 @@ // run-pass -#![feature(generators)] +#![feature(coroutines)] #[kani::proof] fn main() { @@ -35,6 +35,6 @@ fn main() { } }; - // FIXME: size of generators does not work reliably (https://github.com/model-checking/kani/issues/1395) + // FIXME: size of coroutines does not work reliably (https://github.com/model-checking/kani/issues/1395) assert_eq!(8, std::mem::size_of_val(&a)); } diff --git a/tests/kani/Generator/rustc-generator-tests/overlap-locals.rs b/tests/kani/Coroutines/rustc-coroutine-tests/overlap-locals.rs similarity index 58% rename from tests/kani/Generator/rustc-generator-tests/overlap-locals.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/overlap-locals.rs index 319e5d429842..6033b2f06a99 100644 --- a/tests/kani/Generator/rustc-generator-tests/overlap-locals.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/overlap-locals.rs @@ -1,5 +1,5 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/overlap-locals.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/overlap-locals.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // @@ -8,9 +8,9 @@ // run-pass -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] -use std::ops::{Generator, GeneratorState}; +use std::ops::{Coroutine, CoroutineState}; use std::pin::Pin; #[kani::proof] @@ -34,9 +34,9 @@ fn main() { } }; - assert_eq!(Pin::new(&mut a).resume(()), GeneratorState::Yielded(())); - assert_eq!(Pin::new(&mut a).resume(()), GeneratorState::Yielded(())); - assert_eq!(Pin::new(&mut a).resume(()), GeneratorState::Yielded(())); - assert_eq!(Pin::new(&mut a).resume(()), GeneratorState::Yielded(())); - assert_eq!(Pin::new(&mut a).resume(()), GeneratorState::Complete(())); + assert_eq!(Pin::new(&mut a).resume(()), CoroutineState::Yielded(())); + assert_eq!(Pin::new(&mut a).resume(()), CoroutineState::Yielded(())); + assert_eq!(Pin::new(&mut a).resume(()), CoroutineState::Yielded(())); + assert_eq!(Pin::new(&mut a).resume(()), CoroutineState::Yielded(())); + assert_eq!(Pin::new(&mut a).resume(()), CoroutineState::Complete(())); } diff --git a/tests/kani/Generator/rustc-generator-tests/resume-arg-size.rs b/tests/kani/Coroutines/rustc-coroutine-tests/resume-arg-size.rs similarity index 70% rename from tests/kani/Generator/rustc-generator-tests/resume-arg-size.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/resume-arg-size.rs index 70318ebfb6d0..f59ef260bcda 100644 --- a/tests/kani/Generator/rustc-generator-tests/resume-arg-size.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/resume-arg-size.rs @@ -1,12 +1,12 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/resume-arg-size.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/resume-arg-size.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // // Modifications Copyright Kani Contributors // See GitHub history for details. -#![feature(generators)] +#![feature(coroutines)] // run-pass @@ -14,7 +14,7 @@ use std::mem::size_of_val; #[kani::proof] fn main() { - // Generator taking a `Copy`able resume arg. + // Coroutine taking a `Copy`able resume arg. let gen_copy = |mut x: usize| { loop { drop(x); @@ -22,7 +22,7 @@ fn main() { } }; - // Generator taking a non-`Copy` resume arg. + // Coroutine taking a non-`Copy` resume arg. let gen_move = |mut x: Box| { loop { drop(x); @@ -30,9 +30,9 @@ fn main() { } }; - // Neither of these generators have the resume arg live across the `yield`, so they should be + // Neither of these coroutines have the resume arg live across the `yield`, so they should be // 1 Byte in size (only storing the discriminant) - // FIXME: size of generators does not work reliably (https://github.com/model-checking/kani/issues/1395) + // FIXME: size of coroutines does not work reliably (https://github.com/model-checking/kani/issues/1395) assert_eq!(size_of_val(&gen_copy), 1); assert_eq!(size_of_val(&gen_move), 1); } diff --git a/tests/kani/Generator/rustc-generator-tests/resume-arg.rs b/tests/kani/Coroutines/rustc-coroutine-tests/resume-arg.rs similarity index 52% rename from tests/kani/Generator/rustc-generator-tests/resume-arg.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/resume-arg.rs index b2237a8bb96d..0c2c87f5eb2e 100644 --- a/tests/kani/Generator/rustc-generator-tests/resume-arg.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/resume-arg.rs @@ -1,14 +1,14 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/resume-arg-size.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/resume-arg-size.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // // Modifications Copyright Kani Contributors // See GitHub history for details. -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] -use std::ops::{Generator, GeneratorState}; +use std::ops::{Coroutine, CoroutineState}; use std::pin::Pin; // run-pass @@ -17,7 +17,7 @@ use std::mem::size_of_val; #[kani::proof] fn main() { - // Generator taking a `Copy`able resume arg. + // Coroutine taking a `Copy`able resume arg. let mut gen_copy = |mut x: usize| { loop { drop(x); @@ -25,7 +25,7 @@ fn main() { } }; - // Generator taking a non-`Copy` resume arg. + // Coroutine taking a non-`Copy` resume arg. let mut gen_move = |mut x: Box| { loop { drop(x); @@ -33,9 +33,9 @@ fn main() { } }; - assert_eq!(Pin::new(&mut gen_copy).resume(0), GeneratorState::Yielded(())); - assert_eq!(Pin::new(&mut gen_copy).resume(1), GeneratorState::Yielded(())); + assert_eq!(Pin::new(&mut gen_copy).resume(0), CoroutineState::Yielded(())); + assert_eq!(Pin::new(&mut gen_copy).resume(1), CoroutineState::Yielded(())); - assert_eq!(Pin::new(&mut gen_move).resume(Box::new(0)), GeneratorState::Yielded(())); - assert_eq!(Pin::new(&mut gen_move).resume(Box::new(1)), GeneratorState::Yielded(())); + assert_eq!(Pin::new(&mut gen_move).resume(Box::new(0)), CoroutineState::Yielded(())); + assert_eq!(Pin::new(&mut gen_move).resume(Box::new(1)), CoroutineState::Yielded(())); } diff --git a/tests/kani/Generator/rustc-generator-tests/resume-live-across-yield.rs b/tests/kani/Coroutines/rustc-coroutine-tests/resume-live-across-yield.rs similarity index 83% rename from tests/kani/Generator/rustc-generator-tests/resume-live-across-yield.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/resume-live-across-yield.rs index d515a1acc04e..10d4d36223d5 100644 --- a/tests/kani/Generator/rustc-generator-tests/resume-live-across-yield.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/resume-live-across-yield.rs @@ -1,5 +1,5 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/resume-live-across-yield.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/resume-live-across-yield.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // @@ -8,9 +8,9 @@ // run-pass -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] -use std::ops::{Generator, GeneratorState}; +use std::ops::{Coroutine, CoroutineState}; use std::pin::Pin; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -37,11 +37,11 @@ fn main() { assert_eq!( g.as_mut().resume(Dropper(String::from("Hello world!"))), - GeneratorState::Yielded(()) + CoroutineState::Yielded(()) ); assert_eq!(DROP.load(Ordering::Acquire), 0); match g.as_mut().resume(Dropper(String::from("Number Two"))) { - GeneratorState::Complete(dropper) => { + CoroutineState::Complete(dropper) => { assert_eq!(DROP.load(Ordering::Acquire), 1); assert_eq!(dropper.0, "Number Two"); drop(dropper); diff --git a/tests/kani/Generator/rustc-generator-tests/smoke-resume-args.rs b/tests/kani/Coroutines/rustc-coroutine-tests/smoke-resume-args.rs similarity index 90% rename from tests/kani/Generator/rustc-generator-tests/smoke-resume-args.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/smoke-resume-args.rs index 26882285469a..85c75bc8147d 100644 --- a/tests/kani/Generator/rustc-generator-tests/smoke-resume-args.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/smoke-resume-args.rs @@ -1,5 +1,5 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/smoke-resume-args.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/smoke-resume-args.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // @@ -11,20 +11,20 @@ // revisions: default nomiropt //[nomiropt]compile-flags: -Z mir-opt-level=0 -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] use std::fmt::Debug; use std::marker::Unpin; use std::ops::{ - Generator, - GeneratorState::{self, *}, + Coroutine, + CoroutineState::{self, *}, }; use std::pin::Pin; use std::sync::atomic::{AtomicUsize, Ordering}; -fn drain + Unpin, R, Y>( +fn drain + Unpin, R, Y>( gen: &mut G, - inout: Vec<(R, GeneratorState)>, + inout: Vec<(R, CoroutineState)>, ) where Y: Debug + PartialEq, G::Return: Debug + PartialEq, diff --git a/tests/kani/Generator/rustc-generator-tests/smoke.rs b/tests/kani/Coroutines/rustc-coroutine-tests/smoke.rs similarity index 80% rename from tests/kani/Generator/rustc-generator-tests/smoke.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/smoke.rs index 888ec969877b..2b9aa40a06c0 100644 --- a/tests/kani/Generator/rustc-generator-tests/smoke.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/smoke.rs @@ -1,5 +1,5 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/smoke.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/smoke.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // @@ -14,9 +14,9 @@ // ignore-emscripten no threads support // compile-flags: --test -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] -use std::ops::{Generator, GeneratorState}; +use std::ops::{Coroutine, CoroutineState}; use std::pin::Pin; use std::thread; @@ -29,7 +29,7 @@ fn simple() { }; match Pin::new(&mut foo).resume(()) { - GeneratorState::Complete(()) => {} + CoroutineState::Complete(()) => {} s => panic!("bad state: {:?}", s), } } @@ -46,7 +46,7 @@ fn return_capture() { }; match Pin::new(&mut foo).resume(()) { - GeneratorState::Complete(ref s) if *s == "foo" => {} + CoroutineState::Complete(ref s) if *s == "foo" => {} s => panic!("bad state: {:?}", s), } } @@ -58,11 +58,11 @@ fn simple_yield() { }; match Pin::new(&mut foo).resume(()) { - GeneratorState::Yielded(()) => {} + CoroutineState::Yielded(()) => {} s => panic!("bad state: {:?}", s), } match Pin::new(&mut foo).resume(()) { - GeneratorState::Complete(()) => {} + CoroutineState::Complete(()) => {} s => panic!("bad state: {:?}", s), } } @@ -76,11 +76,11 @@ fn yield_capture() { }; match Pin::new(&mut foo).resume(()) { - GeneratorState::Yielded(ref s) if *s == "foo" => {} + CoroutineState::Yielded(ref s) if *s == "foo" => {} s => panic!("bad state: {:?}", s), } match Pin::new(&mut foo).resume(()) { - GeneratorState::Complete(()) => {} + CoroutineState::Complete(()) => {} s => panic!("bad state: {:?}", s), } } @@ -94,11 +94,11 @@ fn simple_yield_value() { }; match Pin::new(&mut foo).resume(()) { - GeneratorState::Yielded(ref s) if *s == "bar" => {} + CoroutineState::Yielded(ref s) if *s == "bar" => {} s => panic!("bad state: {:?}", s), } match Pin::new(&mut foo).resume(()) { - GeneratorState::Complete(ref s) if *s == "foo" => {} + CoroutineState::Complete(ref s) if *s == "foo" => {} s => panic!("bad state: {:?}", s), } } @@ -113,11 +113,11 @@ fn return_after_yield() { }; match Pin::new(&mut foo).resume(()) { - GeneratorState::Yielded(()) => {} + CoroutineState::Yielded(()) => {} s => panic!("bad state: {:?}", s), } match Pin::new(&mut foo).resume(()) { - GeneratorState::Complete(ref s) if *s == "foo" => {} + CoroutineState::Complete(ref s) if *s == "foo" => {} s => panic!("bad state: {:?}", s), } } @@ -163,11 +163,11 @@ fn send_over_threads() { let mut foo = || yield; thread::spawn(move || { match Pin::new(&mut foo).resume(()) { - GeneratorState::Yielded(()) => {} + CoroutineState::Yielded(()) => {} s => panic!("bad state: {:?}", s), } match Pin::new(&mut foo).resume(()) { - GeneratorState::Complete(()) => {} + CoroutineState::Complete(()) => {} s => panic!("bad state: {:?}", s), } }) @@ -178,11 +178,11 @@ fn send_over_threads() { let mut foo = || yield a; thread::spawn(move || { match Pin::new(&mut foo).resume(()) { - GeneratorState::Yielded(ref s) if *s == "a" => {} + CoroutineState::Yielded(ref s) if *s == "a" => {} s => panic!("bad state: {:?}", s), } match Pin::new(&mut foo).resume(()) { - GeneratorState::Complete(()) => {} + CoroutineState::Complete(()) => {} s => panic!("bad state: {:?}", s), } }) diff --git a/tests/kani/Generator/rustc-generator-tests/static-generator.rs b/tests/kani/Coroutines/rustc-coroutine-tests/static-generator.rs similarity index 50% rename from tests/kani/Generator/rustc-generator-tests/static-generator.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/static-generator.rs index 519e00f29afa..52f89438255a 100644 --- a/tests/kani/Generator/rustc-generator-tests/static-generator.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/static-generator.rs @@ -1,5 +1,5 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/static-generator.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/static-coroutine.rs // // SPDX-License-Identifier: Apache-2.0 OR MIT // @@ -8,22 +8,22 @@ // run-pass -#![feature(generators, generator_trait)] +#![feature(coroutines, coroutine_trait)] -use std::ops::{Generator, GeneratorState}; +use std::ops::{Coroutine, CoroutineState}; use std::pin::Pin; #[kani::proof] fn main() { - let mut generator = static || { + let mut coroutine = static || { let a = true; let b = &a; yield; assert_eq!(b as *const _, &a as *const _); }; - // SAFETY: We shadow the original generator variable so have no safe API to + // SAFETY: We shadow the original coroutine variable so have no safe API to // move it after this point. - let mut generator = unsafe { Pin::new_unchecked(&mut generator) }; - assert_eq!(generator.as_mut().resume(()), GeneratorState::Yielded(())); - assert_eq!(generator.as_mut().resume(()), GeneratorState::Complete(())); + let mut coroutine = unsafe { Pin::new_unchecked(&mut coroutine) }; + assert_eq!(coroutine.as_mut().resume(()), CoroutineState::Yielded(())); + assert_eq!(coroutine.as_mut().resume(()), CoroutineState::Complete(())); } diff --git a/tests/kani/Generator/rustc-generator-tests/yield-in-box.rs b/tests/kani/Coroutines/rustc-coroutine-tests/yield-in-box.rs similarity index 64% rename from tests/kani/Generator/rustc-generator-tests/yield-in-box.rs rename to tests/kani/Coroutines/rustc-coroutine-tests/yield-in-box.rs index 79690d9140c1..5dbf580f5f57 100644 --- a/tests/kani/Generator/rustc-generator-tests/yield-in-box.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/yield-in-box.rs @@ -1,5 +1,5 @@ // Copyright rustc Contributors -// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/generator/yield-in-box.rs +// Adapted from rustc: https://github.com/rust-lang/rust/tree/5f98537eb7b5f42c246a52c550813c3cff336069/src/test/ui/coroutine/yield-in-box.rs // SPDX-License-Identifier: Apache-2.0 OR MIT // @@ -9,16 +9,16 @@ // run-pass // Test that box-statements with yields in them work. -#![feature(generators, generator_trait)] -use std::ops::Generator; -use std::ops::GeneratorState; +#![feature(coroutines, coroutine_trait)] +use std::ops::Coroutine; +use std::ops::CoroutineState; use std::pin::Pin; #[kani::proof] fn main() { let x = 0i32; || { - //~ WARN unused generator that must be used + //~ WARN unused coroutine that must be used let y = 2u32; { let _t = Box::new((&x, yield 0, &y)); @@ -29,6 +29,6 @@ fn main() { }; let mut g = |_| Box::new(yield); - assert_eq!(Pin::new(&mut g).resume(1), GeneratorState::Yielded(())); - assert_eq!(Pin::new(&mut g).resume(2), GeneratorState::Complete(Box::new(2))); + assert_eq!(Pin::new(&mut g).resume(1), CoroutineState::Yielded(())); + assert_eq!(Pin::new(&mut g).resume(2), CoroutineState::Complete(Box::new(2))); } diff --git a/tests/kani/Intrinsics/ConstEval/pref_align_of.rs b/tests/kani/Intrinsics/ConstEval/pref_align_of.rs index 3a571faace68..22ff342a8198 100644 --- a/tests/kani/Intrinsics/ConstEval/pref_align_of.rs +++ b/tests/kani/Intrinsics/ConstEval/pref_align_of.rs @@ -41,13 +41,25 @@ fn main() { #[cfg(target_arch = "aarch64")] { // Scalar types + #[cfg(target_os = "linux")] + assert!(unsafe { pref_align_of::() } == 4); + #[cfg(target_os = "macos")] assert!(unsafe { pref_align_of::() } == 1); + #[cfg(target_os = "linux")] + assert!(unsafe { pref_align_of::() } == 4); + #[cfg(target_os = "macos")] assert!(unsafe { pref_align_of::() } == 2); assert!(unsafe { pref_align_of::() } == 4); assert!(unsafe { pref_align_of::() } == 8); assert!(unsafe { pref_align_of::() } == 16); assert!(unsafe { pref_align_of::() } == 8); + #[cfg(target_os = "linux")] + assert!(unsafe { pref_align_of::() } == 4); + #[cfg(target_os = "macos")] assert!(unsafe { pref_align_of::() } == 1); + #[cfg(target_os = "linux")] + assert!(unsafe { pref_align_of::() } == 4); + #[cfg(target_os = "macos")] assert!(unsafe { pref_align_of::() } == 2); assert!(unsafe { pref_align_of::() } == 4); assert!(unsafe { pref_align_of::() } == 8); @@ -55,6 +67,9 @@ fn main() { assert!(unsafe { pref_align_of::() } == 8); assert!(unsafe { pref_align_of::() } == 4); assert!(unsafe { pref_align_of::() } == 8); + #[cfg(target_os = "linux")] + assert!(unsafe { pref_align_of::() } == 4); + #[cfg(target_os = "macos")] assert!(unsafe { pref_align_of::() } == 1); assert!(unsafe { pref_align_of::() } == 4); // Compound types (tuple and array) @@ -62,6 +77,9 @@ fn main() { assert!(unsafe { pref_align_of::<[i32; 5]>() } == 4); // Custom data types (struct and enum) assert!(unsafe { pref_align_of::() } == 8); + #[cfg(target_os = "linux")] + assert!(unsafe { pref_align_of::() } == 4); + #[cfg(target_os = "macos")] assert!(unsafe { pref_align_of::() } == 1); } } diff --git a/tests/kani/Intrinsics/SIMD/Operators/bitmask.rs b/tests/kani/Intrinsics/SIMD/Operators/bitmask.rs new file mode 100644 index 000000000000..4d3293264b6e --- /dev/null +++ b/tests/kani/Intrinsics/SIMD/Operators/bitmask.rs @@ -0,0 +1,59 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Checks that we support `simd_bitmask` intrinsic. +//! +//! This is done by initializing vectors with the contents of 2-member tuples +//! with symbolic values. The result of using each of the intrinsics is compared +//! against the result of using the associated bitwise operator on the tuples. +#![feature(repr_simd, platform_intrinsics)] +#![feature(generic_const_exprs)] +#![feature(portable_simd)] +#![feature(core_intrinsics)] + +use std::fmt::Debug; + +extern "platform-intrinsic" { + fn simd_bitmask(x: T) -> U; +} + +#[repr(simd)] +#[derive(Clone, Debug)] +struct CustomMask([i32; LANES]); + +impl CustomMask { + fn as_array(&self) -> [i32; LANES] { + unsafe { *(&self.clone() as *const Self as *const [i32; LANES]) } + } +} + +impl kani::Arbitrary for CustomMask +where + [bool; LANES]: Sized + kani::Arbitrary, +{ + fn any() -> Self { + CustomMask(kani::any::<[bool; LANES]>().map(|v| if v { 0i32 } else { -1 })) + } +} + +#[kani::proof] +fn check_u8() { + let (true_lane, false_lane) = (-1, 0); + + // This should be the mask 0b1101 for little endian machines. + let input = CustomMask([true_lane, false_lane, true_lane, true_lane]); + let mask = unsafe { simd_bitmask::<_, u8>(input) }; + assert_eq!(mask, 0b1101); + + let input = CustomMask([true_lane; 25]); + let mask = unsafe { simd_bitmask::<_, u32>(input) }; + assert_eq!(mask, 0b1111111111111111111111111); +} + +#[kani::proof] +fn check_unsigned_bitmask() { + let mask = kani::any::>(); + let bitmask = unsafe { simd_bitmask::<_, u8>(mask.clone()) }; + assert_eq!(bitmask.count_ones() as usize, mask.as_array().iter().filter(|e| **e == -1).count()); + assert_eq!(bitmask.count_zeros() as usize, mask.as_array().iter().filter(|e| **e == 0).count()); +} diff --git a/tests/kani/Intrinsics/SIMD/Operators/bitshift.rs b/tests/kani/Intrinsics/SIMD/Operators/bitshift.rs index cd2c7ceb756a..1041f918123f 100644 --- a/tests/kani/Intrinsics/SIMD/Operators/bitshift.rs +++ b/tests/kani/Intrinsics/SIMD/Operators/bitshift.rs @@ -23,7 +23,6 @@ extern "platform-intrinsic" { #[kani::proof] fn test_simd_shl() { let value = kani::any(); - kani::assume(value >= 0); let values = i32x2(value, value); let shift = kani::any(); kani::assume(shift >= 0); diff --git a/tests/kani/Intrinsics/SIMD/Operators/division_float.rs b/tests/kani/Intrinsics/SIMD/Operators/division_float.rs new file mode 100644 index 000000000000..97ea2e6e7ca7 --- /dev/null +++ b/tests/kani/Intrinsics/SIMD/Operators/division_float.rs @@ -0,0 +1,39 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Checks that the `simd_div` intrinsic returns the expected results for floating point numbers. +#![feature(repr_simd, platform_intrinsics)] + +#[repr(simd)] +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, PartialEq, kani::Arbitrary)] +pub struct f32x2(f32, f32); + +impl f32x2 { + fn new_with(f: impl Fn() -> f32) -> Self { + f32x2(f(), f()) + } + + fn non_simd_div(self, divisors: Self) -> Self { + f32x2(self.0 / divisors.0, self.1 / divisors.1) + } +} + +extern "platform-intrinsic" { + fn simd_div(x: T, y: T) -> T; +} + +#[kani::proof] +fn test_simd_div() { + let dividends = f32x2::new_with(|| { + let multiplier = kani::any_where(|&n: &i8| n >= -5 && n <= 5); + 0.5 * f32::from(multiplier) + }); + let divisors = f32x2::new_with(|| { + let multiplier = kani::any_where(|&n: &i8| n != 0 && n >= -5 && n <= 5); + 0.5 * f32::from(multiplier) + }); + let normal_results = dividends.non_simd_div(divisors); + let simd_results = unsafe { simd_div(dividends, divisors) }; + assert_eq!(normal_results, simd_results); +} diff --git a/tests/kani/Intrinsics/SIMD/Operators/remainder_float_fixme.rs b/tests/kani/Intrinsics/SIMD/Operators/remainder_float_fixme.rs new file mode 100644 index 000000000000..c97e96896bcf --- /dev/null +++ b/tests/kani/Intrinsics/SIMD/Operators/remainder_float_fixme.rs @@ -0,0 +1,39 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Checks that the `simd_rem` intrinsic returns the expected results for floating point numbers. +#![feature(repr_simd, platform_intrinsics)] + +#[repr(simd)] +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, PartialEq, kani::Arbitrary)] +pub struct f32x2(f32, f32); + +impl f32x2 { + fn new_with(f: impl Fn() -> f32) -> Self { + f32x2(f(), f()) + } + + fn non_simd_rem(self, divisors: Self) -> Self { + f32x2(self.0 % divisors.0, self.1 % divisors.1) + } +} + +extern "platform-intrinsic" { + fn simd_rem(x: T, y: T) -> T; +} + +#[kani::proof] +fn test_simd_rem() { + let dividends = f32x2::new_with(|| { + let multiplier = kani::any_where(|&n: &i8| n >= -5 && n <= 5); + 0.5 * f32::from(multiplier) + }); + let divisors = f32x2::new_with(|| { + let multiplier = kani::any_where(|&n: &i8| n != 0 && n >= -5 && n <= 5); + 0.5 * f32::from(multiplier) + }); + let normal_results = dividends.non_simd_rem(divisors); + let simd_results = unsafe { simd_rem(dividends, divisors) }; + assert_eq!(normal_results, simd_results); +} diff --git a/tests/kani/Intrinsics/SIMD/Shuffle/main.rs b/tests/kani/Intrinsics/SIMD/Shuffle/main.rs index 7195ebe92351..6d822a18bdc1 100644 --- a/tests/kani/Intrinsics/SIMD/Shuffle/main.rs +++ b/tests/kani/Intrinsics/SIMD/Shuffle/main.rs @@ -17,8 +17,6 @@ pub struct i64x4(i64, i64, i64, i64); extern "platform-intrinsic" { fn simd_shuffle(x: T, y: T, idx: U) -> V; - fn simd_shuffle2(x: T, y: T, idx: [u32; 2]) -> U; - fn simd_shuffle4(x: T, y: T, idx: [u32; 4]) -> U; } #[kani::proof] @@ -35,7 +33,7 @@ fn main() { let y = i64x2(0, 1); let z = i64x2(1, 2); const I: [u32; 2] = [1, 2]; - let x: i64x2 = unsafe { simd_shuffle2(y, z, I) }; + let x: i64x2 = unsafe { simd_shuffle(y, z, I) }; assert!(x.0 == 1); assert!(x.1 == 1); } @@ -43,7 +41,17 @@ fn main() { let a = i64x4(1, 2, 3, 4); let b = i64x4(5, 6, 7, 8); const I: [u32; 4] = [1, 3, 5, 7]; - let c: i64x4 = unsafe { simd_shuffle4(a, b, I) }; + let c: i64x4 = unsafe { simd_shuffle(a, b, I) }; assert!(c == i64x4(2, 4, 6, 8)); } } + +#[kani::proof] +fn check_shuffle() { + { + let y = i64x2(0, 1); + let z = i64x2(1, 2); + const I: [u32; 4] = [1, 2, 0, 3]; + let _x: i64x4 = unsafe { simd_shuffle(y, z, I) }; + } +} diff --git a/tests/kani/LibC/sysconf.rs b/tests/kani/LibC/sysconf.rs new file mode 100644 index 000000000000..5a17dfde34eb --- /dev/null +++ b/tests/kani/LibC/sysconf.rs @@ -0,0 +1,12 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! Check support for `sysconf`. + +#![feature(rustc_private)] +extern crate libc; + +#[kani::proof] +fn main() { + let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize; +} diff --git a/tests/kani/NondetSlices/test2.rs b/tests/kani/NondetSlices/test2.rs index 2f0fc72f0a1f..26a1738f9da8 100644 --- a/tests/kani/NondetSlices/test2.rs +++ b/tests/kani/NondetSlices/test2.rs @@ -9,7 +9,8 @@ fn check(s: &[u8]) { #[kani::proof] fn main() { - // returns a slice of length between 0 and 5 with non-det content - let slice: kani::slice::AnySlice = kani::slice::any_slice(); + let arr: [u8; 5] = kani::any(); + // returns a slice of length between 0 and 5 + let slice = kani::slice::any_slice_of_array(&arr); check(&slice); } diff --git a/tests/kani/NondetSlices/test3.rs b/tests/kani/NondetSlices/test3.rs index 9a334c249284..a876b8ec045d 100644 --- a/tests/kani/NondetSlices/test3.rs +++ b/tests/kani/NondetSlices/test3.rs @@ -1,13 +1,14 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// This test uses kani::slice::any_slice of i32 +// This test uses `kani::slice::any_slice_of_array` with `i32` // kani-flags: --default-unwind 6 #[kani::proof] fn check_any_slice_i32() { - let s = kani::slice::any_slice::(); + let a: [i32; 5] = kani::any(); + let s = kani::slice::any_slice_of_array(&a); s.iter().for_each(|x| kani::assume(*x < 10 && *x > -20)); let sum = s.iter().fold(0, |acc, x| acc + x); assert!(sum <= 45); // 9 * 5 diff --git a/tests/kani/NondetSlices/test4.rs b/tests/kani/NondetSlices/test4.rs deleted file mode 100644 index 1a61233d1e2b..000000000000 --- a/tests/kani/NondetSlices/test4.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -// This test checks that values returned by `kani::slice::any_slice` satisfy the -// type invariant - -// kani-flags: --default-unwind 4 - -extern crate kani; -use kani::slice::{any_slice, AnySlice}; - -#[kani::proof] -fn check_any_slice_valid() { - let s: AnySlice = any_slice(); - for c in s.get_slice() { - kani::assume(*c != char::MAX); - assert!(*c < char::MAX); - } -} diff --git a/tests/kani/SIMD/array_simd_repr.rs b/tests/kani/SIMD/array_simd_repr.rs new file mode 100644 index 000000000000..076427415091 --- /dev/null +++ b/tests/kani/SIMD/array_simd_repr.rs @@ -0,0 +1,36 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Verify that Kani can properly handle SIMD declaration and field access using array syntax. + +#![allow(non_camel_case_types)] +#![feature(repr_simd)] + +#[repr(simd)] +#[derive(Clone, PartialEq, Eq, PartialOrd, kani::Arbitrary)] +pub struct i64x2([i64; 2]); + +#[kani::proof] +fn check_diff() { + let x = i64x2([1, 2]); + let y = i64x2([3, 4]); + assert!(x != y); +} + +#[kani::proof] +fn check_ge() { + let x: i64x2 = kani::any(); + kani::assume(x.0[0] > 0); + kani::assume(x.0[1] > 0); + assert!(x > i64x2([0, 0])); +} + +#[derive(Clone, Debug)] +#[repr(simd)] +struct CustomSimd([T; LANES]); + +#[kani::proof] +fn simd_vec() { + let simd = CustomSimd([0u8; 10]); + let idx: usize = kani::any_where(|x: &usize| *x < 10); + assert_eq!(simd.0[idx], 0); +} diff --git a/tests/kani/SIMD/generic_access.rs b/tests/kani/SIMD/generic_access.rs new file mode 100644 index 000000000000..280175a781c6 --- /dev/null +++ b/tests/kani/SIMD/generic_access.rs @@ -0,0 +1,50 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Ensure we can use a generic function to access field of `repr_simd`. +#![feature(portable_simd, repr_simd)] + +use std::simd::SimdElement; + +mod array_based { + use super::*; + + #[repr(simd)] + struct CustomSimd([T; LANES]); + + fn check_fields( + simd: CustomSimd, + expected: [T; LANES], + ) { + assert_eq!(simd.0, expected); + } + + #[kani::proof] + fn check_field_access() { + let data: [u8; 16] = kani::any(); + let vec = CustomSimd(data.clone()); + check_fields(vec, data); + } +} + +mod fields_based { + use super::*; + + #[repr(simd)] + struct CustomSimd(T, T); + + fn check_fields( + simd: CustomSimd, + expected: [T; LANES], + ) { + assert_eq!(simd.0, expected[0]); + assert_eq!(simd.1, expected[1]) + } + + #[kani::proof] + fn check_field_access() { + let data: [u8; 16] = kani::any(); + let vec = CustomSimd(data[0], data[1]); + check_fields(vec, data); + } +} diff --git a/tests/kani/SIMD/multi_field_simd.rs b/tests/kani/SIMD/multi_field_simd.rs new file mode 100644 index 000000000000..d54cf1a07bdb --- /dev/null +++ b/tests/kani/SIMD/multi_field_simd.rs @@ -0,0 +1,28 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Verify that Kani can properly handle SIMD declaration and field access using multi-field syntax. +//! Note: Multi-field SIMD is actually being deprecated, but until it's removed, we might +//! as well keep supporting it. +//! See for more details. + +#![allow(non_camel_case_types)] +#![feature(repr_simd)] + +#[repr(simd)] +#[derive(PartialEq, Eq, PartialOrd, kani::Arbitrary)] +pub struct i64x2(i64, i64); + +#[kani::proof] +fn check_diff() { + let x = i64x2(1, 2); + let y = i64x2(3, 4); + assert!(x != y); +} + +#[kani::proof] +fn check_ge() { + let x: i64x2 = kani::any(); + kani::assume(x.0 > 0); + kani::assume(x.1 > 0); + assert!(x > i64x2(0, 0)); +} diff --git a/tests/kani/SIMD/portable_simd.rs b/tests/kani/SIMD/portable_simd.rs new file mode 100644 index 000000000000..64cfb4b1fcf3 --- /dev/null +++ b/tests/kani/SIMD/portable_simd.rs @@ -0,0 +1,32 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Ensure we have basic support of portable SIMD. +#![feature(portable_simd)] + +use std::simd::{mask32x4, u32x4, u64x16}; + +#[kani::proof] +fn check_sum_any() { + let a = u64x16::splat(0); + let b = u64x16::from_array(kani::any()); + // Cannot compare them directly: https://github.com/model-checking/kani/issues/2632 + assert_eq!((a + b).as_array(), b.as_array()); +} + +#[kani::proof] +fn check_mask() { + // From array doesn't work either. Manually build [false, true, false, true] + let mut mask = mask32x4::splat(false); + mask.set(1, true); + mask.set(3, true); + let bitmask = mask.to_bitmask(); + assert_eq!(bitmask, 0b1010); +} + +#[kani::proof] +fn check_resize() { + let x = u32x4::from_array([0, 1, 2, 3]); + assert_eq!(x.resize::<8>(9).to_array(), [0, 1, 2, 3, 9, 9, 9, 9]); + assert_eq!(x.resize::<2>(9).to_array(), [0, 1]); +} diff --git a/tests/kani/SIMD/simd_float_ops_fixme.rs b/tests/kani/SIMD/simd_float_ops_fixme.rs new file mode 100644 index 000000000000..d258a2119eca --- /dev/null +++ b/tests/kani/SIMD/simd_float_ops_fixme.rs @@ -0,0 +1,39 @@ +// Copyright Kani Contributors +// 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, platform_intrinsics, portable_simd)] +use std::simd::f32x4; + +extern "platform-intrinsic" { + fn simd_add(x: T, y: T) -> T; + fn simd_eq(x: T, y: T) -> U; +} + +#[repr(simd)] +#[derive(Clone, PartialEq, kani::Arbitrary)] +pub struct f32x2(f32, f32); + +impl f32x2 { + fn as_array(&self) -> &[f32; 2] { + unsafe { &*(self as *const f32x2 as *const [f32; 2]) } + } +} + +#[kani::proof] +fn check_sum() { + 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::proof] +fn check_sum_portable() { + let a = f32x4::splat(0.0); + let b = f32x4::from_array(kani::any()); + // 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/kani/SIMD/swizzle.rs b/tests/kani/SIMD/swizzle.rs new file mode 100644 index 000000000000..26781743dbfa --- /dev/null +++ b/tests/kani/SIMD/swizzle.rs @@ -0,0 +1,31 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Ensure we can safely swizzle between SIMD types of different sizes. +#![feature(portable_simd)] + +use std::simd::{simd_swizzle, u32x4, u32x8}; + +#[kani::proof] +fn harness_from_u32x4_to_u32x4() { + let a = u32x4::from_array([0, 1, 2, 3]); + let b = u32x4::from_array([4, 5, 6, 7]); + let r: u32x4 = simd_swizzle!(a, b, [0, 1, 6, 7]); + assert_eq!(r.to_array(), [0, 1, 6, 7]); +} + +#[kani::proof] +fn harness_from_u32x4_to_u32x8() { + let a = u32x4::from_array([0, 1, 2, 3]); + let b = u32x4::from_array([4, 5, 6, 7]); + let r: u32x8 = simd_swizzle!(a, b, [0, 1, 2, 3, 4, 5, 6, 7]); + assert_eq!(r.to_array(), [0, 1, 2, 3, 4, 5, 6, 7]); +} + +#[kani::proof] +fn harness_from_u32x8_to_u32x4() { + let a = u32x8::from_array([0, 1, 2, 3, 4, 5, 6, 7]); + let b = u32x8::from_array([0, 1, 2, 3, 4, 5, 6, 7]); + let r: u32x4 = simd_swizzle!(a, b, [0, 1, 2, 3]); + assert_eq!(r.to_array(), [0, 1, 2, 3]); +} diff --git a/tests/kani/Scopes_Returning/main.rs b/tests/kani/Scopes_Returning/main.rs index 3566479fdaf1..696e09ffc9b1 100644 --- a/tests/kani/Scopes_Returning/main.rs +++ b/tests/kani/Scopes_Returning/main.rs @@ -34,8 +34,6 @@ fn main() { assert!(e == d || e == 10); let g: u32 = kani::any(); - let h = { - if g < 10 { g } else { 10 } - }; + let h = { if g < 10 { g } else { 10 } }; assert!(h == g || h == 10); } diff --git a/tests/kani/Slice/const_bytes.rs b/tests/kani/Slice/const_bytes.rs new file mode 100644 index 000000000000..58d1ded07ccb --- /dev/null +++ b/tests/kani/Slice/const_bytes.rs @@ -0,0 +1,14 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This test checks that byte slices are codegen correctly. This used to fail +//! in the past (see https://github.com/model-checking/kani/issues/2656). + +#[kani::proof] +fn main() { + const MY_CONSTANT: &[u8] = &[147, 211]; + let x: u8 = MY_CONSTANT[0]; + let y: u8 = MY_CONSTANT[1]; + assert_eq!(x, 147); + assert_eq!(y, 211); +} diff --git a/tests/kani/Slice/slice-drop.rs b/tests/kani/Slice/slice-drop.rs deleted file mode 100644 index 41d596c3be46..000000000000 --- a/tests/kani/Slice/slice-drop.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -// Assigning to a memory location via pointer dereferencing causes Drop::drop to be called for the location to which the pointer points. -// Here, kani::Arbitrary implementation for MyStruct deterministically sets MyStruct.0 to 1. -// We check whether AnySlice will properly initialize memory making the assertion in drop() to pass. - -struct MyStruct(i32); - -impl Drop for MyStruct { - fn drop(&mut self) { - assert!(self.0 == 1); - } -} - -impl kani::Arbitrary for MyStruct { - fn any() -> Self { - MyStruct(1) - } -} - -#[kani::proof] -fn my_proof() { - let my_slice = kani::slice::any_slice::(); -} diff --git a/tests/kani/Stubbing/enum_method.rs b/tests/kani/Stubbing/enum_method.rs index a5cf43d214a0..ac1c5351b976 100644 --- a/tests/kani/Stubbing/enum_method.rs +++ b/tests/kani/Stubbing/enum_method.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness main --enable-unstable --enable-stubbing +// kani-flags: --harness main -Z stubbing // //! This tests stubbing for methods in local enums. diff --git a/tests/kani/Stubbing/fixme_issue_1953.rs b/tests/kani/Stubbing/fixme_issue_1953.rs index 1f319a0a0e91..55df2d4dc835 100644 --- a/tests/kani/Stubbing/fixme_issue_1953.rs +++ b/tests/kani/Stubbing/fixme_issue_1953.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --enable-unstable --enable-stubbing --harness main +// kani-flags: -Z stubbing --harness main // // We currently require a stub and the original/function method to have the same // names for generic parameters; instead, we should allow for renaming. diff --git a/tests/kani/Stubbing/foreign_functions.rs b/tests/kani/Stubbing/foreign_functions.rs new file mode 100644 index 000000000000..cdf57ba33863 --- /dev/null +++ b/tests/kani/Stubbing/foreign_functions.rs @@ -0,0 +1,76 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: -Z stubbing +// +//! Check support for stubbing out foreign functions. + +#![feature(rustc_private)] +extern crate libc; + +use libc::c_char; +use libc::c_int; +use libc::c_longlong; +use libc::size_t; + +#[allow(dead_code)] // Avoid warning when using stubs. +#[allow(unused_variables)] +mod stubs { + use super::*; + + pub unsafe extern "C" fn strlen(cs: *const c_char) -> size_t { + 4 + } + + pub unsafe extern "C" fn sysconf(_input: c_int) -> c_longlong { + 10 + } +} + +fn dig_deeper(input: c_int) { + unsafe { + type FunctionPointerType = unsafe extern "C" fn(c_int) -> c_longlong; + let ptr: FunctionPointerType = libc::sysconf; + assert_eq!(ptr(input) as usize, 10); + } +} + +fn deeper_call() { + dig_deeper(libc::_SC_PAGESIZE) +} + +fn function_pointer_call(function_pointer: unsafe extern "C" fn(c_int) -> c_longlong) { + assert_eq!(unsafe { function_pointer(libc::_SC_PAGESIZE) } as usize, 10); +} + +#[kani::proof] +#[kani::stub(libc::strlen, stubs::strlen)] +fn standard() { + let str: Box = Box::new(4); + let str_ptr: *const c_char = &*str; + assert_eq!(unsafe { libc::strlen(str_ptr) }, 4); +} + +#[kani::proof] +#[kani::stub(libc::strlen, stubs::strlen)] +fn function_pointer_standard() { + let str: Box = Box::new(4); + let str_ptr: *const c_char = &*str; + let new_ptr = libc::strlen; + assert_eq!(unsafe { new_ptr(str_ptr) }, 4); +} + +#[kani::proof] +#[kani::stub(libc::sysconf, stubs::sysconf)] +fn function_pointer_with_layers() { + deeper_call(); +} + +#[kani::proof] +#[kani::stub(libc::sysconf, stubs::sysconf)] +fn function_pointer_as_parameter() { + type FunctionPointerType = unsafe extern "C" fn(c_int) -> c_longlong; + let function_pointer: FunctionPointerType = libc::sysconf; + function_pointer_call(function_pointer); + function_pointer_call(libc::sysconf); +} diff --git a/tests/kani/Stubbing/glob_cycle.rs b/tests/kani/Stubbing/glob_cycle.rs index 42600809c7bf..54aa066f1622 100644 --- a/tests/kani/Stubbing/glob_cycle.rs +++ b/tests/kani/Stubbing/glob_cycle.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness check_stub --enable-unstable --enable-stubbing +// kani-flags: --harness check_stub -Z stubbing //! Test that stub can solve glob cycles. pub mod mod_a { diff --git a/tests/kani/Stubbing/glob_path.rs b/tests/kani/Stubbing/glob_path.rs index 17a9e7ed4264..72ec8a07d980 100644 --- a/tests/kani/Stubbing/glob_path.rs +++ b/tests/kani/Stubbing/glob_path.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness check_stub --enable-unstable --enable-stubbing +// kani-flags: --harness check_stub -Z stubbing //! Test that stub can solve glob cycles even when the path expands the cycle. pub mod mod_a { diff --git a/tests/kani/Stubbing/method_generic_type.rs b/tests/kani/Stubbing/method_generic_type.rs index 55bad2c72715..804c2c5c7f5e 100644 --- a/tests/kani/Stubbing/method_generic_type.rs +++ b/tests/kani/Stubbing/method_generic_type.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness main --enable-unstable --enable-stubbing +// kani-flags: --harness main -Z stubbing // //! This tests stubbing for methods of a local type that has generic parameters. diff --git a/tests/kani/Stubbing/multiple_harnesses.rs b/tests/kani/Stubbing/multiple_harnesses.rs index 59d4199b601f..ac20b53e515e 100644 --- a/tests/kani/Stubbing/multiple_harnesses.rs +++ b/tests/kani/Stubbing/multiple_harnesses.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --enable-unstable --enable-stubbing +// kani-flags: -Z stubbing //! Check that Kani can handle a different combination of stubs in //! the same crate. diff --git a/tests/kani/Stubbing/partial_harness_name.rs b/tests/kani/Stubbing/partial_harness_name.rs index c08de8c226d9..eae475154d1d 100644 --- a/tests/kani/Stubbing/partial_harness_name.rs +++ b/tests/kani/Stubbing/partial_harness_name.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness mod2::harness --enable-unstable --enable-stubbing +// kani-flags: --harness mod2::harness -Z stubbing // //! This tests whether we correctly find harnesses during stubbing that are //! specified with a partially qualified name. diff --git a/tests/kani/Stubbing/private_function.rs b/tests/kani/Stubbing/private_function.rs index d112731444c7..818c94c51623 100644 --- a/tests/kani/Stubbing/private_function.rs +++ b/tests/kani/Stubbing/private_function.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness main --enable-unstable --enable-stubbing +// kani-flags: --harness main -Z stubbing // //! This tests stubbing for private local functions. diff --git a/tests/kani/Stubbing/public_function.rs b/tests/kani/Stubbing/public_function.rs index 85a25e7d48e8..8257081241b5 100644 --- a/tests/kani/Stubbing/public_function.rs +++ b/tests/kani/Stubbing/public_function.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness main --enable-unstable --enable-stubbing +// kani-flags: --harness main -Z stubbing // //! This tests stubbing for public local functions. diff --git a/tests/kani/Stubbing/qualifiers.rs b/tests/kani/Stubbing/qualifiers.rs index b9b4aac560a0..06cee979883b 100644 --- a/tests/kani/Stubbing/qualifiers.rs +++ b/tests/kani/Stubbing/qualifiers.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness harness --enable-unstable --enable-stubbing +// kani-flags: --harness harness -Z stubbing // //! This tests resolving stubs with the path qualifiers `self`, `super`, and //! `crate`. diff --git a/tests/kani/Stubbing/resolve_superseded_glob_use.rs b/tests/kani/Stubbing/resolve_superseded_glob_use.rs index 83f1b3527a0c..e639437133f4 100644 --- a/tests/kani/Stubbing/resolve_superseded_glob_use.rs +++ b/tests/kani/Stubbing/resolve_superseded_glob_use.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness harness --enable-unstable --enable-stubbing +// kani-flags: --harness harness -Z stubbing // //! Tests to make sure that, when we are resolving paths in `kani::stub` //! attributes, we prioritize those that do not come from glob imports. diff --git a/tests/kani/Stubbing/resolve_use.rs b/tests/kani/Stubbing/resolve_use.rs index 9bd25bc90bb5..ac1f6f7325f8 100644 --- a/tests/kani/Stubbing/resolve_use.rs +++ b/tests/kani/Stubbing/resolve_use.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness my_mod::harness --enable-unstable --enable-stubbing +// kani-flags: --harness my_mod::harness -Z stubbing // //! This tests whether we take into account simple local uses (`use XXX;`) when //! resolving paths in `kani::stub` attributes. diff --git a/tests/kani/Stubbing/resolve_use_as.rs b/tests/kani/Stubbing/resolve_use_as.rs index b661636fae7e..790e26e575e9 100644 --- a/tests/kani/Stubbing/resolve_use_as.rs +++ b/tests/kani/Stubbing/resolve_use_as.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness my_mod::harness --enable-unstable --enable-stubbing +// kani-flags: --harness my_mod::harness -Z stubbing // //! This tests whether we take into account simple local use-as statements (`use //! XXX as YYY;`) when resolving paths in `kani::stub` attributes. diff --git a/tests/kani/Stubbing/resolve_use_glob.rs b/tests/kani/Stubbing/resolve_use_glob.rs index f9092e9f1a55..a5e85ce7e10c 100644 --- a/tests/kani/Stubbing/resolve_use_glob.rs +++ b/tests/kani/Stubbing/resolve_use_glob.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness my_mod::harness --enable-unstable --enable-stubbing +// kani-flags: --harness my_mod::harness -Z stubbing // //! This tests whether we take into account local use-glob statements (`use //! XXX::*;`) when resolving paths in `kani::stub` attributes. diff --git a/tests/kani/Stubbing/std_fs_read.rs b/tests/kani/Stubbing/std_fs_read.rs index 397487a3a5b4..fa9d55c06813 100644 --- a/tests/kani/Stubbing/std_fs_read.rs +++ b/tests/kani/Stubbing/std_fs_read.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness harness --enable-unstable --enable-stubbing +// kani-flags: --harness harness -Z stubbing // //! This tests stubbing `std` functions like `std::fs::read`. diff --git a/tests/kani/Stubbing/struct_method.rs b/tests/kani/Stubbing/struct_method.rs index b939bf7b417a..0270941ef705 100644 --- a/tests/kani/Stubbing/struct_method.rs +++ b/tests/kani/Stubbing/struct_method.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness main --enable-unstable --enable-stubbing +// kani-flags: --harness main -Z stubbing // //! This tests stubbing for methods in local structs. diff --git a/tests/kani/Stubbing/stub_harnesses.rs b/tests/kani/Stubbing/stub_harnesses.rs index f83dc309277e..b7969f74d03d 100644 --- a/tests/kani/Stubbing/stub_harnesses.rs +++ b/tests/kani/Stubbing/stub_harnesses.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness check --enable-unstable --enable-stubbing +// kani-flags: --harness check -Z stubbing // //! This tests whether we provide a user friendly error if more than one harness has stubs diff --git a/tests/kani/Stubbing/use_std_fs_read.rs b/tests/kani/Stubbing/use_std_fs_read.rs index e174e49e93a9..8beee26bbbf3 100644 --- a/tests/kani/Stubbing/use_std_fs_read.rs +++ b/tests/kani/Stubbing/use_std_fs_read.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness harness --enable-unstable --enable-stubbing +// kani-flags: --harness harness -Z stubbing // //! This tests whether we can correctly account for `use` statements with `std` //! functions like `std::fs::read` when resolving paths in `kani::stub` diff --git a/tests/kani/Stubbing/validate_mismatched_traits.rs b/tests/kani/Stubbing/validate_mismatched_traits.rs index 32d49593bc18..f575e2bb6fa3 100644 --- a/tests/kani/Stubbing/validate_mismatched_traits.rs +++ b/tests/kani/Stubbing/validate_mismatched_traits.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness harness --enable-unstable --enable-stubbing +// kani-flags: --harness harness -Z stubbing // //! This tests that we allow trait mismatches between the stub and the original //! function/method so long as they do not lead to a trait method call being diff --git a/tests/kani/Vectors/vector_extend_bytes.rs b/tests/kani/Vectors/vector_extend_bytes.rs new file mode 100644 index 000000000000..2c48dcfc4653 --- /dev/null +++ b/tests/kani/Vectors/vector_extend_bytes.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Check that we propertly handle `Vec::extend` with a constant byte slice. +//! This used to fail previously (see +//! https://github.com/model-checking/kani/issues/2656). + +#[kani::proof] +fn check_extend_const_byte_slice() { + const MY_CONSTANT: &[u8] = b"Hi"; + + let mut my_vec: Vec = Vec::new(); + my_vec.extend(MY_CONSTANT); + assert_eq!(my_vec, [72, 105]); +} diff --git a/tests/perf/hashset/Cargo.toml b/tests/perf/hashset/Cargo.toml new file mode 100644 index 000000000000..d0757e11154b --- /dev/null +++ b/tests/perf/hashset/Cargo.toml @@ -0,0 +1,14 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +[package] +name = "hashset" +version = "0.1.0" +edition = "2021" +description = "Verify HashSet basic behavior" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[package.metadata.kani.unstable] +stubbing = true diff --git a/tests/perf/hashset/expected b/tests/perf/hashset/expected new file mode 100644 index 000000000000..8ba783d580f9 --- /dev/null +++ b/tests/perf/hashset/expected @@ -0,0 +1 @@ +4 successfully verified harnesses, 0 failures, 4 total diff --git a/tests/perf/hashset/src/lib.rs b/tests/perf/hashset/src/lib.rs new file mode 100644 index 000000000000..fb29db5c7ed6 --- /dev/null +++ b/tests/perf/hashset/src/lib.rs @@ -0,0 +1,74 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: --enable-stubbing --enable-unstable +//! Try to verify HashSet basic behavior. + +use std::collections::{hash_map::RandomState, HashSet}; +use std::mem::{size_of, size_of_val, transmute}; + +fn concrete_state() -> RandomState { + let keys: [u64; 2] = [0, 0]; + assert_eq!(size_of_val(&keys), size_of::()); + unsafe { transmute(keys) } +} + +#[kani::proof] +#[kani::stub(RandomState::new, concrete_state)] +#[kani::unwind(2)] +#[kani::solver(kissat)] +fn check_insert() { + let mut set: HashSet = HashSet::default(); + let first = kani::any(); + set.insert(first); + assert_eq!(set.len(), 1); + assert_eq!(set.iter().next(), Some(&first)); +} + +#[kani::proof] +#[kani::stub(RandomState::new, concrete_state)] +#[kani::unwind(2)] +#[kani::solver(kissat)] +fn check_contains() { + let first = kani::any(); + let mut set: HashSet = HashSet::from([first]); + assert!(set.contains(&first)); +} + +#[kani::proof] +#[kani::stub(RandomState::new, concrete_state)] +#[kani::unwind(2)] +fn check_contains_str() { + let mut set = HashSet::from(["s"]); + assert!(set.contains(&"s")); +} + +#[kani::proof] +#[kani::stub(RandomState::new, concrete_state)] +#[kani::unwind(2)] +#[kani::solver(kissat)] +fn check_insert_two_concrete() { + let mut set: HashSet = HashSet::default(); + let first = 10; + let second = 20; + set.insert(first); + set.insert(second); + assert_eq!(set.len(), 2); +} + +// too slow so don't run in the regression for now +#[cfg(slow)] +mod slow { + #[kani::proof] + #[kani::stub(RandomState::new, concrete_state)] + #[kani::unwind(3)] + fn check_insert_two_elements() { + let mut set: HashSet = HashSet::default(); + let first = kani::any(); + set.insert(first); + + let second = kani::any(); + set.insert(second); + + if first == second { assert_eq!(set.len(), 1) } else { assert_eq!(set.len(), 2) } + } +} diff --git a/tests/perf/kani-lib/arbitrary/src/check_arbitrary.rs b/tests/perf/kani-lib/arbitrary/src/check_arbitrary.rs index e653744a191f..1833b0768833 100644 --- a/tests/perf/kani-lib/arbitrary/src/check_arbitrary.rs +++ b/tests/perf/kani-lib/arbitrary/src/check_arbitrary.rs @@ -79,3 +79,14 @@ fn check_any_bool() { assert!(!all_true || !all_false); } + +#[kani::proof] +fn check_duration() { + let durations: [Duration; 10] = kani::any(); + let (max, zero): (usize, usize) = kani::any(); + kani::assume(max < durations.len() && zero < durations.len()); + kani::assume(durations[max] == Duration::MAX); + kani::assume(durations[zero] == Duration::ZERO); + assert_eq!(durations.iter().min(), Some(&Duration::ZERO)); + assert_eq!(durations.iter().max(), Some(&Duration::MAX)); +} diff --git a/tests/script-based-pre/cargo-kani-version-flag-version/cargo-kani-version-flag-version.expected b/tests/script-based-pre/cargo-kani-version-flag-version/cargo-kani-version-flag-version.expected new file mode 100644 index 000000000000..90033f7a5621 --- /dev/null +++ b/tests/script-based-pre/cargo-kani-version-flag-version/cargo-kani-version-flag-version.expected @@ -0,0 +1,2 @@ +success: version printed agrees +success: `(cargo plugin)` appears in version line \ No newline at end of file diff --git a/tests/script-based-pre/cargo-kani-version-flag-version/cargo-kani-version-flag-version.sh b/tests/script-based-pre/cargo-kani-version-flag-version/cargo-kani-version-flag-version.sh new file mode 100755 index 000000000000..c2cbb8a924db --- /dev/null +++ b/tests/script-based-pre/cargo-kani-version-flag-version/cargo-kani-version-flag-version.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +set -eu + +KANI_VERSION_CMD=`cargo kani --version` +KANI_VERSION_CMD_VERSION=`echo ${KANI_VERSION_CMD} | awk '{print $2}'` + +# Check that the version printed is the same. Note: We use `sed -n '1p'` instead +# of `head -n 1` to avoid https://github.com/model-checking/kani/issues/2618 +KANI_CARGO_OUTPUT_HEAD=`cd dummy-project; cargo kani | sed -n '1p'` +KANI_CARGO_OUTPUT_HEAD_VERSION=`echo ${KANI_CARGO_OUTPUT_HEAD} | awk '{print $4}'` + +if [[ $KANI_VERSION_CMD_VERSION == $KANI_CARGO_OUTPUT_HEAD_VERSION ]]; then + echo "success: version printed agrees" +else + echo "failed: version printed differs ($KANI_VERSION_CMD_VERSION - $KANI_CARGO_OUTPUT_HEAD_VERSION)" + exit 1 +fi + +KANI_CARGO_OUTPUT_HEAD_MODE=`echo ${KANI_CARGO_OUTPUT_HEAD} | awk '{print $5,$6}'` + +# Check that `(cargo plugin)` appears in the version line +if [[ $KANI_CARGO_OUTPUT_HEAD_MODE == "(cargo plugin)" ]]; then + echo "success: \`(cargo plugin)\` appears in version line" +else + echo "failed: expected \`(cargo plugin)\` in version line" + exit 1 +fi diff --git a/tests/script-based-pre/cargo-kani-version-flag-version/config.yml b/tests/script-based-pre/cargo-kani-version-flag-version/config.yml new file mode 100644 index 000000000000..c909770561f9 --- /dev/null +++ b/tests/script-based-pre/cargo-kani-version-flag-version/config.yml @@ -0,0 +1,4 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +script: cargo-kani-version-flag-version.sh +expected: cargo-kani-version-flag-version.expected diff --git a/tests/script-based-pre/cargo-kani-version-flag-version/dummy-project/Cargo.toml b/tests/script-based-pre/cargo-kani-version-flag-version/dummy-project/Cargo.toml new file mode 100644 index 000000000000..7afd9dd45bac --- /dev/null +++ b/tests/script-based-pre/cargo-kani-version-flag-version/dummy-project/Cargo.toml @@ -0,0 +1,11 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +[package] +name = "dummy-project" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/script-based-pre/cargo-kani-version-flag-version/dummy-project/src/main.rs b/tests/script-based-pre/cargo-kani-version-flag-version/dummy-project/src/main.rs new file mode 100644 index 000000000000..ed4e2a18242b --- /dev/null +++ b/tests/script-based-pre/cargo-kani-version-flag-version/dummy-project/src/main.rs @@ -0,0 +1,14 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This test is used to check that an invocation of `cargo kani` prints the version +//! and invocation type as expected. + +fn main() { + println!("Hello, world!"); +} + +#[kani::proof] +fn dummy() { + assert!(1 + 1 == 2); +} diff --git a/tests/script-based-pre/kani-version-flag-version/config.yml b/tests/script-based-pre/kani-version-flag-version/config.yml new file mode 100644 index 000000000000..8d70c7103c33 --- /dev/null +++ b/tests/script-based-pre/kani-version-flag-version/config.yml @@ -0,0 +1,4 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +script: kani-version-flag-version.sh +expected: kani-version-flag-version.expected diff --git a/tests/script-based-pre/kani-version-flag-version/dummy-file.rs b/tests/script-based-pre/kani-version-flag-version/dummy-file.rs new file mode 100644 index 000000000000..fb042d45cafa --- /dev/null +++ b/tests/script-based-pre/kani-version-flag-version/dummy-file.rs @@ -0,0 +1,10 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This test is used to check that an invocation of `kani` or `cargo kani` +//! prints the version and invocation type as expected. + +#[kani::proof] +fn dummy() { + assert!(1 + 1 == 2); +} diff --git a/tests/script-based-pre/kani-version-flag-version/kani-version-flag-version.expected b/tests/script-based-pre/kani-version-flag-version/kani-version-flag-version.expected new file mode 100644 index 000000000000..d2b7de0d4e0e --- /dev/null +++ b/tests/script-based-pre/kani-version-flag-version/kani-version-flag-version.expected @@ -0,0 +1,2 @@ +success: version printed agrees +success: `(standalone)` appears in version line \ No newline at end of file diff --git a/tests/script-based-pre/kani-version-flag-version/kani-version-flag-version.sh b/tests/script-based-pre/kani-version-flag-version/kani-version-flag-version.sh new file mode 100755 index 000000000000..5c22918535e8 --- /dev/null +++ b/tests/script-based-pre/kani-version-flag-version/kani-version-flag-version.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +set -eu + +KANI_VERSION_CMD=`kani --version` +KANI_VERSION_CMD_VERSION=`echo ${KANI_VERSION_CMD} | awk '{print $2}'` + +# Check that the version printed is the same. Note: We use `sed -n '1p'` instead +# of `head -n 1` to avoid https://github.com/model-checking/kani/issues/2618 +KANI_STANDALONE_OUTPUT_HEAD=`kani dummy-file.rs | sed -n '1p'` +KANI_STANDALONE_OUTPUT_HEAD_VERSION=`echo ${KANI_STANDALONE_OUTPUT_HEAD} | awk '{print $4}'` + +if [[ $KANI_VERSION_CMD_VERSION == $KANI_STANDALONE_OUTPUT_HEAD_VERSION ]]; then + echo "success: version printed agrees" +else + echo "failed: version printed differs ($KANI_VERSION_CMD_VERSION - $KANI_STANDALONE_OUTPUT_HEAD_VERSION)" + exit 1 +fi + +KANI_STANDALONE_OUTPUT_HEAD_MODE=`echo ${KANI_STANDALONE_OUTPUT_HEAD} | awk '{print $5}'` + +# Check that `(standalone)` appears in the version line +if [[ $KANI_STANDALONE_OUTPUT_HEAD_MODE == "(standalone)" ]]; then + echo "success: \`(standalone)\` appears in version line" +else + echo "failed: expected \`(standalone)\` in version line" + exit 1 +fi diff --git a/tests/script-based-pre/playback_print/config.yml b/tests/script-based-pre/playback_print/config.yml new file mode 100644 index 000000000000..abda4b73edb7 --- /dev/null +++ b/tests/script-based-pre/playback_print/config.yml @@ -0,0 +1,4 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +script: playback_print.sh +expected: expected diff --git a/tests/script-based-pre/playback_print/expected b/tests/script-based-pre/playback_print/expected new file mode 100644 index 000000000000..b9a04bb58988 --- /dev/null +++ b/tests/script-based-pre/playback_print/expected @@ -0,0 +1,13 @@ +internal error: entered unreachable code: oops + +assertion `left == right` failed: Found 1 != 0 +left: 1 +right: 0 + +assertion `left == right` failed +left: 0 +right: 1 + +Found value 2 + +test result: FAILED. 0 passed; 4 failed diff --git a/tests/script-based-pre/playback_print/playback_print.sh b/tests/script-based-pre/playback_print/playback_print.sh new file mode 100755 index 000000000000..d8acddb14285 --- /dev/null +++ b/tests/script-based-pre/playback_print/playback_print.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +# Test that concrete playback does not override std print functions + +set -o nounset + +function error() { + echo $@ + # Cleanup + rm ${RS_FILE} + rm output.log + exit 1 +} + +RS_FILE="modified.rs" +cp print_vars.rs ${RS_FILE} +export RUSTFLAGS="--edition 2021" + +echo "[TEST] Generate test..." +kani ${RS_FILE} -Z concrete-playback --concrete-playback=inplace + +echo "[TEST] Run test..." +kani playback -Z concrete-playback ${RS_FILE} 2>&1 | tee output.log + +echo "------ Check output ----------" + +set -e +while read -r line; do + grep "${line}" output.log || error "Failed to find: \"${line}\"" +done < expected + +echo +echo "------ Output OK ----------" +echo + +# Cleanup +rm ${RS_FILE} +rm output.log diff --git a/tests/script-based-pre/playback_print/print_vars.rs b/tests/script-based-pre/playback_print/print_vars.rs new file mode 100644 index 000000000000..0d0f2b5243ff --- /dev/null +++ b/tests/script-based-pre/playback_print/print_vars.rs @@ -0,0 +1,19 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// Harness used to test that playback do not override assertion and panic functions. + +#[kani::proof] +pub fn harness() { + let v1: u32 = kani::any(); + let v2: u32 = kani::any(); + // avoid direct assignments to v1 to block constant propagation. + kani::assume(v1 == v2); + + match v2 { + 0 => assert_eq!(v1, 1), + 1 => assert_eq!(v1, 0, "Found {v1} != 0"), + 2 => panic!("Found value {v1}"), + _ => unreachable!("oops"), + } +} diff --git a/tests/slow/tokio-proofs/Cargo.toml b/tests/slow/tokio-proofs/Cargo.toml index 2f96a3b8bb99..0ffe95b3a4e0 100644 --- a/tests/slow/tokio-proofs/Cargo.toml +++ b/tests/slow/tokio-proofs/Cargo.toml @@ -19,3 +19,6 @@ tokio-util = { version = "0.7.3", features = ["io"] } async-stream = "0.3.3" # mockall = "0.11.1" # async-stream = "0.3" + +[kani.unstable] +async-lib = true diff --git a/tests/stub-tests/HashSet/concrete.rs b/tests/stub-tests/HashSet/concrete.rs deleted file mode 100644 index 7a830963e430..000000000000 --- a/tests/stub-tests/HashSet/concrete.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -// kani-flags: --use-abs --abs-type c-ffi -fn main() { - let mut h: HashSet = HashSet::new(); - - // TODO: This test should ideally work with nondeterminstic values but for - // for the moment it does not. - // - // let a: u16 = kani::any(); - // let b: u16 = kani::any(); - // let c: u16 = kani::any(); - // kani::assume(a != b); - // kani::assume(a != c); - // kani::assume(b != c); - - assert!(h.insert(5)); - assert!(h.contains(&5)); - assert!(!h.contains(&10)); - assert!(h.remove(5)); - assert!(!h.contains(&10)); - assert!(!h.contains(&5)); - assert!(h.insert(0)); - assert!(h.contains(&0)); - assert!(h.remove(0)); - assert!(!h.contains(&0)); - assert!(!h.remove(0)); - assert!(h.insert(6)); - assert!(!h.insert(6)); -} diff --git a/tests/stub-tests/HashSet/ignore-nondet.rs b/tests/stub-tests/HashSet/ignore-nondet.rs deleted file mode 100644 index a1fc5bdd5a16..000000000000 --- a/tests/stub-tests/HashSet/ignore-nondet.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -// kani-flags: --use-abs --abs-type c-ffi -fn main() { - let mut h: HashSet = HashSet::new(); - - // TODO: This test should ideally work with nondeterminstic values but for - // for the moment it does not. - let a: u16 = kani::any(); - let b: u16 = kani::any(); - let c: u16 = kani::any(); - kani::assume(a != b); - kani::assume(a != c); - kani::assume(b != c); - - assert!(h.insert(a)); - assert!(h.contains(&a)); - assert!(!h.contains(&b)); - assert!(h.remove(a)); - assert!(!h.contains(&a)); - assert!(!h.contains(&b)); - assert!(h.insert(b)); - assert!(h.contains(&b)); - assert!(h.remove(b)); - assert!(!h.contains(&b)); - assert!(!h.remove(b)); - assert!(h.insert(c)); - assert!(!h.insert(c)); -} diff --git a/tests/stub-tests/README.md b/tests/stub-tests/README.md deleted file mode 100644 index 227cdc2e9844..000000000000 --- a/tests/stub-tests/README.md +++ /dev/null @@ -1,11 +0,0 @@ -This folder contains tests which can be used to test the compatibility of the -various abstractions against the standard library implementation. - -They are extracted mostly verbatim directly from the [Rust reference manual for the -Vector](https://doc.rust-lang.org/std/vec/struct.Vec.html). - -To run these tests through the compiletest framework for the kani abstraction: - -```bash -$ ./x.py test -i stub-tests -``` diff --git a/tests/stub-tests/Vec/append.rs b/tests/stub-tests/Vec/append.rs deleted file mode 100644 index ed41e0f34cfb..000000000000 --- a/tests/stub-tests/Vec/append.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn append_test() { - let mut vec = kani_vec![1, 2, 3]; - let mut vec2 = kani_vec![4, 5, 6]; - vec.append(&mut vec2); - assert!(vec == [1, 2, 3, 4, 5, 6]); - assert!(vec2 == []); - } - - append_test(); -} diff --git a/tests/stub-tests/Vec/as_mut_ptr.rs b/tests/stub-tests/Vec/as_mut_ptr.rs deleted file mode 100644 index 1caf24c6bcd9..000000000000 --- a/tests/stub-tests/Vec/as_mut_ptr.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn as_mut_ptr_test() { - let size = 4; - let mut x: Vec = Vec::with_capacity(size); - let x_ptr = x.as_mut_ptr(); - - // Initialize elements via raw pointer writes, then set length. - unsafe { - for i in 0..size { - *x_ptr.add(i) = i as i32; - } - x.set_len(size); - } - assert_eq!(&*x, &[0, 1, 2, 3]); - } - - as_mut_ptr_test(); -} diff --git a/tests/stub-tests/Vec/as_mut_slice.rs b/tests/stub-tests/Vec/as_mut_slice.rs deleted file mode 100644 index 053c36a41cd1..000000000000 --- a/tests/stub-tests/Vec/as_mut_slice.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn as_mut_slice_test() { - let mut buffer = kani_vec![1, 2, 3]; - buffer.as_mut_slice().reverse(); - assert!(buffer == [3, 2, 1]); - } - - as_mut_slice_test(); -} diff --git a/tests/stub-tests/Vec/as_ptr.rs b/tests/stub-tests/Vec/as_ptr.rs deleted file mode 100644 index f281f6a0384c..000000000000 --- a/tests/stub-tests/Vec/as_ptr.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn as_ptr_test() { - let x = kani_vec![1, 2, 4]; - let x_ptr = x.as_ptr(); - - unsafe { - for i in 0..x.len() { - assert_eq!(*x_ptr.add(i), 1 << i); - } - } - } - - as_ptr_test() -} diff --git a/tests/stub-tests/Vec/as_slice.rs b/tests/stub-tests/Vec/as_slice.rs deleted file mode 100644 index 6529ccd049c7..000000000000 --- a/tests/stub-tests/Vec/as_slice.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn as_slice_test() { - use std::io::{self, Write}; - let buffer = kani_vec![1, 2, 3, 5, 8]; - io::sink().write(buffer.as_slice()).unwrap(); - } - - as_slice_test(); -} diff --git a/tests/stub-tests/Vec/capacity.rs b/tests/stub-tests/Vec/capacity.rs deleted file mode 100644 index d93b24b682a8..000000000000 --- a/tests/stub-tests/Vec/capacity.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn capacity_test() { - let mut vec = Vec::with_capacity(10); - - // The vector contains no items, even though it has capacity for more - assert_eq!(vec.len(), 0); - assert_eq!(vec.capacity(), 10); - - // These are all done without reallocating... - for i in 0..10 { - vec.push(i); - } - - assert_eq!(vec.len(), 10); - assert_eq!(vec.capacity(), 10); - - // ...but this may make the vector reallocate - vec.push(11); - assert_eq!(vec.len(), 11); - assert!(vec.capacity() >= 11); - } - - capacity_test() -} diff --git a/tests/stub-tests/Vec/clear.rs b/tests/stub-tests/Vec/clear.rs deleted file mode 100644 index f90eabd83f9d..000000000000 --- a/tests/stub-tests/Vec/clear.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn clear_test() { - let mut v = kani_vec![1, 2, 3]; - - v.clear(); - - assert!(v.is_empty()); - } - - clear_test(); -} diff --git a/tests/stub-tests/Vec/clone.rs b/tests/stub-tests/Vec/clone.rs deleted file mode 100644 index 768cde95260a..000000000000 --- a/tests/stub-tests/Vec/clone.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn clone_test() { - let v = kani_vec![1, 2, 3]; - let p = v.clone(); - - assert!(p == [1, 2, 3]); - } - - clone_test(); -} diff --git a/tests/stub-tests/Vec/drop.rs b/tests/stub-tests/Vec/drop.rs deleted file mode 100644 index cae9055dd06b..000000000000 --- a/tests/stub-tests/Vec/drop.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani - -static mut GLOB: i32 = 1; - -struct Test { - _marker: u32, -} - -impl Drop for Test { - fn drop(&mut self) { - unsafe { - GLOB += 1; - } - } -} - -fn main() { - fn drop_test() { - { - let mut v = Vec::new(); - v.push(Test { _marker: 0 }); - v.push(Test { _marker: 0 }); - } - - unsafe { - assert!(GLOB == 3); - } - } - - drop_test(); -} diff --git a/tests/stub-tests/Vec/extend.rs b/tests/stub-tests/Vec/extend.rs deleted file mode 100644 index cd6d574da552..000000000000 --- a/tests/stub-tests/Vec/extend.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn extend_test() { - let mut vec = Vec::new(); - vec.push(1); - vec.push(2); - - assert!(vec.len() == 2); - assert!(vec[0] == 1); - - assert!(vec.pop() == Some(2)); - assert!(vec.len() == 1); - - vec[0] = 7; - assert!(vec[0] == 7); - - vec.extend([1, 2, 3]); - - assert!(vec == [7, 1, 2, 3]); - } - - extend_test(); -} diff --git a/tests/stub-tests/Vec/extend_from_slice.rs b/tests/stub-tests/Vec/extend_from_slice.rs deleted file mode 100644 index c2945429e600..000000000000 --- a/tests/stub-tests/Vec/extend_from_slice.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn extend_from_slice_test() { - let mut vec = kani_vec![1]; - vec.extend_from_slice(&[2, 3, 4]); - assert!(vec == [1, 2, 3, 4]); - } - - extend_from_slice_test(); -} diff --git a/tests/stub-tests/Vec/from_raw_parts.rs b/tests/stub-tests/Vec/from_raw_parts.rs deleted file mode 100644 index 6f872ec170ed..000000000000 --- a/tests/stub-tests/Vec/from_raw_parts.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -use std::ptr; - -fn main() { - fn from_raw_parts_test() { - let v = kani_vec![1, 2, 3]; - - // 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 with 4, 5, 6 - for i in 0..len as isize { - ptr::write(p.offset(i), 4 + i); - } - - // Put everything back together into a Vec - let rebuilt = Vec::from_raw_parts(p, len, cap); - assert_eq!(rebuilt, [4, 5, 6]); - } - } -} diff --git a/tests/stub-tests/Vec/from_slice.rs b/tests/stub-tests/Vec/from_slice.rs deleted file mode 100644 index 060df21feb35..000000000000 --- a/tests/stub-tests/Vec/from_slice.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn from_slice_test() { - assert_eq!(Vec::from(&[1, 2, 3][..]), kani_vec![1, 2, 3]); - assert_eq!(Vec::from(&mut [1, 2, 3][..]), kani_vec![1, 2, 3]); - assert_eq!(Vec::from([3; 4]), kani_vec![3, 3, 3, 3]); - } - - from_slice_test(); -} diff --git a/tests/stub-tests/Vec/from_str.rs b/tests/stub-tests/Vec/from_str.rs deleted file mode 100644 index e0bc9797c668..000000000000 --- a/tests/stub-tests/Vec/from_str.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn from_str_test() { - assert_eq!(Vec::from("123"), kani_vec![b'1', b'2', b'3']); - } - - from_str_test() -} diff --git a/tests/stub-tests/Vec/insert.rs b/tests/stub-tests/Vec/insert.rs deleted file mode 100644 index ccd08fe0157f..000000000000 --- a/tests/stub-tests/Vec/insert.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn insert_test() { - let mut vec = kani_vec![1, 2, 3]; - vec.insert(1, 4); - assert!(vec == [1, 4, 2, 3]); - vec.insert(4, 5); - assert!(vec == [1, 4, 2, 3, 5]); - } - - insert_test(); -} diff --git a/tests/stub-tests/Vec/into_iter.rs b/tests/stub-tests/Vec/into_iter.rs deleted file mode 100644 index 46e0c37e30ec..000000000000 --- a/tests/stub-tests/Vec/into_iter.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn into_iter_test() { - let v = kani_vec![1, 4, 5]; - let mut iter = v.into_iter(); - - assert!(iter.next() == Some(1)); - assert!(iter.next() == Some(4)); - assert!(iter.next() == Some(5)); - assert!(iter.next() == None); - } - - into_iter_test(); -} diff --git a/tests/stub-tests/Vec/is_empty.rs b/tests/stub-tests/Vec/is_empty.rs deleted file mode 100644 index 168f6f4e29bc..000000000000 --- a/tests/stub-tests/Vec/is_empty.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn is_empty_test() { - let mut v = Vec::new(); - assert!(v.is_empty()); - - v.push(1); - assert!(!v.is_empty()); - } - - is_empty_test(); -} diff --git a/tests/stub-tests/Vec/len.rs b/tests/stub-tests/Vec/len.rs deleted file mode 100644 index cfb0225eedf7..000000000000 --- a/tests/stub-tests/Vec/len.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type no-back -fn main() { - fn append_test() { - let mut vec = kani_vec![1, 2, 3]; - assert!(vec.len() == 3); - vec.push(10); - vec.push(15); - assert!(vec.len() == 5); - vec.pop(); - assert!(vec.len() == 4); - vec.pop(); - vec.pop(); - vec.pop(); - vec.pop(); - vec.pop(); - vec.pop(); - assert!(vec.len() == 0); - vec.push(15); - assert!(vec.len() == 1); - } - - append_test(); -} diff --git a/tests/stub-tests/Vec/new.rs b/tests/stub-tests/Vec/new.rs deleted file mode 100644 index ff0cdc4ebbc2..000000000000 --- a/tests/stub-tests/Vec/new.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn new_test() { - let v: Vec = Vec::new(); - } - - new_test(); -} diff --git a/tests/stub-tests/Vec/pop.rs b/tests/stub-tests/Vec/pop.rs deleted file mode 100644 index 05318b1b7eb6..000000000000 --- a/tests/stub-tests/Vec/pop.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn pop_test() { - let mut vec = kani_vec![1, 2, 3]; - assert_eq!(vec.pop(), Some(3)); - assert_eq!(vec, [1, 2]); - } - - pop_test(); -} diff --git a/tests/stub-tests/Vec/push.rs b/tests/stub-tests/Vec/push.rs deleted file mode 100644 index 659510a89f94..000000000000 --- a/tests/stub-tests/Vec/push.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn push_test() { - let mut vec = kani_vec![1, 2]; - vec.push(3); - assert!(vec == [1, 2, 3]); - } - - push_test(); -} diff --git a/tests/stub-tests/Vec/remove.rs b/tests/stub-tests/Vec/remove.rs deleted file mode 100644 index d8304df82c52..000000000000 --- a/tests/stub-tests/Vec/remove.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn remove_test() { - let mut v = kani_vec![1, 2, 3]; - assert_eq!(v.remove(2), 3); - assert_eq!(v, [1, 2]); - assert_eq!(v.remove(1), 2); - assert_eq!(v.remove(0), 1); - - let mut p = kani_vec![1, 2, 3]; - assert_eq!(p.remove(0), 1); - assert_eq!(p, [2, 3]); - } - - remove_test(); -} diff --git a/tests/stub-tests/Vec/reserve.rs b/tests/stub-tests/Vec/reserve.rs deleted file mode 100644 index ee7147e01358..000000000000 --- a/tests/stub-tests/Vec/reserve.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn reserve_test() { - let mut vec = kani_vec![1]; - vec.reserve(10); - assert!(vec.capacity() >= 11); - } - - reserve_test(); -} diff --git a/tests/stub-tests/Vec/reserve_exact.rs b/tests/stub-tests/Vec/reserve_exact.rs deleted file mode 100644 index ee3f9a7f3225..000000000000 --- a/tests/stub-tests/Vec/reserve_exact.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn reserve_exact_test() { - let mut vec = kani_vec![1]; - vec.reserve_exact(10); - assert!(vec.capacity() >= 11); - } - - reserve_exact_test(); -} diff --git a/tests/stub-tests/Vec/resize.rs b/tests/stub-tests/Vec/resize.rs deleted file mode 100644 index 1c593a36f506..000000000000 --- a/tests/stub-tests/Vec/resize.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn resize_test() { - let mut vec = kani_vec![1]; - vec.resize(3, 2); - assert!(vec == [1, 2, 2]); - - let mut vec = kani_vec![1, 2, 3, 4]; - vec.resize(2, 0); - assert!(vec == [1, 2]); - } - - resize_test(); -} diff --git a/tests/stub-tests/Vec/resize_with.rs b/tests/stub-tests/Vec/resize_with.rs deleted file mode 100644 index fb8c9cc80324..000000000000 --- a/tests/stub-tests/Vec/resize_with.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn resize_with_test() { - let mut vec = kani_vec![1, 2, 3]; - vec.resize_with(5, Default::default); - assert_eq!(vec, [1, 2, 3, 0, 0]); - - let mut vec = kani_vec![]; - let mut p = 1; - vec.resize_with(4, || { - p *= 2; - p - }); - assert_eq!(vec, [2, 4, 8, 16]); - } - - resize_with_test(); -} diff --git a/tests/stub-tests/Vec/shrink_to.rs b/tests/stub-tests/Vec/shrink_to.rs deleted file mode 100644 index 481861f97b7b..000000000000 --- a/tests/stub-tests/Vec/shrink_to.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn shrink_to_test() { - let mut vec = Vec::with_capacity(10); - vec.extend([1, 2, 3]); - assert!(vec.capacity() == 10); - vec.shrink_to(4); - assert!(vec.capacity() >= 4); - vec.shrink_to(0); - kani::expect_fail(vec.capacity() >= 3, "Capacity shrinked to 0"); - } - - shrink_to_test() -} diff --git a/tests/stub-tests/Vec/shrink_to_fit.rs b/tests/stub-tests/Vec/shrink_to_fit.rs deleted file mode 100644 index ec1e22070fcc..000000000000 --- a/tests/stub-tests/Vec/shrink_to_fit.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn shrink_to_fit_test() { - let mut vec = Vec::with_capacity(10); - vec.extend([1, 2, 3]); - assert_eq!(vec.capacity(), 10); - vec.shrink_to_fit(); - assert!(vec.capacity() >= 3); - } - - shrink_to_fit_test(); -} diff --git a/tests/stub-tests/Vec/simple.rs b/tests/stub-tests/Vec/simple.rs deleted file mode 100644 index 49e4192708e5..000000000000 --- a/tests/stub-tests/Vec/simple.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn simple_test() { - let mut vec: Vec = kani_vec![1, 2, 3]; - vec.push(3); - vec.push(4); - vec.pop(); - assert!(vec.pop() == Some(3)); - } - - simple_test(); -} diff --git a/tests/stub-tests/Vec/split_off.rs b/tests/stub-tests/Vec/split_off.rs deleted file mode 100644 index 6839e955a7b8..000000000000 --- a/tests/stub-tests/Vec/split_off.rs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn split_off_test() { - let mut vec = kani_vec![1, 2, 3]; - let vec2 = vec.split_off(1); - assert!(vec == [1]); - assert!(vec2 == [2, 3]); - - let mut vec = kani_vec![1, 2, 3]; - let vec2 = vec.split_off(0); - assert!(vec == []); - assert!(vec2 == [1, 2, 3]); - - let mut vec = kani_vec![1, 2, 3]; - let vec2 = vec.split_off(3); - assert!(vec == [1, 2, 3]); - assert!(vec2 == []); - } - - split_off_test(); -} diff --git a/tests/stub-tests/Vec/swap_remove.rs b/tests/stub-tests/Vec/swap_remove.rs deleted file mode 100644 index ed878295005f..000000000000 --- a/tests/stub-tests/Vec/swap_remove.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn swap_remove_test() { - let mut v = kani_vec![1, 2, 3, 4]; - - assert_eq!(v.swap_remove(1), 2); - assert_eq!(v, [1, 4, 3]); - - assert_eq!(v.swap_remove(0), 1); - assert_eq!(v, [3, 4]); - } - - swap_remove_test(); -} diff --git a/tests/stub-tests/Vec/truncate.rs b/tests/stub-tests/Vec/truncate.rs deleted file mode 100644 index a3be72070b6a..000000000000 --- a/tests/stub-tests/Vec/truncate.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn truncate_test() { - let mut vec = kani_vec![1, 2, 3]; - vec.truncate(8); - assert_eq!(vec, [1, 2, 3]); - } - - truncate_test(); -} diff --git a/tests/stub-tests/Vec/truncate_drop.rs b/tests/stub-tests/Vec/truncate_drop.rs deleted file mode 100644 index c180c843b243..000000000000 --- a/tests/stub-tests/Vec/truncate_drop.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -static mut GLOB: i32 = 1; - -struct Test { - _marker: u32, -} - -impl Drop for Test { - fn drop(&mut self) { - unsafe { - GLOB += 1; - } - } -} - -fn main() { - fn truncate_test() { - let mut vec = Vec::new(); - vec.push(Test { _marker: 0 }); - vec.push(Test { _marker: 0 }); - vec.push(Test { _marker: 0 }); - vec.truncate(0); - - unsafe { - assert!(GLOB == 7); - } - } - - truncate_test(); -} diff --git a/tests/stub-tests/Vec/truncate_reduce.rs b/tests/stub-tests/Vec/truncate_reduce.rs deleted file mode 100644 index 3a9c6ccbfd47..000000000000 --- a/tests/stub-tests/Vec/truncate_reduce.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn truncate_reduce_test() { - let mut vec = kani_vec![1, 2, 3, 4, 5]; - vec.truncate(2); - assert_eq!(vec, [1, 2]); - } - - truncate_reduce_test(); -} diff --git a/tests/stub-tests/Vec/truncate_zero.rs b/tests/stub-tests/Vec/truncate_zero.rs deleted file mode 100644 index 3807820c394f..000000000000 --- a/tests/stub-tests/Vec/truncate_zero.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --use-abs --abs-type kani -fn main() { - fn truncate_zero_test() { - let mut vec = kani_vec![1, 2, 3]; - vec.truncate(0); - assert_eq!(vec, []); - } - - truncate_zero_test(); -} diff --git a/tests/ui/arguments-proof/expected b/tests/ui/arguments-proof/expected index e1e9392bd48e..7805c716c2ad 100644 --- a/tests/ui/arguments-proof/expected +++ b/tests/ui/arguments-proof/expected @@ -1 +1,3 @@ - = help: message: #[kani::proof] does not take any arguments +`some` is not a valid option for `#[kani::proof]`. + +the trait bound `NotASchedule: kani::futures::SchedulingStrategy` is not satisfied diff --git a/tests/ui/arguments-proof/main.rs b/tests/ui/arguments-proof/main.rs index 887d51e3b87b..6fefe3d5c27c 100644 --- a/tests/ui/arguments-proof/main.rs +++ b/tests/ui/arguments-proof/main.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --no-unwinding-checks +// kani-flags: --no-unwinding-checks -Z async-lib // This test is to check Kani's error handling for harnesses that have proof attributes // with arguments when the expected declaration takes no arguments. @@ -19,3 +19,12 @@ fn harness() { assert!(counter < 10); } } + +// Test what happens if the schedule option is incorrect: + +struct NotASchedule; + +#[kani::proof(schedule = NotASchedule)] +async fn test() { + assert!(true); +} diff --git a/tests/ui/arguments-proof/missing-unstable-flag/expected b/tests/ui/arguments-proof/missing-unstable-flag/expected new file mode 100644 index 000000000000..8d905c9fc2c9 --- /dev/null +++ b/tests/ui/arguments-proof/missing-unstable-flag/expected @@ -0,0 +1 @@ +error: Use of unstable feature `async-lib`: experimental async support diff --git a/tests/ui/arguments-proof/missing-unstable-flag/main.rs b/tests/ui/arguments-proof/missing-unstable-flag/main.rs new file mode 100644 index 000000000000..b68ed75afe26 --- /dev/null +++ b/tests/ui/arguments-proof/missing-unstable-flag/main.rs @@ -0,0 +1,12 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// compile-flags: --edition 2018 +// kani-flags: --no-unwinding-checks + +// This test is to check that the `schedule` argument requires an unstable flag. + +#[kani::proof(schedule = kani::RoundRobin::default())] +async fn test() { + assert!(true); +} diff --git a/tests/ui/cbmc_checks/signed-overflow/expected b/tests/ui/cbmc_checks/signed-overflow/expected index 143dd683f76f..70669b325e9e 100644 --- a/tests/ui/cbmc_checks/signed-overflow/expected +++ b/tests/ui/cbmc_checks/signed-overflow/expected @@ -8,4 +8,3 @@ Failed Checks: attempt to calculate the remainder with overflow Failed Checks: attempt to shift left with overflow Failed Checks: attempt to shift right with overflow Failed Checks: arithmetic overflow on signed shl -Failed Checks: shift operand is negative diff --git a/tests/ui/concrete-playback/i8/expected b/tests/ui/concrete-playback/i8/expected index b2d5484e2b1a..e585fd6afe27 100644 --- a/tests/ui/concrete-playback/i8/expected +++ b/tests/ui/concrete-playback/i8/expected @@ -11,7 +11,7 @@ fn kani_concrete_playback_harness vec![155], // 0 vec![0], - // 'e' + // 101 vec![101], // 127 vec![127] diff --git a/tests/ui/concrete-playback/message-order/expected b/tests/ui/concrete-playback/message-order/expected index 415584e1f2dc..4fd787d7612b 100644 --- a/tests/ui/concrete-playback/message-order/expected +++ b/tests/ui/concrete-playback/message-order/expected @@ -6,8 +6,8 @@ Concrete playback unit test for `dummy`: #[test] fn kani_concrete_playback_dummy let concrete_vals: Vec> = vec![ - // 32778 - vec![10, 128, 0, 0], + // 10 + vec![10, 0, 0, 0], ]; kani::concrete_playback_run(concrete_vals, dummy); } diff --git a/tests/ui/concrete-playback/message-order/main.rs b/tests/ui/concrete-playback/message-order/main.rs index 81aa4c6886db..a43dc9964cfd 100644 --- a/tests/ui/concrete-playback/message-order/main.rs +++ b/tests/ui/concrete-playback/message-order/main.rs @@ -5,5 +5,5 @@ #[kani::proof] fn dummy() { - kani::cover!(kani::any::() != 10); + kani::cover!(kani::any::() == 10); } diff --git a/tests/ui/concrete-playback/ub/null_ptr/expected b/tests/ui/concrete-playback/ub/null_ptr/expected new file mode 100644 index 000000000000..062d8bbb0bf0 --- /dev/null +++ b/tests/ui/concrete-playback/ub/null_ptr/expected @@ -0,0 +1,13 @@ +VERIFICATION:- FAILED + +Concrete playback +``` +#[test] +fn kani_concrete_playback_null_ptr + let concrete_vals: Vec> = vec![ + // 15 + vec![15, 0, 0, 0], + ]; + kani::concrete_playback_run(concrete_vals, null_ptr); +} +``` diff --git a/tests/ui/concrete-playback/ub/null_ptr/test.rs b/tests/ui/concrete-playback/ub/null_ptr/test.rs new file mode 100644 index 000000000000..c97f4a764885 --- /dev/null +++ b/tests/ui/concrete-playback/ub/null_ptr/test.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// kani-flags: -Zconcrete-playback --concrete-playback=print + +// This test checks that Kani generates a concrete playback test for UB checks +// (e.g. dereferencing a null pointer) + +#[kani::proof] +fn null_ptr() { + let x = 42; + let nd: i32 = kani::any(); + let ptr: *const i32 = if nd != 15 { &x as *const i32 } else { std::ptr::null() }; + let _y = unsafe { *ptr }; +} diff --git a/tests/ui/concrete-playback/ub/oob_ptr/expected b/tests/ui/concrete-playback/ub/oob_ptr/expected new file mode 100644 index 000000000000..3c20d94b2e06 --- /dev/null +++ b/tests/ui/concrete-playback/ub/oob_ptr/expected @@ -0,0 +1,13 @@ +VERIFICATION:- FAILED + +Concrete playback +``` +#[test] +fn kani_concrete_playback_oob_ptr + let concrete_vals: Vec> = vec![ + // 3ul + vec![3, 0, 0, 0, 0, 0, 0, 0], + ]; + kani::concrete_playback_run(concrete_vals, oob_ptr); +} +``` diff --git a/tests/ui/concrete-playback/ub/oob_ptr/test.rs b/tests/ui/concrete-playback/ub/oob_ptr/test.rs new file mode 100644 index 000000000000..d93af9a8903e --- /dev/null +++ b/tests/ui/concrete-playback/ub/oob_ptr/test.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// kani-flags: -Zconcrete-playback --concrete-playback=print + +// This test checks that Kani generates a concrete playback test for UB checks +// (e.g. dereferencing a pointer that is outside the object bounds) + +#[kani::proof] +fn oob_ptr() { + let v = vec![1, 2, 3]; + // BUG: predicate should use strict less-than, i.e. `*idx < v.len()` + let idx: usize = kani::any_where(|idx| *idx <= v.len()); + let _x = unsafe { *v.get_unchecked(idx) }; +} diff --git a/tests/ui/loop-contracts-synthesis/main_unsigned/main_unsigned.rs b/tests/ui/loop-contracts-synthesis/main_unsigned/main_unsigned.rs index f981ca8fa12b..3a1302111b32 100644 --- a/tests/ui/loop-contracts-synthesis/main_unsigned/main_unsigned.rs +++ b/tests/ui/loop-contracts-synthesis/main_unsigned/main_unsigned.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --enable-unstable --synthesize-loop-contracts +// kani-flags: --enable-unstable --synthesize-loop-contracts --cbmc-args --object-bits 4 // Check if goto-synthesizer is correctly called, and synthesizes the required // loop invariants. diff --git a/tests/ui/should-panic-attribute/expected-panics/test.rs b/tests/ui/should-panic-attribute/expected-panics/test.rs index 0cd8e12be96e..b79f8fc35388 100644 --- a/tests/ui/should-panic-attribute/expected-panics/test.rs +++ b/tests/ui/should-panic-attribute/expected-panics/test.rs @@ -1,6 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT - +// +// compile-flags: -Copt-level=1 //! Checks that verfication passes when `#[kani::should_panic]` is used and all //! failures encountered are panics. diff --git a/tests/ui/should-panic-attribute/multiple-attrs/expected b/tests/ui/should-panic-attribute/multiple-attrs/expected index 5dd8c6a61430..ecc7c6fc91c8 100644 --- a/tests/ui/should-panic-attribute/multiple-attrs/expected +++ b/tests/ui/should-panic-attribute/multiple-attrs/expected @@ -1,2 +1,2 @@ error: only one '#[kani::should_panic]' attribute is allowed per harness -error: aborting due to previous error +error: aborting due to 1 previous error diff --git a/tests/ui/should-panic-attribute/unexpected-failures/expected b/tests/ui/should-panic-attribute/unexpected-failures/expected index 6cd10b4bafd5..b3a4cb77c6ae 100644 --- a/tests/ui/should-panic-attribute/unexpected-failures/expected +++ b/tests/ui/should-panic-attribute/unexpected-failures/expected @@ -1,9 +1,9 @@ -undefined-shift\ +arithmetic_overflow\ Status: FAILURE\ -Description: "shift distance too large" +Description: "attempt to shift by excessive shift distance" Failed Checks: panicked on the `1` arm! Failed Checks: panicked on the `0` arm! -Failed Checks: shift distance too large +Failed Checks: attempt to shift by excessive shift distance VERIFICATION:- FAILED (encountered failures other than panics, which were unexpected) diff --git a/tests/ui/should-panic-attribute/unexpected-failures/test.rs b/tests/ui/should-panic-attribute/unexpected-failures/test.rs index 625a15c89683..c96eeb71efaf 100644 --- a/tests/ui/should-panic-attribute/unexpected-failures/test.rs +++ b/tests/ui/should-panic-attribute/unexpected-failures/test.rs @@ -3,7 +3,7 @@ //! Checks that verfication fails when `#[kani::should_panic]` is used but not //! all failures encountered are panics. -#![feature(unchecked_math)] +#![feature(unchecked_shifts)] fn trigger_overflow() { let x: u32 = kani::any(); diff --git a/tests/ui/should-panic-attribute/with-args/expected b/tests/ui/should-panic-attribute/with-args/expected index 3ba218b82a69..2ab549629004 100644 --- a/tests/ui/should-panic-attribute/with-args/expected +++ b/tests/ui/should-panic-attribute/with-args/expected @@ -1,3 +1,3 @@ error: custom attribute panicked help: message: `#[kani::should_panic]` does not take any arguments currently -error: aborting due to previous error +error: aborting due to 1 previous error diff --git a/tests/ui/solver-attribute/invalid/expected b/tests/ui/solver-attribute/invalid/expected index 53f6b87bf547..fe4f1da36d68 100644 --- a/tests/ui/solver-attribute/invalid/expected +++ b/tests/ui/solver-attribute/invalid/expected @@ -3,4 +3,4 @@ test.rs:\ |\ | #[kani::solver(123)]\ | ^^^^^^^^^^^^^^^^^^^^ -error: aborting due to previous error +error: aborting due to 1 previous error diff --git a/tests/ui/solver-attribute/multiple-args/expected b/tests/ui/solver-attribute/multiple-args/expected index 64e1a5468fc3..9bd942eaafa3 100644 --- a/tests/ui/solver-attribute/multiple-args/expected +++ b/tests/ui/solver-attribute/multiple-args/expected @@ -3,4 +3,4 @@ test.rs:\ |\ | #[kani::solver(kissat, minisat)]\ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to previous error +error: aborting due to 1 previous error diff --git a/tests/ui/solver-attribute/multiple-attrs/expected b/tests/ui/solver-attribute/multiple-attrs/expected index 1287dedaaab1..5a6b5cb298b1 100644 --- a/tests/ui/solver-attribute/multiple-attrs/expected +++ b/tests/ui/solver-attribute/multiple-attrs/expected @@ -3,4 +3,4 @@ test.rs:\ |\ | #[kani::solver(kissat)]\ | ^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to previous error +error: aborting due to 1 previous error diff --git a/tests/ui/solver-attribute/no-arg/expected b/tests/ui/solver-attribute/no-arg/expected index 42cadb93b477..c4946d27dafd 100644 --- a/tests/ui/solver-attribute/no-arg/expected +++ b/tests/ui/solver-attribute/no-arg/expected @@ -3,4 +3,4 @@ test.rs:\ |\ | #[kani::solver]\ | ^^^^^^^^^^^^^^^ -error: aborting due to previous error +error: aborting due to 1 previous error diff --git a/tests/ui/solver-attribute/unknown/expected b/tests/ui/solver-attribute/unknown/expected index 7d3bf6d61ef3..7825d4e8c93c 100644 --- a/tests/ui/solver-attribute/unknown/expected +++ b/tests/ui/solver-attribute/unknown/expected @@ -3,4 +3,4 @@ test.rs:\ |\ | #[kani::solver(foo)]\ | ^^^^^^^^^^^^^^^^^^^^ -error: aborting due to previous error +error: aborting due to 1 previous error diff --git a/tests/ui/std-override/format_panic.expected b/tests/ui/std-override/format_panic.expected new file mode 100644 index 000000000000..643fbd2317ec --- /dev/null +++ b/tests/ui/std-override/format_panic.expected @@ -0,0 +1 @@ +Complete - 0 successfully verified harnesses, 2 failures, 2 total. diff --git a/tests/ui/std-override/format_panic.rs b/tests/ui/std-override/format_panic.rs new file mode 100644 index 000000000000..7f0c5089f7a4 --- /dev/null +++ b/tests/ui/std-override/format_panic.rs @@ -0,0 +1,19 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! Ensure Kani override doesn't result in extra warnings which could block compilation when +//! users have strict lints. + +#![deny(unused_variables)] + +#[kani::proof] +pub fn check_panic_format() { + let val: bool = kani::any(); + panic!("Invalid value {val}"); +} + +#[kani::proof] +pub fn check_panic_format_expr() { + let val: bool = kani::any(); + panic!("Invalid value {}", !val); +} diff --git a/tests/ui/stub-attribute/attribute.rs b/tests/ui/stub-attribute/attribute.rs index 2e3b81fb09ea..19fdf5b5b05b 100644 --- a/tests/ui/stub-attribute/attribute.rs +++ b/tests/ui/stub-attribute/attribute.rs @@ -1,6 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// +// kani-flags: -Z stubbing //! Checks that the `kani::stub` attribute is accepted fn foo() {} diff --git a/tests/ui/stubbing/deprecated-enable-stable/deprecated.rs b/tests/ui/stubbing/deprecated-enable-stable/deprecated.rs new file mode 100644 index 000000000000..a5686af20a35 --- /dev/null +++ b/tests/ui/stubbing/deprecated-enable-stable/deprecated.rs @@ -0,0 +1,16 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: --enable-unstable --enable-stubbing +//! Checks that the `kani::stub` attribute is accepted + +fn foo() { + unreachable!(); +} + +fn bar() {} + +#[kani::proof] +#[kani::stub(foo, bar)] +fn main() { + foo(); +} diff --git a/tests/ui/stubbing/deprecated-enable-stable/expected b/tests/ui/stubbing/deprecated-enable-stable/expected new file mode 100644 index 000000000000..eb05b8cf9812 --- /dev/null +++ b/tests/ui/stubbing/deprecated-enable-stable/expected @@ -0,0 +1,2 @@ +warning: The `--enable-stubbing` option is deprecated. This option will be removed soon. Consider using `-Z stubbing` instead +VERIFICATION:- SUCCESSFUL diff --git a/tools/bookrunner/Cargo.toml b/tools/bookrunner/Cargo.toml index 600ba419e492..6c602ce05fd0 100644 --- a/tools/bookrunner/Cargo.toml +++ b/tools/bookrunner/Cargo.toml @@ -15,7 +15,7 @@ rustdoc = { path = "librustdoc" } walkdir = "2.3.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -toml = "0.7" +toml = "0.8" [package.metadata.rust-analyzer] # This package uses rustc crates. diff --git a/tools/bookrunner/librustdoc/doctest.rs b/tools/bookrunner/librustdoc/doctest.rs index 5d11ea7ec55b..734dacd093a6 100644 --- a/tools/bookrunner/librustdoc/doctest.rs +++ b/tools/bookrunner/librustdoc/doctest.rs @@ -5,7 +5,7 @@ use rustc_ast as ast; use rustc_data_structures::sync::Lrc; use rustc_driver::DEFAULT_LOCALE_RESOURCES; -use rustc_errors::{ColorConfig, TerminalUrl}; +use rustc_errors::ColorConfig; use rustc_span::edition::Edition; use rustc_span::source_map::SourceMap; use rustc_span::symbol::sym; @@ -81,36 +81,14 @@ pub fn make_test( let fallback_bundle = rustc_errors::fallback_fluent_bundle(DEFAULT_LOCALE_RESOURCES.to_vec(), false); - supports_color = EmitterWriter::stderr( - ColorConfig::Auto, - None, - None, - fallback_bundle.clone(), - false, - false, - Some(80), - false, - false, - TerminalUrl::No, - ) - .supports_color(); - - let emitter = EmitterWriter::new( - Box::new(io::sink()), - None, - None, - fallback_bundle, - false, - false, - false, - None, - false, - false, - TerminalUrl::No, - ); + supports_color = EmitterWriter::stderr(ColorConfig::Auto, fallback_bundle.clone()) + .diagnostic_width(Some(80)) + .supports_color(); + + let emitter = EmitterWriter::new(Box::new(io::sink()), fallback_bundle); // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser - let handler = Handler::with_emitter(false, None, Box::new(emitter)); + let handler = Handler::with_emitter(Box::new(emitter)); let sess = ParseSess::with_span_handler(handler, sm); let mut found_main = false; diff --git a/tools/bookrunner/librustdoc/lib.rs b/tools/bookrunner/librustdoc/lib.rs index 9046478cf502..68bc77fa14e8 100644 --- a/tools/bookrunner/librustdoc/lib.rs +++ b/tools/bookrunner/librustdoc/lib.rs @@ -17,7 +17,7 @@ #![feature(iter_intersperse)] #![recursion_limit = "256"] #![warn(rustc::internal)] -#![allow(clippy::collapsible_if, clippy::collapsible_else_if)] +#![allow(clippy::collapsible_if, clippy::collapsible_else_if, clippy::arc_with_non_send_sync)] #[macro_use] extern crate tracing; diff --git a/tools/bookrunner/src/books.rs b/tools/bookrunner/src/books.rs index 713e6d2b15f7..0e8b8fb8857d 100644 --- a/tools/bookrunner/src/books.rs +++ b/tools/bookrunner/src/books.rs @@ -88,7 +88,7 @@ impl Book { let summary_dir = summary_path.parent().unwrap().to_path_buf(); let summary = fs::read_to_string(summary_path.clone()).unwrap(); assert!( - summary.starts_with(&summary_start.as_str()), + summary.starts_with(summary_start.as_str()), "Error: The start of {} summary file changed.", self.name ); @@ -409,7 +409,7 @@ fn extract( config_paths: &mut HashSet, default_edition: Edition, ) { - let code = fs::read_to_string(&par_from).unwrap(); + let code = fs::read_to_string(par_from).unwrap(); let mut examples = Examples(Vec::new()); find_testable_code(&code, &mut examples, ErrorCodes::No, false, None); for mut example in examples.0 { @@ -514,7 +514,7 @@ fn generate_text_bookrunner(bookrunner: bookrunner::Tree, path: &Path) { bookrunner.data.num_fail, bookrunner ); - fs::write(&path, bookrunner_str).expect("Error: Unable to write bookrunner results"); + fs::write(path, bookrunner_str).expect("Error: Unable to write bookrunner results"); } /// Runs examples using Litani build. diff --git a/tools/bookrunner/src/util.rs b/tools/bookrunner/src/util.rs index 260c3253df69..542ad70dbc96 100644 --- a/tools/bookrunner/src/util.rs +++ b/tools/bookrunner/src/util.rs @@ -206,7 +206,7 @@ pub fn add_verification_job(litani: &mut Litani, test_props: &TestProps) { // Add `--function main` so we can run these without having to amend them to add `#[kani::proof]`. // Some of test_props.kani_args will contains `--cbmc-args` so we should always put that last. kani.arg(&test_props.path) - .args(&["--enable-unstable", "--function", "main"]) + .args(["--enable-unstable", "--function", "main"]) .args(&test_props.kani_args); if !test_props.rustc_args.is_empty() { kani.env("RUSTFLAGS", test_props.rustc_args.join(" ")); diff --git a/tools/build-kani/Cargo.toml b/tools/build-kani/Cargo.toml index 22a3d5d864e8..cb17e13b3c99 100644 --- a/tools/build-kani/Cargo.toml +++ b/tools/build-kani/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "build-kani" -version = "0.32.0" +version = "0.42.0" edition = "2021" description = "Builds Kani, Sysroot and release bundle." license = "MIT OR Apache-2.0" @@ -11,6 +11,6 @@ publish = false [dependencies] anyhow = "1" -cargo_metadata = "0.15.0" -clap = { version = "4.1.3", features=["derive"] } -which = "4" +cargo_metadata = "0.18.0" +clap = { version = "4.4.11", features=["derive"] } +which = "5" diff --git a/tools/build-kani/src/main.rs b/tools/build-kani/src/main.rs index e781f3ceb942..b94e951fe178 100644 --- a/tools/build-kani/src/main.rs +++ b/tools/build-kani/src/main.rs @@ -83,7 +83,7 @@ fn bundle_kani(dir: &Path) -> Result<()> { // 2. Kani scripts let scripts = dir.join("scripts"); - std::fs::create_dir(&scripts)?; + std::fs::create_dir(scripts)?; // 3. Kani libraries let library = dir.join("library"); @@ -148,7 +148,7 @@ fn bundle_kissat(dir: &Path) -> Result<()> { /// This should include all files as `dir/` in the tarball. /// e.g. `kani-1.0/bin/kani-compiler` not just `bin/kani-compiler`. fn create_release_bundle(dir: &Path, bundle: &str) -> Result<()> { - Command::new("tar").args(&["zcf", bundle]).arg(dir).run() + Command::new("tar").args(["zcf", bundle]).arg(dir).run() } /// Helper trait to fallibly run commands diff --git a/tools/build-kani/src/sysroot.rs b/tools/build-kani/src/sysroot.rs index 3d7e2d1dd5dd..3a6239106826 100644 --- a/tools/build-kani/src/sysroot.rs +++ b/tools/build-kani/src/sysroot.rs @@ -74,7 +74,7 @@ pub fn build_lib(bin_folder: &Path) -> Result<()> { fn build_verification_lib(compiler_path: &Path) -> Result<()> { let extra_args = ["-Z", "build-std=panic_abort,std,test", "--config", "profile.dev.panic=\"abort\""]; - let compiler_args = ["--kani-compiler", "-Cllvm-args=--ignore-global-asm"]; + let compiler_args = ["--kani-compiler", "-Cllvm-args=--ignore-global-asm --build-std"]; build_kani_lib(compiler_path, &kani_sysroot_lib(), &extra_args, &compiler_args) } @@ -151,7 +151,7 @@ fn build_kani_lib( /// Copy all the artifacts to their correct place to generate a valid sysroot. fn copy_artifacts(artifacts: &[Artifact], sysroot_lib: &Path, target: &str) -> Result<()> { // Create sysroot folder hierarchy. - sysroot_lib.exists().then(|| fs::remove_dir_all(&sysroot_lib)); + sysroot_lib.exists().then(|| fs::remove_dir_all(sysroot_lib)); let std_path = path_buf!(&sysroot_lib, "rustlib", target, "lib"); fs::create_dir_all(&std_path).expect(&format!("Failed to create {std_path:?}")); @@ -192,12 +192,12 @@ fn is_std_lib(artifact: &Artifact) -> bool { /// predicate, it will copy the following files to the `target` folder. /// - `rlib`: Store metadata for future codegen and executable code for concrete executions. /// - shared library which are used for proc_macros. -fn copy_libs

(artifacts: &[Artifact], target: &Path, predicate: P) +fn copy_libs

(artifacts: &[Artifact], target: &Path, mut predicate: P) where P: FnMut(&Artifact) -> bool, { assert!(target.is_dir(), "Expected a folder, but found {}", target.display()); - for artifact in artifacts.iter().cloned().filter(predicate) { + for artifact in artifacts.iter().filter(|&x| predicate(x)).cloned() { artifact .filenames .iter() diff --git a/tools/compiletest/src/common.rs b/tools/compiletest/src/common.rs index 949726030b7a..99ec78bef693 100644 --- a/tools/compiletest/src/common.rs +++ b/tools/compiletest/src/common.rs @@ -10,6 +10,7 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::time::Duration; +use test::test::TestTimeOptions; use test::ColorConfig; #[derive(Clone, Copy, Eq, PartialEq, Debug)] @@ -18,6 +19,7 @@ pub enum Mode { KaniFixme, CargoKani, CargoKaniTest, // `cargo kani --tests`. This is temporary and should be removed when s2n-quic moves --tests to `Cargo.toml`. + CoverageBased, Exec, Expected, Stub, @@ -31,6 +33,7 @@ impl FromStr for Mode { "kani-fixme" => Ok(KaniFixme), "cargo-kani" => Ok(CargoKani), "cargo-kani-test" => Ok(CargoKaniTest), + "coverage-based" => Ok(CoverageBased), "exec" => Ok(Exec), "expected" => Ok(Expected), "stub-tests" => Ok(Stub), @@ -46,6 +49,7 @@ impl fmt::Display for Mode { KaniFixme => "kani-fixme", CargoKani => "cargo-kani", CargoKaniTest => "cargo-kani-test", + CoverageBased => "coverage-based", Exec => "exec", Expected => "expected", Stub => "stub-tests", @@ -145,6 +149,14 @@ pub struct Config { /// updating multiple tests. Users should still manually edit the files after to only keep /// relevant expectations. pub fix_expected: bool, + + /// Whether we should measure and limit the time of a test. + pub time_opts: Option, + + /// Extra arguments to be passed to Kani in this regression. + /// Note that there is no validation done whether these flags conflict with existing flags. + /// For example, one could add `--kani-flag=--only-codegen` to only compile all tests. + pub extra_args: Vec, } #[derive(Debug, Clone)] diff --git a/tools/compiletest/src/header.rs b/tools/compiletest/src/header.rs index 89e3603a8510..afbee19e6ade 100644 --- a/tools/compiletest/src/header.rs +++ b/tools/compiletest/src/header.rs @@ -37,7 +37,7 @@ impl TestProps { pub fn from_file(testfile: &Path, config: &Config) -> Self { let mut props = TestProps::new(); props.load_from(testfile, config); - + props.kani_flags.extend(config.extra_args.iter().cloned()); props } diff --git a/tools/compiletest/src/json.rs b/tools/compiletest/src/json.rs index 299d81578eb3..c46c3b3225e8 100644 --- a/tools/compiletest/src/json.rs +++ b/tools/compiletest/src/json.rs @@ -59,18 +59,19 @@ pub fn extract_rendered(output: &str) -> String { } else { Some(format!( "Future incompatibility report: {}", - report - .future_incompat_report - .into_iter() - .map(|item| { - format!( - "Future breakage diagnostic:\n{}", - item.diagnostic - .rendered - .unwrap_or_else(|| "Not rendered".to_string()) - ) - }) - .collect::() + report.future_incompat_report.into_iter().fold( + String::new(), + |mut output, item| { + use std::fmt::Write; + let _ = writeln!(output, "Future breakage diagnostic:"); + let s = item + .diagnostic + .rendered + .unwrap_or_else(|| "Not rendered".to_string()); + let _ = write!(output, "{s}"); + output + } + ) )) } } else if serde_json::from_str::(line).is_ok() { diff --git a/tools/compiletest/src/main.rs b/tools/compiletest/src/main.rs index 98a12f3d1ea3..5418ad47f7fa 100644 --- a/tools/compiletest/src/main.rs +++ b/tools/compiletest/src/main.rs @@ -21,6 +21,7 @@ use std::io::{self}; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::time::{Duration, SystemTime}; +use test::test::TestTimeOptions; use test::ColorConfig; use tracing::*; use walkdir::WalkDir; @@ -90,6 +91,14 @@ pub fn parse_config(args: Vec) -> Config { .optflag("", "dry-run", "don't actually run the tests") .optflag("", "fix-expected", "override all expected files that did not match the output. Tests will NOT fail when there is a mismatch") + .optflag("", "report-time", + "report the time of each test. Configuration is done via env variables, like \ + rust unit tests.") + .optmulti("", "kani-flag", + "pass extra flags to Kani. Note that this may cause spurious failures if the \ + passed flag conflicts with the test configuration. Only works for `kani`, \ + `cargo-kani`, and `expected` modes." + , "ARG") ; let (argv0, args_) = args.split_first().unwrap(); @@ -164,6 +173,10 @@ pub fn parse_config(args: Vec) -> Config { dry_run: matches.opt_present("dry-run"), fix_expected: matches.opt_present("fix-expected"), timeout, + time_opts: matches + .opt_present("report-time") + .then_some(TestTimeOptions::new_from_env(false)), + extra_args: matches.opt_strs("kani-flag"), } } @@ -292,7 +305,7 @@ pub fn test_opts(config: &Config) -> test::TestOpts { skip: vec![], list: false, options: test::Options::new(), - time_options: None, + time_options: config.time_opts, fail_fast: config.fail_fast, force_run_in_process: false, } @@ -454,7 +467,7 @@ fn collect_rs_tests_from_dir( // tests themselves, they race for the privilege of // creating the directories and sometimes fail randomly. let build_dir = output_relative_path(config, relative_dir_path); - fs::create_dir_all(&build_dir).unwrap(); + fs::create_dir_all(build_dir).unwrap(); // Add each `.rs` file as a test, and recurse further on any // subdirectories we find, except for `aux` directories. @@ -558,7 +571,7 @@ fn make_test_name(config: &Config, testpaths: &TestPaths) -> test::TestName { // ui/foo/bar/baz.rs let path = PathBuf::from(config.src_base.file_name().unwrap()) .join(&testpaths.relative_dir) - .join(&testpaths.file.file_name().unwrap()); + .join(testpaths.file.file_name().unwrap()); test::DynTestName(format!("[{}] {}", config.mode, path.display())) } diff --git a/tools/compiletest/src/runtest.rs b/tools/compiletest/src/runtest.rs index 59b79a24d8d9..ee89c252dc4f 100644 --- a/tools/compiletest/src/runtest.rs +++ b/tools/compiletest/src/runtest.rs @@ -7,7 +7,9 @@ use crate::common::KaniFailStep; use crate::common::{output_base_dir, output_base_name}; -use crate::common::{CargoKani, CargoKaniTest, Exec, Expected, Kani, KaniFixme, Stub}; +use crate::common::{ + CargoKani, CargoKaniTest, CoverageBased, Exec, Expected, Kani, KaniFixme, Stub, +}; use crate::common::{Config, TestPaths}; use crate::header::TestProps; use crate::read2::read2; @@ -55,7 +57,7 @@ pub fn run(config: Config, testpaths: &TestPaths) { let props = TestProps::from_file(&testpaths.file, &config); let cx = TestCx { config: &config, props: &props, testpaths }; - create_dir_all(&cx.output_base_dir()).unwrap(); + create_dir_all(cx.output_base_dir()).unwrap(); cx.run(); cx.create_stamp(); } @@ -75,6 +77,7 @@ impl<'test> TestCx<'test> { KaniFixme => self.run_kani_test(), CargoKani => self.run_cargo_kani_test(false), CargoKaniTest => self.run_cargo_kani_test(true), + CoverageBased => self.run_expected_coverage_test(), Exec => self.run_exec_test(), Expected => self.run_expected_test(), Stub => self.run_stub_test(), @@ -94,7 +97,7 @@ impl<'test> TestCx<'test> { env::split_paths(&env::var_os(dylib_env_var()).unwrap_or_default()).collect::>(); // Add the new dylib search path var - let newpath = env::join_paths(&path).unwrap(); + let newpath = env::join_paths(path).unwrap(); command.env(dylib_env_var(), newpath); let mut child = disable_error_reporting(|| command.spawn()) @@ -133,7 +136,7 @@ impl<'test> TestCx<'test> { fn dump_output_file(&self, out: &str, extension: &str) { let outfile = self.make_out_name(extension); - fs::write(&outfile, out).unwrap(); + fs::write(outfile, out).unwrap(); } /// Creates a filename for output with the given extension. @@ -270,7 +273,8 @@ impl<'test> TestCx<'test> { .arg("kani") .arg("--target-dir") .arg(self.output_base_dir().join("target")) - .current_dir(&parent_dir); + .current_dir(parent_dir) + .args(&self.config.extra_args); if test { cargo.arg("--tests"); } @@ -310,6 +314,22 @@ impl<'test> TestCx<'test> { self.compose_and_run(kani) } + /// Run coverage based output for kani on a single file + fn run_kani_with_coverage(&self) -> ProcRes { + let mut kani = Command::new("kani"); + if !self.props.compile_flags.is_empty() { + kani.env("RUSTFLAGS", self.props.compile_flags.join(" ")); + } + kani.arg(&self.testpaths.file).args(&self.props.kani_flags); + kani.arg("--coverage").args(["-Z", "line-coverage"]); + + if !self.props.cbmc_flags.is_empty() { + kani.arg("--cbmc-args").args(&self.props.cbmc_flags); + } + + self.compose_and_run(kani) + } + /// Runs an executable file and: /// * Checks the expected output if an expected file is specified /// * Checks the exit code (assumed to be 0 by default) @@ -371,10 +391,28 @@ impl<'test> TestCx<'test> { } /// Runs Kani on the test file specified by `self.testpaths.file`. An error - /// message is printed to stdout if verification output does not contain - /// the expected output in `expected` file. + /// message is printed to stdout if verification output does not contain the + /// expected output. + /// + /// We read the expected output from the file + /// `self.testpaths.file.with_extension("expected")` (same file name but + /// extension replaced with `.expected`). For backwards compatibility, if we + /// don't find this file, we will also try a file called `expected` in the + /// same directory as `self.testpaths.file`. fn run_expected_test(&self) { let proc_res = self.run_kani(); + let dot_expected_path = self.testpaths.file.with_extension("expected"); + let expected_path = if dot_expected_path.exists() { + dot_expected_path + } else { + self.testpaths.file.parent().unwrap().join("expected") + }; + self.verify_output(&proc_res, &expected_path); + } + + /// Runs Kani in coverage mode on the test file specified by `self.testpaths.file`. + fn run_expected_coverage_test(&self) { + let proc_res = self.run_kani_with_coverage(); let expected_path = self.testpaths.file.parent().unwrap().join("expected"); self.verify_output(&proc_res, &expected_path); } @@ -448,40 +486,27 @@ impl<'test> TestCx<'test> { consecutive_lines.clear(); } } - None + // Someone may add a `\` to the last line (probably by accident) but + // that would mean this test would succeed without actually testing so + // we add a check here again. + (!consecutive_lines.is_empty() && !TestCx::contains(str, &consecutive_lines)) + .then_some(consecutive_lines) } /// Check if there is a set of consecutive lines in `str` where each line /// contains a line from `lines` fn contains(str: &[&str], lines: &[&str]) -> bool { - let mut i = str.iter(); - while let Some(output_line) = i.next() { - if output_line.contains(&lines[0]) { - // Check if the rest of the lines in `lines` are contained in - // the subsequent lines in `str` - let mut matches = true; - // Clone the iterator so that we keep i unchanged - let mut j = i.clone(); - for line in lines.iter().skip(1) { - if let Some(output_line) = j.next() { - if output_line.contains(line) { - continue; - } - } - matches = false; - break; - } - if matches { - return true; - } - } - } - false + // Does *any* subslice of length `lines.len()` satisfy the containment of + // *all* `lines`? + // `trim()` added to ignore trailing and preceding whitespace + str.windows(lines.len()).any(|subslice| { + subslice.iter().zip(lines).all(|(output, expected)| output.contains(expected.trim())) + }) } fn create_stamp(&self) { let stamp = crate::stamp(self.config, self.testpaths); - fs::write(&stamp, "we only support one configuration").unwrap(); + fs::write(stamp, "we only support one configuration").unwrap(); } }