diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index ad0bbf81f2c2..12b08eba7c9c 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -55,12 +55,6 @@ jobs: 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/ diff --git a/.github/workflows/cbmc-latest.yml b/.github/workflows/cbmc-latest.yml index d8da02d21840..f707c5d558a5 100644 --- a/.github/workflows/cbmc-latest.yml +++ b/.github/workflows/cbmc-latest.yml @@ -32,10 +32,6 @@ jobs: os: ${{ matrix.os }} kani_dir: 'kani' - - name: Build Kani - working-directory: ./kani - run: cargo build-dev - - name: Checkout CBMC under "cbmc" uses: actions/checkout@v4 with: diff --git a/.github/workflows/kani-m1.yml b/.github/workflows/kani-m1.yml index 36f2b615c4aa..0f884f2fe013 100644 --- a/.github/workflows/kani-m1.yml +++ b/.github/workflows/kani-m1.yml @@ -23,8 +23,5 @@ jobs: 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 abdc4ee46216..d98e0a2b0a5b 100644 --- a/.github/workflows/kani.yml +++ b/.github/workflows/kani.yml @@ -27,9 +27,6 @@ jobs: with: os: ${{ matrix.os }} - - name: Build Kani - run: cargo build-dev - - name: Execute Kani regression run: ./scripts/kani-regression.sh @@ -88,15 +85,12 @@ jobs: with: os: ubuntu-20.04 - - name: Build Kani using release mode - run: cargo build-dev -- --release - - name: Execute Kani performance tests run: ./scripts/kani-perf.sh env: RUST_TEST_THREADS: 1 - bookrunner: + documentation: runs-on: ubuntu-20.04 permissions: contents: write @@ -104,31 +98,6 @@ jobs: - name: Checkout Kani uses: actions/checkout@v4 - - name: Setup Kani Dependencies - uses: ./.github/actions/setup - with: - os: ubuntu-20.04 - - - name: Build Kani - run: cargo build-dev - - - name: Install book runner dependencies - run: ./scripts/setup/install_bookrunner_deps.sh - - - name: Generate book runner report - run: cargo run -p bookrunner - env: - DOC_RUST_LANG_ORG_CHANNEL: nightly - - - name: Print book runner text results - run: cat build/output/latest/html/bookrunner.txt - - - name: Print book runner failures grouped by stage - run: python3 scripts/ci/bookrunner_failures_by_stage.py build/output/latest/html/index.html - - - name: Detect unexpected book runner failures - run: ./scripts/ci/detect_bookrunner_failures.sh build/output/latest/html/bookrunner.txt - - name: Install book dependencies run: ./scripts/setup/ubuntu/install_doc_deps.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bd3e145e1bce..04ac4d20280b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -101,6 +101,106 @@ jobs: os: linux arch: x86_64-unknown-linux-gnu + test-use-local-toolchain: + name: TestLocalToolchain + needs: [build_bundle_macos, build_bundle_linux] + strategy: + matrix: + os: [macos-13, ubuntu-20.04, ubuntu-22.04] + include: + - os: macos-13 + rust_target: x86_64-apple-darwin + prev_job: ${{ needs.build_bundle_macos.outputs }} + - os: ubuntu-20.04 + rust_target: x86_64-unknown-linux-gnu + prev_job: ${{ needs.build_bundle_linux.outputs }} + - os: ubuntu-22.04 + rust_target: x86_64-unknown-linux-gnu + prev_job: ${{ needs.build_bundle_linux.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: | + ls -lh . + + - name: Get toolchain version used to setup kani + run: | + tar zxvf ${{ matrix.prev_job.bundle }} + DATE=$(cat ./kani-latest/rust-toolchain-version | cut -d'-' -f2,3,4) + echo "Nightly date: $DATE" + echo "DATE=$DATE" >> $GITHUB_ENV + + - name: Install Kani from path + run: | + tar zxvf ${{ matrix.prev_job.package }} + cargo install --locked --path kani-verifier-${{ matrix.prev_job.crate_version }} + + - name: Create a custom toolchain directory + run: mkdir -p ${{ github.workspace }}/../custom_toolchain + + - name: Fetch the nightly tarball + run: | + echo "Downloading Rust toolchain from rust server." + curl --proto '=https' --tlsv1.2 -O https://static.rust-lang.org/dist/$DATE/rust-nightly-${{ matrix.rust_target }}.tar.gz + tar -xzf rust-nightly-${{ matrix.rust_target }}.tar.gz + ./rust-nightly-${{ matrix.rust_target }}/install.sh --prefix=${{ github.workspace }}/../custom_toolchain + + - name: Ensure installation is correct + run: | + cargo kani setup --use-local-bundle ./${{ matrix.prev_job.bundle }} --use-local-toolchain ${{ github.workspace }}/../custom_toolchain/ + + - name: Ensure that the rustup toolchain is not present + run: | + if [ ! -e "~/.rustup/toolchains/" ]; then + echo "Default toolchain file does not exist. Proceeding with running tests." + else + echo "::error::Default toolchain exists despite not installing." + exit 1 + fi + + - name: Checkout tests + uses: actions/checkout@v4 + + - name: Move rust-toolchain file to outside kani + run: | + mkdir -p ${{ github.workspace }}/../post-setup-tests + cp -r tests/cargo-ui ${{ github.workspace }}/../post-setup-tests + cp -r tests/kani/Assert ${{ github.workspace }}/../post-setup-tests + ls ${{ github.workspace }}/../post-setup-tests + + - name: Run cargo-kani tests after moving + run: | + for dir in function multiple-harnesses verbose; do + >&2 echo "Running test $dir" + pushd ${{ github.workspace }}/../post-setup-tests/cargo-ui/$dir + cargo kani + popd + done + + - name: Check --help and --version + run: | + >&2 echo "Running cargo kani --help and --version" + pushd ${{ github.workspace }}/../post-setup-tests/Assert + cargo kani --help && cargo kani --version + popd + + - name: Run standalone kani test + run: | + >&2 echo "Running test on file bool_ref" + pushd ${{ github.workspace }}/../post-setup-tests/Assert + kani bool_ref.rs + popd + test_bundle: name: TestBundle needs: [build_bundle_macos, build_bundle_linux] diff --git a/.gitignore b/.gitignore index a4cfe4ff4e2b..aae8f479aac9 100644 --- a/.gitignore +++ b/.gitignore @@ -74,10 +74,8 @@ package-lock.json tests/rustdoc-gui/src/**.lock # Before adding new lines, see the comment at the top. -/.litani_cache_dir /.ninja_deps /.ninja_log -/tests/bookrunner *Cargo.lock tests/kani-dependency-test/diamond-dependency/build tests/kani-multicrate/type-mismatch/mismatch/target diff --git a/.gitmodules b/.gitmodules index 7246d4c1e60b..b02c263a898e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,3 @@ -[submodule "src/doc/nomicon"] - path = tools/bookrunner/rust-doc/nomicon - url = https://github.com/rust-lang/nomicon.git -[submodule "src/doc/reference"] - path = tools/bookrunner/rust-doc/reference - url = https://github.com/rust-lang/reference.git -[submodule "src/doc/rust-by-example"] - path = tools/bookrunner/rust-doc/rust-by-example - url = https://github.com/rust-lang/rust-by-example.git [submodule "firecracker"] path = firecracker url = https://github.com/firecracker-microvm/firecracker.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 08f687d8e31c..bd592b2f27a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ 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.49.0] + +### What's Changed +* Disable removal of storage markers by @zhassan-aws in https://github.com/model-checking/kani/pull/3083 +* Ensure storage markers are kept in std code by @zhassan-aws in https://github.com/model-checking/kani/pull/3080 +* Implement validity checks by @celinval in https://github.com/model-checking/kani/pull/3085 +* Allow modifies clause for verification only by @feliperodri in https://github.com/model-checking/kani/pull/3098 +* Add optional scatterplot to benchcomp output by @tautschnig in https://github.com/model-checking/kani/pull/3077 +* Expand ${var} in benchcomp variant `env` by @karkhaz in https://github.com/model-checking/kani/pull/3090 +* Add `benchcomp filter` command by @karkhaz in https://github.com/model-checking/kani/pull/3105 +* Upgrade Rust toolchain to 2024-03-29 by @zhassan-aws @celinval @adpaco-aws @feliperodri + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.48.0...kani-0.49.0 + ## [0.48.0] ### Major Changes diff --git a/Cargo.lock b/Cargo.lock index 89c60b06eec7..ae2e712e1049 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,16 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - [[package]] name = "ahash" version = "0.8.11" @@ -26,9 +16,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -83,15 +73,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "bitflags" @@ -101,27 +91,13 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" - -[[package]] -name = "bookrunner" -version = "0.1.0" -dependencies = [ - "Inflector", - "pulldown-cmark", - "pulldown-cmark-escape", - "rustdoc", - "serde", - "serde_json", - "toml", - "walkdir", -] +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "build-kani" -version = "0.48.0" +version = "0.49.0" dependencies = [ "anyhow", "cargo_metadata", @@ -140,9 +116,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" dependencies = [ "serde", ] @@ -169,9 +145,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.2" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -191,14 +167,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -257,7 +233,7 @@ dependencies = [ [[package]] name = "cprover_bindings" -version = "0.48.0" +version = "0.49.0" dependencies = [ "lazy_static", "linear-map", @@ -301,7 +277,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "crossterm_winapi", "libc", "parking_lot", @@ -347,9 +323,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "getopts" @@ -392,6 +368,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "home" version = "0.5.9" @@ -403,9 +385,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", @@ -422,20 +404,20 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "kani" -version = "0.48.0" +version = "0.49.0" dependencies = [ "kani_macros", ] [[package]] name = "kani-compiler" -version = "0.48.0" +version = "0.49.0" dependencies = [ "clap", "cprover_bindings", @@ -456,7 +438,7 @@ dependencies = [ [[package]] name = "kani-driver" -version = "0.48.0" +version = "0.49.0" dependencies = [ "anyhow", "cargo_metadata", @@ -484,7 +466,7 @@ dependencies = [ [[package]] name = "kani-verifier" -version = "0.48.0" +version = "0.49.0" dependencies = [ "anyhow", "home", @@ -493,17 +475,17 @@ dependencies = [ [[package]] name = "kani_macros" -version = "0.48.0" +version = "0.49.0" dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] name = "kani_metadata" -version = "0.48.0" +version = "0.49.0" dependencies = [ "clap", "cprover_bindings", @@ -567,9 +549,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memuse" @@ -670,12 +652,12 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "os_info" -version = "3.7.0" +version = "3.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" +checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" dependencies = [ "log", - "winapi", + "windows-sys", ] [[package]] @@ -715,9 +697,9 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "ppv-lite86" @@ -751,30 +733,13 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] -[[package]] -name = "pulldown-cmark" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce76ce678ffc8e5675b22aa1405de0b7037e2fdf8913fea40d1926c6fe1e6e7" -dependencies = [ - "bitflags 2.4.2", - "memchr", - "unicase", -] - -[[package]] -name = "pulldown-cmark-escape" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d8f9aa0e3cbcfaf8bf00300004ee3b72f74770f9cbac93f6928771f613276b" - [[package]] name = "quote" version = "1.0.35" @@ -816,9 +781,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -845,14 +810,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -872,7 +837,7 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -883,9 +848,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rustc-demangle" @@ -893,20 +858,13 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustdoc" -version = "0.0.0" -dependencies = [ - "pulldown-cmark", -] - [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -966,14 +924,14 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -1000,9 +958,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.32" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", "itoa", @@ -1028,13 +986,13 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "std" -version = "0.48.0" +version = "0.49.0" dependencies = [ "kani", ] @@ -1052,9 +1010,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" @@ -1074,11 +1032,11 @@ version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -1087,11 +1045,11 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -1106,9 +1064,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -1129,22 +1087,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -1159,9 +1117,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af06656561d28735e9c1cd63dfd57132c8155426aa6af24f36a00a351f88c48e" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", @@ -1180,9 +1138,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.7" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18769cd1cec395d70860ceb4d932812a0b4d06b1a4bb336745a4d21b9496e992" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ "indexmap", "serde", @@ -1210,7 +1168,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -1266,15 +1224,6 @@ dependencies = [ "tracing-serde", ] -[[package]] -name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-ident" version = "1.0.12" @@ -1289,9 +1238,9 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unsafe-libyaml" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "utf8parse" @@ -1338,15 +1287,14 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "which" -version = "6.0.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c" +checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" dependencies = [ "either", "home", - "once_cell", "rustix", - "windows-sys", + "winsafe", ] [[package]] @@ -1512,6 +1460,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "zerocopy" version = "0.7.32" @@ -1529,5 +1483,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] diff --git a/Cargo.toml b/Cargo.toml index 8d397f2b0bd4..e271a3650c03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-verifier" -version = "0.48.0" +version = "0.49.0" edition = "2021" description = "A bit-precise model checker for Rust." readme = "README.md" @@ -38,14 +38,11 @@ strip = "debuginfo" members = [ "library/kani", "library/std", - "tools/bookrunner", "tools/compiletest", "tools/build-kani", "kani-driver", "kani-compiler", "kani_metadata", - # `librustdoc` is still needed by bookrunner. - "tools/bookrunner/librustdoc", ] # This indicates what package to e.g. build with 'cargo build' without --workspace diff --git a/cprover_bindings/Cargo.toml b/cprover_bindings/Cargo.toml index b9d0259b3577..ed0e57847e71 100644 --- a/cprover_bindings/Cargo.toml +++ b/cprover_bindings/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "cprover_bindings" -version = "0.48.0" +version = "0.49.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 69be21a07ff5..ff7914c1a07a 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -31,7 +31,6 @@ - [cargo kani assess](./dev-assess.md) - [Testing](./testing.md) - [Regression testing](./regression-testing.md) - - [Book runner](./bookrunner.md) - [(Experimental) Testing with a Large Number of Repositories](./repo-crawl.md) - [Performance comparisons](./performance-comparisons.md) - [`benchcomp` command line](./benchcomp-cli.md) diff --git a/docs/src/benchcomp-conf.md b/docs/src/benchcomp-conf.md index 77236d0917bf..065143a39c2c 100644 --- a/docs/src/benchcomp-conf.md +++ b/docs/src/benchcomp-conf.md @@ -4,6 +4,45 @@ This page lists the different visualizations that are available. +## Variants + +A *variant* is a single invocation of a benchmark suite. Benchcomp runs several +variants, so that their performance can be compared later. A variant consists of +a command-line argument, working directory, and environment. Benchcomp invokes +the command using the operating system environment, updated with the keys and +values in `env`. If any values in `env` contain strings of the form `${var}`, +Benchcomp expands them to the value of the environment variable `$var`. + +```yaml +variants: + variant_1: + config: + command_line: echo "Hello, world" + directory: /tmp + env: + PATH: /my/local/directory:${PATH} +``` + + +## Filters + +After benchcomp has finished parsing the results, it writes the results to `results.yaml` by default. +Before visualizing the results (see below), benchcomp can *filter* the results by piping them into an external program. + +To filter results before visualizing them, add `filters` to the configuration file. + +```yaml +filters: + - command_line: ./scripts/remove-redundant-results.py + - command_line: cat +``` + +The value of `filters` is a list of dicts. +Currently the only legal key for each of the dicts is `command_line`. +Benchcomp invokes each `command_line` in order, passing the results as a JSON file on stdin, and interprets the stdout as a YAML-formatted modified set of results. +Filter scripts can emit either YAML (which might be more readable while developing the script), or JSON (which benchcomp will parse as a subset of YAML). + + ## Built-in visualizations The following visualizations are available; these can be added to the `visualize` list of `benchcomp.yaml`. diff --git a/docs/src/bookrunner.md b/docs/src/bookrunner.md deleted file mode 100644 index c9e647dd6552..000000000000 --- a/docs/src/bookrunner.md +++ /dev/null @@ -1,81 +0,0 @@ -# Book runner - -The [book runner](./bookrunner/index.html) is a testing tool based on [Litani](https://github.com/awslabs/aws-build-accumulator). - -The purpose of the book runner is to get data about feature coverage in Kani. -To this end, we use Rust code snippet examples from the following general Rust documentation books: - * The Rust Reference - * The Rustonomicon - * The Rust Unstable Book - * Rust By Example - -However, not all examples from these books are suited for verification. -For instance, some of them are only included to show what is valid Rust code (or what is not). - -Because of that, we run up to three different types of jobs when generating the report: - * `check` jobs: This check uses the Rust front-end to detect if the example is valid Rust code. - * `codegen` jobs: This check uses the Kani back-end to determine if we can generate GotoC code. - * `verification` jobs: This check uses CBMC to obtain a verification result. - -Note that these are incremental: A `verification` job depends on a previous `codegen` job. -Similary, a `codegen` job depends on a `check` job. - -> **NOTE**: [Litani](https://github.com/awslabs/aws-build-accumulator) does not -> support hierarchical views at the moment. For this reason, we are publishing a -> [text version of the book runner report](./bookrunner/bookrunner.txt) which -> displays the same results in a hierarchical way while we work on [improvements -> for the visualization and navigation of book runner -> results](https://github.com/model-checking/kani/issues/699). - -Before running the above mentioned jobs, we pre-process the examples to: - 1. Set the expected output according to flags present in the code snippet. - 2. Add any required compiler/Kani flags (e.g., unwinding). - -Finally, we run all jobs, collect their outputs and compare them against the expected outputs. -The results are summarized as follows: If the obtained and expected outputs differ, -the color of the stage bar will be red. Otherwise, it will be blue. -If an example shows one red bar, it's considered a failed example that cannot be handled by Kani. - -The [book runner report](./bookrunner/index.html) and [its text version](./bookrunner/bookrunner.txt) are -automatically updated whenever a PR gets merged into Kani. - -## The book running procedure - -This section describes how the book runner operates at a high level. - -To kick off the book runner process use: - -```bash -cargo run -p bookrunner -``` - -The main function of the bookrunner is `generate_run()` (code available -[here](https://github.com/model-checking/kani/blob/main/tools/bookrunner/src/books.rs)) -which follows these steps: - 1. Sets up all the books, including data about their summaries. - 2. Then, for each book: - * Calls the `parse_hierarchy()` method to parse its summary - files. - * Calls the `extract_examples()` method to extract all - examples from the book. Note that `extract_examples()` uses `rustdoc` - functions to ensure the extracted examples are runnable. - * Checks if there is a corresponding `.props` file - in `src/tools/bookrunner/configs/`. If there is, prepends the contents of these files - ([testing options](./regression-testing.md#testing-options)) to the example. - * The resulting examples are written to the `src/test/bookrunner/books/` folder. - -> In general, the path to a given example is -> `src/test/bookrunner/books///
//.rs` -> where `` is the line number where the example appears in the markdown -> file where it's written. The `.props` files mentioned above follow the same -> naming scheme in order to match them and detect conflicts. - - 3. Runs all examples using - [Litani](https://github.com/awslabs/aws-build-accumulator) with the - `litani_run_tests()` function. - 4. Parses the Litani log file with `parse_litani_output(...)`. - 5. Generates the [text version of the bookrunner](./bookrunner/bookrunner.txt) - with `generate_text_bookrunner(...)`. - -> **NOTE**: Any changes done to the examples in `src/test/bookrunner/books/` may -> be overwritten if the bookrunner is executed. diff --git a/docs/src/cheat-sheets.md b/docs/src/cheat-sheets.md index 95cc9991e46f..9b42d313ede2 100644 --- a/docs/src/cheat-sheets.md +++ b/docs/src/cheat-sheets.md @@ -19,7 +19,7 @@ cargo build-dev ### Test ```bash -# Full regression suite (does not run bookrunner) +# Full regression suite ./scripts/kani-regression.sh ``` @@ -39,12 +39,6 @@ rm -r build/x86_64-apple-darwin/tests/ cargo run -p compiletest -- --suite kani --mode kani ``` -```bash -# Run bookrunner -./scripts/setup/install_bookrunner_deps.sh -cargo run -p bookrunner -``` - ```bash # Build documentation cd docs diff --git a/docs/src/getting-started/verification-results/src/main.rs b/docs/src/getting-started/verification-results/src/main.rs index 7a03b34f0f9e..72653cf4dc8f 100644 --- a/docs/src/getting-started/verification-results/src/main.rs +++ b/docs/src/getting-started/verification-results/src/main.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT #[kani::proof] +#[kani::unwind(4)] // ANCHOR: success_example fn success_example() { let mut sum = 0; diff --git a/docs/src/rust-feature-support/intrinsics.md b/docs/src/rust-feature-support/intrinsics.md index 7df1a093943f..49a1f0845ecc 100644 --- a/docs/src/rust-feature-support/intrinsics.md +++ b/docs/src/rust-feature-support/intrinsics.md @@ -220,6 +220,7 @@ truncf64 | Yes | | try | No | [#267](https://github.com/model-checking/kani/issues/267) | type_id | Yes | | type_name | Yes | | +typed_swap | Yes | | unaligned_volatile_load | No | See [Notes - Concurrency](#concurrency) | unaligned_volatile_store | No | See [Notes - Concurrency](#concurrency) | unchecked_add | Yes | | diff --git a/docs/src/testing.md b/docs/src/testing.md index 21438e861444..85e1a46838bf 100644 --- a/docs/src/testing.md +++ b/docs/src/testing.md @@ -15,6 +15,5 @@ two very good reasons to do it: We recommend reading our section on [Regression Testing](./regression-testing.md) if you're interested in Kani -development. At present, we obtain metrics based on the [book -runner](./bookrunner.md). To run kani on a large number of remotely +development. To run kani on a large number of remotely hosted crates, please see [Repository Crawl](./repo-crawl.md). diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index a5b7fd006180..ffc508e90866 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-compiler" -version = "0.48.0" +version = "0.49.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/kani-compiler/src/args.rs b/kani-compiler/src/args.rs index 69a82b61e19d..225cb94f4264 100644 --- a/kani-compiler/src/args.rs +++ b/kani-compiler/src/args.rs @@ -71,4 +71,18 @@ pub struct Arguments { #[clap(long)] /// A legacy flag that is now ignored. goto_c: bool, + /// Enable specific checks. + #[clap(long)] + pub ub_check: Vec, + /// Ignore storage markers. + #[clap(long)] + pub ignore_storage_markers: bool, +} + +#[derive(Debug, Clone, Copy, AsRefStr, EnumString, VariantNames, PartialEq, Eq)] +#[strum(serialize_all = "snake_case")] +pub enum ExtraChecks { + /// Check that produced values are valid except for uninitialized values. + /// See https://github.com/model-checking/kani/issues/920. + Validity, } diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs index 7cf4b979f407..d55696bfdc87 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs @@ -60,7 +60,7 @@ impl<'tcx> GotocCtx<'tcx> { debug!("Double codegen of {:?}", old_sym); } else { assert!(old_sym.is_function()); - let body = instance.body().unwrap(); + let body = self.transformer.body(self.tcx, instance); self.set_current_fn(instance, &body); self.print_instance(instance, &body); self.codegen_function_prelude(&body); @@ -201,7 +201,7 @@ impl<'tcx> GotocCtx<'tcx> { pub fn declare_function(&mut self, instance: Instance) { debug!("declaring {}; {:?}", instance.name(), instance); - let body = instance.body().unwrap(); + let body = self.transformer.body(self.tcx, instance); self.set_current_fn(instance, &body); debug!(krate=?instance.def.krate(), is_std=self.current_fn().is_std(), "declare_function"); self.ensure(&self.symbol_name_stable(instance), |ctx, fname| { diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs index 2cabc4cfd273..8a61d46ed518 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs @@ -69,7 +69,7 @@ impl<'tcx> GotocCtx<'tcx> { /// Handles codegen for non returning intrinsics /// Non returning intrinsics are not associated with a destination pub fn codegen_never_return_intrinsic(&mut self, instance: Instance, span: Span) -> Stmt { - let intrinsic = instance.mangled_name(); + let intrinsic = instance.intrinsic_name().unwrap(); debug!("codegen_never_return_intrinsic:\n\tinstance {:?}\n\tspan {:?}", instance, span); @@ -112,8 +112,8 @@ impl<'tcx> GotocCtx<'tcx> { place: &Place, span: Span, ) -> Stmt { - let intrinsic_sym = instance.trimmed_name(); - let intrinsic = intrinsic_sym.as_str(); + let intrinsic_name = instance.intrinsic_name().unwrap(); + let intrinsic = intrinsic_name.as_str(); let loc = self.codegen_span_stable(span); debug!(?instance, "codegen_intrinsic"); debug!(?fargs, "codegen_intrinsic"); @@ -288,23 +288,6 @@ impl<'tcx> GotocCtx<'tcx> { }}; } - /// Gets the basename of an intrinsic given its trimmed name. - /// - /// For example, given `arith_offset::` this returns `arith_offset`. - fn intrinsic_basename(name: &str) -> &str { - let scope_sep_count = name.matches("::").count(); - // We expect at most one `::` separator from trimmed intrinsic names - debug_assert!( - scope_sep_count < 2, - "expected at most one `::` in intrinsic name, but found {scope_sep_count} in `{name}`" - ); - let name_split = name.split_once("::"); - if let Some((base_name, _type_args)) = name_split { base_name } else { name } - } - // The trimmed name includes type arguments if the intrinsic was defined - // on generic types, but we only need the basename for the match below. - let intrinsic = intrinsic_basename(intrinsic); - if let Some(stripped) = intrinsic.strip_prefix("simd_shuffle") { assert!(fargs.len() == 3, "`simd_shuffle` had unexpected arguments {fargs:?}"); let n: u64 = self.simd_shuffle_length(stripped, farg_types, span); @@ -597,6 +580,7 @@ impl<'tcx> GotocCtx<'tcx> { "truncf64" => codegen_simple_intrinsic!(Trunc), "type_id" => codegen_intrinsic_const!(), "type_name" => codegen_intrinsic_const!(), + "typed_swap" => self.codegen_swap(fargs, farg_types, loc), "unaligned_volatile_load" => { unstable_codegen!( self.codegen_expr_to_place_stable(place, fargs.remove(0).dereference()) @@ -1960,6 +1944,47 @@ impl<'tcx> GotocCtx<'tcx> { let zero = Type::size_t().zero(); cast_ptr.rem(align).eq(zero) } + + /// Swaps the memory contents pointed to by arguments `x` and `y`, respectively, which is + /// required for the `typed_swap` intrinsic. + /// + /// The standard library API requires that `x` and `y` are readable and writable as their + /// (common) type (which auto-generated checks for dereferencing will take care of), and the + /// memory regions pointed to must be non-overlapping. + pub fn codegen_swap(&mut self, mut fargs: Vec, farg_types: &[Ty], loc: Location) -> Stmt { + // two parameters, and both must be raw pointers with the same base type + assert!(fargs.len() == 2); + assert!(farg_types[0].kind().is_raw_ptr()); + assert!(farg_types[0] == farg_types[1]); + + let x = fargs.remove(0); + let y = fargs.remove(0); + + // if(same_object(x, y)) { + // assert(x + 1 <= y || y + 1 <= x); + // assume(x + 1 <= y || y + 1 <= x); + // } + let one = Expr::int_constant(1, Type::c_int()); + let non_overlapping = + x.clone().plus(one.clone()).le(y.clone()).or(y.clone().plus(one.clone()).le(x.clone())); + let non_overlapping_check = self.codegen_assert_assume( + non_overlapping, + PropertyClass::SafetyCheck, + "memory regions pointed to by `x` and `y` must not overlap", + loc, + ); + let non_overlapping_stmt = + Stmt::if_then_else(x.clone().same_object(y.clone()), non_overlapping_check, None, loc); + + // T t = *y; *y = *x; *x = t; + let deref_y = y.clone().dereference(); + let (temp_var, assign_to_t) = + self.decl_temp_variable(deref_y.typ().clone(), Some(deref_y), loc); + let assign_to_y = y.dereference().assign(x.clone().dereference(), loc); + let assign_to_x = x.dereference().assign(temp_var, loc); + + Stmt::block(vec![non_overlapping_stmt, assign_to_t, assign_to_y, assign_to_x], loc) + } } fn instance_args(instance: &Instance) -> GenericArgs { diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs index 5867448d9973..74622d41ed50 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs @@ -725,7 +725,7 @@ impl<'tcx> GotocCtx<'tcx> { .bytes(), Type::size_t(), ), - NullOp::UbCheck(_) => Expr::c_false(), + NullOp::UbChecks => Expr::c_false(), } } Rvalue::ShallowInitBox(ref operand, content_ty) => { diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs index 6379a023423f..9a78515fde90 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs @@ -76,9 +76,19 @@ impl<'tcx> GotocCtx<'tcx> { self.codegen_set_discriminant(dest_ty, dest_expr, *variant_index, location) } StatementKind::StorageLive(var_id) => { - Stmt::decl(self.codegen_local(*var_id), None, location) + if self.queries.args().ignore_storage_markers { + Stmt::skip(location) + } else { + Stmt::decl(self.codegen_local(*var_id), None, location) + } + } + StatementKind::StorageDead(var_id) => { + if self.queries.args().ignore_storage_markers { + Stmt::skip(location) + } else { + Stmt::dead(self.codegen_local(*var_id), location) + } } - StatementKind::StorageDead(var_id) => Stmt::dead(self.codegen_local(*var_id), location), StatementKind::Intrinsic(NonDivergingIntrinsic::CopyNonOverlapping( CopyNonOverlapping { src, dst, count }, )) => { diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs index 089d871d22b4..b5ccf1a83716 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs @@ -570,7 +570,7 @@ impl<'tcx> GotocCtx<'tcx> { // Note: This is not valid C but CBMC seems to be ok with it. 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::Ref(_, t, _) | ty::RawPtr(t, _) => self.codegen_ty_ref(*t), ty::FnDef(def_id, args) => { let instance = Instance::resolve(self.tcx, ty::ParamEnv::reveal_all(), *def_id, args) @@ -993,7 +993,7 @@ impl<'tcx> GotocCtx<'tcx> { | ty::Foreign(_) | ty::Coroutine(..) | ty::Int(_) - | ty::RawPtr(_) + | ty::RawPtr(_, _) | ty::Ref(..) | ty::Tuple(_) | ty::Uint(_) => self.codegen_ty(pointee_type).to_pointer(), @@ -1352,10 +1352,7 @@ impl<'tcx> GotocCtx<'tcx> { // `F16` and `F128` are not yet handled. // Tracked here: Primitive::F16 | Primitive::F128 => unimplemented!(), - Primitive::Pointer(_) => Ty::new_ptr( - self.tcx, - ty::TypeAndMut { ty: self.tcx.types.u8, mutbl: Mutability::Not }, - ), + Primitive::Pointer(_) => Ty::new_ptr(self.tcx, self.tcx.types.u8, Mutability::Not), } } @@ -1659,7 +1656,7 @@ fn common_vtable_fields(drop_in_place: Type) -> Vec { pub fn pointee_type(mir_type: Ty) -> Option { match mir_type.kind() { ty::Ref(_, pointee_type, _) => Some(*pointee_type), - ty::RawPtr(ty::TypeAndMut { ty: pointee_type, .. }) => Some(*pointee_type), + ty::RawPtr(pointee_type, _) => Some(*pointee_type), _ => None, } } diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index cad8bcbfb9ce..039c50eefe18 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -12,6 +12,7 @@ use crate::kani_middle::provide; use crate::kani_middle::reachability::{ collect_reachable_items, filter_const_crate_items, filter_crate_items, }; +use crate::kani_middle::transform::BodyTransformation; use crate::kani_middle::{check_reachable_items, dump_mir_items}; use crate::kani_queries::QueryDb; use cbmc::goto_program::Location; @@ -86,16 +87,18 @@ impl GotocCodegenBackend { symtab_goto: &Path, machine_model: &MachineModel, check_contract: Option, + mut transformer: BodyTransformation, ) -> (GotocCtx<'tcx>, Vec, Option) { let items = with_timer( - || collect_reachable_items(tcx, starting_items), + || collect_reachable_items(tcx, &mut transformer, starting_items), "codegen reachability analysis", ); dump_mir_items(tcx, &items, &symtab_goto.with_extension("kani.mir")); // Follow rustc naming convention (cx is abbrev for context). // https://rustc-dev-guide.rust-lang.org/conventions.html#naming-conventions - let mut gcx = GotocCtx::new(tcx, (*self.queries.lock().unwrap()).clone(), machine_model); + let mut gcx = + GotocCtx::new(tcx, (*self.queries.lock().unwrap()).clone(), machine_model, transformer); check_reachable_items(gcx.tcx, &gcx.queries, &items); let contract_info = with_timer( @@ -225,8 +228,10 @@ impl CodegenBackend for GotocCodegenBackend { // - 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 base_filepath = tcx.output_filenames(()).path(OutputType::Object); + let base_filename = base_filepath.as_path(); let reachability = queries.args().reachability_analysis; + let mut transformer = BodyTransformation::new(&queries, tcx); let mut results = GotoCodegenResults::new(tcx, reachability); match reachability { ReachabilityType::Harnesses => { @@ -248,8 +253,9 @@ impl CodegenBackend for GotocCodegenBackend { model_path, &results.machine_model, contract_metadata, + transformer, ); - results.extend(gcx, items, None); + transformer = results.extend(gcx, items, None); if let Some(assigns_contract) = contract_info { self.queries.lock().unwrap().register_assigns_contract( canonical_mangled_name(harness).intern(), @@ -263,7 +269,7 @@ impl CodegenBackend for GotocCodegenBackend { // 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, |_, item| { + let harnesses = filter_const_crate_items(tcx, &mut transformer, |_, item| { if is_test_harness_description(tcx, item.def) { descriptions.push(item.def); true @@ -282,6 +288,7 @@ impl CodegenBackend for GotocCodegenBackend { &model_path, &results.machine_model, Default::default(), + transformer, ); results.extend(gcx, items, None); @@ -319,9 +326,10 @@ impl CodegenBackend for GotocCodegenBackend { &model_path, &results.machine_model, Default::default(), + transformer, ); assert!(contract_info.is_none()); - results.extend(gcx, items, None); + let _ = results.extend(gcx, items, None); } } @@ -405,7 +413,8 @@ impl CodegenBackend for GotocCodegenBackend { builder.build(&out_path); } else { // Write the location of the kani metadata file in the requested compiler output file. - let base_filename = outputs.output_path(OutputType::Object); + let base_filepath = outputs.path(OutputType::Object); + let base_filename = base_filepath.as_path(); let content_stub = CompilerArtifactStub { metadata_path: base_filename.with_extension(ArtifactType::Metadata), }; @@ -613,12 +622,18 @@ impl GotoCodegenResults { } } - fn extend(&mut self, gcx: GotocCtx, items: Vec, metadata: Option) { + fn extend( + &mut self, + gcx: GotocCtx, + items: Vec, + metadata: Option, + ) -> BodyTransformation { let mut items = items; self.harnesses.extend(metadata); self.concurrent_constructs.extend(gcx.concurrent_constructs); self.unsupported_constructs.extend(gcx.unsupported_constructs); self.items.append(&mut items); + gcx.transformer } /// Prints a report at the end of the compilation. 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 10ee6a876588..3a6501d544d8 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs @@ -18,11 +18,11 @@ use super::vtable_ctx::VtableCtx; use crate::codegen_cprover_gotoc::overrides::{fn_hooks, GotocHooks}; use crate::codegen_cprover_gotoc::utils::full_crate_name; use crate::codegen_cprover_gotoc::UnsupportedConstructs; +use crate::kani_middle::transform::BodyTransformation; use crate::kani_queries::QueryDb; use cbmc::goto_program::{DatatypeComponent, Expr, Location, Stmt, Symbol, SymbolTable, Type}; use cbmc::utils::aggr_tag; use cbmc::{InternedString, MachineModel}; -use kani_metadata::HarnessMetadata; use rustc_data_structures::fx::FxHashMap; use rustc_middle::span_bug; use rustc_middle::ty::layout::{ @@ -60,8 +60,6 @@ pub struct GotocCtx<'tcx> { /// map from symbol identifier to string literal /// TODO: consider making the map from Expr to String instead pub str_literals: FxHashMap, - pub proof_harnesses: Vec, - pub test_harnesses: Vec, /// a global counter for generating unique IDs for checks pub global_checks_count: u64, /// A map of unsupported constructs that were found while codegen @@ -70,6 +68,8 @@ pub struct GotocCtx<'tcx> { /// We collect them and print one warning at the end if not empty instead of printing one /// warning at each occurrence. pub concurrent_constructs: UnsupportedConstructs, + /// The body transformation agent. + pub transformer: BodyTransformation, } /// Constructor @@ -78,6 +78,7 @@ impl<'tcx> GotocCtx<'tcx> { tcx: TyCtxt<'tcx>, queries: QueryDb, machine_model: &MachineModel, + transformer: BodyTransformation, ) -> GotocCtx<'tcx> { let fhks = fn_hooks(); let symbol_table = SymbolTable::new(machine_model.clone()); @@ -94,11 +95,10 @@ impl<'tcx> GotocCtx<'tcx> { current_fn: None, type_map: FxHashMap::default(), str_literals: FxHashMap::default(), - proof_harnesses: vec![], - test_harnesses: vec![], global_checks_count: 0, unsupported_constructs: FxHashMap::default(), concurrent_constructs: FxHashMap::default(), + transformer, } } } diff --git a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs index 2fcbaa36fb44..18cc44b7b20d 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs @@ -10,6 +10,7 @@ use crate::codegen_cprover_gotoc::codegen::{bb_label, PropertyClass}; use crate::codegen_cprover_gotoc::GotocCtx; +use crate::kani_middle::attributes::matches_diagnostic as matches_function; use crate::unwrap_or_return_codegen_unimplemented_stmt; use cbmc::goto_program::{BuiltinFn, Expr, Location, Stmt, Type}; use rustc_middle::ty::TyCtxt; @@ -35,17 +36,6 @@ pub trait GotocHook { ) -> 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 rustc_internal::internal(tcx, instance.def.def_id()) == *attr_id { - debug!("matched: {:?} {:?}", attr_id, attr_sym); - return true; - } - } - false -} - /// A hook for Kani's `cover` function (declared in `library/kani/src/lib.rs`). /// The function takes two arguments: a condition expression (bool) and a /// message (&'static str). @@ -57,7 +47,7 @@ fn matches_function(tcx: TyCtxt, instance: Instance, attr_name: &str) -> bool { struct Cover; impl GotocHook for Cover { fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool { - matches_function(tcx, instance, "KaniCover") + matches_function(tcx, instance.def, "KaniCover") } fn handle( @@ -92,7 +82,7 @@ impl GotocHook for Cover { struct Assume; impl GotocHook for Assume { fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool { - matches_function(tcx, instance, "KaniAssume") + matches_function(tcx, instance.def, "KaniAssume") } fn handle( @@ -116,7 +106,7 @@ impl GotocHook for Assume { struct Assert; impl GotocHook for Assert { fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool { - matches_function(tcx, instance, "KaniAssert") + matches_function(tcx, instance.def, "KaniAssert") } fn handle( @@ -157,7 +147,7 @@ struct Nondet; impl GotocHook for Nondet { fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool { - matches_function(tcx, instance, "KaniAnyRaw") + matches_function(tcx, instance.def, "KaniAnyRaw") } fn handle( @@ -201,7 +191,7 @@ impl GotocHook for Panic { || 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") + || matches_function(tcx, instance.def, "KaniPanic") } fn handle( @@ -221,7 +211,7 @@ impl GotocHook for Panic { struct IsReadOk; impl GotocHook for IsReadOk { fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool { - matches_function(tcx, instance, "KaniIsReadOk") + matches_function(tcx, instance.def, "KaniIsReadOk") } fn handle( @@ -365,7 +355,7 @@ struct UntrackedDeref; impl GotocHook for UntrackedDeref { fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool { - matches_function(tcx, instance, "KaniUntrackedDeref") + matches_function(tcx, instance.def, "KaniUntrackedDeref") } fn handle( diff --git a/kani-compiler/src/kani_compiler.rs b/kani-compiler/src/kani_compiler.rs index 48b4318db5bf..fc5f5891ecae 100644 --- a/kani-compiler/src/kani_compiler.rs +++ b/kani-compiler/src/kani_compiler.rs @@ -304,7 +304,8 @@ impl KaniCompiler { }; if self.queries.lock().unwrap().args().reachability_analysis == ReachabilityType::Harnesses { - let base_filename = tcx.output_filenames(()).output_path(OutputType::Object); + let base_filepath = tcx.output_filenames(()).path(OutputType::Object); + let base_filename = base_filepath.as_path(); let harnesses = filter_crate_items(tcx, |_, instance| is_proof_harness(tcx, instance)); let all_harnesses = harnesses .into_iter() @@ -376,7 +377,7 @@ impl KaniCompiler { /// Write the metadata to a file fn store_metadata(&self, metadata: &KaniMetadata, filename: &Path) { - debug!(?filename, "write_metadata"); + debug!(?filename, "store_metadata"); let out_file = File::create(filename).unwrap(); let writer = BufWriter::new(out_file); if self.queries.lock().unwrap().args().output_pretty_json { @@ -457,9 +458,9 @@ fn generate_metadata( /// 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 + let filepath = tcx.output_filenames(()).path(OutputType::Object); + let filename = filepath.as_path(); + filename.with_extension(ArtifactType::Metadata).to_path_buf() } #[cfg(test)] diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index df494d7daa3c..4f9dce49af95 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -70,6 +70,10 @@ enum KaniAttributeKind { /// expanded with additional pointer arguments that are not used in the function /// but referenced by the `modifies` annotation. InnerCheck, + /// Attribute used to mark contracts for functions with recursion. + /// We use this attribute to properly instantiate `kani::any_modifies` in + /// cases when recursion is present given our contracts instrumentation. + Recursion, } impl KaniAttributeKind { @@ -84,6 +88,7 @@ impl KaniAttributeKind { | KaniAttributeKind::StubVerified | KaniAttributeKind::Unwind => true, KaniAttributeKind::Unstable + | KaniAttributeKind::Recursion | KaniAttributeKind::ReplacedWith | KaniAttributeKind::CheckedWith | KaniAttributeKind::Modifies @@ -102,13 +107,6 @@ impl KaniAttributeKind { 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 @@ -200,6 +198,14 @@ impl<'tcx> KaniAttributes<'tcx> { .collect() } + pub(crate) fn is_contract_generated(&self) -> bool { + self.map.contains_key(&KaniAttributeKind::IsContractGenerated) + } + + pub(crate) fn has_recursion(&self) -> bool { + self.map.contains_key(&KaniAttributeKind::Recursion) + } + /// 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). @@ -316,6 +322,12 @@ impl<'tcx> KaniAttributes<'tcx> { expect_no_args(self.tcx, kind, attr); }) } + KaniAttributeKind::Recursion => { + 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| { @@ -452,6 +464,9 @@ impl<'tcx> KaniAttributes<'tcx> { self.map.iter().fold(HarnessAttributes::default(), |mut harness, (kind, attributes)| { match kind { KaniAttributeKind::ShouldPanic => harness.should_panic = true, + KaniAttributeKind::Recursion => { + self.tcx.dcx().span_err(self.tcx.def_span(self.item), "The attribute `kani::recursion` should only be used in combination with function contracts."); + }, KaniAttributeKind::Solver => { harness.solver = parse_solver(self.tcx, attributes[0]); } @@ -661,12 +676,6 @@ fn has_kani_attribute 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, instance: InstanceStable) -> bool { @@ -1037,3 +1046,14 @@ fn attr_kind(tcx: TyCtxt, attr: &Attribute) -> Option { _ => None, } } + +pub fn matches_diagnostic(tcx: TyCtxt, def: T, 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 rustc_internal::internal(tcx, def.def_id()) == *attr_id { + debug!("matched: {:?} {:?}", attr_id, attr_sym); + return true; + } + } + false +} diff --git a/kani-compiler/src/kani_middle/mod.rs b/kani-compiler/src/kani_middle/mod.rs index e65befc9624e..17992953d5c7 100644 --- a/kani-compiler/src/kani_middle/mod.rs +++ b/kani-compiler/src/kani_middle/mod.rs @@ -21,10 +21,8 @@ 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::mir::mono::{Instance, InstanceKind, MonoItem}; -use stable_mir::mir::pretty::pretty_ty; -use stable_mir::ty::{BoundVariableKind, RigidTy, Span as SpanStable, Ty, TyKind}; -use stable_mir::visitor::{Visitable, Visitor as TypeVisitor}; +use stable_mir::mir::mono::{InstanceKind, MonoItem}; +use stable_mir::ty::{FnDef, RigidTy, Span as SpanStable, TyKind}; use stable_mir::{CrateDef, DefId}; use std::fs::File; use std::io::BufWriter; @@ -41,13 +39,14 @@ pub mod provide; pub mod reachability; pub mod resolve; pub mod stubbing; +pub mod transform; /// Check that all crate 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_crate_items(tcx: TyCtxt, ignore_asm: bool) { let krate = tcx.crate_name(LOCAL_CRATE); - for item in tcx.hir_crate_items(()).items() { + for item in tcx.hir().items() { let def_id = item.owner_id.def_id.to_def_id(); KaniAttributes::for_item(tcx, def_id).check_attributes(); if tcx.def_kind(def_id) == DefKind::GlobalAsm { @@ -88,108 +87,10 @@ pub fn check_reachable_items(tcx: TyCtxt, queries: &QueryDb, items: &[MonoItem]) .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, - rustc_internal::internal(tcx, def_id), - ) { - check_is_contract_safe(tcx, *instance); - } - } } tcx.dcx().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: TyCtxt, instance: Instance) { - struct NoMutPtr<'tcx> { - tcx: TyCtxt<'tcx>, - is_prohibited: fn(Ty) -> 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> { - type Break = (); - fn visit_ty(&mut self, ty: &Ty) -> std::ops::ControlFlow { - if (self.is_prohibited)(*ty) { - // TODO make this more user friendly - self.tcx.dcx().err(format!( - "{} contains a {}pointer ({}). This is prohibited for functions with contracts, \ - as they cannot yet reason about the pointer behavior.", self.r#where, self.what, - pretty_ty(ty.kind()))); - } - - // 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 TyKind::RigidTy(RigidTy::Adt(adt_def, generics)) = ty.kind() { - for variant in adt_def.variants() { - for field in &variant.fields() { - self.visit_ty(&field.ty_with_args(&generics))?; - } - } - std::ops::ControlFlow::Continue(()) - } else { - // For every other type. - ty.super_visit(self) - } - } - } - - fn is_raw_mutable_ptr(ty: Ty) -> bool { - let kind = ty.kind(); - kind.is_raw_ptr() && kind.is_mutable_ptr() - } - - fn is_raw_ptr(ty: Ty) -> bool { - let kind = ty.kind(); - kind.is_raw_ptr() - } - - // TODO: Replace this with fn_abi. - // https://github.com/model-checking/kani/issues/1365 - let bound_fn_sig = instance.ty().kind().fn_sig().unwrap(); - - for var in &bound_fn_sig.bound_vars { - if let BoundVariableKind::Ty(t) = var { - tcx.dcx().span_err( - rustc_internal::internal(tcx, instance.def.span()), - format!("Found a bound type variable {t:?} after monomorphization"), - ); - } - } - - let fn_typ = bound_fn_sig.skip_binder(); - - for (input_ty, (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(), (is_raw_ptr as fn(_) -> _, "The return", ""))]) - { - let mut v = NoMutPtr { tcx, is_prohibited, r#where, what }; - v.visit_ty(&input_ty); - } -} - /// 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. @@ -226,9 +127,11 @@ pub fn dump_mir_items(tcx: TyCtxt, items: &[MonoItem], output: &Path) { pub struct SourceLocation { pub filename: String, pub start_line: usize, - pub start_col: usize, + #[allow(dead_code)] + pub start_col: usize, // set, but not currently used in Goto output pub end_line: usize, - pub end_col: usize, + #[allow(dead_code)] + pub end_col: usize, // set, but not currently used in Goto output } impl SourceLocation { @@ -316,3 +219,18 @@ impl<'tcx> FnAbiOfHelpers<'tcx> for CompilerHelpers<'tcx> { } } } + +/// Find an instance of a function from the given crate that has been annotated with `diagnostic` +/// item. +fn find_fn_def(tcx: TyCtxt, diagnostic: &str) -> Option { + let attr_id = tcx + .all_diagnostic_items(()) + .name_to_id + .get(&rustc_span::symbol::Symbol::intern(diagnostic))?; + let TyKind::RigidTy(RigidTy::FnDef(def, _)) = + rustc_internal::stable(tcx.type_of(attr_id)).value.kind() + else { + return None; + }; + Some(def) +} diff --git a/kani-compiler/src/kani_middle/provide.rs b/kani-compiler/src/kani_middle/provide.rs index d5495acb67a7..d9197493f4db 100644 --- a/kani-compiler/src/kani_middle/provide.rs +++ b/kani-compiler/src/kani_middle/provide.rs @@ -8,12 +8,15 @@ 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_middle::transform::BodyTransformation; use crate::kani_queries::QueryDb; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_middle::util::Providers; use rustc_middle::{mir::Body, query::queries, ty::TyCtxt}; use stable_mir::mir::mono::MonoItem; +use crate::kani_middle::KaniAttributes; + /// Sets up rustc's query mechanism to apply Kani's custom queries to code from /// a crate. pub fn provide(providers: &mut Providers, queries: &QueryDb) { @@ -60,6 +63,17 @@ fn run_kani_mir_passes<'tcx>( tracing::debug!(?def_id, "Run Kani transformation passes"); let mut transformed_body = stubbing::transform(tcx, def_id, body); stubbing::transform_foreign_functions(tcx, &mut transformed_body); + let item_attributes = KaniAttributes::for_item(tcx, def_id); + // If we apply `transform_any_modifies` in all contract-generated items, + // we will ended up instantiating `kani::any_modifies` for the replace function + // every time, even if we are only checking the contract, because the function + // is always included during contract instrumentation. Thus, we must only apply + // the transformation if we are using a verified stub or in the presence of recursion. + if item_attributes.is_contract_generated() + && (stubbing::get_stub_key(tcx, def_id).is_some() || item_attributes.has_recursion()) + { + stubbing::transform_any_modifies(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) @@ -79,8 +93,9 @@ fn collect_and_partition_mono_items( rustc_smir::rustc_internal::run(tcx, || { let local_reachable = filter_crate_items(tcx, |_, _| true).into_iter().map(MonoItem::Fn).collect::>(); + // We do not actually need the value returned here. - collect_reachable_items(tcx, &local_reachable); + collect_reachable_items(tcx, &mut BodyTransformation::dummy(), &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 2fa1bf057c1c..456d5425b245 100644 --- a/kani-compiler/src/kani_middle/reachability.rs +++ b/kani-compiler/src/kani_middle/reachability.rs @@ -25,7 +25,6 @@ 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, @@ -34,15 +33,21 @@ use stable_mir::ty::{Allocation, ClosureKind, ConstantKind, RigidTy, Ty, TyKind} use stable_mir::CrateItem; use stable_mir::{CrateDef, ItemKind}; +use crate::kani_middle::attributes::matches_diagnostic as matches_function; use crate::kani_middle::coercion; use crate::kani_middle::coercion::CoercionBase; use crate::kani_middle::stubbing::{get_stub, validate_instance}; +use crate::kani_middle::transform::BodyTransformation; /// Collect all reachable items starting from the given starting points. -pub fn collect_reachable_items(tcx: TyCtxt, starting_points: &[MonoItem]) -> Vec { +pub fn collect_reachable_items( + tcx: TyCtxt, + transformer: &mut BodyTransformation, + starting_points: &[MonoItem], +) -> 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); + let mut collector = MonoItemsCollector::new(tcx, transformer); for item in starting_points { collector.collect(item.clone()); } @@ -92,7 +97,11 @@ where /// /// 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, + transformer: &mut BodyTransformation, + mut predicate: F, +) -> Vec where F: FnMut(TyCtxt, Instance) -> bool, { @@ -103,7 +112,7 @@ where // Only collect monomorphic items. if let Ok(instance) = Instance::try_from(item) { if predicate(tcx, instance) { - let body = instance.body().unwrap(); + let body = transformer.body(tcx, instance); let mut collector = MonoItemsFnCollector { tcx, body: &body, @@ -118,9 +127,11 @@ where roots } -struct MonoItemsCollector<'tcx> { +struct MonoItemsCollector<'tcx, 'a> { /// The compiler context. tcx: TyCtxt<'tcx>, + /// The body transformation object used to retrieve a transformed body. + transformer: &'a mut BodyTransformation, /// Set of collected items used to avoid entering recursion loops. collected: FxHashSet, /// Items enqueued for visiting. @@ -129,14 +140,15 @@ struct MonoItemsCollector<'tcx> { call_graph: debug::CallGraph, } -impl<'tcx> MonoItemsCollector<'tcx> { - pub fn new(tcx: TyCtxt<'tcx>) -> Self { +impl<'tcx, 'a> MonoItemsCollector<'tcx, 'a> { + pub fn new(tcx: TyCtxt<'tcx>, transformer: &'a mut BodyTransformation) -> Self { MonoItemsCollector { tcx, collected: FxHashSet::default(), queue: vec![], #[cfg(debug_assertions)] call_graph: debug::CallGraph::default(), + transformer, } } @@ -174,7 +186,7 @@ impl<'tcx> MonoItemsCollector<'tcx> { fn visit_fn(&mut self, instance: Instance) -> Vec { let _guard = debug_span!("visit_fn", function=?instance).entered(); if validate_instance(self.tcx, instance) { - let body = instance.body().unwrap(); + let body = self.transformer.body(self.tcx, instance); let mut collector = MonoItemsFnCollector { tcx: self.tcx, collected: FxHashSet::default(), @@ -421,13 +433,28 @@ impl<'a, 'tcx> MirVisitor for MonoItemsFnCollector<'a, 'tcx> { `{}`. The function `{}` \ cannot be stubbed by `{}` due to \ generic bounds not being met. Callee: {}", - pretty_ty(receiver_ty.kind()), + receiver_ty, trait_, caller, self.tcx.def_path_str(stub), callee, ), ); + } else if matches_function(self.tcx, self.instance.def, "KaniAny") { + let receiver_ty = args.0[0].expect_ty(); + let sep = callee.rfind("::").unwrap(); + let trait_ = &callee[..sep]; + self.tcx.dcx().span_err( + rustc_internal::internal(self.tcx, terminator.span), + format!( + "`{}` doesn't implement \ + `{}`. Callee: `{}`\nPlease, check whether the type of all \ + objects in the modifies clause (including return types) \ + implement `{}`.\nThis is a strict condition to use \ + function contracts as verified stubs.", + receiver_ty, trait_, callee, trait_, + ), + ); } else { panic!("unable to resolve call to `{callee}` in `{caller}`") } @@ -572,7 +599,8 @@ mod debug { if let Ok(target) = std::env::var("KANI_REACH_DEBUG") { debug!(?target, "dump_dot"); let outputs = tcx.output_filenames(()); - let path = outputs.output_path(OutputType::Metadata).with_extension("dot"); + let base_path = outputs.path(OutputType::Metadata); + let path = base_path.as_path().with_extension("dot"); let out_file = File::create(path)?; let mut writer = BufWriter::new(out_file); writeln!(writer, "digraph ReachabilityGraph {{")?; diff --git a/kani-compiler/src/kani_middle/stubbing/mod.rs b/kani-compiler/src/kani_middle/stubbing/mod.rs index 0db518ceef9f..37426bfe0b77 100644 --- a/kani-compiler/src/kani_middle/stubbing/mod.rs +++ b/kani-compiler/src/kani_middle/stubbing/mod.rs @@ -5,6 +5,7 @@ mod annotations; mod transform; +use rustc_span::DUMMY_SP; use std::collections::BTreeMap; use tracing::{debug, trace}; @@ -93,7 +94,7 @@ impl<'tcx> MirVisitor for StubConstChecker<'tcx> { 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() { + if self.tcx.const_eval_resolve(ParamEnv::reveal_all(), un_eval, DUMMY_SP).is_err() { // The `monomorphize` call should have evaluated that constant already. let tcx = self.tcx; let mono_const = &un_eval; diff --git a/kani-compiler/src/kani_middle/stubbing/transform.rs b/kani-compiler/src/kani_middle/stubbing/transform.rs index ff845b188dca..16c46990bc6f 100644 --- a/kani-compiler/src/kani_middle/stubbing/transform.rs +++ b/kani-compiler/src/kani_middle/stubbing/transform.rs @@ -23,8 +23,13 @@ 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. pub fn get_stub(tcx: TyCtxt, def_id: DefId) -> Option { - let mapping = get_stub_mapping(tcx)?; - mapping.get(&def_id).copied() + let stub_map = get_stub_mapping(tcx)?; + stub_map.get(&def_id).copied() +} + +pub fn get_stub_key(tcx: TyCtxt, def_id: DefId) -> Option { + let stub_map = get_stub_mapping(tcx)?; + stub_map.iter().find_map(|(&key, &val)| if val == def_id { Some(key) } else { None }) } /// Returns the new body of a function/method if it has been stubbed out; @@ -56,6 +61,48 @@ pub fn transform_foreign_functions<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx } } +/// Traverse `body` searching for calls to `kani::any_modifies` and replace these calls +/// with calls to `kani::any`. This happens as a separate step as it is only necessary +/// for contract-generated functions. +pub fn transform_any_modifies<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { + let mut visitor = AnyModifiesTransformer { tcx, local_decls: body.clone().local_decls }; + visitor.visit_body(body); +} + +struct AnyModifiesTransformer<'tcx> { + /// The compiler context. + tcx: TyCtxt<'tcx>, + /// Local declarations of the callee function. Kani searches here for foreign functions. + local_decls: IndexVec>, +} + +impl<'tcx> MutVisitor<'tcx> for AnyModifiesTransformer<'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 let Some(any_modifies) = self.tcx.get_diagnostic_name(reachable_function) + && any_modifies.as_str() == "KaniAnyModifies" + { + let Operand::Constant(function_definition) = operand else { + return; + }; + let kani_any_symbol = self + .tcx + .get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniAny")) + .expect("We should have a `kani::any()` definition at this point."); + function_definition.const_ = Const::from_value( + ConstValue::ZeroSized, + self.tcx.type_of(kani_any_symbol).instantiate(self.tcx, arguments), + ); + } + } + } +} + struct ForeignFunctionTransformer<'tcx> { /// The compiler context. tcx: TyCtxt<'tcx>, diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs new file mode 100644 index 000000000000..d14aca3a7c5b --- /dev/null +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -0,0 +1,275 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! Utility functions that allow us to modify a function body. + +use crate::kani_middle::find_fn_def; +use rustc_middle::ty::TyCtxt; +use stable_mir::mir::mono::Instance; +use stable_mir::mir::{ + BasicBlock, BasicBlockIdx, BinOp, Body, CastKind, Constant, Local, LocalDecl, Mutability, + Operand, Place, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, UnwindAction, + VarDebugInfo, +}; +use stable_mir::ty::{Const, GenericArgs, Span, Ty, UintTy}; +use std::mem; + +/// This structure mimics a Body that can actually be modified. +pub struct MutableBody { + blocks: Vec, + + /// Declarations of locals within the function. + /// + /// The first local is the return value pointer, followed by `arg_count` + /// locals for the function arguments, followed by any user-declared + /// variables and temporaries. + locals: Vec, + + /// The number of arguments this function takes. + arg_count: usize, + + /// Debug information pertaining to user variables, including captures. + var_debug_info: Vec, + + /// Mark an argument (which must be a tuple) as getting passed as its individual components. + /// + /// This is used for the "rust-call" ABI such as closures. + spread_arg: Option, + + /// The span that covers the entire function body. + span: Span, +} + +impl MutableBody { + /// Get the basic blocks of this builder. + pub fn blocks(&self) -> &[BasicBlock] { + &self.blocks + } + + pub fn locals(&self) -> &[LocalDecl] { + &self.locals + } + + /// Create a mutable body from the original MIR body. + pub fn from(body: Body) -> Self { + MutableBody { + locals: body.locals().to_vec(), + arg_count: body.arg_locals().len(), + spread_arg: body.spread_arg(), + blocks: body.blocks, + var_debug_info: body.var_debug_info, + span: body.span, + } + } + + /// Create the new body consuming this mutable body. + pub fn into(self) -> Body { + Body::new( + self.blocks, + self.locals, + self.arg_count, + self.var_debug_info, + self.spread_arg, + self.span, + ) + } + + /// Add a new local to the body with the given attributes. + pub fn new_local(&mut self, ty: Ty, span: Span, mutability: Mutability) -> Local { + let decl = LocalDecl { ty, span, mutability }; + let local = self.locals.len(); + self.locals.push(decl); + local + } + + pub fn new_str_operand(&mut self, msg: &str, span: Span) -> Operand { + let literal = Const::from_str(msg); + Operand::Constant(Constant { span, user_ty: None, literal }) + } + + pub fn new_const_operand(&mut self, val: u128, uint_ty: UintTy, span: Span) -> Operand { + let literal = Const::try_from_uint(val, uint_ty).unwrap(); + Operand::Constant(Constant { span, user_ty: None, literal }) + } + + /// Create a raw pointer of `*mut type` and return a new local where that value is stored. + pub fn new_cast_ptr( + &mut self, + from: Operand, + pointee_ty: Ty, + mutability: Mutability, + before: &mut SourceInstruction, + ) -> Local { + assert!(from.ty(self.locals()).unwrap().kind().is_raw_ptr()); + let target_ty = Ty::new_ptr(pointee_ty, mutability); + let rvalue = Rvalue::Cast(CastKind::PtrToPtr, from, target_ty); + self.new_assignment(rvalue, before) + } + + /// Add a new assignment for the given binary operation. + /// + /// Return the local where the result is saved. + pub fn new_binary_op( + &mut self, + bin_op: BinOp, + lhs: Operand, + rhs: Operand, + before: &mut SourceInstruction, + ) -> Local { + let rvalue = Rvalue::BinaryOp(bin_op, lhs, rhs); + self.new_assignment(rvalue, before) + } + + /// Add a new assignment. + /// + /// Return local where the result is saved. + pub fn new_assignment(&mut self, rvalue: Rvalue, before: &mut SourceInstruction) -> Local { + let span = before.span(&self.blocks); + let ret_ty = rvalue.ty(&self.locals).unwrap(); + let result = self.new_local(ret_ty, span, Mutability::Not); + let stmt = Statement { kind: StatementKind::Assign(Place::from(result), rvalue), span }; + self.insert_stmt(stmt, before); + result + } + + /// Add a new assert to the basic block indicated by the given index. + /// + /// The new assertion will have the same span as the source instruction, and the basic block + /// will be split. The source instruction will be adjusted to point to the first instruction in + /// the new basic block. + pub fn add_check( + &mut self, + tcx: TyCtxt, + check_type: &CheckType, + source: &mut SourceInstruction, + value: Local, + msg: &str, + ) { + assert_eq!( + self.locals[value].ty, + Ty::bool_ty(), + "Expected boolean value as the assert input" + ); + let new_bb = self.blocks.len(); + let span = source.span(&self.blocks); + match check_type { + CheckType::Assert(assert_fn) => { + let assert_op = Operand::Copy(Place::from(self.new_local( + assert_fn.ty(), + span, + Mutability::Not, + ))); + let msg_op = self.new_str_operand(msg, span); + let kind = TerminatorKind::Call { + func: assert_op, + args: vec![Operand::Move(Place::from(value)), msg_op], + destination: Place { + local: self.new_local(Ty::new_tuple(&[]), span, Mutability::Not), + projection: vec![], + }, + target: Some(new_bb), + unwind: UnwindAction::Terminate, + }; + let terminator = Terminator { kind, span }; + self.split_bb(source, terminator); + } + CheckType::Panic | CheckType::NoCore => { + tcx.sess + .dcx() + .struct_err("Failed to instrument the code. Cannot find `kani::assert`") + .with_note("Kani requires `kani` library in order to verify a crate.") + .emit(); + tcx.sess.dcx().abort_if_errors(); + unreachable!(); + } + } + } + + /// Split a basic block right before the source location and use the new terminator + /// in the basic block that was split. + /// + /// The source is updated to point to the same instruction which is now in the new basic block. + pub fn split_bb(&mut self, source: &mut SourceInstruction, new_term: Terminator) { + let new_bb_idx = self.blocks.len(); + let (idx, bb) = match source { + SourceInstruction::Statement { idx, bb } => { + let (orig_idx, orig_bb) = (*idx, *bb); + *idx = 0; + *bb = new_bb_idx; + (orig_idx, orig_bb) + } + SourceInstruction::Terminator { bb } => { + let orig_bb = *bb; + *bb = new_bb_idx; + (self.blocks[orig_bb].statements.len(), orig_bb) + } + }; + let old_term = mem::replace(&mut self.blocks[bb].terminator, new_term); + let bb_stmts = &mut self.blocks[bb].statements; + let remaining = bb_stmts.split_off(idx); + let new_bb = BasicBlock { statements: remaining, terminator: old_term }; + self.blocks.push(new_bb); + } + + /// Insert statement before the source instruction and update the source as needed. + pub fn insert_stmt(&mut self, new_stmt: Statement, before: &mut SourceInstruction) { + match before { + SourceInstruction::Statement { idx, bb } => { + self.blocks[*bb].statements.insert(*idx, new_stmt); + *idx += 1; + } + SourceInstruction::Terminator { bb } => { + // Append statements at the end of the basic block. + self.blocks[*bb].statements.push(new_stmt); + } + } + } +} + +#[derive(Clone, Debug)] +pub enum CheckType { + /// This is used by default when the `kani` crate is available. + Assert(Instance), + /// When the `kani` crate is not available, we have to model the check as an `if { panic!() }`. + Panic, + /// When building non-core crate, such as `rustc-std-workspace-core`, we cannot + /// instrument code, but we can still compile them. + NoCore, +} + +impl CheckType { + /// This will create the type of check that is available in the current crate. + /// + /// If `kani` crate is available, this will return [CheckType::Assert], and the instance will + /// point to `kani::assert`. Otherwise, we will collect the `core::panic_str` method and return + /// [CheckType::Panic]. + pub fn new(tcx: TyCtxt) -> CheckType { + if let Some(instance) = find_instance(tcx, "KaniAssert") { + CheckType::Assert(instance) + } else if find_instance(tcx, "panic_str").is_some() { + CheckType::Panic + } else { + CheckType::NoCore + } + } +} + +/// We store the index of an instruction to avoid borrow checker issues and unnecessary copies. +#[derive(Copy, Clone, Debug)] +pub enum SourceInstruction { + Statement { idx: usize, bb: BasicBlockIdx }, + Terminator { bb: BasicBlockIdx }, +} + +impl SourceInstruction { + pub fn span(&self, blocks: &[BasicBlock]) -> Span { + match *self { + SourceInstruction::Statement { idx, bb } => blocks[bb].statements[idx].span, + SourceInstruction::Terminator { bb } => blocks[bb].terminator.span, + } + } +} + +fn find_instance(tcx: TyCtxt, diagnostic: &str) -> Option { + Instance::resolve(find_fn_def(tcx, diagnostic)?, &GenericArgs(vec![])).ok() +} diff --git a/kani-compiler/src/kani_middle/transform/check_values.rs b/kani-compiler/src/kani_middle/transform/check_values.rs new file mode 100644 index 000000000000..4888e032af4f --- /dev/null +++ b/kani-compiler/src/kani_middle/transform/check_values.rs @@ -0,0 +1,924 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! Implement a transformation pass that instrument the code to detect possible UB due to +//! the generation of an invalid value. +//! +//! This pass highly depend on Rust type layouts. For more details, see: +//! +//! +//! For that, we traverse the function body and look for unsafe operations that may generate +//! invalid values. For each operation found, we add checks to ensure the value is valid. +//! +//! Note: There is some redundancy in the checks that could be optimized. Example: +//! 1. We could merge the invalid values by the offset. +//! 2. We could avoid checking places that have been checked before. +use crate::args::ExtraChecks; +use crate::kani_middle::transform::body::{CheckType, MutableBody, SourceInstruction}; +use crate::kani_middle::transform::check_values::SourceOp::UnsupportedCheck; +use crate::kani_middle::transform::{TransformPass, TransformationType}; +use crate::kani_queries::QueryDb; +use rustc_middle::ty::TyCtxt; +use rustc_smir::rustc_internal; +use stable_mir::abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape, WrappingRange}; +use stable_mir::mir::mono::{Instance, InstanceKind}; +use stable_mir::mir::visit::{Location, PlaceContext, PlaceRef}; +use stable_mir::mir::{ + AggregateKind, BasicBlockIdx, BinOp, Body, CastKind, Constant, FieldIdx, Local, LocalDecl, + MirVisitor, Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, + Statement, StatementKind, Terminator, TerminatorKind, +}; +use stable_mir::target::{MachineInfo, MachineSize}; +use stable_mir::ty::{AdtKind, Const, IndexedVal, RigidTy, Ty, TyKind, UintTy}; +use stable_mir::CrateDef; +use std::fmt::{Debug, Formatter}; +use strum_macros::AsRefStr; +use tracing::{debug, trace}; + +/// Instrument the code with checks for invalid values. +pub struct ValidValuePass { + check_type: CheckType, +} + +impl ValidValuePass { + pub fn new(tcx: TyCtxt) -> Self { + ValidValuePass { check_type: CheckType::new(tcx) } + } +} + +impl TransformPass for ValidValuePass { + fn transformation_type() -> TransformationType + where + Self: Sized, + { + TransformationType::Instrumentation + } + + fn is_enabled(&self, query_db: &QueryDb) -> bool + where + Self: Sized, + { + let args = query_db.args(); + args.ub_check.contains(&ExtraChecks::Validity) + } + + /// Transform the function body by inserting checks one-by-one. + /// For every unsafe dereference or a transmute operation, we check all values are valid. + fn transform(&self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { + trace!(function=?instance.name(), "transform"); + let mut new_body = MutableBody::from(body); + let orig_len = new_body.blocks().len(); + // Do not cache body.blocks().len() since it will change as we add new checks. + for bb_idx in 0..new_body.blocks().len() { + let Some(candidate) = + CheckValueVisitor::find_next(&new_body, bb_idx, bb_idx >= orig_len) + else { + continue; + }; + self.build_check(tcx, &mut new_body, candidate); + } + (orig_len != new_body.blocks().len(), new_body.into()) + } +} + +impl Debug for ValidValuePass { + /// Implement manually since MachineInfo doesn't currently derive Debug. + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + "ValidValuePass".fmt(f) + } +} + +impl ValidValuePass { + fn build_check(&self, tcx: TyCtxt, body: &mut MutableBody, instruction: UnsafeInstruction) { + debug!(?instruction, "build_check"); + let mut source = instruction.source; + for operation in instruction.operations { + match operation { + SourceOp::BytesValidity { ranges, target_ty, rvalue } => { + let value = body.new_assignment(rvalue, &mut source); + let rvalue_ptr = Rvalue::AddressOf(Mutability::Not, Place::from(value)); + for range in ranges { + let result = + self.build_limits(body, &range, rvalue_ptr.clone(), &mut source); + let msg = format!( + "Undefined Behavior: Invalid value of type `{}`", + // TODO: Fix pretty_ty + rustc_internal::internal(tcx, target_ty) + ); + body.add_check(tcx, &self.check_type, &mut source, result, &msg); + } + } + SourceOp::DerefValidity { pointee_ty, rvalue, ranges } => { + for range in ranges { + let result = self.build_limits(body, &range, rvalue.clone(), &mut source); + let msg = format!( + "Undefined Behavior: Invalid value of type `{}`", + // TODO: Fix pretty_ty + rustc_internal::internal(tcx, pointee_ty) + ); + body.add_check(tcx, &self.check_type, &mut source, result, &msg); + } + } + SourceOp::UnsupportedCheck { check, ty } => { + let reason = format!( + "Kani currently doesn't support checking validity of `{check}` for `{}` type", + rustc_internal::internal(tcx, ty) + ); + self.unsupported_check(tcx, body, &mut source, &reason); + } + } + } + } + + fn build_limits( + &self, + body: &mut MutableBody, + req: &ValidValueReq, + rvalue_ptr: Rvalue, + source: &mut SourceInstruction, + ) -> Local { + let span = source.span(body.blocks()); + debug!(?req, ?rvalue_ptr, ?span, "build_limits"); + let primitive_ty = uint_ty(req.size.bytes()); + let start_const = body.new_const_operand(req.valid_range.start, primitive_ty, span); + let end_const = body.new_const_operand(req.valid_range.end, primitive_ty, span); + let orig_ptr = if req.offset != 0 { + let start_ptr = move_local(body.new_assignment(rvalue_ptr, source)); + let byte_ptr = move_local(body.new_cast_ptr( + start_ptr, + Ty::unsigned_ty(UintTy::U8), + Mutability::Not, + source, + )); + let offset_const = body.new_const_operand(req.offset as _, UintTy::Usize, span); + let offset = move_local(body.new_assignment(Rvalue::Use(offset_const), source)); + move_local(body.new_binary_op(BinOp::Offset, byte_ptr, offset, source)) + } else { + move_local(body.new_assignment(rvalue_ptr, source)) + }; + let value_ptr = + body.new_cast_ptr(orig_ptr, Ty::unsigned_ty(primitive_ty), Mutability::Not, source); + let value = + Operand::Copy(Place { local: value_ptr, projection: vec![ProjectionElem::Deref] }); + let start_result = body.new_binary_op(BinOp::Ge, value.clone(), start_const, source); + let end_result = body.new_binary_op(BinOp::Le, value, end_const, source); + if req.valid_range.wraps_around() { + // valid >= start || valid <= end + body.new_binary_op( + BinOp::BitOr, + move_local(start_result), + move_local(end_result), + source, + ) + } else { + // valid >= start && valid <= end + body.new_binary_op( + BinOp::BitAnd, + move_local(start_result), + move_local(end_result), + source, + ) + } + } + + fn unsupported_check( + &self, + tcx: TyCtxt, + body: &mut MutableBody, + source: &mut SourceInstruction, + reason: &str, + ) { + let span = source.span(body.blocks()); + let rvalue = Rvalue::Use(Operand::Constant(Constant { + literal: Const::from_bool(false), + span, + user_ty: None, + })); + let result = body.new_assignment(rvalue, source); + body.add_check(tcx, &self.check_type, source, result, reason); + } +} + +fn move_local(local: Local) -> Operand { + Operand::Move(Place::from(local)) +} + +fn uint_ty(bytes: usize) -> UintTy { + match bytes { + 1 => UintTy::U8, + 2 => UintTy::U16, + 4 => UintTy::U32, + 8 => UintTy::U64, + 16 => UintTy::U128, + _ => unreachable!("Unexpected size: {bytes}"), + } +} + +/// Represent a requirement for the value stored in the given offset. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +struct ValidValueReq { + /// Offset in bytes. + offset: usize, + /// Size of this requirement. + size: MachineSize, + /// The range restriction is represented by a Scalar. + valid_range: WrappingRange, +} + +// TODO: Optimize checks by merging requirements whenever possible. +// There are a few cases that would need to be cover: +// 1- Ranges intersection is the same as one of the ranges (or both). +// 2- Ranges intersection is a new valid range. +// 3- Ranges intersection is a combination of two new ranges. +// 4- Intersection is empty. +impl ValidValueReq { + /// Only a type with `ValueAbi::Scalar` and `ValueAbi::ScalarPair` can be directly assigned an + /// invalid value directly. + /// + /// It's not possible to define a `rustc_layout_scalar_valid_range_*` to any other structure. + /// Note that this annotation only applies to the first scalar in the layout. + pub fn try_from_ty(machine_info: &MachineInfo, ty: Ty) -> Option { + let shape = ty.layout().unwrap().shape(); + match shape.abi { + ValueAbi::Scalar(Scalar::Initialized { value, valid_range }) + | ValueAbi::ScalarPair(Scalar::Initialized { value, valid_range }, _) => { + Some(ValidValueReq { offset: 0, size: value.size(machine_info), valid_range }) + } + ValueAbi::Scalar(_) + | ValueAbi::ScalarPair(_, _) + | ValueAbi::Uninhabited + | ValueAbi::Vector { .. } + | ValueAbi::Aggregate { .. } => None, + } + } + + /// Check if range is full. + pub fn is_full(&self) -> bool { + self.valid_range.is_full(self.size).unwrap() + } + + /// Check if this range contains `other` range. + /// + /// I.e., `scalar_2` ⊆ `scalar_1` + pub fn contains(&self, other: &ValidValueReq) -> bool { + assert_eq!(self.size, other.size); + match (self.valid_range.wraps_around(), other.valid_range.wraps_around()) { + (true, true) | (false, false) => { + self.valid_range.start <= other.valid_range.start + && self.valid_range.end >= other.valid_range.end + } + (true, false) => { + self.valid_range.start <= other.valid_range.start + || self.valid_range.end >= other.valid_range.end + } + (false, true) => self.is_full(), + } + } +} + +#[derive(AsRefStr, Clone, Debug)] +enum SourceOp { + /// Validity checks are done on a byte level when the Rvalue can generate invalid value. + /// + /// This variant tracks a location that is valid for its current type, but it may not be + /// valid for the given location in target type. This happens for: + /// - Transmute + /// - Field assignment + /// - Aggregate assignment + /// - Union Access + /// + /// Each range is a pair of offset and scalar that represents the valid values. + /// Note that the same offset may have multiple ranges that may require being joined. + BytesValidity { target_ty: Ty, rvalue: Rvalue, ranges: Vec }, + + /// Similar to BytesValidity, but it stores any dereference that may be unsafe. + /// + /// This can happen for: + /// - Raw pointer dereference + DerefValidity { pointee_ty: Ty, rvalue: Rvalue, ranges: Vec }, + + /// Represents a range check Kani currently does not support. + /// + /// This will translate into an assertion failure with an unsupported message. + /// There are many corner cases with the usage of #[rustc_layout_scalar_valid_range_*] + /// attribute. Such as valid ranges that do not intersect or enumeration with variants + /// with niche. + /// + /// Supporting all cases require significant work, and it is unlikely to exist in real world + /// code. To be on the sound side, we just emit an unsupported check, and users will need to + /// disable the check in person, and create a feature request for their case. + /// + /// TODO: Consider replacing the assertion(false) by an unsupported operation that emits a + /// compilation warning. + UnsupportedCheck { check: String, ty: Ty }, +} + +/// The unsafe instructions that may generate invalid values. +/// We need to instrument all operations to ensure the instruction is safe. +#[derive(Clone, Debug)] +struct UnsafeInstruction { + /// The instruction that depends on the potentially invalid value. + source: SourceInstruction, + /// The unsafe operations that may cause an invalid value in this instruction. + operations: Vec, +} + +/// Extract any source that may potentially trigger UB due to the generation of an invalid value. +/// +/// Generating an invalid value requires an unsafe operation, however, in MIR, it +/// may just be represented as a regular assignment. +/// +/// Thus, we have to instrument every assignment to an object that has niche and that the source +/// is an object of a different source, e.g.: +/// - Aggregate assignment +/// - Transmute +/// - MemCopy +/// - Cast +struct CheckValueVisitor<'a> { + locals: &'a [LocalDecl], + /// Whether we should skip the next instruction, since it might've been instrumented already. + /// When we instrument an instruction, we partition the basic block, and the instruction that + /// may trigger UB becomes the first instruction of the basic block, which we need to skip + /// later. + skip_next: bool, + /// The instruction being visited at a given point. + current: SourceInstruction, + /// The target instruction that should be verified. + pub target: Option, + /// The basic block being visited. + bb: BasicBlockIdx, + /// Machine information needed to calculate Niche. + machine: MachineInfo, +} + +impl<'a> CheckValueVisitor<'a> { + fn find_next( + body: &'a MutableBody, + bb: BasicBlockIdx, + skip_first: bool, + ) -> Option { + let mut visitor = CheckValueVisitor { + locals: body.locals(), + skip_next: skip_first, + current: SourceInstruction::Statement { idx: 0, bb }, + target: None, + bb, + machine: MachineInfo::target(), + }; + visitor.visit_basic_block(&body.blocks()[bb]); + visitor.target + } + + fn push_target(&mut self, op: SourceOp) { + let target = self + .target + .get_or_insert_with(|| UnsafeInstruction { source: self.current, operations: vec![] }); + target.operations.push(op); + } +} + +impl<'a> MirVisitor for CheckValueVisitor<'a> { + fn visit_statement(&mut self, stmt: &Statement, location: Location) { + if self.skip_next { + self.skip_next = false; + } else if self.target.is_none() { + // Leave it as an exhaustive match to be notified when a new kind is added. + match &stmt.kind { + StatementKind::Intrinsic(NonDivergingIntrinsic::CopyNonOverlapping(_)) => { + // Source and destination have the same type, so no invalid value cannot be + // generated. + } + StatementKind::Assign(place, rvalue) => { + // First check rvalue. + self.super_statement(stmt, location); + // Then check the destination place. + let ranges = assignment_check_points( + &self.machine, + self.locals, + place, + rvalue.ty(self.locals).unwrap(), + ); + if !ranges.is_empty() { + self.push_target(SourceOp::BytesValidity { + target_ty: self.locals[place.local].ty, + rvalue: rvalue.clone(), + ranges, + }); + } + } + StatementKind::FakeRead(_, _) + | StatementKind::SetDiscriminant { .. } + | StatementKind::Deinit(_) + | StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) + | StatementKind::Retag(_, _) + | StatementKind::PlaceMention(_) + | StatementKind::AscribeUserType { .. } + | StatementKind::Coverage(_) + | StatementKind::ConstEvalCounter + | StatementKind::Intrinsic(NonDivergingIntrinsic::Assume(_)) + | StatementKind::Nop => self.super_statement(stmt, location), + } + } + + let SourceInstruction::Statement { idx, bb } = self.current else { unreachable!() }; + self.current = SourceInstruction::Statement { idx: idx + 1, bb }; + } + fn visit_terminator(&mut self, term: &Terminator, location: Location) { + if !(self.skip_next || self.target.is_some()) { + self.current = SourceInstruction::Terminator { bb: self.bb }; + // Leave it as an exhaustive match to be notified when a new kind is added. + match &term.kind { + TerminatorKind::Call { func, args, .. } => { + // Note: For transmute, both Src and Dst must be valid type. + // In this case, we need to save the Dst, and invoke super_terminator. + self.super_terminator(term, location); + let instance = expect_instance(self.locals, func); + if instance.kind == InstanceKind::Intrinsic { + match instance.intrinsic_name().unwrap().as_str() { + "write_bytes" => { + // The write bytes intrinsic may trigger UB in safe code. + // pub unsafe fn write_bytes(dst: *mut T, val: u8, count: usize) + // + // We don't support this operation yet. + let TyKind::RigidTy(RigidTy::RawPtr(target_ty, Mutability::Mut)) = + args[0].ty(self.locals).unwrap().kind() + else { + unreachable!() + }; + let validity = ty_validity_per_offset(&self.machine, target_ty, 0); + match validity { + Ok(ranges) if ranges.is_empty() => {} + _ => self.push_target(SourceOp::UnsupportedCheck { + check: "write_bytes".to_string(), + ty: target_ty, + }), + } + } + "transmute" | "transmute_copy" => { + unreachable!("Should've been lowered") + } + _ => {} + } + } + } + TerminatorKind::Goto { .. } + | TerminatorKind::SwitchInt { .. } + | TerminatorKind::Resume + | TerminatorKind::Abort + | TerminatorKind::Return + | TerminatorKind::Unreachable + | TerminatorKind::Drop { .. } + | TerminatorKind::Assert { .. } + | TerminatorKind::InlineAsm { .. } => self.super_terminator(term, location), + } + } + } + + fn visit_place(&mut self, place: &Place, ptx: PlaceContext, location: Location) { + for (idx, elem) in place.projection.iter().enumerate() { + let place_ref = PlaceRef { local: place.local, projection: &place.projection[..idx] }; + match elem { + ProjectionElem::Deref => { + let ptr_ty = place_ref.ty(self.locals).unwrap(); + if ptr_ty.kind().is_raw_ptr() { + let target_ty = elem.ty(ptr_ty).unwrap(); + let validity = ty_validity_per_offset(&self.machine, target_ty, 0); + match validity { + Ok(ranges) if !ranges.is_empty() => { + self.push_target(SourceOp::DerefValidity { + pointee_ty: target_ty, + rvalue: Rvalue::Use( + Operand::Copy(Place { + local: place_ref.local, + projection: place_ref.projection.to_vec(), + }) + .clone(), + ), + ranges, + }) + } + Err(_msg) => self.push_target(SourceOp::UnsupportedCheck { + check: "raw pointer dereference".to_string(), + ty: target_ty, + }), + _ => {} + } + } + } + ProjectionElem::Field(idx, target_ty) => { + if target_ty.kind().is_union() + && (!ptx.is_mutating() || place.projection.len() > idx + 1) + { + let validity = ty_validity_per_offset(&self.machine, *target_ty, 0); + match validity { + Ok(ranges) if !ranges.is_empty() => { + self.push_target(SourceOp::BytesValidity { + target_ty: *target_ty, + rvalue: Rvalue::Use(Operand::Copy(Place { + local: place_ref.local, + projection: place_ref.projection.to_vec(), + })), + ranges, + }) + } + Err(_msg) => self.push_target(SourceOp::UnsupportedCheck { + check: "union access".to_string(), + ty: *target_ty, + }), + _ => {} + } + } + } + ProjectionElem::Downcast(_) => {} + ProjectionElem::OpaqueCast(_) => {} + ProjectionElem::Subtype(_) => {} + ProjectionElem::Index(_) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } => { /* safe */ } + } + } + self.super_place(place, ptx, location) + } + + fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) { + match rvalue { + Rvalue::Cast(kind, op, dest_ty) => match kind { + CastKind::PtrToPtr => { + // For mutable raw pointer, if the type we are casting to is less restrictive + // than the original type, writing to the pointer could generate UB if the + // value is ever read again using the original pointer. + let TyKind::RigidTy(RigidTy::RawPtr(dest_pointee_ty, Mutability::Mut)) = + dest_ty.kind() + else { + // We only care about *mut T as *mut U + return; + }; + let src_ty = op.ty(self.locals).unwrap(); + debug!(?src_ty, ?dest_ty, "visit_rvalue mutcast"); + let TyKind::RigidTy(RigidTy::RawPtr(src_pointee_ty, _)) = src_ty.kind() else { + unreachable!() + }; + if let Ok(src_validity) = + ty_validity_per_offset(&self.machine, src_pointee_ty, 0) + { + if !src_validity.is_empty() { + if let Ok(dest_validity) = + ty_validity_per_offset(&self.machine, dest_pointee_ty, 0) + { + if dest_validity != src_validity { + self.push_target(SourceOp::UnsupportedCheck { + check: "mutable cast".to_string(), + ty: src_ty, + }) + } + } else { + self.push_target(SourceOp::UnsupportedCheck { + check: "mutable cast".to_string(), + ty: *dest_ty, + }) + } + } + } else { + self.push_target(SourceOp::UnsupportedCheck { + check: "mutable cast".to_string(), + ty: src_ty, + }) + } + } + CastKind::Transmute => { + debug!(?dest_ty, "transmute"); + // For transmute, we care about the destination type only. + // This could be optimized to only add a check if the requirements of the + // destination type are stricter than the source. + if let Ok(dest_validity) = ty_validity_per_offset(&self.machine, *dest_ty, 0) { + trace!(?dest_validity, "transmute"); + if !dest_validity.is_empty() { + self.push_target(SourceOp::BytesValidity { + target_ty: *dest_ty, + rvalue: rvalue.clone(), + ranges: dest_validity, + }) + } + } else { + self.push_target(SourceOp::UnsupportedCheck { + check: "transmute".to_string(), + ty: *dest_ty, + }) + } + } + CastKind::DynStar => self.push_target(UnsupportedCheck { + check: "Dyn*".to_string(), + ty: (rvalue.ty(self.locals).unwrap()), + }), + CastKind::PointerExposeAddress + | CastKind::PointerFromExposedAddress + | CastKind::PointerCoercion(_) + | CastKind::IntToInt + | CastKind::FloatToInt + | CastKind::FloatToFloat + | CastKind::IntToFloat + | CastKind::FnPtrToPtr => {} + }, + Rvalue::ShallowInitBox(_, _) => { + // The contents of the box is considered uninitialized. + // This should already be covered by the Assign detection. + } + Rvalue::Aggregate(kind, operands) => match kind { + // If the aggregated structure has invalid value, this could generate invalid value. + // But only if the operands don't have the exact same restrictions. + // This happens today with the usage of `rustc_layout_scalar_valid_range_*` + // attributes. + // In this case, only the value of the first member in memory can be restricted, + // thus, we only need to check the operand used to assign to the first in memory + // field. + AggregateKind::Adt(def, _variant, args, _, _) => { + if def.kind() == AdtKind::Struct { + let dest_ty = Ty::from_rigid_kind(RigidTy::Adt(*def, args.clone())); + if let Some(req) = ValidValueReq::try_from_ty(&self.machine, dest_ty) + && !req.is_full() + { + let dest_layout = dest_ty.layout().unwrap().shape(); + let first_op = + first_aggregate_operand(dest_ty, &dest_layout.fields, operands); + let first_ty = first_op.ty(self.locals).unwrap(); + // Rvalue must have same Abi layout except for range. + if !req.contains( + &ValidValueReq::try_from_ty(&self.machine, first_ty).unwrap(), + ) { + self.push_target(SourceOp::BytesValidity { + target_ty: dest_ty, + rvalue: Rvalue::Use(first_op), + ranges: vec![req], + }) + } + } + } + } + // Only aggregate value. + AggregateKind::Array(_) + | AggregateKind::Closure(_, _) + | AggregateKind::Coroutine(_, _, _) + | AggregateKind::Tuple => {} + }, + Rvalue::AddressOf(_, _) + | Rvalue::BinaryOp(_, _, _) + | Rvalue::CheckedBinaryOp(_, _, _) + | Rvalue::CopyForDeref(_) + | Rvalue::Discriminant(_) + | Rvalue::Len(_) + | Rvalue::Ref(_, _, _) + | Rvalue::Repeat(_, _) + | Rvalue::ThreadLocalRef(_) + | Rvalue::NullaryOp(_, _) + | Rvalue::UnaryOp(_, _) + | Rvalue::Use(_) => {} + } + self.super_rvalue(rvalue, location); + } +} + +/// Gets the operand that corresponds to the assignment of the first sized field in memory. +/// +/// The first field of a structure is the only one that can have extra value restrictions imposed +/// by `rustc_layout_scalar_valid_range_*` attributes. +/// +/// Note: This requires at least one operand to be sized and there's a 1:1 match between operands +/// and field types. +fn first_aggregate_operand(dest_ty: Ty, dest_shape: &FieldsShape, operands: &[Operand]) -> Operand { + let Some(first) = first_sized_field_idx(dest_ty, dest_shape) else { unreachable!() }; + operands[first].clone() +} + +/// Index of the first non_1zst fields in memory order. +fn first_sized_field_idx(ty: Ty, shape: &FieldsShape) -> Option { + if let TyKind::RigidTy(RigidTy::Adt(adt_def, args)) = ty.kind() + && adt_def.kind() == AdtKind::Struct + { + let offset_order = shape.fields_by_offset_order(); + let fields = adt_def.variants_iter().next().unwrap().fields(); + offset_order + .into_iter() + .find(|idx| !fields[*idx].ty_with_args(&args).layout().unwrap().shape().is_1zst()) + } else { + None + } +} + +/// An assignment to a field with invalid values is unsafe, and it may trigger UB if +/// the assigned value is invalid. +/// +/// This can only happen to the first in memory sized field of a struct, and only if the field +/// type invalid range is a valid value for the rvalue type. +fn assignment_check_points( + machine_info: &MachineInfo, + locals: &[LocalDecl], + place: &Place, + rvalue_ty: Ty, +) -> Vec { + let mut ty = locals[place.local].ty; + let Some(rvalue_range) = ValidValueReq::try_from_ty(machine_info, rvalue_ty) else { + // Rvalue Abi must be Scalar / ScalarPair since destination must be Scalar / ScalarPair. + return vec![]; + }; + let mut invalid_ranges = vec![]; + for proj in &place.projection { + match proj { + ProjectionElem::Field(field_idx, field_ty) => { + let shape = ty.layout().unwrap().shape(); + if first_sized_field_idx(ty, &shape.fields) == Some(*field_idx) + && let Some(dest_valid) = ValidValueReq::try_from_ty(machine_info, ty) + && !dest_valid.is_full() + && dest_valid.size == rvalue_range.size + { + if !dest_valid.contains(&rvalue_range) { + invalid_ranges.push(dest_valid) + } + } else { + // Invalidate collected ranges so far since we are no longer in the path of + // the first element. + invalid_ranges.clear(); + } + ty = *field_ty; + } + ProjectionElem::Deref + | ProjectionElem::Index(_) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } + | ProjectionElem::Downcast(_) + | ProjectionElem::OpaqueCast(_) + | ProjectionElem::Subtype(_) => ty = proj.ty(ty).unwrap(), + }; + } + invalid_ranges +} + +/// Retrieve instance for the given function operand. +/// +/// This will panic if the operand is not a function or if it cannot be resolved. +fn expect_instance(locals: &[LocalDecl], func: &Operand) -> Instance { + let ty = func.ty(locals).unwrap(); + match ty.kind() { + TyKind::RigidTy(RigidTy::FnDef(def, args)) => Instance::resolve(def, &args).unwrap(), + _ => unreachable!(), + } +} + +/// Traverse the type and find all invalid values and their location in memory. +/// +/// Not all values are currently supported. For those not supported, we return Error. +fn ty_validity_per_offset( + machine_info: &MachineInfo, + ty: Ty, + current_offset: usize, +) -> Result, String> { + let layout = ty.layout().unwrap().shape(); + let ty_req = || { + if let Some(mut req) = ValidValueReq::try_from_ty(machine_info, ty) + && !req.is_full() + { + req.offset = current_offset; + vec![req] + } else { + vec![] + } + }; + match layout.fields { + FieldsShape::Primitive => Ok(ty_req()), + FieldsShape::Array { stride, count } if count > 0 => { + let TyKind::RigidTy(RigidTy::Array(elem_ty, _)) = ty.kind() else { unreachable!() }; + let elem_validity = ty_validity_per_offset(machine_info, elem_ty, current_offset)?; + let mut result = vec![]; + if !elem_validity.is_empty() { + for idx in 0..count { + let idx: usize = idx.try_into().unwrap(); + let elem_offset = idx * stride.bytes(); + let mut next_validity = elem_validity + .iter() + .cloned() + .map(|mut req| { + req.offset += elem_offset; + req + }) + .collect::>(); + result.append(&mut next_validity) + } + } + Ok(result) + } + FieldsShape::Arbitrary { ref offsets } => { + match ty.kind().rigid().expect(&format!("unexpected type: {ty:?}")) { + RigidTy::Adt(def, args) => { + match def.kind() { + AdtKind::Enum => { + // Support basic enumeration forms + let ty_variants = def.variants(); + match layout.variants { + VariantsShape::Single { index } => { + // Only one variant is reachable. This behaves like a struct. + let fields = ty_variants[index.to_index()].fields(); + let mut fields_validity = vec![]; + for idx in layout.fields.fields_by_offset_order() { + let field_offset = offsets[idx].bytes(); + let field_ty = fields[idx].ty_with_args(&args); + fields_validity.append(&mut ty_validity_per_offset( + machine_info, + field_ty, + field_offset + current_offset, + )?); + } + Ok(fields_validity) + } + VariantsShape::Multiple { + tag_encoding: TagEncoding::Niche { .. }, + .. + } => { + Err(format!("Unsupported Enum `{}` check", def.trimmed_name()))? + } + VariantsShape::Multiple { variants, .. } => { + let enum_validity = ty_req(); + let mut fields_validity = vec![]; + for (index, variant) in variants.iter().enumerate() { + let fields = ty_variants[index].fields(); + for field_idx in variant.fields.fields_by_offset_order() { + let field_offset = offsets[field_idx].bytes(); + let field_ty = fields[field_idx].ty_with_args(&args); + fields_validity.append(&mut ty_validity_per_offset( + machine_info, + field_ty, + field_offset + current_offset, + )?); + } + } + if fields_validity.is_empty() { + Ok(enum_validity) + } else { + Err(format!( + "Unsupported Enum `{}` check", + def.trimmed_name() + )) + } + } + } + } + AdtKind::Union => unreachable!(), + AdtKind::Struct => { + // If the struct range has niche add that. + let mut struct_validity = ty_req(); + let fields = def.variants_iter().next().unwrap().fields(); + for idx in layout.fields.fields_by_offset_order() { + let field_offset = offsets[idx].bytes(); + let field_ty = fields[idx].ty_with_args(&args); + struct_validity.append(&mut ty_validity_per_offset( + machine_info, + field_ty, + field_offset + current_offset, + )?); + } + Ok(struct_validity) + } + } + } + RigidTy::Tuple(tys) => { + let mut tuple_validity = vec![]; + for idx in layout.fields.fields_by_offset_order() { + let field_offset = offsets[idx].bytes(); + let field_ty = tys[idx]; + tuple_validity.append(&mut ty_validity_per_offset( + machine_info, + field_ty, + field_offset + current_offset, + )?); + } + Ok(tuple_validity) + } + RigidTy::Bool + | RigidTy::Char + | RigidTy::Int(_) + | RigidTy::Uint(_) + | RigidTy::Float(_) + | RigidTy::Never => { + unreachable!("Expected primitive layout for {ty:?}") + } + RigidTy::Str | RigidTy::Slice(_) | RigidTy::Array(_, _) => { + unreachable!("Expected array layout for {ty:?}") + } + RigidTy::RawPtr(_, _) | RigidTy::Ref(_, _, _) => { + // Fat pointer has arbitrary shape. + Ok(ty_req()) + } + RigidTy::FnDef(_, _) + | RigidTy::FnPtr(_) + | RigidTy::Closure(_, _) + | RigidTy::Coroutine(_, _, _) + | RigidTy::CoroutineWitness(_, _) + | RigidTy::Foreign(_) + | RigidTy::Dynamic(_, _, _) => Err(format!("Unsupported {ty:?}")), + } + } + FieldsShape::Union(_) | FieldsShape::Array { .. } => { + /* Anything is valid */ + Ok(vec![]) + } + } +} diff --git a/kani-compiler/src/kani_middle/transform/mod.rs b/kani-compiler/src/kani_middle/transform/mod.rs new file mode 100644 index 000000000000..a6cd17e8c7db --- /dev/null +++ b/kani-compiler/src/kani_middle/transform/mod.rs @@ -0,0 +1,135 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! This module is responsible for optimizing and instrumenting function bodies. +//! +//! We make transformations on bodies already monomorphized, which allow us to make stronger +//! decisions based on the instance types and constants. +//! +//! The main downside is that some transformation that don't depend on the specialized type may be +//! applied multiple times, one per specialization. +//! +//! Another downside is that these modifications cannot be applied to concrete playback, since they +//! are applied on the top of StableMIR body, which cannot be propagated back to rustc's backend. +//! +//! # Warn +//! +//! For all instrumentation passes, always use exhaustive matches to ensure soundness in case a new +//! case is added. +use crate::kani_middle::transform::check_values::ValidValuePass; +use crate::kani_queries::QueryDb; +use rustc_middle::ty::TyCtxt; +use stable_mir::mir::mono::Instance; +use stable_mir::mir::Body; +use std::collections::HashMap; +use std::fmt::Debug; + +mod body; +mod check_values; + +/// Object used to retrieve a transformed instance body. +/// The transformations to be applied may be controlled by user options. +/// +/// The order however is always the same, we run optimizations first, and instrument the code +/// after. +#[derive(Debug)] +pub struct BodyTransformation { + /// The passes that may optimize the function body. + /// We store them separately from the instrumentation passes because we run the in specific order. + opt_passes: Vec>, + /// The passes that may add safety checks to the function body. + inst_passes: Vec>, + /// Cache transformation results. + cache: HashMap, +} + +impl BodyTransformation { + pub fn new(queries: &QueryDb, tcx: TyCtxt) -> Self { + let mut transformer = BodyTransformation { + opt_passes: vec![], + inst_passes: vec![], + cache: Default::default(), + }; + transformer.add_pass(queries, ValidValuePass::new(tcx)); + transformer + } + + /// Allow the creation of a dummy transformer that doesn't apply any transformation due to + /// the stubbing validation hack (see `collect_and_partition_mono_items` override. + /// Once we move the stubbing logic to a [TransformPass], we should be able to remove this. + pub fn dummy() -> Self { + BodyTransformation { opt_passes: vec![], inst_passes: vec![], cache: Default::default() } + } + + /// Retrieve the body of an instance. + /// + /// Note that this assumes that the instance does have a body since existing consumers already + /// assume that. Use `instance.has_body()` to check if an instance has a body. + pub fn body(&mut self, tcx: TyCtxt, instance: Instance) -> Body { + match self.cache.get(&instance) { + Some(TransformationResult::Modified(body)) => body.clone(), + Some(TransformationResult::NotModified) => instance.body().unwrap(), + None => { + let mut body = instance.body().unwrap(); + let mut modified = false; + for pass in self.opt_passes.iter().chain(self.inst_passes.iter()) { + let result = pass.transform(tcx, body, instance); + modified |= result.0; + body = result.1; + } + + let result = if modified { + TransformationResult::Modified(body.clone()) + } else { + TransformationResult::NotModified + }; + self.cache.insert(instance, result); + body + } + } + } + + fn add_pass(&mut self, query_db: &QueryDb, pass: P) { + if pass.is_enabled(&query_db) { + match P::transformation_type() { + TransformationType::Instrumentation => self.inst_passes.push(Box::new(pass)), + TransformationType::Optimization => { + unreachable!() + } + } + } + } +} + +/// The type of transformation that a pass may perform. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +enum TransformationType { + /// Should only add assertion checks to ensure the program is correct. + Instrumentation, + /// May replace inefficient code with more performant but equivalent code. + #[allow(dead_code)] + Optimization, +} + +/// A trait to represent transformation passes that can be used to modify the body of a function. +trait TransformPass: Debug { + /// The type of transformation that this pass implements. + fn transformation_type() -> TransformationType + where + Self: Sized; + + fn is_enabled(&self, query_db: &QueryDb) -> bool + where + Self: Sized; + + /// Run a transformation pass in the function body. + fn transform(&self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body); +} + +/// The transformation result. +/// We currently only cache the body of functions that were instrumented. +#[derive(Clone, Debug)] +enum TransformationResult { + Modified(Body), + NotModified, +} diff --git a/kani-compiler/src/kani_queries/mod.rs b/kani-compiler/src/kani_queries/mod.rs index e918bf1b8a73..d6b37a35f9cb 100644 --- a/kani-compiler/src/kani_queries/mod.rs +++ b/kani-compiler/src/kani_queries/mod.rs @@ -4,7 +4,6 @@ use cbmc::{InternString, InternedString}; use kani_metadata::AssignsContract; -use std::fmt::{Display, Formatter, Write}; use std::{ collections::HashMap, path::PathBuf, @@ -65,20 +64,3 @@ impl QueryDb { self.modifies_contracts.iter() } } - -struct PrintList(I); - -impl + Clone> Display for PrintList { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_char('[')?; - let mut is_first = true; - for e in self.0.clone() { - if is_first { - f.write_str(", ")?; - is_first = false; - } - e.fmt(f)?; - } - f.write_char(']') - } -} diff --git a/kani-driver/Cargo.toml b/kani-driver/Cargo.toml index 36a979a1366d..3a476922a838 100644 --- a/kani-driver/Cargo.toml +++ b/kani-driver/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-driver" -version = "0.48.0" +version = "0.49.0" edition = "2021" description = "Build a project with Kani and run all proof harnesses" license = "MIT OR Apache-2.0" diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index aeda94168840..36d9e0af2d0e 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -252,6 +252,14 @@ pub struct VerificationArgs { #[arg(long, hide_short_help = true, requires("enable_unstable"))] pub ignore_global_asm: bool, + /// Ignore lifetimes of local variables. This effectively extends their + /// lifetimes to the function scope, and hence may cause Kani to miss + /// undefined behavior resulting from using the variable after it dies. + /// This option may impact the soundness of the analysis and may cause false + /// proofs and/or counterexamples + #[arg(long, hide_short_help = true, requires("enable_unstable"))] + pub ignore_locals_lifetime: bool, + /// Write the GotoC symbol table to a file in JSON format instead of goto binary format. #[arg(long, hide_short_help = true)] pub write_json_symtab: bool, diff --git a/kani-driver/src/call_cargo.rs b/kani-driver/src/call_cargo.rs index b68d18c84a40..9d27e5853696 100644 --- a/kani-driver/src/call_cargo.rs +++ b/kani-driver/src/call_cargo.rs @@ -211,7 +211,27 @@ impl KaniSession { } }, Message::CompilerArtifact(rustc_artifact) => { - if rustc_artifact.target == *target { + /// Compares two targets, and falls back to a weaker + /// comparison where we avoid dashes in their names. + fn same_target(t1: &Target, t2: &Target) -> bool { + (t1 == t2) + || (t1.name.replace('-', "_") == t2.name.replace('-', "_") + && t1.kind == t2.kind + && t1.src_path == t2.src_path + && t1.edition == t2.edition + && t1.doctest == t2.doctest + && t1.test == t2.test + && t1.doc == t2.doc) + } + // This used to be `rustc_artifact == *target`, but it + // started to fail after the `cargo` change in + // + // + // We should revisit this check after a while to see if + // it's not needed anymore or it can be restricted to + // certain cases. + // TODO: + if same_target(&rustc_artifact.target, target) { debug_assert!( artifact.is_none(), "expected only one artifact for `{target:?}`", diff --git a/kani-driver/src/call_cbmc.rs b/kani-driver/src/call_cbmc.rs index a806331401cd..7d0edf6f50e5 100644 --- a/kani-driver/src/call_cbmc.rs +++ b/kani-driver/src/call_cbmc.rs @@ -11,7 +11,7 @@ use std::time::{Duration, Instant}; use crate::args::{OutputFormat, VerificationArgs}; use crate::cbmc_output_parser::{ - extract_results, process_cbmc_output, CheckStatus, ParserItem, Property, VerificationOutput, + extract_results, process_cbmc_output, CheckStatus, Property, VerificationOutput, }; use crate::cbmc_property_renderer::{format_coverage, format_result, kani_cbmc_output_filter}; use crate::session::KaniSession; @@ -45,9 +45,6 @@ pub struct VerificationResult { pub status: VerificationStatus, /// The compact representation for failed properties pub failed_properties: FailedProperties, - /// The parsed output, message by message, of CBMC. However, the `Result` message has been - /// removed and is available in `results` instead. - pub messages: Option>, /// The `Result` properties in detail or the exit_status of CBMC. /// Note: CBMC process exit status is only potentially useful if `status` is `Failure`. /// Kani will see CBMC report "failure" that's actually success (interpreting "failed" @@ -254,7 +251,7 @@ impl VerificationResult { start_time: Instant, ) -> VerificationResult { let runtime = start_time.elapsed(); - let (items, results) = extract_results(output.processed_items); + let (_, results) = extract_results(output.processed_items); if let Some(results) = results { let (status, failed_properties) = @@ -262,7 +259,6 @@ impl VerificationResult { VerificationResult { status, failed_properties, - messages: Some(items), results: Ok(results), runtime, generated_concrete_test: false, @@ -272,7 +268,6 @@ impl VerificationResult { VerificationResult { status: VerificationStatus::Failure, failed_properties: FailedProperties::Other, - messages: Some(items), results: Err(output.process_status), runtime, generated_concrete_test: false, @@ -284,7 +279,6 @@ impl VerificationResult { VerificationResult { status: VerificationStatus::Success, failed_properties: FailedProperties::None, - messages: None, results: Ok(vec![]), runtime: Duration::from_secs(0), generated_concrete_test: false, @@ -295,7 +289,6 @@ impl VerificationResult { VerificationResult { status: VerificationStatus::Failure, failed_properties: FailedProperties::Other, - messages: None, // on failure, exit codes in theory might be used, // but `mock_failure` should never be used in a context where they will, // so again use something weird: diff --git a/kani-driver/src/call_single_file.rs b/kani-driver/src/call_single_file.rs index fc41bac69d9c..07899c8c4899 100644 --- a/kani-driver/src/call_single_file.rs +++ b/kani-driver/src/call_single_file.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use anyhow::Result; +use kani_metadata::UnstableFeature; use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::process::Command; @@ -100,6 +101,14 @@ impl KaniSession { flags.push("--coverage-checks".into()); } + if self.args.common_args.unstable_features.contains(UnstableFeature::ValidValueChecks) { + flags.push("--ub-check=validity".into()) + } + + if self.args.ignore_locals_lifetime { + flags.push("--ignore-storage-markers".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 @@ -123,7 +132,7 @@ impl KaniSession { "-Z", "panic_abort_tests=yes", "-Z", - "sanitizer=address", + "mir-enable-passes=-RemoveStorageMarkers", ] .map(OsString::from), ); diff --git a/kani-driver/src/cbmc_output_parser.rs b/kani-driver/src/cbmc_output_parser.rs index 1bb353d7d4c0..127f98beab56 100644 --- a/kani-driver/src/cbmc_output_parser.rs +++ b/kani-driver/src/cbmc_output_parser.rs @@ -287,9 +287,7 @@ fn filepath(file: String) -> String { #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TraceItem { - pub thread: u32, pub step_type: String, - pub hidden: bool, pub lhs: Option, pub source_location: Option, pub value: Option, @@ -301,7 +299,6 @@ pub struct TraceItem { /// The fields included right now are relevant to primitive types. #[derive(Clone, Debug, Deserialize)] pub struct TraceValue { - pub name: String, pub binary: Option, pub data: Option, pub width: Option, diff --git a/kani-driver/src/concrete_playback/test_generator.rs b/kani-driver/src/concrete_playback/test_generator.rs index 53c3a92dd94c..dbd2fb9e03d2 100644 --- a/kani-driver/src/concrete_playback/test_generator.rs +++ b/kani-driver/src/concrete_playback/test_generator.rs @@ -588,9 +588,7 @@ mod tests { line: None, }, trace: Some(vec![TraceItem { - thread: 0, step_type: "assignment".to_string(), - hidden: false, lhs: Some("goto_symex$$return_value".to_string()), source_location: Some(SourceLocation { column: None, @@ -599,7 +597,6 @@ mod tests { line: None, }), value: Some(TraceValue { - name: "".to_string(), binary: Some("0000001100000001".to_string()), data: Some(TraceData::NonBool("385".to_string())), width: Some(16), diff --git a/kani_metadata/Cargo.toml b/kani_metadata/Cargo.toml index 6b6db72f4a6d..582c20c7bd9f 100644 --- a/kani_metadata/Cargo.toml +++ b/kani_metadata/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_metadata" -version = "0.48.0" +version = "0.49.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/kani_metadata/src/unstable.rs b/kani_metadata/src/unstable.rs index 3820f3f2238e..878b468dbdc3 100644 --- a/kani_metadata/src/unstable.rs +++ b/kani_metadata/src/unstable.rs @@ -84,6 +84,9 @@ pub enum UnstableFeature { FunctionContracts, /// Memory predicate APIs. MemPredicates, + /// Automatically check that no invalid value is produced which is considered UB in Rust. + /// Note that this does not include checking uninitialized value. + ValidValueChecks, } impl UnstableFeature { diff --git a/library/kani/Cargo.toml b/library/kani/Cargo.toml index 4da7f91a9ed7..b87536740fcd 100644 --- a/library/kani/Cargo.toml +++ b/library/kani/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani" -version = "0.48.0" +version = "0.49.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index e3d27c4b1bfb..25b1b389a2b1 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -156,11 +156,27 @@ pub const fn cover(_cond: bool, _msg: &'static str) {} /// Note: This is a safe construct and can only be used with types that implement the `Arbitrary` /// trait. The Arbitrary trait is used to build a symbolic value that represents all possible /// valid values for type `T`. +#[rustc_diagnostic_item = "KaniAny"] #[inline(always)] pub fn any() -> T { T::any() } +/// This function is only used for function contract instrumentation. +/// It behaves exaclty like `kani::any()`, except it will check for the trait bounds +/// at compilation time. It allows us to avoid type checking errors while using function +/// contracts only for verification. +#[rustc_diagnostic_item = "KaniAnyModifies"] +#[inline(never)] +#[doc(hidden)] +pub fn any_modifies() -> T { + // This function should not be reacheable. + // Users must include `#[kani::recursion]` in any function contracts for recursive functions; + // otherwise, this might not be properly instantiate. We mark this as unreachable to make + // sure Kani doesn't report any false positives. + unreachable!() +} + /// This creates a symbolic *valid* value of type `T`. /// The value is constrained to be a value accepted by the predicate passed to the filter. /// You can assign the return value of this function to a variable that you want to make symbolic. diff --git a/library/kani_macros/Cargo.toml b/library/kani_macros/Cargo.toml index b2254b5c8954..fe4279fc4366 100644 --- a/library/kani_macros/Cargo.toml +++ b/library/kani_macros/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_macros" -version = "0.48.0" +version = "0.49.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false @@ -16,3 +16,7 @@ proc-macro2 = "1.0" proc-macro-error = "1.0.4" quote = "1.0.20" syn = { version = "2.0.18", features = ["full", "visit-mut", "visit"] } + +[package.metadata.rust-analyzer] +# This package uses rustc crates. +rustc_private=true diff --git a/library/kani_macros/src/lib.rs b/library/kani_macros/src/lib.rs index 32bd44d2ea38..0991730f8802 100644 --- a/library/kani_macros/src/lib.rs +++ b/library/kani_macros/src/lib.rs @@ -52,6 +52,16 @@ pub fn should_panic(attr: TokenStream, item: TokenStream) -> TokenStream { attr_impl::should_panic(attr, item) } +/// Specifies that a function contains recursion for contract instrumentation.** +/// +/// This attribute is only used for function-contract instrumentation. Kani uses +/// this annotation to identify recursive functions and properly instantiate +/// `kani::any_modifies` to check such functions using induction. +#[proc_macro_attribute] +pub fn recursion(attr: TokenStream, item: TokenStream) -> TokenStream { + attr_impl::recursion(attr, item) +} + /// Set Loop unwind limit for proof harnesses /// 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. @@ -331,6 +341,7 @@ mod sysroot { } kani_attribute!(should_panic, no_args); + kani_attribute!(recursion, no_args); kani_attribute!(solver); kani_attribute!(stub); kani_attribute!(unstable); @@ -363,6 +374,7 @@ mod regular { } no_op!(should_panic); + no_op!(recursion); no_op!(solver); no_op!(stub); no_op!(unstable); diff --git a/library/kani_macros/src/sysroot/contracts/bootstrap.rs b/library/kani_macros/src/sysroot/contracts/bootstrap.rs index 98f52c19d9e7..8d930fdebda9 100644 --- a/library/kani_macros/src/sysroot/contracts/bootstrap.rs +++ b/library/kani_macros/src/sysroot/contracts/bootstrap.rs @@ -8,11 +8,7 @@ use proc_macro2::Span; use quote::quote; use syn::ItemFn; -use super::{ - helpers::*, - shared::{attach_require_kani_any, identifier_for_generated_function}, - ContractConditionsHandler, -}; +use super::{helpers::*, shared::identifier_for_generated_function, ContractConditionsHandler}; impl<'a> ContractConditionsHandler<'a> { /// The complex case. We are the first time a contract is handled on this function, so @@ -74,7 +70,6 @@ impl<'a> ContractConditionsHandler<'a> { )); 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::>(); diff --git a/library/kani_macros/src/sysroot/contracts/replace.rs b/library/kani_macros/src/sysroot/contracts/replace.rs index 0b3e1bae5b26..28e401c22d48 100644 --- a/library/kani_macros/src/sysroot/contracts/replace.rs +++ b/library/kani_macros/src/sysroot/contracts/replace.rs @@ -8,7 +8,7 @@ use quote::quote; use super::{ helpers::*, - shared::{attach_require_kani_any, make_unsafe_argument_copies, try_as_result_assign}, + shared::{make_unsafe_argument_copies, try_as_result_assign}, ContractConditionsData, ContractConditionsHandler, }; @@ -39,7 +39,7 @@ impl<'a> ContractConditionsHandler<'a> { fn ensure_bootstrapped_replace_body(&self) -> (Vec, Vec) { if self.is_first_emit() { let return_type = return_type_to_type(&self.annotated_fn.sig.output); - (vec![syn::parse_quote!(let result : #return_type = kani::any();)], vec![]) + (vec![syn::parse_quote!(let result : #return_type = kani::any_modifies();)], vec![]) } else { let stmts = &self.annotated_fn.block.stmts; let idx = stmts @@ -91,7 +91,7 @@ impl<'a> ContractConditionsHandler<'a> { ContractConditionsData::Modifies { attr } => { quote!( #(#before)* - #(*unsafe { kani::internal::Pointer::assignable(#attr) } = kani::any();)* + #(*unsafe { kani::internal::Pointer::assignable(#attr) } = kani::any_modifies();)* #(#after)* result ) @@ -112,9 +112,6 @@ impl<'a> ContractConditionsHandler<'a> { self.output.extend(quote!(#[kanitool::is_contract_generated(replace)])); } let mut sig = self.annotated_fn.sig.clone(); - if self.is_first_emit() { - attach_require_kani_any(&mut sig); - } let body = self.make_replace_body(); if let Some(ident) = override_function_ident { sig.ident = ident; @@ -129,7 +126,7 @@ impl<'a> ContractConditionsHandler<'a> { } } -/// Is this statement `let result : <...> = kani::any();`. +/// Is this statement `let result : <...> = kani::any_modifies();`. fn is_replace_return_havoc(stmt: &syn::Stmt) -> bool { let Some(syn::LocalInit { diverge: None, expr: e, .. }) = try_as_result_assign(stmt) else { return false; @@ -152,7 +149,7 @@ fn is_replace_return_havoc(stmt: &syn::Stmt) -> bool { }) if path.segments.len() == 2 && path.segments[0].ident == "kani" - && path.segments[1].ident == "any" + && path.segments[1].ident == "any_modifies" && attrs.is_empty() ) ) diff --git a/library/kani_macros/src/sysroot/contracts/shared.rs b/library/kani_macros/src/sysroot/contracts/shared.rs index 4c52cd35a69f..bf5a8000da50 100644 --- a/library/kani_macros/src/sysroot/contracts/shared.rs +++ b/library/kani_macros/src/sysroot/contracts/shared.rs @@ -11,11 +11,9 @@ use std::collections::HashMap; use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; use quote::{quote, ToTokens}; -use syn::{ - Attribute, PredicateType, ReturnType, Signature, TraitBound, TypeParamBound, WhereClause, -}; +use syn::Attribute; -use super::{helpers::return_type_to_type, ContractConditionsHandler, ContractFunctionState}; +use super::{ContractConditionsHandler, ContractFunctionState}; impl ContractFunctionState { /// Do we need to emit the `is_contract_generated` tag attribute on the @@ -88,57 +86,6 @@ pub fn make_unsafe_argument_copies( ) } -/// 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. -pub 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(), - })) -} - /// Used as the "single source of truth" for [`try_as_result_assign`] and [`try_as_result_assign_mut`] /// since we can't abstract over mutability. Input is the object to match on and the name of the /// function used to convert an `Option` into the result type (e.g. `as_ref` and `as_mut` diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 29467fddf70b..05f5b4de5635 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.48.0" +version = "0.49.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/rust-toolchain.toml b/rust-toolchain.toml index b6f6e8a63534..eee073e29a89 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [toolchain] -channel = "nightly-2024-03-11" +channel = "nightly-2024-03-29" components = ["llvm-tools-preview", "rustc-dev", "rust-src", "rustfmt"] diff --git a/rustfmt.toml b/rustfmt.toml index 32ff2d638c56..95988ef7f52a 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -15,5 +15,4 @@ ignore = [ # For some reason, this is not working without the directory wildcard. "**/firecracker", "**/tests/perf/s2n-quic/", - "**/tools/bookrunner/rust-doc/", ] diff --git a/scripts/build-docs.sh b/scripts/build-docs.sh index 3400e2849b53..cd40a2edabad 100755 --- a/scripts/build-docs.sh +++ b/scripts/build-docs.sh @@ -32,33 +32,12 @@ else MDBOOK=${SCRIPT_DIR}/mdbook fi -# Publish bookrunner report into our documentation KANI_DIR=$SCRIPT_DIR/.. DOCS_DIR=$KANI_DIR/docs RFC_DIR=$KANI_DIR/rfc -HTML_DIR=$KANI_DIR/build/output/latest/html/ cd $DOCS_DIR -if [ -d $HTML_DIR ]; then - # Litani run is copied into `src` to avoid deletion by `mdbook` - cp -r $HTML_DIR src/bookrunner/ - # Replace artifacts by examples under test - BOOKS_DIR=$KANI_DIR/tests/bookrunner/books - rm -r src/bookrunner/artifacts - # Remove any json files that Kani might've left behind due to crash or timeout. - find $BOOKS_DIR -name '*.json' -exec rm {} \; - find $BOOKS_DIR -name '*.out' -exec rm {} \; - cp -r $BOOKS_DIR src/bookrunner/artifacts - # Update paths in HTML report - python $KANI_DIR/scripts/ci/update_bookrunner_report.py src/bookrunner/index.html new_index.html - mv new_index.html src/bookrunner/index.html - - # rm src/bookrunner/run.json -else - echo "WARNING: Could not find the latest bookrunner run." -fi - echo "Building user documentation..." # Generate benchcomp documentation from source code mkdir -p gen_src diff --git a/scripts/ci/bookrunner_failures_by_stage.py b/scripts/ci/bookrunner_failures_by_stage.py deleted file mode 100644 index 941cc62ae2e5..000000000000 --- a/scripts/ci/bookrunner_failures_by_stage.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/python3 -# Copyright Kani Contributors -# SPDX-License-Identifier: Apache-2.0 OR MIT - -import argparse -from bs4 import BeautifulSoup - -def main(): - parser = argparse.ArgumentParser( - description='Scans an HTML dashboard file and prints' - 'the number of failures grouped by stage') - parser.add_argument('input') - args = parser.parse_args() - - with open(args.input) as fp: - run = BeautifulSoup(fp, 'html.parser') - - failures = [0] * 3 - - for row in run.find_all('div', attrs={'class': 'pipeline-row'}): - stages = row.find_all('div', attrs={'class': 'pipeline-stage'}) - i = 0 - for stage in stages: - if stage.a['class'][1] == 'fail': - failures[i] += 1 - break - i += 1 - - print('bookrunner failures grouped by stage:') - print(' * rustc-compilation: ' + str(failures[0])) - print(' * kani-codegen: ' + str(failures[1])) - print(' * cbmc-verification: ' + str(failures[2])) - - -if __name__ == "__main__": - main() diff --git a/scripts/ci/detect_bookrunner_failures.sh b/scripts/ci/detect_bookrunner_failures.sh deleted file mode 100755 index 027ea8174b5d..000000000000 --- a/scripts/ci/detect_bookrunner_failures.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -# Copyright Kani Contributors -# SPDX-License-Identifier: Apache-2.0 OR MIT - -set -eu - -# This script checks that the number of failures in a bookrunner run are below -# the threshold computed below. -# -# The threshold is roughly computed as: `1.05 * ` -# The extra 5% allows us to account for occasional timeouts. It is reviewed and -# updated whenever the Rust toolchain version is updated. -EXPECTED=82 -THRESHOLD=$(expr ${EXPECTED} \* 105 / 100) # Add 5% threshold - -if [[ $# -ne 1 ]]; then - echo "$0: Error: Specify the bookrunner text report" - exit 1 -fi - -# Get the summary line, which looks like: -# `# of tests: ✔️ ` -SUMMARY_LINE=`head -n 1 $1` - -# Parse the summary line and extract the number of failures -read -a strarr <<< $SUMMARY_LINE -NUM_FAILURES=${strarr[-1]} - -# Print a message and return a nonzero code if the threshold is exceeded -if [[ $NUM_FAILURES -ge $THRESHOLD ]]; then - echo "Error: The number of failures from bookrunner is higher than expected!" - echo - echo "Found $NUM_FAILURES which is higher than the threshold of $THRESHOLD" - echo "This means that your changes are causing at least 5% more failures than in previous bookrunner runs." - echo "To check these failures locally, run \`cargo run -p bookrunner\` and inspect the report in \`build/output/latest/html/index.html\`." - echo "For more details on bookrunner, go to https://model-checking.github.io/kani/bookrunner.html" - exit 1 -fi diff --git a/scripts/ci/update_bookrunner_report.py b/scripts/ci/update_bookrunner_report.py deleted file mode 100644 index fc1c47ad78c4..000000000000 --- a/scripts/ci/update_bookrunner_report.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/python3 -# Copyright Kani Contributors -# SPDX-License-Identifier: Apache-2.0 OR MIT - -import argparse -from bs4 import BeautifulSoup - -def update_path(run, path): - ''' - Shortens a path referring to an example and adds a link to the file. - - By default, the path to an example follows this pattern: - - `tests/bookrunner/books///
//.rs` - - However, only the first part is shown since these paths are enclosed - in paragraph markers (`

` and `

`). So they are often rendered as: - - `tests/bookrunner/books///... - - This update removes `tests/bookrunner/books/` from the path (common to - all examples) and transforms them into anchor elements with a link to - the example, so the path to the example is shown as: - - `//
//.rs` - ''' - orig_path = path.p.string - new_string = '/'.join(orig_path.split('/')[4:]) - new_tag = run.new_tag('a') - new_tag.string = new_string - # Add link to the example - new_tag['href'] = "artifacts/" + new_string - path.p.replace_with(new_tag) - -def main(): - parser = argparse.ArgumentParser( - description='Produces an updated HTML report file from the ' - 'contents of an HTML file generated with `litani`') - parser.add_argument('input') - parser.add_argument('output') - args = parser.parse_args() - - with open(args.input) as fp: - run = BeautifulSoup(fp, 'html.parser') - - # Update pipeline names to link to the example under test - for row in run.find_all(lambda tag: tag.name == 'div' and - tag.get('class') == ['pipeline-row']): - path = row.find('div', attrs={'class': 'pipeline-name'}) - # Some paths here may be `None` - skip them - if path.p: - update_path(run, path) - - # Delete links to empty artifacts folder from progress bars - for bar in run.find_all('a', attrs={'class': 'stage-artifacts-link fail'}): - del bar['href'] - for bar in run.find_all('a', attrs={'class': 'stage-artifacts-link success'}): - del bar['href'] - - with open(args.output, "w") as file: - file.write(str(run)) - - -if __name__ == "__main__": - main() diff --git a/scripts/setup/install_bookrunner_deps.sh b/scripts/setup/install_bookrunner_deps.sh deleted file mode 100755 index f1457c1e9c4d..000000000000 --- a/scripts/setup/install_bookrunner_deps.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# Copyright Kani Contributors -# SPDX-License-Identifier: Apache-2.0 OR MIT - -set -eu - -# The book runner report is generated using [Litani](https://github.com/awslabs/aws-build-accumulator) -FILE="litani-1.22.0.deb" -URL="https://github.com/awslabs/aws-build-accumulator/releases/download/1.22.0/$FILE" - -set -x - -# Install Litani -wget -O "$FILE" "$URL" -sudo DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends --yes ./"$FILE" - -PYTHON_DEPS=( - bs4 # Used for report updates -) - -python3 -m pip install "${PYTHON_DEPS[@]}" \ No newline at end of file diff --git a/src/setup.rs b/src/setup.rs index 050a8e166334..730679f06c10 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -141,22 +141,42 @@ pub(crate) fn get_rust_toolchain_version(kani_dir: &Path) -> Result { .context("Reading release bundle rust-toolchain-version") } +pub(crate) fn get_rustc_version_from_build(kani_dir: &Path) -> Result { + std::fs::read_to_string(kani_dir.join("rustc-version")) + .context("Reading release bundle rustc-version") +} + /// Install the Rust toolchain version we require fn setup_rust_toolchain(kani_dir: &Path, use_local_toolchain: Option) -> Result { // Currently this means we require the bundle to have been unpacked first! let toolchain_version = get_rust_toolchain_version(kani_dir)?; - println!("[3/5] Installing rust toolchain version: {}", &toolchain_version); + let rustc_version = get_rustc_version_from_build(kani_dir)?.trim().to_string(); // Symlink to a local toolchain if the user explicitly requests if let Some(local_toolchain_path) = use_local_toolchain { let toolchain_path = Path::new(&local_toolchain_path); - // TODO: match the version against which kani was built - // Issue: https://github.com/model-checking/kani/issues/3060 - symlink_rust_toolchain(toolchain_path, kani_dir)?; - return Ok(toolchain_version); + + let custom_toolchain_rustc_version = + get_rustc_version_from_local_toolchain(local_toolchain_path.clone())?; + + if rustc_version == custom_toolchain_rustc_version { + symlink_rust_toolchain(toolchain_path, kani_dir)?; + println!( + "[3/5] Installing rust toolchain from path provided: {}", + &toolchain_path.to_string_lossy() + ); + return Ok(toolchain_version); + } else { + bail!( + "The toolchain with rustc {:?} being used to setup is not the same as the one Kani used in its release bundle {:?}. Try to setup with the same version as the bundle.", + custom_toolchain_rustc_version, + rustc_version, + ); + } } // This is the default behaviour when no explicit path to a toolchain is mentioned + println!("[3/5] Installing rust toolchain version: {}", &toolchain_version); Command::new("rustup").args(["toolchain", "install", &toolchain_version]).run()?; let toolchain = home::rustup_home()?.join("toolchains").join(&toolchain_version); symlink_rust_toolchain(&toolchain, kani_dir)?; @@ -189,6 +209,27 @@ fn download_filename() -> String { format!("kani-{VERSION}-{TARGET}.tar.gz") } +/// Get the version of rustc that is being used to setup kani by the user +fn get_rustc_version_from_local_toolchain(path: OsString) -> Result { + let path = Path::new(&path); + let rustc_path = path.join("bin").join("rustc"); + + let output = Command::new(rustc_path).arg("--version").output(); + + match output { + Ok(output) => { + if output.status.success() { + Ok(String::from_utf8(output.stdout).map(|s| s.trim().to_string())?) + } else { + bail!( + "Could not parse rustc version string. Toolchain installation likely invalid. " + ); + } + } + Err(_) => bail!("Could not get rustc version. Toolchain installation likely invalid"), + } +} + /// The download URL for this version of Kani fn download_url() -> String { let tag: &str = &format!("kani-{VERSION}"); diff --git a/tests/cargo-ui/assess-error/expected b/tests/cargo-ui/assess-error/expected index 70754ddea192..213d811ae577 100644 --- a/tests/cargo-ui/assess-error/expected +++ b/tests/cargo-ui/assess-error/expected @@ -1,2 +1,2 @@ -error: Failed to compile lib `compilation-error` +error: Failed to compile lib `compilation_error` error: Failed to assess project: Failed to build all targets diff --git a/tests/coverage/unreachable/abort/main.rs b/tests/coverage/unreachable/abort/main.rs index 2941ec126f3c..39c0b0efb54f 100644 --- a/tests/coverage/unreachable/abort/main.rs +++ b/tests/coverage/unreachable/abort/main.rs @@ -5,7 +5,7 @@ use std::process; -#[kani::proof] +#[cfg_attr(kani, kani::proof, kani::unwind(5))] fn main() { for i in 0..4 { if i == 1 { diff --git a/tests/expected/abort/main.rs b/tests/expected/abort/main.rs index 2941ec126f3c..9e2f5b7a808c 100644 --- a/tests/expected/abort/main.rs +++ b/tests/expected/abort/main.rs @@ -6,6 +6,7 @@ use std::process; #[kani::proof] +#[kani::unwind(5)] fn main() { for i in 0..4 { if i == 1 { diff --git a/tests/expected/function-contract/fail_on_two.rs b/tests/expected/function-contract/fail_on_two.rs index 4302cdc7da33..605065449194 100644 --- a/tests/expected/function-contract/fail_on_two.rs +++ b/tests/expected/function-contract/fail_on_two.rs @@ -17,6 +17,7 @@ //! contract replacement) is used we'd expect the verification to succeed. #[kani::ensures(result < 3)] +#[kani::recursion] fn fail_on_two(i: i32) -> i32 { match i { 0 => fail_on_two(i + 1), @@ -32,6 +33,7 @@ fn harness() { } #[kani::ensures(result < 3)] +#[kani::recursion] fn fail_on_two_in_postcondition(i: i32) -> i32 { let j = i + 1; if i < 2 { fail_on_two_in_postcondition(j) } else { j } diff --git a/tests/expected/function-contract/gcd_rec_code_fail.rs b/tests/expected/function-contract/gcd_rec_code_fail.rs index 5f63cb247ebf..da38318e2a66 100644 --- a/tests/expected/function-contract/gcd_rec_code_fail.rs +++ b/tests/expected/function-contract/gcd_rec_code_fail.rs @@ -6,6 +6,7 @@ type T = u8; /// Euclid's algorithm for calculating the GCD of two numbers #[kani::requires(x != 0 && y != 0)] #[kani::ensures(result != 0 && x % result == 0 && y % result == 0)] +#[kani::recursion] fn gcd(x: T, y: T) -> T { let mut max = x; let mut min = y; diff --git a/tests/expected/function-contract/gcd_rec_comparison_pass.rs b/tests/expected/function-contract/gcd_rec_comparison_pass.rs index b08d94504bf7..1db9691ae8e4 100644 --- a/tests/expected/function-contract/gcd_rec_comparison_pass.rs +++ b/tests/expected/function-contract/gcd_rec_comparison_pass.rs @@ -6,6 +6,7 @@ type T = u8; /// Euclid's algorithm for calculating the GCD of two numbers #[kani::requires(x != 0 && y != 0)] #[kani::ensures(result != 0 && x % result == 0 && y % result == 0)] +#[kani::recursion] fn gcd(x: T, y: T) -> T { let mut max = x; let mut min = y; diff --git a/tests/expected/function-contract/gcd_rec_simple_pass.rs b/tests/expected/function-contract/gcd_rec_simple_pass.rs index ae55efa62b45..464e3d8bbea1 100644 --- a/tests/expected/function-contract/gcd_rec_simple_pass.rs +++ b/tests/expected/function-contract/gcd_rec_simple_pass.rs @@ -6,6 +6,7 @@ type T = u8; /// Euclid's algorithm for calculating the GCD of two numbers #[kani::requires(x != 0 && y != 0)] #[kani::ensures(result != 0 && x % result == 0 && y % result == 0)] +#[kani::recursion] fn gcd(x: T, y: T) -> T { let mut max = x; let mut min = y; diff --git a/tests/expected/function-contract/generic_infinity_recursion.rs b/tests/expected/function-contract/generic_infinity_recursion.rs index d40b7694dbdb..7512b4e1e461 100644 --- a/tests/expected/function-contract/generic_infinity_recursion.rs +++ b/tests/expected/function-contract/generic_infinity_recursion.rs @@ -5,6 +5,7 @@ //! Check Kani handling of generics and recursion with function contracts. #[kani::requires(x != 0)] +#[kani::recursion] fn foo>(x: T) { assert_ne!(x, 0); foo(x); diff --git a/tests/expected/function-contract/modifies/check_invalid_modifies.expected b/tests/expected/function-contract/modifies/check_invalid_modifies.expected new file mode 100644 index 000000000000..fa1d96f1fe3f --- /dev/null +++ b/tests/expected/function-contract/modifies/check_invalid_modifies.expected @@ -0,0 +1,3 @@ +error: `&str` doesn't implement `kani::Arbitrary`. Callee: `kani::Arbitrary::any` \ + Please, check whether the type of all objects in the modifies clause (including return types) implement `kani::Arbitrary`. \ + This is a strict condition to use function contracts as verified stubs. diff --git a/tests/expected/function-contract/modifies/check_invalid_modifies.rs b/tests/expected/function-contract/modifies/check_invalid_modifies.rs new file mode 100644 index 000000000000..5cd832d0f456 --- /dev/null +++ b/tests/expected/function-contract/modifies/check_invalid_modifies.rs @@ -0,0 +1,27 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +//! Check that Kani reports the correct error message when modifies clause +//! includes objects of types that do not implement `kani::Arbitrary`. +//! This restriction is only applied when using contracts as verified stubs. + +#[kani::requires(*ptr < 100)] +#[kani::modifies(ptr)] +fn modify(ptr: &mut u32) -> &'static str { + *ptr += 1; + let msg: &'static str = "done"; + msg +} + +fn use_modify(ptr: &mut u32) { + *ptr = 99; + assert!(modify(ptr) == "done"); +} + +#[kani::proof] +#[kani::stub_verified(modify)] +fn harness() { + let mut i = kani::any_where(|x| *x < 100); + use_modify(&mut i); +} diff --git a/tests/expected/function-contract/modifies/check_only_verification.expected b/tests/expected/function-contract/modifies/check_only_verification.expected new file mode 100644 index 000000000000..34c886c358cb --- /dev/null +++ b/tests/expected/function-contract/modifies/check_only_verification.expected @@ -0,0 +1 @@ +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/modifies/check_only_verification.rs b/tests/expected/function-contract/modifies/check_only_verification.rs new file mode 100644 index 000000000000..2f247a718ae9 --- /dev/null +++ b/tests/expected/function-contract/modifies/check_only_verification.rs @@ -0,0 +1,34 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +//! Check that Kani does not report any error when unused modifies clauses +//! includes objects of types that do not implement `kani::Arbitrary`. + +#[kani::requires(*ptr < 100)] +#[kani::modifies(ptr)] +#[kani::ensures(result == 100)] +fn modify(ptr: &mut u32) -> u32 { + *ptr += 1; + *ptr +} + +#[kani::requires(*ptr < 100)] +#[kani::modifies(ptr)] +fn wrong_modify(ptr: &mut u32) -> &'static str { + *ptr += 1; + let msg: &'static str = "done"; + msg +} + +fn use_modify(ptr: &mut u32) { + *ptr = 99; + assert!(modify(ptr) == 100); +} + +#[kani::proof] +#[kani::stub_verified(modify)] +fn harness() { + let mut i = kani::any_where(|x| *x < 100); + use_modify(&mut i); +} diff --git a/tests/expected/function-contract/modifies/fail_missing_recursion_attr.expected b/tests/expected/function-contract/modifies/fail_missing_recursion_attr.expected new file mode 100644 index 000000000000..6591cf27a8db --- /dev/null +++ b/tests/expected/function-contract/modifies/fail_missing_recursion_attr.expected @@ -0,0 +1 @@ +VERIFICATION:- FAILED diff --git a/tests/expected/function-contract/modifies/fail_missing_recursion_attr.rs b/tests/expected/function-contract/modifies/fail_missing_recursion_attr.rs new file mode 100644 index 000000000000..644b641e731e --- /dev/null +++ b/tests/expected/function-contract/modifies/fail_missing_recursion_attr.rs @@ -0,0 +1,21 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +//! Check whether Kani fails if users forgot to annotate recursive +//! functions with `#[kani::recursion]` when using function contracts. + +#[kani::ensures(result < 3)] +fn fail_on_two(i: i32) -> i32 { + match i { + 0 => fail_on_two(i + 1), + 1 => 2, + _ => unreachable!("fail on two"), + } +} + +#[kani::proof_for_contract(fail_on_two)] +fn harness() { + let first = fail_on_two(0); + let _ = fail_on_two(first); +} diff --git a/tests/expected/function-contract/modifies/mistake_condition_return.expected b/tests/expected/function-contract/modifies/mistake_condition_return.expected new file mode 100644 index 000000000000..ad1cbf4f72d2 --- /dev/null +++ b/tests/expected/function-contract/modifies/mistake_condition_return.expected @@ -0,0 +1,3 @@ +Failed Checks: assertion failed: res == 100 + +VERIFICATION:- FAILED diff --git a/tests/expected/function-contract/modifies/mistake_condition_return.rs b/tests/expected/function-contract/modifies/mistake_condition_return.rs new file mode 100644 index 000000000000..484819af4248 --- /dev/null +++ b/tests/expected/function-contract/modifies/mistake_condition_return.rs @@ -0,0 +1,39 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +//! Provide an example where users might get confuse on how to constrain +//! the return value of functions when writing function contracts. +//! In this case, users must remember that when using contracts as +//! verified stubs, the return value will be havoced. To retrict the return +//! value of a function, users may use the `result` keyword in their +//! ensures clauses. + +#[kani::requires(*ptr < 100)] +#[kani::modifies(ptr)] +// In this case, one may think that by assuming `*ptr == 100`, automatically +// we can assume the return value of this function will also be equal to 100. +// However, contract instrumentation will create a separate non-deterministic +// value to return in this function that can only be constrained by using the +// `result` keyword. Thus the correct condition would be +// `#[kani::ensures(result == 100)]`. +#[kani::ensures(*ptr == 100)] +fn modify(ptr: &mut u32) -> u32 { + *ptr += 1; + *ptr +} + +fn use_modify(ptr: &mut u32) { + *ptr = 99; + let res = modify(ptr); + // This assertion won't hold because the return + // value of `modify` is unconstrained. + assert!(res == 100); +} + +#[kani::proof] +#[kani::stub_verified(modify)] +fn harness() { + let mut i = kani::any(); + use_modify(&mut i); +} diff --git a/tests/expected/function-contract/modifies/simple_only_verification.expected b/tests/expected/function-contract/modifies/simple_only_verification.expected new file mode 100644 index 000000000000..34c886c358cb --- /dev/null +++ b/tests/expected/function-contract/modifies/simple_only_verification.expected @@ -0,0 +1 @@ +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/modifies/simple_only_verification.rs b/tests/expected/function-contract/modifies/simple_only_verification.rs new file mode 100644 index 000000000000..34d625485817 --- /dev/null +++ b/tests/expected/function-contract/modifies/simple_only_verification.rs @@ -0,0 +1,22 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +//! Check that is possible to use `modifies` clause for verifciation, but not stubbing. +//! Using contracts as verified stubs require users to ensure the type of any object in +//! the modifies clause (including return types) to implement `kani::Arbitrary`. +//! This requirement is not necessary if the contract is only used for verification. + +#[kani::requires(*ptr < 100)] +#[kani::modifies(ptr)] +fn modify(ptr: &mut u32) -> &'static str { + *ptr += 1; + let msg: &'static str = "done"; + msg +} + +#[kani::proof_for_contract(modify)] +fn harness() { + let mut i = kani::any_where(|x| *x < 100); + modify(&mut i); +} diff --git a/tests/expected/function-contract/prohibit-pointers/hidden.expected b/tests/expected/function-contract/prohibit-pointers/hidden.expected index 59e40d5aadc8..34c886c358cb 100644 --- a/tests/expected/function-contract/prohibit-pointers/hidden.expected +++ b/tests/expected/function-contract/prohibit-pointers/hidden.expected @@ -1 +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. +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/prohibit-pointers/plain_pointer.expected b/tests/expected/function-contract/prohibit-pointers/plain_pointer.expected index 916854aa3131..34c886c358cb 100644 --- a/tests/expected/function-contract/prohibit-pointers/plain_pointer.expected +++ b/tests/expected/function-contract/prohibit-pointers/plain_pointer.expected @@ -1 +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. +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/prohibit-pointers/return_pointer.expected b/tests/expected/function-contract/prohibit-pointers/return_pointer.expected index 8f3689888d92..34c886c358cb 100644 --- a/tests/expected/function-contract/prohibit-pointers/return_pointer.expected +++ b/tests/expected/function-contract/prohibit-pointers/return_pointer.expected @@ -1 +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. +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/prohibit-pointers/return_pointer.rs b/tests/expected/function-contract/prohibit-pointers/return_pointer.rs index fc279667ad13..2bacdca9bbb6 100644 --- a/tests/expected/function-contract/prohibit-pointers/return_pointer.rs +++ b/tests/expected/function-contract/prohibit-pointers/return_pointer.rs @@ -2,24 +2,16 @@ // 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::ensures(unsafe{ *result == *input })] +fn return_pointer(input: *const usize) -> *const usize { + input } #[kani::proof_for_contract(return_pointer)] fn return_ptr_harness() { - return_pointer(); + let input: usize = 10; + let input_ptr = &input as *const usize; + unsafe { + assert!(*(return_pointer(input_ptr)) == input); + } } diff --git a/tests/expected/iterator/main.rs b/tests/expected/iterator/main.rs index b1cb4a89cfbf..5cf9402bcb23 100644 --- a/tests/expected/iterator/main.rs +++ b/tests/expected/iterator/main.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT #[kani::proof] +#[kani::unwind(4)] fn main() { let mut z = 1; for i in 1..4 { diff --git a/tests/kani/Coroutines/main.rs b/tests/kani/Coroutines/main.rs index 10d92571aaa6..14cbeb426321 100644 --- a/tests/kani/Coroutines/main.rs +++ b/tests/kani/Coroutines/main.rs @@ -9,6 +9,7 @@ use std::ops::{Coroutine, CoroutineState}; use std::pin::Pin; #[kani::proof] +#[kani::unwind(3)] fn main() { let mut add_one = |mut resume: u8| { loop { diff --git a/tests/kani/Intrinsics/typed_swap.rs b/tests/kani/Intrinsics/typed_swap.rs new file mode 100644 index 000000000000..4279bb713ece --- /dev/null +++ b/tests/kani/Intrinsics/typed_swap.rs @@ -0,0 +1,21 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// Check that `typed_swap` yields the expected results. +// https://doc.rust-lang.org/nightly/std/intrinsics/fn.typed_swap.html + +#![feature(core_intrinsics)] +#![allow(internal_features)] + +#[kani::proof] +fn test_typed_swap_u32() { + let mut a: u32 = kani::any(); + let a_before = a; + let mut b: u32 = kani::any(); + let b_before = b; + unsafe { + std::intrinsics::typed_swap(&mut a, &mut b); + } + assert!(b == a_before); + assert!(a == b_before); +} diff --git a/tests/kani/Spurious/storage_fixme.rs b/tests/kani/Spurious/storage_fixme.rs new file mode 100644 index 000000000000..51d13f31bcef --- /dev/null +++ b/tests/kani/Spurious/storage_fixme.rs @@ -0,0 +1,652 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// Modifications Copyright Kani Contributors +// See GitHub history for details. + +// Our handling of storage markers causes spurious failures in this test. +// https://github.com/model-checking/kani/issues/3099 +// The code is extracted from the implementation of `BTreeMap` which is where we +// originally saw the spurious failures while trying to enable storage markers +// for `std` in https://github.com/model-checking/kani/pull/3080 + +use std::alloc::Layout; +use std::marker::PhantomData; +use std::mem::ManuallyDrop; + +use std::ptr::{self, NonNull}; + +/// This replaces the value behind the `v` unique reference by calling the +/// relevant function, and returns a result obtained along the way. +/// +/// If a panic occurs in the `change` closure, the entire process will be aborted. +#[inline] +fn replace(v: &mut T, change: impl FnOnce(T) -> (T, R)) -> R { + let value = unsafe { ptr::read(v) }; + let (new_value, ret) = change(value); + unsafe { + ptr::write(v, new_value); + } + ret +} + +const B: usize = 6; +const CAPACITY: usize = 2 * B - 1; + +/// The underlying representation of leaf nodes and part of the representation of internal nodes. +struct LeafNode { + /// We want to be covariant in `K` and `V`. + parent: Option>, + + /// This node's index into the parent node's `edges` array. + /// `*node.parent.edges[node.parent_idx]` should be the same thing as `node`. + /// This is only guaranteed to be initialized when `parent` is non-null. + parent_idx: u16, + + /// The number of keys and values this node stores. + len: u16, +} + +impl LeafNode { + /// Creates a new boxed `LeafNode`. + fn new() -> Box { + Box::new(LeafNode { parent: None, parent_idx: 0, len: 0 }) + } +} + +/// The underlying representation of internal nodes. As with `LeafNode`s, these should be hidden +/// behind `BoxedNode`s to prevent dropping uninitialized keys and values. Any pointer to an +/// `InternalNode` can be directly cast to a pointer to the underlying `LeafNode` portion of the +/// node, allowing code to act on leaf and internal nodes generically without having to even check +/// which of the two a pointer is pointing at. This property is enabled by the use of `repr(C)`. +// gdb_providers.py uses this type name for introspection. +struct InternalNode {} + +// N.B. `NodeRef` is always covariant in `K` and `V`, even when the `BorrowType` +// is `Mut`. This is technically wrong, but cannot result in any unsafety due to +// internal use of `NodeRef` because we stay completely generic over `K` and `V`. +// However, whenever a public type wraps `NodeRef`, make sure that it has the +// correct variance. +/// +/// A reference to a node. +/// +/// This type has a number of parameters that controls how it acts: +/// - `BorrowType`: A dummy type that describes the kind of borrow and carries a lifetime. +/// - When this is `Immut<'a>`, the `NodeRef` acts roughly like `&'a Node`. +/// - When this is `ValMut<'a>`, the `NodeRef` acts roughly like `&'a Node` +/// with respect to keys and tree structure, but also allows many +/// mutable references to values throughout the tree to coexist. +/// - When this is `Mut<'a>`, the `NodeRef` acts roughly like `&'a mut Node`, +/// although insert methods allow a mutable pointer to a value to coexist. +/// - When this is `Owned`, the `NodeRef` acts roughly like `Box`, +/// but does not have a destructor, and must be cleaned up manually. +/// - When this is `Dying`, the `NodeRef` still acts roughly like `Box`, +/// but has methods to destroy the tree bit by bit, and ordinary methods, +/// while not marked as unsafe to call, can invoke UB if called incorrectly. +/// Since any `NodeRef` allows navigating through the tree, `BorrowType` +/// effectively applies to the entire tree, not just to the node itself. +/// - `K` and `V`: These are the types of keys and values stored in the nodes. +/// - `Type`: This can be `Leaf`, `Internal`, or `LeafOrInternal`. When this is +/// `Leaf`, the `NodeRef` points to a leaf node, when this is `Internal` the +/// `NodeRef` points to an internal node, and when this is `LeafOrInternal` the +/// `NodeRef` could be pointing to either type of node. +/// `Type` is named `NodeType` when used outside `NodeRef`. +/// +/// Both `BorrowType` and `NodeType` restrict what methods we implement, to +/// exploit static type safety. There are limitations in the way we can apply +/// such restrictions: +/// - For each type parameter, we can only define a method either generically +/// or for one particular type. For example, we cannot define a method like +/// `into_kv` generically for all `BorrowType`, or once for all types that +/// carry a lifetime, because we want it to return `&'a` references. +/// Therefore, we define it only for the least powerful type `Immut<'a>`. +/// - We cannot get implicit coercion from say `Mut<'a>` to `Immut<'a>`. +/// Therefore, we have to explicitly call `reborrow` on a more powerful +/// `NodeRef` in order to reach a method like `into_kv`. +/// +/// All methods on `NodeRef` that return some kind of reference, either: +/// - Take `self` by value, and return the lifetime carried by `BorrowType`. +/// Sometimes, to invoke such a method, we need to call `reborrow_mut`. +/// - Take `self` by reference, and (implicitly) return that reference's +/// lifetime, instead of the lifetime carried by `BorrowType`. That way, +/// the borrow checker guarantees that the `NodeRef` remains borrowed as long +/// as the returned reference is used. +/// The methods supporting insert bend this rule by returning a raw pointer, +/// i.e., a reference without any lifetime. +struct NodeRef { + /// The number of levels that the node and the level of leaves are apart, a + /// constant of the node that cannot be entirely described by `Type`, and that + /// the node itself does not store. We only need to store the height of the root + /// node, and derive every other node's height from it. + /// Must be zero if `Type` is `Leaf` and non-zero if `Type` is `Internal`. + height: usize, + /// The pointer to the leaf or internal node. The definition of `InternalNode` + /// ensures that the pointer is valid either way. + node: NonNull, + _marker: PhantomData<(BorrowType, Type)>, +} + +/// The root node of an owned tree. +/// +/// Note that this does not have a destructor, and must be cleaned up manually. +type Root = NodeRef; + +impl NodeRef { + fn new_leaf() -> Self { + Self::from_new_leaf(LeafNode::new()) + } + + fn from_new_leaf(leaf: Box) -> Self { + NodeRef { height: 0, node: NonNull::from(Box::leak(leaf)), _marker: PhantomData } + } +} + +impl NodeRef { + /// Unpack a node reference that was packed as `NodeRef::parent`. + fn from_internal(node: NonNull, height: usize) -> Self { + debug_assert!(height > 0); + NodeRef { height, node: node.cast(), _marker: PhantomData } + } +} + +impl NodeRef { + /// Finds the length of the node. This is the number of keys or values. + /// The number of edges is `len() + 1`. + /// Note that, despite being safe, calling this function can have the side effect + /// of invalidating mutable references that unsafe code has created. + fn len(&self) -> usize { + // Crucially, we only access the `len` field here. If BorrowType is marker::ValMut, + // there might be outstanding mutable references to values that we must not invalidate. + unsafe { usize::from((*Self::as_leaf_ptr(self)).len) } + } + + /// Exposes the leaf portion of any leaf or internal node. + /// + /// Returns a raw ptr to avoid invalidating other references to this node. + fn as_leaf_ptr(this: &Self) -> *mut LeafNode { + // The node must be valid for at least the LeafNode portion. + // This is not a reference in the NodeRef type because we don't know if + // it should be unique or shared. + this.node.as_ptr() + } +} + +impl NodeRef { + /// Finds the parent of the current node. Returns `Ok(handle)` if the current + /// node actually has a parent, where `handle` points to the edge of the parent + /// that points to the current node. Returns `Err(self)` if the current node has + /// no parent, giving back the original `NodeRef`. + /// + /// The method name assumes you picture trees with the root node on top. + /// + /// `edge.descend().ascend().unwrap()` and `node.ascend().unwrap().descend()` should + /// both, upon success, do nothing. + fn ascend(self) -> Result, marker::Edge>, Self> { + // We need to use raw pointers to nodes because, if BorrowType is marker::ValMut, + // there might be outstanding mutable references to values that we must not invalidate. + let leaf_ptr: *const _ = Self::as_leaf_ptr(&self); + unsafe { (*leaf_ptr).parent } + .as_ref() + .map(|parent| Handle { + node: NodeRef::from_internal(*parent, self.height + 1), + idx: unsafe { usize::from((*leaf_ptr).parent_idx) }, + _marker: PhantomData, + }) + .ok_or(self) + } + + fn first_edge(self) -> Handle { + unsafe { Handle::new_edge(self, 0) } + } +} + +impl NodeRef { + /// Similar to `ascend`, gets a reference to a node's parent node, but also + /// deallocates the current node in the process. This is unsafe because the + /// current node will still be accessible despite being deallocated. + unsafe fn deallocate_and_ascend( + self, + ) -> Option, marker::Edge>> { + let height = self.height; + let node = self.node; + let ret = self.ascend().ok(); + unsafe { + std::alloc::dealloc( + node.as_ptr() as *mut u8, + if height > 0 { Layout::new::() } else { Layout::new::() }, + ); + } + ret + } +} + +impl<'a, Type> NodeRef, Type> { + /// Borrows exclusive access to the leaf portion of a leaf or internal node. + fn as_leaf_mut(&mut self) -> &mut LeafNode { + let ptr = Self::as_leaf_ptr(self); + // SAFETY: we have exclusive access to the entire node. + unsafe { &mut *ptr } + } +} + +impl<'a, Type> NodeRef, Type> { + /// Borrows exclusive access to the length of the node. + fn len_mut(&mut self) -> &mut u16 { + &mut self.as_leaf_mut().len + } +} + +impl NodeRef { + /// Mutably borrows the owned root node. Unlike `reborrow_mut`, this is safe + /// because the return value cannot be used to destroy the root, and there + /// cannot be other references to the tree. + fn borrow_mut(&mut self) -> NodeRef, Type> { + NodeRef { height: self.height, node: self.node, _marker: PhantomData } + } + + /// Irreversibly transitions to a reference that permits traversal and offers + /// destructive methods and little else. + fn into_dying(self) -> NodeRef { + NodeRef { height: self.height, node: self.node, _marker: PhantomData } + } +} + +impl<'a> NodeRef, marker::Leaf> { + /// Adds a key-value pair to the end of the node, and returns + /// a handle to the inserted value. + /// + /// # Safety + /// + /// The returned handle has an unbound lifetime. + unsafe fn push_with_handle<'b>( + &mut self, + ) -> Handle, marker::Leaf>, marker::KV> { + let len = self.len_mut(); + let idx = usize::from(*len); + assert!(idx < CAPACITY); + *len += 1; + unsafe { + Handle::new_kv( + NodeRef { height: self.height, node: self.node, _marker: PhantomData }, + idx, + ) + } + } + + /// Adds a key-value pair to the end of the node, and returns + /// the mutable reference of the inserted value. + fn push(&mut self) -> i32 { + // SAFETY: The unbound handle is no longer accessible. + let _ = unsafe { self.push_with_handle() }; + 0 + } +} + +impl NodeRef { + /// Removes any static information asserting that this node is a `Leaf` node. + fn forget_type(self) -> NodeRef { + NodeRef { height: self.height, node: self.node, _marker: PhantomData } + } +} + +impl NodeRef { + /// Removes any static information asserting that this node is an `Internal` node. + fn forget_type(self) -> NodeRef { + NodeRef { height: self.height, node: self.node, _marker: PhantomData } + } +} + +impl NodeRef { + /// Checks whether a node is an `Internal` node or a `Leaf` node. + fn force(self) -> ForceResult> { + if self.height == 0 { + ForceResult::Leaf(NodeRef { + height: self.height, + node: self.node, + _marker: PhantomData, + }) + } else { + panic!() + } + } +} + +/// A reference to a specific key-value pair or edge within a node. The `Node` parameter +/// must be a `NodeRef`, while the `Type` can either be `KV` (signifying a handle on a key-value +/// pair) or `Edge` (signifying a handle on an edge). +/// +/// Note that even `Leaf` nodes can have `Edge` handles. Instead of representing a pointer to +/// a child node, these represent the spaces where child pointers would go between the key-value +/// pairs. For example, in a node with length 2, there would be 3 possible edge locations - one +/// to the left of the node, one between the two pairs, and one at the right of the node. +struct Handle { + node: Node, + idx: usize, + _marker: PhantomData, +} + +impl Handle { + /// Retrieves the node that contains the edge or key-value pair this handle points to. + fn into_node(self) -> Node { + self.node + } +} + +impl Handle, marker::KV> { + /// Creates a new handle to a key-value pair in `node`. + /// Unsafe because the caller must ensure that `idx < node.len()`. + unsafe fn new_kv(node: NodeRef, idx: usize) -> Self { + debug_assert!(idx < node.len()); + + Handle { node, idx, _marker: PhantomData } + } + + fn right_edge(self) -> Handle, marker::Edge> { + unsafe { Handle::new_edge(self.node, self.idx + 1) } + } +} + +impl Handle, marker::Edge> { + /// Creates a new handle to an edge in `node`. + /// Unsafe because the caller must ensure that `idx <= node.len()`. + unsafe fn new_edge(node: NodeRef, idx: usize) -> Self { + debug_assert!(idx <= node.len()); + + Handle { node, idx, _marker: PhantomData } + } + + fn right_kv(self) -> Result, marker::KV>, Self> { + if self.idx < self.node.len() { + Ok(unsafe { Handle::new_kv(self.node, self.idx) }) + } else { + Err(self) + } + } +} + +impl Handle, marker::Edge> { + fn forget_node_type(self) -> Handle, marker::Edge> { + unsafe { Handle::new_edge(self.node.forget_type(), self.idx) } + } +} + +impl Handle, marker::Edge> { + fn forget_node_type(self) -> Handle, marker::Edge> { + unsafe { Handle::new_edge(self.node.forget_type(), self.idx) } + } +} + +impl Handle, Type> { + /// Checks whether the underlying node is an `Internal` node or a `Leaf` node. + fn force(self) -> ForceResult, Type>> { + match self.node.force() { + ForceResult::Leaf(node) => { + ForceResult::Leaf(Handle { node, idx: self.idx, _marker: PhantomData }) + } + } + } +} +enum ForceResult { + Leaf(Leaf), +} + +mod marker { + use std::marker::PhantomData; + + pub enum Leaf {} + pub enum Internal {} + pub enum Owned {} + pub enum Dying {} + pub struct Mut<'a>(PhantomData<&'a mut ()>); + + pub trait BorrowType { + /// If node references of this borrow type allow traversing to other + /// nodes in the tree, this constant is set to `true`. It can be used + /// for a compile-time assertion. + const TRAVERSAL_PERMIT: bool = true; + } + impl BorrowType for Owned { + /// Reject traversal, because it isn't needed. Instead traversal + /// happens using the result of `borrow_mut`. + /// By disabling traversal, and only creating new references to roots, + /// we know that every reference of the `Owned` type is to a root node. + const TRAVERSAL_PERMIT: bool = false; + } + impl BorrowType for Dying {} + impl<'a> BorrowType for Mut<'a> {} + + pub enum KV {} + pub enum Edge {} +} + +enum LazyLeafHandle { + Root(NodeRef), // not yet descended + Edge(Handle, marker::Edge>), +} + +// `front` and `back` are always both `None` or both `Some`. +struct LazyLeafRange { + front: Option>, +} + +impl LazyLeafRange { + fn none() -> Self { + LazyLeafRange { front: None } + } +} + +impl LazyLeafRange { + fn take_front(&mut self) -> Option, marker::Edge>> { + match self.front.take()? { + LazyLeafHandle::Root(root) => Some(root.first_leaf_edge()), + LazyLeafHandle::Edge(edge) => Some(edge), + } + } + + #[inline] + unsafe fn deallocating_next_unchecked( + &mut self, + ) -> Handle, marker::KV> { + debug_assert!(self.front.is_some()); + let front = self.init_front().unwrap(); + unsafe { front.deallocating_next_unchecked() } + } + + #[inline] + fn deallocating_end(&mut self) { + if let Some(front) = self.take_front() { + front.deallocating_end() + } + } +} + +impl LazyLeafRange { + fn init_front( + &mut self, + ) -> Option<&mut Handle, marker::Edge>> { + if let Some(LazyLeafHandle::Root(root)) = &self.front { + self.front = Some(LazyLeafHandle::Edge(unsafe { ptr::read(root) }.first_leaf_edge())); + } + match &mut self.front { + None => None, + Some(LazyLeafHandle::Edge(edge)) => Some(edge), + // SAFETY: the code above would have replaced it. + Some(LazyLeafHandle::Root(_)) => panic!(), + } + } +} + +fn full_range( + root1: NodeRef, +) -> LazyLeafRange { + LazyLeafRange { front: Some(LazyLeafHandle::Root(root1)) } +} + +impl NodeRef { + /// Splits a unique reference into a pair of leaf edges delimiting the full range of the tree. + /// The results are non-unique references allowing massively destructive mutation, so must be + /// used with the utmost care. + fn full_range(self) -> LazyLeafRange { + // We duplicate the root NodeRef here -- we will never access it in a way + // that overlaps references obtained from the root. + full_range(self) + } +} + +impl Handle, marker::Edge> { + /// Given a leaf edge handle into a dying tree, returns the next leaf edge + /// on the right side, and the key-value pair in between, if they exist. + /// + /// If the given edge is the last one in a leaf, this method deallocates + /// the leaf, as well as any ancestor nodes whose last edge was reached. + /// This implies that if no more key-value pair follows, the entire tree + /// will have been deallocated and there is nothing left to return. + /// + /// # Safety + /// - The given edge must not have been previously returned by counterpart + /// `deallocating_next_back`. + /// - The returned KV handle is only valid to access the key and value, + /// and only valid until the next call to a `deallocating_` method. + unsafe fn deallocating_next( + self, + ) -> Option<(Self, Handle, marker::KV>)> { + let mut edge = self.forget_node_type(); + loop { + edge = match edge.right_kv() { + Ok(kv) => return Some((unsafe { ptr::read(&kv) }.next_leaf_edge(), kv)), + Err(last_edge) => match unsafe { last_edge.into_node().deallocate_and_ascend() } { + Some(parent_edge) => parent_edge.forget_node_type(), + None => return None, + }, + } + } + } + + /// Deallocates a pile of nodes from the leaf up to the root. + /// This is the only way to deallocate the remainder of a tree after + /// `deallocating_next` and `deallocating_next_back` have been nibbling at + /// both sides of the tree, and have hit the same edge. As it is intended + /// only to be called when all keys and values have been returned, + /// no cleanup is done on any of the keys or values. + fn deallocating_end(self) { + let mut edge = self.forget_node_type(); + while let Some(parent_edge) = unsafe { edge.into_node().deallocate_and_ascend() } { + edge = parent_edge.forget_node_type(); + } + } +} + +impl Handle, marker::Edge> { + /// Moves the leaf edge handle to the next leaf edge and returns the key and value + /// in between, deallocating any node left behind while leaving the corresponding + /// edge in its parent node dangling. + /// + /// # Safety + /// - There must be another KV in the direction travelled. + /// - That KV was not previously returned by counterpart + /// `deallocating_next_back_unchecked` on any copy of the handles + /// being used to traverse the tree. + /// + /// The only safe way to proceed with the updated handle is to compare it, drop it, + /// or call this method or counterpart `deallocating_next_back_unchecked` again. + unsafe fn deallocating_next_unchecked( + &mut self, + ) -> Handle, marker::KV> { + replace(self, |leaf_edge| unsafe { leaf_edge.deallocating_next().unwrap() }) + } +} + +impl NodeRef { + /// Returns the leftmost leaf edge in or underneath a node - in other words, the edge + /// you need first when navigating forward (or last when navigating backward). + #[inline] + fn first_leaf_edge(self) -> Handle, marker::Edge> { + let node = self; + match node.force() { + ForceResult::Leaf(leaf) => return leaf.first_edge(), + } + } +} + +impl Handle, marker::KV> { + /// Returns the leaf edge closest to a KV for forward navigation. + fn next_leaf_edge(self) -> Handle, marker::Edge> { + match self.force() { + ForceResult::Leaf(leaf_kv) => leaf_kv.right_edge(), + } + } +} + +struct Foo { + root: Option, + length: usize, +} + +impl Drop for Foo { + fn drop(&mut self) { + drop(unsafe { core::ptr::read(self) }.into_iter()) + } +} + +impl IntoIterator for Foo { + type Item = (); + type IntoIter = IntoIter; + + fn into_iter(self) -> IntoIter { + let mut me = ManuallyDrop::new(self); + if let Some(root) = me.root.take() { + let full_range = root.into_dying().full_range(); + + IntoIter { range: full_range, length: me.length } + } else { + IntoIter { range: LazyLeafRange::none(), length: 0 } + } + } +} + +impl Drop for IntoIter { + fn drop(&mut self) { + while let Some(_kv) = self.dying_next() {} + } +} + +impl IntoIter { + /// Core of a `next` method returning a dying KV handle, + /// invalidated by further calls to this function and some others. + fn dying_next(&mut self) -> Option, marker::KV>> { + if self.length == 0 { + self.range.deallocating_end(); + None + } else { + self.length -= 1; + Some(unsafe { self.range.deallocating_next_unchecked() }) + } + } +} + +impl Iterator for IntoIter { + type Item = (); + + fn next(&mut self) -> Option<()> { + None + } +} + +/// An owning iterator over the entries of a `BTreeMap`. +/// +/// This `struct` is created by the [`into_iter`] method on [`BTreeMap`] +/// (provided by the [`IntoIterator`] trait). See its documentation for more. +/// +/// [`into_iter`]: IntoIterator::into_iter +struct IntoIter { + range: LazyLeafRange, + length: usize, +} + +#[cfg_attr(kani, kani::proof, kani::unwind(3))] +fn check_storagemarker_btreemap() { + let mut f = Foo { root: None, length: 0 }; + let mut root: NodeRef = NodeRef::new_leaf(); + root.borrow_mut().push(); + f.root = Some(root.forget_type()); + f.length = 1; +} diff --git a/tests/kani/ValidValues/constants.rs b/tests/kani/ValidValues/constants.rs new file mode 100644 index 000000000000..5230e6e5e6cb --- /dev/null +++ b/tests/kani/ValidValues/constants.rs @@ -0,0 +1,40 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z valid-value-checks +//! Check that Kani can identify UB when it is reading from a constant. +//! Note that this UB will be removed for `-Z mir-opt-level=2` + +#[kani::proof] +fn transmute_valid_bool() { + let _b = unsafe { std::mem::transmute::(1) }; +} + +#[kani::proof] +fn cast_to_valid_char() { + let _c = unsafe { *(&100u32 as *const u32 as *const char) }; +} + +#[kani::proof] +fn cast_to_valid_offset() { + let val = [100u32, 80u32]; + let _c = unsafe { *(&val as *const [u32; 2] as *const [char; 2]) }; +} + +#[kani::proof] +#[kani::should_panic] +fn transmute_invalid_bool() { + let _b = unsafe { std::mem::transmute::(2) }; +} + +#[kani::proof] +#[kani::should_panic] +fn cast_to_invalid_char() { + let _c = unsafe { *(&u32::MAX as *const u32 as *const char) }; +} + +#[kani::proof] +#[kani::should_panic] +fn cast_to_invalid_offset() { + let val = [100u32, u32::MAX]; + let _c = unsafe { *(&val as *const [u32; 2] as *const [char; 2]) }; +} diff --git a/tests/kani/ValidValues/custom_niche.rs b/tests/kani/ValidValues/custom_niche.rs new file mode 100644 index 000000000000..02b5b87bd092 --- /dev/null +++ b/tests/kani/ValidValues/custom_niche.rs @@ -0,0 +1,100 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z valid-value-checks +//! Check that Kani can identify UB when using niche attribute for a custom operation. +#![feature(rustc_attrs)] + +use std::mem; +use std::mem::size_of; + +/// A possible implementation for a system of rating that defines niche. +/// A Rating represents the number of stars of a given product (1..=5). +#[rustc_layout_scalar_valid_range_start(1)] +#[rustc_layout_scalar_valid_range_end(5)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Rating { + stars: u8, +} + +impl kani::Arbitrary for Rating { + fn any() -> Self { + let stars = kani::any_where(|s: &u8| *s >= 1 && *s <= 5); + unsafe { Rating { stars } } + } +} + +impl Rating { + /// Buggy version of new. Note that this still creates an invalid Rating. + /// + /// This is because `then_some` eagerly create the Rating value before assessing the condition. + /// Even though the value is never used, it is still considered UB. + pub fn new(value: u8) -> Option { + (value > 0 && value <= 5).then_some(unsafe { Rating { stars: value } }) + } + + pub unsafe fn new_unchecked(stars: u8) -> Rating { + Rating { stars } + } +} + +#[kani::proof] +#[kani::should_panic] +pub fn check_new_with_ub() { + assert_eq!(Rating::new(10), None); +} + +#[kani::proof] +#[kani::should_panic] +pub fn check_unchecked_new_ub() { + let val = kani::any(); + assert_eq!(unsafe { Rating::new_unchecked(val).stars }, val); +} + +#[kani::proof] +#[kani::should_panic] +pub fn check_new_with_ub_limits() { + let stars = kani::any_where(|s: &u8| *s == 0 || *s > 5); + let _ = Rating::new(stars); +} + +#[kani::proof] +#[kani::should_panic] +pub fn check_invalid_dereference() { + let any: u8 = kani::any(); + let _rating: Rating = unsafe { *(&any as *const _ as *const _) }; +} + +#[kani::proof] +#[kani::should_panic] +pub fn check_invalid_transmute() { + let any: u8 = kani::any(); + let _rating: Rating = unsafe { mem::transmute(any) }; +} + +#[kani::proof] +#[kani::should_panic] +pub fn check_invalid_transmute_copy() { + let any: u8 = kani::any(); + let _rating: Rating = unsafe { mem::transmute_copy(&any) }; +} + +#[kani::proof] +#[kani::should_panic] +pub fn check_invalid_increment() { + let mut orig: Rating = kani::any(); + unsafe { orig.stars += 1 }; +} + +#[kani::proof] +pub fn check_valid_increment() { + let mut orig: Rating = kani::any(); + kani::assume(orig.stars < 5); + unsafe { orig.stars += 1 }; +} + +/// Check that the compiler relies on valid value range of Rating to implement niche optimization. +#[kani::proof] +pub fn check_niche() { + assert_eq!(size_of::(), size_of::>()); + assert_eq!(size_of::(), size_of::>>()); +} diff --git a/tests/kani/ValidValues/non_null.rs b/tests/kani/ValidValues/non_null.rs new file mode 100644 index 000000000000..4874b61bf2d0 --- /dev/null +++ b/tests/kani/ValidValues/non_null.rs @@ -0,0 +1,26 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z valid-value-checks +//! Check that Kani can identify UB when unsafely writing to NonNull. + +use std::num::NonZeroU8; +use std::ptr::{self, NonNull}; + +#[kani::proof] +#[kani::should_panic] +pub fn check_invalid_value() { + let _ = unsafe { NonNull::new_unchecked(ptr::null_mut::()) }; +} + +#[kani::proof] +#[kani::should_panic] +pub fn check_invalid_value_cfg() { + let nn = unsafe { NonNull::new_unchecked(ptr::null_mut::()) }; + // This should be unreachable. TODO: Make this expected test. + assert_ne!(unsafe { nn.as_ref() }, &10); +} + +#[kani::proof] +pub fn check_valid_dangling() { + let _ = unsafe { NonNull::new_unchecked(4 as *mut u32) }; +} diff --git a/tests/kani/ValidValues/write_invalid_fixme.rs b/tests/kani/ValidValues/write_invalid_fixme.rs new file mode 100644 index 000000000000..f04a667a47d1 --- /dev/null +++ b/tests/kani/ValidValues/write_invalid_fixme.rs @@ -0,0 +1,40 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z valid-value-checks + +//! Check that Kani can identify UB after writing an invalid value. +//! Writing invalid bytes is not UB as long as the incorrect value is not read. +//! However, we over-approximate for sake of simplicity and performance. + +// Note: We're getting an unexpected compilation error because the type returned +// from StableMIR is `Alias`: https://github.com/model-checking/kani/issues/3113 + +use std::num::NonZeroU8; + +#[kani::proof] +#[kani::should_panic] +pub fn write_invalid_bytes_no_ub_with_spurious_cex() { + let mut non_zero: NonZeroU8 = kani::any(); + let dest = &mut non_zero as *mut _; + unsafe { std::intrinsics::write_bytes(dest, 0, 1) }; +} + +#[kani::proof] +#[kani::should_panic] +pub fn write_valid_before_read() { + let mut non_zero: NonZeroU8 = kani::any(); + let mut non_zero_2: NonZeroU8 = kani::any(); + let dest = &mut non_zero as *mut _; + unsafe { std::intrinsics::write_bytes(dest, 0, 1) }; + unsafe { std::intrinsics::write_bytes(dest, non_zero_2.get(), 1) }; + assert_eq!(non_zero, non_zero_2) +} + +#[kani::proof] +#[kani::should_panic] +pub fn read_invalid_is_ub() { + let mut non_zero: NonZeroU8 = kani::any(); + let dest = &mut non_zero as *mut _; + unsafe { std::intrinsics::write_bytes(dest, 0, 1) }; + assert_eq!(non_zero.get(), 0) +} diff --git a/tests/perf/btreeset/insert_any/Cargo.toml b/tests/perf/btreeset/insert_any/Cargo.toml index 41fa0a2db3ba..66d8ecdddeb1 100644 --- a/tests/perf/btreeset/insert_any/Cargo.toml +++ b/tests/perf/btreeset/insert_any/Cargo.toml @@ -9,3 +9,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] + +# Temporarily ignore the handling of storage markers till +# https://github.com/model-checking/kani/issues/3099 is fixed +[package.metadata.kani] +flags = { ignore-locals-lifetime = true, enable-unstable = true } diff --git a/tests/perf/btreeset/insert_multi/Cargo.toml b/tests/perf/btreeset/insert_multi/Cargo.toml index bdd2f4e3528a..44028f8c842d 100644 --- a/tests/perf/btreeset/insert_multi/Cargo.toml +++ b/tests/perf/btreeset/insert_multi/Cargo.toml @@ -9,3 +9,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] + +# Temporarily ignore the handling of storage markers till +# https://github.com/model-checking/kani/issues/3099 is fixed +[package.metadata.kani] +flags = { ignore-locals-lifetime = true, enable-unstable = true } diff --git a/tests/perf/btreeset/insert_same/Cargo.toml b/tests/perf/btreeset/insert_same/Cargo.toml index 0a4e0f7ee037..465119c74fbe 100644 --- a/tests/perf/btreeset/insert_same/Cargo.toml +++ b/tests/perf/btreeset/insert_same/Cargo.toml @@ -9,3 +9,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] + +# Temporarily ignore the handling of storage markers till +# https://github.com/model-checking/kani/issues/3099 is fixed +[package.metadata.kani] +flags = { ignore-locals-lifetime = true, enable-unstable = true } diff --git a/tests/perf/hashset/Cargo.toml b/tests/perf/hashset/Cargo.toml index d0757e11154b..464fba412e6d 100644 --- a/tests/perf/hashset/Cargo.toml +++ b/tests/perf/hashset/Cargo.toml @@ -12,3 +12,8 @@ description = "Verify HashSet basic behavior" [package.metadata.kani.unstable] stubbing = true + +# Temporarily ignore the handling of storage markers till +# https://github.com/model-checking/kani/issues/3099 is fixed +[package.metadata.kani] +flags = { ignore-locals-lifetime = true, enable-unstable = true } diff --git a/tests/perf/s2n-quic b/tests/perf/s2n-quic index 1a7faa87ebcf..2d5e891f3fdc 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit 1a7faa87ebcf2b84a068f21af3306a6f42eb8c74 +Subproject commit 2d5e891f3fdc8a88b2d457baceedea5751efaa0d diff --git a/tools/benchcomp/benchcomp/__init__.py b/tools/benchcomp/benchcomp/__init__.py index eedd6ebc1f32..a82e50aae412 100644 --- a/tools/benchcomp/benchcomp/__init__.py +++ b/tools/benchcomp/benchcomp/__init__.py @@ -62,7 +62,16 @@ class ConfigFile(collections.UserDict): anyof: - schema: type: {} -filter: {} +filters: + type: list + default: [] + schema: + type: dict + keysrules: + type: string + allowed: ["command_line"] + valuesrules: + type: string visualize: {} """) diff --git a/tools/benchcomp/benchcomp/cmd_args.py b/tools/benchcomp/benchcomp/cmd_args.py index d8b7e735824f..8dd85f71080b 100644 --- a/tools/benchcomp/benchcomp/cmd_args.py +++ b/tools/benchcomp/benchcomp/cmd_args.py @@ -170,7 +170,13 @@ def _get_args_dict(): }, "filter": { "help": "transform a result by piping it through a program", - "args": [], + "args": [{ + "flags": ["--result-file"], + "metavar": "F", + "default": pathlib.Path("result.yaml"), + "type": pathlib.Path, + "help": "read result from F instead of %(default)s." + }], }, "visualize": { "help": "render a result in various formats", @@ -180,7 +186,7 @@ def _get_args_dict(): "default": pathlib.Path("result.yaml"), "type": pathlib.Path, "help": - "read result from F instead of %(default)s. " + "read result from F instead of %(default)s." }, { "flags": ["--only"], "nargs": "+", @@ -234,6 +240,11 @@ def get(): subparsers = ad["subparsers"].pop("parsers") subs = parser.add_subparsers(**ad["subparsers"]) + + # Add all subcommand-specific flags to the top-level argument parser, + # but only add them once. + flag_set = set() + for subcommand, info in subparsers.items(): args = info.pop("args") subparser = subs.add_parser(name=subcommand, **info) @@ -246,7 +257,9 @@ def get(): for arg in args: flags = arg.pop("flags") subparser.add_argument(*flags, **arg) - if arg not in global_args: + long_flag = flags[-1] + if arg not in global_args and long_flag not in flag_set: + flag_set.add(long_flag) parser.add_argument(*flags, **arg) return parser.parse_args() diff --git a/tools/benchcomp/benchcomp/entry/benchcomp.py b/tools/benchcomp/benchcomp/entry/benchcomp.py index e64f9699b3eb..9b8c1443c01d 100644 --- a/tools/benchcomp/benchcomp/entry/benchcomp.py +++ b/tools/benchcomp/benchcomp/entry/benchcomp.py @@ -16,4 +16,6 @@ def main(args): args.suites_dir = run_result.out_prefix / run_result.out_symlink results = benchcomp.entry.collate.main(args) + results = benchcomp.entry.filter.main(args) + benchcomp.entry.visualize.main(args) diff --git a/tools/benchcomp/benchcomp/entry/filter.py b/tools/benchcomp/benchcomp/entry/filter.py index 8523a198ce6a..c29a1795194b 100644 --- a/tools/benchcomp/benchcomp/entry/filter.py +++ b/tools/benchcomp/benchcomp/entry/filter.py @@ -4,5 +4,91 @@ # Entrypoint for `benchcomp filter` -def main(_): - raise NotImplementedError # TODO +import json +import logging +import pathlib +import subprocess +import sys +import tempfile + +import yaml + + +def main(args): + """Filter the results file by piping it into a list of scripts""" + + with open(args.result_file) as handle: + old_results = yaml.safe_load(handle) + + if "filters" not in args.config: + return old_results + + tmp_root = pathlib.Path(tempfile.gettempdir()) / "benchcomp" / "filter" + tmp_root.mkdir(parents=True, exist_ok=True) + tmpdir = pathlib.Path(tempfile.mkdtemp(dir=str(tmp_root))) + + for idx, filt in enumerate(args.config["filters"]): + with open(args.result_file) as handle: + old_results = yaml.safe_load(handle) + + json_results = json.dumps(old_results, indent=2) + in_file = tmpdir / f"{idx}.in.json" + out_file = tmpdir / f"{idx}.out.json" + cmd_out = _pipe( + filt["command_line"], json_results, in_file, out_file) + + try: + new_results = yaml.safe_load(cmd_out) + except yaml.YAMLError as exc: + logging.exception( + "Filter command '%s' produced invalid YAML. Stdin of" + " the command is saved in %s, stdout is saved in %s.", + filt["command_line"], in_file, out_file) + if hasattr(exc, "problem_mark"): + logging.error( + "Parse error location: line %d, column %d", + exc.problem_mark.line+1, exc.problem_mark.column+1) + sys.exit(1) + + with open(args.result_file, "w") as handle: + yaml.dump(new_results, handle, default_flow_style=False, indent=2) + + return new_results + + +def _pipe(shell_command, in_text, in_file, out_file): + """Pipe `in_text` into `shell_command` and return the output text + + Save the in and out text into files for later inspection if necessary. + """ + + with open(in_file, "w") as handle: + print(in_text, file=handle) + + logging.debug( + "Piping the contents of '%s' into '%s', saving into '%s'", + in_file, shell_command, out_file) + + timeout = 60 + with subprocess.Popen( + shell_command, shell=True, text=True, stdin=subprocess.PIPE, + stdout=subprocess.PIPE) as proc: + try: + out, _ = proc.communicate(input=in_text, timeout=timeout) + except subprocess.TimeoutExpired: + logging.error( + "Filter command failed to terminate after %ds: '%s'", + timeout, shell_command) + sys.exit(1) + + with open(out_file, "w") as handle: + print(out, file=handle) + + if proc.returncode: + logging.error( + "Filter command '%s' exited with code %d. Stdin of" + " the command is saved in %s, stdout is saved in %s.", + shell_command, proc.returncode, in_file, out_file) + sys.exit(1) + + return out diff --git a/tools/benchcomp/benchcomp/entry/run.py b/tools/benchcomp/benchcomp/entry/run.py index a870e7e9a1b0..28457381da2b 100644 --- a/tools/benchcomp/benchcomp/entry/run.py +++ b/tools/benchcomp/benchcomp/entry/run.py @@ -13,6 +13,7 @@ import logging import os import pathlib +import re import shutil import subprocess import typing @@ -53,9 +54,10 @@ def __post_init__(self): else: self.working_copy = pathlib.Path(self.directory) + def __call__(self): - env = dict(os.environ) - env.update(self.env) + update_environment_with = _EnvironmentUpdater() + env = update_environment_with(self.env) if self.copy_benchmarks_dir: shutil.copytree( @@ -128,6 +130,44 @@ def __call__(self): tmp_symlink.rename(self.out_symlink) + +@dataclasses.dataclass +class _EnvironmentUpdater: + """Update the OS environment with keys and values containing variables + + When called, this class returns the operating environment updated with new + keys and values. The values can contain variables of the form '${var_name}'. + The class evaluates those variables using values already in the environment. + """ + + os_environment: dict = dataclasses.field( + default_factory=lambda : dict(os.environ)) + pattern: re.Pattern = re.compile(r"\$\{(\w+?)\}") + + + def _evaluate(self, key, value): + """Evaluate all ${var} in value using self.os_environment""" + old_value = value + + for variable in re.findall(self.pattern, value): + if variable not in self.os_environment: + logging.error( + "Couldn't evaluate ${%s} in the value '%s' for environment " + "variable '%s'. Ensure the environment variable $%s is set", + variable, old_value, key, variable) + sys.exit(1) + value = re.sub( + r"\$\{" + variable + "\}", self.os_environment[variable], value) + return value + + + def __call__(self, new_environment): + ret = dict(self.os_environment) + for key, value in new_environment.items(): + ret[key] = self._evaluate(key, value) + return ret + + def get_default_out_symlink(): return "latest" diff --git a/tools/benchcomp/benchcomp/visualizers/__init__.py b/tools/benchcomp/benchcomp/visualizers/__init__.py index f9987832ad62..8de4a9f130b6 100644 --- a/tools/benchcomp/benchcomp/visualizers/__init__.py +++ b/tools/benchcomp/benchcomp/visualizers/__init__.py @@ -3,8 +3,10 @@ import dataclasses +import enum import json import logging +import math import subprocess import sys import textwrap @@ -125,11 +127,21 @@ def __call__(self, results): +class Plot(enum.Enum): + """Scatterplot configuration options + """ + OFF = 1 + LINEAR = 2 + LOG = 3 + + + class dump_markdown_results_table: """Print Markdown-formatted tables displaying benchmark results For each metric, this visualization prints out a table of benchmarks, - showing the value of the metric for each variant. + showing the value of the metric for each variant, combined with an optional + scatterplot. The 'out_file' key is mandatory; specify '-' to print to stdout. @@ -145,12 +157,16 @@ class dump_markdown_results_table: particular combinations of values for different variants, such as regressions or performance improvements. + 'scatterplot' takes the values 'off' (default), 'linear' (linearly scaled + axes), or 'log' (logarithmically scaled axes). + Sample configuration: ``` visualize: - type: dump_markdown_results_table out_file: "-" + scatterplot: linear extra_columns: runtime: - column_name: ratio @@ -187,9 +203,10 @@ class dump_markdown_results_table: """ - def __init__(self, out_file, extra_columns=None): + def __init__(self, out_file, extra_columns=None, scatterplot=None): self.get_out_file = benchcomp.Outfile(out_file) self.extra_columns = self._eval_column_text(extra_columns or {}) + self.scatterplot = self._parse_scatterplot_config(scatterplot) @staticmethod @@ -206,12 +223,48 @@ def _eval_column_text(column_spec): return column_spec + @staticmethod + def _parse_scatterplot_config(scatterplot_config_string): + if (scatterplot_config_string is None or + scatterplot_config_string == "off"): + return Plot.OFF + elif scatterplot_config_string == "linear": + return Plot.LINEAR + elif scatterplot_config_string == "log": + return Plot.LOG + else: + logging.error( + "Invalid scatterplot configuration '%s'", + scatterplot_config_string) + sys.exit(1) + + @staticmethod def _get_template(): return textwrap.dedent("""\ {% for metric, benchmarks in d["metrics"].items() %} ## {{ metric }} + {% if scatterplot and metric in d["scaled_metrics"] and d["scaled_variants"][metric]|length == 2 -%} + ```mermaid + %%{init: { "quadrantChart": { "titlePadding": 15, "xAxisLabelPadding": 20, "yAxisLabelPadding": 20, "quadrantLabelFontSize": 0, "pointRadius": 2, "pointLabelFontSize": 2 }, "themeVariables": { "quadrant1Fill": "#FFFFFF", "quadrant2Fill": "#FFFFFF", "quadrant3Fill": "#FFFFFF", "quadrant4Fill": "#FFFFFF", "quadrant1TextFill": "#FFFFFF", "quadrant2TextFill": "#FFFFFF", "quadrant3TextFill": "#FFFFFF", "quadrant4TextFill": "#FFFFFF", "quadrantInternalBorderStrokeFill": "#FFFFFF" } } }%% + quadrantChart + title {{ metric }} + x-axis "{{ d["scaled_variants"][metric][0] }}" + y-axis "{{ d["scaled_variants"][metric][1] }}" + quadrant-1 1 + quadrant-2 2 + quadrant-3 3 + quadrant-4 4 + {%- for bench_name, bench_variants in d["scaled_metrics"][metric]["benchmarks"].items () %} + {% set v0 = bench_variants[d["scaled_variants"][metric][0]] -%} + {% set v1 = bench_variants[d["scaled_variants"][metric][1]] -%} + "{{ bench_name }}": [{{ v0|round(3) }}, {{ v1|round(3) }}] + {%- endfor %} + ``` + Scatterplot axis ranges are {{ d["scaled_metrics"][metric]["min_value"] }} (bottom/left) to {{ d["scaled_metrics"][metric]["max_value"] }} (top/right). + + {% endif -%} | Benchmark | {% for variant in d["variants"][metric] %} {{ variant }} |{% endfor %} | --- |{% for variant in d["variants"][metric] %} --- |{% endfor -%} {% for bench_name, bench_variants in benchmarks.items () %} @@ -228,7 +281,48 @@ def _get_variant_names(results): @staticmethod - def _organize_results_into_metrics(results): + def _compute_scaled_metric(data_for_metric, log_scaling): + min_value = math.inf + max_value = -math.inf + for bench, bench_result in data_for_metric.items(): + for variant, variant_result in bench_result.items(): + if isinstance(variant_result, (bool, str)): + return None + if not isinstance(variant_result, (int, float)): + return None + if variant_result < min_value: + min_value = variant_result + if variant_result > max_value: + max_value = variant_result + ret = { + "benchmarks": {bench: {} for bench in data_for_metric.keys()}, + "min_value": "log({})".format(min_value) if log_scaling else min_value, + "max_value": "log({})".format(max_value) if log_scaling else max_value, + } + # 1.0 is not a permissible value for mermaid, so make sure all scaled + # results stay below that by use 0.99 as hard-coded value or + # artificially increasing the range by 10 per cent + if min_value == math.inf or min_value == max_value: + for bench, bench_result in data_for_metric.items(): + ret["benchmarks"][bench] = {variant: 0.99 for variant in bench_result.keys()} + else: + if log_scaling: + min_value = math.log(min_value, 10) + max_value = math.log(max_value, 10) + value_range = max_value - min_value + value_range = value_range * 1.1 + for bench, bench_result in data_for_metric.items(): + for variant, variant_result in bench_result.items(): + if log_scaling: + abs_value = math.log(variant_result, 10) + else: + abs_value = variant_result + ret["benchmarks"][bench][variant] = (abs_value - min_value) / value_range + return ret + + + @staticmethod + def _organize_results_into_metrics(results, log_scaling): ret = {metric: {} for metric in results["metrics"]} for bench, bench_result in results["benchmarks"].items(): for variant, variant_result in bench_result["variants"].items(): @@ -246,7 +340,13 @@ def _organize_results_into_metrics(results): ret[metric][bench] = { variant: variant_result["metrics"][metric] } - return ret + ret_scaled = {} + for metric, bench_result in ret.items(): + scaled = dump_markdown_results_table._compute_scaled_metric( + bench_result, log_scaling) + if scaled is not None: + ret_scaled[metric] = scaled + return (ret, ret_scaled) def _add_extra_columns(self, metrics): @@ -271,13 +371,26 @@ def _get_variants(metrics): return ret + @staticmethod + def _get_scaled_variants(metrics): + ret = {} + for metric, entries in metrics.items(): + for bench, variants in entries["benchmarks"].items(): + ret[metric] = list(variants.keys()) + break + return ret + + def __call__(self, results): - metrics = self._organize_results_into_metrics(results) + (metrics, scaled) = self._organize_results_into_metrics( + results, self.scatterplot == Plot.LOG) self._add_extra_columns(metrics) data = { "metrics": metrics, "variants": self._get_variants(metrics), + "scaled_metrics": scaled, + "scaled_variants": self._get_scaled_variants(scaled), } env = jinja2.Environment( @@ -285,6 +398,7 @@ def __call__(self, results): enabled_extensions=("html"), default_for_string=True)) template = env.from_string(self._get_template()) - output = template.render(d=data)[:-1] + include_scatterplot = self.scatterplot != Plot.OFF + output = template.render(d=data, scatterplot=include_scatterplot)[:-1] with self.get_out_file() as handle: print(output, file=handle) diff --git a/tools/benchcomp/configs/perf-regression.yaml b/tools/benchcomp/configs/perf-regression.yaml index a0d88e0558db..c938b3dd861f 100644 --- a/tools/benchcomp/configs/perf-regression.yaml +++ b/tools/benchcomp/configs/perf-regression.yaml @@ -33,6 +33,7 @@ visualize: - type: dump_markdown_results_table out_file: '-' + scatterplot: linear extra_columns: # For these two metrics, display the difference between old and new and diff --git a/tools/benchcomp/test/test_regression.py b/tools/benchcomp/test/test_regression.py index 87df67a071cc..ccf2259f7f0b 100644 --- a/tools/benchcomp/test/test_regression.py +++ b/tools/benchcomp/test/test_regression.py @@ -436,6 +436,7 @@ def test_markdown_results_table(self): "visualize": [{ "type": "dump_markdown_results_table", "out_file": "-", + "scatterplot": "linear", "extra_columns": { "runtime": [{ "column_name": "ratio", @@ -461,6 +462,21 @@ def test_markdown_results_table(self): run_bc.stdout, textwrap.dedent(""" ## runtime + ```mermaid + %%{init: { "quadrantChart": { "titlePadding": 15, "xAxisLabelPadding": 20, "yAxisLabelPadding": 20, "quadrantLabelFontSize": 0, "pointRadius": 2, "pointLabelFontSize": 2 }, "themeVariables": { "quadrant1Fill": "#FFFFFF", "quadrant2Fill": "#FFFFFF", "quadrant3Fill": "#FFFFFF", "quadrant4Fill": "#FFFFFF", "quadrant1TextFill": "#FFFFFF", "quadrant2TextFill": "#FFFFFF", "quadrant3TextFill": "#FFFFFF", "quadrant4TextFill": "#FFFFFF", "quadrantInternalBorderStrokeFill": "#FFFFFF" } } }%% + quadrantChart + title runtime + x-axis "variant_1" + y-axis "variant_2" + quadrant-1 1 + quadrant-2 2 + quadrant-3 3 + quadrant-4 4 + "bench_1": [0.0, 0.909] + "bench_2": [0.909, 0.0] + ``` + Scatterplot axis ranges are 5 (bottom/left) to 10 (top/right). + | Benchmark | variant_1 | variant_2 | ratio | | --- | --- | --- | --- | | bench_1 | 5 | 10 | **2.0** | @@ -646,6 +662,135 @@ def test_return_0_on_fail(self): result = yaml.safe_load(handle) + def test_bad_filters(self): + """Ensure that bad filters terminate benchcomp""" + + with tempfile.TemporaryDirectory() as tmp: + run_bc = Benchcomp({ + "variants": { + "variant-1": { + "config": { + "command_line": "true", + "directory": tmp, + "env": {}, + } + }, + }, + "run": { + "suites": { + "suite_1": { + "parser": { + "command": textwrap.dedent("""\ + echo '{ + "benchmarks": { }, + "metrics": { } + }' + """) + }, + "variants": ["variant-1"] + } + } + }, + "filters": [{ + "command_line": "false" + }], + "visualize": [], + }) + run_bc() + self.assertEqual(run_bc.proc.returncode, 1, msg=run_bc.stderr) + + + def test_two_filters(self): + """Ensure that the output can be filtered""" + + with tempfile.TemporaryDirectory() as tmp: + run_bc = Benchcomp({ + "variants": { + "variant-1": { + "config": { + "command_line": "true", + "directory": tmp, + "env": {}, + } + }, + }, + "run": { + "suites": { + "suite_1": { + "parser": { + "command": textwrap.dedent("""\ + echo '{ + "benchmarks": { + "bench-1": { + "variants": { + "variant-1": { + "metrics": { + "runtime": 10, + "memory": 5 + } + } + } + } + }, + "metrics": { + "runtime": {}, + "memory": {}, + } + }' + """) + }, + "variants": ["variant-1"] + } + } + }, + "filters": [{ + "command_line": "sed -e 's/10/20/;s/5/10/'" + }, { + "command_line": """grep '"runtime": 20'""" + }], + "visualize": [], + }) + run_bc() + self.assertEqual(run_bc.proc.returncode, 0, msg=run_bc.stderr) + + + def test_env_expansion(self): + """Ensure that config parser expands '${}' in env key""" + + with tempfile.TemporaryDirectory() as tmp: + run_bc = Benchcomp({ + "variants": { + "env_set": { + "config": { + "command_line": 'echo "$__BENCHCOMP_ENV_VAR" > out', + "directory": tmp, + "env": {"__BENCHCOMP_ENV_VAR": "foo:${PATH}"} + } + }, + }, + "run": { + "suites": { + "suite_1": { + "parser": { + # The word 'bin' typically appears in $PATH, so + # check that what was echoed contains 'bin'. + "command": textwrap.dedent("""\ + grep bin out && grep '^foo:' out && echo '{ + "benchmarks": {}, + "metrics": {} + }' + """) + }, + "variants": ["env_set"] + } + } + }, + "visualize": [], + }) + run_bc() + self.assertEqual(run_bc.proc.returncode, 0, msg=run_bc.stderr) + + def test_env(self): """Ensure that benchcomp reads the 'env' key of variant config""" diff --git a/tools/benchcomp/test/test_unit.py b/tools/benchcomp/test/test_unit.py new file mode 100644 index 000000000000..12320116f217 --- /dev/null +++ b/tools/benchcomp/test/test_unit.py @@ -0,0 +1,66 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# Benchcomp regression testing suite. This suite uses Python's stdlib unittest +# module, but nevertheless actually runs the binary rather than running unit +# tests. + +import unittest +import uuid + +import benchcomp.entry.run + + + +class TestEnvironmentUpdater(unittest.TestCase): + def test_environment_construction(self): + """Test that the default constructor reads the OS environment""" + + update_environment = benchcomp.entry.run._EnvironmentUpdater() + environment = update_environment({}) + self.assertIn("PATH", environment) + + + def test_placeholder_construction(self): + """Test that the placeholder constructor reads the placeholder""" + + key, value = [str(uuid.uuid4()) for _ in range(2)] + update_environment = benchcomp.entry.run._EnvironmentUpdater({ + key: value, + }) + environment = update_environment({}) + self.assertIn(key, environment) + self.assertEqual(environment[key], value) + + + def test_environment_update(self): + """Test that the environment is updated""" + + key, value, update = [str(uuid.uuid4()) for _ in range(3)] + update_environment = benchcomp.entry.run._EnvironmentUpdater({ + key: value, + }) + environment = update_environment({ + key: update + }) + self.assertIn(key, environment) + self.assertEqual(environment[key], update) + + + def test_environment_update_variable(self): + """Test that the environment is updated""" + + old_env = { + "key1": str(uuid.uuid4()), + "key2": str(uuid.uuid4()), + } + + actual_update = "${key2}xxx${key1}" + expected_update = f"{old_env['key2']}xxx{old_env['key1']}" + + update_environment = benchcomp.entry.run._EnvironmentUpdater(old_env) + environment = update_environment({ + "key1": actual_update, + }) + self.assertIn("key1", environment) + self.assertEqual(environment["key1"], expected_update) diff --git a/tools/bookrunner/Cargo.toml b/tools/bookrunner/Cargo.toml deleted file mode 100644 index 74befa4ca5b7..000000000000 --- a/tools/bookrunner/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright Kani Contributors -# SPDX-License-Identifier: Apache-2.0 OR MIT - -[package] -name = "bookrunner" -version = "0.1.0" -edition = "2018" -license = "MIT OR Apache-2.0" -publish = false - -[dependencies] -Inflector = "0.11.4" -pulldown-cmark = { version = "0.10", default-features = false } -pulldown-cmark-escape = { version = "0.10", default-features = false } -rustdoc = { path = "librustdoc" } -walkdir = "2.3.2" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -toml = "0.8" - -[package.metadata.rust-analyzer] -# This package uses rustc crates. -rustc_private=true diff --git a/tools/bookrunner/README.md b/tools/bookrunner/README.md deleted file mode 100644 index 560f2a73c3e4..000000000000 --- a/tools/bookrunner/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Book Runner - -This tool extracts examples from different Rust books, runs Kani on them, and -displays the results in a report. - -Run the following command to build this tool and generate the report: -```bash -./x.py run -i --stage 1 bookrunner -``` diff --git a/tools/bookrunner/configs/books/Rust by Example/Custom Types/Enums/Testcase: linked-list/5.props b/tools/bookrunner/configs/books/Rust by Example/Custom Types/Enums/Testcase: linked-list/5.props deleted file mode 100644 index 1fb5bcc55586..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Custom Types/Enums/Testcase: linked-list/5.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 3 diff --git a/tools/bookrunner/configs/books/Rust by Example/Error handling/Iterating over Results/22.props b/tools/bookrunner/configs/books/Rust by Example/Error handling/Iterating over Results/22.props deleted file mode 100644 index 55a4254aa3a2..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Error handling/Iterating over Results/22.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 4 --object-bits 9 diff --git a/tools/bookrunner/configs/books/Rust by Example/Error handling/Iterating over Results/39.props b/tools/bookrunner/configs/books/Rust by Example/Error handling/Iterating over Results/39.props deleted file mode 100644 index 55a4254aa3a2..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Error handling/Iterating over Results/39.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 4 --object-bits 9 diff --git a/tools/bookrunner/configs/books/Rust by Example/Error handling/Iterating over Results/5.props b/tools/bookrunner/configs/books/Rust by Example/Error handling/Iterating over Results/5.props deleted file mode 100644 index a12b6d6b639f..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Error handling/Iterating over Results/5.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 4 diff --git a/tools/bookrunner/configs/books/Rust by Example/Error handling/Iterating over Results/54.props b/tools/bookrunner/configs/books/Rust by Example/Error handling/Iterating over Results/54.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Error handling/Iterating over Results/54.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/Rust by Example/Error handling/Iterating over Results/69.props b/tools/bookrunner/configs/books/Rust by Example/Error handling/Iterating over Results/69.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Error handling/Iterating over Results/69.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/Rust by Example/Flow of Control/for and range/102.props b/tools/bookrunner/configs/books/Rust by Example/Flow of Control/for and range/102.props deleted file mode 100644 index 2f4921c863e9..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Flow of Control/for and range/102.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 7 diff --git a/tools/bookrunner/configs/books/Rust by Example/Flow of Control/for and range/63.props b/tools/bookrunner/configs/books/Rust by Example/Flow of Control/for and range/63.props deleted file mode 100644 index 2f4921c863e9..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Flow of Control/for and range/63.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 7 diff --git a/tools/bookrunner/configs/books/Rust by Example/Functions/Closures/Capturing/89.props b/tools/bookrunner/configs/books/Rust by Example/Functions/Closures/Capturing/89.props deleted file mode 100644 index a12b6d6b639f..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Functions/Closures/Capturing/89.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 4 diff --git a/tools/bookrunner/configs/books/Rust by Example/Functions/Closures/Examples in std/Iterator::any/22.props b/tools/bookrunner/configs/books/Rust by Example/Functions/Closures/Examples in std/Iterator::any/22.props deleted file mode 100644 index a12b6d6b639f..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Functions/Closures/Examples in std/Iterator::any/22.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 4 diff --git a/tools/bookrunner/configs/books/Rust by Example/Functions/Closures/Examples in std/Searching through iterators/22.props b/tools/bookrunner/configs/books/Rust by Example/Functions/Closures/Examples in std/Searching through iterators/22.props deleted file mode 100644 index a12b6d6b639f..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Functions/Closures/Examples in std/Searching through iterators/22.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 4 diff --git a/tools/bookrunner/configs/books/Rust by Example/Functions/Closures/Examples in std/Searching through iterators/52.props b/tools/bookrunner/configs/books/Rust by Example/Functions/Closures/Examples in std/Searching through iterators/52.props deleted file mode 100644 index 2f4921c863e9..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Functions/Closures/Examples in std/Searching through iterators/52.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 7 diff --git a/tools/bookrunner/configs/books/Rust by Example/Hello World/Formatted print/Formatting/17.props b/tools/bookrunner/configs/books/Rust by Example/Hello World/Formatted print/Formatting/17.props deleted file mode 100644 index a12b6d6b639f..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Hello World/Formatted print/Formatting/17.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 4 diff --git a/tools/bookrunner/configs/books/Rust by Example/Std library types/HashMap/14.props b/tools/bookrunner/configs/books/Rust by Example/Std library types/HashMap/14.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Std library types/HashMap/14.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/Rust by Example/Std library types/HashMap/Alternate_custom key types/29.props b/tools/bookrunner/configs/books/Rust by Example/Std library types/HashMap/Alternate_custom key types/29.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Std library types/HashMap/Alternate_custom key types/29.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/Rust by Example/Std library types/Strings/12.props b/tools/bookrunner/configs/books/Rust by Example/Std library types/Strings/12.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Std library types/Strings/12.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/Rust by Example/Std misc/Channels/7.props b/tools/bookrunner/configs/books/Rust by Example/Std misc/Channels/7.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Std misc/Channels/7.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/Rust by Example/Std misc/File I_O/read lines/9.props b/tools/bookrunner/configs/books/Rust by Example/Std misc/File I_O/read lines/9.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Std misc/File I_O/read lines/9.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/Rust by Example/Std misc/Program arguments/8.props b/tools/bookrunner/configs/books/Rust by Example/Std misc/Program arguments/8.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Std misc/Program arguments/8.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/Rust by Example/Std misc/Program arguments/Argument parsing/5.props b/tools/bookrunner/configs/books/Rust by Example/Std misc/Program arguments/Argument parsing/5.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Std misc/Program arguments/Argument parsing/5.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/Rust by Example/Std misc/Threads/6.props b/tools/bookrunner/configs/books/Rust by Example/Std misc/Threads/6.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Std misc/Threads/6.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/Rust by Example/Std misc/Threads/Testcase: map-reduce/24.props b/tools/bookrunner/configs/books/Rust by Example/Std misc/Threads/Testcase: map-reduce/24.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Std misc/Threads/Testcase: map-reduce/24.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/Rust by Example/Traits/Disambiguating overlapping traits/11.props b/tools/bookrunner/configs/books/Rust by Example/Traits/Disambiguating overlapping traits/11.props deleted file mode 100644 index c8d448e9c718..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Traits/Disambiguating overlapping traits/11.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 10 diff --git a/tools/bookrunner/configs/books/Rust by Example/Traits/Iterators/12.props b/tools/bookrunner/configs/books/Rust by Example/Traits/Iterators/12.props deleted file mode 100644 index d55948277f51..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Traits/Iterators/12.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 5 diff --git a/tools/bookrunner/configs/books/Rust by Example/Traits/impl Trait/115.props b/tools/bookrunner/configs/books/Rust by Example/Traits/impl Trait/115.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/Rust by Example/Traits/impl Trait/115.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/The Rust Reference/Appendices/Glossary/263.diff b/tools/bookrunner/configs/books/The Rust Reference/Appendices/Glossary/263.diff deleted file mode 100644 index f8eff2a40536..000000000000 --- a/tools/bookrunner/configs/books/The Rust Reference/Appendices/Glossary/263.diff +++ /dev/null @@ -1,4 +0,0 @@ -- 1 -+ 1 let ok_num = Ok::<_, ()>(5); -+ 2 assert!(!ok_num.is_err()); -+ 4 assert!([2, 4, 6][..] == vec[..]); diff --git a/tools/bookrunner/configs/books/The Rust Reference/Appendices/Glossary/263.props b/tools/bookrunner/configs/books/The Rust Reference/Appendices/Glossary/263.props deleted file mode 100644 index a12b6d6b639f..000000000000 --- a/tools/bookrunner/configs/books/The Rust Reference/Appendices/Glossary/263.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 4 diff --git a/tools/bookrunner/configs/books/The Rust Reference/Attributes/Limits/45.props b/tools/bookrunner/configs/books/The Rust Reference/Attributes/Limits/45.props deleted file mode 100644 index 5fc51f2a8212..000000000000 --- a/tools/bookrunner/configs/books/The Rust Reference/Attributes/Limits/45.props +++ /dev/null @@ -1 +0,0 @@ -// kani-codegen-fail diff --git a/tools/bookrunner/configs/books/The Rust Reference/Linkage/190.props b/tools/bookrunner/configs/books/The Rust Reference/Linkage/190.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/The Rust Reference/Linkage/190.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/The Rust Reference/Patterns/367.props b/tools/bookrunner/configs/books/The Rust Reference/Patterns/367.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/The Rust Reference/Patterns/367.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/The Rust Reference/Statements and expressions/Expressions/Loop expressions/133.props b/tools/bookrunner/configs/books/The Rust Reference/Statements and expressions/Expressions/Loop expressions/133.props deleted file mode 100644 index a12b6d6b639f..000000000000 --- a/tools/bookrunner/configs/books/The Rust Reference/Statements and expressions/Expressions/Loop expressions/133.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 4 diff --git a/tools/bookrunner/configs/books/The Rust Reference/Statements and expressions/Expressions/Method call expressions/10.props b/tools/bookrunner/configs/books/The Rust Reference/Statements and expressions/Expressions/Method call expressions/10.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/The Rust Reference/Statements and expressions/Expressions/Method call expressions/10.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/The Rust Reference/Type system/Types/113.props b/tools/bookrunner/configs/books/The Rust Reference/Type system/Types/113.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/The Rust Reference/Type system/Types/113.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/The Rustonomicon/Concurrency/Atomics/198.props b/tools/bookrunner/configs/books/The Rustonomicon/Concurrency/Atomics/198.props deleted file mode 100644 index 41f1f747f90d..000000000000 --- a/tools/bookrunner/configs/books/The Rustonomicon/Concurrency/Atomics/198.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 0 diff --git a/tools/bookrunner/configs/books/The Unstable Book/Language Features/generators/165.props b/tools/bookrunner/configs/books/The Unstable Book/Language Features/generators/165.props deleted file mode 100644 index ef7a7cac2c14..000000000000 --- a/tools/bookrunner/configs/books/The Unstable Book/Language Features/generators/165.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 3 \ No newline at end of file diff --git a/tools/bookrunner/configs/books/The Unstable Book/Language Features/generators/28.props b/tools/bookrunner/configs/books/The Unstable Book/Language Features/generators/28.props deleted file mode 100644 index f69c62f168c6..000000000000 --- a/tools/bookrunner/configs/books/The Unstable Book/Language Features/generators/28.props +++ /dev/null @@ -1 +0,0 @@ -// kani-flags: --enable-unstable --cbmc-args --unwind 5 \ No newline at end of file diff --git a/tools/bookrunner/librustdoc/Cargo.toml b/tools/bookrunner/librustdoc/Cargo.toml deleted file mode 100644 index aaf3a7100c06..000000000000 --- a/tools/bookrunner/librustdoc/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 OR MIT -# -# Modifications Copyright Kani Contributors -# See GitHub history for details. -[package] -name = "rustdoc" -version = "0.0.0" -edition = "2021" -license = "MIT OR Apache-2.0" -publish = false - -# From upstream librustdoc: -# https://github.com/rust-lang/rust/tree/master/src/librustdoc -# Upstream crate does not list license but Rust statues: -# Rust is primarily distributed under the terms of both the MIT -# license and the Apache License (Version 2.0), with portions -# covered by various BSD-like licenses. - -[lib] -path = "lib.rs" - -[dependencies] -pulldown-cmark = { version = "0.10", default-features = false } - -[package.metadata.rust-analyzer] -rustc_private = true diff --git a/tools/bookrunner/librustdoc/README.md b/tools/bookrunner/librustdoc/README.md deleted file mode 100644 index 5a5f547068d6..000000000000 --- a/tools/bookrunner/librustdoc/README.md +++ /dev/null @@ -1,3 +0,0 @@ -For more information about how `librustdoc` works, see the [rustc dev guide]. - -[rustc dev guide]: https://rustc-dev-guide.rust-lang.org/rustdoc.html diff --git a/tools/bookrunner/librustdoc/build.rs b/tools/bookrunner/librustdoc/build.rs deleted file mode 100644 index bca0827fbc46..000000000000 --- a/tools/bookrunner/librustdoc/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -//! Build script that allows us to build this dependency without bootstrap script. -pub(crate) fn main() { - // Hard code nightly configuration to build librustdoc. - println!("cargo:rustc-env=DOC_RUST_LANG_ORG_CHANNEL=nightly"); -} diff --git a/tools/bookrunner/librustdoc/doctest.rs b/tools/bookrunner/librustdoc/doctest.rs deleted file mode 100644 index 8d4c8c5eda5c..000000000000 --- a/tools/bookrunner/librustdoc/doctest.rs +++ /dev/null @@ -1,322 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 OR MIT -// -// Modifications Copyright Kani Contributors -// See GitHub history for details. -use rustc_ast as ast; -use rustc_data_structures::sync::Lrc; -use rustc_driver::DEFAULT_LOCALE_RESOURCES; -use rustc_errors::ColorConfig; -use rustc_span::edition::Edition; -use rustc_span::source_map::SourceMap; -use rustc_span::symbol::sym; -use rustc_span::FileName; - -use std::io::{self}; -use std::str; - -use crate::html::markdown::LangString; - -/// Options that apply to all doctests in a crate or Markdown file (for `rustdoc foo.md`). -#[derive(Clone, Default)] -pub struct GlobalTestOptions { - /// Whether to disable the default `extern crate my_crate;` when creating doctests. - pub(crate) no_crate_inject: bool, - /// Additional crate-level attributes to add to doctests. - pub(crate) attrs: Vec, -} - -/// Transforms a test into code that can be compiled into a Rust binary, and returns the number of -/// lines before the test code begins as well as if the output stream supports colors or not. -pub fn make_test( - s: &str, - crate_name: Option<&str>, - dont_insert_main: bool, - opts: &GlobalTestOptions, - edition: Edition, - test_id: Option<&str>, -) -> (String, usize, bool) { - let (crate_attrs, everything_else, crates) = partition_source(s); - let everything_else = everything_else.trim(); - let mut line_offset = 0; - let mut prog = String::new(); - let mut supports_color = false; - - if opts.attrs.is_empty() { - // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some - // lints that are commonly triggered in doctests. The crate-level test attributes are - // commonly used to make tests fail in case they trigger warnings, so having this there in - // that case may cause some tests to pass when they shouldn't have. - prog.push_str("#![allow(unused)]\n"); - line_offset += 1; - } - - // Next, any attributes that came from the crate root via #![doc(test(attr(...)))]. - for attr in &opts.attrs { - prog.push_str(&format!("#![{attr}]\n")); - line_offset += 1; - } - - // Now push any outer attributes from the example, assuming they - // are intended to be crate attributes. - prog.push_str(&crate_attrs); - prog.push_str(&crates); - - // Uses librustc_ast to parse the doctest and find if there's a main fn and the extern - // crate already is included. - let result = rustc_driver::catch_fatal_errors(|| { - rustc_span::create_session_if_not_set_then(edition, |_| { - use rustc_errors::emitter::stderr_destination; - use rustc_errors::emitter::{Emitter, HumanEmitter}; - use rustc_errors::DiagCtxt; - use rustc_parse::maybe_new_parser_from_source_str; - use rustc_parse::parser::ForceCollect; - use rustc_session::parse::ParseSess; - use rustc_span::source_map::FilePathMapping; - - let filename = FileName::anon_source_code(s); - let source = crates + everything_else; - - // Any errors in parsing should also appear when the doctest is compiled for real, so just - // send all the errors that librustc_ast emits directly into a `Sink` instead of stderr. - let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); - - let fallback_bundle = - rustc_errors::fallback_fluent_bundle(DEFAULT_LOCALE_RESOURCES.to_vec(), false); - supports_color = - HumanEmitter::new(stderr_destination(ColorConfig::Auto), fallback_bundle.clone()) - .diagnostic_width(Some(80)) - .supports_color(); - - let emitter = HumanEmitter::new(Box::new(io::sink()), fallback_bundle); - // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser - let handler = DiagCtxt::new(Box::new(emitter)); - let sess = ParseSess::with_dcx(handler, sm); - - let mut found_main = false; - let mut found_extern_crate = crate_name.is_none(); - let mut found_macro = false; - - let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source) { - Ok(p) => p, - Err(_errs) => { - return (found_main, found_extern_crate, found_macro); - } - }; - - loop { - match parser.parse_item(ForceCollect::No) { - Ok(Some(item)) => { - if !found_main { - if let ast::ItemKind::Fn(..) = item.kind { - if item.ident.name == sym::main { - found_main = true; - } - } - } - - if !found_extern_crate { - if let ast::ItemKind::ExternCrate(original) = item.kind { - // This code will never be reached if `crate_name` is none because - // `found_extern_crate` is initialized to `true` if it is none. - let crate_name = crate_name.unwrap(); - - match original { - Some(name) => found_extern_crate = name.as_str() == crate_name, - None => found_extern_crate = item.ident.as_str() == crate_name, - } - } - } - - if !found_macro { - if let ast::ItemKind::MacCall(..) = item.kind { - found_macro = true; - } - } - - if found_main && found_extern_crate { - break; - } - } - Ok(None) => break, - Err(e) => { - e.cancel(); - break; - } - } - - // The supplied slice is only used for diagnostics, - // which are swallowed here anyway. - parser.maybe_consume_incorrect_semicolon(&[]); - } - - // Reset errors so that they won't be reported as compiler bugs when dropping the - // handler. Any errors in the tests will be reported when the test file is compiled, - // Note that we still need to cancel the errors above otherwise `DiagnosticBuilder` - // will panic on drop. - sess.dcx.reset_err_count(); - - (found_main, found_extern_crate, found_macro) - }) - }); - let (already_has_main, already_has_extern_crate, found_macro) = match result { - Ok(result) => result, - Err(_) => { - // If the parser panicked due to a fatal error, pass the test code through unchanged. - // The error will be reported during compilation. - return (s.to_owned(), 0, false); - } - }; - - // If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't - // see it. In that case, run the old text-based scan to see if they at least have a main - // function written inside a macro invocation. See - // https://github.com/rust-lang/rust/issues/56898 - let already_has_main = if found_macro && !already_has_main { - s.lines() - .map(|line| { - let comment = line.find("//"); - if let Some(comment_begins) = comment { &line[0..comment_begins] } else { line } - }) - .any(|code| code.contains("fn main")) - } else { - already_has_main - }; - - // Don't inject `extern crate std` because it's already injected by the - // compiler. - if !already_has_extern_crate && !opts.no_crate_inject && crate_name != Some("std") { - if let Some(crate_name) = crate_name { - // Don't inject `extern crate` if the crate is never used. - // NOTE: this is terribly inaccurate because it doesn't actually - // parse the source, but only has false positives, not false - // negatives. - if s.contains(crate_name) { - prog.push_str(&format!("extern crate r#{crate_name};\n")); - line_offset += 1; - } - } - } - - // FIXME: This code cannot yet handle no_std test cases yet - if dont_insert_main || already_has_main || prog.contains("![no_std]") { - prog.push_str(everything_else); - } else { - let returns_result = everything_else.trim_end().ends_with("(())"); - // Give each doctest main function a unique name. - // This is for example needed for the tooling around `-Z instrument-coverage`. - let inner_fn_name = if let Some(test_id) = test_id { - format!("_doctest_main_{test_id}") - } else { - "_inner".into() - }; - let inner_attr = if test_id.is_some() { "#[allow(non_snake_case)] " } else { "" }; - let (main_pre, main_post) = if returns_result { - ( - format!( - "fn main() {{ {inner_attr}fn {inner_fn_name}() -> Result<(), impl core::fmt::Debug> {{\n" - ), - format!("\n}} {inner_fn_name}().unwrap() }}"), - ) - } else if test_id.is_some() { - ( - format!("fn main() {{ {inner_attr}fn {inner_fn_name}() {{\n"), - format!("\n}} {inner_fn_name}() }}"), - ) - } else { - ("fn main() {\n".into(), "\n}".into()) - }; - // Note on newlines: We insert a line/newline *before*, and *after* - // the doctest and adjust the `line_offset` accordingly. - // In the case of `-Z instrument-coverage`, this means that the generated - // inner `main` function spans from the doctest opening codeblock to the - // closing one. For example - // /// ``` <- start of the inner main - // /// <- code under doctest - // /// ``` <- end of the inner main - line_offset += 1; - - prog.extend([&main_pre, everything_else, &main_post].iter().cloned()); - } - - debug!("final doctest:\n{}", prog); - - (prog, line_offset, supports_color) -} - -// FIXME(aburka): use a real parser to deal with multiline attributes -fn partition_source(s: &str) -> (String, String, String) { - #[derive(Copy, Clone, PartialEq)] - enum PartitionState { - Attrs, - Crates, - Other, - } - let mut state = PartitionState::Attrs; - let mut before = String::new(); - let mut crates = String::new(); - let mut after = String::new(); - - for line in s.lines() { - let trimline = line.trim(); - - // FIXME(misdreavus): if a doc comment is placed on an extern crate statement, it will be - // shunted into "everything else" - match state { - PartitionState::Attrs => { - state = if trimline.starts_with("#![") - || trimline.chars().all(|c| c.is_whitespace()) - || (trimline.starts_with("//") && !trimline.starts_with("///")) - { - PartitionState::Attrs - } else if trimline.starts_with("extern crate") - || trimline.starts_with("#[macro_use] extern crate") - { - PartitionState::Crates - } else { - PartitionState::Other - }; - } - PartitionState::Crates => { - state = if trimline.starts_with("extern crate") - || trimline.starts_with("#[macro_use] extern crate") - || trimline.chars().all(|c| c.is_whitespace()) - || (trimline.starts_with("//") && !trimline.starts_with("///")) - { - PartitionState::Crates - } else { - PartitionState::Other - }; - } - PartitionState::Other => {} - } - - match state { - PartitionState::Attrs => { - before.push_str(line); - before.push('\n'); - } - PartitionState::Crates => { - crates.push_str(line); - crates.push('\n'); - } - PartitionState::Other => { - after.push_str(line); - after.push('\n'); - } - } - } - - debug!("before:\n{}", before); - debug!("crates:\n{}", crates); - debug!("after:\n{}", after); - - (before, after, crates) -} - -pub trait Tester { - fn add_test(&mut self, test: String, config: LangString, line: usize); - fn get_line(&self) -> usize { - 0 - } - fn register_header(&mut self, _name: &str, _level: u32) {} -} diff --git a/tools/bookrunner/librustdoc/html/markdown.rs b/tools/bookrunner/librustdoc/html/markdown.rs deleted file mode 100644 index 857b42bcd76a..000000000000 --- a/tools/bookrunner/librustdoc/html/markdown.rs +++ /dev/null @@ -1,338 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 OR MIT -// -// Modifications Copyright Kani Contributors -// See GitHub history for details. -//! Markdown formatting for rustdoc. -//! - -use rustc_span::edition::Edition; - -use std::str; -use std::{borrow::Cow, marker::PhantomData}; - -use crate::doctest; - -use pulldown_cmark::{CodeBlockKind, Event, Parser, Tag}; - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub /* via find_testable_code */ enum ErrorCodes { - Yes, - No, -} - -impl ErrorCodes { - pub(crate) fn as_bool(self) -> bool { - match self { - ErrorCodes::Yes => true, - ErrorCodes::No => false, - } - } -} - -/// Controls whether a line will be hidden or shown in HTML output. -/// -/// All lines are used in documentation tests. -enum Line<'a> { - Hidden(&'a str), - Shown(Cow<'a, str>), -} - -impl<'a> Line<'a> { - fn for_code(self) -> Cow<'a, str> { - match self { - Line::Shown(l) => l, - Line::Hidden(l) => Cow::Borrowed(l), - } - } -} - -// FIXME: There is a minor inconsistency here. For lines that start with ##, we -// have no easy way of removing a potential single space after the hashes, which -// is done in the single # case. This inconsistency seems okay, if non-ideal. In -// order to fix it we'd have to iterate to find the first non-# character, and -// then reallocate to remove it; which would make us return a String. -fn map_line(s: &str) -> Line<'_> { - let trimmed = s.trim(); - if trimmed.starts_with("##") { - Line::Shown(Cow::Owned(s.replacen("##", "#", 1))) - } else if let Some(stripped) = trimmed.strip_prefix("# ") { - // # text - Line::Hidden(stripped) - } else if trimmed == "#" { - // We cannot handle '#text' because it could be #[attr]. - Line::Hidden("") - } else { - Line::Shown(Cow::Borrowed(s)) - } -} - -pub fn find_testable_code( - doc: &str, - tests: &mut T, - error_codes: ErrorCodes, - enable_per_target_ignores: bool, - extra_info: Option<&ExtraInfo<'_>>, -) { - let mut parser = Parser::new(doc).into_offset_iter(); - let mut prev_offset = 0; - let mut nb_lines = 0; - let mut register_header = None; - while let Some((event, offset)) = parser.next() { - match event { - Event::Start(Tag::CodeBlock(kind)) => { - let block_info = match kind { - CodeBlockKind::Fenced(ref lang) => { - if lang.is_empty() { - Default::default() - } else { - LangString::parse( - lang, - error_codes, - enable_per_target_ignores, - extra_info, - ) - } - } - CodeBlockKind::Indented => Default::default(), - }; - if !block_info.rust { - continue; - } - - let mut test_s = String::new(); - - while let Some((Event::Text(s), _)) = parser.next() { - test_s.push_str(&s); - } - let text = test_s - .lines() - .map(|l| map_line(l).for_code()) - .collect::>>() - .join("\n"); - - nb_lines += doc[prev_offset..offset.start].lines().count(); - // If there are characters between the preceding line ending and - // this code block, `str::lines` will return an additional line, - // which we subtract here. - if nb_lines != 0 && !&doc[prev_offset..offset.start].ends_with('\n') { - nb_lines -= 1; - } - let line = tests.get_line() + nb_lines + 1; - tests.add_test(text, block_info, line); - prev_offset = offset.start; - } - Event::Start(Tag::Heading { level, .. }) => { - register_header = Some(level as u32); - } - Event::Text(ref s) if register_header.is_some() => { - let level = register_header.unwrap(); - if s.is_empty() { - tests.register_header("", level); - } else { - tests.register_header(s, level); - } - register_header = None; - } - _ => {} - } - } -} - -// We never pass an actual ExtraInfo, only None for Option -pub struct ExtraInfo<'tcx> { - _unused: PhantomData<&'tcx ()>, -} - -impl<'tcx> ExtraInfo<'tcx> { - fn error_invalid_codeblock_attr(&self, msg: &str, _help: &str) { - unreachable!("{}", msg); - } -} - -#[derive(Eq, PartialEq, Clone, Debug)] -pub struct LangString { - original: String, - pub should_panic: bool, - pub(crate) no_run: bool, - pub ignore: Ignore, - pub(crate) rust: bool, - pub(crate) test_harness: bool, - pub compile_fail: bool, - pub(crate) error_codes: Vec, - pub(crate) allow_fail: bool, - pub edition: Option, -} - -#[derive(Eq, PartialEq, Clone, Debug)] -pub enum Ignore { - All, - None, - Some(Vec), -} - -impl Default for LangString { - fn default() -> Self { - Self { - original: String::new(), - should_panic: false, - no_run: false, - ignore: Ignore::None, - rust: true, - test_harness: false, - compile_fail: false, - error_codes: Vec::new(), - allow_fail: false, - edition: None, - } - } -} - -impl LangString { - fn tokens(string: &str) -> impl Iterator { - // Pandoc, which Rust once used for generating documentation, - // expects lang strings to be surrounded by `{}` and for each token - // to be proceeded by a `.`. Since some of these lang strings are still - // loose in the wild, we strip a pair of surrounding `{}` from the lang - // string and a leading `.` from each token. - - let string = string.trim(); - - let first = string.chars().next(); - let last = string.chars().last(); - - let string = if first == Some('{') && last == Some('}') { - &string[1..string.len() - 1] - } else { - string - }; - - string - .split(|c| c == ',' || c == ' ' || c == '\t') - .map(str::trim) - .map(|token| token.strip_prefix('.').unwrap_or(token)) - .filter(|token| !token.is_empty()) - } - - fn parse( - string: &str, - allow_error_code_check: ErrorCodes, - enable_per_target_ignores: bool, - extra: Option<&ExtraInfo<'_>>, - ) -> LangString { - let allow_error_code_check = allow_error_code_check.as_bool(); - let mut seen_rust_tags = false; - let mut seen_other_tags = false; - let mut data = LangString::default(); - let mut ignores = vec![]; - - string.clone_into(&mut data.original); - - for token in Self::tokens(string) { - match token { - "should_panic" => { - data.should_panic = true; - seen_rust_tags = !seen_other_tags; - } - "no_run" => { - data.no_run = true; - seen_rust_tags = !seen_other_tags; - } - "ignore" => { - data.ignore = Ignore::All; - seen_rust_tags = !seen_other_tags; - } - x if x.starts_with("ignore-") => { - if enable_per_target_ignores { - ignores.push(x.trim_start_matches("ignore-").to_owned()); - seen_rust_tags = !seen_other_tags; - } - } - "allow_fail" => { - data.allow_fail = true; - seen_rust_tags = !seen_other_tags; - } - "rust" => { - data.rust = true; - seen_rust_tags = true; - } - "test_harness" => { - data.test_harness = true; - seen_rust_tags = !seen_other_tags || seen_rust_tags; - } - "compile_fail" => { - data.compile_fail = true; - seen_rust_tags = !seen_other_tags || seen_rust_tags; - data.no_run = true; - } - x if x.starts_with("edition") => { - data.edition = x[7..].parse::().ok(); - } - x if allow_error_code_check && x.starts_with('E') && x.len() == 5 => { - if x[1..].parse::().is_ok() { - data.error_codes.push(x.to_owned()); - seen_rust_tags = !seen_other_tags || seen_rust_tags; - } else { - seen_other_tags = true; - } - } - x if extra.is_some() => { - let s = x.to_lowercase(); - if let Some((flag, help)) = if s == "compile-fail" - || s == "compile_fail" - || s == "compilefail" - { - Some(( - "compile_fail", - "the code block will either not be tested if not marked as a rust one \ - or won't fail if it compiles successfully", - )) - } else if s == "should-panic" || s == "should_panic" || s == "shouldpanic" { - Some(( - "should_panic", - "the code block will either not be tested if not marked as a rust one \ - or won't fail if it doesn't panic when running", - )) - } else if s == "no-run" || s == "no_run" || s == "norun" { - Some(( - "no_run", - "the code block will either not be tested if not marked as a rust one \ - or will be run (which you might not want)", - )) - } else if s == "allow-fail" || s == "allow_fail" || s == "allowfail" { - Some(( - "allow_fail", - "the code block will either not be tested if not marked as a rust one \ - or will be run (which you might not want)", - )) - } else if s == "test-harness" || s == "test_harness" || s == "testharness" { - Some(( - "test_harness", - "the code block will either not be tested if not marked as a rust one \ - or the code will be wrapped inside a main function", - )) - } else { - None - } { - if let Some(extra) = extra { - extra.error_invalid_codeblock_attr( - &format!("unknown attribute `{x}`. Did you mean `{flag}`?"), - help, - ); - } - } - seen_other_tags = true; - } - _ => seen_other_tags = true, - } - } - - // ignore-foo overrides ignore - if !ignores.is_empty() { - data.ignore = Ignore::Some(ignores); - } - - data.rust &= !seen_other_tags || seen_rust_tags; - - data - } -} diff --git a/tools/bookrunner/librustdoc/html/mod.rs b/tools/bookrunner/librustdoc/html/mod.rs deleted file mode 100644 index bb4c77e39854..000000000000 --- a/tools/bookrunner/librustdoc/html/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 OR MIT -// -// Modifications Copyright Kani Contributors -// See GitHub history for details. -// used by the error-index generator, so it needs to be public -pub mod markdown; diff --git a/tools/bookrunner/librustdoc/lib.rs b/tools/bookrunner/librustdoc/lib.rs deleted file mode 100644 index cddc369b4dc1..000000000000 --- a/tools/bookrunner/librustdoc/lib.rs +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 OR MIT -// -// Modifications Copyright Kani Contributors -// See GitHub history for details. -#![doc( - html_root_url = "https://doc.rust-lang.org/nightly/", - html_playground_url = "https://play.rust-lang.org/" -)] -#![feature(rustc_private)] -#![feature(assert_matches)] -#![feature(box_patterns)] -#![feature(control_flow_enum)] -#![feature(test)] -#![feature(never_type)] -#![feature(type_ascription)] -#![feature(iter_intersperse)] -#![recursion_limit = "256"] -#![warn(rustc::internal)] -#![allow(clippy::collapsible_if, clippy::collapsible_else_if, clippy::arc_with_non_send_sync)] - -#[macro_use] -extern crate tracing; - -// N.B. these need `extern crate` even in 2018 edition -// because they're loaded implicitly from the sysroot. -// The reason they're loaded from the sysroot is because -// the rustdoc artifacts aren't stored in rustc's cargo target directory. -// So if `rustc` was specified in Cargo.toml, this would spuriously rebuild crates. -// -// Dependencies listed in Cargo.toml do not need `extern crate`. - -extern crate rustc_ast; -extern crate rustc_ast_lowering; -extern crate rustc_ast_pretty; -extern crate rustc_attr; -extern crate rustc_const_eval; -extern crate rustc_data_structures; -extern crate rustc_driver; -extern crate rustc_errors; -extern crate rustc_expand; -extern crate rustc_feature; -extern crate rustc_hir; -extern crate rustc_hir_pretty; -extern crate rustc_index; -extern crate rustc_infer; -extern crate rustc_interface; -extern crate rustc_lexer; -extern crate rustc_lint; -extern crate rustc_lint_defs; -extern crate rustc_macros; -extern crate rustc_metadata; -extern crate rustc_middle; -extern crate rustc_parse; -extern crate rustc_passes; -extern crate rustc_resolve; -extern crate rustc_serialize; -extern crate rustc_session; -extern crate rustc_span; -extern crate rustc_target; -extern crate rustc_trait_selection; -extern crate test; - -pub mod doctest; -// used by the error-index generator, so it needs to be public -pub mod html; diff --git a/tools/bookrunner/print.sh b/tools/bookrunner/print.sh deleted file mode 100755 index 904409ee1c87..000000000000 --- a/tools/bookrunner/print.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -# Copyright Kani Contributors -# SPDX-License-Identifier: Apache-2.0 OR MIT - -# `rustdoc` treats this script as `rustc` and sends code extracted from markdown -# files to stdin of this script. Instead of compiling the code, this scripts -# simply copies the contents of stdin to the location where `rustdoc` caches the -# "compiled" output. - -FILE="$6" -BASE=`basename "$FILE"` -mkdir -p "$BASE" -cp "/dev/stdin" "$FILE" diff --git a/tools/bookrunner/rust-doc/nomicon b/tools/bookrunner/rust-doc/nomicon deleted file mode 160000 index c05c452b3635..000000000000 --- a/tools/bookrunner/rust-doc/nomicon +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c05c452b36358821bf4122f9c418674edd1d713d diff --git a/tools/bookrunner/rust-doc/reference b/tools/bookrunner/rust-doc/reference deleted file mode 160000 index f8ba2f12df60..000000000000 --- a/tools/bookrunner/rust-doc/reference +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f8ba2f12df60ee19b96de24ae5b73af3de8a446b diff --git a/tools/bookrunner/rust-doc/rust-by-example b/tools/bookrunner/rust-doc/rust-by-example deleted file mode 160000 index 43f82530210b..000000000000 --- a/tools/bookrunner/rust-doc/rust-by-example +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 43f82530210b83cf888282b207ed13d5893da9b2 diff --git a/tools/bookrunner/rust-doc/unstable-book/.gitignore b/tools/bookrunner/rust-doc/unstable-book/.gitignore deleted file mode 100644 index 7585238efedf..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/.gitignore +++ /dev/null @@ -1 +0,0 @@ -book diff --git a/tools/bookrunner/rust-doc/unstable-book/book.toml b/tools/bookrunner/rust-doc/unstable-book/book.toml deleted file mode 100644 index dfbd8311dae4..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/book.toml +++ /dev/null @@ -1,10 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 OR MIT -# -# Modifications Copyright Kani Contributors -# See GitHub history for details. -[book] -title = "The Rust Unstable Book" -author = "The Rust Community" - -[output.html] -git-repository-url = "https://github.com/rust-lang/rust/tree/master/src/doc/unstable-book" diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags.md deleted file mode 100644 index 43eadb351016..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags.md +++ /dev/null @@ -1 +0,0 @@ -# Compiler flags diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/branch-protection.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/branch-protection.md deleted file mode 100644 index 85403748e1dc..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/branch-protection.md +++ /dev/null @@ -1,18 +0,0 @@ -# `branch-protection` - -This option lets you enable branch authentication instructions on AArch64. -This option is ignored for non-AArch64 architectures. -It takes some combination of the following values, separated by a `,`. - -- `pac-ret` - Enable pointer authentication for non-leaf functions. -- `leaf` - Enable pointer authentication for all functions, including leaf functions. -- `b-key` - Sign return addresses with key B, instead of the default key A. -- `bti` - Enable branch target identification. - -`leaf` and `b-key` are only valid if `pac-ret` was previously specified. -For example, `-Z branch-protection=bti,pac-ret,leaf` is valid, but -`-Z branch-protection=bti,leaf,pac-ret` is not. - -Rust's standard library does not ship with BTI or pointer authentication enabled by default. -In Cargo projects the standard library can be recompiled with pointer authentication using the nightly -[build-std](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std) feature. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/codegen-backend.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/codegen-backend.md deleted file mode 100644 index 3c0cd32fae17..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/codegen-backend.md +++ /dev/null @@ -1,31 +0,0 @@ -# `codegen-backend` - -The tracking issue for this feature is: [#77933](https://github.com/rust-lang/rust/issues/77933). - ------------------------- - -This feature allows you to specify a path to a dynamic library to use as rustc's -code generation backend at runtime. - -Set the `-Zcodegen-backend=` compiler flag to specify the location of the -backend. The library must be of crate type `dylib` and must contain a function -named `__rustc_codegen_backend` with a signature of `fn() -> Box`. - -## Example -See also the [`hotplug_codegen_backend`](https://github.com/rust-lang/rust/tree/master/src/test/run-make-fulldeps/hotplug_codegen_backend) test -for a full example. - -```rust,ignore (partial-example) -use rustc_codegen_ssa::traits::CodegenBackend; - -struct MyBackend; - -impl CodegenBackend for MyBackend { - // Implement codegen methods -} - -#[no_mangle] -pub fn __rustc_codegen_backend() -> Box { - Box::new(MyBackend) -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/control-flow-guard.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/control-flow-guard.md deleted file mode 100644 index 08c16d95f467..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/control-flow-guard.md +++ /dev/null @@ -1,59 +0,0 @@ -# `control-flow-guard` - -The tracking issue for this feature is: [#68793](https://github.com/rust-lang/rust/issues/68793). - ------------------------- - -The rustc flag `-Z control-flow-guard` enables the Windows [Control Flow Guard](https://docs.microsoft.com/en-us/windows/win32/secbp/control-flow-guard) (CFG) platform security feature. - -CFG is an exploit mitigation designed to enforce control-flow integrity for software running on supported [Windows platforms (Windows 8.1 onwards)](https://docs.microsoft.com/en-us/windows/win32/secbp/control-flow-guard). Specifically, CFG uses runtime checks to validate the target address of every indirect call/jump before allowing the call to complete. - -During compilation, the compiler identifies all indirect calls/jumps and adds CFG checks. It also emits metadata containing the relative addresses of all address-taken functions. At runtime, if the binary is run on a CFG-aware operating system, the loader uses the CFG metadata to generate a bitmap of the address space and marks those addresses that contain valid targets. On each indirect call, the inserted check determines whether the target address is marked in this bitmap. If the target is not valid, the process is terminated. - -In terms of interoperability: -- Code compiled with CFG enabled can be linked with libraries and object files that are not compiled with CFG. In this case, a CFG-aware linker can identify address-taken functions in the non-CFG libraries. -- Libraries compiled with CFG can linked into non-CFG programs. In this case, the CFG runtime checks in the libraries are not used (i.e. the mitigation is completely disabled). - -CFG functionality is completely implemented in the LLVM backend and is supported for X86 (32-bit and 64-bit), ARM, and Aarch64 targets. The rustc flag adds the relevant LLVM module flags to enable the feature. This flag will be ignored for all non-Windows targets. - - -## When to use Control Flow Guard - -The primary motivation for enabling CFG in Rust is to enhance security when linking against non-Rust code, especially C/C++ code. To achieve full CFG protection, all indirect calls (including any from Rust code) must have the appropriate CFG checks, as added by this flag. CFG can also improve security for Rust code that uses the `unsafe` keyword. - -Another motivation behind CFG is to harden programs against [return-oriented programming (ROP)](https://en.wikipedia.org/wiki/Return-oriented_programming) attacks. CFG disallows an attacker from taking advantage of the program's own instructions while redirecting control flow in unexpected ways. - -## Overhead of Control Flow Guard - -The CFG checks and metadata can potentially increase binary size and runtime overhead. The magnitude of any increase depends on the number and frequency of indirect calls. For example, enabling CFG for the Rust standard library increases binary size by approximately 0.14%. Enabling CFG in the SPEC CPU 2017 Integer Speed benchmark suite (compiled with Clang/LLVM) incurs approximate runtime overheads of between 0% and 8%, with a geometric mean of 2.9%. - - -## Testing Control Flow Guard - -The rustc flag `-Z control-flow-guard=nochecks` instructs LLVM to emit the list of valid call targets without inserting runtime checks. This flag should only be used for testing purposes as it does not provide security enforcement. - - -## Control Flow Guard in libraries - -It is strongly recommended to also enable CFG checks for all linked libraries, including the standard library. - -To enable CFG in the standard library, use the [cargo `-Z build-std` functionality][build-std] to recompile the standard library with the same configuration options as the main program. - -[build-std]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std - -For example: -```cmd -rustup toolchain install --force nightly -rustup component add rust-src -SET RUSTFLAGS=-Z control-flow-guard -cargo +nightly build -Z build-std --target x86_64-pc-windows-msvc -``` - -```PowerShell -rustup toolchain install --force nightly -rustup component add rust-src -$Env:RUSTFLAGS = "-Z control-flow-guard" -cargo +nightly build -Z build-std --target x86_64-pc-windows-msvc -``` - -Alternatively, if you are building the standard library from source, you can set `control-flow-guard = true` in the config.toml file. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/debug_info_for_profiling.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/debug_info_for_profiling.md deleted file mode 100644 index 44bd3baeeedf..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/debug_info_for_profiling.md +++ /dev/null @@ -1,35 +0,0 @@ -# `debug-info-for-profiling - ---- - -## Introduction - -Automatic Feedback Directed Optimization (AFDO) is a method for using sampling -based profiles to guide optimizations. This is contrasted with other methods of -FDO or profile-guided optimization (PGO) which use instrumented profiling. - -Unlike PGO (controlled by the `rustc` flags `-Cprofile-generate` and -`-Cprofile-use`), a binary being profiled does not perform significantly worse, -and thus it's possible to profile binaries used in real workflows and not -necessary to construct artificial workflows. - -## Use - -In order to use AFDO, the target platform must be Linux running on an `x86_64` -architecture with the performance profiler `perf` available. In addition, the -external tool `create_llvm_prof` from [this repository] must be used. - -Given a Rust file `main.rs`, we can produce an optimized binary as follows: - -```shell -rustc -O -Zdebug-info-for-profiling main.rs -o main -perf record -b ./main -create_llvm_prof --binary=main --out=code.prof -rustc -O -Zprofile-sample-use=code.prof main.rs -o main2 -``` - -The `perf` command produces a profile `perf.data`, which is then used by the -`create_llvm_prof` command to create `code.prof`. This final profile is then -used by `rustc` to guide optimizations in producing the binary `main2`. - -[this repository]: https://github.com/google/autofdo diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/emit-stack-sizes.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/emit-stack-sizes.md deleted file mode 100644 index 47f45a0b91f8..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/emit-stack-sizes.md +++ /dev/null @@ -1,167 +0,0 @@ -# `emit-stack-sizes` - -The tracking issue for this feature is: [#54192] - -[#54192]: https://github.com/rust-lang/rust/issues/54192 - ------------------------- - -The rustc flag `-Z emit-stack-sizes` makes LLVM emit stack size metadata. - -> **NOTE**: This LLVM feature only supports the ELF object format as of LLVM -> 8.0. Using this flag with targets that use other object formats (e.g. macOS -> and Windows) will result in it being ignored. - -Consider this crate: - -``` -#![crate_type = "lib"] - -use std::ptr; - -pub fn foo() { - // this function doesn't use the stack -} - -pub fn bar() { - let xs = [0u32; 2]; - - // force LLVM to allocate `xs` on the stack - unsafe { ptr::read_volatile(&xs.as_ptr()); } -} -``` - -Using the `-Z emit-stack-sizes` flag produces extra linker sections in the -output *object file*. - -``` console -$ rustc -C opt-level=3 --emit=obj foo.rs - -$ size -A foo.o -foo.o : -section size addr -.text 0 0 -.text._ZN3foo3foo17he211d7b4a3a0c16eE 1 0 -.text._ZN3foo3bar17h1acb594305f70c2eE 22 0 -.note.GNU-stack 0 0 -.eh_frame 72 0 -Total 95 - -$ rustc -C opt-level=3 --emit=obj -Z emit-stack-sizes foo.rs - -$ size -A foo.o -foo.o : -section size addr -.text 0 0 -.text._ZN3foo3foo17he211d7b4a3a0c16eE 1 0 -.stack_sizes 9 0 -.text._ZN3foo3bar17h1acb594305f70c2eE 22 0 -.stack_sizes 9 0 -.note.GNU-stack 0 0 -.eh_frame 72 0 -Total 113 -``` - -As of LLVM 7.0 the data will be written into a section named `.stack_sizes` and -the format is "an array of pairs of function symbol values (pointer size) and -stack sizes (unsigned LEB128)". - -``` console -$ objdump -d foo.o - -foo.o: file format elf64-x86-64 - -Disassembly of section .text._ZN3foo3foo17he211d7b4a3a0c16eE: - -0000000000000000 <_ZN3foo3foo17he211d7b4a3a0c16eE>: - 0: c3 retq - -Disassembly of section .text._ZN3foo3bar17h1acb594305f70c2eE: - -0000000000000000 <_ZN3foo3bar17h1acb594305f70c2eE>: - 0: 48 83 ec 10 sub $0x10,%rsp - 4: 48 8d 44 24 08 lea 0x8(%rsp),%rax - 9: 48 89 04 24 mov %rax,(%rsp) - d: 48 8b 04 24 mov (%rsp),%rax - 11: 48 83 c4 10 add $0x10,%rsp - 15: c3 retq - -$ objdump -s -j .stack_sizes foo.o - -foo.o: file format elf64-x86-64 - -Contents of section .stack_sizes: - 0000 00000000 00000000 00 ......... -Contents of section .stack_sizes: - 0000 00000000 00000000 10 ......... -``` - -It's important to note that linkers will discard this linker section by default. -To preserve the section you can use a linker script like the one shown below. - -``` text -/* file: keep-stack-sizes.x */ -SECTIONS -{ - /* `INFO` makes the section not allocatable so it won't be loaded into memory */ - .stack_sizes (INFO) : - { - KEEP(*(.stack_sizes)); - } -} -``` - -The linker script must be passed to the linker using a rustc flag like `-C -link-arg`. - -``` -// file: src/main.rs -use std::ptr; - -#[inline(never)] -fn main() { - let xs = [0u32; 2]; - - // force LLVM to allocate `xs` on the stack - unsafe { ptr::read_volatile(&xs.as_ptr()); } -} -``` - -``` console -$ RUSTFLAGS="-Z emit-stack-sizes" cargo build --release - -$ size -A target/release/hello | grep stack_sizes || echo section was not found -section was not found - -$ RUSTFLAGS="-Z emit-stack-sizes" cargo rustc --release -- \ - -C link-arg=-Wl,-Tkeep-stack-sizes.x \ - -C link-arg=-N - -$ size -A target/release/hello | grep stack_sizes -.stack_sizes 90 176272 - -$ # non-allocatable section (flags don't contain the "A" (alloc) flag) -$ readelf -S target/release/hello -Section Headers: - [Nr] Name Type Address Offset - Size EntSize Flags Link Info Align -(..) - [1031] .stack_sizes PROGBITS 000000000002b090 0002b0f0 - 000000000000005a 0000000000000000 L 5 0 1 - -$ objdump -s -j .stack_sizes target/release/hello - -target/release/hello: file format elf64-x86-64 - -Contents of section .stack_sizes: - 2b090 c0040000 00000000 08f00400 00000000 ................ - 2b0a0 00080005 00000000 00000810 05000000 ................ - 2b0b0 00000000 20050000 00000000 10400500 .... ........@.. - 2b0c0 00000000 00087005 00000000 00000080 ......p......... - 2b0d0 05000000 00000000 90050000 00000000 ................ - 2b0e0 00a00500 00000000 0000 .......... -``` - -> Author note: I'm not entirely sure why, in *this* case, `-N` is required in -> addition to `-Tkeep-stack-sizes.x`. For example, it's not required when -> producing statically linked files for the ARM Cortex-M architecture. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/extern-location.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/extern-location.md deleted file mode 100644 index 1c80d5426bf7..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/extern-location.md +++ /dev/null @@ -1,31 +0,0 @@ -# `extern-location` - -MCP for this feature: [#303] - -[#303]: https://github.com/rust-lang/compiler-team/issues/303 - ------------------------- - -The `unused-extern-crates` lint reports when a crate was specified on the rustc -command-line with `--extern name=path` but no symbols were referenced in it. -This is useful to know, but it's hard to map that back to a specific place a user -or tool could fix (ie, to remove the unused dependency). - -The `--extern-location` flag allows the build system to associate a location with -the `--extern` option, which is then emitted as part of the diagnostics. This location -is abstract and just round-tripped through rustc; the compiler never attempts to -interpret it in any way. - -There are two supported forms of location: a bare string, or a blob of json: -- `--extern-location foo=raw:Makefile:123` would associate the raw string `Makefile:123` -- `--extern-location 'bar=json:{"target":"//my_project:library","dep":"//common:serde"}` would - associate the json structure with `--extern bar=`, indicating which dependency of - which rule introduced the unused extern crate. - -This primarily intended to be used with tooling - for example a linter which can automatically -remove unused dependencies - rather than being directly presented to users. - -`raw` locations are presented as part of the normal rendered diagnostics and included in -the json form. `json` locations are only included in the json form of diagnostics, -as a `tool_metadata` field. For `raw` locations `tool_metadata` is simply a json string, -whereas `json` allows the rustc invoker to fully control its form and content. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/img/llvm-cov-show-01.png b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/img/llvm-cov-show-01.png deleted file mode 100644 index 35f04594347a..000000000000 Binary files a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/img/llvm-cov-show-01.png and /dev/null differ diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/instrument-coverage.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/instrument-coverage.md deleted file mode 100644 index 39eb407269c1..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/instrument-coverage.md +++ /dev/null @@ -1,346 +0,0 @@ -# `instrument-coverage` - -The tracking issue for this feature is: [#79121]. - -[#79121]: https://github.com/rust-lang/rust/issues/79121 - ---- - -## Introduction - -The Rust compiler includes two code coverage implementations: - -- A GCC-compatible, gcov-based coverage implementation, enabled with `-Z profile`, which derives coverage data based on DebugInfo. -- A source-based code coverage implementation, enabled with `-Z instrument-coverage`, which uses LLVM's native, efficient coverage instrumentation to generate very precise coverage data. - -This document describes how to enable and use the LLVM instrumentation-based coverage, via the `-Z instrument-coverage` compiler flag. - -## How it works - -When `-Z instrument-coverage` is enabled, the Rust compiler enhances rust-based libraries and binaries by: - -- Automatically injecting calls to an LLVM intrinsic ([`llvm.instrprof.increment`]), at functions and branches in compiled code, to increment counters when conditional sections of code are executed. -- Embedding additional information in the data section of each library and binary (using the [LLVM Code Coverage Mapping Format] _Version 5_, if compiling with LLVM 12, or _Version 6_, if compiling with LLVM 13 or higher), to define the code regions (start and end positions in the source code) being counted. - -When running a coverage-instrumented program, the counter values are written to a `profraw` file at program termination. LLVM bundles tools that read the counter results, combine those results with the coverage map (embedded in the program binary), and generate coverage reports in multiple formats. - -[`llvm.instrprof.increment`]: https://llvm.org/docs/LangRef.html#llvm-instrprof-increment-intrinsic -[llvm code coverage mapping format]: https://llvm.org/docs/CoverageMappingFormat.html - -> **Note**: `-Z instrument-coverage` also automatically enables `-C symbol-mangling-version=v0` (tracking issue [#60705]). The `v0` symbol mangler is strongly recommended, but be aware that this demangler is also experimental. The `v0` demangler can be overridden by explicitly adding `-Z unstable-options -C symbol-mangling-version=legacy`. - -[#60705]: https://github.com/rust-lang/rust/issues/60705 - -## Enable coverage profiling in the Rust compiler - -Rust's source-based code coverage requires the Rust "profiler runtime". Without it, compiling with `-Z instrument-coverage` generates an error that the profiler runtime is missing. - -The Rust `nightly` distribution channel includes the profiler runtime, by default. - -> **Important**: If you are building the Rust compiler from the source distribution, the profiler runtime is _not_ enabled in the default `config.toml.example`. Edit your `config.toml` file and ensure the `profiler` feature is set it to `true` (either under the `[build]` section, or under the settings for an individual `[target.]`): -> -> ```toml -> # Build the profiler runtime (required when compiling with options that depend -> # on this runtime, such as `-C profile-generate` or `-Z instrument-coverage`). -> profiler = true -> ``` - -### Building the demangler - -LLVM coverage reporting tools generate results that can include function names and other symbol references, and the raw coverage results report symbols using the compiler's "mangled" version of the symbol names, which can be difficult to interpret. To work around this issue, LLVM coverage tools also support a user-specified symbol name demangler. - -One option for a Rust demangler is [`rustfilt`], which can be installed with: - -```shell -cargo install rustfilt -``` - -Another option, if you are building from the Rust compiler source distribution, is to use the `rust-demangler` tool included in the Rust source distribution, which can be built with: - -```shell -$ ./x.py build rust-demangler -``` - -[`rustfilt`]: https://crates.io/crates/rustfilt - -## Compiling with coverage enabled - -Set the `-Z instrument-coverage` compiler flag in order to enable LLVM source-based code coverage profiling. - -The default option generates coverage for all functions, including unused (never called) functions and generics. The compiler flag supports an optional value to tailor this behavior. (See [`-Z instrument-coverage=`](#-z-instrument-coverageoptions), below.) - -With `cargo`, you can instrument your program binary _and_ dependencies at the same time. - -For example (if your project's Cargo.toml builds a binary by default): - -```shell -$ cd your-project -$ cargo clean -$ RUSTFLAGS="-Z instrument-coverage" cargo build -``` - -If `cargo` is not configured to use your `profiler`-enabled version of `rustc`, set the path explicitly via the `RUSTC` environment variable. Here is another example, using a `stage1` build of `rustc` to compile an `example` binary (from the [`json5format`] crate): - -```shell -$ RUSTC=$HOME/rust/build/x86_64-unknown-linux-gnu/stage1/bin/rustc \ - RUSTFLAGS="-Z instrument-coverage" \ - cargo build --example formatjson5 -``` - -> **Note**: that some compiler options, combined with `-Z instrument-coverage`, can produce LLVM IR and/or linked binaries that are incompatible with LLVM coverage maps. For example, coverage requires references to actual functions in LLVM IR. If any covered function is optimized out, the coverage tools may not be able to process the coverage results. If you need to pass additional options, with coverage enabled, test them early, to confirm you will get the coverage results you expect. - -## Running the instrumented binary to generate raw coverage profiling data - -In the previous example, `cargo` generated the coverage-instrumented binary `formatjson5`: - -```shell -$ echo "{some: 'thing'}" | target/debug/examples/formatjson5 - -``` - -```json5 -{ - some: "thing", -} -``` - -After running this program, a new file, `default.profraw`, should be in the current working directory. It's often preferable to set a specific file name or path. You can change the output file using the environment variable `LLVM_PROFILE_FILE`: - -```shell -$ echo "{some: 'thing'}" \ - | LLVM_PROFILE_FILE="formatjson5.profraw" target/debug/examples/formatjson5 - -... -$ ls formatjson5.profraw -formatjson5.profraw -``` - -If `LLVM_PROFILE_FILE` contains a path to a non-existent directory, the missing directory structure will be created. Additionally, the following special pattern strings are rewritten: - -- `%p` - The process ID. -- `%h` - The hostname of the machine running the program. -- `%t` - The value of the TMPDIR environment variable. -- `%Nm` - the instrumented binary’s signature: The runtime creates a pool of N raw profiles, used for on-line profile merging. The runtime takes care of selecting a raw profile from the pool, locking it, and updating it before the program exits. `N` must be between `1` and `9`, and defaults to `1` if omitted (with simply `%m`). -- `%c` - Does not add anything to the filename, but enables a mode (on some platforms, including Darwin) in which profile counter updates are continuously synced to a file. This means that if the instrumented program crashes, or is killed by a signal, perfect coverage information can still be recovered. - -## Installing LLVM coverage tools - -LLVM's supplies two tools—`llvm-profdata` and `llvm-cov`—that process coverage data and generate reports. There are several ways to find and/or install these tools, but note that the coverage mapping data generated by the Rust compiler requires LLVM version 12 or higher. (`llvm-cov --version` typically shows the tool's LLVM version number.): - -- The LLVM tools may be installed (or installable) directly to your OS (such as via `apt-get`, for Linux). -- If you are building the Rust compiler from source, you can optionally use the bundled LLVM tools, built from source. Those tool binaries can typically be found in your build platform directory at something like: `rust/build/x86_64-unknown-linux-gnu/llvm/bin/llvm-*`. -- You can install compatible versions of these tools via `rustup`. - -The `rustup` option is guaranteed to install a compatible version of the LLVM tools, but they can be hard to find. We recommend [`cargo-binutils`], which installs Rust-specific wrappers around these and other LLVM tools, so you can invoke them via `cargo` commands! - -```shell -$ rustup component add llvm-tools-preview -$ cargo install cargo-binutils -$ cargo profdata -- --help # note the additional "--" preceding the tool-specific arguments -``` - -[`cargo-binutils`]: https://crates.io/crates/cargo-binutils - -## Creating coverage reports - -Raw profiles have to be indexed before they can be used to generate coverage reports. This is done using [`llvm-profdata merge`] (or `cargo profdata -- merge`), which can combine multiple raw profiles and index them at the same time: - -```shell -$ llvm-profdata merge -sparse formatjson5.profraw -o formatjson5.profdata -``` - -Finally, the `.profdata` file is used, in combination with the coverage map (from the program binary) to generate coverage reports using [`llvm-cov report`] (or `cargo cov -- report`), for a coverage summaries; and [`llvm-cov show`] (or `cargo cov -- show`), to see detailed coverage of lines and regions (character ranges) overlaid on the original source code. - -These commands have several display and filtering options. For example: - -```shell -$ llvm-cov show -Xdemangler=rustfilt target/debug/examples/formatjson5 \ - -instr-profile=formatjson5.profdata \ - -show-line-counts-or-regions \ - -show-instantiations \ - -name=add_quoted_string -``` - -Screenshot of sample `llvm-cov show` result, for function add_quoted_string -
-
- -Some of the more notable options in this example include: - -- `--Xdemangler=rustfilt` - the command name or path used to demangle Rust symbols (`rustfilt` in the example, but this could also be a path to the `rust-demangler` tool) -- `target/debug/examples/formatjson5` - the instrumented binary (from which to extract the coverage map) -- `--instr-profile=.profdata` - the location of the `.profdata` file created by `llvm-profdata merge` (from the `.profraw` file generated by the instrumented binary) -- `--name=` - to show coverage for a specific function (or, consider using another filter option, such as `--name-regex=`) - -[`llvm-profdata merge`]: https://llvm.org/docs/CommandGuide/llvm-profdata.html#profdata-merge -[`llvm-cov report`]: https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-report -[`llvm-cov show`]: https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-show - -> **Note**: Coverage can also be disabled on an individual function by annotating the function with the [`no_coverage` attribute] (which requires the feature flag `#![feature(no_coverage)]`). - -[`no_coverage` attribute]: ../language-features/no-coverage.md - -## Interpreting reports - -There are four statistics tracked in a coverage summary: - -- Function coverage is the percentage of functions that have been executed at least once. A function is considered to be executed if any of its instantiations are executed. -- Instantiation coverage is the percentage of function instantiations that have been executed at least once. Generic functions and functions generated from macros are two kinds of functions that may have multiple instantiations. -- Line coverage is the percentage of code lines that have been executed at least once. Only executable lines within function bodies are considered to be code lines. -- Region coverage is the percentage of code regions that have been executed at least once. A code region may span multiple lines: for example, in a large function body with no control flow. In other cases, a single line can contain multiple code regions: `return x || (y && z)` has countable code regions for `x` (which may resolve the expression, if `x` is `true`), `|| (y && z)` (executed only if `x` was `false`), and `return` (executed in either situation). - -Of these four statistics, function coverage is usually the least granular while region coverage is the most granular. The project-wide totals for each statistic are listed in the summary. - -## Test coverage - -A typical use case for coverage analysis is test coverage. Rust's source-based coverage tools can both measure your tests' code coverage as percentage, and pinpoint functions and branches not tested. - -The following example (using the [`json5format`] crate, for demonstration purposes) show how to generate and analyze coverage results for all tests in a crate. - -Since `cargo test` both builds and runs the tests, we set both the additional `RUSTFLAGS`, to add the `-Z instrument-coverage` flag, and `LLVM_PROFILE_FILE`, to set a custom filename for the raw profiling data generated during the test runs. Since there may be more than one test binary, apply `%m` in the filename pattern. This generates unique names for each test binary. (Otherwise, each executed test binary would overwrite the coverage results from the previous binary.) - -```shell -$ RUSTFLAGS="-Z instrument-coverage" \ - LLVM_PROFILE_FILE="json5format-%m.profraw" \ - cargo test --tests -``` - -Make note of the test binary file paths, displayed after the word "`Running`" in the test output: - -```text - ... - Compiling json5format v0.1.3 ($HOME/json5format) - Finished test [unoptimized + debuginfo] target(s) in 14.60s - - Running target/debug/deps/json5format-fececd4653271682 -running 25 tests -... -test result: ok. 25 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out - - Running target/debug/deps/lib-30768f9c53506dc5 -running 31 tests -... -test result: ok. 31 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out -``` - -You should have one or more `.profraw` files now, one for each test binary. Run the `profdata` tool to merge them: - -```shell -$ cargo profdata -- merge \ - -sparse json5format-*.profraw -o json5format.profdata -``` - -Then run the `cov` tool, with the `profdata` file and all test binaries: - -```shell -$ cargo cov -- report \ - --use-color --ignore-filename-regex='/.cargo/registry' \ - --instr-profile=json5format.profdata \ - --object target/debug/deps/lib-30768f9c53506dc5 \ - --object target/debug/deps/json5format-fececd4653271682 -$ cargo cov -- show \ - --use-color --ignore-filename-regex='/.cargo/registry' \ - --instr-profile=json5format.profdata \ - --object target/debug/deps/lib-30768f9c53506dc5 \ - --object target/debug/deps/json5format-fececd4653271682 \ - --show-instantiations --show-line-counts-or-regions \ - --Xdemangler=rustfilt | less -R -``` - -> **Note**: The command line option `--ignore-filename-regex=/.cargo/registry`, which excludes the sources for dependencies from the coverage results.\_ - -### Tips for listing the binaries automatically - -For `bash` users, one suggested way to automatically complete the `cov` command with the list of binaries is with a command like: - -```bash -$ cargo cov -- report \ - $( \ - for file in \ - $( \ - RUSTFLAGS="-Z instrument-coverage" \ - cargo test --tests --no-run --message-format=json \ - | jq -r "select(.profile.test == true) | .filenames[]" \ - | grep -v dSYM - \ - ); \ - do \ - printf "%s %s " -object $file; \ - done \ - ) \ - --instr-profile=json5format.profdata --summary-only # and/or other options -``` - -Adding `--no-run --message-format=json` to the _same_ `cargo test` command used to run -the tests (including the same environment variables and flags) generates output in a JSON -format that `jq` can easily query. - -The `printf` command takes this list and generates the `--object ` arguments -for each listed test binary. - -### Including doc tests - -The previous examples run `cargo test` with `--tests`, which excludes doc tests.[^79417] - -To include doc tests in the coverage results, drop the `--tests` flag, and apply the -`-Z instrument-coverage` flag, and some doc-test-specific options in the -`RUSTDOCFLAGS` environment variable. (The `cargo profdata` command does not change.) - -```bash -$ RUSTFLAGS="-Z instrument-coverage" \ - RUSTDOCFLAGS="-Z instrument-coverage -Z unstable-options --persist-doctests target/debug/doctestbins" \ - LLVM_PROFILE_FILE="json5format-%m.profraw" \ - cargo test -$ cargo profdata -- merge \ - -sparse json5format-*.profraw -o json5format.profdata -``` - -The `-Z unstable-options --persist-doctests` flag is required, to save the test binaries -(with their coverage maps) for `llvm-cov`. - -```bash -$ cargo cov -- report \ - $( \ - for file in \ - $( \ - RUSTFLAGS="-Z instrument-coverage" \ - RUSTDOCFLAGS="-Z instrument-coverage -Z unstable-options --persist-doctests target/debug/doctestbins" \ - cargo test --no-run --message-format=json \ - | jq -r "select(.profile.test == true) | .filenames[]" \ - | grep -v dSYM - \ - ) \ - target/debug/doctestbins/*/rust_out; \ - do \ - [[ -x $file ]] && printf "%s %s " -object $file; \ - done \ - ) \ - --instr-profile=json5format.profdata --summary-only # and/or other options -``` - -> **Note**: The differences in this `cargo cov` command, compared with the version without -> doc tests, include: - -- The `cargo test ... --no-run` command is updated with the same environment variables - and flags used to _build_ the tests, _including_ the doc tests. (`LLVM_PROFILE_FILE` - is only used when _running_ the tests.) -- The file glob pattern `target/debug/doctestbins/*/rust_out` adds the `rust_out` - binaries generated for doc tests (note, however, that some `rust_out` files may not - be executable binaries). -- `[[ -x $file ]] &&` filters the files passed on to the `printf`, to include only - executable binaries. - -[^79417]: - There is ongoing work to resolve a known issue - [(#79417)](https://github.com/rust-lang/rust/issues/79417) that doc test coverage - generates incorrect source line numbers in `llvm-cov show` results. - -## `-Z instrument-coverage=` - -- `-Z instrument-coverage=all`: Instrument all functions, including unused functions and unused generics. (This is the same as `-Z instrument-coverage`, with no value.) -- `-Z instrument-coverage=except-unused-generics`: Instrument all functions except unused generics. -- `-Z instrument-coverage=except-unused-functions`: Instrument only used (called) functions and instantiated generic functions. -- `-Z instrument-coverage=off`: Do not instrument any functions. (This is the same as simply not including the `-Z instrument-coverage` option.) - -## Other references - -Rust's implementation and workflow for source-based code coverage is based on the same library and tools used to implement [source-based code coverage in Clang]. (This document is partially based on the Clang guide.) - -[source-based code coverage in clang]: https://clang.llvm.org/docs/SourceBasedCodeCoverage.html -[`json5format`]: https://crates.io/crates/json5format diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/location-detail.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/location-detail.md deleted file mode 100644 index 08d937cc2820..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/location-detail.md +++ /dev/null @@ -1,43 +0,0 @@ -# `location-detail` - -The tracking issue for this feature is: [#70580](https://github.com/rust-lang/rust/issues/70580). - ------------------------- - -Option `-Z location-detail=val` controls what location details are tracked when -using `caller_location`. This allows users to control what location details -are printed as part of panic messages, by allowing them to exclude any combination -of filenames, line numbers, and column numbers. This option is intended to provide -users with a way to mitigate the size impact of `#[track_caller]`. - -This option supports a comma separated list of location details to be included. Valid options -within this list are: - -- `file` - the filename of the panic will be included in the panic output -- `line` - the source line of the panic will be included in the panic output -- `column` - the source column of the panic will be included in the panic output - -Any combination of these three options are supported. If this option is not specified, -all three are included by default. - -An example of a panic output when using `-Z location-detail=line`: -```text -panicked at 'Process blink had a fault', :323:0 -``` - -The code size savings from this option are two-fold. First, the `&'static str` values -for each path to a file containing a panic are removed from the binary. For projects -with deep directory structures and many files with panics, this can add up. This category -of savings can only be realized by excluding filenames from the panic output. Second, -savings can be realized by allowing multiple panics to be fused into a single panicking -branch. It is often the case that within a single file, multiple panics with the same -panic message exist -- e.g. two calls to `Option::unwrap()` in a single line, or -two calls to `Result::expect()` on adjacent lines. If column and line information -are included in the `Location` struct passed to the panic handler, these branches cannot -be fused, as the output is different depending on which panic occurs. However if line -and column information is identical for all panics, these branches can be fused, which -can lead to substantial code size savings, especially for small embedded binaries with -many panics. - -The savings from this option are amplified when combined with the use of `-Zbuild-std`, as -otherwise paths for panics within the standard library are still included in your binary. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/move-size-limit.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/move-size-limit.md deleted file mode 100644 index 88f022af2ecf..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/move-size-limit.md +++ /dev/null @@ -1,10 +0,0 @@ -# `move_size_limit` - --------------------- - -The `-Zmove-size-limit=N` compiler flag enables `large_assignments` lints which -will warn when moving objects whose size exceeds `N` bytes. - -Lint warns only about moves in functions that participate in code generation. -Consequently it will be ineffective for compiler invocatation that emit -metadata only, i.e., `cargo check` like workflows. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/no-unique-section-names.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/no-unique-section-names.md deleted file mode 100644 index 5c1c7cda7013..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/no-unique-section-names.md +++ /dev/null @@ -1,9 +0,0 @@ -# `no-unique-section-names` - ------------------------- - -This flag currently applies only to ELF-based targets using the LLVM codegen backend. It prevents the generation of unique ELF section names for each separate code and data item when `-Z function-sections` is also in use, which is the default for most targets. This option can reduce the size of object files, and depending on the linker, the final ELF binary as well. - -For example, a function `func` will by default generate a code section called `.text.func`. Normally this is fine because the linker will merge all those `.text.*` sections into a single one in the binary. However, starting with [LLVM 12](https://github.com/llvm/llvm-project/commit/ee5d1a04), the backend will also generate unique section names for exception handling, so you would see a section name of `.gcc_except_table.func` in the object file and potentially in the final ELF binary, which could add significant bloat to programs that contain many functions. - -This flag instructs LLVM to use the same `.text` and `.gcc_except_table` section name for each function, and it is analogous to Clang's `-fno-unique-section-names` option. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/profile.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/profile.md deleted file mode 100644 index 71303bfaff20..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/profile.md +++ /dev/null @@ -1,27 +0,0 @@ -# `profile` - -The tracking issue for this feature is: [#42524](https://github.com/rust-lang/rust/issues/42524). - ------------------------- - -This feature allows the generation of code coverage reports. - -Set the `-Zprofile` compiler flag in order to enable gcov profiling. - -For example: -```Bash -cargo new testgcov --bin -cd testgcov -export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" -export CARGO_INCREMENTAL=0 -cargo build -cargo run -``` - -Once you've built and run your program, files with the `gcno` (after build) and `gcda` (after execution) extensions will be created. -You can parse them with [llvm-cov gcov](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-gcov) or [grcov](https://github.com/mozilla/grcov). - -Please note that `RUSTFLAGS` by default applies to everything that cargo builds and runs during a build! -When the `--target` flag is explicitly passed to cargo, the `RUSTFLAGS` no longer apply to build scripts and procedural macros. -For more fine-grained control consider passing a `RUSTC_WRAPPER` program to cargo that only adds the profiling flags to -rustc for the specific crates you want to profile. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/profile_sample_use.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/profile_sample_use.md deleted file mode 100644 index ce894ce6ac7f..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/profile_sample_use.md +++ /dev/null @@ -1,10 +0,0 @@ -# `profile-sample-use - ---- - -`-Zprofile-sample-use=code.prof` directs `rustc` to use the profile -`code.prof` as a source for Automatic Feedback Directed Optimization (AFDO). -See the documentation of [`-Zdebug-info-for-profiling`] for more information -on using AFDO. - -[`-Zdebug-info-for-profiling`]: debug_info_for_profiling.html diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/remap-cwd-prefix.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/remap-cwd-prefix.md deleted file mode 100644 index 977d258529f8..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/remap-cwd-prefix.md +++ /dev/null @@ -1,24 +0,0 @@ -# `remap-cwd-prefix` - -The tracking issue for this feature is: [#87325](https://github.com/rust-lang/rust/issues/87325). - ------------------------- - -This flag will rewrite absolute paths under the current working directory, -replacing the current working directory prefix with a specified value. - -The given value may be absolute or relative, or empty. This switch takes -precidence over `--remap-path-prefix` in case they would both match a given -path. - -This flag helps to produce deterministic output, by removing the current working -directory from build output, while allowing the command line to be universally -reproducible, such that the same execution will work on all machines, regardless -of build environment. - -## Example -```sh -# This would produce an absolute path to main.rs in build outputs of -# "./main.rs". -rustc -Z remap-cwd-prefix=. main.rs -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/report-time.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/report-time.md deleted file mode 100644 index ac0093f77aec..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/report-time.md +++ /dev/null @@ -1,80 +0,0 @@ -# `report-time` - -The tracking issue for this feature is: [#64888] - -[#64888]: https://github.com/rust-lang/rust/issues/64888 - ------------------------- - -The `report-time` feature adds a possibility to report execution time of the -tests generated via `libtest`. - -This is unstable feature, so you have to provide `-Zunstable-options` to get -this feature working. - -Sample usage command: - -```sh -./test_executable -Zunstable-options --report-time -``` - -Available options: - -```sh ---report-time [plain|colored] - Show execution time of each test. Available values: - plain = do not colorize the execution time (default); - colored = colorize output according to the `color` - parameter value; - Threshold values for colorized output can be - configured via - `RUST_TEST_TIME_UNIT`, `RUST_TEST_TIME_INTEGRATION` - and - `RUST_TEST_TIME_DOCTEST` environment variables. - Expected format of environment variable is - `VARIABLE=WARN_TIME,CRITICAL_TIME`. - Not available for --format=terse ---ensure-time - Treat excess of the test execution time limit as - error. - Threshold values for this option can be configured via - `RUST_TEST_TIME_UNIT`, `RUST_TEST_TIME_INTEGRATION` - and - `RUST_TEST_TIME_DOCTEST` environment variables. - Expected format of environment variable is - `VARIABLE=WARN_TIME,CRITICAL_TIME`. - `CRITICAL_TIME` here means the limit that should not be - exceeded by test. -``` - -Example of the environment variable format: - -```sh -RUST_TEST_TIME_UNIT=100,200 -``` - -where 100 stands for warn time, and 200 stands for critical time. - -## Examples - -```sh -cargo test --tests -- -Zunstable-options --report-time - Finished dev [unoptimized + debuginfo] target(s) in 0.02s - Running target/debug/deps/example-27fb188025bec02c - -running 3 tests -test tests::unit_test_quick ... ok <0.000s> -test tests::unit_test_warn ... ok <0.055s> -test tests::unit_test_critical ... ok <0.110s> - -test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out - - Running target/debug/deps/tests-cedb06f6526d15d9 - -running 3 tests -test unit_test_quick ... ok <0.000s> -test unit_test_warn ... ok <0.550s> -test unit_test_critical ... ok <1.100s> - -test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/sanitizer.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/sanitizer.md deleted file mode 100644 index d630f4ecb7b2..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/sanitizer.md +++ /dev/null @@ -1,594 +0,0 @@ -# `sanitizer` - -The tracking issues for this feature are: - -* [#39699](https://github.com/rust-lang/rust/issues/39699). -* [#89653](https://github.com/rust-lang/rust/issues/89653). - ------------------------- - -This feature allows for use of one of following sanitizers: - -* [AddressSanitizer][clang-asan] a fast memory error detector. -* [ControlFlowIntegrity][clang-cfi] LLVM Control Flow Integrity (CFI) provides - forward-edge control flow protection. -* [HWAddressSanitizer][clang-hwasan] a memory error detector similar to - AddressSanitizer, but based on partial hardware assistance. -* [LeakSanitizer][clang-lsan] a run-time memory leak detector. -* [MemorySanitizer][clang-msan] a detector of uninitialized reads. -* [ThreadSanitizer][clang-tsan] a fast data race detector. - -To enable a sanitizer compile with `-Zsanitizer=address`,`-Zsanitizer=cfi`, -`-Zsanitizer=hwaddress`, `-Zsanitizer=leak`, `-Zsanitizer=memory` or -`-Zsanitizer=thread`. - -# AddressSanitizer - -AddressSanitizer is a memory error detector. It can detect the following types -of bugs: - -* Out of bound accesses to heap, stack and globals -* Use after free -* Use after return (runtime flag `ASAN_OPTIONS=detect_stack_use_after_return=1`) -* Use after scope -* Double-free, invalid free -* Memory leaks - -The memory leak detection is enabled by default on Linux, and can be enabled -with runtime flag `ASAN_OPTIONS=detect_leaks=1` on macOS. - -AddressSanitizer is supported on the following targets: - -* `aarch64-apple-darwin` -* `aarch64-fuchsia` -* `aarch64-unknown-linux-gnu` -* `x86_64-apple-darwin` -* `x86_64-fuchsia` -* `x86_64-unknown-freebsd` -* `x86_64-unknown-linux-gnu` - -AddressSanitizer works with non-instrumented code although it will impede its -ability to detect some bugs. It is not expected to produce false positive -reports. - -## Examples - -Stack buffer overflow: - -```rust -fn main() { - let xs = [0, 1, 2, 3]; - let _y = unsafe { *xs.as_ptr().offset(4) }; -} -``` - -```shell -$ export RUSTFLAGS=-Zsanitizer=address RUSTDOCFLAGS=-Zsanitizer=address -$ cargo run -Zbuild-std --target x86_64-unknown-linux-gnu -==37882==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffe400e6250 at pc 0x5609a841fb20 bp 0x7ffe400e6210 sp 0x7ffe400e6208 -READ of size 4 at 0x7ffe400e6250 thread T0 - #0 0x5609a841fb1f in example::main::h628ffc6626ed85b2 /.../src/main.rs:3:23 - ... - -Address 0x7ffe400e6250 is located in stack of thread T0 at offset 48 in frame - #0 0x5609a841f8af in example::main::h628ffc6626ed85b2 /.../src/main.rs:1 - - This frame has 1 object(s): - [32, 48) 'xs' (line 2) <== Memory access at offset 48 overflows this variable -HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork - (longjmp and C++ exceptions *are* supported) -SUMMARY: AddressSanitizer: stack-buffer-overflow /.../src/main.rs:3:23 in example::main::h628ffc6626ed85b2 -Shadow bytes around the buggy address: - 0x100048014bf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0x100048014c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0x100048014c10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0x100048014c20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0x100048014c30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -=>0x100048014c40: 00 00 00 00 f1 f1 f1 f1 00 00[f3]f3 00 00 00 00 - 0x100048014c50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0x100048014c60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0x100048014c70: f1 f1 f1 f1 00 00 f3 f3 00 00 00 00 00 00 00 00 - 0x100048014c80: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1 - 0x100048014c90: 00 00 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 -Shadow byte legend (one shadow byte represents 8 application bytes): - Addressable: 00 - Partially addressable: 01 02 03 04 05 06 07 - Heap left redzone: fa - Freed heap region: fd - Stack left redzone: f1 - Stack mid redzone: f2 - Stack right redzone: f3 - Stack after return: f5 - Stack use after scope: f8 - Global redzone: f9 - Global init order: f6 - Poisoned by user: f7 - Container overflow: fc - Array cookie: ac - Intra object redzone: bb - ASan internal: fe - Left alloca redzone: ca - Right alloca redzone: cb - Shadow gap: cc -==37882==ABORTING -``` - -Use of a stack object after its scope has already ended: - -```rust -static mut P: *mut usize = std::ptr::null_mut(); - -fn main() { - unsafe { - { - let mut x = 0; - P = &mut x; - } - std::ptr::write_volatile(P, 123); - } -} -``` - -```shell -$ export RUSTFLAGS=-Zsanitizer=address RUSTDOCFLAGS=-Zsanitizer=address -$ cargo run -Zbuild-std --target x86_64-unknown-linux-gnu -================================================================= -==39249==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7ffc7ed3e1a0 at pc 0x55c98b262a8e bp 0x7ffc7ed3e050 sp 0x7ffc7ed3e048 -WRITE of size 8 at 0x7ffc7ed3e1a0 thread T0 - #0 0x55c98b262a8d in core::ptr::write_volatile::he21f1df5a82f329a /.../src/rust/src/libcore/ptr/mod.rs:1048:5 - #1 0x55c98b262cd2 in example::main::h628ffc6626ed85b2 /.../src/main.rs:9:9 - ... - -Address 0x7ffc7ed3e1a0 is located in stack of thread T0 at offset 32 in frame - #0 0x55c98b262bdf in example::main::h628ffc6626ed85b2 /.../src/main.rs:3 - - This frame has 1 object(s): - [32, 40) 'x' (line 6) <== Memory access at offset 32 is inside this variable -HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork - (longjmp and C++ exceptions *are* supported) -SUMMARY: AddressSanitizer: stack-use-after-scope /.../src/rust/src/libcore/ptr/mod.rs:1048:5 in core::ptr::write_volatile::he21f1df5a82f329a -Shadow bytes around the buggy address: - 0x10000fd9fbe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0x10000fd9fbf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0x10000fd9fc00: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1 - 0x10000fd9fc10: f8 f8 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 - 0x10000fd9fc20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -=>0x10000fd9fc30: f1 f1 f1 f1[f8]f3 f3 f3 00 00 00 00 00 00 00 00 - 0x10000fd9fc40: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1 - 0x10000fd9fc50: 00 00 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 - 0x10000fd9fc60: 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00 00 f3 f3 - 0x10000fd9fc70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0x10000fd9fc80: 00 00 00 00 f1 f1 f1 f1 00 00 f3 f3 00 00 00 00 -Shadow byte legend (one shadow byte represents 8 application bytes): - Addressable: 00 - Partially addressable: 01 02 03 04 05 06 07 - Heap left redzone: fa - Freed heap region: fd - Stack left redzone: f1 - Stack mid redzone: f2 - Stack right redzone: f3 - Stack after return: f5 - Stack use after scope: f8 - Global redzone: f9 - Global init order: f6 - Poisoned by user: f7 - Container overflow: fc - Array cookie: ac - Intra object redzone: bb - ASan internal: fe - Left alloca redzone: ca - Right alloca redzone: cb - Shadow gap: cc -==39249==ABORTING -``` - -# ControlFlowIntegrity - -The LLVM Control Flow Integrity (CFI) support in the Rust compiler initially -provides forward-edge control flow protection for Rust-compiled code only by -aggregating function pointers in groups identified by their number of arguments. - -Forward-edge control flow protection for C or C++ and Rust -compiled code "mixed -binaries" (i.e., for when C or C++ and Rust -compiled code share the same -virtual address space) will be provided in later work by defining and using -compatible type identifiers (see Type metadata in the design document in the -tracking issue [#89653](https://github.com/rust-lang/rust/issues/89653)). - -LLVM CFI can be enabled with -Zsanitizer=cfi and requires LTO (i.e., -Clto). - -## Example - -```text -#![feature(naked_functions)] - -use std::arch::asm; -use std::mem; - -fn add_one(x: i32) -> i32 { - x + 1 -} - -#[naked] -pub extern "C" fn add_two(x: i32) { - // x + 2 preceeded by a landing pad/nop block - unsafe { - asm!( - " - nop - nop - nop - nop - nop - nop - nop - nop - nop - lea rax, [rdi+2] - ret - ", - options(noreturn) - ); - } -} - -fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { - f(arg) + f(arg) -} - -fn main() { - let answer = do_twice(add_one, 5); - - println!("The answer is: {}", answer); - - println!("With CFI enabled, you should not see the next answer"); - let f: fn(i32) -> i32 = unsafe { - // Offsets 0-8 make it land in the landing pad/nop block, and offsets 1-8 are - // invalid branch/call destinations (i.e., within the body of the function). - mem::transmute::<*const u8, fn(i32) -> i32>((add_two as *const u8).offset(5)) - }; - let next_answer = do_twice(f, 5); - - println!("The next answer is: {}", next_answer); -} -``` -Fig. 1. Modified example from the [Advanced Functions and -Closures][rust-book-ch19-05] chapter of the [The Rust Programming -Language][rust-book] book. - -[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged) - -```shell -$ rustc rust_cfi.rs -o rust_cfi -$ ./rust_cfi -The answer is: 12 -With CFI enabled, you should not see the next answer -The next answer is: 14 -$ -``` -Fig. 2. Build and execution of the modified example with LLVM CFI disabled. - -[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged) - -```shell -$ rustc -Clto -Zsanitizer=cfi rust_cfi.rs -o rust_cfi -$ ./rust_cfi -The answer is: 12 -With CFI enabled, you should not see the next answer -Illegal instruction -$ -``` -Fig. 3. Build and execution of the modified example with LLVM CFI enabled. - -When LLVM CFI is enabled, if there are any attempts to change/hijack control -flow using an indirect branch/call to an invalid destination, the execution is -terminated (see Fig. 3). - -```rust -use std::mem; - -fn add_one(x: i32) -> i32 { - x + 1 -} - -fn add_two(x: i32, _y: i32) -> i32 { - x + 2 -} - -fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { - f(arg) + f(arg) -} - -fn main() { - let answer = do_twice(add_one, 5); - - println!("The answer is: {}", answer); - - println!("With CFI enabled, you should not see the next answer"); - let f: fn(i32) -> i32 = - unsafe { mem::transmute::<*const u8, fn(i32) -> i32>(add_two as *const u8) }; - let next_answer = do_twice(f, 5); - - println!("The next answer is: {}", next_answer); -} -``` -Fig. 4. Another modified example from the [Advanced Functions and -Closures][rust-book-ch19-05] chapter of the [The Rust Programming -Language][rust-book] book. - -[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged) - -```shell -$ rustc rust_cfi.rs -o rust_cfi -$ ./rust_cfi -The answer is: 12 -With CFI enabled, you should not see the next answer -The next answer is: 14 -$ -``` -Fig. 5. Build and execution of the modified example with LLVM CFI disabled. - -[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged) - -```shell -$ rustc -Clto -Zsanitizer=cfi rust_cfi.rs -o rust_cfi -$ ./rust_cfi -The answer is: 12 -With CFI enabled, you should not see the next answer -Illegal instruction -$ -``` -Fig. 6. Build and execution of the modified example with LLVM CFI enabled. - -When LLVM CFI is enabled, if there are any attempts to change/hijack control -flow using an indirect branch/call to a function with different number of -arguments than intended/passed in the call/branch site, the execution is also -terminated (see Fig. 6). - -Forward-edge control flow protection not only by aggregating function pointers -in groups identified by their number of arguments, but also their argument -types, will also be provided in later work by defining and using compatible type -identifiers (see Type metadata in the design document in the tracking -issue [#89653](https://github.com/rust-lang/rust/issues/89653)). - -[rust-book-ch19-05]: https://doc.rust-lang.org/book/ch19-05-advanced-functions-and-closures.html -[rust-book]: https://doc.rust-lang.org/book/title-page.html - -# HWAddressSanitizer - -HWAddressSanitizer is a newer variant of AddressSanitizer that consumes much -less memory. - -HWAddressSanitizer is supported on the following targets: - -* `aarch64-linux-android` -* `aarch64-unknown-linux-gnu` - -HWAddressSanitizer requires `tagged-globals` target feature to instrument -globals. To enable this target feature compile with `-C -target-feature=+tagged-globals` - -## Example - -Heap buffer overflow: - -```rust -fn main() { - let xs = vec![0, 1, 2, 3]; - let _y = unsafe { *xs.as_ptr().offset(4) }; -} -``` - -```shell -$ rustc main.rs -Zsanitizer=hwaddress -C target-feature=+tagged-globals -C -linker=aarch64-linux-gnu-gcc -C link-arg=-fuse-ld=lld --target -aarch64-unknown-linux-gnu -``` - -```shell -$ ./main -==241==ERROR: HWAddressSanitizer: tag-mismatch on address 0xefdeffff0050 at pc 0xaaaae0ae4a98 -READ of size 4 at 0xefdeffff0050 tags: 2c/00 (ptr/mem) in thread T0 - #0 0xaaaae0ae4a94 (/.../main+0x54a94) - ... - -[0xefdeffff0040,0xefdeffff0060) is a small allocated heap chunk; size: 32 offset: 16 -0xefdeffff0050 is located 0 bytes to the right of 16-byte region [0xefdeffff0040,0xefdeffff0050) -allocated here: - #0 0xaaaae0acb80c (/.../main+0x3b80c) - ... - -Thread: T0 0xeffe00002000 stack: [0xffffc28ad000,0xffffc30ad000) sz: 8388608 tls: [0xffffaa10a020,0xffffaa10a7d0) -Memory tags around the buggy address (one tag corresponds to 16 bytes): - 0xfefcefffef80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0xfefcefffef90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0xfefcefffefa0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0xfefcefffefb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0xfefcefffefc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0xfefcefffefd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0xfefcefffefe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0xfefcefffeff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -=>0xfefceffff000: d7 d7 05 00 2c [00] 00 00 00 00 00 00 00 00 00 00 - 0xfefceffff010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0xfefceffff020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0xfefceffff030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0xfefceffff040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0xfefceffff050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0xfefceffff060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0xfefceffff070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 0xfefceffff080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Tags for short granules around the buggy address (one tag corresponds to 16 bytes): - 0xfefcefffeff0: .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. -=>0xfefceffff000: .. .. 8c .. .. [..] .. .. .. .. .. .. .. .. .. .. - 0xfefceffff010: .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. -See https://clang.llvm.org/docs/HardwareAssistedAddressSanitizerDesign.html#short-granules for a description of short granule tags -Registers where the failure occurred (pc 0xaaaae0ae4a98): - x0 2c00efdeffff0050 x1 0000000000000004 x2 0000000000000004 x3 0000000000000000 - x4 0000fffefc30ac37 x5 000000000000005d x6 00000ffffc30ac37 x7 0000efff00000000 - x8 2c00efdeffff0050 x9 0200efff00000000 x10 0000000000000000 x11 0200efff00000000 - x12 0200effe00000310 x13 0200effe00000310 x14 0000000000000008 x15 5d00ffffc30ac360 - x16 0000aaaae0ad062c x17 0000000000000003 x18 0000000000000001 x19 0000ffffc30ac658 - x20 4e00ffffc30ac6e0 x21 0000aaaae0ac5e10 x22 0000000000000000 x23 0000000000000000 - x24 0000000000000000 x25 0000000000000000 x26 0000000000000000 x27 0000000000000000 - x28 0000000000000000 x29 0000ffffc30ac5a0 x30 0000aaaae0ae4a98 -SUMMARY: HWAddressSanitizer: tag-mismatch (/.../main+0x54a94) -``` - -# LeakSanitizer - -LeakSanitizer is run-time memory leak detector. - -LeakSanitizer is supported on the following targets: - -* `aarch64-apple-darwin` -* `aarch64-unknown-linux-gnu` -* `x86_64-apple-darwin` -* `x86_64-unknown-linux-gnu` - -# MemorySanitizer - -MemorySanitizer is detector of uninitialized reads. - -MemorySanitizer is supported on the following targets: - -* `aarch64-unknown-linux-gnu` -* `x86_64-unknown-freebsd` -* `x86_64-unknown-linux-gnu` - -MemorySanitizer requires all program code to be instrumented. C/C++ dependencies -need to be recompiled using Clang with `-fsanitize=memory` option. Failing to -achieve that will result in false positive reports. - -## Example - -Detecting the use of uninitialized memory. The `-Zbuild-std` flag rebuilds and -instruments the standard library, and is strictly necessary for the correct -operation of the tool. The `-Zsanitizer-memory-track-origins` enables tracking -of the origins of uninitialized memory: - -```rust -use std::mem::MaybeUninit; - -fn main() { - unsafe { - let a = MaybeUninit::<[usize; 4]>::uninit(); - let a = a.assume_init(); - println!("{}", a[2]); - } -} -``` - -```shell -$ export \ - RUSTFLAGS='-Zsanitizer=memory -Zsanitizer-memory-track-origins' \ - RUSTDOCFLAGS='-Zsanitizer=memory -Zsanitizer-memory-track-origins' -$ cargo clean -$ cargo run -Zbuild-std --target x86_64-unknown-linux-gnu -==9416==WARNING: MemorySanitizer: use-of-uninitialized-value - #0 0x560c04f7488a in core::fmt::num::imp::fmt_u64::haa293b0b098501ca $RUST/build/x86_64-unknown-linux-gnu/stage1/lib/rustlib/src/rust/src/libcore/fmt/num.rs:202:16 -... - Uninitialized value was stored to memory at - #0 0x560c04ae898a in __msan_memcpy.part.0 $RUST/src/llvm-project/compiler-rt/lib/msan/msan_interceptors.cc:1558:3 - #1 0x560c04b2bf88 in memory::main::hd2333c1899d997f5 $CWD/src/main.rs:6:16 - - Uninitialized value was created by an allocation of 'a' in the stack frame of function '_ZN6memory4main17hd2333c1899d997f5E' - #0 0x560c04b2bc50 in memory::main::hd2333c1899d997f5 $CWD/src/main.rs:3 -``` - -# ThreadSanitizer - -ThreadSanitizer is a data race detection tool. It is supported on the following -targets: - -* `aarch64-apple-darwin` -* `aarch64-unknown-linux-gnu` -* `x86_64-apple-darwin` -* `x86_64-unknown-freebsd` -* `x86_64-unknown-linux-gnu` - -To work correctly ThreadSanitizer needs to be "aware" of all synchronization -operations in a program. It generally achieves that through combination of -library interception (for example synchronization performed through -`pthread_mutex_lock` / `pthread_mutex_unlock`) and compile time instrumentation -(e.g. atomic operations). Using it without instrumenting all the program code -can lead to false positive reports. - -ThreadSanitizer does not support atomic fences `std::sync::atomic::fence`, -nor synchronization performed using inline assembly code. - -## Example - -```rust -static mut A: usize = 0; - -fn main() { - let t = std::thread::spawn(|| { - unsafe { A += 1 }; - }); - unsafe { A += 1 }; - - t.join().unwrap(); -} -``` - -```shell -$ export RUSTFLAGS=-Zsanitizer=thread RUSTDOCFLAGS=-Zsanitizer=thread -$ cargo run -Zbuild-std --target x86_64-unknown-linux-gnu -================== -WARNING: ThreadSanitizer: data race (pid=10574) - Read of size 8 at 0x5632dfe3d030 by thread T1: - #0 example::main::_$u7b$$u7b$closure$u7d$$u7d$::h23f64b0b2f8c9484 ../src/main.rs:5:18 (example+0x86cec) - ... - - Previous write of size 8 at 0x5632dfe3d030 by main thread: - #0 example::main::h628ffc6626ed85b2 /.../src/main.rs:7:14 (example+0x868c8) - ... - #11 main (example+0x86a1a) - - Location is global 'example::A::h43ac149ddf992709' of size 8 at 0x5632dfe3d030 (example+0x000000bd9030) -``` - -# Instrumentation of external dependencies and std - -The sanitizers to varying degrees work correctly with partially instrumented -code. On the one extreme is LeakSanitizer that doesn't use any compile time -instrumentation, on the other is MemorySanitizer that requires that all program -code to be instrumented (failing to achieve that will inevitably result in -false positives). - -It is strongly recommended to combine sanitizers with recompiled and -instrumented standard library, for example using [cargo `-Zbuild-std` -functionality][build-std]. - -[build-std]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std - -# Build scripts and procedural macros - -Use of sanitizers together with build scripts and procedural macros is -technically possible, but in almost all cases it would be best avoided. This -is especially true for procedural macros which would require an instrumented -version of rustc. - -In more practical terms when using cargo always remember to pass `--target` -flag, so that rustflags will not be applied to build scripts and procedural -macros. - -# Symbolizing the Reports - -Sanitizers produce symbolized stacktraces when llvm-symbolizer binary is in `PATH`. - -# Additional Information - -* [Sanitizers project page](https://github.com/google/sanitizers/wiki/) -* [AddressSanitizer in Clang][clang-asan] -* [ControlFlowIntegrity in Clang][clang-cfi] -* [HWAddressSanitizer in Clang][clang-hwasan] -* [LeakSanitizer in Clang][clang-lsan] -* [MemorySanitizer in Clang][clang-msan] -* [ThreadSanitizer in Clang][clang-tsan] - -[clang-asan]: https://clang.llvm.org/docs/AddressSanitizer.html -[clang-cfi]: https://clang.llvm.org/docs/ControlFlowIntegrity.html -[clang-hwasan]: https://clang.llvm.org/docs/HardwareAssistedAddressSanitizerDesign.html -[clang-lsan]: https://clang.llvm.org/docs/LeakSanitizer.html -[clang-msan]: https://clang.llvm.org/docs/MemorySanitizer.html -[clang-tsan]: https://clang.llvm.org/docs/ThreadSanitizer.html diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/self-profile-events.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/self-profile-events.md deleted file mode 100644 index 3ce18743be50..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/self-profile-events.md +++ /dev/null @@ -1,74 +0,0 @@ -# `self-profile-events` - ---------------------- - -The `-Zself-profile-events` compiler flag controls what events are recorded by the self-profiler when it is enabled via the `-Zself-profile` flag. - -This flag takes a comma delimited list of event types to record. - -For example: - -```console -$ rustc -Zself-profile -Zself-profile-events=default,args -``` - -## Event types - -- `query-provider` - - Traces each query used internally by the compiler. - -- `generic-activity` - - Traces other parts of the compiler not covered by the query system. - -- `query-cache-hit` - - Adds tracing information that records when the in-memory query cache is "hit" and does not need to re-execute a query which has been cached. - - Disabled by default because this significantly increases the trace file size. - -- `query-blocked` - - Tracks time that a query tries to run but is blocked waiting on another thread executing the same query to finish executing. - - Query blocking only occurs when the compiler is built with parallel mode support. - -- `incr-cache-load` - - Tracks time that is spent loading and deserializing query results from the incremental compilation on-disk cache. - -- `query-keys` - - Adds a serialized representation of each query's query key to the tracing data. - - Disabled by default because this significantly increases the trace file size. - -- `function-args` - - Adds additional tracing data to some `generic-activity` events. - - Disabled by default for parity with `query-keys`. - -- `llvm` - - Adds tracing information about LLVM passes and codegeneration. - - Disabled by default because this only works when `-Znew-llvm-pass-manager` is enabled. - -## Event synonyms - -- `none` - - Disables all events. - Equivalent to the self-profiler being disabled. - -- `default` - - The default set of events which stikes a balance between providing detailed tracing data and adding additional overhead to the compilation. - -- `args` - - Equivalent to `query-keys` and `function-args`. - -- `all` - - Enables all events. - -## Examples - -Enable the profiler and capture the default set of events (both invocations are equivalent): - -```console -$ rustc -Zself-profile -$ rustc -Zself-profile -Zself-profile-events=default -``` - -Enable the profiler and capture the default events and their arguments: - -```console -$ rustc -Zself-profile -Zself-profile-events=default,args -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/self-profile.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/self-profile.md deleted file mode 100644 index 7305141a4271..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/self-profile.md +++ /dev/null @@ -1,47 +0,0 @@ -# `self-profile` - --------------------- - -The `-Zself-profile` compiler flag enables rustc's internal profiler. -When enabled, the compiler will output three binary files in the specified directory (or the current working directory if no directory is specified). -These files can be analyzed by using the tools in the [`measureme`] repository. - -To control the data recorded in the trace files, use the `-Zself-profile-events` flag. - -For example: - -First, run a compilation session and provide the `-Zself-profile` flag: - -```console -$ rustc --crate-name foo -Zself-profile -``` - -This will generate three files in the working directory such as: - -- `foo-1234.events` -- `foo-1234.string_data` -- `foo-1234.string_index` - -Where `foo` is the name of the crate and `1234` is the process id of the rustc process. - -To get a summary of where the compiler is spending its time: - -```console -$ ../measureme/target/release/summarize summarize foo-1234 -``` - -To generate a flamegraph of the same data: - -```console -$ ../measureme/target/release/inferno foo-1234 -``` - -To dump the event data in a Chromium-profiler compatible format: - -```console -$ ../measureme/target/release/crox foo-1234 -``` - -For more information, consult the [`measureme`] documentation. - -[`measureme`]: https://github.com/rust-lang/measureme.git diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/source-based-code-coverage.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/source-based-code-coverage.md deleted file mode 100644 index cb65978e0a07..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/source-based-code-coverage.md +++ /dev/null @@ -1,5 +0,0 @@ -# `source-based-code-coverage` - -See compiler flag [`-Z instrument-coverage`]. - -[`-z instrument-coverage`]: ./instrument-coverage.html diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/src-hash-algorithm.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/src-hash-algorithm.md deleted file mode 100644 index ff776741b212..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/src-hash-algorithm.md +++ /dev/null @@ -1,11 +0,0 @@ -# `src-hash-algorithm` - -The tracking issue for this feature is: [#70401](https://github.com/rust-lang/rust/issues/70401). - ------------------------- - -The `-Z src-hash-algorithm` compiler flag controls which algorithm is used when hashing each source file. The hash is stored in the debug info and can be used by a debugger to verify the source code matches the executable. - -Supported hash algorithms are: `md5`, `sha1`, and `sha256`. Note that not all hash algorithms are supported by all debug info formats. - -By default, the compiler chooses the hash algorithm based on the target specification. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/temps-dir.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/temps-dir.md deleted file mode 100644 index e25011f71197..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/temps-dir.md +++ /dev/null @@ -1,10 +0,0 @@ -# `temps-dir` - --------------------- - -The `-Ztemps-dir` compiler flag specifies the directory to write the -intermediate files in. If not set, the output directory is used. This option is -useful if you are running more than one instance of `rustc` (e.g. with different -`--crate-type` settings), and you need to make sure they are not overwriting -each other's intermediate files. No files are kept unless `-C save-temps=yes` is -also set. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/tls-model.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/tls-model.md deleted file mode 100644 index 8b19e785c6a5..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/tls-model.md +++ /dev/null @@ -1,25 +0,0 @@ -# `tls_model` - -The tracking issue for this feature is: None. - ------------------------- - -Option `-Z tls-model` controls [TLS model](https://www.akkadia.org/drepper/tls.pdf) used to -generate code for accessing `#[thread_local]` `static` items. - -Supported values for this option are: - -- `global-dynamic` - General Dynamic TLS Model (alternatively called Global Dynamic) is the most -general option usable in all circumstances, even if the TLS data is defined in a shared library -loaded at runtime and is accessed from code outside of that library. -This is the default for most targets. -- `local-dynamic` - model usable if the TLS data is only accessed from the shared library or -executable it is defined in. The TLS data may be in a library loaded after startup (via `dlopen`). -- `initial-exec` - model usable if the TLS data is defined in the executable or in a shared library -loaded at program startup. -The TLS data must not be in a library loaded after startup (via `dlopen`). -- `local-exec` - model usable only if the TLS data is defined directly in the executable, -but not in a shared library, and is accessed only from that executable. - -`rustc` and LLVM may use a more optimized model than specified if they know that we are producing -an executable rather than a library, or that the `static` item is private enough. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/unsound-mir-opts.md b/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/unsound-mir-opts.md deleted file mode 100644 index 8e46e227c25b..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/compiler-flags/unsound-mir-opts.md +++ /dev/null @@ -1,8 +0,0 @@ -# `unsound-mir-opts` - --------------------- - -The `-Zunsound-mir-opts` compiler flag enables [MIR optimization passes] which can cause unsound behavior. -This flag should only be used by MIR optimization tests in the rustc test suite. - -[MIR optimization passes]: https://rustc-dev-guide.rust-lang.org/mir/optimizations.html diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features.md deleted file mode 100644 index a27514df97d6..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features.md +++ /dev/null @@ -1 +0,0 @@ -# Language features diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/abi-c-cmse-nonsecure-call.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/abi-c-cmse-nonsecure-call.md deleted file mode 100644 index 79a177cb28b1..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/abi-c-cmse-nonsecure-call.md +++ /dev/null @@ -1,88 +0,0 @@ -# `abi_c_cmse_nonsecure_call` - -The tracking issue for this feature is: [#81391] - -[#81391]: https://github.com/rust-lang/rust/issues/81391 - ------------------------- - -The [TrustZone-M -feature](https://developer.arm.com/documentation/100690/latest/) is available -for targets with the Armv8-M architecture profile (`thumbv8m` in their target -name). -LLVM, the Rust compiler and the linker are providing -[support](https://developer.arm.com/documentation/ecm0359818/latest/) for the -TrustZone-M feature. - -One of the things provided, with this unstable feature, is the -`C-cmse-nonsecure-call` function ABI. This ABI is used on function pointers to -non-secure code to mark a non-secure function call (see [section -5.5](https://developer.arm.com/documentation/ecm0359818/latest/) for details). - -With this ABI, the compiler will do the following to perform the call: -* save registers needed after the call to Secure memory -* clear all registers that might contain confidential information -* clear the Least Significant Bit of the function address -* branches using the BLXNS instruction - -To avoid using the non-secure stack, the compiler will constrain the number and -type of parameters/return value. - -The `extern "C-cmse-nonsecure-call"` ABI is otherwise equivalent to the -`extern "C"` ABI. - - - -``` rust,ignore -#![no_std] -#![feature(abi_c_cmse_nonsecure_call)] - -#[no_mangle] -pub fn call_nonsecure_function(addr: usize) -> u32 { - let non_secure_function = - unsafe { core::mem::transmute:: u32>(addr) }; - non_secure_function() -} -``` - -``` text -$ rustc --emit asm --crate-type lib --target thumbv8m.main-none-eabi function.rs - -call_nonsecure_function: - .fnstart - .save {r7, lr} - push {r7, lr} - .setfp r7, sp - mov r7, sp - .pad #16 - sub sp, #16 - str r0, [sp, #12] - ldr r0, [sp, #12] - str r0, [sp, #8] - b .LBB0_1 -.LBB0_1: - ldr r0, [sp, #8] - push.w {r4, r5, r6, r7, r8, r9, r10, r11} - bic r0, r0, #1 - mov r1, r0 - mov r2, r0 - mov r3, r0 - mov r4, r0 - mov r5, r0 - mov r6, r0 - mov r7, r0 - mov r8, r0 - mov r9, r0 - mov r10, r0 - mov r11, r0 - mov r12, r0 - msr apsr_nzcvq, r0 - blxns r0 - pop.w {r4, r5, r6, r7, r8, r9, r10, r11} - str r0, [sp, #4] - b .LBB0_2 -.LBB0_2: - ldr r0, [sp, #4] - add sp, #16 - pop {r7, pc} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/abi-msp430-interrupt.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/abi-msp430-interrupt.md deleted file mode 100644 index b10bc41cb143..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/abi-msp430-interrupt.md +++ /dev/null @@ -1,42 +0,0 @@ -# `abi_msp430_interrupt` - -The tracking issue for this feature is: [#38487] - -[#38487]: https://github.com/rust-lang/rust/issues/38487 - ------------------------- - -In the MSP430 architecture, interrupt handlers have a special calling -convention. You can use the `"msp430-interrupt"` ABI to make the compiler apply -the right calling convention to the interrupt handlers you define. - - - -``` rust,ignore -#![feature(abi_msp430_interrupt)] -#![no_std] - -// Place the interrupt handler at the appropriate memory address -// (Alternatively, you can use `#[used]` and remove `pub` and `#[no_mangle]`) -#[link_section = "__interrupt_vector_10"] -#[no_mangle] -pub static TIM0_VECTOR: extern "msp430-interrupt" fn() = tim0; - -// The interrupt handler -extern "msp430-interrupt" fn tim0() { - // .. -} -``` - -``` text -$ msp430-elf-objdump -CD ./target/msp430/release/app -Disassembly of section __interrupt_vector_10: - -0000fff2 : - fff2: 00 c0 interrupt service routine at 0xc000 - -Disassembly of section .text: - -0000c000 : - c000: 00 13 reti -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/abi-ptx.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/abi-ptx.md deleted file mode 100644 index 0ded3ceeaef2..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/abi-ptx.md +++ /dev/null @@ -1,60 +0,0 @@ -# `abi_ptx` - -The tracking issue for this feature is: [#38788] - -[#38788]: https://github.com/rust-lang/rust/issues/38788 - ------------------------- - -When emitting PTX code, all vanilla Rust functions (`fn`) get translated to -"device" functions. These functions are *not* callable from the host via the -CUDA API so a crate with only device functions is not too useful! - -OTOH, "global" functions *can* be called by the host; you can think of them -as the real public API of your crate. To produce a global function use the -`"ptx-kernel"` ABI. - - - -``` rust,ignore -#![feature(abi_ptx)] -#![no_std] - -pub unsafe extern "ptx-kernel" fn global_function() { - device_function(); -} - -pub fn device_function() { - // .. -} -``` - -``` text -$ xargo rustc --target nvptx64-nvidia-cuda --release -- --emit=asm - -$ cat $(find -name '*.s') -// -// Generated by LLVM NVPTX Back-End -// - -.version 3.2 -.target sm_20 -.address_size 64 - - // .globl _ZN6kernel15global_function17h46111ebe6516b382E - -.visible .entry _ZN6kernel15global_function17h46111ebe6516b382E() -{ - - - ret; -} - - // .globl _ZN6kernel15device_function17hd6a0e4993bbf3f78E -.visible .func _ZN6kernel15device_function17hd6a0e4993bbf3f78E() -{ - - - ret; -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/abi-thiscall.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/abi-thiscall.md deleted file mode 100644 index 73bc6eacf42c..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/abi-thiscall.md +++ /dev/null @@ -1,12 +0,0 @@ -# `abi_thiscall` - -The tracking issue for this feature is: [#42202] - -[#42202]: https://github.com/rust-lang/rust/issues/42202 - ------------------------- - -The MSVC ABI on x86 Windows uses the `thiscall` calling convention for C++ -instance methods by default; it is identical to the usual (C) calling -convention on x86 Windows except that the first parameter of the method, -the `this` pointer, is passed in the ECX register. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/allocator-internals.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/allocator-internals.md deleted file mode 100644 index 2023d758fe3d..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/allocator-internals.md +++ /dev/null @@ -1,7 +0,0 @@ -# `allocator_internals` - -This feature does not have a tracking issue, it is an unstable implementation -detail of the `global_allocator` feature not intended for use outside the -compiler. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/arbitrary-enum-discriminant.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/arbitrary-enum-discriminant.md deleted file mode 100644 index e0bb782270e2..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/arbitrary-enum-discriminant.md +++ /dev/null @@ -1,37 +0,0 @@ -# `arbitrary_enum_discriminant` - -The tracking issue for this feature is: [#60553] - -[#60553]: https://github.com/rust-lang/rust/issues/60553 - ------------------------- - -The `arbitrary_enum_discriminant` feature permits tuple-like and -struct-like enum variants with `#[repr()]` to have explicit discriminants. - -## Examples - -```rust -#![feature(arbitrary_enum_discriminant)] - -#[allow(dead_code)] -#[repr(u8)] -enum Enum { - Unit = 3, - Tuple(u16) = 2, - Struct { - a: u8, - b: u16, - } = 1, -} - -impl Enum { - fn tag(&self) -> u8 { - unsafe { *(self as *const Self as *const u8) } - } -} - -assert_eq!(3, Enum::Unit.tag()); -assert_eq!(2, Enum::Tuple(5).tag()); -assert_eq!(1, Enum::Struct{a: 7, b: 11}.tag()); -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/asm-const.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/asm-const.md deleted file mode 100644 index 1063c23b6dfb..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/asm-const.md +++ /dev/null @@ -1,11 +0,0 @@ -# `asm_const` - -The tracking issue for this feature is: [#72016] - -[#72016]: https://github.com/rust-lang/rust/issues/72016 - ------------------------- - -This feature adds a `const ` operand type to `asm!` and `global_asm!`. -- `` must be an integer constant expression. -- The value of the expression is formatted as a string and substituted directly into the asm template string. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/asm-experimental-arch.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/asm-experimental-arch.md deleted file mode 100644 index ec97eaa8b2b5..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/asm-experimental-arch.md +++ /dev/null @@ -1,117 +0,0 @@ -# `asm_experimental_arch` - -The tracking issue for this feature is: [#72016] - -[#72016]: https://github.com/rust-lang/rust/issues/72016 - ------------------------- - -This feature tracks `asm!` and `global_asm!` support for the following architectures: -- NVPTX -- PowerPC -- Hexagon -- MIPS32r2 and MIPS64r2 -- wasm32 -- BPF -- SPIR-V -- AVR - -## Register classes - -| Architecture | Register class | Registers | LLVM constraint code | -| ------------ | -------------- | ---------------------------------- | -------------------- | -| MIPS | `reg` | `$[2-25]` | `r` | -| MIPS | `freg` | `$f[0-31]` | `f` | -| NVPTX | `reg16` | None\* | `h` | -| NVPTX | `reg32` | None\* | `r` | -| NVPTX | `reg64` | None\* | `l` | -| Hexagon | `reg` | `r[0-28]` | `r` | -| PowerPC | `reg` | `r[0-31]` | `r` | -| PowerPC | `reg_nonzero` | `r[1-31]` | `b` | -| PowerPC | `freg` | `f[0-31]` | `f` | -| PowerPC | `cr` | `cr[0-7]`, `cr` | Only clobbers | -| PowerPC | `xer` | `xer` | Only clobbers | -| wasm32 | `local` | None\* | `r` | -| BPF | `reg` | `r[0-10]` | `r` | -| BPF | `wreg` | `w[0-10]` | `w` | -| AVR | `reg` | `r[2-25]`, `XH`, `XL`, `ZH`, `ZL` | `r` | -| AVR | `reg_upper` | `r[16-25]`, `XH`, `XL`, `ZH`, `ZL` | `d` | -| AVR | `reg_pair` | `r3r2` .. `r25r24`, `X`, `Z` | `r` | -| AVR | `reg_iw` | `r25r24`, `X`, `Z` | `w` | -| AVR | `reg_ptr` | `X`, `Z` | `e` | - -> **Notes**: -> - NVPTX doesn't have a fixed register set, so named registers are not supported. -> -> - WebAssembly doesn't have registers, so named registers are not supported. - -# Register class supported types - -| Architecture | Register class | Target feature | Allowed types | -| ------------ | ------------------------------- | -------------- | --------------------------------------- | -| MIPS32 | `reg` | None | `i8`, `i16`, `i32`, `f32` | -| MIPS32 | `freg` | None | `f32`, `f64` | -| MIPS64 | `reg` | None | `i8`, `i16`, `i32`, `i64`, `f32`, `f64` | -| MIPS64 | `freg` | None | `f32`, `f64` | -| NVPTX | `reg16` | None | `i8`, `i16` | -| NVPTX | `reg32` | None | `i8`, `i16`, `i32`, `f32` | -| NVPTX | `reg64` | None | `i8`, `i16`, `i32`, `f32`, `i64`, `f64` | -| Hexagon | `reg` | None | `i8`, `i16`, `i32`, `f32` | -| PowerPC | `reg` | None | `i8`, `i16`, `i32` | -| PowerPC | `reg_nonzero` | None | `i8`, `i16`, `i32` | -| PowerPC | `freg` | None | `f32`, `f64` | -| PowerPC | `cr` | N/A | Only clobbers | -| PowerPC | `xer` | N/A | Only clobbers | -| wasm32 | `local` | None | `i8` `i16` `i32` `i64` `f32` `f64` | -| BPF | `reg` | None | `i8` `i16` `i32` `i64` | -| BPF | `wreg` | `alu32` | `i8` `i16` `i32` | -| AVR | `reg`, `reg_upper` | None | `i8` | -| AVR | `reg_pair`, `reg_iw`, `reg_ptr` | None | `i16` | - -## Register aliases - -| Architecture | Base register | Aliases | -| ------------ | ------------- | --------- | -| Hexagon | `r29` | `sp` | -| Hexagon | `r30` | `fr` | -| Hexagon | `r31` | `lr` | -| BPF | `r[0-10]` | `w[0-10]` | -| AVR | `XH` | `r27` | -| AVR | `XL` | `r26` | -| AVR | `ZH` | `r31` | -| AVR | `ZL` | `r30` | - -## Unsupported registers - -| Architecture | Unsupported register | Reason | -| ------------ | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| All | `sp` | The stack pointer must be restored to its original value at the end of an asm code block. | -| All | `fr` (Hexagon), `$fp` (MIPS), `Y` (AVR) | The frame pointer cannot be used as an input or output. | -| All | `r19` (Hexagon) | This is used internally by LLVM as a "base pointer" for functions with complex stack frames. | -| MIPS | `$0` or `$zero` | This is a constant zero register which can't be modified. | -| MIPS | `$1` or `$at` | Reserved for assembler. | -| MIPS | `$26`/`$k0`, `$27`/`$k1` | OS-reserved registers. | -| MIPS | `$28`/`$gp` | Global pointer cannot be used as inputs or outputs. | -| MIPS | `$ra` | Return address cannot be used as inputs or outputs. | -| Hexagon | `lr` | This is the link register which cannot be used as an input or output. | -| AVR | `r0`, `r1`, `r1r0` | Due to an issue in LLVM, the `r0` and `r1` registers cannot be used as inputs or outputs. If modified, they must be restored to their original values before the end of the block. | - -## Template modifiers - -| Architecture | Register class | Modifier | Example output | LLVM modifier | -| ------------ | -------------- | -------- | -------------- | ------------- | -| MIPS | `reg` | None | `$2` | None | -| MIPS | `freg` | None | `$f0` | None | -| NVPTX | `reg16` | None | `rs0` | None | -| NVPTX | `reg32` | None | `r0` | None | -| NVPTX | `reg64` | None | `rd0` | None | -| Hexagon | `reg` | None | `r0` | None | -| PowerPC | `reg` | None | `0` | None | -| PowerPC | `reg_nonzero` | None | `3` | `b` | -| PowerPC | `freg` | None | `0` | None | - -# Flags covered by `preserves_flags` - -These flags registers must be restored upon exiting the asm block if the `preserves_flags` option is set: -- AVR - - The status register `SREG`. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/asm-sym.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/asm-sym.md deleted file mode 100644 index 7544e20807e9..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/asm-sym.md +++ /dev/null @@ -1,13 +0,0 @@ -# `asm_sym` - -The tracking issue for this feature is: [#72016] - -[#72016]: https://github.com/rust-lang/rust/issues/72016 - ------------------------- - -This feature adds a `sym ` operand type to `asm!` and `global_asm!`. -- `` must refer to a `fn` or `static`. -- A mangled symbol name referring to the item is substituted into the asm template string. -- The substituted string does not include any modifiers (e.g. GOT, PLT, relocations, etc). -- `` is allowed to point to a `#[thread_local]` static, in which case the asm code can combine the symbol with relocations (e.g. `@plt`, `@TPOFF`) to read from thread-local data. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/asm-unwind.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/asm-unwind.md deleted file mode 100644 index 414193fe8017..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/asm-unwind.md +++ /dev/null @@ -1,9 +0,0 @@ -# `asm_unwind` - -The tracking issue for this feature is: [#72016] - -[#72016]: https://github.com/rust-lang/rust/issues/72016 - ------------------------- - -This feature adds a `may_unwind` option to `asm!` which allows an `asm` block to unwind stack and be part of the stack unwinding process. This option is only supported by the LLVM backend right now. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/auto-traits.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/auto-traits.md deleted file mode 100644 index f967c11fc4d0..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/auto-traits.md +++ /dev/null @@ -1,106 +0,0 @@ -# `auto_traits` - -The tracking issue for this feature is [#13231] - -[#13231]: https://github.com/rust-lang/rust/issues/13231 - ----- - -The `auto_traits` feature gate allows you to define auto traits. - -Auto traits, like [`Send`] or [`Sync`] in the standard library, are marker traits -that are automatically implemented for every type, unless the type, or a type it contains, -has explicitly opted out via a negative impl. (Negative impls are separately controlled -by the `negative_impls` feature.) - -[`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html -[`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html - -```rust,ignore (partial-example) -impl !Trait for Type {} -``` - -Example: - -```rust -#![feature(negative_impls)] -#![feature(auto_traits)] - -auto trait Valid {} - -struct True; -struct False; - -impl !Valid for False {} - -struct MaybeValid(T); - -fn must_be_valid(_t: T) { } - -fn main() { - // works - must_be_valid( MaybeValid(True) ); - - // compiler error - trait bound not satisfied - // must_be_valid( MaybeValid(False) ); -} -``` - -## Automatic trait implementations - -When a type is declared as an `auto trait`, we will automatically -create impls for every struct/enum/union, unless an explicit impl is -provided. These automatic impls contain a where clause for each field -of the form `T: AutoTrait`, where `T` is the type of the field and -`AutoTrait` is the auto trait in question. As an example, consider the -struct `List` and the auto trait `Send`: - -```rust -struct List { - data: T, - next: Option>>, -} -``` - -Presuming that there is no explicit impl of `Send` for `List`, the -compiler will supply an automatic impl of the form: - -```rust -struct List { - data: T, - next: Option>>, -} - -unsafe impl Send for List -where - T: Send, // from the field `data` - Option>>: Send, // from the field `next` -{ } -``` - -Explicit impls may be either positive or negative. They take the form: - -```rust,ignore (partial-example) -impl<...> AutoTrait for StructName<..> { } -impl<...> !AutoTrait for StructName<..> { } -``` - -## Coinduction: Auto traits permit cyclic matching - -Unlike ordinary trait matching, auto traits are **coinductive**. This -means, in short, that cycles which occur in trait matching are -considered ok. As an example, consider the recursive struct `List` -introduced in the previous section. In attempting to determine whether -`List: Send`, we would wind up in a cycle: to apply the impl, we must -show that `Option>: Send`, which will in turn require -`Box: Send` and then finally `List: Send` again. Under ordinary -trait matching, this cycle would be an error, but for an auto trait it -is considered a successful match. - -## Items - -Auto traits cannot have any trait items, such as methods or associated types. This ensures that we can generate default implementations. - -## Supertraits - -Auto traits cannot have supertraits. This is for soundness reasons, as the interaction of coinduction with implied bounds is difficult to reconcile. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/box-patterns.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/box-patterns.md deleted file mode 100644 index bf0819ec920b..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/box-patterns.md +++ /dev/null @@ -1,32 +0,0 @@ -# `box_patterns` - -The tracking issue for this feature is: [#29641] - -[#29641]: https://github.com/rust-lang/rust/issues/29641 - -See also [`box_syntax`](box-syntax.md) - ------------------------- - -Box patterns let you match on `Box`s: - - -```rust -#![feature(box_patterns)] - -fn main() { - let b = Some(Box::new(5)); - match b { - Some(box n) if n < 0 => { - println!("Box contains negative number {}", n); - }, - Some(box n) if n >= 0 => { - println!("Box contains non-negative number {}", n); - }, - None => { - println!("No box"); - }, - _ => unreachable!() - } -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/box-syntax.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/box-syntax.md deleted file mode 100644 index 9569974d22ca..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/box-syntax.md +++ /dev/null @@ -1,22 +0,0 @@ -# `box_syntax` - -The tracking issue for this feature is: [#49733] - -[#49733]: https://github.com/rust-lang/rust/issues/49733 - -See also [`box_patterns`](box-patterns.md) - ------------------------- - -Currently the only stable way to create a `Box` is via the `Box::new` method. -Also it is not possible in stable Rust to destructure a `Box` in a match -pattern. The unstable `box` keyword can be used to create a `Box`. An example -usage would be: - -```rust -#![feature(box_syntax)] - -fn main() { - let b = box 5; -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/c-unwind.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/c-unwind.md deleted file mode 100644 index 2801d9b5e777..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/c-unwind.md +++ /dev/null @@ -1,15 +0,0 @@ -# `c_unwind` - -The tracking issue for this feature is: [#74990] - -[#74990]: https://github.com/rust-lang/rust/issues/74990 - ------------------------- - -Introduces four new ABI strings: "C-unwind", "stdcall-unwind", -"thiscall-unwind", and "system-unwind". These enable unwinding from other -languages (such as C++) into Rust frames and from Rust into other languages. - -See [RFC 2945] for more information. - -[RFC 2945]: https://github.com/rust-lang/rfcs/blob/master/text/2945-c-unwind-abi.md diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/c-variadic.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/c-variadic.md deleted file mode 100644 index 9e7968d906fb..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/c-variadic.md +++ /dev/null @@ -1,24 +0,0 @@ -# `c_variadic` - -The tracking issue for this feature is: [#44930] - -[#44930]: https://github.com/rust-lang/rust/issues/44930 - ------------------------- - -The `c_variadic` language feature enables C-variadic functions to be -defined in Rust. The may be called both from within Rust and via FFI. - -## Examples - -```rust -#![feature(c_variadic)] - -pub unsafe extern "C" fn add(n: usize, mut args: ...) -> usize { - let mut sum = 0; - for _ in 0..n { - sum += args.arg::(); - } - sum -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/cfg-panic.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/cfg-panic.md deleted file mode 100644 index f5b73128ad6c..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/cfg-panic.md +++ /dev/null @@ -1,38 +0,0 @@ -# `cfg_panic` - -The tracking issue for this feature is: [#77443] - -[#77443]: https://github.com/rust-lang/rust/issues/77443 - ------------------------- - -The `cfg_panic` feature makes it possible to execute different code -depending on the panic strategy. - -Possible values at the moment are `"unwind"` or `"abort"`, although -it is possible that new panic strategies may be added to Rust in the -future. - -## Examples - -```rust -#![feature(cfg_panic)] - -#[cfg(panic = "unwind")] -fn a() { - // ... -} - -#[cfg(not(panic = "unwind"))] -fn a() { - // ... -} - -fn b() { - if cfg!(panic = "abort") { - // ... - } else { - // ... - } -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/cfg-sanitize.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/cfg-sanitize.md deleted file mode 100644 index 3442abf46df8..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/cfg-sanitize.md +++ /dev/null @@ -1,34 +0,0 @@ -# `cfg_sanitize` - -The tracking issue for this feature is: [#39699] - -[#39699]: https://github.com/rust-lang/rust/issues/39699 - ------------------------- - -The `cfg_sanitize` feature makes it possible to execute different code -depending on whether a particular sanitizer is enabled or not. - -## Examples - -```rust -#![feature(cfg_sanitize)] - -#[cfg(sanitize = "thread")] -fn a() { - // ... -} - -#[cfg(not(sanitize = "thread"))] -fn a() { - // ... -} - -fn b() { - if cfg!(sanitize = "leak") { - // ... - } else { - // ... - } -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/cfg-version.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/cfg-version.md deleted file mode 100644 index a6ec42cecba8..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/cfg-version.md +++ /dev/null @@ -1,35 +0,0 @@ -# `cfg_version` - -The tracking issue for this feature is: [#64796] - -[#64796]: https://github.com/rust-lang/rust/issues/64796 - ------------------------- - -The `cfg_version` feature makes it possible to execute different code -depending on the compiler version. It will return true if the compiler -version is greater than or equal to the specified version. - -## Examples - -```rust -#![feature(cfg_version)] - -#[cfg(version("1.42"))] // 1.42 and above -fn a() { - // ... -} - -#[cfg(not(version("1.42")))] // 1.41 and below -fn a() { - // ... -} - -fn b() { - if cfg!(version("1.42")) { - // ... - } else { - // ... - } -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/closure-track-caller.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/closure-track-caller.md deleted file mode 100644 index c948810d3e5a..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/closure-track-caller.md +++ /dev/null @@ -1,12 +0,0 @@ -# `closure_track_caller` - -The tracking issue for this feature is: [#87417] - -[#87417]: https://github.com/rust-lang/rust/issues/87417 - ------------------------- - -Allows using the `#[track_caller]` attribute on closures and generators. -Calls made to the closure or generator will have caller information -available through `std::panic::Location::caller()`, just like using -`#[track_caller]` on a function. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/cmse-nonsecure-entry.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/cmse-nonsecure-entry.md deleted file mode 100644 index 338fbc4b2bfc..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/cmse-nonsecure-entry.md +++ /dev/null @@ -1,81 +0,0 @@ -# `cmse_nonsecure_entry` - -The tracking issue for this feature is: [#75835] - -[#75835]: https://github.com/rust-lang/rust/issues/75835 - ------------------------- - -The [TrustZone-M -feature](https://developer.arm.com/documentation/100690/latest/) is available -for targets with the Armv8-M architecture profile (`thumbv8m` in their target -name). -LLVM, the Rust compiler and the linker are providing -[support](https://developer.arm.com/documentation/ecm0359818/latest/) for the -TrustZone-M feature. - -One of the things provided, with this unstable feature, is the -`cmse_nonsecure_entry` attribute. This attribute marks a Secure function as an -entry function (see [section -5.4](https://developer.arm.com/documentation/ecm0359818/latest/) for details). -With this attribute, the compiler will do the following: -* add a special symbol on the function which is the `__acle_se_` prefix and the - standard function name -* constrain the number of parameters to avoid using the Non-Secure stack -* before returning from the function, clear registers that might contain Secure - information -* use the `BXNS` instruction to return - -Because the stack can not be used to pass parameters, there will be compilation -errors if: -* the total size of all parameters is too big (for example more than four 32 - bits integers) -* the entry function is not using a C ABI - -The special symbol `__acle_se_` will be used by the linker to generate a secure -gateway veneer. - - - -``` rust,ignore -#![feature(cmse_nonsecure_entry)] - -#[no_mangle] -#[cmse_nonsecure_entry] -pub extern "C" fn entry_function(input: u32) -> u32 { - input + 6 -} -``` - -``` text -$ rustc --emit obj --crate-type lib --target thumbv8m.main-none-eabi function.rs -$ arm-none-eabi-objdump -D function.o - -00000000 : - 0: b580 push {r7, lr} - 2: 466f mov r7, sp - 4: b082 sub sp, #8 - 6: 9001 str r0, [sp, #4] - 8: 1d81 adds r1, r0, #6 - a: 460a mov r2, r1 - c: 4281 cmp r1, r0 - e: 9200 str r2, [sp, #0] - 10: d30b bcc.n 2a - 12: e7ff b.n 14 - 14: 9800 ldr r0, [sp, #0] - 16: b002 add sp, #8 - 18: e8bd 4080 ldmia.w sp!, {r7, lr} - 1c: 4671 mov r1, lr - 1e: 4672 mov r2, lr - 20: 4673 mov r3, lr - 22: 46f4 mov ip, lr - 24: f38e 8800 msr CPSR_f, lr - 28: 4774 bxns lr - 2a: f240 0000 movw r0, #0 - 2e: f2c0 0000 movt r0, #0 - 32: f240 0200 movw r2, #0 - 36: f2c0 0200 movt r2, #0 - 3a: 211c movs r1, #28 - 3c: f7ff fffe bl 0 <_ZN4core9panicking5panic17h5c028258ca2fb3f5E> - 40: defe udf #254 ; 0xfe -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/compiler-builtins.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/compiler-builtins.md deleted file mode 100644 index 52fac575b6e8..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/compiler-builtins.md +++ /dev/null @@ -1,5 +0,0 @@ -# `compiler_builtins` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/const-eval-limit.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/const-eval-limit.md deleted file mode 100644 index df68e83bcac7..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/const-eval-limit.md +++ /dev/null @@ -1,7 +0,0 @@ -# `const_eval_limit` - -The tracking issue for this feature is: [#67217] - -[#67217]: https://github.com/rust-lang/rust/issues/67217 - -The `const_eval_limit` allows someone to limit the evaluation steps the CTFE undertakes to evaluate a `const fn`. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/crate-visibility-modifier.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/crate-visibility-modifier.md deleted file mode 100644 index b59859dd348e..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/crate-visibility-modifier.md +++ /dev/null @@ -1,20 +0,0 @@ -# `crate_visibility_modifier` - -The tracking issue for this feature is: [#53120] - -[#53120]: https://github.com/rust-lang/rust/issues/53120 - ------ - -The `crate_visibility_modifier` feature allows the `crate` keyword to be used -as a visibility modifier synonymous to `pub(crate)`, indicating that a type -(function, _&c._) is to be visible to the entire enclosing crate, but not to -other crates. - -```rust -#![feature(crate_visibility_modifier)] - -crate struct Foo { - bar: usize, -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/custom-test-frameworks.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/custom-test-frameworks.md deleted file mode 100644 index 53ecac9314d7..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/custom-test-frameworks.md +++ /dev/null @@ -1,32 +0,0 @@ -# `custom_test_frameworks` - -The tracking issue for this feature is: [#50297] - -[#50297]: https://github.com/rust-lang/rust/issues/50297 - ------------------------- - -The `custom_test_frameworks` feature allows the use of `#[test_case]` and `#![test_runner]`. -Any function, const, or static can be annotated with `#[test_case]` causing it to be aggregated (like `#[test]`) -and be passed to the test runner determined by the `#![test_runner]` crate attribute. - -```rust -#![feature(custom_test_frameworks)] -#![test_runner(my_runner)] - -fn my_runner(tests: &[&i32]) { - for t in tests { - if **t == 0 { - println!("PASSED"); - } else { - println!("FAILED"); - } - } -} - -#[test_case] -const WILL_PASS: i32 = 0; - -#[test_case] -const WILL_FAIL: i32 = 4; -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/doc-cfg.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/doc-cfg.md deleted file mode 100644 index e75f1aea9922..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/doc-cfg.md +++ /dev/null @@ -1,46 +0,0 @@ -# `doc_cfg` - -The tracking issue for this feature is: [#43781] - ------- - -The `doc_cfg` feature allows an API be documented as only available in some specific platforms. -This attribute has two effects: - -1. In the annotated item's documentation, there will be a message saying "This is supported on - (platform) only". - -2. The item's doc-tests will only run on the specific platform. - -In addition to allowing the use of the `#[doc(cfg)]` attribute, this feature enables the use of a -special conditional compilation flag, `#[cfg(doc)]`, set whenever building documentation on your -crate. - -This feature was introduced as part of PR [#43348] to allow the platform-specific parts of the -standard library be documented. - -```rust -#![feature(doc_cfg)] - -#[cfg(any(windows, doc))] -#[doc(cfg(windows))] -/// The application's icon in the notification area (a.k.a. system tray). -/// -/// # Examples -/// -/// ```no_run -/// extern crate my_awesome_ui_library; -/// use my_awesome_ui_library::current_app; -/// use my_awesome_ui_library::windows::notification; -/// -/// let icon = current_app().get::(); -/// icon.show(); -/// icon.show_message("Hello"); -/// ``` -pub struct Icon { - // ... -} -``` - -[#43781]: https://github.com/rust-lang/rust/issues/43781 -[#43348]: https://github.com/rust-lang/rust/issues/43348 diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/doc-masked.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/doc-masked.md deleted file mode 100644 index 609939bfc22f..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/doc-masked.md +++ /dev/null @@ -1,24 +0,0 @@ -# `doc_masked` - -The tracking issue for this feature is: [#44027] - ------ - -The `doc_masked` feature allows a crate to exclude types from a given crate from appearing in lists -of trait implementations. The specifics of the feature are as follows: - -1. When rustdoc encounters an `extern crate` statement annotated with a `#[doc(masked)]` attribute, - it marks the crate as being masked. - -2. When listing traits a given type implements, rustdoc ensures that traits from masked crates are - not emitted into the documentation. - -3. When listing types that implement a given trait, rustdoc ensures that types from masked crates - are not emitted into the documentation. - -This feature was introduced in PR [#44026] to ensure that compiler-internal and -implementation-specific types and traits were not included in the standard library's documentation. -Such types would introduce broken links into the documentation. - -[#44026]: https://github.com/rust-lang/rust/pull/44026 -[#44027]: https://github.com/rust-lang/rust/pull/44027 diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/doc-notable-trait.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/doc-notable-trait.md deleted file mode 100644 index dc402ed4253a..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/doc-notable-trait.md +++ /dev/null @@ -1,33 +0,0 @@ -# `doc_notable_trait` - -The tracking issue for this feature is: [#45040] - -The `doc_notable_trait` feature allows the use of the `#[doc(notable_trait)]` -attribute, which will display the trait in a "Notable traits" dialog for -functions returning types that implement the trait. For example, this attribute -is applied to the `Iterator`, `Future`, `io::Read`, and `io::Write` traits in -the standard library. - -You can do this on your own traits like so: - -``` -#![feature(doc_notable_trait)] - -#[doc(notable_trait)] -pub trait MyTrait {} - -pub struct MyStruct; -impl MyTrait for MyStruct {} - -/// The docs for this function will have a button that displays a dialog about -/// `MyStruct` implementing `MyTrait`. -pub fn my_fn() -> MyStruct { MyStruct } -``` - -This feature was originally implemented in PR [#45039]. - -See also its documentation in [the rustdoc book][rustdoc-book-notable_trait]. - -[#45040]: https://github.com/rust-lang/rust/issues/45040 -[#45039]: https://github.com/rust-lang/rust/pull/45039 -[rustdoc-book-notable_trait]: ../../rustdoc/unstable-features.html#adding-your-trait-to-the-notable-traits-dialog diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/exclusive-range-pattern.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/exclusive-range-pattern.md deleted file mode 100644 index d26512703f49..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/exclusive-range-pattern.md +++ /dev/null @@ -1,26 +0,0 @@ -# `exclusive_range_pattern` - -The tracking issue for this feature is: [#37854]. - - -[#67264]: https://github.com/rust-lang/rust/issues/67264 -[#37854]: https://github.com/rust-lang/rust/issues/37854 ------ - -The `exclusive_range_pattern` feature allows non-inclusive range -patterns (`0..10`) to be used in appropriate pattern matching -contexts. It also can be combined with `#![feature(half_open_range_patterns]` -to be able to use RangeTo patterns (`..10`). - -It also enabled RangeFrom patterns but that has since been -stabilized. - -```rust -#![feature(exclusive_range_pattern)] - let x = 5; - match x { - 0..10 => println!("single digit"), - 10 => println!("ten isn't part of the above range"), - _ => println!("nor is everything else.") - } -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/explicit-generic-args-with-impl-trait.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/explicit-generic-args-with-impl-trait.md deleted file mode 100644 index 479571d85fe0..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/explicit-generic-args-with-impl-trait.md +++ /dev/null @@ -1,53 +0,0 @@ -# `explicit_generic_args_with_impl_trait` - -The tracking issue for this feature is: [#83701] - -[#83701]: https://github.com/rust-lang/rust/issues/83701 - ------------------------- - -The `explicit_generic_args_with_impl_trait` feature gate lets you specify generic arguments even -when `impl Trait` is used in argument position. - -A simple example is: - -```rust -#![feature(explicit_generic_args_with_impl_trait)] - -fn foo(_f: impl AsRef) {} - -fn main() { - foo::("".to_string()); -} -``` - -This is currently rejected: - -```text -error[E0632]: cannot provide explicit generic arguments when `impl Trait` is used in argument position - --> src/main.rs:6:11 - | -6 | foo::("".to_string()); - | ^^^ explicit generic argument not allowed - -``` - -However it would compile if `explicit_generic_args_with_impl_trait` is enabled. - -Note that the synthetic type parameters from `impl Trait` are still implicit and you -cannot explicitly specify these: - -```rust,compile_fail -#![feature(explicit_generic_args_with_impl_trait)] - -fn foo(_f: impl AsRef) {} -fn bar>(_f: F) {} - -fn main() { - bar::("".to_string()); // Okay - bar::("".to_string()); // Okay - - foo::("".to_string()); // Okay - foo::("".to_string()); // Error, you cannot specify `impl Trait` explicitly -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/ffi-const.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/ffi-const.md deleted file mode 100644 index 24a304437542..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/ffi-const.md +++ /dev/null @@ -1,52 +0,0 @@ -# `ffi_const` - -The tracking issue for this feature is: [#58328] - ------- - -The `#[ffi_const]` attribute applies clang's `const` attribute to foreign -functions declarations. - -That is, `#[ffi_const]` functions shall have no effects except for its return -value, which can only depend on the values of the function parameters, and is -not affected by changes to the observable state of the program. - -Applying the `#[ffi_const]` attribute to a function that violates these -requirements is undefined behaviour. - -This attribute enables Rust to perform common optimizations, like sub-expression -elimination, and it can avoid emitting some calls in repeated invocations of the -function with the same argument values regardless of other operations being -performed in between these functions calls (as opposed to `#[ffi_pure]` -functions). - -## Pitfalls - -A `#[ffi_const]` function can only read global memory that would not affect -its return value for the whole execution of the program (e.g. immutable global -memory). `#[ffi_const]` functions are referentially-transparent and therefore -more strict than `#[ffi_pure]` functions. - -A common pitfall involves applying the `#[ffi_const]` attribute to a -function that reads memory through pointer arguments which do not necessarily -point to immutable global memory. - -A `#[ffi_const]` function that returns unit has no effect on the abstract -machine's state, and a `#[ffi_const]` function cannot be `#[ffi_pure]`. - -A `#[ffi_const]` function must not diverge, neither via a side effect (e.g. a -call to `abort`) nor by infinite loops. - -When translating C headers to Rust FFI, it is worth verifying for which targets -the `const` attribute is enabled in those headers, and using the appropriate -`cfg` macros in the Rust side to match those definitions. While the semantics of -`const` are implemented identically by many C and C++ compilers, e.g., clang, -[GCC], [ARM C/C++ compiler], [IBM ILE C/C++], etc. they are not necessarily -implemented in this way on all of them. It is therefore also worth verifying -that the semantics of the C toolchain used to compile the binary being linked -against are compatible with those of the `#[ffi_const]`. - -[#58328]: https://github.com/rust-lang/rust/issues/58328 -[ARM C/C++ compiler]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/Cacgigch.html -[GCC]: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-const-function-attribute -[IBM ILE C/C++]: https://www.ibm.com/support/knowledgecenter/fr/ssw_ibm_i_71/rzarg/fn_attrib_const.htm diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/ffi-pure.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/ffi-pure.md deleted file mode 100644 index 236ccb9f9053..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/ffi-pure.md +++ /dev/null @@ -1,56 +0,0 @@ -# `ffi_pure` - -The tracking issue for this feature is: [#58329] - ------- - -The `#[ffi_pure]` attribute applies clang's `pure` attribute to foreign -functions declarations. - -That is, `#[ffi_pure]` functions shall have no effects except for its return -value, which shall not change across two consecutive function calls with -the same parameters. - -Applying the `#[ffi_pure]` attribute to a function that violates these -requirements is undefined behavior. - -This attribute enables Rust to perform common optimizations, like sub-expression -elimination and loop optimizations. Some common examples of pure functions are -`strlen` or `memcmp`. - -These optimizations are only applicable when the compiler can prove that no -program state observable by the `#[ffi_pure]` function has changed between calls -of the function, which could alter the result. See also the `#[ffi_const]` -attribute, which provides stronger guarantees regarding the allowable behavior -of a function, enabling further optimization. - -## Pitfalls - -A `#[ffi_pure]` function can read global memory through the function -parameters (e.g. pointers), globals, etc. `#[ffi_pure]` functions are not -referentially-transparent, and are therefore more relaxed than `#[ffi_const]` -functions. - -However, accessing global memory through volatile or atomic reads can violate the -requirement that two consecutive function calls shall return the same value. - -A `pure` function that returns unit has no effect on the abstract machine's -state. - -A `#[ffi_pure]` function must not diverge, neither via a side effect (e.g. a -call to `abort`) nor by infinite loops. - -When translating C headers to Rust FFI, it is worth verifying for which targets -the `pure` attribute is enabled in those headers, and using the appropriate -`cfg` macros in the Rust side to match those definitions. While the semantics of -`pure` are implemented identically by many C and C++ compilers, e.g., clang, -[GCC], [ARM C/C++ compiler], [IBM ILE C/C++], etc. they are not necessarily -implemented in this way on all of them. It is therefore also worth verifying -that the semantics of the C toolchain used to compile the binary being linked -against are compatible with those of the `#[ffi_pure]`. - - -[#58329]: https://github.com/rust-lang/rust/issues/58329 -[ARM C/C++ compiler]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/Cacigdac.html -[GCC]: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-pure-function-attribute -[IBM ILE C/C++]: https://www.ibm.com/support/knowledgecenter/fr/ssw_ibm_i_71/rzarg/fn_attrib_pure.htm diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/generators.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/generators.md deleted file mode 100644 index 7b865c9c679b..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/generators.md +++ /dev/null @@ -1,246 +0,0 @@ -# `generators` - -The tracking issue for this feature is: [#43122] - -[#43122]: https://github.com/rust-lang/rust/issues/43122 - ------------------------- - -The `generators` feature gate in Rust allows you to define generator or -coroutine literals. A generator is a "resumable function" that syntactically -resembles a closure but compiles to much different semantics in the compiler -itself. The primary feature of a generator is that it can be suspended during -execution to be resumed at a later date. Generators use the `yield` keyword to -"return", and then the caller can `resume` a generator to resume execution just -after the `yield` keyword. - -Generators are an extra-unstable feature in the compiler right now. Added in -[RFC 2033] they're mostly intended right now as a information/constraint -gathering phase. The intent is that experimentation can happen on the nightly -compiler before actual stabilization. A further RFC will be required to -stabilize generators/coroutines and will likely contain at least a few small -tweaks to the overall design. - -[RFC 2033]: https://github.com/rust-lang/rfcs/pull/2033 - -A syntactical example of a generator is: - -```rust -#![feature(generators, generator_trait)] - -use std::ops::{Generator, GeneratorState}; -use std::pin::Pin; - -fn main() { - let mut generator = || { - yield 1; - return "foo" - }; - - match Pin::new(&mut generator).resume(()) { - GeneratorState::Yielded(1) => {} - _ => panic!("unexpected value from resume"), - } - match Pin::new(&mut generator).resume(()) { - GeneratorState::Complete("foo") => {} - _ => panic!("unexpected value from resume"), - } -} -``` - -Generators are closure-like literals which can contain a `yield` statement. The -`yield` statement takes an optional expression of a value to yield out of the -generator. All generator literals implement the `Generator` trait in the -`std::ops` module. The `Generator` trait has one main method, `resume`, which -resumes execution of the generator at the previous suspension point. - -An example of the control flow of generators is that the following example -prints all numbers in order: - -```rust -#![feature(generators, generator_trait)] - -use std::ops::Generator; -use std::pin::Pin; - -fn main() { - let mut generator = || { - println!("2"); - yield; - println!("4"); - }; - - println!("1"); - Pin::new(&mut generator).resume(()); - println!("3"); - Pin::new(&mut generator).resume(()); - println!("5"); -} -``` - -At this time the main intended use case of generators is an implementation -primitive for async/await syntax, but generators will likely be extended to -ergonomic implementations of iterators and other primitives in the future. -Feedback on the design and usage is always appreciated! - -### The `Generator` trait - -The `Generator` trait in `std::ops` currently looks like: - -```rust -# #![feature(arbitrary_self_types, generator_trait)] -# use std::ops::GeneratorState; -# use std::pin::Pin; - -pub trait Generator { - type Yield; - type Return; - fn resume(self: Pin<&mut Self>, resume: R) -> GeneratorState; -} -``` - -The `Generator::Yield` type is the type of values that can be yielded with the -`yield` statement. The `Generator::Return` type is the returned type of the -generator. This is typically the last expression in a generator's definition or -any value passed to `return` in a generator. The `resume` function is the entry -point for executing the `Generator` itself. - -The return value of `resume`, `GeneratorState`, looks like: - -```rust -pub enum GeneratorState { - Yielded(Y), - Complete(R), -} -``` - -The `Yielded` variant indicates that the generator can later be resumed. This -corresponds to a `yield` point in a generator. The `Complete` variant indicates -that the generator is complete and cannot be resumed again. Calling `resume` -after a generator has returned `Complete` will likely result in a panic of the -program. - -### Closure-like semantics - -The closure-like syntax for generators alludes to the fact that they also have -closure-like semantics. Namely: - -* When created, a generator executes no code. A closure literal does not - actually execute any of the closure's code on construction, and similarly a - generator literal does not execute any code inside the generator when - constructed. - -* Generators can capture outer variables by reference or by move, and this can - be tweaked with the `move` keyword at the beginning of the closure. Like - closures all generators will have an implicit environment which is inferred by - the compiler. Outer variables can be moved into a generator for use as the - generator progresses. - -* Generator literals produce a value with a unique type which implements the - `std::ops::Generator` trait. This allows actual execution of the generator - through the `Generator::resume` method as well as also naming it in return - types and such. - -* Traits like `Send` and `Sync` are automatically implemented for a `Generator` - depending on the captured variables of the environment. Unlike closures, - generators also depend on variables live across suspension points. This means - that although the ambient environment may be `Send` or `Sync`, the generator - itself may not be due to internal variables live across `yield` points being - not-`Send` or not-`Sync`. Note that generators do - not implement traits like `Copy` or `Clone` automatically. - -* Whenever a generator is dropped it will drop all captured environment - variables. - -### Generators as state machines - -In the compiler, generators are currently compiled as state machines. Each -`yield` expression will correspond to a different state that stores all live -variables over that suspension point. Resumption of a generator will dispatch on -the current state and then execute internally until a `yield` is reached, at -which point all state is saved off in the generator and a value is returned. - -Let's take a look at an example to see what's going on here: - -```rust -#![feature(generators, generator_trait)] - -use std::ops::Generator; -use std::pin::Pin; - -fn main() { - let ret = "foo"; - let mut generator = move || { - yield 1; - return ret - }; - - Pin::new(&mut generator).resume(()); - Pin::new(&mut generator).resume(()); -} -``` - -This generator literal will compile down to something similar to: - -```rust -#![feature(arbitrary_self_types, generators, generator_trait)] - -use std::ops::{Generator, GeneratorState}; -use std::pin::Pin; - -fn main() { - let ret = "foo"; - let mut generator = { - enum __Generator { - Start(&'static str), - Yield1(&'static str), - Done, - } - - impl Generator for __Generator { - type Yield = i32; - type Return = &'static str; - - fn resume(mut self: Pin<&mut Self>, resume: ()) -> GeneratorState { - use std::mem; - match mem::replace(&mut *self, __Generator::Done) { - __Generator::Start(s) => { - *self = __Generator::Yield1(s); - GeneratorState::Yielded(1) - } - - __Generator::Yield1(s) => { - *self = __Generator::Done; - GeneratorState::Complete(s) - } - - __Generator::Done => { - panic!("generator resumed after completion") - } - } - } - } - - __Generator::Start(ret) - }; - - Pin::new(&mut generator).resume(()); - Pin::new(&mut generator).resume(()); -} -``` - -Notably here we can see that the compiler is generating a fresh type, -`__Generator` in this case. This type has a number of states (represented here -as an `enum`) corresponding to each of the conceptual states of the generator. -At the beginning we're closing over our outer variable `foo` and then that -variable is also live over the `yield` point, so it's stored in both states. - -When the generator starts it'll immediately yield 1, but it saves off its state -just before it does so indicating that it has reached the yield point. Upon -resuming again we'll execute the `return ret` which returns the `Complete` -state. - -Here we can also note that the `Done` state, if resumed, panics immediately as -it's invalid to resume a completed generator. It's also worth noting that this -is just a rough desugaring, not a normative specification for what the compiler -does. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/half-open-range-patterns.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/half-open-range-patterns.md deleted file mode 100644 index 3b16dd049ce3..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/half-open-range-patterns.md +++ /dev/null @@ -1,27 +0,0 @@ -# `half_open_range_patterns` - -The tracking issue for this feature is: [#67264] -It is part of the `#![exclusive_range_pattern]` feature, -tracked at [#37854]. - -[#67264]: https://github.com/rust-lang/rust/issues/67264 -[#37854]: https://github.com/rust-lang/rust/issues/37854 ------ - -The `half_open_range_patterns` feature allows RangeTo patterns -(`..10`) to be used in appropriate pattern matching contexts. -This requires also enabling the `exclusive_range_pattern` feature. - -It also enabled RangeFrom patterns but that has since been -stabilized. - -```rust -#![feature(half_open_range_patterns)] -#![feature(exclusive_range_pattern)] - let x = 5; - match x { - ..0 => println!("negative!"), // "RangeTo" pattern. Unstable. - 0 => println!("zero!"), - 1.. => println!("positive!"), // "RangeFrom" pattern. Stable. - } -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/infer-static-outlives-requirements.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/infer-static-outlives-requirements.md deleted file mode 100644 index 5f3f1b4dd8a3..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/infer-static-outlives-requirements.md +++ /dev/null @@ -1,44 +0,0 @@ -# `infer_static_outlives_requirements` - -The tracking issue for this feature is: [#54185] - -[#54185]: https://github.com/rust-lang/rust/issues/54185 - ------------------------- -The `infer_static_outlives_requirements` feature indicates that certain -`'static` outlives requirements can be inferred by the compiler rather than -stating them explicitly. - -Note: It is an accompanying feature to `infer_outlives_requirements`, -which must be enabled to infer outlives requirements. - -For example, currently generic struct definitions that contain -references, require where-clauses of the form T: 'static. By using -this feature the outlives predicates will be inferred, although -they may still be written explicitly. - -```rust,ignore (pseudo-Rust) -struct Foo where U: 'static { // <-- currently required - bar: Bar -} -struct Bar { - x: T, -} -``` - - -## Examples: - -```rust,ignore (pseudo-Rust) -#![feature(infer_outlives_requirements)] -#![feature(infer_static_outlives_requirements)] - -#[rustc_outlives] -// Implicitly infer U: 'static -struct Foo { - bar: Bar -} -struct Bar { - x: T, -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/inline-const-pat.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/inline-const-pat.md deleted file mode 100644 index 5f0f7547a0a8..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/inline-const-pat.md +++ /dev/null @@ -1,24 +0,0 @@ -# `inline_const_pat` - -The tracking issue for this feature is: [#76001] - -See also [`inline_const`](inline-const.md) - ------- - -This feature allows you to use inline constant expressions in pattern position: - -```rust -#![feature(inline_const_pat)] - -const fn one() -> i32 { 1 } - -let some_int = 3; -match some_int { - const { 1 + 2 } => println!("Matched 1 + 2"), - const { one() } => println!("Matched const fn returning 1"), - _ => println!("Didn't match anything :("), -} -``` - -[#76001]: https://github.com/rust-lang/rust/issues/76001 diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/inline-const.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/inline-const.md deleted file mode 100644 index 7be70eed6ced..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/inline-const.md +++ /dev/null @@ -1,32 +0,0 @@ -# `inline_const` - -The tracking issue for this feature is: [#76001] - -See also [`inline_const_pat`](inline-const-pat.md) - ------- - -This feature allows you to use inline constant expressions. For example, you can -turn this code: - -```rust -# fn add_one(x: i32) -> i32 { x + 1 } -const MY_COMPUTATION: i32 = 1 + 2 * 3 / 4; - -fn main() { - let x = add_one(MY_COMPUTATION); -} -``` - -into this code: - -```rust -#![feature(inline_const)] - -# fn add_one(x: i32) -> i32 { x + 1 } -fn main() { - let x = add_one(const { 1 + 2 * 3 / 4 }); -} -``` - -[#76001]: https://github.com/rust-lang/rust/issues/76001 diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/intra-doc-pointers.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/intra-doc-pointers.md deleted file mode 100644 index fbc83f4b4f48..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/intra-doc-pointers.md +++ /dev/null @@ -1,15 +0,0 @@ -# `intra-doc-pointers` - -The tracking issue for this feature is: [#80896] - -[#80896]: https://github.com/rust-lang/rust/issues/80896 - ------------------------- - -Rustdoc does not currently allow disambiguating between `*const` and `*mut`, and -raw pointers in intra-doc links are unstable until it does. - -```rust -#![feature(intra_doc_pointers)] -//! [pointer::add] -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/intrinsics.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/intrinsics.md deleted file mode 100644 index a0fb4e743d3f..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/intrinsics.md +++ /dev/null @@ -1,29 +0,0 @@ -# `intrinsics` - -The tracking issue for this feature is: None. - -Intrinsics are never intended to be stable directly, but intrinsics are often -exported in some sort of stable manner. Prefer using the stable interfaces to -the intrinsic directly when you can. - ------------------------- - - -These are imported as if they were FFI functions, with the special -`rust-intrinsic` ABI. For example, if one was in a freestanding -context, but wished to be able to `transmute` between types, and -perform efficient pointer arithmetic, one would import those functions -via a declaration like - -```rust -#![feature(intrinsics)] -# fn main() {} - -extern "rust-intrinsic" { - fn transmute(x: T) -> U; - - fn offset(dst: *const T, offset: isize) -> *const T; -} -``` - -As with any other FFI functions, these are always `unsafe` to call. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/lang-items.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/lang-items.md deleted file mode 100644 index 86bedb51538b..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/lang-items.md +++ /dev/null @@ -1,295 +0,0 @@ -# `lang_items` - -The tracking issue for this feature is: None. - ------------------------- - -The `rustc` compiler has certain pluggable operations, that is, -functionality that isn't hard-coded into the language, but is -implemented in libraries, with a special marker to tell the compiler -it exists. The marker is the attribute `#[lang = "..."]` and there are -various different values of `...`, i.e. various different 'lang -items'. - -For example, `Box` pointers require two lang items, one for allocation -and one for deallocation. A freestanding program that uses the `Box` -sugar for dynamic allocations via `malloc` and `free`: - -```rust,ignore (libc-is-finicky) -#![feature(lang_items, box_syntax, start, libc, core_intrinsics, rustc_private)] -#![no_std] -use core::intrinsics; -use core::panic::PanicInfo; - -extern crate libc; - -#[lang = "owned_box"] -pub struct Box(*mut T); - -#[lang = "exchange_malloc"] -unsafe fn allocate(size: usize, _align: usize) -> *mut u8 { - let p = libc::malloc(size as libc::size_t) as *mut u8; - - // Check if `malloc` failed: - if p as usize == 0 { - intrinsics::abort(); - } - - p -} - -#[lang = "box_free"] -unsafe fn box_free(ptr: *mut T) { - libc::free(ptr as *mut libc::c_void) -} - -#[start] -fn main(_argc: isize, _argv: *const *const u8) -> isize { - let _x = box 1; - - 0 -} - -#[lang = "eh_personality"] extern fn rust_eh_personality() {} -#[lang = "panic_impl"] extern fn rust_begin_panic(info: &PanicInfo) -> ! { unsafe { intrinsics::abort() } } -#[no_mangle] pub extern fn rust_eh_register_frames () {} -#[no_mangle] pub extern fn rust_eh_unregister_frames () {} -``` - -Note the use of `abort`: the `exchange_malloc` lang item is assumed to -return a valid pointer, and so needs to do the check internally. - -Other features provided by lang items include: - -- overloadable operators via traits: the traits corresponding to the - `==`, `<`, dereferencing (`*`) and `+` (etc.) operators are all - marked with lang items; those specific four are `eq`, `ord`, - `deref`, and `add` respectively. -- stack unwinding and general failure; the `eh_personality`, - `panic` and `panic_bounds_check` lang items. -- the traits in `std::marker` used to indicate types of - various kinds; lang items `send`, `sync` and `copy`. -- the marker types and variance indicators found in - `std::marker`; lang items `covariant_type`, - `contravariant_lifetime`, etc. - -Lang items are loaded lazily by the compiler; e.g. if one never uses -`Box` then there is no need to define functions for `exchange_malloc` -and `box_free`. `rustc` will emit an error when an item is needed -but not found in the current crate or any that it depends on. - -Most lang items are defined by `libcore`, but if you're trying to build -an executable without the standard library, you'll run into the need -for lang items. The rest of this page focuses on this use-case, even though -lang items are a bit broader than that. - -### Using libc - -In order to build a `#[no_std]` executable we will need libc as a dependency. -We can specify this using our `Cargo.toml` file: - -```toml -[dependencies] -libc = { version = "0.2.14", default-features = false } -``` - -Note that the default features have been disabled. This is a critical step - -**the default features of libc include the standard library and so must be -disabled.** - -### Writing an executable without stdlib - -Controlling the entry point is possible in two ways: the `#[start]` attribute, -or overriding the default shim for the C `main` function with your own. - -The function marked `#[start]` is passed the command line parameters -in the same format as C: - -```rust,ignore (libc-is-finicky) -#![feature(lang_items, core_intrinsics, rustc_private)] -#![feature(start)] -#![no_std] -use core::intrinsics; -use core::panic::PanicInfo; - -// Pull in the system libc library for what crt0.o likely requires. -extern crate libc; - -// Entry point for this program. -#[start] -fn start(_argc: isize, _argv: *const *const u8) -> isize { - 0 -} - -// These functions are used by the compiler, but not -// for a bare-bones hello world. These are normally -// provided by libstd. -#[lang = "eh_personality"] -#[no_mangle] -pub extern fn rust_eh_personality() { -} - -#[lang = "panic_impl"] -#[no_mangle] -pub extern fn rust_begin_panic(info: &PanicInfo) -> ! { - unsafe { intrinsics::abort() } -} -``` - -To override the compiler-inserted `main` shim, one has to disable it -with `#![no_main]` and then create the appropriate symbol with the -correct ABI and the correct name, which requires overriding the -compiler's name mangling too: - -```rust,ignore (libc-is-finicky) -#![feature(lang_items, core_intrinsics, rustc_private)] -#![feature(start)] -#![no_std] -#![no_main] -use core::intrinsics; -use core::panic::PanicInfo; - -// Pull in the system libc library for what crt0.o likely requires. -extern crate libc; - -// Entry point for this program. -#[no_mangle] // ensure that this symbol is called `main` in the output -pub extern fn main(_argc: i32, _argv: *const *const u8) -> i32 { - 0 -} - -// These functions are used by the compiler, but not -// for a bare-bones hello world. These are normally -// provided by libstd. -#[lang = "eh_personality"] -#[no_mangle] -pub extern fn rust_eh_personality() { -} - -#[lang = "panic_impl"] -#[no_mangle] -pub extern fn rust_begin_panic(info: &PanicInfo) -> ! { - unsafe { intrinsics::abort() } -} -``` - -In many cases, you may need to manually link to the `compiler_builtins` crate -when building a `no_std` binary. You may observe this via linker error messages -such as "```undefined reference to `__rust_probestack'```". - -## More about the language items - -The compiler currently makes a few assumptions about symbols which are -available in the executable to call. Normally these functions are provided by -the standard library, but without it you must define your own. These symbols -are called "language items", and they each have an internal name, and then a -signature that an implementation must conform to. - -The first of these functions, `rust_eh_personality`, is used by the failure -mechanisms of the compiler. This is often mapped to GCC's personality function -(see the [libstd implementation][unwind] for more information), but crates -which do not trigger a panic can be assured that this function is never -called. The language item's name is `eh_personality`. - -[unwind]: https://github.com/rust-lang/rust/blob/master/library/panic_unwind/src/gcc.rs - -The second function, `rust_begin_panic`, is also used by the failure mechanisms of the -compiler. When a panic happens, this controls the message that's displayed on -the screen. While the language item's name is `panic_impl`, the symbol name is -`rust_begin_panic`. - -Finally, a `eh_catch_typeinfo` static is needed for certain targets which -implement Rust panics on top of C++ exceptions. - -## List of all language items - -This is a list of all language items in Rust along with where they are located in -the source code. - -- Primitives - - `i8`: `libcore/num/mod.rs` - - `i16`: `libcore/num/mod.rs` - - `i32`: `libcore/num/mod.rs` - - `i64`: `libcore/num/mod.rs` - - `i128`: `libcore/num/mod.rs` - - `isize`: `libcore/num/mod.rs` - - `u8`: `libcore/num/mod.rs` - - `u16`: `libcore/num/mod.rs` - - `u32`: `libcore/num/mod.rs` - - `u64`: `libcore/num/mod.rs` - - `u128`: `libcore/num/mod.rs` - - `usize`: `libcore/num/mod.rs` - - `f32`: `libstd/f32.rs` - - `f64`: `libstd/f64.rs` - - `char`: `libcore/char.rs` - - `slice`: `liballoc/slice.rs` - - `str`: `liballoc/str.rs` - - `const_ptr`: `libcore/ptr.rs` - - `mut_ptr`: `libcore/ptr.rs` - - `unsafe_cell`: `libcore/cell.rs` -- Runtime - - `start`: `libstd/rt.rs` - - `eh_personality`: `libpanic_unwind/emcc.rs` (EMCC) - - `eh_personality`: `libpanic_unwind/gcc.rs` (GNU) - - `eh_personality`: `libpanic_unwind/seh.rs` (SEH) - - `eh_catch_typeinfo`: `libpanic_unwind/emcc.rs` (EMCC) - - `panic`: `libcore/panicking.rs` - - `panic_bounds_check`: `libcore/panicking.rs` - - `panic_impl`: `libcore/panicking.rs` - - `panic_impl`: `libstd/panicking.rs` -- Allocations - - `owned_box`: `liballoc/boxed.rs` - - `exchange_malloc`: `liballoc/heap.rs` - - `box_free`: `liballoc/heap.rs` -- Operands - - `not`: `libcore/ops/bit.rs` - - `bitand`: `libcore/ops/bit.rs` - - `bitor`: `libcore/ops/bit.rs` - - `bitxor`: `libcore/ops/bit.rs` - - `shl`: `libcore/ops/bit.rs` - - `shr`: `libcore/ops/bit.rs` - - `bitand_assign`: `libcore/ops/bit.rs` - - `bitor_assign`: `libcore/ops/bit.rs` - - `bitxor_assign`: `libcore/ops/bit.rs` - - `shl_assign`: `libcore/ops/bit.rs` - - `shr_assign`: `libcore/ops/bit.rs` - - `deref`: `libcore/ops/deref.rs` - - `deref_mut`: `libcore/ops/deref.rs` - - `index`: `libcore/ops/index.rs` - - `index_mut`: `libcore/ops/index.rs` - - `add`: `libcore/ops/arith.rs` - - `sub`: `libcore/ops/arith.rs` - - `mul`: `libcore/ops/arith.rs` - - `div`: `libcore/ops/arith.rs` - - `rem`: `libcore/ops/arith.rs` - - `neg`: `libcore/ops/arith.rs` - - `add_assign`: `libcore/ops/arith.rs` - - `sub_assign`: `libcore/ops/arith.rs` - - `mul_assign`: `libcore/ops/arith.rs` - - `div_assign`: `libcore/ops/arith.rs` - - `rem_assign`: `libcore/ops/arith.rs` - - `eq`: `libcore/cmp.rs` - - `ord`: `libcore/cmp.rs` -- Functions - - `fn`: `libcore/ops/function.rs` - - `fn_mut`: `libcore/ops/function.rs` - - `fn_once`: `libcore/ops/function.rs` - - `generator_state`: `libcore/ops/generator.rs` - - `generator`: `libcore/ops/generator.rs` -- Other - - `coerce_unsized`: `libcore/ops/unsize.rs` - - `drop`: `libcore/ops/drop.rs` - - `drop_in_place`: `libcore/ptr.rs` - - `clone`: `libcore/clone.rs` - - `copy`: `libcore/marker.rs` - - `send`: `libcore/marker.rs` - - `sized`: `libcore/marker.rs` - - `unsize`: `libcore/marker.rs` - - `sync`: `libcore/marker.rs` - - `phantom_data`: `libcore/marker.rs` - - `discriminant_kind`: `libcore/marker.rs` - - `freeze`: `libcore/marker.rs` - - `debug_trait`: `libcore/fmt/mod.rs` - - `non_zero`: `libcore/nonzero.rs` - - `arc`: `liballoc/sync.rs` - - `rc`: `liballoc/rc.rs` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/link-cfg.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/link-cfg.md deleted file mode 100644 index ee0fd5bf8698..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/link-cfg.md +++ /dev/null @@ -1,5 +0,0 @@ -# `link_cfg` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/marker-trait-attr.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/marker-trait-attr.md deleted file mode 100644 index be350cd61696..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/marker-trait-attr.md +++ /dev/null @@ -1,35 +0,0 @@ -# `marker_trait_attr` - -The tracking issue for this feature is: [#29864] - -[#29864]: https://github.com/rust-lang/rust/issues/29864 - ------------------------- - -Normally, Rust keeps you from adding trait implementations that could -overlap with each other, as it would be ambiguous which to use. This -feature, however, carves out an exception to that rule: a trait can -opt-in to having overlapping implementations, at the cost that those -implementations are not allowed to override anything (and thus the -trait itself cannot have any associated items, as they're pointless -when they'd need to do the same thing for every type anyway). - -```rust -#![feature(marker_trait_attr)] - -#[marker] trait CheapToClone: Clone {} - -impl CheapToClone for T {} - -// These could potentially overlap with the blanket implementation above, -// so are only allowed because CheapToClone is a marker trait. -impl CheapToClone for (T, U) {} -impl CheapToClone for std::ops::Range {} - -fn cheap_clone(t: T) -> T { - t.clone() -} -``` - -This is expected to replace the unstable `overlapping_marker_traits` -feature, which applied to all empty traits (without needing an opt-in). diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/more-qualified-paths.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/more-qualified-paths.md deleted file mode 100644 index 857af577a6cf..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/more-qualified-paths.md +++ /dev/null @@ -1,29 +0,0 @@ -# `more_qualified_paths` - -The `more_qualified_paths` feature can be used in order to enable the -use of qualified paths in patterns. - -## Example - -```rust -#![feature(more_qualified_paths)] - -fn main() { - // destructure through a qualified path - let ::Assoc { br } = StructStruct { br: 2 }; -} - -struct StructStruct { - br: i8, -} - -struct Foo; - -trait A { - type Assoc; -} - -impl A for Foo { - type Assoc = StructStruct; -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/native-link-modifiers-as-needed.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/native-link-modifiers-as-needed.md deleted file mode 100644 index 1757673612c4..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/native-link-modifiers-as-needed.md +++ /dev/null @@ -1,18 +0,0 @@ -# `native_link_modifiers_as_needed` - -The tracking issue for this feature is: [#81490] - -[#81490]: https://github.com/rust-lang/rust/issues/81490 - ------------------------- - -The `native_link_modifiers_as_needed` feature allows you to use the `as-needed` modifier. - -`as-needed` is only compatible with the `dynamic` and `framework` linking kinds. Using any other kind will result in a compiler error. - -`+as-needed` means that the library will be actually linked only if it satisfies some undefined symbols at the point at which it is specified on the command line, making it similar to static libraries in this regard. - -This modifier translates to `--as-needed` for ld-like linkers, and to `-dead_strip_dylibs` / `-needed_library` / `-needed_framework` for ld64. -The modifier does nothing for linkers that don't support it (e.g. `link.exe`). - -The default for this modifier is unclear, some targets currently specify it as `+as-needed`, some do not. We may want to try making `+as-needed` a default for all targets. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/native-link-modifiers-bundle.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/native-link-modifiers-bundle.md deleted file mode 100644 index ac192cff13a3..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/native-link-modifiers-bundle.md +++ /dev/null @@ -1,19 +0,0 @@ -# `native_link_modifiers_bundle` - -The tracking issue for this feature is: [#81490] - -[#81490]: https://github.com/rust-lang/rust/issues/81490 - ------------------------- - -The `native_link_modifiers_bundle` feature allows you to use the `bundle` modifier. - -Only compatible with the `static` linking kind. Using any other kind will result in a compiler error. - -`+bundle` means objects from the static library are bundled into the produced crate (a rlib, for example) and are used from this crate later during linking of the final binary. - -`-bundle` means the static library is included into the produced rlib "by name" and object files from it are included only during linking of the final binary, the file search by that name is also performed during final linking. - -This modifier is supposed to supersede the `static-nobundle` linking kind defined by [RFC 1717](https://github.com/rust-lang/rfcs/pull/1717). - -The default for this modifier is currently `+bundle`, but it could be changed later on some future edition boundary. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/native-link-modifiers-verbatim.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/native-link-modifiers-verbatim.md deleted file mode 100644 index 02bd87e50956..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/native-link-modifiers-verbatim.md +++ /dev/null @@ -1,20 +0,0 @@ -# `native_link_modifiers_verbatim` - -The tracking issue for this feature is: [#81490] - -[#81490]: https://github.com/rust-lang/rust/issues/81490 - ------------------------- - -The `native_link_modifiers_verbatim` feature allows you to use the `verbatim` modifier. - -`+verbatim` means that rustc itself won't add any target-specified library prefixes or suffixes (like `lib` or `.a`) to the library name, and will try its best to ask for the same thing from the linker. - -For `ld`-like linkers rustc will use the `-l:filename` syntax (note the colon) when passing the library, so the linker won't add any prefixes or suffixes as well. -See [`-l namespec`](https://sourceware.org/binutils/docs/ld/Options.html) in ld documentation for more details. -For linkers not supporting any verbatim modifiers (e.g. `link.exe` or `ld64`) the library name will be passed as is. - -The default for this modifier is `-verbatim`. - -This RFC changes the behavior of `raw-dylib` linking kind specified by [RFC 2627](https://github.com/rust-lang/rfcs/pull/2627). The `.dll` suffix (or other target-specified suffixes for other targets) is now added automatically. -If your DLL doesn't have the `.dll` suffix, it can be specified with `+verbatim`. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/native-link-modifiers-whole-archive.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/native-link-modifiers-whole-archive.md deleted file mode 100644 index 4961e88cad1e..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/native-link-modifiers-whole-archive.md +++ /dev/null @@ -1,18 +0,0 @@ -# `native_link_modifiers_whole_archive` - -The tracking issue for this feature is: [#81490] - -[#81490]: https://github.com/rust-lang/rust/issues/81490 - ------------------------- - -The `native_link_modifiers_whole_archive` feature allows you to use the `whole-archive` modifier. - -Only compatible with the `static` linking kind. Using any other kind will result in a compiler error. - -`+whole-archive` means that the static library is linked as a whole archive without throwing any object files away. - -This modifier translates to `--whole-archive` for `ld`-like linkers, to `/WHOLEARCHIVE` for `link.exe`, and to `-force_load` for `ld64`. -The modifier does nothing for linkers that don't support it. - -The default for this modifier is `-whole-archive`. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/native-link-modifiers.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/native-link-modifiers.md deleted file mode 100644 index fc8b57546217..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/native-link-modifiers.md +++ /dev/null @@ -1,11 +0,0 @@ -# `native_link_modifiers` - -The tracking issue for this feature is: [#81490] - -[#81490]: https://github.com/rust-lang/rust/issues/81490 - ------------------------- - -The `native_link_modifiers` feature allows you to use the `modifiers` syntax with the `#[link(..)]` attribute. - -Modifiers are specified as a comma-delimited string with each modifier prefixed with either a `+` or `-` to indicate that the modifier is enabled or disabled, respectively. The last boolean value specified for a given modifier wins. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/negative-impls.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/negative-impls.md deleted file mode 100644 index 151520f0e4ab..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/negative-impls.md +++ /dev/null @@ -1,57 +0,0 @@ -# `negative_impls` - -The tracking issue for this feature is [#68318]. - -[#68318]: https://github.com/rust-lang/rust/issues/68318 - ----- - -With the feature gate `negative_impls`, you can write negative impls as well as positive ones: - -```rust -#![feature(negative_impls)] -trait DerefMut { } -impl !DerefMut for &T { } -``` - -Negative impls indicate a semver guarantee that the given trait will not be implemented for the given types. Negative impls play an additional purpose for auto traits, described below. - -Negative impls have the following characteristics: - -* They do not have any items. -* They must obey the orphan rules as if they were a positive impl. -* They cannot "overlap" with any positive impls. - -## Semver interaction - -It is a breaking change to remove a negative impl. Negative impls are a commitment not to implement the given trait for the named types. - -## Orphan and overlap rules - -Negative impls must obey the same orphan rules as a positive impl. This implies you cannot add a negative impl for types defined in upstream crates and so forth. - -Similarly, negative impls cannot overlap with positive impls, again using the same "overlap" check that we ordinarily use to determine if two impls overlap. (Note that positive impls typically cannot overlap with one another either, except as permitted by specialization.) - -## Interaction with auto traits - -Declaring a negative impl `impl !SomeAutoTrait for SomeType` for an -auto-trait serves two purposes: - -* as with any trait, it declares that `SomeType` will never implement `SomeAutoTrait`; -* it disables the automatic `SomeType: SomeAutoTrait` impl that would otherwise have been generated. - -Note that, at present, there is no way to indicate that a given type -does not implement an auto trait *but that it may do so in the -future*. For ordinary types, this is done by simply not declaring any -impl at all, but that is not an option for auto traits. A workaround -is that one could embed a marker type as one of the fields, where the -marker type is `!AutoTrait`. - -## Immediate uses - -Negative impls are used to declare that `&T: !DerefMut` and `&mut T: !Clone`, as required to fix the soundness of `Pin` described in [#66544](https://github.com/rust-lang/rust/issues/66544). - -This serves two purposes: - -* For proving the correctness of unsafe code, we can use that impl as evidence that no `DerefMut` or `Clone` impl exists. -* It prevents downstream crates from creating such impls. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/no-coverage.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/no-coverage.md deleted file mode 100644 index 327cdb39791a..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/no-coverage.md +++ /dev/null @@ -1,30 +0,0 @@ -# `no_coverage` - -The tracking issue for this feature is: [#84605] - -[#84605]: https://github.com/rust-lang/rust/issues/84605 - ---- - -The `no_coverage` attribute can be used to selectively disable coverage -instrumentation in an annotated function. This might be useful to: - -- Avoid instrumentation overhead in a performance critical function -- Avoid generating coverage for a function that is not meant to be executed, - but still target 100% coverage for the rest of the program. - -## Example - -```rust -#![feature(no_coverage)] - -// `foo()` will get coverage instrumentation (by default) -fn foo() { - // ... -} - -#[no_coverage] -fn bar() { - // ... -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/no-sanitize.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/no-sanitize.md deleted file mode 100644 index 28c683934d4e..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/no-sanitize.md +++ /dev/null @@ -1,29 +0,0 @@ -# `no_sanitize` - -The tracking issue for this feature is: [#39699] - -[#39699]: https://github.com/rust-lang/rust/issues/39699 - ------------------------- - -The `no_sanitize` attribute can be used to selectively disable sanitizer -instrumentation in an annotated function. This might be useful to: avoid -instrumentation overhead in a performance critical function, or avoid -instrumenting code that contains constructs unsupported by given sanitizer. - -The precise effect of this annotation depends on particular sanitizer in use. -For example, with `no_sanitize(thread)`, the thread sanitizer will no longer -instrument non-atomic store / load operations, but it will instrument atomic -operations to avoid reporting false positives and provide meaning full stack -traces. - -## Examples - -``` rust -#![feature(no_sanitize)] - -#[no_sanitize(address)] -fn foo() { - // ... -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/plugin.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/plugin.md deleted file mode 100644 index 040f46f8b7c7..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/plugin.md +++ /dev/null @@ -1,116 +0,0 @@ -# `plugin` - -The tracking issue for this feature is: [#29597] - -[#29597]: https://github.com/rust-lang/rust/issues/29597 - - -This feature is part of "compiler plugins." It will often be used with the -`rustc_private` feature. - ------------------------- - -`rustc` can load compiler plugins, which are user-provided libraries that -extend the compiler's behavior with new lint checks, etc. - -A plugin is a dynamic library crate with a designated *registrar* function that -registers extensions with `rustc`. Other crates can load these extensions using -the crate attribute `#![plugin(...)]`. See the -`rustc_driver::plugin` documentation for more about the -mechanics of defining and loading a plugin. - -In the vast majority of cases, a plugin should *only* be used through -`#![plugin]` and not through an `extern crate` item. Linking a plugin would -pull in all of librustc_ast and librustc as dependencies of your crate. This is -generally unwanted unless you are building another plugin. - -The usual practice is to put compiler plugins in their own crate, separate from -any `macro_rules!` macros or ordinary Rust code meant to be used by consumers -of a library. - -# Lint plugins - -Plugins can extend [Rust's lint -infrastructure](../../reference/attributes/diagnostics.md#lint-check-attributes) with -additional checks for code style, safety, etc. Now let's write a plugin -[`lint-plugin-test.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui-fulldeps/auxiliary/lint-plugin-test.rs) -that warns about any item named `lintme`. - -```rust,ignore (requires-stage-2) -#![feature(box_syntax, rustc_private)] - -extern crate rustc_ast; - -// Load rustc as a plugin to get macros -extern crate rustc_driver; -#[macro_use] -extern crate rustc_lint; -#[macro_use] -extern crate rustc_session; - -use rustc_driver::plugin::Registry; -use rustc_lint::{EarlyContext, EarlyLintPass, LintArray, LintContext, LintPass}; -use rustc_ast::ast; -declare_lint!(TEST_LINT, Warn, "Warn about items named 'lintme'"); - -declare_lint_pass!(Pass => [TEST_LINT]); - -impl EarlyLintPass for Pass { - fn check_item(&mut self, cx: &EarlyContext, it: &ast::Item) { - if it.ident.name.as_str() == "lintme" { - cx.lint(TEST_LINT, |lint| { - lint.build("item is named 'lintme'").set_span(it.span).emit() - }); - } - } -} - -#[no_mangle] -fn __rustc_plugin_registrar(reg: &mut Registry) { - reg.lint_store.register_lints(&[&TEST_LINT]); - reg.lint_store.register_early_pass(|| box Pass); -} -``` - -Then code like - -```rust,ignore (requires-plugin) -#![feature(plugin)] -#![plugin(lint_plugin_test)] - -fn lintme() { } -``` - -will produce a compiler warning: - -```txt -foo.rs:4:1: 4:16 warning: item is named 'lintme', #[warn(test_lint)] on by default -foo.rs:4 fn lintme() { } - ^~~~~~~~~~~~~~~ -``` - -The components of a lint plugin are: - -* one or more `declare_lint!` invocations, which define static `Lint` structs; - -* a struct holding any state needed by the lint pass (here, none); - -* a `LintPass` - implementation defining how to check each syntax element. A single - `LintPass` may call `span_lint` for several different `Lint`s, but should - register them all through the `get_lints` method. - -Lint passes are syntax traversals, but they run at a late stage of compilation -where type information is available. `rustc`'s [built-in -lints](https://github.com/rust-lang/rust/blob/master/src/librustc_session/lint/builtin.rs) -mostly use the same infrastructure as lint plugins, and provide examples of how -to access type information. - -Lints defined by plugins are controlled by the usual [attributes and compiler -flags](../../reference/attributes/diagnostics.md#lint-check-attributes), e.g. -`#[allow(test_lint)]` or `-A test-lint`. These identifiers are derived from the -first argument to `declare_lint!`, with appropriate case and punctuation -conversion. - -You can run `rustc -W help foo.rs` to see a list of lints known to `rustc`, -including those provided by plugins loaded by `foo.rs`. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/profiler-runtime.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/profiler-runtime.md deleted file mode 100644 index aee86f63952a..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/profiler-runtime.md +++ /dev/null @@ -1,5 +0,0 @@ -# `profiler_runtime` - -The tracking issue for this feature is: [#42524](https://github.com/rust-lang/rust/issues/42524). - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/raw-dylib.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/raw-dylib.md deleted file mode 100644 index 23fc5b3052d8..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/raw-dylib.md +++ /dev/null @@ -1,34 +0,0 @@ -# `raw_dylib` - -The tracking issue for this feature is: [#58713] - -[#58713]: https://github.com/rust-lang/rust/issues/58713 - ------------------------- - -The `raw_dylib` feature allows you to link against the implementations of functions in an `extern` -block without, on Windows, linking against an import library. - -```rust,ignore (partial-example) -#![feature(raw_dylib)] - -#[link(name="library", kind="raw-dylib")] -extern { - fn extern_function(x: i32); -} - -fn main() { - unsafe { - extern_function(14); - } -} -``` - -## Limitations - -Currently, this feature is only supported on `-windows-msvc` targets. Non-Windows platforms don't have import -libraries, and an incompatibility between LLVM and the BFD linker means that it is not currently supported on -`-windows-gnu` targets. - -On the `i686-pc-windows-msvc` target, this feature supports only the `cdecl`, `stdcall`, `system`, and `fastcall` -calling conventions. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/repr128.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/repr128.md deleted file mode 100644 index 146f50ee67b5..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/repr128.md +++ /dev/null @@ -1,18 +0,0 @@ -# `repr128` - -The tracking issue for this feature is: [#56071] - -[#56071]: https://github.com/rust-lang/rust/issues/56071 - ------------------------- - -The `repr128` feature adds support for `#[repr(u128)]` on `enum`s. - -```rust -#![feature(repr128)] - -#[repr(u128)] -enum Foo { - Bar(u64), -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/rustc-attrs.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/rustc-attrs.md deleted file mode 100644 index c67b806f06af..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/rustc-attrs.md +++ /dev/null @@ -1,53 +0,0 @@ -# `rustc_attrs` - -This feature has no tracking issue, and is therefore internal to -the compiler, not being intended for general use. - -Note: `rustc_attrs` enables many rustc-internal attributes and this page -only discuss a few of them. - ------------------------- - -The `rustc_attrs` feature allows debugging rustc type layouts by using -`#[rustc_layout(...)]` to debug layout at compile time (it even works -with `cargo check`) as an alternative to `rustc -Z print-type-sizes` -that is way more verbose. - -Options provided by `#[rustc_layout(...)]` are `debug`, `size`, `align`, -`abi`. Note that it only works on sized types without generics. - -## Examples - -```rust,compile_fail -#![feature(rustc_attrs)] - -#[rustc_layout(abi, size)] -pub enum X { - Y(u8, u8, u8), - Z(isize), -} -``` - -When that is compiled, the compiler will error with something like - -```text -error: abi: Aggregate { sized: true } - --> src/lib.rs:4:1 - | -4 | / pub enum T { -5 | | Y(u8, u8, u8), -6 | | Z(isize), -7 | | } - | |_^ - -error: size: Size { raw: 16 } - --> src/lib.rs:4:1 - | -4 | / pub enum T { -5 | | Y(u8, u8, u8), -6 | | Z(isize), -7 | | } - | |_^ - -error: aborting due to 2 previous errors -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/trait-alias.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/trait-alias.md deleted file mode 100644 index f1be053ddc42..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/trait-alias.md +++ /dev/null @@ -1,34 +0,0 @@ -# `trait_alias` - -The tracking issue for this feature is: [#41517] - -[#41517]: https://github.com/rust-lang/rust/issues/41517 - ------------------------- - -The `trait_alias` feature adds support for trait aliases. These allow aliases -to be created for one or more traits (currently just a single regular trait plus -any number of auto-traits), and used wherever traits would normally be used as -either bounds or trait objects. - -```rust -#![feature(trait_alias)] - -trait Foo = std::fmt::Debug + Send; -trait Bar = Foo + Sync; - -// Use trait alias as bound on type parameter. -fn foo(v: &T) { - println!("{:?}", v); -} - -pub fn main() { - foo(&1); - - // Use trait alias for trait objects. - let a: &Bar = &123; - println!("{:?}", a); - let b = Box::new(456) as Box; - println!("{:?}", b); -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/trait-upcasting.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/trait-upcasting.md deleted file mode 100644 index 3697ae38f9d8..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/trait-upcasting.md +++ /dev/null @@ -1,27 +0,0 @@ -# `trait_upcasting` - -The tracking issue for this feature is: [#65991] - -[#65991]: https://github.com/rust-lang/rust/issues/65991 - ------------------------- - -The `trait_upcasting` feature adds support for trait upcasting coercion. This allows a -trait object of type `dyn Bar` to be cast to a trait object of type `dyn Foo` -so long as `Bar: Foo`. - -```rust,edition2018 -#![feature(trait_upcasting)] -#![allow(incomplete_features)] - -trait Foo {} - -trait Bar: Foo {} - -impl Foo for i32 {} - -impl Bar for T {} - -let bar: &dyn Bar = &123; -let foo: &dyn Foo = bar; -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/transparent-unions.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/transparent-unions.md deleted file mode 100644 index 9b39b8971644..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/transparent-unions.md +++ /dev/null @@ -1,83 +0,0 @@ -# `transparent_unions` - -The tracking issue for this feature is [#60405] - -[#60405]: https://github.com/rust-lang/rust/issues/60405 - ----- - -The `transparent_unions` feature allows you mark `union`s as -`#[repr(transparent)]`. A `union` may be `#[repr(transparent)]` in exactly the -same conditions in which a `struct` may be `#[repr(transparent)]` (generally, -this means the `union` must have exactly one non-zero-sized field). Some -concrete illustrations follow. - -```rust -#![feature(transparent_unions)] - -// This union has the same representation as `f32`. -#[repr(transparent)] -union SingleFieldUnion { - field: f32, -} - -// This union has the same representation as `usize`. -#[repr(transparent)] -union MultiFieldUnion { - field: usize, - nothing: (), -} -``` - -For consistency with transparent `struct`s, `union`s must have exactly one -non-zero-sized field. If all fields are zero-sized, the `union` must not be -`#[repr(transparent)]`: - -```rust -#![feature(transparent_unions)] - -// This (non-transparent) union is already valid in stable Rust: -pub union GoodUnion { - pub nothing: (), -} - -// Error: transparent union needs exactly one non-zero-sized field, but has 0 -// #[repr(transparent)] -// pub union BadUnion { -// pub nothing: (), -// } -``` - -The one exception is if the `union` is generic over `T` and has a field of type -`T`, it may be `#[repr(transparent)]` even if `T` is a zero-sized type: - -```rust -#![feature(transparent_unions)] - -// This union has the same representation as `T`. -#[repr(transparent)] -pub union GenericUnion { // Unions with non-`Copy` fields are unstable. - pub field: T, - pub nothing: (), -} - -// This is okay even though `()` is a zero-sized type. -pub const THIS_IS_OKAY: GenericUnion<()> = GenericUnion { field: () }; -``` - -Like transarent `struct`s, a transparent `union` of type `U` has the same -layout, size, and ABI as its single non-ZST field. If it is generic over a type -`T`, and all its fields are ZSTs except for exactly one field of type `T`, then -it has the same layout and ABI as `T` (even if `T` is a ZST when monomorphized). - -Like transparent `struct`s, transparent `union`s are FFI-safe if and only if -their underlying representation type is also FFI-safe. - -A `union` may not be eligible for the same nonnull-style optimizations that a -`struct` or `enum` (with the same fields) are eligible for. Adding -`#[repr(transparent)]` to `union` does not change this. To give a more concrete -example, it is unspecified whether `size_of::()` is equal to -`size_of::>()`, where `T` is a `union` (regardless of whether or not -it is transparent). The Rust compiler is free to perform this optimization if -possible, but is not required to, and different compiler versions may differ in -their application of these optimizations. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/try-blocks.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/try-blocks.md deleted file mode 100644 index e342c260a739..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/try-blocks.md +++ /dev/null @@ -1,30 +0,0 @@ -# `try_blocks` - -The tracking issue for this feature is: [#31436] - -[#31436]: https://github.com/rust-lang/rust/issues/31436 - ------------------------- - -The `try_blocks` feature adds support for `try` blocks. A `try` -block creates a new scope one can use the `?` operator in. - -```rust,edition2018 -#![feature(try_blocks)] - -use std::num::ParseIntError; - -let result: Result = try { - "1".parse::()? - + "2".parse::()? - + "3".parse::()? -}; -assert_eq!(result, Ok(6)); - -let result: Result = try { - "1".parse::()? - + "foo".parse::()? - + "3".parse::()? -}; -assert!(result.is_err()); -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/type-changing-struct-update.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/type-changing-struct-update.md deleted file mode 100644 index 9909cf35b5b5..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/type-changing-struct-update.md +++ /dev/null @@ -1,33 +0,0 @@ -# `type_changing_struct_update` - -The tracking issue for this feature is: [#86555] - -[#86555]: https://github.com/rust-lang/rust/issues/86555 - ------------------------- - -This implements [RFC2528]. When turned on, you can create instances of the same struct -that have different generic type or lifetime parameters. - -[RFC2528]: https://github.com/rust-lang/rfcs/blob/master/text/2528-type-changing-struct-update-syntax.md - -```rust -#![allow(unused_variables, dead_code)] -#![feature(type_changing_struct_update)] - -fn main () { - struct Foo { - field1: T, - field2: U, - } - - let base: Foo = Foo { - field1: String::from("hello"), - field2: 1234, - }; - let updated: Foo = Foo { - field1: 3.14, - ..base - }; -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/unboxed-closures.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/unboxed-closures.md deleted file mode 100644 index e4113d72d091..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/unboxed-closures.md +++ /dev/null @@ -1,25 +0,0 @@ -# `unboxed_closures` - -The tracking issue for this feature is [#29625] - -See Also: [`fn_traits`](../library-features/fn-traits.md) - -[#29625]: https://github.com/rust-lang/rust/issues/29625 - ----- - -The `unboxed_closures` feature allows you to write functions using the `"rust-call"` ABI, -required for implementing the [`Fn*`] family of traits. `"rust-call"` functions must have -exactly one (non self) argument, a tuple representing the argument list. - -[`Fn*`]: https://doc.rust-lang.org/std/ops/trait.Fn.html - -```rust -#![feature(unboxed_closures)] - -extern "rust-call" fn add_args(args: (u32, u32)) -> u32 { - args.0 + args.1 -} - -fn main() {} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/unsized-locals.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/unsized-locals.md deleted file mode 100644 index d5b01a3d6168..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/unsized-locals.md +++ /dev/null @@ -1,175 +0,0 @@ -# `unsized_locals` - -The tracking issue for this feature is: [#48055] - -[#48055]: https://github.com/rust-lang/rust/issues/48055 - ------------------------- - -This implements [RFC1909]. When turned on, you can have unsized arguments and locals: - -[RFC1909]: https://github.com/rust-lang/rfcs/blob/master/text/1909-unsized-rvalues.md - -```rust -#![allow(incomplete_features)] -#![feature(unsized_locals, unsized_fn_params)] - -use std::any::Any; - -fn main() { - let x: Box = Box::new(42); - let x: dyn Any = *x; - // ^ unsized local variable - // ^^ unsized temporary - foo(x); -} - -fn foo(_: dyn Any) {} -// ^^^^^^ unsized argument -``` - -The RFC still forbids the following unsized expressions: - -```rust,compile_fail -#![feature(unsized_locals)] - -use std::any::Any; - -struct MyStruct { - content: T, -} - -struct MyTupleStruct(T); - -fn answer() -> Box { - Box::new(42) -} - -fn main() { - // You CANNOT have unsized statics. - static X: dyn Any = *answer(); // ERROR - const Y: dyn Any = *answer(); // ERROR - - // You CANNOT have struct initialized unsized. - MyStruct { content: *answer() }; // ERROR - MyTupleStruct(*answer()); // ERROR - (42, *answer()); // ERROR - - // You CANNOT have unsized return types. - fn my_function() -> dyn Any { *answer() } // ERROR - - // You CAN have unsized local variables... - let mut x: dyn Any = *answer(); // OK - // ...but you CANNOT reassign to them. - x = *answer(); // ERROR - - // You CANNOT even initialize them separately. - let y: dyn Any; // OK - y = *answer(); // ERROR - - // Not mentioned in the RFC, but by-move captured variables are also Sized. - let x: dyn Any = *answer(); - (move || { // ERROR - let y = x; - })(); - - // You CAN create a closure with unsized arguments, - // but you CANNOT call it. - // This is an implementation detail and may be changed in the future. - let f = |x: dyn Any| {}; - f(*answer()); // ERROR -} -``` - -## By-value trait objects - -With this feature, you can have by-value `self` arguments without `Self: Sized` bounds. - -```rust -#![feature(unsized_fn_params)] - -trait Foo { - fn foo(self) {} -} - -impl Foo for T {} - -fn main() { - let slice: Box<[i32]> = Box::new([1, 2, 3]); - <[i32] as Foo>::foo(*slice); -} -``` - -And `Foo` will also be object-safe. - -```rust -#![feature(unsized_fn_params)] - -trait Foo { - fn foo(self) {} -} - -impl Foo for T {} - -fn main () { - let slice: Box = Box::new([1, 2, 3]); - // doesn't compile yet - ::foo(*slice); -} -``` - -One of the objectives of this feature is to allow `Box`. - -## Variable length arrays - -The RFC also describes an extension to the array literal syntax: `[e; dyn n]`. In the syntax, `n` isn't necessarily a constant expression. The array is dynamically allocated on the stack and has the type of `[T]`, instead of `[T; n]`. - -```rust,ignore (not-yet-implemented) -#![feature(unsized_locals)] - -fn mergesort(a: &mut [T]) { - let mut tmp = [T; dyn a.len()]; - // ... -} - -fn main() { - let mut a = [3, 1, 5, 6]; - mergesort(&mut a); - assert_eq!(a, [1, 3, 5, 6]); -} -``` - -VLAs are not implemented yet. The syntax isn't final, either. We may need an alternative syntax for Rust 2015 because, in Rust 2015, expressions like `[e; dyn(1)]` would be ambiguous. One possible alternative proposed in the RFC is `[e; n]`: if `n` captures one or more local variables, then it is considered as `[e; dyn n]`. - -## Advisory on stack usage - -It's advised not to casually use the `#![feature(unsized_locals)]` feature. Typical use-cases are: - -- When you need a by-value trait objects. -- When you really need a fast allocation of small temporary arrays. - -Another pitfall is repetitive allocation and temporaries. Currently the compiler simply extends the stack frame every time it encounters an unsized assignment. So for example, the code - -```rust -#![feature(unsized_locals)] - -fn main() { - let x: Box<[i32]> = Box::new([1, 2, 3, 4, 5]); - let _x = {{{{{{{{{{*x}}}}}}}}}}; -} -``` - -and the code - -```rust -#![feature(unsized_locals)] - -fn main() { - for _ in 0..10 { - let x: Box<[i32]> = Box::new([1, 2, 3, 4, 5]); - let _x = *x; - } -} -``` - -will unnecessarily extend the stack frame. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/language-features/unsized-tuple-coercion.md b/tools/bookrunner/rust-doc/unstable-book/src/language-features/unsized-tuple-coercion.md deleted file mode 100644 index 310c8d962948..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/language-features/unsized-tuple-coercion.md +++ /dev/null @@ -1,27 +0,0 @@ -# `unsized_tuple_coercion` - -The tracking issue for this feature is: [#42877] - -[#42877]: https://github.com/rust-lang/rust/issues/42877 - ------------------------- - -This is a part of [RFC0401]. According to the RFC, there should be an implementation like this: - -```rust,ignore (partial-example) -impl<..., T, U: ?Sized> Unsized<(..., U)> for (..., T) where T: Unsized {} -``` - -This implementation is currently gated behind `#[feature(unsized_tuple_coercion)]` to avoid insta-stability. Therefore you can use it like this: - -```rust -#![feature(unsized_tuple_coercion)] - -fn main() { - let x : ([i32; 3], [i32; 3]) = ([1, 2, 3], [4, 5, 6]); - let y : &([i32; 3], [i32]) = &x; - assert_eq!(y.1[0], 4); -} -``` - -[RFC0401]: https://github.com/rust-lang/rfcs/blob/master/text/0401-coercions.md diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features.md deleted file mode 100644 index 9f537e26132b..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features.md +++ /dev/null @@ -1 +0,0 @@ -# Library Features diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/allocator-api.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/allocator-api.md deleted file mode 100644 index 9f045ce08a43..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/allocator-api.md +++ /dev/null @@ -1,15 +0,0 @@ -# `allocator_api` - -The tracking issue for this feature is [#32838] - -[#32838]: https://github.com/rust-lang/rust/issues/32838 - ------------------------- - -Sometimes you want the memory for one collection to use a different -allocator than the memory for another collection. In this case, -replacing the global allocator is not a workable option. Instead, -you need to pass in an instance of an `AllocRef` to each collection -for which you want a custom allocator. - -TBD diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/c-variadic.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/c-variadic.md deleted file mode 100644 index 77762116e6b1..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/c-variadic.md +++ /dev/null @@ -1,26 +0,0 @@ -# `c_variadic` - -The tracking issue for this feature is: [#44930] - -[#44930]: https://github.com/rust-lang/rust/issues/44930 - ------------------------- - -The `c_variadic` library feature exposes the `VaList` structure, -Rust's analogue of C's `va_list` type. - -## Examples - -```rust -#![feature(c_variadic)] - -use std::ffi::VaList; - -pub unsafe extern "C" fn vadd(n: usize, mut args: VaList) -> usize { - let mut sum = 0; - for _ in 0..n { - sum += args.arg::(); - } - sum -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/c-void-variant.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/c-void-variant.md deleted file mode 100644 index a2fdc9936300..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/c-void-variant.md +++ /dev/null @@ -1,5 +0,0 @@ -# `c_void_variant` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/char-error-internals.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/char-error-internals.md deleted file mode 100644 index 8013b4988e14..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/char-error-internals.md +++ /dev/null @@ -1,5 +0,0 @@ -# `char_error_internals` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/concat-idents.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/concat-idents.md deleted file mode 100644 index 73f6cfa21787..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/concat-idents.md +++ /dev/null @@ -1,22 +0,0 @@ -# `concat_idents` - -The tracking issue for this feature is: [#29599] - -[#29599]: https://github.com/rust-lang/rust/issues/29599 - ------------------------- - -The `concat_idents` feature adds a macro for concatenating multiple identifiers -into one identifier. - -## Examples - -```rust -#![feature(concat_idents)] - -fn main() { - fn foobar() -> u32 { 23 } - let f = concat_idents!(foo, bar); - assert_eq!(f(), 23); -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/core-intrinsics.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/core-intrinsics.md deleted file mode 100644 index 28ad3525ef7a..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/core-intrinsics.md +++ /dev/null @@ -1,5 +0,0 @@ -# `core_intrinsics` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/core-panic.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/core-panic.md deleted file mode 100644 index c197588404c9..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/core-panic.md +++ /dev/null @@ -1,5 +0,0 @@ -# `core_panic` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/core-private-bignum.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/core-private-bignum.md deleted file mode 100644 index f85811c545e4..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/core-private-bignum.md +++ /dev/null @@ -1,5 +0,0 @@ -# `core_private_bignum` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/core-private-diy-float.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/core-private-diy-float.md deleted file mode 100644 index 8465921d673b..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/core-private-diy-float.md +++ /dev/null @@ -1,5 +0,0 @@ -# `core_private_diy_float` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/dec2flt.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/dec2flt.md deleted file mode 100644 index 311ab4adcfd7..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/dec2flt.md +++ /dev/null @@ -1,5 +0,0 @@ -# `dec2flt` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/default-free-fn.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/default-free-fn.md deleted file mode 100644 index d40a27dddf36..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/default-free-fn.md +++ /dev/null @@ -1,47 +0,0 @@ -# `default_free_fn` - -The tracking issue for this feature is: [#73014] - -[#73014]: https://github.com/rust-lang/rust/issues/73014 - ------------------------- - -Adds a free `default()` function to the `std::default` module. This function -just forwards to [`Default::default()`], but may remove repetition of the word -"default" from the call site. - -[`Default::default()`]: https://doc.rust-lang.org/nightly/std/default/trait.Default.html#tymethod.default - -Here is an example: - -```rust -#![feature(default_free_fn)] -use std::default::default; - -#[derive(Default)] -struct AppConfig { - foo: FooConfig, - bar: BarConfig, -} - -#[derive(Default)] -struct FooConfig { - foo: i32, -} - -#[derive(Default)] -struct BarConfig { - bar: f32, - baz: u8, -} - -fn main() { - let options = AppConfig { - foo: default(), - bar: BarConfig { - bar: 10.1, - ..default() - }, - }; -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/derive-clone-copy.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/derive-clone-copy.md deleted file mode 100644 index cc603911cbd2..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/derive-clone-copy.md +++ /dev/null @@ -1,5 +0,0 @@ -# `derive_clone_copy` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/derive-eq.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/derive-eq.md deleted file mode 100644 index 68a275f5419d..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/derive-eq.md +++ /dev/null @@ -1,5 +0,0 @@ -# `derive_eq` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/fd-read.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/fd-read.md deleted file mode 100644 index e78d4330abfc..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/fd-read.md +++ /dev/null @@ -1,5 +0,0 @@ -# `fd_read` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/fd.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/fd.md deleted file mode 100644 index 0414244285ba..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/fd.md +++ /dev/null @@ -1,5 +0,0 @@ -# `fd` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/flt2dec.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/flt2dec.md deleted file mode 100644 index 15e62a3a7dad..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/flt2dec.md +++ /dev/null @@ -1,5 +0,0 @@ -# `flt2dec` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/fmt-internals.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/fmt-internals.md deleted file mode 100644 index 7cbe3c89a644..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/fmt-internals.md +++ /dev/null @@ -1,5 +0,0 @@ -# `fmt_internals` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/fn-traits.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/fn-traits.md deleted file mode 100644 index 29a8aecee6c2..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/fn-traits.md +++ /dev/null @@ -1,35 +0,0 @@ -# `fn_traits` - -The tracking issue for this feature is [#29625] - -See Also: [`unboxed_closures`](../language-features/unboxed-closures.md) - -[#29625]: https://github.com/rust-lang/rust/issues/29625 - ----- - -The `fn_traits` feature allows for implementation of the [`Fn*`] traits -for creating custom closure-like types. - -[`Fn*`]: https://doc.rust-lang.org/std/ops/trait.Fn.html - -```rust -#![feature(unboxed_closures)] -#![feature(fn_traits)] - -struct Adder { - a: u32 -} - -impl FnOnce<(u32, )> for Adder { - type Output = u32; - extern "rust-call" fn call_once(self, b: (u32, )) -> Self::Output { - self.a + b.0 - } -} - -fn main() { - let adder = Adder { a: 3 }; - assert_eq!(adder(2), 5); -} -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/int-error-internals.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/int-error-internals.md deleted file mode 100644 index 402e4fa5ef6d..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/int-error-internals.md +++ /dev/null @@ -1,5 +0,0 @@ -# `int_error_internals` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/internal-output-capture.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/internal-output-capture.md deleted file mode 100644 index 7e1241fce985..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/internal-output-capture.md +++ /dev/null @@ -1,5 +0,0 @@ -# `internal_output_capture` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/is-sorted.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/is-sorted.md deleted file mode 100644 index e3b7dc3b28eb..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/is-sorted.md +++ /dev/null @@ -1,11 +0,0 @@ -# `is_sorted` - -The tracking issue for this feature is: [#53485] - -[#53485]: https://github.com/rust-lang/rust/issues/53485 - ------------------------- - -Add the methods `is_sorted`, `is_sorted_by` and `is_sorted_by_key` to `[T]`; -add the methods `is_sorted`, `is_sorted_by` and `is_sorted_by_key` to -`Iterator`. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/libstd-sys-internals.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/libstd-sys-internals.md deleted file mode 100644 index 1b53faa8a007..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/libstd-sys-internals.md +++ /dev/null @@ -1,5 +0,0 @@ -# `libstd_sys_internals` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/libstd-thread-internals.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/libstd-thread-internals.md deleted file mode 100644 index b682d12e7cdd..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/libstd-thread-internals.md +++ /dev/null @@ -1,5 +0,0 @@ -# `libstd_thread_internals` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/print-internals.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/print-internals.md deleted file mode 100644 index a68557872af5..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/print-internals.md +++ /dev/null @@ -1,5 +0,0 @@ -# `print_internals` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/profiler-runtime-lib.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/profiler-runtime-lib.md deleted file mode 100644 index a01f1e73ab40..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/profiler-runtime-lib.md +++ /dev/null @@ -1,5 +0,0 @@ -# `profiler_runtime_lib` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/rt.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/rt.md deleted file mode 100644 index 007acc207a65..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/rt.md +++ /dev/null @@ -1,5 +0,0 @@ -# `rt` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/sort-internals.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/sort-internals.md deleted file mode 100644 index 6f2385e53008..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/sort-internals.md +++ /dev/null @@ -1,5 +0,0 @@ -# `sort_internals` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/str-internals.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/str-internals.md deleted file mode 100644 index af8ef056dbe2..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/str-internals.md +++ /dev/null @@ -1,5 +0,0 @@ -# `str_internals` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/test.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/test.md deleted file mode 100644 index c99584e5fb39..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/test.md +++ /dev/null @@ -1,158 +0,0 @@ -# `test` - -The tracking issue for this feature is: None. - ------------------------- - -The internals of the `test` crate are unstable, behind the `test` flag. The -most widely used part of the `test` crate are benchmark tests, which can test -the performance of your code. Let's make our `src/lib.rs` look like this -(comments elided): - -```rust,no_run -#![feature(test)] - -extern crate test; - -pub fn add_two(a: i32) -> i32 { - a + 2 -} - -#[cfg(test)] -mod tests { - use super::*; - use test::Bencher; - - #[test] - fn it_works() { - assert_eq!(4, add_two(2)); - } - - #[bench] - fn bench_add_two(b: &mut Bencher) { - b.iter(|| add_two(2)); - } -} -``` - -Note the `test` feature gate, which enables this unstable feature. - -We've imported the `test` crate, which contains our benchmarking support. -We have a new function as well, with the `bench` attribute. Unlike regular -tests, which take no arguments, benchmark tests take a `&mut Bencher`. This -`Bencher` provides an `iter` method, which takes a closure. This closure -contains the code we'd like to benchmark. - -We can run benchmark tests with `cargo bench`: - -```bash -$ cargo bench - Compiling adder v0.0.1 (file:///home/steve/tmp/adder) - Running target/release/adder-91b3e234d4ed382a - -running 2 tests -test tests::it_works ... ignored -test tests::bench_add_two ... bench: 1 ns/iter (+/- 0) - -test result: ok. 0 passed; 0 failed; 1 ignored; 1 measured -``` - -Our non-benchmark test was ignored. You may have noticed that `cargo bench` -takes a bit longer than `cargo test`. This is because Rust runs our benchmark -a number of times, and then takes the average. Because we're doing so little -work in this example, we have a `1 ns/iter (+/- 0)`, but this would show -the variance if there was one. - -Advice on writing benchmarks: - - -* Move setup code outside the `iter` loop; only put the part you want to measure inside -* Make the code do "the same thing" on each iteration; do not accumulate or change state -* Make the outer function idempotent too; the benchmark runner is likely to run - it many times -* Make the inner `iter` loop short and fast so benchmark runs are fast and the - calibrator can adjust the run-length at fine resolution -* Make the code in the `iter` loop do something simple, to assist in pinpointing - performance improvements (or regressions) - -## Gotcha: optimizations - -There's another tricky part to writing benchmarks: benchmarks compiled with -optimizations activated can be dramatically changed by the optimizer so that -the benchmark is no longer benchmarking what one expects. For example, the -compiler might recognize that some calculation has no external effects and -remove it entirely. - -```rust,no_run -#![feature(test)] - -extern crate test; -use test::Bencher; - -#[bench] -fn bench_xor_1000_ints(b: &mut Bencher) { - b.iter(|| { - (0..1000).fold(0, |old, new| old ^ new); - }); -} -``` - -gives the following results - -```text -running 1 test -test bench_xor_1000_ints ... bench: 0 ns/iter (+/- 0) - -test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured -``` - -The benchmarking runner offers two ways to avoid this. Either, the closure that -the `iter` method receives can return an arbitrary value which forces the -optimizer to consider the result used and ensures it cannot remove the -computation entirely. This could be done for the example above by adjusting the -`b.iter` call to - -```rust -# struct X; -# impl X { fn iter(&self, _: F) where F: FnMut() -> T {} } let b = X; -b.iter(|| { - // Note lack of `;` (could also use an explicit `return`). - (0..1000).fold(0, |old, new| old ^ new) -}); -``` - -Or, the other option is to call the generic `test::black_box` function, which -is an opaque "black box" to the optimizer and so forces it to consider any -argument as used. - -```rust -#![feature(test)] - -extern crate test; - -# fn main() { -# struct X; -# impl X { fn iter(&self, _: F) where F: FnMut() -> T {} } let b = X; -b.iter(|| { - let n = test::black_box(1000); - - (0..n).fold(0, |a, b| a ^ b) -}) -# } -``` - -Neither of these read or modify the value, and are very cheap for small values. -Larger values can be passed indirectly to reduce overhead (e.g. -`black_box(&huge_struct)`). - -Performing either of the above changes gives the following benchmarking results - -```text -running 1 test -test bench_xor_1000_ints ... bench: 131 ns/iter (+/- 3) - -test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured -``` - -However, the optimizer can still modify a testcase in an undesirable manner -even when using either of the above. diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/thread-local-internals.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/thread-local-internals.md deleted file mode 100644 index e1cdcc339d22..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/thread-local-internals.md +++ /dev/null @@ -1,5 +0,0 @@ -# `thread_local_internals` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/trace-macros.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/trace-macros.md deleted file mode 100644 index 41aa286e69bf..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/trace-macros.md +++ /dev/null @@ -1,39 +0,0 @@ -# `trace_macros` - -The tracking issue for this feature is [#29598]. - -[#29598]: https://github.com/rust-lang/rust/issues/29598 - ------------------------- - -With `trace_macros` you can trace the expansion of macros in your code. - -## Examples - -```rust -#![feature(trace_macros)] - -fn main() { - trace_macros!(true); - println!("Hello, Rust!"); - trace_macros!(false); -} -``` - -The `cargo build` output: - -```txt -note: trace_macro - --> src/main.rs:5:5 - | -5 | println!("Hello, Rust!"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: expanding `println! { "Hello, Rust!" }` - = note: to `print ! ( concat ! ( "Hello, Rust!" , "\n" ) )` - = note: expanding `print! { concat ! ( "Hello, Rust!" , "\n" ) }` - = note: to `$crate :: io :: _print ( format_args ! ( concat ! ( "Hello, Rust!" , "\n" ) ) - )` - - Finished dev [unoptimized + debuginfo] target(s) in 0.60 secs -``` diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/update-panic-count.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/update-panic-count.md deleted file mode 100644 index d315647ba104..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/update-panic-count.md +++ /dev/null @@ -1,5 +0,0 @@ -# `update_panic_count` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/windows-c.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/windows-c.md deleted file mode 100644 index 3f833eb3d093..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/windows-c.md +++ /dev/null @@ -1,5 +0,0 @@ -# `windows_c` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/windows-handle.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/windows-handle.md deleted file mode 100644 index f47a8425045b..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/windows-handle.md +++ /dev/null @@ -1,5 +0,0 @@ -# `windows_handle` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/windows-net.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/windows-net.md deleted file mode 100644 index 174960d4f004..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/windows-net.md +++ /dev/null @@ -1,5 +0,0 @@ -# `windows_net` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/library-features/windows-stdio.md b/tools/bookrunner/rust-doc/unstable-book/src/library-features/windows-stdio.md deleted file mode 100644 index 4d361442386a..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/library-features/windows-stdio.md +++ /dev/null @@ -1,5 +0,0 @@ -# `windows_stdio` - -This feature is internal to the Rust compiler and is not intended for general use. - ------------------------- diff --git a/tools/bookrunner/rust-doc/unstable-book/src/the-unstable-book.md b/tools/bookrunner/rust-doc/unstable-book/src/the-unstable-book.md deleted file mode 100644 index 554c52c3c9c2..000000000000 --- a/tools/bookrunner/rust-doc/unstable-book/src/the-unstable-book.md +++ /dev/null @@ -1,22 +0,0 @@ -# The Unstable Book - -Welcome to the Unstable Book! This book consists of a number of chapters, -each one organized by a "feature flag." That is, when using an unstable -feature of Rust, you must use a flag, like this: - -```rust -#![feature(box_syntax)] - -fn main() { - let five = box 5; -} -``` - -The `box_syntax` feature [has a chapter][box] describing how to use it. - -[box]: language-features/box-syntax.md - -Because this documentation relates to unstable features, we make no guarantees -that what is contained here is accurate or up to date. It's developed on a -best-effort basis. Each page will have a link to its tracking issue with the -latest developments; you might want to check those as well. diff --git a/tools/bookrunner/src/bookrunner.rs b/tools/bookrunner/src/bookrunner.rs deleted file mode 100644 index ecce5bd3e779..000000000000 --- a/tools/bookrunner/src/bookrunner.rs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -//! Data structures representing the book report and their utilities. - -use std::fmt::{Display, Formatter, Result, Write}; - -/// This data structure holds the results of running a test or a suite. -#[derive(Clone, Debug)] -pub struct Node { - pub name: String, - pub num_pass: u32, - pub num_fail: u32, -} - -impl Node { - /// Creates a new test [`Node`]. - pub fn new(name: String, num_pass: u32, num_fail: u32) -> Node { - Node { name, num_pass, num_fail } - } -} - -/// Tree data structure representing a book report. `children` -/// represent sub-tests and sub-suites of the current test suite. This tree -/// structure allows us to collect and display a summary for test results in an -/// organized manner. -#[derive(Clone, Debug)] -pub struct Tree { - pub data: Node, - pub children: Vec, -} - -impl Tree { - /// Creates a new [`Tree`] representing a book report or a part of it. - pub fn new(data: Node, children: Vec) -> Tree { - Tree { data, children } - } - - /// Merges two trees, if their root have equal node names, and returns the - /// merged tree. - pub fn merge(mut l: Tree, r: Tree) -> Option { - if l.data.name != r.data.name { - return None; - } - // For each subtree of `r`... - for cnr in r.children { - // Look for a subtree of `l` with an equal root node name. - let index = l.children.iter().position(|cnl| cnl.data.name == cnr.data.name); - if let Some(index) = index { - // If you find one, merge it with `r`'s subtree. - let cnl = l.children.remove(index); - l.children.insert(index, Tree::merge(cnl, cnr)?); - } else { - // Otherwise, `r`'s subtree is new. So, add it to `l`'s - // list of subtrees. - l.children.push(cnr); - } - } - Some(Tree::new( - Node::new( - l.data.name, - l.data.num_pass + r.data.num_pass, - l.data.num_fail + r.data.num_fail, - ), - l.children, - )) - } - - /// A helper format function that indents each level of the tree. - fn fmt_aux(&self, p: usize, f: &mut Formatter<'_>) -> Result { - // Do not print line numbers. - if self.children.is_empty() { - return Ok(()); - } - // Write `p` spaces into the formatter. - f.write_fmt(format_args!("{:p$}", ""))?; - f.write_str(&self.data.name)?; - if self.data.num_pass > 0 { - f.write_fmt(format_args!(" ✔️ {}", self.data.num_pass))?; - } - if self.data.num_fail > 0 { - f.write_fmt(format_args!(" ❌ {}", self.data.num_fail))?; - } - f.write_char('\n')?; - for cn in &self.children { - cn.fmt_aux(p + 2, f)?; - } - Ok(()) - } -} - -impl Display for Tree { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - self.fmt_aux(0, f) - } -} diff --git a/tools/bookrunner/src/books.rs b/tools/bookrunner/src/books.rs deleted file mode 100644 index be0c39497795..000000000000 --- a/tools/bookrunner/src/books.rs +++ /dev/null @@ -1,575 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -//! Utilities to extract examples from Rust books, run them through Kani, and -//! display their results. - -extern crate rustc_span; - -use crate::{ - bookrunner, - litani::{Litani, LitaniPipeline, LitaniRun}, - util::{self, FailStep, TestProps}, -}; -use inflector::cases::{snakecase::to_snake_case, titlecase::to_title_case}; -use pulldown_cmark::{Event, Parser, Tag, TagEnd}; -use rustc_span::edition::Edition; -use rustdoc::{ - doctest::{make_test, Tester}, - html::markdown::{find_testable_code, ErrorCodes, Ignore, LangString}, -}; -use std::{ - collections::{HashMap, HashSet}, - ffi::OsStr, - fmt::Write, - fs, - io::BufReader, - iter::FromIterator, - path::{Path, PathBuf}, - str::FromStr, -}; -use walkdir::WalkDir; - -// Books may include a `SUMMARY.md` file or not. If they do, the info in -// `SummaryData` is helpful to parse the hierarchy, otherwise we use a -// `DirectoryData` structure - -// Data needed for parsing a book with a summary file -struct SummaryData { - // Path to the summary file - summary_path: PathBuf, - // Line that indicates the start of the summary section - summary_start: String, -} - -// Data needed for parsing book without a summary file -struct DirectoryData { - // Directory to be processed, starting from root of the book - src: PathBuf, - // Directory where the examples extracted from the book should reside - dest: PathBuf, -} - -// Data structure representing a Rust book -struct Book { - // Name of the book - name: String, - // Default Rust edition - default_edition: Edition, - // Data about the summary file - summary_data: Option, - // Data about the source/destination directories - directory_data: Option, - // Path to the `book.toml` file - toml_path: PathBuf, - // The hierarchy map used for example extraction - hierarchy: HashMap, -} - -impl Book { - /// Parse the chapter/section hierarchy and set the default edition - fn parse_hierarchy(&mut self) { - if self.summary_data.is_some() { - assert!(self.directory_data.is_none()); - self.parse_hierarchy_with_summary(); - } else { - assert!(self.directory_data.is_some()); - self.parse_hierarchy_without_summary(); - } - self.default_edition = self.get_rust_edition().unwrap_or(Edition::Edition2015); - } - - /// Parses the chapter/section hierarchy in the markdown file specified by - /// `summary_path` and returns a mapping from markdown files containing rust - /// code to corresponding directories where the extracted rust code should - /// reside. - fn parse_hierarchy_with_summary(&mut self) { - let summary_path = &self.summary_data.as_ref().unwrap().summary_path; - let summary_start = &self.summary_data.as_ref().unwrap().summary_start; - 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()), - "Error: The start of {} summary file changed.", - self.name - ); - // Skip the `start` of the summary. - let n = Parser::new(summary_start.as_str()).count(); - let parser = Parser::new(&summary).skip(n); - // Set `self.name` as the root of the hierarchical path. - let mut hierarchy_path: PathBuf = - ["tests", "bookrunner", "books", self.name.as_str()].iter().collect(); - let mut prev_event_is_text_or_code = false; - let mut current_link_url = String::from(""); - for event in parser { - match event { - Event::End(TagEnd::Item) => { - // Pop the current chapter/section from the hierarchy once - // we are done processing it and its subsections. - hierarchy_path.pop(); - prev_event_is_text_or_code = false; - } - Event::Start(Tag::Link { dest_url, .. }) => { - current_link_url = dest_url.into_string(); - } - Event::End(TagEnd::Link) => { - // At the start of the link tag, the hierarchy does not yet - // contain the title of the current chapter/section. So, we wait - // for the end of the link tag before adding the path and - // hierarchy of the current chapter/section to the map. - let mut full_path = summary_dir.clone(); - full_path.extend(current_link_url.split('/')); - self.hierarchy.insert(full_path, hierarchy_path.clone()); - prev_event_is_text_or_code = false; - } - Event::Text(text) | Event::Code(text) => { - // Remove characters that are problematic to the file system or - // terminal. - let text = text.replace(&['/', '(', ')', '\''][..], "_"); - // Does the chapter/section title contain normal text and inline - // code? - if prev_event_is_text_or_code { - // If so, we combine them into one hierarchy level. - let prev_text = - hierarchy_path.file_name().unwrap().to_str().unwrap().to_string(); - hierarchy_path.pop(); - hierarchy_path.push(format!("{prev_text}{text}")); - } else { - // If not, add the current title to the hierarchy. - hierarchy_path.push(&text); - } - prev_event_is_text_or_code = true; - } - _ => (), - } - } - } - - /// Parses books that do not have a `SUMMARY.md` file (i.e., a table of - /// contents). We parse them manually and make a "best effort" to make it - /// look like the online version. - fn parse_hierarchy_without_summary(&mut self) { - let directory_data = self.directory_data.as_ref().unwrap(); - let src = &directory_data.src; - let dest = &directory_data.dest; - let mut src_prefix: PathBuf = src.clone(); - let mut dest_prefix: PathBuf = dest.clone(); - for entry in WalkDir::new(&src_prefix) { - let entry = entry.unwrap().into_path(); - // `WalkDir` returns entries in a depth-first fashion. Once we are done - // processing a directory, it will jump to a different child entry of a - // predecessor. To copy examples to the correct location, we need to - // know how far back we jumped and update `dest_prefix` accordingly. - while !entry.starts_with(&src_prefix) { - src_prefix.pop(); - dest_prefix.pop(); - } - if entry.is_dir() { - src_prefix.push(entry.file_name().unwrap()); - // Follow the book's title case format for directories. - dest_prefix.push(to_title_case(entry.file_name().unwrap().to_str().unwrap())); - } else { - // Only process markdown files. - if entry.extension() == Some(OsStr::new("md")) { - let entry_stem = entry.file_stem().unwrap().to_str().unwrap(); - // If a file has the stem name as a sibling directory... - if src_prefix.join(entry.file_stem().unwrap()).exists() { - // Its extracted examples should reside under that - // directory. - self.hierarchy - .insert(entry.clone(), dest_prefix.join(to_title_case(entry_stem))); - } else { - // Otherwise, follow the book's snake case format for files. - self.hierarchy - .insert(entry.clone(), dest_prefix.join(to_snake_case(entry_stem))); - } - } - } - } - } - - // Get the Rust edition from the `book.toml` file - fn get_rust_edition(&self) -> Option { - let file = fs::read_to_string(&self.toml_path).unwrap(); - let toml_data: toml::Value = toml::from_str(&file).unwrap(); - // The Rust edition is specified in the `rust.edition` attribute - let rust_block = toml_data.get("rust")?; - let edition_attr = rust_block.get("edition")?; - let edition_str = edition_attr.as_str()?; - Some(Edition::from_str(edition_str).unwrap()) - } - - /// Extracts examples from the markdown files specified by each key in the given - /// `map`, pre-processes those examples, and saves them in the directory - /// specified by the corresponding value. - fn extract_examples(&self) { - let mut config_paths = get_config_paths(self.name.as_str()); - for (par_from, par_to) in &self.hierarchy { - extract(par_from, par_to, &mut config_paths, self.default_edition); - } - if !config_paths.is_empty() { - panic!( - "Error: The examples corresponding to the following config files \ - were not encountered in the pre-processing step:\n{}This is most \ - likely because the line numbers of the config files are not in \ - sync with the line numbers of the corresponding code blocks in \ - the latest versions of the Rust books. Please update the line \ - numbers of the config files and rerun the program.", - paths_to_string(config_paths) - ); - } - } -} -/// Set up [The Rust Reference](https://doc.rust-lang.org/nightly/reference) -/// book. -fn setup_reference_book() -> Book { - let summary_data = SummaryData { - summary_start: "# The Rust Reference\n\n[Introduction](introduction.md)".to_string(), - summary_path: ["tools", "bookrunner", "rust-doc", "reference", "src", "SUMMARY.md"] - .iter() - .collect(), - }; - Book { - name: "The Rust Reference".to_string(), - summary_data: Some(summary_data), - directory_data: None, - toml_path: ["tools", "bookrunner", "rust-doc", "reference", "book.toml"].iter().collect(), - hierarchy: HashMap::from_iter([( - ["tools", "bookrunner", "rust-doc", "reference", "src", "introduction.md"] - .iter() - .collect(), - ["tests", "bookrunner", "books", "The Rust Reference", "Introduction"].iter().collect(), - )]), - default_edition: Edition::Edition2015, - } -} - -/// Set up [The Rustonomicon](https://doc.rust-lang.org/nightly/nomicon) book. -fn setup_nomicon_book() -> Book { - let summary_data = SummaryData { - summary_path: ["tools", "bookrunner", "rust-doc", "nomicon", "src", "SUMMARY.md"] - .iter() - .collect(), - summary_start: "# Summary\n\n[Introduction](intro.md)".to_string(), - }; - Book { - name: "The Rustonomicon".to_string(), - summary_data: Some(summary_data), - directory_data: None, - toml_path: ["tools", "bookrunner", "rust-doc", "nomicon", "book.toml"].iter().collect(), - hierarchy: HashMap::from_iter([( - ["tools", "bookrunner", "rust-doc", "nomicon", "src", "intro.md"].iter().collect(), - ["tests", "bookrunner", "books", "The Rustonomicon", "Introduction"].iter().collect(), - )]), - default_edition: Edition::Edition2015, - } -} - -/// Set up the -/// [Rust Unstable Book](https://doc.rust-lang.org/beta/unstable-book/). -fn setup_unstable_book() -> Book { - let directory_data = DirectoryData { - src: ["tools", "bookrunner", "rust-doc", "unstable-book", "src"].iter().collect(), - dest: ["tests", "bookrunner", "books", "The Unstable Book"].iter().collect(), - }; - Book { - name: "The Rust Unstable Book".to_string(), - summary_data: None, - directory_data: Some(directory_data), - toml_path: ["tools", "bookrunner", "rust-doc", "unstable-book", "book.toml"] - .iter() - .collect(), - hierarchy: HashMap::new(), - default_edition: Edition::Edition2015, - } -} - -/// Set up the -/// [Rust by Example](https://doc.rust-lang.org/nightly/rust-by-example) book. -fn setup_rust_by_example_book() -> Book { - let summary_data = SummaryData { - summary_path: ["tools", "bookrunner", "rust-doc", "rust-by-example", "src", "SUMMARY.md"] - .iter() - .collect(), - summary_start: "# Summary\n\n[Introduction](index.md)".to_string(), - }; - Book { - name: "Rust by Example".to_string(), - summary_data: Some(summary_data), - directory_data: None, - toml_path: ["tools", "bookrunner", "rust-doc", "rust-by-example", "book.toml"] - .iter() - .collect(), - hierarchy: HashMap::from_iter([( - ["tools", "bookrunner", "rust-doc", "rust-by-example", "src", "index.md"] - .iter() - .collect(), - ["tests", "bookrunner", "books", "Rust by Example", "Introduction"].iter().collect(), - )]), - default_edition: Edition::Edition2015, - } -} - -/// This data structure contains the code and configs of an example in the Rust books. -struct Example { - /// The example code extracted from a codeblock. - code: String, - // Line number of the code block. - line: usize, - // Configurations in the header of the codeblock. - config: LangString, -} - -/// Data structure representing a list of examples. Mainly for implementing the -/// [`Tester`] trait. -struct Examples(Vec); - -impl Tester for Examples { - fn add_test(&mut self, test: String, config: LangString, line: usize) { - if config.ignore != Ignore::All { - self.0.push(Example { code: test, line, config }) - } - } -} - -/// Applies the diff corresponding to `example` with parent `path` (if it exists). -fn apply_diff(path: &Path, example: &mut Example, config_paths: &mut HashSet) { - let config_dir: PathBuf = ["tools", "bookrunner", "configs"].iter().collect(); - let test_dir: PathBuf = ["tests", "bookrunner"].iter().collect(); - // `path` has the following form: - // `tests/bookrunner/books/ - // If `example` has a custom diff file, the path to the diff file will have - // the following form: - // `tools/bookrunner/configs/books//.diff` - // where is the same for both paths. - let mut diff_path = config_dir.join(path.strip_prefix(&test_dir).unwrap()); - diff_path.extend_one(format!("{}.diff", example.line)); - if diff_path.exists() { - config_paths.remove(&diff_path); - let mut code_lines: Vec<_> = example.code.lines().collect(); - let diff = fs::read_to_string(diff_path).unwrap(); - for line in diff.lines() { - // `*.diff` files have a simple format: - // `- ` for removing lines. - // `+ ` for inserting lines. - // Notice that for a series of `+` and `-`, the developer must keep - // track of the changing line numbers. - let mut split = line.splitn(3, ' '); - let symbol = split.next().unwrap(); - let line = split.next().unwrap().parse::().unwrap() - 1; - if symbol == "+" { - let diff = split.next().unwrap(); - code_lines.insert(line, diff); - } else { - code_lines.remove(line); - } - } - example.code = code_lines.join("\n"); - } -} - -/// Prepends example properties in `example.config` to the code in `example.code`. -fn prepend_props(path: &Path, example: &mut Example, config_paths: &mut HashSet) { - let config_dir: PathBuf = ["tools", "bookrunner", "configs"].iter().collect(); - let test_dir: PathBuf = ["tests", "bookrunner"].iter().collect(); - // `path` has the following form: - // `tests/bookrunner/books/ - // If `example` has a custom props file, the path to the props file will - // have the following form: - // `tools/bookrunner/configs/books//.props` - // where is the same for both paths. - let mut props_path = config_dir.join(path.strip_prefix(&test_dir).unwrap()); - props_path.extend_one(format!("{}.props", example.line)); - let mut props = if props_path.exists() { - config_paths.remove(&props_path); - util::parse_test_header(&props_path) - } else { - TestProps::new(path.to_path_buf(), None, Vec::new(), Vec::new()) - }; - // Add edition flag to the example - let edition_year = format!("{}", example.config.edition.unwrap()); - props.rustc_args.push(String::from("--edition")); - props.rustc_args.push(edition_year); - - if props.fail_step.is_none() { - if example.config.compile_fail { - // Most examples with `compile_fail` annotation fail because of - // check errors. This heuristic can be overridden by manually - //specifying the fail step in the corresponding config file. - props.fail_step = Some(FailStep::Check); - } else if example.config.should_panic { - // Kani should catch run-time errors. - props.fail_step = Some(FailStep::Verification); - } - } - example.code = format!("{props}{}", example.code); -} - -/// Extracts examples from the markdown file specified by `par_from`, -/// pre-processes those examples, and saves them in the directory specified by -/// `par_to`. -fn extract( - par_from: &Path, - par_to: &Path, - config_paths: &mut HashSet, - default_edition: Edition, -) { - 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 { - apply_diff(par_to, &mut example, config_paths); - example.config.edition = Some(example.config.edition.unwrap_or(default_edition)); - example.code = make_test( - &example.code, - None, - false, - &Default::default(), - example.config.edition.unwrap(), - None, - ) - .0; - prepend_props(par_to, &mut example, config_paths); - let rs_path = par_to.join(format!("{}.rs", example.line)); - fs::create_dir_all(rs_path.parent().unwrap()).unwrap(); - fs::write(rs_path, example.code).unwrap(); - } -} - -/// Returns a set of paths to the config files for examples in the Rust books. -fn get_config_paths(book_name: &str) -> HashSet { - let config_dir: PathBuf = - ["tools", "bookrunner", "configs", "books", book_name].iter().collect(); - let mut config_paths = HashSet::new(); - if config_dir.exists() { - for entry in WalkDir::new(config_dir) { - let entry = entry.unwrap().into_path(); - if entry.is_file() { - config_paths.insert(entry); - } - } - } - config_paths -} - -/// Pretty prints the `paths` set. -fn paths_to_string(paths: HashSet) -> String { - let mut f = String::new(); - for path in paths { - f.write_fmt(format_args!(" {:?}\n", path.to_str().unwrap())).unwrap(); - } - f -} - -/// Creates a new [`bookrunner::Tree`] from `path`, and a test `result`. -fn tree_from_path(mut path: Vec, result: bool) -> bookrunner::Tree { - assert!(!path.is_empty(), "Error: `path` must contain at least 1 element."); - let mut tree = bookrunner::Tree::new( - bookrunner::Node::new( - path.pop().unwrap(), - if result { 1 } else { 0 }, - if result { 0 } else { 1 }, - ), - vec![], - ); - for _ in 0..path.len() { - tree = bookrunner::Tree::new( - bookrunner::Node::new(path.pop().unwrap(), tree.data.num_pass, tree.data.num_fail), - vec![tree], - ); - } - tree -} - -/// Parses a `litani` run and generates a bookrunner tree from it -fn parse_litani_output(path: &Path) -> bookrunner::Tree { - let file = fs::File::open(path).unwrap(); - let reader = BufReader::new(file); - let run: LitaniRun = serde_json::from_reader(reader).unwrap(); - let mut tests = - bookrunner::Tree::new(bookrunner::Node::new(String::from("bookrunner"), 0, 0), vec![]); - let pipelines = run.get_pipelines(); - for pipeline in pipelines { - let (ns, l) = parse_log_line(&pipeline); - tests = bookrunner::Tree::merge(tests, tree_from_path(ns, l)).unwrap(); - } - tests -} - -/// Parses a `litani` pipeline and returns a pair containing -/// the path to a test and its result. -fn parse_log_line(pipeline: &LitaniPipeline) -> (Vec, bool) { - let l = pipeline.get_status(); - let name = pipeline.get_name(); - let mut ns: Vec = name.split(&['/', '.'][..]).map(String::from).collect(); - // Remove unnecessary items from the path until "bookrunner" - let dash_index = ns.iter().position(|item| item == "bookrunner").unwrap(); - ns.drain(..dash_index); - // Remove unnecessary "rs" suffix. - ns.pop(); - (ns, l) -} - -/// Format and write a text version of the bookrunner report -fn generate_text_bookrunner(bookrunner: bookrunner::Tree, path: &Path) { - let bookrunner_str = format!( - "# of tests: {}\t✔️ {}\t❌ {}\n{}", - bookrunner.data.num_pass + bookrunner.data.num_fail, - bookrunner.data.num_pass, - bookrunner.data.num_fail, - bookrunner - ); - fs::write(path, bookrunner_str).expect("Error: Unable to write bookrunner results"); -} - -/// Runs examples using Litani build. -fn litani_run_tests() { - let output_prefix: PathBuf = ["build", "output"].iter().collect(); - let output_symlink: PathBuf = output_prefix.join("latest"); - let bookrunner_dir: PathBuf = ["tests", "bookrunner"].iter().collect(); - let stage_names = ["check", "codegen", "verification"]; - - util::add_kani_to_path(); - let mut litani = Litani::init("Book Runner", &stage_names, &output_prefix, &output_symlink); - - // Run all tests under the `tests/bookrunner` directory. - for entry in WalkDir::new(bookrunner_dir) { - let entry = entry.unwrap().into_path(); - if entry.is_file() { - // Ensure that we parse only Rust files by checking their extension - let entry_ext = &entry.extension().and_then(OsStr::to_str); - if let Some("rs") = entry_ext { - let test_props = util::parse_test_header(&entry); - util::add_test_pipeline(&mut litani, &test_props); - } - } - } - litani.run_build(); -} - -/// Extracts examples from the Rust books, run them through Kani, and displays -/// their results in a HTML webpage. -pub fn generate_run() { - let litani_log: PathBuf = ["build", "output", "latest", "run.json"].iter().collect(); - let text_dash: PathBuf = - ["build", "output", "latest", "html", "bookrunner.txt"].iter().collect(); - // Set up books - let books: Vec = vec![ - setup_reference_book(), - setup_nomicon_book(), - setup_unstable_book(), - setup_rust_by_example_book(), - ]; - for mut book in books { - // Parse the chapter/section hierarchy - book.parse_hierarchy(); - // Extract examples, pre-process them, and save them according to the - // parsed hierarchy - book.extract_examples(); - } - // Generate Litani's HTML bookrunner - litani_run_tests(); - // Parse Litani's output - let bookrunner = parse_litani_output(&litani_log); - // Generate text version - generate_text_bookrunner(bookrunner, &text_dash); -} diff --git a/tools/bookrunner/src/litani.rs b/tools/bookrunner/src/litani.rs deleted file mode 100644 index 620aa9c9f0d0..000000000000 --- a/tools/bookrunner/src/litani.rs +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -//! Utilities to interact with the `Litani` build accumulator. - -use pulldown_cmark_escape::StrWrite; -use serde::Deserialize; -use std::collections::HashMap; -use std::path::Path; -use std::process::{Child, Command}; - -/// Data structure representing a full `litani` run. -/// The same representation is used to represent a run -/// in the `run.json` (cache) file generated by `litani` -/// -/// Deserialization is performed automatically for most -/// attributes in such files, but it may require you to -/// extend it if advanced features are used (e.g., pools) -#[derive(Debug, Deserialize)] -pub struct LitaniRun { - pub aux: Option>, - pub project: String, - pub version: String, - pub version_major: u32, - pub version_minor: u32, - pub version_patch: u32, - pub release_candidate: bool, - pub run_id: String, - pub start_time: String, - pub parallelism: LitaniParalellism, - pub latest_symlink: Option, - pub end_time: String, - pub pipelines: Vec, -} - -impl LitaniRun { - pub fn get_pipelines(self) -> Vec { - self.pipelines - } -} - -#[derive(Debug, Deserialize)] -pub struct LitaniParalellism { - pub trace: Vec, - pub max_paralellism: Option, - pub n_proc: u32, -} - -#[derive(Debug, Deserialize)] -pub struct LitaniTrace { - pub running: u32, - pub finished: u32, - pub total: u32, - pub time: String, -} - -#[derive(Debug, Deserialize)] -pub struct LitaniPipeline { - pub name: String, - pub ci_stages: Vec, - pub url: String, - pub status: String, -} - -impl LitaniPipeline { - pub fn get_name(&self) -> &String { - &self.name - } - - pub fn get_status(&self) -> bool { - match self.status.as_str() { - "fail" => false, - "success" => true, - _ => panic!("pipeline status is not \"fail\" nor \"success\""), - } - } -} - -#[derive(Debug, Deserialize)] -pub struct LitaniStage { - pub jobs: Vec, - pub progress: u32, - pub complete: bool, - pub status: String, - pub url: String, - pub name: String, -} - -// Some attributes in litani's `jobs` are not always included -// or they are null, so we use `Option<...>` to deserialize them -#[derive(Debug, Deserialize)] -pub struct LitaniJob { - pub wrapper_arguments: LitaniWrapperArguments, - pub complete: bool, - pub start_time: Option, - pub timeout_reached: Option, - pub command_return_code: Option, - pub memory_trace: Option>, - pub loaded_outcome_dict: Option>, - pub outcome: Option, - pub wrapper_return_code: Option, - pub stdout: Option>, - pub stderr: Option>, - pub end_time: Option, - pub duration_str: Option, - pub duration: Option, -} - -// Some attributes in litani's `wrapper_arguments` are not always included -// or they are null, so we use `Option<...>` to deserialize them -#[derive(Debug, Deserialize)] -pub struct LitaniWrapperArguments { - pub subcommand: String, - pub verbose: bool, - pub very_verbose: bool, - pub inputs: Vec, - pub command: String, - pub outputs: Option>, - pub pipeline_name: String, - pub ci_stage: String, - pub cwd: Option, - pub timeout: Option, - pub timeout_ok: Option, - pub timeout_ignore: Option, - pub ignore_returns: Option, - pub ok_returns: Vec, - pub outcome_table: Option>, - pub interleave_stdout_stderr: bool, - pub stdout_file: Option, - pub stderr_file: Option, - pub pool: Option, - pub description: String, - pub profile_memory: bool, - pub profile_memory_interval: u32, - pub phony_outputs: Option>, - pub tags: Option, - pub status_file: String, - pub job_id: String, -} - -/// Data structure representing a `Litani` build. -pub struct Litani { - /// A buffer of the `spawn`ed Litani jobs so far. `Litani` takes some time - /// to execute each `add-job` command and executing thousands of them - /// sequentially takes a considerable amount of time. To speed up the - /// execution of those commands, we spawn those commands sequentially (as - /// normal). However, instead of `wait`ing for each process to terminate, - /// we add its handle to a buffer of the `spawn`ed processes and continue - /// with our program. Once we are done adding jobs, we wait for all of them - /// to terminate before we run the `run-build` command. - spawned_commands: Vec, -} - -impl Litani { - /// Sets up a new [`Litani`] run. - pub fn init( - project_name: &str, - stage_names: &[&str], - output_prefix: &Path, - output_symlink: &Path, - ) -> Self { - Command::new("litani") - .args([ - "init", - "--project-name", - project_name, - "--output-prefix", - output_prefix.to_str().unwrap(), - "--output-symlink", - output_symlink.to_str().unwrap(), - "--stages", - ]) - .args(stage_names) - .spawn() - .unwrap() - .wait() - .unwrap(); - Self { spawned_commands: Vec::new() } - } - - /// Adds a single command with its dependencies. - #[allow(clippy::too_many_arguments)] - pub fn add_job( - &mut self, - command: &Command, - inputs: &[&Path], - outputs: &[&Path], - description: &str, - pipeline: &str, - stage: &str, - exit_status: i32, - timeout: u32, - ) { - let mut job = Command::new("litani"); - // The given command may contain additional env vars. Prepend those vars - // to the command before passing it to Litani. - let job_envs: HashMap<_, _> = job.get_envs().collect(); - let mut new_envs = String::new(); - command.get_envs().fold(&mut new_envs, |fmt, (k, v)| { - if !job_envs.contains_key(k) { - fmt.write_fmt(format_args!( - "{}=\"{}\" ", - k.to_str().unwrap(), - v.unwrap().to_str().unwrap() - )) - .unwrap(); - } - fmt - }); - job.args([ - "add-job", - "--command", - &format!("{new_envs}{command:?}"), - "--description", - description, - "--pipeline-name", - pipeline, - "--ci-stage", - stage, - "--ok-returns", - &exit_status.to_string(), - "--timeout", - &timeout.to_string(), - ]); - if !inputs.is_empty() { - job.arg("--inputs").args(inputs); - } - if !outputs.is_empty() { - job.arg("--outputs").args(outputs).arg("--phony-outputs").args(outputs); - } - // Start executing the command, but do not wait for it to terminate. - self.spawned_commands.push(job.spawn().unwrap()); - } - - /// Starts a [`Litani`] run. - pub fn run_build(&mut self) { - // Wait for all spawned processes to terminate. - for command in self.spawned_commands.iter_mut() { - command.wait().unwrap(); - } - self.spawned_commands.clear(); - // Run `run-build` command and wait for it to finish. - Command::new("litani") - .args(["run-build", "--no-pipeline-dep-graph"]) - .spawn() - .unwrap() - .wait() - .unwrap(); - } -} diff --git a/tools/bookrunner/src/main.rs b/tools/bookrunner/src/main.rs deleted file mode 100644 index 65fd9196b365..000000000000 --- a/tools/bookrunner/src/main.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -#![feature(extend_one)] -#![feature(rustc_private)] - -mod bookrunner; -mod books; -mod litani; -mod util; - -fn main() { - books::generate_run(); -} diff --git a/tools/bookrunner/src/util.rs b/tools/bookrunner/src/util.rs deleted file mode 100644 index 542ad70dbc96..000000000000 --- a/tools/bookrunner/src/util.rs +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -//! Utilities types and procedures to parse and run tests. -//! -//! TODO: The types and procedures in this modules are similar to the ones in -//! `compiletest`. Consider using `Litani` to run the test suites (see -//! [issue #390](https://github.com/model-checking/kani/issues/390)). - -use crate::litani::Litani; -use std::{ - env, - fmt::{self, Display, Formatter, Write}, - fs::File, - io::{BufRead, BufReader}, - path::{Path, PathBuf}, - process::Command, -}; - -/// Step at which Kani should panic. -#[derive(PartialEq, Eq)] -pub enum FailStep { - /// Kani panics before the codegen step (up to MIR generation). This step - /// runs the same checks on the test code as `cargo check` including syntax, - /// type, name resolution, and borrow checks. - Check, - /// Kani panics at the codegen step because the test code uses unimplemented - /// and/or unsupported features. - Codegen, - /// Kani panics after the codegen step because of verification failures or - /// other CBMC errors. - Verification, -} - -impl Display for FailStep { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let str = match self { - FailStep::Check => "check", - FailStep::Codegen => "codegen", - FailStep::Verification => "verify", - }; - f.write_str(str) - } -} - -/// Data structure representing properties specific to each test. -pub struct TestProps { - pub path: PathBuf, - /// How far this test should proceed to start failing. - pub fail_step: Option, - /// Extra arguments to pass to `rustc`. - pub rustc_args: Vec, - /// Extra arguments to pass to Kani. - pub kani_args: Vec, -} - -impl TestProps { - /// Creates a new instance of [`TestProps`] for a test. - pub fn new( - path: PathBuf, - fail_step: Option, - rustc_args: Vec, - kani_args: Vec, - ) -> Self { - Self { path, fail_step, rustc_args, kani_args } - } -} - -impl Display for TestProps { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(fail_step) = &self.fail_step { - f.write_fmt(format_args!("// kani-{fail_step}-fail\n"))?; - } - if !self.rustc_args.is_empty() { - f.write_str("// compile-flags:")?; - for arg in &self.rustc_args { - f.write_fmt(format_args!(" {arg}"))?; - } - f.write_char('\n')?; - } - if !self.kani_args.is_empty() { - f.write_str("// kani-flags:")?; - for arg in &self.kani_args { - f.write_fmt(format_args!(" {arg}"))?; - } - f.write_char('\n')?; - } - Ok(()) - } -} - -/// Parses strings of the form `kani-*-fail` and returns the step at which Kani is -/// expected to panic. -fn try_parse_fail_step(cur_fail_step: Option, line: &str) -> Option { - let fail_step = if line.contains("kani-check-fail") { - Some(FailStep::Check) - } else if line.contains("kani-codegen-fail") { - Some(FailStep::Codegen) - } else if line.contains("kani-verify-fail") { - Some(FailStep::Verification) - } else { - None - }; - match (cur_fail_step.is_some(), fail_step.is_some()) { - (true, true) => panic!("Error: multiple `kani-*-fail` headers in a single test."), - (false, true) => fail_step, - _ => cur_fail_step, - } -} - -/// Parses strings of the form `-flags: ...` and returns the list of -/// arguments. -fn try_parse_args(cur_args: Vec, name: &str, line: &str) -> Vec { - let name = format!("{name}-flags:"); - let mut split = line.split(&name).skip(1); - let args: Vec = if let Some(rest) = split.next() { - rest.split_whitespace().map(String::from).collect() - } else { - Vec::new() - }; - match (cur_args.is_empty(), args.is_empty()) { - (false, false) => panic!("Error: multiple `{}-flags: ...` headers in a single test.", name), - (true, false) => args, - _ => cur_args, - } -} - -/// Parses and returns the properties in a test file. -pub fn parse_test_header(path: &Path) -> TestProps { - let mut fail_step = None; - let mut rustc_args = Vec::new(); - let mut kani_args = Vec::new(); - let it = BufReader::new(File::open(path).unwrap()); - for line in it.lines() { - let line = line.unwrap(); - let line = line.trim_start(); - if line.is_empty() { - continue; - } - if !line.starts_with("//") { - break; - } - fail_step = try_parse_fail_step(fail_step, line); - rustc_args = try_parse_args(rustc_args, "compile", line); - kani_args = try_parse_args(kani_args, "kani", line); - } - TestProps::new(path.to_path_buf(), fail_step, rustc_args, kani_args) -} - -/// Adds Kani to the current `PATH` environment variable. -pub fn add_kani_to_path() { - let cwd = env::current_dir().unwrap(); - let kani_bin = cwd.join("target").join("kani").join("bin"); - let kani_scripts = cwd.join("scripts"); - env::set_var( - "PATH", - format!("{}:{}:{}", kani_scripts.display(), kani_bin.display(), env::var("PATH").unwrap()), - ); -} - -/// Does Kani catch syntax, type, and borrow errors (if any)? -pub fn add_check_job(litani: &mut Litani, test_props: &TestProps) { - let exit_status = if test_props.fail_step == Some(FailStep::Check) { 1 } else { 0 }; - let mut kani_rustc = Command::new("kani-compiler"); - kani_rustc.args(&test_props.rustc_args).args(["-Z", "no-codegen"]).arg(&test_props.path); - - let mut phony_out = test_props.path.clone(); - phony_out.set_extension("check"); - litani.add_job( - &kani_rustc, - &[&test_props.path], - &[&phony_out], - "Is this valid Rust code?", - test_props.path.to_str().unwrap(), - "check", - exit_status, - 5, - ); -} - -/// Is Kani expected to codegen all the Rust features in the test? -pub fn add_codegen_job(litani: &mut Litani, test_props: &TestProps) { - let exit_status = if test_props.fail_step == Some(FailStep::Codegen) { 1 } else { 0 }; - let mut kani_rustc = Command::new("kani-compiler"); - kani_rustc.args(&test_props.rustc_args).args(["--out-dir", "build/tmp"]).arg(&test_props.path); - - let mut phony_in = test_props.path.clone(); - phony_in.set_extension("check"); - let mut phony_out = test_props.path.clone(); - phony_out.set_extension("codegen"); - litani.add_job( - &kani_rustc, - &[&phony_in], - &[&phony_out], - "Does Kani support all the Rust features used in it?", - test_props.path.to_str().unwrap(), - "codegen", - exit_status, - 10, - ); -} - -// Does verification pass/fail as it is expected to? -pub fn add_verification_job(litani: &mut Litani, test_props: &TestProps) { - let exit_status = if test_props.fail_step == Some(FailStep::Verification) { 10 } else { 0 }; - let mut kani = Command::new("kani"); - // 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(&test_props.kani_args); - if !test_props.rustc_args.is_empty() { - kani.env("RUSTFLAGS", test_props.rustc_args.join(" ")); - } - - let mut phony_in = test_props.path.clone(); - phony_in.set_extension("codegen"); - litani.add_job( - &kani, - &[&phony_in], - &[], - "Can Kani reason about it?", - test_props.path.to_str().unwrap(), - "verification", - exit_status, - 60, - ); -} - -/// Creates a new pipeline for the test specified by `path` consisting of 3 -/// jobs/steps: `check`, `codegen`, and `verification`. -pub fn add_test_pipeline(litani: &mut Litani, test_props: &TestProps) { - // The first step ensures that the Rust code in the test compiles (if it is - // expected to). - add_check_job(litani, test_props); - if test_props.fail_step == Some(FailStep::Check) { - return; - } - // The second step ensures that we can codegen the code in the test. - add_codegen_job(litani, test_props); - if test_props.fail_step == Some(FailStep::Codegen) { - return; - } - // The final step ensures that CBMC can verify the code in the test. - // Notice that 10 is the expected error code for verification failure. - add_verification_job(litani, test_props); -} diff --git a/tools/build-kani/Cargo.toml b/tools/build-kani/Cargo.toml index b75c373655bd..525c060232a7 100644 --- a/tools/build-kani/Cargo.toml +++ b/tools/build-kani/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "build-kani" -version = "0.48.0" +version = "0.49.0" edition = "2021" description = "Builds Kani, Sysroot and release bundle." license = "MIT OR Apache-2.0" diff --git a/tools/build-kani/src/main.rs b/tools/build-kani/src/main.rs index b94e951fe178..f65d52e03fd1 100644 --- a/tools/build-kani/src/main.rs +++ b/tools/build-kani/src/main.rs @@ -97,8 +97,9 @@ fn bundle_kani(dir: &Path) -> Result<()> { cp_dir(&kani_sysroot_lib(), dir)?; cp_dir(&kani_playback_lib().parent().unwrap(), dir)?; - // 5. Record the exact toolchain we use + // 5. Record the exact toolchain and rustc version we use std::fs::write(dir.join("rust-toolchain-version"), env!("RUSTUP_TOOLCHAIN"))?; + std::fs::write(dir.join("rustc-version"), get_rustc_version()?)?; // 6. Include a licensing note cp(Path::new("tools/build-kani/license-notes.txt"), dir)?; @@ -181,6 +182,13 @@ fn cp(src: &Path, dst: &Path) -> Result<()> { Ok(()) } +/// Record version of rustc being used to build Kani +fn get_rustc_version() -> Result { + let output = Command::new("rustc").arg("--version").output(); + let rustc_version = String::from_utf8(output.unwrap().stdout)?; + Ok(rustc_version) +} + /// Copy files from `src` to `dst` that respect the given pattern. pub fn cp_files

(src: &Path, dst: &Path, predicate: P) -> Result<()> where diff --git a/tools/build-kani/src/sysroot.rs b/tools/build-kani/src/sysroot.rs index 3a6239106826..b831ed0d63a8 100644 --- a/tools/build-kani/src/sysroot.rs +++ b/tools/build-kani/src/sysroot.rs @@ -124,7 +124,14 @@ fn build_kani_lib( "--message-format", "json-diagnostic-rendered-ansi", ]; - let mut rustc_args = vec!["--cfg=kani", "--cfg=kani_sysroot", "-Z", "always-encode-mir"]; + let mut rustc_args = vec![ + "--cfg=kani", + "--cfg=kani_sysroot", + "-Z", + "always-encode-mir", + "-Z", + "mir-enable-passes=-RemoveStorageMarkers", + ]; rustc_args.extend_from_slice(extra_rustc_args); let mut cmd = Command::new("cargo") .env("CARGO_ENCODED_RUSTFLAGS", rustc_args.join("\x1f"))