diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0dab9f509..3bc4163ab5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: env: HOST_TARGET: ${{ matrix.host_target }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Show Rust version (stable toolchain) run: | @@ -45,7 +45,7 @@ jobs: # over time). - name: Add cache for cargo id: cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | # Taken from . @@ -57,12 +57,12 @@ jobs: ~/.cargo/bin ~/.cargo/.crates.toml ~/.cargo/.crates2.json - key: cargo-${{ runner.os }}-reset20240331-${{ hashFiles('**/Cargo.lock') }} - restore-keys: cargo-${{ runner.os }}-reset20240331 + key: cargo-${{ runner.os }}-reset20240425-${{ hashFiles('**/Cargo.lock') }} + restore-keys: cargo-${{ runner.os }}-reset20240425 - - name: Install rustup-toolchain-install-master - if: ${{ steps.cache.outputs.cache-hit != 'true' }} - run: cargo install -f rustup-toolchain-install-master + - name: Install tools + if: steps.cache.outputs.cache-hit != 'true' + run: cargo install -f rustup-toolchain-install-master hyperfine - name: Install miri toolchain run: | @@ -78,6 +78,12 @@ jobs: rustc -Vv cargo -V + # The `style` job only runs on Linux; this makes sure the Windows-host-specific + # code is also covered by clippy. + - name: Check clippy + if: matrix.os == 'windows-latest' + run: ./miri clippy -- -D warnings + - name: Test Miri run: ./ci/ci.sh @@ -85,7 +91,7 @@ jobs: name: style checks runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # This is exactly duplicated from above. GHA is pretty terrible when it comes # to avoiding code duplication. @@ -95,7 +101,7 @@ jobs: # over time). - name: Add cache for cargo id: cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | # Taken from . @@ -111,7 +117,7 @@ jobs: restore-keys: cargo-${{ runner.os }}-reset20240331 - name: Install rustup-toolchain-install-master - if: ${{ steps.cache.outputs.cache-hit != 'true' }} + if: steps.cache.outputs.cache-hit != 'true' run: cargo install -f rustup-toolchain-install-master - name: Install "master" toolchain @@ -165,7 +171,7 @@ jobs: name: cronjob failure notification runs-on: ubuntu-latest needs: [build, style] - if: github.event_name == 'schedule' && (failure() || cancelled()) + if: github.event_name == 'schedule' && failure() steps: # Send a Zulip notification - name: Install zulip-send @@ -191,7 +197,7 @@ jobs: The Miri Cronjobs Bot' # Attempt to auto-sync with rustc - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 256 # get a bit more of the history - name: install josh-proxy diff --git a/.gitignore b/.gitignore index 924a93e807..97e006e8b1 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,5 @@ tex/*/out perf.data perf.data.old flamegraph.svg -tests/extern-so/libtestlib.so +tests/native-lib/libtestlib.so .auto-* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60bc1d5282..092ad46a7c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,14 +72,14 @@ For example: You can (cross-)run the entire test suite using: -``` +```sh ./miri test -MIRI_TEST_TARGET=i686-unknown-linux-gnu ./miri test +./miri test --target i686-unknown-linux-gnu ``` `./miri test FILTER` only runs those tests that contain `FILTER` in their filename (including the -base directory, e.g. `./miri test fail` will run all compile-fail tests). These filters are passed -to `cargo test`, so for multiple filters you need to use `./miri test -- FILTER1 FILTER2`. +base directory, e.g. `./miri test fail` will run all compile-fail tests). Multiple filters are +supported: `./miri test FILTER1 FILTER2` runs all tests that contain either string. #### Fine grained logging @@ -139,9 +139,8 @@ and then you can use it as if it was installed by `rustup` as a component of the in the `miri` toolchain's sysroot to prevent conflicts with other toolchains. The Miri binaries in the `cargo` bin directory (usually `~/.cargo/bin`) are managed by rustup. -There's a test for the cargo wrapper in the `test-cargo-miri` directory; run -`./run-test.py` in there to execute it. Like `./miri test`, this respects the -`MIRI_TEST_TARGET` environment variable to execute the test for another target. +There's a test for the cargo wrapper in the `test-cargo-miri` directory; run `./run-test.py` in +there to execute it. You can pass `--target` to execute the test for another target. ### Using a modified standard library @@ -287,3 +286,41 @@ https. Add the following to your `.gitconfig`: [url "git@github.com:"] pushInsteadOf = https://github.com/ ``` + +## Internal environment variables + +The following environment variables are *internal* and must not be used by +anyone but Miri itself. They are used to communicate between different Miri +binaries, and as such worth documenting: + +* `CARGO_EXTRA_FLAGS` is understood by `./miri` and passed to all host cargo invocations. +* `MIRI_BE_RUSTC` can be set to `host` or `target`. It tells the Miri driver to + actually not interpret the code but compile it like rustc would. With `target`, Miri sets + some compiler flags to prepare the code for interpretation; with `host`, this is not done. + This environment variable is useful to be sure that the compiled `rlib`s are compatible + with Miri. +* `MIRI_CALLED_FROM_SETUP` is set during the Miri sysroot build, + which will re-invoke `cargo-miri` as the `rustc` to use for this build. +* `MIRI_CALLED_FROM_RUSTDOC` when set to any value tells `cargo-miri` that it is + running as a child process of `rustdoc`, which invokes it twice for each doc-test + and requires special treatment, most notably a check-only build before interpretation. + This is set by `cargo-miri` itself when running as a `rustdoc`-wrapper. +* `MIRI_CWD` when set to any value tells the Miri driver to change to the given + directory after loading all the source files, but before commencing + interpretation. This is useful if the interpreted program wants a different + working directory at run-time than at build-time. +* `MIRI_LOCAL_CRATES` is set by `cargo-miri` to tell the Miri driver which + crates should be given special treatment in diagnostics, in addition to the + crate currently being compiled. +* `MIRI_ORIG_RUSTDOC` is set and read by different phases of `cargo-miri` to remember the + value of `RUSTDOC` from before it was overwritten. +* `MIRI_REPLACE_LIBRS_IF_NOT_TEST` when set to any value enables a hack that helps bootstrap + run the standard library tests in Miri. +* `MIRI_TEST_TARGET` is set by `./miri test` (and `./x.py test miri`) to tell the test harness about + the chosen target. +* `MIRI_VERBOSE` when set to any value tells the various `cargo-miri` phases to + perform verbose logging. +* `MIRI_HOST_SYSROOT` is set by bootstrap to tell `cargo-miri` which sysroot to use for *host* + operations. +* `RUSTC_BLESS` is set by `./miri test` (and `./x.py test miri`) to indicate bless-mode to the test + harness. diff --git a/Cargo.lock b/Cargo.lock index 4fb479e1c5..417ecb09b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,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", ] @@ -58,21 +58,21 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -85,21 +85,15 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.3.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bstr" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "regex-automata", @@ -117,9 +111,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", ] @@ -140,9 +134,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.86" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9fa1897e4325be0d68d48df6aa1a71ac2ed4d27723887e7754192705350730" +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" [[package]] name = "cfg-if" @@ -150,6 +144,43 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "num-traits", +] + +[[package]] +name = "chrono-tz" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "cipher" version = "0.4.4" @@ -162,9 +193,9 @@ dependencies = [ [[package]] name = "color-eyre" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" dependencies = [ "backtrace", "color-spantrace", @@ -227,9 +258,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" dependencies = [ "crossbeam-utils", ] @@ -252,14 +283,35 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.2" +version = "3.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" dependencies = [ "nix", "windows-sys 0.52.0", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -288,9 +340,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "generic-array" @@ -304,9 +356,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -358,9 +410,9 @@ 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 = "jemalloc-sys" @@ -386,9 +438,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libffi" @@ -411,12 +463,22 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.5", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", ] [[package]] @@ -427,9 +489,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -437,9 +499,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "measureme" @@ -457,9 +519,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 = "memmap2" @@ -484,11 +546,13 @@ name = "miri" version = "0.1.0" dependencies = [ "aes", + "chrono", + "chrono-tz", "colored", "ctrlc", + "directories", "getrandom", "jemalloc-sys", - "lazy_static", "libc", "libffi", "libloading", @@ -503,15 +567,25 @@ dependencies = [ [[package]] name = "nix" -version = "0.27.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.4.2", + "bitflags", "cfg-if", + "cfg_aliases", "libc", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "number_prefix" version = "0.4.0" @@ -533,6 +607,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "owo-colors" version = "3.5.0" @@ -550,9 +630,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -560,15 +640,24 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", +] + +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", ] [[package]] @@ -580,11 +669,49 @@ dependencies = [ "libc", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[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 = "portable-atomic" @@ -610,18 +737,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -658,18 +785,29 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ - "bitflags 1.3.2", + "getrandom", + "libredox", + "thiserror", ] [[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", @@ -679,9 +817,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -690,9 +828,9 @@ dependencies = [ [[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" @@ -729,11 +867,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.2", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -763,18 +901,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", @@ -783,9 +921,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -801,17 +939,23 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[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 = "syn" -version = "2.0.50" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -820,9 +964,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", @@ -832,18 +976,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", @@ -942,9 +1086,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "valuable" @@ -1001,7 +1145,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.3", + "windows-targets 0.52.5", ] [[package]] @@ -1021,17 +1165,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.3", - "windows_aarch64_msvc 0.52.3", - "windows_i686_gnu 0.52.3", - "windows_i686_msvc 0.52.3", - "windows_x86_64_gnu 0.52.3", - "windows_x86_64_gnullvm 0.52.3", - "windows_x86_64_msvc 0.52.3", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -1042,9 +1187,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -1054,9 +1199,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -1066,9 +1211,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.3" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -1078,9 +1229,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -1090,9 +1241,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -1102,9 +1253,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -1114,9 +1265,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "yansi-term" diff --git a/Cargo.toml b/Cargo.toml index 9d24d3c6f4..976bd08086 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,9 @@ smallvec = "1.7" aes = { version = "0.8.3", features = ["hazmat"] } measureme = "11" ctrlc = "3.2.5" +chrono = { version = "0.4.38", default-features = false } +chrono-tz = "0.9" +directories = "5" # Copied from `compiler/rustc/Cargo.toml`. # But only for some targets, it fails for others. Rustc configures this in its CI, but we can't @@ -44,7 +47,6 @@ colored = "2" ui_test = "0.21.1" rustc_version = "0.4" regex = "1.5.5" -lazy_static = "1.4.0" tempfile = "3" [package.metadata.rust-analyzer] diff --git a/README.md b/README.md index 11b7778083..208a8b9ee6 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,34 @@ # Miri -An experimental interpreter for [Rust][rust]'s -[mid-level intermediate representation][mir] (MIR). It can run binaries and -test suites of cargo projects and detect certain classes of -[undefined behavior](https://doc.rust-lang.org/reference/behavior-considered-undefined.html), -for example: +Miri is an [Undefined Behavior][reference-ub] detection tool for Rust. It can run binaries and test +suites of cargo projects and detect unsafe code that fails to uphold its safety requirements. For +instance: * Out-of-bounds memory accesses and use-after-free * Invalid use of uninitialized data * Violation of intrinsic preconditions (an [`unreachable_unchecked`] being reached, calling [`copy_nonoverlapping`] with overlapping ranges, ...) * Not sufficiently aligned memory accesses and references -* Violation of *some* basic type invariants (a `bool` that is not 0 or 1, for example, +* Violation of basic type invariants (a `bool` that is not 0 or 1, for example, or an invalid enum discriminant) * **Experimental**: Violations of the [Stacked Borrows] rules governing aliasing for reference types * **Experimental**: Violations of the [Tree Borrows] aliasing rules, as an optional alternative to [Stacked Borrows] -* **Experimental**: Data races +* **Experimental**: Data races and emulation of weak memory effects, i.e., + atomic reads can return outdated values. On top of that, Miri will also tell you about memory leaks: when there is memory still allocated at the end of the execution, and that memory is not reachable from a global `static`, Miri will raise an error. -Miri supports almost all Rust language features; in particular, unwinding and -concurrency are properly supported (including some experimental emulation of -weak memory effects, i.e., reads can return outdated values). - You can use Miri to emulate programs on other targets, e.g. to ensure that byte-level data manipulation works correctly both on little-endian and big-endian systems. See [cross-interpretation](#cross-interpretation-running-for-different-targets) below. -Miri has already discovered some [real-world bugs](#bugs-found-by-miri). If you +Miri has already discovered many [real-world bugs](#bugs-found-by-miri). If you found a bug with Miri, we'd appreciate if you tell us and we'll add it to the list! @@ -45,24 +40,27 @@ clocks, are replaced by deterministic "fake" implementations. Set (In particular, the "fake" system RNG APIs make Miri **not suited for cryptographic use**! Do not generate keys using Miri.) -All that said, be aware that Miri will **not catch all cases of undefined -behavior** in your program, and cannot run all programs: +All that said, be aware that Miri does **not catch every violation of the Rust specification** in +your program, not least because there is no such specification. Miri uses its own approximation of +what is and is not Undefined Behavior in Rust. To the best of our knowledge, all Undefined Behavior +that has the potential to affect a program's correctness *is* being detected by Miri (modulo +[bugs][I-misses-ub]), but you should consult [the Reference][reference-ub] for the official +definition of Undefined Behavior. Miri will be updated with the Rust compiler to protect against UB +as it is understood by the current compiler, but it makes no promises about future versions of +rustc. -* There are still plenty of open questions around the basic invariants for some - types and when these invariants even have to hold. Miri tries to avoid false - positives here, so if your program runs fine in Miri right now that is by no - means a guarantee that it is UB-free when these questions get answered. +Further caveats that Miri users should be aware of: - In particular, Miri does not check that references point to valid data. * If the program relies on unspecified details of how data is laid out, it will still run fine in Miri -- but might break (including causing UB) on different - compiler versions or different platforms. + compiler versions or different platforms. (You can use `-Zrandomize-layout` + to detect some of these cases.) * Program execution is non-deterministic when it depends, for example, on where exactly in memory allocations end up, or on the exact interleaving of concurrent threads. Miri tests one of many possible executions of your - program. You can alleviate this to some extent by running Miri with different - values for `-Zmiri-seed`, but that will still by far not explore all possible - executions. + program, but it will miss bugs that only occur in a different possible execution. + You can alleviate this to some extent by running Miri with different + values for `-Zmiri-seed`, but that will still by far not explore all possible executions. * Miri runs the program as a platform-independent interpreter, so the program has no access to most platform-specific APIs or FFI. A few APIs have been implemented (such as printing to stdout, accessing environment variables, and @@ -70,8 +68,8 @@ behavior** in your program, and cannot run all programs: not support networking. System API support varies between targets; if you run on Windows it is a good idea to use `--target x86_64-unknown-linux-gnu` to get better support. -* Weak memory emulation may [produce weak behaviours](https://github.com/rust-lang/miri/issues/2301) - unobservable by compiled programs running on real hardware when `SeqCst` fences are used, and it +* Weak memory emulation may [produce weak behaviors](https://github.com/rust-lang/miri/issues/2301) + when `SeqCst` fences are used that are not actually permitted by the Rust memory model, and it cannot produce all behaviors possibly observable on real hardware. Moreover, Miri fundamentally cannot tell you whether your code is *sound*. [Soundness] is the property @@ -87,6 +85,8 @@ coverage. [Stacked Borrows]: https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md [Tree Borrows]: https://perso.crans.org/vanille/treebor/ [Soundness]: https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#soundness-of-code--of-a-library +[reference-ub]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html +[I-misses-ub]: https://github.com/rust-lang/miri/labels/I-misses-UB ## Using Miri @@ -97,14 +97,8 @@ Install Miri on Rust nightly via `rustup`: rustup +nightly component add miri ``` -If `rustup` says the `miri` component is unavailable, that's because not all -nightly releases come with all tools. Check out -[this website](https://rust-lang.github.io/rustup-components-history) to -determine a nightly version that comes with Miri and install that using `rustup -toolchain install nightly-YYYY-MM-DD`. Either way, all of the following commands -assume the right toolchain is pinned via `rustup override set nightly` or -`rustup override set nightly-YYYY-MM-DD`. (Alternatively, use `cargo -+nightly`/`cargo +nightly-YYYY-MM-DD` for each of the following commands.) +All the following commands assume the nightly toolchain is pinned via `rustup override set nightly`. +Alternatively, use `cargo +nightly` for each of the following commands. Now you can run your project in Miri: @@ -118,12 +112,12 @@ dependencies. It will ask you for confirmation before installing anything. example, `cargo miri test filter` only runs the tests containing `filter` in their name. -You can pass arguments to Miri via `MIRIFLAGS`. For example, +You can pass [flags][miri-flags] to Miri via `MIRIFLAGS`. For example, `MIRIFLAGS="-Zmiri-disable-stacked-borrows" cargo miri run` runs the program without checking the aliasing of references. When compiling code via `cargo miri`, the `cfg(miri)` config flag is set for code -that will be interpret under Miri. You can use this to ignore test cases that fail +that will be interpreted under Miri. You can use this to ignore test cases that fail under Miri because they do things Miri does not support: ```rust @@ -141,7 +135,7 @@ interpreter will explicitly tell you when it finds something unsupported: error: unsupported operation: can't call foreign function: bind ... = help: this is likely not a bug in the program; it indicates that the program \ - performed an operation that the interpreter does not support + performed an operation that Miri does not support ``` ### Cross-interpretation: running for different targets @@ -159,10 +153,8 @@ endian-sensitive code. ### Running Miri on CI -To run Miri on CI, make sure that you handle the case where the latest nightly -does not ship the Miri component because it currently does not build. `rustup -toolchain install --component` knows how to handle this situation, so the -following snippet should always work: +When running Miri on CI, use the following snippet to install a nightly toolchain with the Miri +component: ```sh rustup toolchain install nightly --component miri @@ -224,8 +216,12 @@ degree documented below): - `s390x-unknown-linux-gnu` is supported as our "big-endian target of choice". - For every other target with OS `linux`, `macos`, or `windows`, Miri should generally work, but we make no promises and we don't run tests for such targets. -- For targets on other operating systems, even basic operations such as printing to the standard - output might not work, and Miri might fail before even reaching the `main` function. +- We have unofficial support (not maintained by the Miri team itself) for some further operating systems. + - `freebsd`: **maintainer wanted**. Supports `std::env` and parts of `std::{thread, fs}`, but not `std::sync`. + - `android`: **maintainer wanted**. Support very incomplete, but a basic "hello world" works. + - `solaris` / `illumos`: maintained by @devnexen. Support very incomplete, but a basic "hello world" works. + - `wasm`: **maintainer wanted**. Support very incomplete, not even standard output works, but an empty `main` function works. +- For targets on other operating systems, Miri might fail before even reaching the `main` function. However, even for targets that we do support, the degree of support for accessing platform APIs (such as the file system) differs between targets: generally, Linux targets have the best support, @@ -269,25 +265,12 @@ To get a backtrace, you need to disable isolation RUST_BACKTRACE=1 MIRIFLAGS="-Zmiri-disable-isolation" cargo miri test ``` -#### "found possibly newer version of crate `std` which `` depends on" - -Your build directory may contain artifacts from an earlier build that have/have -not been built for Miri. Run `cargo clean` before switching from non-Miri to -Miri builds and vice-versa. - #### "found crate `std` compiled by an incompatible version of rustc" You may be running `cargo miri` with a different compiler version than the one used to build the custom libstd that Miri uses, and Miri failed to detect that. Try running `cargo miri clean`. -#### "no mir for `std::rt::lang_start_internal`" - -This means the sysroot you are using was not compiled with Miri in mind. This -should never happen when you use `cargo miri` because that takes care of setting -up the sysroot. If you are using `miri` (the Miri driver) directly, see the -[contributors' guide](CONTRIBUTING.md) for how to use `./miri` to best do that. - ## Miri `-Z` flags and environment variables [miri-flags]: #miri--z-flags-and-environment-variables @@ -295,6 +278,16 @@ up the sysroot. If you are using `miri` (the Miri driver) directly, see the Miri adds its own set of `-Z` flags, which are usually set via the `MIRIFLAGS` environment variable. We first document the most relevant and most commonly used flags: +* `-Zmiri-address-reuse-rate=` changes the probability that a freed *non-stack* allocation + will be added to the pool for address reuse, and the probability that a new *non-stack* allocation + will be taken from the pool. Stack allocations never get added to or taken from the pool. The + default is `0.5`. +* `-Zmiri-address-reuse-cross-thread-rate=` changes the probability that an allocation which + attempts to reuse a previously freed block of memory will also consider blocks freed by *other + threads*. The default is `0.1`, which means by default, in 90% of the cases where an address reuse + attempt is made, only addresses from the same thread will be considered. Reusing an address from + another thread induces synchronization between those threads, which can mask data races and weak + memory bugs. * `-Zmiri-compare-exchange-weak-failure-rate=` changes the failure rate of `compare_exchange_weak` operations. The default is `0.8` (so 4 out of 5 weak ops will fail). You can change it to any value between `0.0` and `1.0`, where `1.0` means it @@ -311,6 +304,10 @@ environment variable. We first document the most relevant and most commonly used * `-Zmiri-env-forward=` forwards the `var` environment variable to the interpreted program. Can be used multiple times to forward several variables. Execution will still be deterministic if the value of forwarded variables stays the same. Has no effect if `-Zmiri-disable-isolation` is set. +* `-Zmiri-env-set==` sets the `var` environment variable to `value` in the interpreted program. + It can be used to pass environment variables without needing to alter the host environment. It can + be used multiple times to set several variables. If `-Zmiri-disable-isolation` or `-Zmiri-env-forward` + is set, values set with this option will have priority over values from the host environment. * `-Zmiri-ignore-leaks` disables the memory leak checker, and also allows some remaining threads to exist when the main thread exits. * `-Zmiri-isolation-error=` configures Miri's response to operations @@ -377,17 +374,17 @@ to Miri failing to detect cases of undefined behavior in a program. this flag is **unsound**. * `-Zmiri-disable-weak-memory-emulation` disables the emulation of some C++11 weak memory effects. -* `-Zmiri-extern-so-file=` is an experimental flag for providing support - for FFI calls. Functions not provided by that file are still executed via the usual Miri shims. - **WARNING**: If an invalid/incorrect `.so` file is specified, this can cause undefined behaviour in Miri itself! - And of course, Miri cannot do any checks on the actions taken by the external code. +* `-Zmiri-native-lib=` is an experimental flag for providing support + for calling native functions from inside the interpreter via FFI. Functions not provided by that + file are still executed via the usual Miri shims. + **WARNING**: If an invalid/incorrect `.so` file is specified, this can cause Undefined Behavior in Miri itself! + And of course, Miri cannot do any checks on the actions taken by the native code. Note that Miri has its own handling of file descriptors, so if you want to replace *some* functions working on file descriptors, you will have to replace *all* of them, or the two kinds of file descriptors will be mixed up. This is **work in progress**; currently, only integer arguments and return values are supported (and no, pointer/integer casts to work around this limitation will not work; - they will fail horribly). It also only works on unix hosts for now. - Follow [the discussion on supporting other types](https://github.com/rust-lang/miri/issues/2365). + they will fail horribly). It also only works on Linux hosts for now. * `-Zmiri-measureme=` enables `measureme` profiling for the interpreted program. This can be used to find which parts of your program are executing slowly under Miri. The profile is written out to a file inside a directory called ``, and can be processed @@ -451,67 +448,29 @@ Some native rustc `-Z` flags are also very relevant for Miri: * `-Zmir-emit-retag` controls whether `Retag` statements are emitted. Miri enables this per default because it is needed for [Stacked Borrows] and [Tree Borrows]. -Moreover, Miri recognizes some environment variables: +Moreover, Miri recognizes some environment variables (unless noted otherwise, these are supported +by all intended entry points, i.e. `cargo miri` and `./miri {test,run}`): * `MIRI_AUTO_OPS` indicates whether the automatic execution of rustfmt, clippy and toolchain setup should be skipped. If it is set to `no`, they are skipped. This is used to allow automated IDE actions to avoid the auto ops. * `MIRI_LOG`, `MIRI_BACKTRACE` control logging and backtrace printing during Miri executions, also [see "Testing the Miri driver" in `CONTRIBUTING.md`][testing-miri]. -* `MIRIFLAGS` (recognized by `cargo miri` and the test suite) defines extra - flags to be passed to Miri. -* `MIRI_LIB_SRC` defines the directory where Miri expects the sources of the - standard library that it will build and use for interpretation. This directory - must point to the `library` subdirectory of a `rust-lang/rust` repository - checkout. -* `MIRI_SYSROOT` (recognized by `cargo miri` and the Miri driver) indicates the sysroot to use. When - using `cargo miri`, this skips the automatic setup -- only set this if you do not want to use the - automatically created sysroot. For directly invoking the Miri driver, this variable (or a - `--sysroot` flag) is mandatory. When invoking `cargo miri setup`, this indicates where the sysroot - will be put. -* `MIRI_TEST_TARGET` (recognized by the test suite and the `./miri` script) indicates which target - architecture to test against. `miri` and `cargo miri` accept the `--target` flag for the same - purpose. -* `MIRI_TEST_THREADS` (recognized by the test suite): set the number of threads to use for running tests. - By default the number of cores is used. -* `MIRI_NO_STD` (recognized by `cargo miri`) makes sure that the target's sysroot is built without - libstd. This allows testing and running no_std programs. - (Miri has a heuristic to detect no-std targets based on the target name; this environment variable - is only needed when that heuristic fails.) -* `RUSTC_BLESS` (recognized by the test suite and `cargo-miri-test/run-test.py`): overwrite all - `stderr` and `stdout` files instead of checking whether the output matches. -* `MIRI_SKIP_UI_CHECKS` (recognized by the test suite): don't check whether the +* `MIRIFLAGS` defines extra flags to be passed to Miri. +* `MIRI_LIB_SRC` defines the directory where Miri expects the sources of the standard library that + it will build and use for interpretation. This directory must point to the `library` subdirectory + of a `rust-lang/rust` repository checkout. +* `MIRI_SYSROOT` indicates the sysroot to use. When using `cargo miri`, this skips the automatic + setup -- only set this if you do not want to use the automatically created sysroot. When invoking + `cargo miri setup`, this indicates where the sysroot will be put. +* `MIRI_TEST_THREADS` (recognized by `./miri test`): set the number of threads to use for running tests. + By default, the number of cores is used. +* `MIRI_NO_STD` makes sure that the target's sysroot is built without libstd. This allows testing + and running no_std programs. (Miri has a heuristic to detect no-std targets based on the target + name; this environment variable is only needed when that heuristic fails.) +* `MIRI_SKIP_UI_CHECKS` (recognized by `./miri test`): don't check whether the `stderr` or `stdout` files match the actual output. -The following environment variables are *internal* and must not be used by -anyone but Miri itself. They are used to communicate between different Miri -binaries, and as such worth documenting: - -* `MIRI_BE_RUSTC` can be set to `host` or `target`. It tells the Miri driver to - actually not interpret the code but compile it like rustc would. With `target`, Miri sets - some compiler flags to prepare the code for interpretation; with `host`, this is not done. - This environment variable is useful to be sure that the compiled `rlib`s are compatible - with Miri. -* `MIRI_CALLED_FROM_SETUP` is set during the Miri sysroot build, - which will re-invoke `cargo-miri` as the `rustc` to use for this build. -* `MIRI_CALLED_FROM_RUSTDOC` when set to any value tells `cargo-miri` that it is - running as a child process of `rustdoc`, which invokes it twice for each doc-test - and requires special treatment, most notably a check-only build before interpretation. - This is set by `cargo-miri` itself when running as a `rustdoc`-wrapper. -* `MIRI_CWD` when set to any value tells the Miri driver to change to the given - directory after loading all the source files, but before commencing - interpretation. This is useful if the interpreted program wants a different - working directory at run-time than at build-time. -* `MIRI_LOCAL_CRATES` is set by `cargo-miri` to tell the Miri driver which - crates should be given special treatment in diagnostics, in addition to the - crate currently being compiled. -* `MIRI_ORIG_RUSTDOC` is set and read by different phases of `cargo-miri` to remember the - value of `RUSTDOC` from before it was overwritten. -* `MIRI_VERBOSE` when set to any value tells the various `cargo-miri` phases to - perform verbose logging. -* `MIRI_HOST_SYSROOT` is set by bootstrap to tell `cargo-miri` which sysroot to use for *host* - operations. - [testing-miri]: CONTRIBUTING.md#testing-the-miri-driver ## Miri `extern` functions @@ -552,7 +511,8 @@ used according to their aliasing restrictions. ## Bugs found by Miri -Miri has already found a number of bugs in the Rust standard library and beyond, which we collect here. +Miri has already found a number of bugs in the Rust standard library and beyond, some of which we collect here. +If Miri helped you find a subtle UB bug in your code, we'd appreciate a PR adding it to the list! Definite bugs found: @@ -587,6 +547,7 @@ Definite bugs found: * [Deallocating with the wrong layout in new specializations for in-place `Iterator::collect`](https://github.com/rust-lang/rust/pull/118460) * [Incorrect offset computation for highly-aligned types in `portable-atomic-util`](https://github.com/taiki-e/portable-atomic/pull/138) * [Occasional memory leak in `std::mpsc` channels](https://github.com/rust-lang/rust/issues/121582) (original code in [crossbeam](https://github.com/crossbeam-rs/crossbeam/pull/1084)) +* [Weak-memory-induced memory leak in Windows thread-local storage](https://github.com/rust-lang/rust/pull/124281) Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment): diff --git a/bench-cargo-miri/mse/src/main.rs b/bench-cargo-miri/mse/src/main.rs index c09dc2484d..e16ccd2c0d 100644 --- a/bench-cargo-miri/mse/src/main.rs +++ b/bench-cargo-miri/mse/src/main.rs @@ -4,9 +4,6 @@ static EXPECTED: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, static PCM: &[i16] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, -2, 0, -2, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 1, 0, 1, 0, 0, -2, 0, -2, 0, -2, 0, -2, -2, -2, -3, -3, -3, -3, -4, -2, -5, -2, -5, -2, -4, 0, -4, 0, -4, 0, -4, 1, -4, 1, -4, 2, -4, 2, -4, 2, -4, 2, -4, 2, -3, 1, -4, 0, -4, 0, -5, 0, -5, 0, -5, 0, -4, 2, -4, 3, -4, 4, -3, 5, -2, 5, -3, 6, -3, 6, -3, 5, -3, 5, -2, 4, -2, 3, -5, 0, -6, 0, -3, -2, -4, -4, -9, -5, -9, -4, -4, -2, -4, -2, -4, 0, -2, 1, 1, 1, 4, 2, 8, 2, 12, 1, 13, 0, 12, 0, 11, 0, 8, -2, 7, 0, 7, -3, 11, -8, 15, -9, 17, -6, 17, -5, 13, -3, 7, 0, 3, 0, -2, 0, -4, 0, -4, -2, -6, 0, -14, -2, -17, -4, -8, 0, -7, 5, -17, 7, -18, 10, -7, 18, -2, 25, -3, 27, 0, 31, 4, 34, 4, 34, 8, 36, 8, 37, 2, 36, 4, 34, 8, 28, 3, 15, 0, 11, 0, 12, -5, 8, -4, 10, 0, 23, -4, 31, -8, 30, -2, 30, 0, 26, -6, 22, -6, 20, -12, 15, -19, 10, -10, 13, -14, 6, -43, -13, -43, -16, -9, -12, -10, -29, -42, -40, -37, -28, -5, -21, 1, -24, -8, -20, 4, -18, 26, -24, 44, -26, 66, -30, 86, -37, 88, -41, 72, -46, 50, -31, 28, 23, 14, 64, 16, 51, 26, 32, 34, 39, 42, 48, 35, 58, 0, 72, -36, 69, -59, 58, -98, 54, -124, 36, -103, 12, -110, 5, -173, -19, -146, -59, -4, -42, 51, 1, -23, -6, -30, -6, 45, 46, 47, 70, 6, 55, 19, 60, 38, 62, 42, 47, 61, 46, 40, 42, -19, 22, -34, 6, -35, -50, -61, -141, -37, -171, 17, -163, 26, -180, 46, -154, 80, -63, 48, -4, 18, 20, 50, 47, 58, 53, 44, 61, 57, 85, 37, 80, 0, 86, -8, 106, -95, 49, -213, -8, -131, 47, 49, 63, 40, -39, -69, -74, -37, -20, 63, -12, 58, -14, -12, 25, -31, 41, 11, 45, 76, 47, 167, 5, 261, -37, 277, -83, 183, -172, 35, -122, -79, 138, -70, 266, 69, 124, 228, 0, 391, -29, 594, -84, 702, -78, 627, -8, 551, -13, 509, 13, 372, 120, 352, 125, 622, 127, 691, 223, 362, 126, 386, -33, 915, 198, 958, 457, 456, 298, 500, 233, 1027, 469, 1096, 426, 918, 160, 1067, 141, 1220, 189, 1245, 164, 1375, 297, 1378, 503, 1299, 702, 1550, 929, 1799, 855, 1752, 547, 1830, 602, 1928, 832, 1736, 796, 1735, 933, 1961, 1385, 1935, 1562, 2105, 1485, 2716, 1449, 2948, 1305, 2768, 1205, 2716, 1346, 2531, 1450, 2470, 1653, 3117, 2111, 3370, 2176, 2696, 1947, 2925, 2305, 3846, 2658, 2425, 2184, -877, 1981, -2261, 2623, -1645, 2908, -1876, 2732, -2704, 2953, -2484, 3116, -2120, 2954, -2442, 3216, -2466, 3499, -2192, 3234, -2392, 3361, -2497, 3869, -2078, 3772, -1858, 3915, -2066, 4438, -2285, 2934, -2294, -280, -2066, -1762, -1992, -1412, -2298, -1535, -2399, -1789, -2223, -1419, -2244, -1334, -2092, -1476, -1777, -1396, -2014, -1571, -2199, -1574, -1843, -1167, -1910, -1446, -2007, -1818]; fn main() { - #[cfg(increase_thread_usage)] - let thread = std::thread::spawn(|| 4); - for _ in 0..2 { mse(PCM.len(), PCM, EXPECTED); } diff --git a/cargo-miri/Cargo.lock b/cargo-miri/Cargo.lock index 6841a345ce..7d965dce07 100644 --- a/cargo-miri/Cargo.lock +++ b/cargo-miri/Cargo.lock @@ -4,21 +4,15 @@ version = 3 [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "bitflags" -version = "1.3.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "camino" @@ -44,9 +38,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", ] @@ -104,15 +98,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -121,25 +115,24 @@ 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 = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.2", + "bitflags", "libc", - "redox_syscall", ] [[package]] @@ -156,36 +149,27 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", @@ -194,9 +178,9 @@ dependencies = [ [[package]] name = "rustc-build-sysroot" -version = "0.4.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26170e1d79ea32f7ccec3188dd13cfc1f18c82764a9cbc1071667c0f865a4ea" +checksum = "de6077473f0c46779b49e4587a81f1b8919e0ec26630409ecfda0ba3259efb43" dependencies = [ "anyhow", "rustc_version", @@ -221,11 +205,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.2", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -258,18 +242,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", @@ -278,9 +262,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -289,9 +273,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.50" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -300,9 +284,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", @@ -312,18 +296,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", @@ -338,9 +322,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -352,37 +336,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" version = "0.48.0" @@ -398,7 +360,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.3", + "windows-targets 0.52.5", ] [[package]] @@ -418,17 +380,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.3", - "windows_aarch64_msvc 0.52.3", - "windows_i686_gnu 0.52.3", - "windows_i686_msvc 0.52.3", - "windows_x86_64_gnu 0.52.3", - "windows_x86_64_gnullvm 0.52.3", - "windows_x86_64_msvc 0.52.3", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -439,9 +402,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -451,9 +414,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -463,9 +426,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.3" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -475,9 +444,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -487,9 +456,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -499,9 +468,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -511,6 +480,6 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/cargo-miri/Cargo.toml b/cargo-miri/Cargo.toml index 55f6b5ac7e..a68854de62 100644 --- a/cargo-miri/Cargo.toml +++ b/cargo-miri/Cargo.toml @@ -18,7 +18,7 @@ directories = "5" rustc_version = "0.4" serde_json = "1.0.40" cargo_metadata = "0.18.0" -rustc-build-sysroot = "0.4.1" +rustc-build-sysroot = "0.5.0" # Enable some feature flags that dev-dependencies need but dependencies # do not. This makes `./miri install` after `./miri build` faster. diff --git a/cargo-miri/src/phases.rs b/cargo-miri/src/phases.rs index 694720ab21..e2fc2a0c27 100644 --- a/cargo-miri/src/phases.rs +++ b/cargo-miri/src/phases.rs @@ -3,7 +3,7 @@ use std::env; use std::fs::{self, File}; use std::io::BufReader; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::Command; use rustc_version::VersionMeta; @@ -28,7 +28,7 @@ Examples: cargo miri run cargo miri test -- test-suite-filter - cargo miri setup --print sysroot + cargo miri setup --print-sysroot This will print the path to the generated sysroot (and nothing else) on stdout. stderr will still contain progress information about how the build is doing. @@ -87,6 +87,7 @@ pub fn phase_cargo_miri(mut args: impl Iterator) { ), }; let verbose = num_arg_flag("-v"); + let quiet = has_arg_flag("-q") || has_arg_flag("--quiet"); // Determine the involved architectures. let rustc_version = VersionMeta::for_command(miri_for_host()).unwrap_or_else(|err| { @@ -110,7 +111,7 @@ pub fn phase_cargo_miri(mut args: impl Iterator) { } // We always setup. - let miri_sysroot = setup(&subcommand, target, &rustc_version, verbose); + let miri_sysroot = setup(&subcommand, target, &rustc_version, verbose, quiet); // Invoke actual cargo for the job, but with different flags. // We re-use `cargo test` and `cargo run`, which makes target and binary handling very easy but @@ -172,8 +173,6 @@ pub fn phase_cargo_miri(mut args: impl Iterator) { // Forward all further arguments (not consumed by `ArgSplitFlagValue`) to cargo. cmd.args(args); - // Let it know where the Miri sysroot lives. - cmd.env("MIRI_SYSROOT", miri_sysroot); // Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation, // i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish // the two codepaths. (That extra argument is why we prefer this over setting `RUSTC`.) @@ -200,10 +199,7 @@ pub fn phase_cargo_miri(mut args: impl Iterator) { // always applied. However, buggy build scripts (https://github.com/eyre-rs/eyre/issues/84) and // also cargo (https://github.com/rust-lang/cargo/issues/10885) will invoke `rustc` even when // `RUSTC_WRAPPER` is set, bypassing the wrapper. To make sure everything is coherent, we want - // that to be the Miri driver, but acting as rustc, on the target level. (Target, rather than - // host, is needed for cross-interpretation situations.) This is not a perfect emulation of real - // rustc (it might be unable to produce binaries since the sysroot is check-only), but it's as - // close as we can get, and it's good enough for autocfg. + // that to be the Miri driver, but acting as rustc, in host mode. // // In `main`, we need the value of `RUSTC` to distinguish RUSTC_WRAPPER invocations from rustdoc // or TARGET_RUNNER invocations, so we canonicalize it here to make it exceedingly unlikely that @@ -212,7 +208,10 @@ pub fn phase_cargo_miri(mut args: impl Iterator) { // bootstrap `rustc` thing in our way! Instead, we have MIRI_HOST_SYSROOT to use for host // builds. cmd.env("RUSTC", fs::canonicalize(find_miri()).unwrap()); - cmd.env("MIRI_BE_RUSTC", "target"); // we better remember to *unset* this in the other phases! + // In case we get invoked as RUSTC without the wrapper, let's be a host rustc. This makes no + // sense for cross-interpretation situations, but without the wrapper, this will use the host + // sysroot, so asking it to behave like a target build makes even less sense. + cmd.env("MIRI_BE_RUSTC", "host"); // we better remember to *unset* this in the other phases! // Set rustdoc to us as well, so we can run doctests. if let Some(orig_rustdoc) = env::var_os("RUSTDOC") { @@ -220,6 +219,8 @@ pub fn phase_cargo_miri(mut args: impl Iterator) { } cmd.env("RUSTDOC", &cargo_miri_path); + // Forward some crucial information to our own re-invocations. + cmd.env("MIRI_SYSROOT", miri_sysroot); cmd.env("MIRI_LOCAL_CRATES", local_crates(&metadata)); if verbose > 0 { cmd.env("MIRI_VERBOSE", verbose.to_string()); // This makes the other phases verbose. @@ -412,9 +413,31 @@ pub fn phase_rustc(mut args: impl Iterator, phase: RustcPhase) { // Arguments are treated very differently depending on whether this crate is // for interpretation by Miri, or for use by a build script / proc macro. if target_crate { - // Forward arguments, but remove "link" from "--emit" to make this a check-only build. + if phase != RustcPhase::Setup { + // Set the sysroot -- except during setup, where we don't have an existing sysroot yet + // and where the bootstrap wrapper adds its own `--sysroot` flag so we can't set ours. + cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap()); + } + + // Forward arguments, but patched. let emit_flag = "--emit"; + // This hack helps bootstrap run standard library tests in Miri. The issue is as follows: + // when running `cargo miri test` on libcore, cargo builds a local copy of core and makes it + // a dependency of the integration test crate. This copy duplicates all the lang items, so + // the build fails. (Regular testing avoids this because the sysroot is a literal copy of + // what `cargo build` produces, but since Miri builds its own sysroot this does not work for + // us.) So we need to make it so that the locally built libcore contains all the items from + // `core`, but does not re-define them -- we want to replace the entire crate but a + // re-export of the sysroot crate. We do this by swapping out the source file: if + // `MIRI_REPLACE_LIBRS_IF_NOT_TEST` is set and we are building a `lib.rs` file, and a + // `lib.miri.rs` file exists in the same folder, we build that instead. But crucially we + // only do that for the library, not the unit test crate (which would be runnable) or + // rustdoc (which would have a different `phase`). + let replace_librs = env::var_os("MIRI_REPLACE_LIBRS_IF_NOT_TEST").is_some() + && !runnable_crate + && phase == RustcPhase::Build; while let Some(arg) = args.next() { + // Patch `--emit`: remove "link" from "--emit" to make this a check-only build. if let Some(val) = arg.strip_prefix(emit_flag) { // Patch this argument. First, extract its value. let val = @@ -429,13 +452,31 @@ pub fn phase_rustc(mut args: impl Iterator, phase: RustcPhase) { } } cmd.arg(format!("{emit_flag}={}", val.join(","))); - } else if arg == "--extern" { - // Patch `--extern` filenames, since Cargo sometimes passes stub `.rlib` files: - // https://github.com/rust-lang/miri/issues/1705 + continue; + } + // Patch `--extern` filenames, since Cargo sometimes passes stub `.rlib` files: + // https://github.com/rust-lang/miri/issues/1705 + if arg == "--extern" { forward_patched_extern_arg(&mut args, &mut cmd); - } else { - cmd.arg(arg); + continue; + } + // If the REPLACE_LIBRS hack is enabled and we are building a `lib.rs` file, and a + // `lib.miri.rs` file exists, then build that instead. + if replace_librs { + let path = Path::new(&arg); + if path.file_name().is_some_and(|f| f == "lib.rs") && path.is_file() { + let miri_rs = Path::new(&arg).with_extension("miri.rs"); + if miri_rs.is_file() { + if verbose > 0 { + eprintln!("Performing REPLACE_LIBRS hack: {arg:?} -> {miri_rs:?}"); + } + cmd.arg(miri_rs); + continue; + } + } } + // Fallback: just propagate the argument. + cmd.arg(arg); } // During setup, patch the panic runtime for `libpanic_abort` (mirroring what bootstrap usually does). @@ -540,6 +581,12 @@ pub fn phase_runner(mut binary_args: impl Iterator, phase: Runner cmd.env(name, val); } + if phase != RunnerPhase::Rustdoc { + // Set the sysroot. Not necessary in rustdoc, where we already set the sysroot in + // `phase_rustdoc`. rustdoc will forward that flag when invoking rustc (i.e., us), so the + // flag is present in `info.args`. + cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap()); + } // Forward rustc arguments. // We need to patch "--extern" filenames because we forced a check-only // build without cargo knowing about that: replace `.rlib` suffix by diff --git a/cargo-miri/src/setup.rs b/cargo-miri/src/setup.rs index 401e9158fa..508d304536 100644 --- a/cargo-miri/src/setup.rs +++ b/cargo-miri/src/setup.rs @@ -6,7 +6,7 @@ use std::fmt::Write; use std::path::PathBuf; use std::process::{self, Command}; -use rustc_build_sysroot::{BuildMode, SysrootBuilder, SysrootConfig}; +use rustc_build_sysroot::{BuildMode, SysrootBuilder, SysrootConfig, SysrootStatus}; use rustc_version::VersionMeta; use crate::util::*; @@ -19,6 +19,7 @@ pub fn setup( target: &str, rustc_version: &VersionMeta, verbose: usize, + quiet: bool, ) -> PathBuf { let only_setup = matches!(subcommand, MiriCommand::Setup); let ask_user = !only_setup; @@ -119,6 +120,9 @@ pub fn setup( for _ in 0..verbose { command.arg("-v"); } + if quiet { + command.arg("--quiet"); + } } else { // Suppress output. command.stdout(process::Stdio::null()); @@ -133,32 +137,52 @@ pub fn setup( // not apply `RUSTFLAGS` to the sysroot either. let rustflags = &["-Cdebug-assertions=off", "-Coverflow-checks=on"]; - // Do the build. - if print_sysroot { - // Be silent. - } else { + let notify = || { let mut msg = String::new(); write!(msg, "Preparing a sysroot for Miri (target: {target})").unwrap(); if verbose > 0 { write!(msg, " in {}", sysroot_dir.display()).unwrap(); } write!(msg, "...").unwrap(); - if only_setup { + + if print_sysroot || quiet { + // Be silent. + } else if only_setup { // We want to be explicit. eprintln!("{msg}"); } else { // We want to be quiet, but still let the user know that something is happening. eprint!("{msg} "); } - } - SysrootBuilder::new(&sysroot_dir, target) + }; + + // Do the build. + let status = SysrootBuilder::new(&sysroot_dir, target) .build_mode(BuildMode::Check) .rustc_version(rustc_version.clone()) .sysroot_config(sysroot_config) .rustflags(rustflags) .cargo(cargo_cmd) - .build_from_source(&rust_src) - .unwrap_or_else(|err| { + .when_build_required(notify) + .build_from_source(&rust_src); + match status { + Ok(SysrootStatus::AlreadyCached) => + if only_setup && !(print_sysroot || quiet) { + eprintln!( + "A sysroot for Miri is already available in `{}`.", + sysroot_dir.display() + ); + }, + Ok(SysrootStatus::SysrootBuilt) => { + if print_sysroot || quiet { + // Be silent. + } else if only_setup { + eprintln!("A sysroot for Miri is now available in `{}`.", sysroot_dir.display()); + } else { + eprintln!("done"); + } + } + Err(err) => if print_sysroot { show_error!("failed to build sysroot") } else if only_setup { @@ -167,15 +191,9 @@ pub fn setup( show_error!( "failed to build sysroot; run `cargo miri setup` to see the error details" ) - } - }); - if print_sysroot { - // Be silent. - } else if only_setup { - eprintln!("A sysroot for Miri is now available in `{}`.", sysroot_dir.display()); - } else { - eprintln!("done"); + }, } + if print_sysroot { // Print just the sysroot and nothing else to stdout; this way we do not need any escaping. println!("{}", sysroot_dir.display()); diff --git a/ci/ci.sh b/ci/ci.sh index f8ba612750..6292d1033b 100755 --- a/ci/ci.sh +++ b/ci/ci.sh @@ -13,15 +13,6 @@ function endgroup { begingroup "Building Miri" -# Special Windows hacks -if [ "$HOST_TARGET" = i686-pc-windows-msvc ]; then - # The $BASH variable is `/bin/bash` here, but that path does not actually work. There are some - # hacks in place somewhere to try to paper over this, but the hacks dont work either (see - # ). So we hard-code the correct location for Github - # CI instead. - BASH="C:/Program Files/Git/usr/bin/bash" -fi - # Global configuration export RUSTFLAGS="-D warnings" export CARGO_INCREMENTAL=0 @@ -40,24 +31,26 @@ time ./miri build --all-targets # the build that all the `./miri test` below wil endgroup # Run tests. Recognizes these variables: -# - MIRI_TEST_TARGET: the target to test. Empty for host target. +# - TEST_TARGET: the target to test. Empty for host target. # - GC_STRESS: if non-empty, run the GC stress test for the main test suite. # - MIR_OPT: if non-empty, re-run test `pass` tests with mir-opt-level=4 # - MANY_SEEDS: if set to N, run the "many-seeds" tests N times # - TEST_BENCH: if non-empty, check that the benchmarks all build # - CARGO_MIRI_ENV: if non-empty, set some env vars and config to potentially confuse cargo-miri function run_tests { - if [ -n "${MIRI_TEST_TARGET-}" ]; then - begingroup "Testing foreign architecture $MIRI_TEST_TARGET" + if [ -n "${TEST_TARGET-}" ]; then + begingroup "Testing foreign architecture $TEST_TARGET" + TARGET_FLAG="--target $TEST_TARGET" else begingroup "Testing host architecture" + TARGET_FLAG="" fi ## ui test suite if [ -n "${GC_STRESS-}" ]; then - time MIRIFLAGS="${MIRIFLAGS-} -Zmiri-provenance-gc=1" ./miri test + time MIRIFLAGS="${MIRIFLAGS-} -Zmiri-provenance-gc=1" ./miri test $TARGET_FLAG else - time ./miri test + time ./miri test $TARGET_FLAG fi ## advanced tests @@ -68,18 +61,17 @@ function run_tests { # them. Also error locations change so we don't run the failing tests. # We explicitly enable debug-assertions here, they are disabled by -O but we have tests # which exist to check that we panic on debug assertion failures. - time MIRIFLAGS="${MIRIFLAGS-} -O -Zmir-opt-level=4 -Cdebug-assertions=yes" MIRI_SKIP_UI_CHECKS=1 ./miri test -- tests/{pass,panic} + time MIRIFLAGS="${MIRIFLAGS-} -O -Zmir-opt-level=4 -Cdebug-assertions=yes" MIRI_SKIP_UI_CHECKS=1 ./miri test $TARGET_FLAG tests/{pass,panic} fi if [ -n "${MANY_SEEDS-}" ]; then - # Also run some many-seeds tests. 64 seeds means this takes around a minute per test. - # (Need to invoke via explicit `bash -c` for Windows.) + # Also run some many-seeds tests. time for FILE in tests/many-seeds/*.rs; do - MIRI_SEEDS=$MANY_SEEDS ./miri many-seeds "$BASH" -c "./miri run '$FILE'" + ./miri run "--many-seeds=0..$MANY_SEEDS" $TARGET_FLAG "$FILE" done fi if [ -n "${TEST_BENCH-}" ]; then - # Check that the benchmarks build and run, but without actually benchmarking. - time HYPERFINE="'$BASH' -c" ./miri bench + # Check that the benchmarks build and run, but only once. + time HYPERFINE="hyperfine -w0 -r1" ./miri bench $TARGET_FLAG fi ## test-cargo-miri @@ -101,7 +93,7 @@ function run_tests { echo 'build.rustc-wrapper = "thisdoesnotexist"' > .cargo/config.toml fi # Run the actual test - time ${PYTHON} test-cargo-miri/run-test.py + time ${PYTHON} test-cargo-miri/run-test.py $TARGET_FLAG # Clean up unset RUSTC MIRI rm -rf .cargo @@ -110,17 +102,18 @@ function run_tests { } function run_tests_minimal { - if [ -n "${MIRI_TEST_TARGET-}" ]; then - begingroup "Testing MINIMAL foreign architecture $MIRI_TEST_TARGET: only testing $@" + if [ -n "${TEST_TARGET-}" ]; then + begingroup "Testing MINIMAL foreign architecture $TEST_TARGET: only testing $@" + TARGET_FLAG="--target $TEST_TARGET" else - echo "run_tests_minimal requires MIRI_TEST_TARGET to be set" + echo "run_tests_minimal requires TEST_TARGET to be set" exit 1 fi - ./miri test -- "$@" + time ./miri test $TARGET_FLAG "$@" # Ensure that a small smoke test of cargo-miri works. - cargo miri run --manifest-path test-cargo-miri/no-std-smoke/Cargo.toml --target ${MIRI_TEST_TARGET-$HOST_TARGET} + time cargo miri run --manifest-path test-cargo-miri/no-std-smoke/Cargo.toml $TARGET_FLAG endgroup } @@ -128,45 +121,51 @@ function run_tests_minimal { ## Main Testing Logic ## # In particular, fully cover all tier 1 targets. +# We also want to run the many-seeds tests on all tier 1 targets. case $HOST_TARGET in x86_64-unknown-linux-gnu) # Host GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests # Extra tier 1 - MIRI_TEST_TARGET=i686-unknown-linux-gnu run_tests - MIRI_TEST_TARGET=aarch64-unknown-linux-gnu run_tests - MIRI_TEST_TARGET=x86_64-apple-darwin run_tests - MIRI_TEST_TARGET=i686-pc-windows-gnu run_tests - MIRI_TEST_TARGET=x86_64-pc-windows-gnu run_tests - # Extra tier 2 - MIRI_TEST_TARGET=aarch64-apple-darwin run_tests - MIRI_TEST_TARGET=arm-unknown-linux-gnueabi run_tests - # Partially supported targets (tier 2) - MIRI_TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal hello integer vec panic/panic concurrency/simple pthread-threadname libc-getentropy libc-getrandom libc-misc libc-fs atomic env align num_cpus - MIRI_TEST_TARGET=i686-unknown-freebsd run_tests_minimal hello integer vec panic/panic concurrency/simple pthread-threadname libc-getentropy libc-getrandom libc-misc libc-fs atomic env align num_cpus - MIRI_TEST_TARGET=aarch64-linux-android run_tests_minimal hello integer vec panic/panic - MIRI_TEST_TARGET=wasm32-wasi run_tests_minimal no_std integer strings wasm - MIRI_TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std integer strings wasm - MIRI_TEST_TARGET=thumbv7em-none-eabihf run_tests_minimal no_std - # Custom target JSON file - MIRI_TEST_TARGET=tests/avr.json MIRI_NO_STD=1 run_tests_minimal no_std + # With reduced many-seed count to avoid spending too much time on that. + # (All OSes and ABIs are run with 64 seeds at least once though via the macOS runner.) + MANY_SEEDS=16 TEST_TARGET=i686-unknown-linux-gnu run_tests + MANY_SEEDS=16 TEST_TARGET=aarch64-unknown-linux-gnu run_tests + MANY_SEEDS=16 TEST_TARGET=x86_64-apple-darwin run_tests + MANY_SEEDS=16 TEST_TARGET=x86_64-pc-windows-gnu run_tests ;; aarch64-apple-darwin) # Host (tier 2) GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests # Extra tier 1 - MIRI_TEST_TARGET=x86_64-pc-windows-msvc CARGO_MIRI_ENV=1 run_tests + MANY_SEEDS=64 TEST_TARGET=i686-pc-windows-gnu run_tests + MANY_SEEDS=64 TEST_TARGET=x86_64-pc-windows-msvc CARGO_MIRI_ENV=1 run_tests # Extra tier 2 - MIRI_TEST_TARGET=s390x-unknown-linux-gnu run_tests # big-endian architecture + TEST_TARGET=arm-unknown-linux-gnueabi run_tests + TEST_TARGET=s390x-unknown-linux-gnu run_tests # big-endian architecture of choice + # Partially supported targets (tier 2) + VERY_BASIC="integer vec string btreemap" # common things we test on all of them (if they have std), requires no target-specific shims + BASIC="$VERY_BASIC hello hashmap alloc align" # ensures we have the shims for stdout and basic data structures + TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal $BASIC panic/panic concurrency/simple atomic threadname libc-mem libc-misc libc-random libc-time fs env num_cpus + TEST_TARGET=i686-unknown-freebsd run_tests_minimal $BASIC panic/panic concurrency/simple atomic threadname libc-mem libc-misc libc-random libc-time fs env num_cpus + TEST_TARGET=x86_64-unknown-illumos run_tests_minimal $BASIC hello panic/panic concurrency/simple pthread-sync libc-mem libc-misc libc-random env + TEST_TARGET=x86_64-pc-solaris run_tests_minimal $BASIC hello panic/panic concurrency/simple pthread-sync libc-mem libc-misc libc-random env + TEST_TARGET=aarch64-linux-android run_tests_minimal $VERY_BASIC hello panic/panic + TEST_TARGET=wasm32-wasi run_tests_minimal $VERY_BASIC wasm + TEST_TARGET=wasm32-unknown-unknown run_tests_minimal $VERY_BASIC wasm + TEST_TARGET=thumbv7em-none-eabihf run_tests_minimal no_std + # Custom target JSON file + TEST_TARGET=tests/avr.json MIRI_NO_STD=1 run_tests_minimal no_std ;; i686-pc-windows-msvc) # Host - # Only smoke-test `many-seeds`; 64 runs take 15min here! - GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=1 TEST_BENCH=1 run_tests + # Without GC_STRESS and with reduced many-seeds count as this is the slowest runner. + # (The macOS runner checks windows-msvc with full many-seeds count.) + MIR_OPT=1 MANY_SEEDS=16 TEST_BENCH=1 run_tests # Extra tier 1 # We really want to ensure a Linux target works on a Windows host, # and a 64bit target works on a 32bit host. - MIRI_TEST_TARGET=x86_64-unknown-linux-gnu run_tests + TEST_TARGET=x86_64-unknown-linux-gnu run_tests ;; *) echo "FATAL: unknown host target: $HOST_TARGET" diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000000..284e18a45a --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +arithmetic-side-effects-allowed = ["rustc_target::abi::Size"] diff --git a/miri-script/Cargo.lock b/miri-script/Cargo.lock index a6f7467f0a..5e792abac1 100644 --- a/miri-script/Cargo.lock +++ b/miri-script/Cargo.lock @@ -466,15 +466,15 @@ checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" [[package]] name = "xshell" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce2107fe03e558353b4c71ad7626d58ed82efaf56c54134228608893c77023ad" +checksum = "6db0ab86eae739efd1b054a8d3d16041914030ac4e01cd1dca0cf252fd8b6437" dependencies = [ "xshell-macros", ] [[package]] name = "xshell-macros" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2c411759b501fb9501aac2b1b2d287a6e93e5bdcf13c25306b23e1b716dd0e" +checksum = "9d422e8e38ec76e2f06ee439ccc765e9c6a9638b9e7c9f2e8255e4d41e8bd852" diff --git a/miri-script/Cargo.toml b/miri-script/Cargo.toml index aaa788d584..631d3a8210 100644 --- a/miri-script/Cargo.toml +++ b/miri-script/Cargo.toml @@ -8,7 +8,9 @@ version = "0.1.0" default-run = "miri-script" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace] +# We make this a workspace root so that cargo does not go looking in ../Cargo.toml for the workspace root. +# This is needed to make this package build on stable when the parent package uses unstable cargo features. [dependencies] which = "4.4" @@ -17,7 +19,7 @@ itertools = "0.11" path_macro = "1.0" shell-words = "1.1" anyhow = "1.0" -xshell = "0.2" +xshell = "0.2.6" rustc_version = "0.4" dunce = "1.0.4" directories = "5" diff --git a/miri-script/src/commands.rs b/miri-script/src/commands.rs index 55b3b62819..8e2b07ad80 100644 --- a/miri-script/src/commands.rs +++ b/miri-script/src/commands.rs @@ -1,7 +1,9 @@ use std::env; -use std::ffi::OsString; +use std::ffi::{OsStr, OsString}; use std::io::Write; use std::ops::Not; +use std::ops::Range; +use std::path::PathBuf; use std::process; use std::thread; use std::time; @@ -20,10 +22,13 @@ const JOSH_FILTER: &str = const JOSH_PORT: &str = "42042"; impl MiriEnv { - fn build_miri_sysroot(&mut self, quiet: bool) -> Result<()> { - if self.sh.var("MIRI_SYSROOT").is_ok() { + /// Returns the location of the sysroot. + /// + /// If the target is None the sysroot will be built for the host machine. + fn build_miri_sysroot(&mut self, quiet: bool, target: Option<&OsStr>) -> Result { + if let Some(miri_sysroot) = self.sh.var_os("MIRI_SYSROOT") { // Sysroot already set, use that. - return Ok(()); + return Ok(miri_sysroot.into()); } let manifest_path = path!(self.miri_dir / "cargo-miri" / "Cargo.toml"); let Self { toolchain, cargo_extra_flags, .. } = &self; @@ -32,33 +37,35 @@ impl MiriEnv { self.build(path!(self.miri_dir / "Cargo.toml"), &[], quiet)?; self.build(&manifest_path, &[], quiet)?; - let target = &match self.sh.var("MIRI_TEST_TARGET") { - Ok(target) => vec!["--target".into(), target], - Err(_) => vec![], - }; + let target_flag = + if let Some(target) = target { vec![OsStr::new("--target"), target] } else { vec![] }; + let target_flag = &target_flag; + if !quiet { - match self.sh.var("MIRI_TEST_TARGET") { - Ok(target) => eprintln!("$ (building Miri sysroot for {target})"), - Err(_) => eprintln!("$ (building Miri sysroot)"), + if let Some(target) = target { + eprintln!("$ (building Miri sysroot for {})", target.to_string_lossy()); + } else { + eprintln!("$ (building Miri sysroot)"); } } + let output = cmd!(self.sh, "cargo +{toolchain} --quiet run {cargo_extra_flags...} --manifest-path {manifest_path} -- - miri setup --print-sysroot {target...}" + miri setup --print-sysroot {target_flag...}" ).read(); let Ok(output) = output else { // Run it again (without `--print-sysroot` or `--quiet`) so the user can see the error. cmd!( self.sh, "cargo +{toolchain} run {cargo_extra_flags...} --manifest-path {manifest_path} -- - miri setup {target...}" + miri setup {target_flag...}" ) .run() .with_context(|| "`cargo miri setup` failed")?; panic!("`cargo miri setup` didn't fail again the 2nd time?"); }; - self.sh.set_var("MIRI_SYSROOT", output); - Ok(()) + self.sh.set_var("MIRI_SYSROOT", &output); + Ok(output.into()) } } @@ -148,7 +155,6 @@ impl Command { | Command::Fmt { .. } | Command::Clippy { .. } | Command::Cargo { .. } => Self::auto_actions()?, - | Command::ManySeeds { .. } | Command::Toolchain { .. } | Command::Bench { .. } | Command::RustcPull { .. } @@ -159,13 +165,13 @@ impl Command { Command::Install { flags } => Self::install(flags), Command::Build { flags } => Self::build(flags), Command::Check { flags } => Self::check(flags), - Command::Test { bless, flags } => Self::test(bless, flags), - Command::Run { dep, flags } => Self::run(dep, flags), + Command::Test { bless, flags, target } => Self::test(bless, flags, target), + Command::Run { dep, verbose, many_seeds, flags } => + Self::run(dep, verbose, many_seeds, flags), Command::Fmt { flags } => Self::fmt(flags), Command::Clippy { flags } => Self::clippy(flags), Command::Cargo { flags } => Self::cargo(flags), - Command::ManySeeds { command } => Self::many_seeds(command), - Command::Bench { benches } => Self::bench(benches), + Command::Bench { target, benches } => Self::bench(target, benches), Command::Toolchain { flags } => Self::toolchain(flags), Command::RustcPull { commit } => Self::rustc_pull(commit.clone()), Command::RustcPush { github_user, branch } => Self::rustc_push(github_user, branch), @@ -237,6 +243,8 @@ impl Command { // the merge has confused the heck out of josh in the past. // We pass `--no-verify` to avoid running git hooks like `./miri fmt` that could in turn // trigger auto-actions. + // We do this before the merge so that if there are merge conflicts, we have + // the right rust-version file while resolving them. sh.write_file("rust-version", format!("{commit}\n"))?; const PREPARING_COMMIT_MESSAGE: &str = "Preparing for merge from rustc"; cmd!(sh, "git commit rust-version --no-verify -m {PREPARING_COMMIT_MESSAGE}") @@ -255,12 +263,26 @@ impl Command { }) .context("FAILED to fetch new commits, something went wrong (committing the rust-version file has been undone)")?; + // This should not add any new root commits. So count those before and after merging. + let num_roots = || -> Result { + Ok(cmd!(sh, "git rev-list HEAD --max-parents=0 --count") + .read() + .context("failed to determine the number of root commits")? + .parse::()?) + }; + let num_roots_before = num_roots()?; + // Merge the fetched commit. const MERGE_COMMIT_MESSAGE: &str = "Merge from rustc"; cmd!(sh, "git merge FETCH_HEAD --no-verify --no-ff -m {MERGE_COMMIT_MESSAGE}") .run() .context("FAILED to merge new commits, something went wrong")?; + // Check that the number of roots did not increase. + if num_roots()? != num_roots_before { + bail!("Josh created a new root commit. This is probably not the history you want."); + } + drop(josh); Ok(()) } @@ -351,44 +373,7 @@ impl Command { Ok(()) } - fn many_seeds(command: Vec) -> Result<()> { - let seed_start: u64 = env::var("MIRI_SEED_START") - .unwrap_or_else(|_| "0".into()) - .parse() - .context("failed to parse MIRI_SEED_START")?; - let seed_end: u64 = match (env::var("MIRI_SEEDS"), env::var("MIRI_SEED_END")) { - (Ok(_), Ok(_)) => bail!("Only one of MIRI_SEEDS and MIRI_SEED_END may be set"), - (Ok(seeds), Err(_)) => - seed_start + seeds.parse::().context("failed to parse MIRI_SEEDS")?, - (Err(_), Ok(seed_end)) => seed_end.parse().context("failed to parse MIRI_SEED_END")?, - (Err(_), Err(_)) => seed_start + 256, - }; - if seed_end <= seed_start { - bail!("the end of the seed range must be larger than the start."); - } - - let Some((command_name, trailing_args)) = command.split_first() else { - bail!("expected many-seeds command to be non-empty"); - }; - let sh = Shell::new()?; - sh.set_var("MIRI_AUTO_OPS", "no"); // just in case we get recursively invoked - for seed in seed_start..seed_end { - println!("Trying seed: {seed}"); - let mut miriflags = env::var_os("MIRIFLAGS").unwrap_or_default(); - miriflags.push(format!(" -Zlayout-seed={seed} -Zmiri-seed={seed}")); - let status = cmd!(sh, "{command_name} {trailing_args...}") - .env("MIRIFLAGS", miriflags) - .quiet() - .run(); - if let Err(err) = status { - println!("Failing seed: {seed}"); - return Err(err.into()); - } - } - Ok(()) - } - - fn bench(benches: Vec) -> Result<()> { + fn bench(target: Option, benches: Vec) -> Result<()> { // The hyperfine to use let hyperfine = env::var("HYPERFINE"); let hyperfine = hyperfine.as_deref().unwrap_or("hyperfine -w 1 -m 5 --shell=none"); @@ -396,8 +381,6 @@ impl Command { let Some((program_name, args)) = hyperfine.split_first() else { bail!("expected HYPERFINE environment variable to be non-empty"); }; - // Extra flags to pass to cargo. - let cargo_extra_flags = std::env::var("CARGO_EXTRA_FLAGS").unwrap_or_default(); // Make sure we have an up-to-date Miri installed and selected the right toolchain. Self::install(vec![])?; @@ -413,6 +396,14 @@ impl Command { } else { benches.to_owned() }; + let target_flag = if let Some(target) = target { + let mut flag = OsString::from("--target="); + flag.push(target); + flag + } else { + OsString::new() + }; + let target_flag = &target_flag; // Run the requested benchmarks for bench in benches { let current_bench = path!(benches_dir / bench / "Cargo.toml"); @@ -420,7 +411,7 @@ impl Command { // That seems to make Windows CI happy. cmd!( sh, - "{program_name} {args...} 'cargo miri run '{cargo_extra_flags}' --manifest-path \"'{current_bench}'\"'" + "{program_name} {args...} 'cargo miri run '{target_flag}' --manifest-path \"'{current_bench}'\"'" ) .run()?; } @@ -465,65 +456,89 @@ impl Command { Ok(()) } - fn test(bless: bool, flags: Vec) -> Result<()> { + fn test(bless: bool, mut flags: Vec, target: Option) -> Result<()> { let mut e = MiriEnv::new()?; + // Prepare a sysroot. - e.build_miri_sysroot(/* quiet */ false)?; + e.build_miri_sysroot(/* quiet */ false, target.as_deref())?; - // Then test, and let caller control flags. - // Only in root project as `cargo-miri` has no tests. + // Forward information to test harness. if bless { e.sh.set_var("RUSTC_BLESS", "Gesundheit"); } + if let Some(target) = target { + // Tell the harness which target to test. + e.sh.set_var("MIRI_TEST_TARGET", target); + } + + // Make sure the flags are going to the test harness, not cargo. + flags.insert(0, "--".into()); + + // Then test, and let caller control flags. + // Only in root project as `cargo-miri` has no tests. e.test(path!(e.miri_dir / "Cargo.toml"), &flags)?; Ok(()) } - fn run(dep: bool, mut flags: Vec) -> Result<()> { + fn run( + dep: bool, + verbose: bool, + many_seeds: Option>, + mut flags: Vec, + ) -> Result<()> { let mut e = MiriEnv::new()?; - // Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so - // that we set the MIRI_SYSROOT up the right way. We must make sure that - // MIRI_TEST_TARGET and `--target` are in sync. - use itertools::Itertools; - let target = flags - .iter() - .take_while(|arg| *arg != "--") - .tuple_windows() - .find(|(first, _)| *first == "--target"); - if let Some((_, target)) = target { - // Found it! - e.sh.set_var("MIRI_TEST_TARGET", target); - } else if let Ok(target) = std::env::var("MIRI_TEST_TARGET") { - // Convert `MIRI_TEST_TARGET` into `--target`. - flags.push("--target".into()); - flags.push(target.into()); - } + let target = arg_flag_value(&flags, "--target"); + // Scan for "--edition", set one ourselves if that flag is not present. - let have_edition = - flags.iter().take_while(|arg| *arg != "--").any(|arg| *arg == "--edition"); + let have_edition = arg_flag_value(&flags, "--edition").is_some(); if !have_edition { flags.push("--edition=2021".into()); // keep in sync with `tests/ui.rs`.` } - // Prepare a sysroot. - e.build_miri_sysroot(/* quiet */ true)?; + // Prepare a sysroot, and add it to the flags. + let miri_sysroot = e.build_miri_sysroot(/* quiet */ !verbose, target.as_deref())?; + flags.push("--sysroot".into()); + flags.push(miri_sysroot.into()); - // Then run the actual command. Also add MIRIFLAGS. + // Compute everything needed to run the actual command. Also add MIRIFLAGS. let miri_manifest = path!(e.miri_dir / "Cargo.toml"); let miri_flags = e.sh.var("MIRIFLAGS").unwrap_or_default(); let miri_flags = flagsplit(&miri_flags); let toolchain = &e.toolchain; let extra_flags = &e.cargo_extra_flags; - if dep { - cmd!( - e.sh, - "cargo +{toolchain} --quiet test {extra_flags...} --manifest-path {miri_manifest} --test ui -- --miri-run-dep-mode {miri_flags...} {flags...}" - ).quiet().run()?; + let quiet_flag = if verbose { None } else { Some("--quiet") }; + // This closure runs the command with the given `seed_flag` added between the MIRIFLAGS and + // the `flags` given on the command-line. + let run_miri = |sh: &Shell, seed_flag: Option| -> Result<()> { + // The basic command that executes the Miri driver. + let mut cmd = if dep { + cmd!( + sh, + "cargo +{toolchain} {quiet_flag...} test {extra_flags...} --manifest-path {miri_manifest} --test ui -- --miri-run-dep-mode" + ) + } else { + cmd!( + sh, + "cargo +{toolchain} {quiet_flag...} run {extra_flags...} --manifest-path {miri_manifest} --" + ) + }; + cmd.set_quiet(!verbose); + // Add Miri flags + let cmd = cmd.args(&miri_flags).args(seed_flag).args(&flags); + // And run the thing. + Ok(cmd.run()?) + }; + // Run the closure once or many times. + if let Some(seed_range) = many_seeds { + e.run_many_times(seed_range, |sh, seed| { + eprintln!("Trying seed: {seed}"); + run_miri(sh, Some(format!("-Zmiri-seed={seed}"))).map_err(|err| { + eprintln!("FAILING SEED: {seed}"); + err + }) + })?; } else { - cmd!( - e.sh, - "cargo +{toolchain} --quiet run {extra_flags...} --manifest-path {miri_manifest} -- {miri_flags...} {flags...}" - ).quiet().run()?; + run_miri(&e.sh, None)?; } Ok(()) } diff --git a/miri-script/src/main.rs b/miri-script/src/main.rs index 712180be28..a8626ceb45 100644 --- a/miri-script/src/main.rs +++ b/miri-script/src/main.rs @@ -3,10 +3,10 @@ mod commands; mod util; -use std::env; use std::ffi::OsString; +use std::{env, ops::Range}; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; #[derive(Clone, Debug)] pub enum Command { @@ -31,13 +31,18 @@ pub enum Command { /// Build miri, set up a sysroot and then run the test suite. Test { bless: bool, - /// Flags that are passed through to `cargo test`. + /// The cross-interpretation target. + /// If none then the host is the target. + target: Option, + /// Flags that are passed through to the test harness. flags: Vec, }, /// Build miri, set up a sysroot and then run the driver with the given . /// (Also respects MIRIFLAGS environment variable.) Run { dep: bool, + verbose: bool, + many_seeds: Option>, /// Flags that are passed through to `miri`. flags: Vec, }, @@ -54,12 +59,9 @@ pub enum Command { /// Runs just `cargo ` with the Miri-specific environment variables. /// Mainly meant to be invoked by rust-analyzer. Cargo { flags: Vec }, - /// Runs over and over again with different seeds for Miri. The MIRIFLAGS - /// variable is set to its original value appended with ` -Zmiri-seed=$SEED` for - /// many different seeds. - ManySeeds { command: Vec }, /// Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed. Bench { + target: Option, /// List of benchmarks to run. By default all benchmarks are run. benches: Vec, }, @@ -86,13 +88,15 @@ Just build miri. are passed to `cargo build`. ./miri check : Just check miri. are passed to `cargo check`. -./miri test [--bless] : -Build miri, set up a sysroot and then run the test suite. are passed -to the final `cargo test` invocation. +./miri test [--bless] [--target ] : +Build miri, set up a sysroot and then run the test suite. + are passed to the test harness. -./miri run [--dep] : +./miri run [--dep] [-v|--verbose] [--many-seeds|--many-seeds=..to|--many-seeds=from..to] : Build miri, set up a sysroot and then run the driver with the given . (Also respects MIRIFLAGS environment variable.) +If `--many-seeds` is present, Miri is run many times in parallel with different seeds. +The range defaults to `0..256`. ./miri fmt : Format all sources and tests. are passed to `rustfmt`. @@ -110,14 +114,7 @@ install`. Sets up the rpath such that the installed binary should work in any working directory. Note that the binaries are placed in the `miri` toolchain sysroot, to prevent conflicts with other toolchains. -./miri many-seeds : -Runs over and over again with different seeds for Miri. The MIRIFLAGS -variable is set to its original value appended with ` -Zmiri-seed=$SEED` for -many different seeds. MIRI_SEED_START controls the first seed to try (default: 0). -MIRI_SEEDS controls how many seeds are being tried (default: 256); -alternatively, MIRI_SEED_END controls the end of the (exclusive) seed range to try. - -./miri bench : +./miri bench [--target ] : Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed. can explicitly list the benchmarks to run; by default, all of them are run. @@ -132,10 +129,10 @@ Pull and merge Miri changes from the rustc repo. Defaults to fetching the latest rustc commit. The fetched commit is stored in the `rust-version` file, so the next `./miri toolchain` will install the rustc that just got pulled. -./miri rustc-push : +./miri rustc-push []: Push Miri changes back to the rustc repo. This will pull a copy of the rustc history into the Miri repo, unless you set the RUSTC_GIT env var to an existing -clone of the rustc repo. +clone of the rustc repo. The branch defaults to `miri-sync`. ENVIRONMENT VARIABLES @@ -154,27 +151,88 @@ fn main() -> Result<()> { Some("build") => Command::Build { flags: args.collect() }, Some("check") => Command::Check { flags: args.collect() }, Some("test") => { - let bless = args.peek().is_some_and(|a| a.to_str() == Some("--bless")); - if bless { - // Consume the flag. + let mut target = None; + let mut bless = false; + + while let Some(arg) = args.peek().and_then(|s| s.to_str()) { + match arg { + "--bless" => bless = true, + "--target" => { + // Skip "--target" + args.next().unwrap(); + // Next argument is the target triple. + let val = args.peek().ok_or_else(|| { + anyhow!("`--target` must be followed by target triple") + })?; + target = Some(val.to_owned()); + } + // Only parse the leading flags. + _ => break, + } + + // Consume the flag, look at the next one. args.next().unwrap(); } - Command::Test { bless, flags: args.collect() } + + Command::Test { bless, flags: args.collect(), target } } Some("run") => { - let dep = args.peek().is_some_and(|a| a.to_str() == Some("--dep")); - if dep { - // Consume the flag. + let mut dep = false; + let mut verbose = false; + let mut many_seeds = None; + while let Some(arg) = args.peek().and_then(|s| s.to_str()) { + if arg == "--dep" { + dep = true; + } else if arg == "-v" || arg == "--verbose" { + verbose = true; + } else if arg == "--many-seeds" { + many_seeds = Some(0..256); + } else if let Some(val) = arg.strip_prefix("--many-seeds=") { + let (from, to) = val.split_once("..").ok_or_else(|| { + anyhow!("invalid format for `--many-seeds`: expected `from..to`") + })?; + let from: u32 = if from.is_empty() { + 0 + } else { + from.parse().context("invalid `from` in `--many-seeds=from..to")? + }; + let to: u32 = to.parse().context("invalid `to` in `--many-seeds=from..to")?; + many_seeds = Some(from..to); + } else { + break; // not for us + } + // Consume the flag, look at the next one. args.next().unwrap(); } - Command::Run { dep, flags: args.collect() } + Command::Run { dep, verbose, many_seeds, flags: args.collect() } } Some("fmt") => Command::Fmt { flags: args.collect() }, Some("clippy") => Command::Clippy { flags: args.collect() }, Some("cargo") => Command::Cargo { flags: args.collect() }, Some("install") => Command::Install { flags: args.collect() }, - Some("many-seeds") => Command::ManySeeds { command: args.collect() }, - Some("bench") => Command::Bench { benches: args.collect() }, + Some("bench") => { + let mut target = None; + while let Some(arg) = args.peek().and_then(|s| s.to_str()) { + match arg { + "--target" => { + // Skip "--target" + args.next().unwrap(); + // Next argument is the target triple. + let val = args.peek().ok_or_else(|| { + anyhow!("`--target` must be followed by target triple") + })?; + target = Some(val.to_owned()); + } + // Only parse the leading flags. + _ => break, + } + + // Consume the flag, look at the next one. + args.next().unwrap(); + } + + Command::Bench { target, benches: args.collect() } + } Some("toolchain") => Command::Toolchain { flags: args.collect() }, Some("rustc-pull") => { let commit = args.next().map(|a| a.to_string_lossy().into_owned()); @@ -187,17 +245,12 @@ fn main() -> Result<()> { let github_user = args .next() .ok_or_else(|| { - anyhow!("Missing first argument for `./miri rustc-push GITHUB_USER BRANCH`") - })? - .to_string_lossy() - .into_owned(); - let branch = args - .next() - .ok_or_else(|| { - anyhow!("Missing second argument for `./miri rustc-push GITHUB_USER BRANCH`") + anyhow!("Missing first argument for `./miri rustc-push GITHUB_USER [BRANCH]`") })? .to_string_lossy() .into_owned(); + let branch = + args.next().unwrap_or_else(|| "miri-sync".into()).to_string_lossy().into_owned(); if args.next().is_some() { bail!("Too many arguments for `./miri rustc-push GITHUB_USER BRANCH`"); } diff --git a/miri-script/src/util.rs b/miri-script/src/util.rs index 361a4ca0cf..2b5791f3ea 100644 --- a/miri-script/src/util.rs +++ b/miri-script/src/util.rs @@ -1,5 +1,8 @@ use std::ffi::{OsStr, OsString}; +use std::ops::Range; use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; +use std::thread; use anyhow::{anyhow, Context, Result}; use dunce::canonicalize; @@ -24,6 +27,30 @@ pub fn flagsplit(flags: &str) -> Vec { flags.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string).collect() } +pub fn arg_flag_value( + args: impl IntoIterator>, + flag: &str, +) -> Option { + let mut args = args.into_iter(); + while let Some(arg) = args.next() { + let arg = arg.as_ref(); + if arg == "--" { + return None; + } + let Some(arg) = arg.to_str() else { + // Skip non-UTF-8 arguments. + continue; + }; + if arg == flag { + // Next one is the value. + return Some(args.next()?.as_ref().to_owned()); + } else if let Some(val) = arg.strip_prefix(flag).and_then(|s| s.strip_prefix("=")) { + return Some(val.to_owned().into()); + } + } + None +} + /// Some extra state we track for building Miri, such as the right RUSTFLAGS. pub struct MiriEnv { /// miri_dir is the root of the miri repository checkout we are working in. @@ -189,4 +216,54 @@ impl MiriEnv { Ok(()) } + + /// Run the given closure many times in parallel with access to the shell, once for each value in the `range`. + pub fn run_many_times( + &self, + range: Range, + run: impl Fn(&Shell, u32) -> Result<()> + Sync, + ) -> Result<()> { + // `next` is atomic so threads can concurrently fetch their next value to run. + let next = AtomicU32::new(range.start); + let end = range.end; // exclusive! + let failed = AtomicBool::new(false); + thread::scope(|s| { + let mut handles = Vec::new(); + // Spawn one worker per core. + for _ in 0..thread::available_parallelism()?.get() { + // Create a copy of the shell for this thread. + let local_shell = self.sh.clone(); + let handle = s.spawn(|| -> Result<()> { + let local_shell = local_shell; // move the copy into this thread. + // Each worker thread keeps asking for numbers until we're all done. + loop { + let cur = next.fetch_add(1, Ordering::Relaxed); + if cur >= end { + // We hit the upper limit and are done. + break; + } + // Run the command with this seed. + run(&local_shell, cur).map_err(|err| { + // If we failed, tell everyone about this. + failed.store(true, Ordering::Relaxed); + err + })?; + // Check if some other command failed (in which case we'll stop as well). + if failed.load(Ordering::Relaxed) { + return Ok(()); + } + } + Ok(()) + }); + handles.push(handle); + } + // Wait for all workers to be done. + for handle in handles { + handle.join().unwrap()?; + } + // If all workers succeeded, we can't have failed. + assert!(!failed.load(Ordering::Relaxed)); + Ok(()) + }) + } } diff --git a/rust-version b/rust-version index 90194ec603..603417b77e 100644 --- a/rust-version +++ b/rust-version @@ -1 +1 @@ -b688d53a1736c17e49328a706a90829a9937a91a +b71fa82d786ae1b5866510f1b3a7e5b7e1890e4c diff --git a/src/alloc_addresses/mod.rs b/src/alloc_addresses/mod.rs index 6b8e1510a6..2bbb34c9a4 100644 --- a/src/alloc_addresses/mod.rs +++ b/src/alloc_addresses/mod.rs @@ -13,13 +13,14 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_span::Span; use rustc_target::abi::{Align, HasDataLayout, Size}; -use crate::*; -use reuse_pool::ReusePool; +use crate::{concurrency::VClock, *}; + +use self::reuse_pool::ReusePool; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ProvenanceMode { - /// We support `expose_addr`/`with_exposed_provenance` via "wildcard" provenance. - /// However, we want on `with_exposed_provenance` to alert the user of the precision loss. + /// We support `expose_provenance`/`with_exposed_provenance` via "wildcard" provenance. + /// However, we warn on `with_exposed_provenance` to alert the user of the precision loss. Default, /// Like `Default`, but without the warning. Permissive, @@ -77,7 +78,7 @@ impl GlobalStateInner { GlobalStateInner { int_to_ptr_map: Vec::default(), base_addr: FxHashMap::default(), - reuse: ReusePool::new(), + reuse: ReusePool::new(config), exposed: FxHashSet::default(), next_base_addr: stack_addr, provenance_mode: config.provenance_mode, @@ -141,7 +142,11 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } - fn addr_from_alloc_id(&self, alloc_id: AllocId) -> InterpResult<'tcx, u64> { + fn addr_from_alloc_id( + &self, + alloc_id: AllocId, + memory_kind: MemoryKind, + ) -> InterpResult<'tcx, u64> { let ecx = self.eval_context_ref(); let mut global_state = ecx.machine.alloc_addresses.borrow_mut(); let global_state = &mut *global_state; @@ -159,9 +164,18 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { assert!(!matches!(kind, AllocKind::Dead)); // This allocation does not have a base address yet, pick or reuse one. - let base_addr = if let Some(reuse_addr) = - global_state.reuse.take_addr(&mut *rng, size, align) - { + let base_addr = if let Some((reuse_addr, clock)) = global_state.reuse.take_addr( + &mut *rng, + size, + align, + memory_kind, + ecx.get_active_thread(), + ) { + if let Some(clock) = clock + && let Some(data_race) = &ecx.machine.data_race + { + data_race.acquire_clock(&clock, ecx.get_active_thread()); + } reuse_addr } else { // We have to pick a fresh address. @@ -283,16 +297,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } /// Convert a relative (tcx) pointer to a Miri pointer. - fn ptr_from_rel_ptr( + fn adjust_alloc_root_pointer( &self, ptr: Pointer, tag: BorTag, + kind: MemoryKind, ) -> InterpResult<'tcx, Pointer> { let ecx = self.eval_context_ref(); let (prov, offset) = ptr.into_parts(); // offset is relative (AllocId provenance) let alloc_id = prov.alloc_id(); - let base_addr = ecx.addr_from_alloc_id(alloc_id)?; + let base_addr = ecx.addr_from_alloc_id(alloc_id, kind)?; // Add offset with the right kind of pointer-overflowing arithmetic. let dl = ecx.data_layout(); @@ -314,9 +329,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ecx.alloc_id_from_addr(addr.bytes())? }; - // This cannot fail: since we already have a pointer with that provenance, rel_ptr_to_addr + // This cannot fail: since we already have a pointer with that provenance, adjust_alloc_root_pointer // must have been called in the past, so we can just look up the address in the map. - let base_addr = ecx.addr_from_alloc_id(alloc_id).unwrap(); + let base_addr = *ecx.machine.alloc_addresses.borrow().base_addr.get(&alloc_id).unwrap(); // Wrapping "addr - base_addr" #[allow(clippy::cast_possible_wrap)] // we want to wrap here @@ -328,14 +343,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } -impl GlobalStateInner { - pub fn free_alloc_id( - &mut self, - rng: &mut impl Rng, - dead_id: AllocId, - size: Size, - align: Align, - ) { +impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { + pub fn free_alloc_id(&mut self, dead_id: AllocId, size: Size, align: Align, kind: MemoryKind) { + let global_state = self.alloc_addresses.get_mut(); + let rng = self.rng.get_mut(); + // We can *not* remove this from `base_addr`, since the interpreter design requires that we // be able to retrieve an AllocId + offset for any memory access *before* we check if the // access is valid. Specifically, `ptr_get_alloc` is called on each attempt at a memory @@ -348,15 +360,25 @@ impl GlobalStateInner { // returns a dead allocation. // To avoid a linear scan we first look up the address in `base_addr`, and then find it in // `int_to_ptr_map`. - let addr = *self.base_addr.get(&dead_id).unwrap(); - let pos = self.int_to_ptr_map.binary_search_by_key(&addr, |(addr, _)| *addr).unwrap(); - let removed = self.int_to_ptr_map.remove(pos); + let addr = *global_state.base_addr.get(&dead_id).unwrap(); + let pos = + global_state.int_to_ptr_map.binary_search_by_key(&addr, |(addr, _)| *addr).unwrap(); + let removed = global_state.int_to_ptr_map.remove(pos); assert_eq!(removed, (addr, dead_id)); // double-check that we removed the right thing // We can also remove it from `exposed`, since this allocation can anyway not be returned by // `alloc_id_from_addr` any more. - self.exposed.remove(&dead_id); + global_state.exposed.remove(&dead_id); // Also remember this address for future reuse. - self.reuse.add_addr(rng, addr, size, align) + let thread = self.threads.get_active_thread_id(); + global_state.reuse.add_addr(rng, addr, size, align, kind, thread, || { + if let Some(data_race) = &self.data_race { + data_race + .release_clock(thread, self.threads.active_thread_ref().current_span()) + .clone() + } else { + VClock::default() + } + }) } } diff --git a/src/alloc_addresses/reuse_pool.rs b/src/alloc_addresses/reuse_pool.rs index 8374d0ec60..77fc9f53f9 100644 --- a/src/alloc_addresses/reuse_pool.rs +++ b/src/alloc_addresses/reuse_pool.rs @@ -4,11 +4,9 @@ use rand::Rng; use rustc_target::abi::{Align, Size}; -const MAX_POOL_SIZE: usize = 64; +use crate::{concurrency::VClock, MemoryKind, MiriConfig, ThreadId}; -// Just use fair coins, until we have evidence that other numbers are better. -const ADDR_REMEMBER_CHANCE: f64 = 0.5; -const ADDR_TAKE_CHANCE: f64 = 0.5; +const MAX_POOL_SIZE: usize = 64; /// The pool strikes a balance between exploring more possible executions and making it more likely /// to find bugs. The hypothesis is that bugs are more likely to occur when reuse happens for @@ -16,20 +14,29 @@ const ADDR_TAKE_CHANCE: f64 = 0.5; /// structure. Therefore we only reuse allocations when size and alignment match exactly. #[derive(Debug)] pub struct ReusePool { + address_reuse_rate: f64, + address_reuse_cross_thread_rate: f64, /// The i-th element in `pool` stores allocations of alignment `2^i`. We store these reusable - /// allocations as address-size pairs, the list must be sorted by the size. + /// allocations as address-size pairs, the list must be sorted by the size and then the thread ID. /// /// Each of these maps has at most MAX_POOL_SIZE elements, and since alignment is limited to /// less than 64 different possible value, that bounds the overall size of the pool. - pool: Vec>, + /// + /// We also store the ID and the data-race clock of the thread that donated this pool element, + /// to ensure synchronization with the thread that picks up this address. + pool: Vec>, } impl ReusePool { - pub fn new() -> Self { - ReusePool { pool: vec![] } + pub fn new(config: &MiriConfig) -> Self { + ReusePool { + address_reuse_rate: config.address_reuse_rate, + address_reuse_cross_thread_rate: config.address_reuse_cross_thread_rate, + pool: vec![], + } } - fn subpool(&mut self, align: Align) -> &mut Vec<(u64, Size)> { + fn subpool(&mut self, align: Align) -> &mut Vec<(u64, Size, ThreadId, VClock)> { let pool_idx: usize = align.bytes().trailing_zeros().try_into().unwrap(); if self.pool.len() <= pool_idx { self.pool.resize(pool_idx + 1, Vec::new()); @@ -37,40 +44,73 @@ impl ReusePool { &mut self.pool[pool_idx] } - pub fn add_addr(&mut self, rng: &mut impl Rng, addr: u64, size: Size, align: Align) { + pub fn add_addr( + &mut self, + rng: &mut impl Rng, + addr: u64, + size: Size, + align: Align, + kind: MemoryKind, + thread: ThreadId, + clock: impl FnOnce() -> VClock, + ) { // Let's see if we even want to remember this address. - if !rng.gen_bool(ADDR_REMEMBER_CHANCE) { + // We don't remember stack addresses: there's a lot of them (so the perf impact is big), + // and we only want to reuse stack slots within the same thread or else we'll add a lot of + // undesired synchronization. + if kind == MemoryKind::Stack || !rng.gen_bool(self.address_reuse_rate) { return; } + let clock = clock(); // Determine the pool to add this to, and where in the pool to put it. let subpool = self.subpool(align); - let pos = subpool.partition_point(|(_addr, other_size)| *other_size < size); + let pos = subpool.partition_point(|(_addr, other_size, other_thread, _)| { + (*other_size, *other_thread) < (size, thread) + }); // Make sure the pool does not grow too big. if subpool.len() >= MAX_POOL_SIZE { // Pool full. Replace existing element, or last one if this would be even bigger. let clamped_pos = pos.min(subpool.len() - 1); - subpool[clamped_pos] = (addr, size); + subpool[clamped_pos] = (addr, size, thread, clock); return; } // Add address to pool, at the right position. - subpool.insert(pos, (addr, size)); + subpool.insert(pos, (addr, size, thread, clock)); } - pub fn take_addr(&mut self, rng: &mut impl Rng, size: Size, align: Align) -> Option { - // Determine whether we'll even attempt a reuse. - if !rng.gen_bool(ADDR_TAKE_CHANCE) { + /// Returns the address to use and optionally a clock we have to synchronize with. + pub fn take_addr( + &mut self, + rng: &mut impl Rng, + size: Size, + align: Align, + kind: MemoryKind, + thread: ThreadId, + ) -> Option<(u64, Option)> { + // Determine whether we'll even attempt a reuse. As above, we don't do reuse for stack addresses. + if kind == MemoryKind::Stack || !rng.gen_bool(self.address_reuse_rate) { return None; } + let cross_thread_reuse = rng.gen_bool(self.address_reuse_cross_thread_rate); // Determine the pool to take this from. let subpool = self.subpool(align); // Let's see if we can find something of the right size. We want to find the full range of - // such items, beginning with the first, so we can't use `binary_search_by_key`. - let begin = subpool.partition_point(|(_addr, other_size)| *other_size < size); + // such items, beginning with the first, so we can't use `binary_search_by_key`. If we do + // *not* want to consider other thread's allocations, we effectively use the lexicographic + // order on `(size, thread)`. + let begin = subpool.partition_point(|(_addr, other_size, other_thread, _)| { + *other_size < size + || (*other_size == size && !cross_thread_reuse && *other_thread < thread) + }); let mut end = begin; - while let Some((_addr, other_size)) = subpool.get(end) { + while let Some((_addr, other_size, other_thread, _)) = subpool.get(end) { if *other_size != size { break; } + if !cross_thread_reuse && *other_thread != thread { + // We entered the allocations of another thread. + break; + } end += 1; } if end == begin { @@ -80,8 +120,10 @@ impl ReusePool { // Pick a random element with the desired size. let idx = rng.gen_range(begin..end); // Remove it from the pool and return. - let (chosen_addr, chosen_size) = subpool.remove(idx); + let (chosen_addr, chosen_size, chosen_thread, clock) = subpool.remove(idx); debug_assert!(chosen_size >= size && chosen_addr % align.bytes() == 0); - Some(chosen_addr) + debug_assert!(cross_thread_reuse || chosen_thread == thread); + // No synchronization needed if we reused from the current thread. + Some((chosen_addr, if chosen_thread == thread { None } else { Some(clock) })) } } diff --git a/src/bin/miri.rs b/src/bin/miri.rs index 8fffb91542..305e9cd8d3 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -1,4 +1,3 @@ -#![feature(generic_nonzero)] #![feature(rustc_private, stmt_expr_attributes)] #![allow( clippy::manual_range_contains, @@ -32,8 +31,9 @@ use rustc_driver::Compilation; use rustc_hir::{self as hir, Node}; use rustc_interface::interface::Config; use rustc_middle::{ - middle::exported_symbols::{ - ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel, + middle::{ + codegen_fn_attrs::CodegenFnAttrFlags, + exported_symbols::{ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel}, }, query::LocalCrate, ty::TyCtxt, @@ -136,6 +136,7 @@ impl rustc_driver::Callbacks for MiriBeRustCompilerCalls { config.override_queries = Some(|_, local_providers| { // `exported_symbols` and `reachable_non_generics` provided by rustc always returns // an empty result if `tcx.sess.opts.output_types.should_codegen()` is false. + // In addition we need to add #[used] symbols to exported_symbols for `lookup_link_section`. local_providers.exported_symbols = |tcx, LocalCrate| { let reachable_set = tcx.with_stable_hashing_context(|hcx| { tcx.reachable_set(()).to_sorted(&hcx, true) @@ -160,19 +161,28 @@ impl rustc_driver::Callbacks for MiriBeRustCompilerCalls { }) if !tcx.generics_of(local_def_id).requires_monomorphization(tcx) ); - (is_reachable_non_generic - && tcx.codegen_fn_attrs(local_def_id).contains_extern_indicator()) - .then_some(( - ExportedSymbol::NonGeneric(local_def_id.to_def_id()), - // Some dummy `SymbolExportInfo` here. We only use - // `exported_symbols` in shims/foreign_items.rs and the export info - // is ignored. - SymbolExportInfo { - level: SymbolExportLevel::C, - kind: SymbolExportKind::Text, - used: false, - }, - )) + if !is_reachable_non_generic { + return None; + } + let codegen_fn_attrs = tcx.codegen_fn_attrs(local_def_id); + if codegen_fn_attrs.contains_extern_indicator() + || codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::USED) + || codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) + { + Some(( + ExportedSymbol::NonGeneric(local_def_id.to_def_id()), + // Some dummy `SymbolExportInfo` here. We only use + // `exported_symbols` in shims/foreign_items.rs and the export info + // is ignored. + SymbolExportInfo { + level: SymbolExportLevel::C, + kind: SymbolExportKind::Text, + used: false, + }, + )) + } else { + None + } }), ) } @@ -271,25 +281,6 @@ fn run_compiler( callbacks: &mut (dyn rustc_driver::Callbacks + Send), using_internal_features: std::sync::Arc, ) -> ! { - if target_crate { - // Miri needs a custom sysroot for target crates. - // If no `--sysroot` is given, the `MIRI_SYSROOT` env var is consulted to find where - // that sysroot lives, and that is passed to rustc. - let sysroot_flag = "--sysroot"; - if !args.iter().any(|e| e.starts_with(sysroot_flag)) { - // Using the built-in default here would be plain wrong, so we *require* - // the env var to make sure things make sense. - let miri_sysroot = env::var("MIRI_SYSROOT").unwrap_or_else(|_| { - show_error!( - "Miri was invoked in 'target' mode without `MIRI_SYSROOT` or `--sysroot` being set" - ) - }); - - args.push(sysroot_flag.to_owned()); - args.push(miri_sysroot); - } - } - // Don't insert `MIRI_DEFAULT_ARGS`, in particular, `--cfg=miri`, if we are building // a "host" crate. That may cause procedural macros (and probably build scripts) to // depend on Miri-only symbols, such as `miri_resolve_frame`: @@ -315,6 +306,15 @@ fn parse_comma_list(input: &str) -> Result, T::Err> { input.split(',').map(str::parse::).collect() } +/// Parses the input as a float in the range from 0.0 to 1.0 (inclusive). +fn parse_rate(input: &str) -> Result { + match input.parse::() { + Ok(rate) if rate >= 0.0 && rate <= 1.0 => Ok(rate), + Ok(_) => Err("must be between `0.0` and `1.0`"), + Err(_) => Err("requires a `f64` between `0.0` and `1.0`"), + } +} + #[cfg(any(target_os = "linux", target_os = "macos"))] fn jemalloc_magic() { // These magic runes are copied from @@ -506,15 +506,15 @@ fn main() { ); } else if let Some(param) = arg.strip_prefix("-Zmiri-env-forward=") { miri_config.forwarded_env_vars.push(param.to_owned()); - } else if let Some(param) = arg.strip_prefix("-Zmiri-track-pointer-tag=") { - let ids: Vec = match parse_comma_list(param) { - Ok(ids) => ids, - Err(err) => - show_error!( - "-Zmiri-track-pointer-tag requires a comma separated list of valid `u64` arguments: {}", - err - ), + } else if let Some(param) = arg.strip_prefix("-Zmiri-env-set=") { + let Some((name, value)) = param.split_once('=') else { + show_error!("-Zmiri-env-set requires an argument of the form ="); }; + miri_config.set_env_vars.insert(name.to_owned(), value.to_owned()); + } else if let Some(param) = arg.strip_prefix("-Zmiri-track-pointer-tag=") { + let ids: Vec = parse_comma_list(param).unwrap_or_else(|err| { + show_error!("-Zmiri-track-pointer-tag requires a comma separated list of valid `u64` arguments: {err}") + }); for id in ids.into_iter().map(miri::BorTag::new) { if let Some(id) = id { miri_config.tracked_pointer_tags.insert(id); @@ -523,14 +523,9 @@ fn main() { } } } else if let Some(param) = arg.strip_prefix("-Zmiri-track-call-id=") { - let ids: Vec = match parse_comma_list(param) { - Ok(ids) => ids, - Err(err) => - show_error!( - "-Zmiri-track-call-id requires a comma separated list of valid `u64` arguments: {}", - err - ), - }; + let ids: Vec = parse_comma_list(param).unwrap_or_else(|err| { + show_error!("-Zmiri-track-call-id requires a comma separated list of valid `u64` arguments: {err}") + }); for id in ids.into_iter().map(miri::CallId::new) { if let Some(id) = id { miri_config.tracked_call_ids.insert(id); @@ -539,56 +534,37 @@ fn main() { } } } else if let Some(param) = arg.strip_prefix("-Zmiri-track-alloc-id=") { - let ids: Vec = match parse_comma_list::>(param) { - Ok(ids) => ids.into_iter().map(miri::AllocId).collect(), - Err(err) => - show_error!( - "-Zmiri-track-alloc-id requires a comma separated list of valid non-zero `u64` arguments: {}", - err - ), - }; - miri_config.tracked_alloc_ids.extend(ids); + let ids = parse_comma_list::>(param).unwrap_or_else(|err| { + show_error!("-Zmiri-track-alloc-id requires a comma separated list of valid non-zero `u64` arguments: {err}") + }); + miri_config.tracked_alloc_ids.extend(ids.into_iter().map(miri::AllocId)); } else if arg == "-Zmiri-track-alloc-accesses" { miri_config.track_alloc_accesses = true; + } else if let Some(param) = arg.strip_prefix("-Zmiri-address-reuse-rate=") { + miri_config.address_reuse_rate = parse_rate(param) + .unwrap_or_else(|err| show_error!("-Zmiri-address-reuse-rate {err}")); + } else if let Some(param) = arg.strip_prefix("-Zmiri-address-reuse-cross-thread-rate=") { + miri_config.address_reuse_cross_thread_rate = parse_rate(param) + .unwrap_or_else(|err| show_error!("-Zmiri-address-reuse-cross-thread-rate {err}")); } else if let Some(param) = arg.strip_prefix("-Zmiri-compare-exchange-weak-failure-rate=") { - let rate = match param.parse::() { - Ok(rate) if rate >= 0.0 && rate <= 1.0 => rate, - Ok(_) => - show_error!( - "-Zmiri-compare-exchange-weak-failure-rate must be between `0.0` and `1.0`" - ), - Err(err) => - show_error!( - "-Zmiri-compare-exchange-weak-failure-rate requires a `f64` between `0.0` and `1.0`: {}", - err - ), - }; - miri_config.cmpxchg_weak_failure_rate = rate; + miri_config.cmpxchg_weak_failure_rate = parse_rate(param).unwrap_or_else(|err| { + show_error!("-Zmiri-compare-exchange-weak-failure-rate {err}") + }); } else if let Some(param) = arg.strip_prefix("-Zmiri-preemption-rate=") { - let rate = match param.parse::() { - Ok(rate) if rate >= 0.0 && rate <= 1.0 => rate, - Ok(_) => show_error!("-Zmiri-preemption-rate must be between `0.0` and `1.0`"), - Err(err) => - show_error!( - "-Zmiri-preemption-rate requires a `f64` between `0.0` and `1.0`: {}", - err - ), - }; - miri_config.preemption_rate = rate; + miri_config.preemption_rate = + parse_rate(param).unwrap_or_else(|err| show_error!("-Zmiri-preemption-rate {err}")); } else if arg == "-Zmiri-report-progress" { // This makes it take a few seconds between progress reports on my laptop. miri_config.report_progress = Some(1_000_000); } else if let Some(param) = arg.strip_prefix("-Zmiri-report-progress=") { - let interval = match param.parse::() { - Ok(i) => i, - Err(err) => show_error!("-Zmiri-report-progress requires a `u32`: {}", err), - }; + let interval = param.parse::().unwrap_or_else(|err| { + show_error!("-Zmiri-report-progress requires a `u32`: {}", err) + }); miri_config.report_progress = Some(interval); } else if let Some(param) = arg.strip_prefix("-Zmiri-provenance-gc=") { - let interval = match param.parse::() { - Ok(i) => i, - Err(err) => show_error!("-Zmiri-provenance-gc requires a `u32`: {}", err), - }; + let interval = param.parse::().unwrap_or_else(|err| { + show_error!("-Zmiri-provenance-gc requires a `u32`: {}", err) + }); miri_config.gc_interval = interval; } else if let Some(param) = arg.strip_prefix("-Zmiri-measureme=") { miri_config.measureme_out = Some(param.to_string()); @@ -599,37 +575,31 @@ fn main() { "full" => BacktraceStyle::Full, _ => show_error!("-Zmiri-backtrace may only be 0, 1, or full"), }; - } else if let Some(param) = arg.strip_prefix("-Zmiri-extern-so-file=") { + } else if let Some(param) = arg.strip_prefix("-Zmiri-native-lib=") { let filename = param.to_string(); if std::path::Path::new(&filename).exists() { - if let Some(other_filename) = miri_config.external_so_file { - show_error!( - "-Zmiri-extern-so-file is already set to {}", - other_filename.display() - ); + if let Some(other_filename) = miri_config.native_lib { + show_error!("-Zmiri-native-lib is already set to {}", other_filename.display()); } - miri_config.external_so_file = Some(filename.into()); + miri_config.native_lib = Some(filename.into()); } else { - show_error!("-Zmiri-extern-so-file `{}` does not exist", filename); + show_error!("-Zmiri-native-lib `{}` does not exist", filename); } } else if let Some(param) = arg.strip_prefix("-Zmiri-num-cpus=") { - let num_cpus = match param.parse::() { - Ok(i) => i, - Err(err) => show_error!("-Zmiri-num-cpus requires a `u32`: {}", err), - }; - + let num_cpus = param + .parse::() + .unwrap_or_else(|err| show_error!("-Zmiri-num-cpus requires a `u32`: {}", err)); miri_config.num_cpus = num_cpus; } else if let Some(param) = arg.strip_prefix("-Zmiri-force-page-size=") { - let page_size = match param.parse::() { - Ok(i) => - if i.is_power_of_two() { - i * 1024 - } else { - show_error!("-Zmiri-force-page-size requires a power of 2: {}", i) - }, - Err(err) => show_error!("-Zmiri-force-page-size requires a `u64`: {}", err), + let page_size = param.parse::().unwrap_or_else(|err| { + show_error!("-Zmiri-force-page-size requires a `u64`: {}", err) + }); + // Convert from kilobytes to bytes. + let page_size = if page_size.is_power_of_two() { + page_size * 1024 + } else { + show_error!("-Zmiri-force-page-size requires a power of 2: {page_size}"); }; - miri_config.page_size = Some(page_size); } else { // Forward to rustc. diff --git a/src/borrow_tracker/mod.rs b/src/borrow_tracker/mod.rs index 8d76a48826..24e2a9a74b 100644 --- a/src/borrow_tracker/mod.rs +++ b/src/borrow_tracker/mod.rs @@ -89,10 +89,10 @@ pub struct GlobalStateInner { borrow_tracker_method: BorrowTrackerMethod, /// Next unused pointer ID (tag). next_ptr_tag: BorTag, - /// Table storing the "base" tag for each allocation. - /// The base tag is the one used for the initial pointer. + /// Table storing the "root" tag for each allocation. + /// The root tag is the one used for the initial pointer. /// We need this in a separate table to handle cyclic statics. - base_ptr_tags: FxHashMap, + root_ptr_tags: FxHashMap, /// Next unused call ID (for protectors). next_call_id: CallId, /// All currently protected tags. @@ -175,7 +175,7 @@ impl GlobalStateInner { GlobalStateInner { borrow_tracker_method, next_ptr_tag: BorTag::one(), - base_ptr_tags: FxHashMap::default(), + root_ptr_tags: FxHashMap::default(), next_call_id: NonZero::new(1).unwrap(), protected_tags: FxHashMap::default(), tracked_pointer_tags, @@ -213,8 +213,8 @@ impl GlobalStateInner { } } - pub fn base_ptr_tag(&mut self, id: AllocId, machine: &MiriMachine<'_, '_>) -> BorTag { - self.base_ptr_tags.get(&id).copied().unwrap_or_else(|| { + pub fn root_ptr_tag(&mut self, id: AllocId, machine: &MiriMachine<'_, '_>) -> BorTag { + self.root_ptr_tags.get(&id).copied().unwrap_or_else(|| { let tag = self.new_ptr(); if self.tracked_pointer_tags.contains(&tag) { machine.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag( @@ -223,14 +223,14 @@ impl GlobalStateInner { None, )); } - trace!("New allocation {:?} has base tag {:?}", id, tag); - self.base_ptr_tags.try_insert(id, tag).unwrap(); + trace!("New allocation {:?} has rpot tag {:?}", id, tag); + self.root_ptr_tags.try_insert(id, tag).unwrap(); tag }) } pub fn remove_unreachable_allocs(&mut self, allocs: &LiveAllocs<'_, '_, '_>) { - self.base_ptr_tags.retain(|id, _| allocs.is_live(*id)); + self.root_ptr_tags.retain(|id, _| allocs.is_live(*id)); } } @@ -260,7 +260,7 @@ impl GlobalStateInner { &mut self, id: AllocId, alloc_size: Size, - kind: MemoryKind, + kind: MemoryKind, machine: &MiriMachine<'_, '_>, ) -> AllocState { match self.borrow_tracker_method { diff --git a/src/borrow_tracker/stacked_borrows/diagnostics.rs b/src/borrow_tracker/stacked_borrows/diagnostics.rs index aa99a14b18..cb677b8653 100644 --- a/src/borrow_tracker/stacked_borrows/diagnostics.rs +++ b/src/borrow_tracker/stacked_borrows/diagnostics.rs @@ -20,7 +20,7 @@ fn err_sb_ub<'tcx>( #[derive(Clone, Debug)] pub struct AllocHistory { id: AllocId, - base: (Item, Span), + root: (Item, Span), creations: smallvec::SmallVec<[Creation; 1]>, invalidations: smallvec::SmallVec<[Invalidation; 1]>, protectors: smallvec::SmallVec<[Protection; 1]>, @@ -225,7 +225,7 @@ impl AllocHistory { pub fn new(id: AllocId, item: Item, machine: &MiriMachine<'_, '_>) -> Self { Self { id, - base: (item, machine.current_span()), + root: (item, machine.current_span()), creations: SmallVec::new(), invalidations: SmallVec::new(), protectors: SmallVec::new(), @@ -342,15 +342,15 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> { }) }) .or_else(|| { - // If we didn't find a retag that created this tag, it might be the base tag of + // If we didn't find a retag that created this tag, it might be the root tag of // this allocation. - if self.history.base.0.tag() == tag { + if self.history.root.0.tag() == tag { Some(( format!( - "{tag:?} was created here, as the base tag for {:?}", + "{tag:?} was created here, as the root tag for {:?}", self.history.id ), - self.history.base.1.data(), + self.history.root.1.data(), )) } else { None @@ -438,7 +438,7 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> { .machine .threads .all_stacks() - .flatten() + .flat_map(|(_id, stack)| stack) .map(|frame| { frame.extra.borrow_tracker.as_ref().expect("we should have borrow tracking data") }) diff --git a/src/borrow_tracker/stacked_borrows/mod.rs b/src/borrow_tracker/stacked_borrows/mod.rs index 96ff298402..3da8744626 100644 --- a/src/borrow_tracker/stacked_borrows/mod.rs +++ b/src/borrow_tracker/stacked_borrows/mod.rs @@ -136,7 +136,7 @@ impl NewPermission { cx: &crate::MiriInterpCx<'_, 'tcx>, ) -> Self { // `ty` is not the `Box` but the field of the Box with this pointer (due to allocator handling). - let pointee = ty.builtin_deref(true).unwrap().ty; + let pointee = ty.builtin_deref(true).unwrap(); if pointee.is_unpin(*cx.tcx, cx.param_env()) { // A regular box. On `FnEntry` this is `noalias`, but not `dereferenceable` (hence only // a weak protector). @@ -509,7 +509,7 @@ impl Stacks { id: AllocId, size: Size, state: &mut GlobalStateInner, - kind: MemoryKind, + kind: MemoryKind, machine: &MiriMachine<'_, '_>, ) -> Self { let (base_tag, perm) = match kind { @@ -518,9 +518,9 @@ impl Stacks { // not through a pointer). That is, whenever we directly write to a local, this will pop // everything else off the stack, invalidating all previous pointers, // and in particular, *all* raw pointers. - MemoryKind::Stack => (state.base_ptr_tag(id, machine), Permission::Unique), + MemoryKind::Stack => (state.root_ptr_tag(id, machine), Permission::Unique), // Everything else is shared by default. - _ => (state.base_ptr_tag(id, machine), Permission::SharedReadWrite), + _ => (state.root_ptr_tag(id, machine), Permission::SharedReadWrite), }; Stacks::new(size, perm, base_tag, id, machine) } diff --git a/src/borrow_tracker/stacked_borrows/stack.rs b/src/borrow_tracker/stacked_borrows/stack.rs index 76430498e2..55ff09c53f 100644 --- a/src/borrow_tracker/stacked_borrows/stack.rs +++ b/src/borrow_tracker/stacked_borrows/stack.rs @@ -47,7 +47,7 @@ impl Stack { let mut first_removed = None; // We never consider removing the bottom-most tag. For stacks without an unknown - // bottom this preserves the base tag. + // bottom this preserves the root tag. // Note that the algorithm below is based on considering the tag at read_idx - 1, // so precisely considering the tag at index 0 for removal when we have an unknown // bottom would complicate the implementation. The simplification of not considering @@ -93,7 +93,7 @@ impl Stack { self.unique_range = 0..self.len(); } - // Replace any Items which have been collected with the base item, a known-good value. + // Replace any Items which have been collected with the root item, a known-good value. for i in 0..CACHE_LEN { if self.cache.idx[i] >= first_removed { self.cache.items[i] = self.borrows[0]; @@ -248,7 +248,7 @@ impl<'tcx> Stack { #[cfg(feature = "stack-cache")] fn find_granting_cache(&mut self, access: AccessKind, tag: BorTag) -> Option { // This looks like a common-sense optimization; we're going to do a linear search of the - // cache or the borrow stack to scan the shorter of the two. This optimization is miniscule + // cache or the borrow stack to scan the shorter of the two. This optimization is minuscule // and this check actually ensures we do not access an invalid cache. // When a stack is created and when items are removed from the top of the borrow stack, we // need some valid value to populate the cache. In both cases, we try to use the bottom @@ -331,7 +331,7 @@ impl<'tcx> Stack { self.verify_cache_consistency(); } - /// Construct a new `Stack` using the passed `Item` as the base tag. + /// Construct a new `Stack` using the passed `Item` as the root tag. pub fn new(item: Item) -> Self { Stack { borrows: vec![item], @@ -438,8 +438,8 @@ impl<'tcx> Stack { let mut removed = 0; let mut cursor = 0; // Remove invalid entries from the cache by rotating them to the end of the cache, then - // keep track of how many invalid elements there are and overwrite them with the base tag. - // The base tag here serves as a harmless default value. + // keep track of how many invalid elements there are and overwrite them with the root tag. + // The root tag here serves as a harmless default value. for _ in 0..CACHE_LEN - 1 { if self.cache.idx[cursor] >= start { self.cache.idx[cursor..CACHE_LEN - removed].rotate_left(1); diff --git a/src/borrow_tracker/tree_borrows/diagnostics.rs b/src/borrow_tracker/tree_borrows/diagnostics.rs index 2690bc026a..b9f0b5bc17 100644 --- a/src/borrow_tracker/tree_borrows/diagnostics.rs +++ b/src/borrow_tracker/tree_borrows/diagnostics.rs @@ -48,7 +48,7 @@ impl AccessCause { /// Complete data for an event: #[derive(Clone, Debug)] pub struct Event { - /// Transformation of permissions that occured because of this event. + /// Transformation of permissions that occurred because of this event. pub transition: PermTransition, /// Kind of the access that triggered this event. pub access_cause: AccessCause, @@ -58,7 +58,7 @@ pub struct Event { /// `None` means that this is an implicit access to the entire allocation /// (used for the implicit read on protector release). pub access_range: Option, - /// The transition recorded by this event only occured on a subrange of + /// The transition recorded by this event only occurred on a subrange of /// `access_range`: a single access on `access_range` triggers several events, /// each with their own mutually disjoint `transition_range`. No-op transitions /// should not be recorded as events, so the union of all `transition_range` is not diff --git a/src/borrow_tracker/tree_borrows/exhaustive.rs b/src/borrow_tracker/tree_borrows/exhaustive.rs index daf3590358..d50a22a910 100644 --- a/src/borrow_tracker/tree_borrows/exhaustive.rs +++ b/src/borrow_tracker/tree_borrows/exhaustive.rs @@ -2,7 +2,6 @@ //! (These are used in Tree Borrows `#[test]`s for thorough verification //! of the behavior of the state machine of permissions, //! but the contents of this file are extremely generic) -#![cfg(test)] pub trait Exhaustive: Sized { fn exhaustive() -> Box>; diff --git a/src/borrow_tracker/tree_borrows/mod.rs b/src/borrow_tracker/tree_borrows/mod.rs index a3d49756e4..b5bf16d3d3 100644 --- a/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/borrow_tracker/tree_borrows/mod.rs @@ -34,10 +34,10 @@ impl<'tcx> Tree { id: AllocId, size: Size, state: &mut GlobalStateInner, - _kind: MemoryKind, + _kind: MemoryKind, machine: &MiriMachine<'_, 'tcx>, ) -> Self { - let tag = state.base_ptr_tag(id, machine); // Fresh tag for the root + let tag = state.root_ptr_tag(id, machine); // Fresh tag for the root let span = machine.current_span(); Tree::new(tag, size, span) } @@ -173,7 +173,7 @@ impl<'tcx> NewPermission { cx: &crate::MiriInterpCx<'_, 'tcx>, zero_size: bool, ) -> Option { - let pointee = ty.builtin_deref(true).unwrap().ty; + let pointee = ty.builtin_deref(true).unwrap(); pointee.is_unpin(*cx.tcx, cx.param_env()).then_some(()).map(|()| { // Regular `Unpin` box, give it `noalias` but only a weak protector // because it is valid to deallocate it within the function. diff --git a/src/borrow_tracker/tree_borrows/tree.rs b/src/borrow_tracker/tree_borrows/tree.rs index 0fea78daa8..ff4589657a 100644 --- a/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/borrow_tracker/tree_borrows/tree.rs @@ -106,6 +106,8 @@ impl LocationState { let old_perm = self.permission; let transition = Permission::perform_access(access_kind, rel_pos, old_perm, protected) .ok_or(TransitionError::ChildAccessForbidden(old_perm))?; + self.initialized |= !rel_pos.is_foreign(); + self.permission = transition.applied(old_perm).unwrap(); // Why do only initialized locations cause protector errors? // Consider two mutable references `x`, `y` into disjoint parts of // the same allocation. A priori, these may actually both be used to @@ -123,8 +125,6 @@ impl LocationState { if protected && self.initialized && transition.produces_disabled() { return Err(TransitionError::ProtectedDisabled(old_perm)); } - self.permission = transition.applied(old_perm).unwrap(); - self.initialized |= !rel_pos.is_foreign(); Ok(transition) } @@ -473,7 +473,7 @@ impl Tree { let rperms = { let mut perms = UniValMap::default(); // We manually set it to `Active` on all in-bounds positions. - // We also ensure that it is initalized, so that no `Active` but + // We also ensure that it is initialized, so that no `Active` but // not yet initialized nodes exist. Essentially, we pretend there // was a write that initialized these to `Active`. perms.insert(root_idx, LocationState::new_init(Permission::new_active())); diff --git a/src/borrow_tracker/tree_borrows/tree/tests.rs b/src/borrow_tracker/tree_borrows/tree/tests.rs index f568850d8d..6777f41ac2 100644 --- a/src/borrow_tracker/tree_borrows/tree/tests.rs +++ b/src/borrow_tracker/tree_borrows/tree/tests.rs @@ -75,7 +75,7 @@ fn protected_enforces_noalias() { } } -/// We are going to exhaustively test the possibily of inserting +/// We are going to exhaustively test the possibility of inserting /// a spurious read in some code. /// /// We choose some pointer `x` through which we want a spurious read to be inserted. @@ -270,7 +270,7 @@ mod spurious_read { match self { TestEvent::Access(acc) => write!(f, "{acc}"), // The fields of the `Ret` variants just serve to make them - // impossible to instanciate via the `RetX = NoRet` type; we can + // impossible to instantiate via the `RetX = NoRet` type; we can // always ignore their value. TestEvent::RetX(_) => write!(f, "ret x"), TestEvent::RetY(_) => write!(f, "ret y"), @@ -395,7 +395,7 @@ mod spurious_read { match evt { TestEvent::Access(acc) => self.perform_test_access(acc), // The fields of the `Ret` variants just serve to make them - // impossible to instanciate via the `RetX = NoRet` type; we can + // impossible to instantiate via the `RetX = NoRet` type; we can // always ignore their value. TestEvent::RetX(_) => self.end_protector_x(), TestEvent::RetY(_) => self.end_protector_y(), diff --git a/src/concurrency/data_race.rs b/src/concurrency/data_race.rs index d51160b283..f2bec972b1 100644 --- a/src/concurrency/data_race.rs +++ b/src/concurrency/data_race.rs @@ -547,9 +547,9 @@ impl MemoryCellClocks { ) -> Result<(), DataRace> { trace!("Unsynchronized read with vectors: {:#?} :: {:#?}", self, thread_clocks); if !current_span.is_dummy() { - thread_clocks.clock[index].span = current_span; + thread_clocks.clock.index_mut(index).span = current_span; } - thread_clocks.clock[index].set_read_type(read_type); + thread_clocks.clock.index_mut(index).set_read_type(read_type); if self.write_was_before(&thread_clocks.clock) { let race_free = if let Some(atomic) = self.atomic() { // We must be ordered-after all atomic accesses, reads and writes. @@ -577,7 +577,7 @@ impl MemoryCellClocks { ) -> Result<(), DataRace> { trace!("Unsynchronized write with vectors: {:#?} :: {:#?}", self, thread_clocks); if !current_span.is_dummy() { - thread_clocks.clock[index].span = current_span; + thread_clocks.clock.index_mut(index).span = current_span; } if self.write_was_before(&thread_clocks.clock) && self.read <= thread_clocks.clock { let race_free = if let Some(atomic) = self.atomic() { @@ -844,9 +844,10 @@ impl VClockAlloc { global: &GlobalState, thread_mgr: &ThreadManager<'_, '_>, len: Size, - kind: MemoryKind, + kind: MemoryKind, current_span: Span, ) -> VClockAlloc { + // Determine the thread that did the allocation, and when it did it. let (alloc_timestamp, alloc_index) = match kind { // User allocated and stack memory should track allocation. MemoryKind::Machine( @@ -858,13 +859,13 @@ impl VClockAlloc { | MiriMemoryKind::Mmap, ) | MemoryKind::Stack => { - let (alloc_index, clocks) = global.current_thread_state(thread_mgr); + let (alloc_index, clocks) = global.active_thread_state(thread_mgr); let mut alloc_timestamp = clocks.clock[alloc_index]; alloc_timestamp.span = current_span; (alloc_timestamp, alloc_index) } // Other global memory should trace races but be allocated at the 0 timestamp - // (conceptually they are allocated before everything). + // (conceptually they are allocated on the main thread before everything). MemoryKind::Machine( MiriMemoryKind::Global | MiriMemoryKind::Machine @@ -872,7 +873,8 @@ impl VClockAlloc { | MiriMemoryKind::ExternStatic | MiriMemoryKind::Tls, ) - | MemoryKind::CallerLocation => (VTimestamp::ZERO, VectorIdx::MAX_INDEX), + | MemoryKind::CallerLocation => + (VTimestamp::ZERO, global.thread_index(ThreadId::MAIN_THREAD)), }; VClockAlloc { alloc_ranges: RefCell::new(RangeMap::new( @@ -930,7 +932,7 @@ impl VClockAlloc { ptr_dbg: Pointer, ty: Option>, ) -> InterpResult<'tcx> { - let (current_index, current_clocks) = global.current_thread_state(thread_mgr); + let (active_index, active_clocks) = global.active_thread_state(thread_mgr); let mut other_size = None; // if `Some`, this was a size-mismatch race let write_clock; let (other_access, other_thread, other_clock) = @@ -939,30 +941,30 @@ impl VClockAlloc { // we are reporting races between two non-atomic reads. if !access.is_atomic() && let Some(atomic) = mem_clocks.atomic() && - let Some(idx) = Self::find_gt_index(&atomic.write_vector, ¤t_clocks.clock) + let Some(idx) = Self::find_gt_index(&atomic.write_vector, &active_clocks.clock) { (AccessType::AtomicStore, idx, &atomic.write_vector) } else if !access.is_atomic() && let Some(atomic) = mem_clocks.atomic() && - let Some(idx) = Self::find_gt_index(&atomic.read_vector, ¤t_clocks.clock) + let Some(idx) = Self::find_gt_index(&atomic.read_vector, &active_clocks.clock) { (AccessType::AtomicLoad, idx, &atomic.read_vector) // Then check races with non-atomic writes/reads. - } else if mem_clocks.write.1 > current_clocks.clock[mem_clocks.write.0] { + } else if mem_clocks.write.1 > active_clocks.clock[mem_clocks.write.0] { write_clock = mem_clocks.write(); (AccessType::NaWrite(mem_clocks.write_type), mem_clocks.write.0, &write_clock) - } else if let Some(idx) = Self::find_gt_index(&mem_clocks.read, ¤t_clocks.clock) { + } else if let Some(idx) = Self::find_gt_index(&mem_clocks.read, &active_clocks.clock) { (AccessType::NaRead(mem_clocks.read[idx].read_type()), idx, &mem_clocks.read) // Finally, mixed-size races. } else if access.is_atomic() && let Some(atomic) = mem_clocks.atomic() && atomic.size != access_size { // This is only a race if we are not synchronized with all atomic accesses, so find // the one we are not synchronized with. other_size = Some(atomic.size); - if let Some(idx) = Self::find_gt_index(&atomic.write_vector, ¤t_clocks.clock) + if let Some(idx) = Self::find_gt_index(&atomic.write_vector, &active_clocks.clock) { (AccessType::AtomicStore, idx, &atomic.write_vector) } else if let Some(idx) = - Self::find_gt_index(&atomic.read_vector, ¤t_clocks.clock) + Self::find_gt_index(&atomic.read_vector, &active_clocks.clock) { (AccessType::AtomicLoad, idx, &atomic.read_vector) } else { @@ -975,7 +977,7 @@ impl VClockAlloc { }; // Load elaborated thread information about the racing thread actions. - let current_thread_info = global.print_thread_metadata(thread_mgr, current_index); + let active_thread_info = global.print_thread_metadata(thread_mgr, active_index); let other_thread_info = global.print_thread_metadata(thread_mgr, other_thread); let involves_non_atomic = !access.is_atomic() || !other_access.is_atomic(); @@ -1003,8 +1005,8 @@ impl VClockAlloc { }, op2: RacingOp { action: access.description(ty, other_size.map(|_| access_size)), - thread_info: current_thread_info, - span: current_clocks.clock.as_slice()[current_index.index()].span_data(), + thread_info: active_thread_info, + span: active_clocks.clock.as_slice()[active_index.index()].span_data(), }, }))? } @@ -1026,7 +1028,7 @@ impl VClockAlloc { let current_span = machine.current_span(); let global = machine.data_race.as_ref().unwrap(); if global.race_detecting() { - let (index, mut thread_clocks) = global.current_thread_state_mut(&machine.threads); + let (index, mut thread_clocks) = global.active_thread_state_mut(&machine.threads); let mut alloc_ranges = self.alloc_ranges.borrow_mut(); for (mem_clocks_range, mem_clocks) in alloc_ranges.iter_mut(access_range.start, access_range.size) @@ -1069,7 +1071,7 @@ impl VClockAlloc { let current_span = machine.current_span(); let global = machine.data_race.as_mut().unwrap(); if global.race_detecting() { - let (index, mut thread_clocks) = global.current_thread_state_mut(&machine.threads); + let (index, mut thread_clocks) = global.active_thread_state_mut(&machine.threads); for (mem_clocks_range, mem_clocks) in self.alloc_ranges.get_mut().iter_mut(access_range.start, access_range.size) { @@ -1454,7 +1456,7 @@ impl GlobalState { // Setup the main-thread since it is not explicitly created: // uses vector index and thread-id 0. let index = global_state.vector_clocks.get_mut().push(ThreadClockSet::default()); - global_state.vector_info.get_mut().push(ThreadId::new(0)); + global_state.vector_info.get_mut().push(ThreadId::MAIN_THREAD); global_state .thread_info .get_mut() @@ -1518,7 +1520,7 @@ impl GlobalState { thread: ThreadId, current_span: Span, ) { - let current_index = self.current_index(thread_mgr); + let current_index = self.active_thread_index(thread_mgr); // Enable multi-threaded execution, there are now at least two threads // so data-races are now possible. @@ -1642,7 +1644,7 @@ impl GlobalState { /// `thread_joined`. #[inline] pub fn thread_terminated(&mut self, thread_mgr: &ThreadManager<'_, '_>, current_span: Span) { - let current_index = self.current_index(thread_mgr); + let current_index = self.active_thread_index(thread_mgr); // Increment the clock to a unique termination timestamp. let vector_clocks = self.vector_clocks.get_mut(); @@ -1680,9 +1682,9 @@ impl GlobalState { op: impl FnOnce(VectorIdx, RefMut<'_, ThreadClockSet>) -> InterpResult<'tcx, bool>, ) -> InterpResult<'tcx> { if self.multi_threaded.get() { - let (index, clocks) = self.current_thread_state_mut(thread_mgr); + let (index, clocks) = self.active_thread_state_mut(thread_mgr); if op(index, clocks)? { - let (_, mut clocks) = self.current_thread_state_mut(thread_mgr); + let (_, mut clocks) = self.active_thread_state_mut(thread_mgr); clocks.increment_clock(index, current_span); } } @@ -1701,102 +1703,91 @@ impl GlobalState { format!("thread `{thread_name}`") } - /// Acquire a lock, express that the previous call of - /// `validate_lock_release` must happen before this. + /// Acquire the given clock into the given thread, establishing synchronization with + /// the moment when that clock snapshot was taken via `release_clock`. /// As this is an acquire operation, the thread timestamp is not /// incremented. - pub fn validate_lock_acquire(&self, lock: &VClock, thread: ThreadId) { - let (_, mut clocks) = self.load_thread_state_mut(thread); + pub fn acquire_clock(&self, lock: &VClock, thread: ThreadId) { + let (_, mut clocks) = self.thread_state_mut(thread); clocks.clock.join(lock); } - /// Release a lock handle, express that this happens-before - /// any subsequent calls to `validate_lock_acquire`. - /// For normal locks this should be equivalent to `validate_lock_release_shared` - /// since an acquire operation should have occurred before, however - /// for futex & condvar operations this is not the case and this - /// operation must be used. - pub fn validate_lock_release(&self, lock: &mut VClock, thread: ThreadId, current_span: Span) { - let (index, mut clocks) = self.load_thread_state_mut(thread); - lock.clone_from(&clocks.clock); + /// Returns the `release` clock of the given thread. + /// Other threads can acquire this clock in the future to establish synchronization + /// with this program point. + pub fn release_clock(&self, thread: ThreadId, current_span: Span) -> Ref<'_, VClock> { + // We increment the clock each time this happens, to ensure no two releases + // can be confused with each other. + let (index, mut clocks) = self.thread_state_mut(thread); clocks.increment_clock(index, current_span); + drop(clocks); + // To return a read-only view, we need to release the RefCell + // and borrow it again. + let (_index, clocks) = self.thread_state(thread); + Ref::map(clocks, |c| &c.clock) } - /// Release a lock handle, express that this happens-before - /// any subsequent calls to `validate_lock_acquire` as well - /// as any previous calls to this function after any - /// `validate_lock_release` calls. - /// For normal locks this should be equivalent to `validate_lock_release`. - /// This function only exists for joining over the set of concurrent readers - /// in a read-write lock and should not be used for anything else. - pub fn validate_lock_release_shared( - &self, - lock: &mut VClock, - thread: ThreadId, - current_span: Span, - ) { - let (index, mut clocks) = self.load_thread_state_mut(thread); - lock.join(&clocks.clock); - clocks.increment_clock(index, current_span); + fn thread_index(&self, thread: ThreadId) -> VectorIdx { + self.thread_info.borrow()[thread].vector_index.expect("thread has no assigned vector") } /// Load the vector index used by the given thread as well as the set of vector clocks /// used by the thread. #[inline] - fn load_thread_state_mut(&self, thread: ThreadId) -> (VectorIdx, RefMut<'_, ThreadClockSet>) { - let index = self.thread_info.borrow()[thread] - .vector_index - .expect("Loading thread state for thread with no assigned vector"); + fn thread_state_mut(&self, thread: ThreadId) -> (VectorIdx, RefMut<'_, ThreadClockSet>) { + let index = self.thread_index(thread); let ref_vector = self.vector_clocks.borrow_mut(); let clocks = RefMut::map(ref_vector, |vec| &mut vec[index]); (index, clocks) } + /// Load the vector index used by the given thread as well as the set of vector clocks + /// used by the thread. + #[inline] + fn thread_state(&self, thread: ThreadId) -> (VectorIdx, Ref<'_, ThreadClockSet>) { + let index = self.thread_index(thread); + let ref_vector = self.vector_clocks.borrow(); + let clocks = Ref::map(ref_vector, |vec| &vec[index]); + (index, clocks) + } + /// Load the current vector clock in use and the current set of thread clocks /// in use for the vector. #[inline] - pub(super) fn current_thread_state( + pub(super) fn active_thread_state( &self, thread_mgr: &ThreadManager<'_, '_>, ) -> (VectorIdx, Ref<'_, ThreadClockSet>) { - let index = self.current_index(thread_mgr); - let ref_vector = self.vector_clocks.borrow(); - let clocks = Ref::map(ref_vector, |vec| &vec[index]); - (index, clocks) + self.thread_state(thread_mgr.get_active_thread_id()) } /// Load the current vector clock in use and the current set of thread clocks /// in use for the vector mutably for modification. #[inline] - pub(super) fn current_thread_state_mut( + pub(super) fn active_thread_state_mut( &self, thread_mgr: &ThreadManager<'_, '_>, ) -> (VectorIdx, RefMut<'_, ThreadClockSet>) { - let index = self.current_index(thread_mgr); - let ref_vector = self.vector_clocks.borrow_mut(); - let clocks = RefMut::map(ref_vector, |vec| &mut vec[index]); - (index, clocks) + self.thread_state_mut(thread_mgr.get_active_thread_id()) } /// Return the current thread, should be the same /// as the data-race active thread. #[inline] - fn current_index(&self, thread_mgr: &ThreadManager<'_, '_>) -> VectorIdx { + fn active_thread_index(&self, thread_mgr: &ThreadManager<'_, '_>) -> VectorIdx { let active_thread_id = thread_mgr.get_active_thread_id(); - self.thread_info.borrow()[active_thread_id] - .vector_index - .expect("active thread has no assigned vector") + self.thread_index(active_thread_id) } // SC ATOMIC STORE rule in the paper. pub(super) fn sc_write(&self, thread_mgr: &ThreadManager<'_, '_>) { - let (index, clocks) = self.current_thread_state(thread_mgr); + let (index, clocks) = self.active_thread_state(thread_mgr); self.last_sc_write.borrow_mut().set_at_index(&clocks.clock, index); } // SC ATOMIC READ rule in the paper. pub(super) fn sc_read(&self, thread_mgr: &ThreadManager<'_, '_>) { - let (.., mut clocks) = self.current_thread_state_mut(thread_mgr); + let (.., mut clocks) = self.active_thread_state_mut(thread_mgr); clocks.read_seqcst.join(&self.last_sc_fence.borrow()); } } diff --git a/src/concurrency/init_once.rs b/src/concurrency/init_once.rs index 35dcfecbbe..a01b59c916 100644 --- a/src/concurrency/init_once.rs +++ b/src/concurrency/init_once.rs @@ -41,7 +41,7 @@ pub enum InitOnceStatus { pub(super) struct InitOnce<'mir, 'tcx> { status: InitOnceStatus, waiters: VecDeque>, - data_race: VClock, + clock: VClock, } impl<'mir, 'tcx> VisitProvenance for InitOnce<'mir, 'tcx> { @@ -61,10 +61,8 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let current_thread = this.get_active_thread(); if let Some(data_race) = &this.machine.data_race { - data_race.validate_lock_acquire( - &this.machine.threads.sync.init_onces[id].data_race, - current_thread, - ); + data_race + .acquire_clock(&this.machine.threads.sync.init_onces[id].clock, current_thread); } } @@ -77,7 +75,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); let current_thread = this.get_active_thread(); - this.unblock_thread(waiter.thread); + this.unblock_thread(waiter.thread, BlockReason::InitOnce(id)); // Call callback, with the woken-up thread as `current`. this.set_active_thread(waiter.thread); @@ -142,7 +140,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let init_once = &mut this.machine.threads.sync.init_onces[id]; assert_ne!(init_once.status, InitOnceStatus::Complete, "queueing on complete init once"); init_once.waiters.push_back(InitOnceWaiter { thread, callback }); - this.block_thread(thread); + this.block_thread(thread, BlockReason::InitOnce(id)); } /// Begin initializing this InitOnce. Must only be called after checking that it is currently @@ -176,7 +174,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Each complete happens-before the end of the wait if let Some(data_race) = &this.machine.data_race { - data_race.validate_lock_release(&mut init_once.data_race, current_thread, current_span); + init_once.clock.clone_from(&data_race.release_clock(current_thread, current_span)); } // Wake up everyone. @@ -202,7 +200,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Each complete happens-before the end of the wait if let Some(data_race) = &this.machine.data_race { - data_race.validate_lock_release(&mut init_once.data_race, current_thread, current_span); + init_once.clock.clone_from(&data_race.release_clock(current_thread, current_span)); } // Wake up one waiting thread, so they can go ahead and try to init this. diff --git a/src/concurrency/mod.rs b/src/concurrency/mod.rs index 45903107f1..15e1a94d6d 100644 --- a/src/concurrency/mod.rs +++ b/src/concurrency/mod.rs @@ -6,3 +6,5 @@ pub mod init_once; pub mod thread; mod vector_clock; pub mod weak_memory; + +pub use vector_clock::VClock; diff --git a/src/concurrency/sync.rs b/src/concurrency/sync.rs index 956a02ded0..9467695599 100644 --- a/src/concurrency/sync.rs +++ b/src/concurrency/sync.rs @@ -69,12 +69,8 @@ struct Mutex { lock_count: usize, /// The queue of threads waiting for this mutex. queue: VecDeque, - /// Data race handle. This tracks the happens-before - /// relationship between each mutex access. It is - /// released to during unlock and acquired from during - /// locking, and therefore stores the clock of the last - /// thread to release this mutex. - data_race: VClock, + /// Mutex clock. This tracks the moment of the last unlock. + clock: VClock, } declare_id!(RwLockId); @@ -91,7 +87,7 @@ struct RwLock { writer_queue: VecDeque, /// The queue of reader threads waiting for this lock. reader_queue: VecDeque, - /// Data race handle for writers. Tracks the happens-before + /// Data race clock for writers. Tracks the happens-before /// ordering between each write access to a rwlock and is updated /// after a sequence of concurrent readers to track the happens- /// before ordering between the set of previous readers and @@ -99,8 +95,8 @@ struct RwLock { /// Contains the clock of the last thread to release a writer /// lock or the joined clock of the set of last threads to release /// shared reader locks. - data_race: VClock, - /// Data race handle for readers. This is temporary storage + clock_unlocked: VClock, + /// Data race clock for readers. This is temporary storage /// for the combined happens-before ordering for between all /// concurrent readers and the next writer, and the value /// is stored to the main data_race variable once all @@ -110,30 +106,18 @@ struct RwLock { /// add happens-before orderings between shared reader /// locks. /// This is only relevant when there is an active reader. - data_race_reader: VClock, + clock_current_readers: VClock, } declare_id!(CondvarId); -#[derive(Debug, Copy, Clone)] -pub enum RwLockMode { - Read, - Write, -} - -#[derive(Debug)] -pub enum CondvarLock { - Mutex(MutexId), - RwLock { id: RwLockId, mode: RwLockMode }, -} - /// A thread waiting on a conditional variable. #[derive(Debug)] struct CondvarWaiter { /// The thread that is waiting on this variable. thread: ThreadId, - /// The mutex or rwlock on which the thread is waiting. - lock: CondvarLock, + /// The mutex on which the thread is waiting. + lock: MutexId, } /// The conditional variable state. @@ -144,8 +128,8 @@ struct Condvar { /// between a cond-var signal and a cond-var /// wait during a non-spurious signal event. /// Contains the clock of the last thread to - /// perform a futex-signal. - data_race: VClock, + /// perform a condvar-signal. + clock: VClock, } /// The futex state. @@ -157,7 +141,7 @@ struct Futex { /// during a non-spurious wake event. /// Contains the clock of the last thread to /// perform a futex-wake. - data_race: VClock, + clock: VClock, } /// A thread waiting on a futex. @@ -232,7 +216,7 @@ pub(super) trait EvalContextExtPriv<'mir, 'tcx: 'mir>: fn rwlock_dequeue_and_lock_reader(&mut self, id: RwLockId) -> bool { let this = self.eval_context_mut(); if let Some(reader) = this.machine.threads.sync.rwlocks[id].reader_queue.pop_front() { - this.unblock_thread(reader); + this.unblock_thread(reader, BlockReason::RwLock(id)); this.rwlock_reader_lock(id, reader); true } else { @@ -246,7 +230,7 @@ pub(super) trait EvalContextExtPriv<'mir, 'tcx: 'mir>: fn rwlock_dequeue_and_lock_writer(&mut self, id: RwLockId) -> bool { let this = self.eval_context_mut(); if let Some(writer) = this.machine.threads.sync.rwlocks[id].writer_queue.pop_front() { - this.unblock_thread(writer); + this.unblock_thread(writer, BlockReason::RwLock(id)); this.rwlock_writer_lock(id, writer); true } else { @@ -260,7 +244,7 @@ pub(super) trait EvalContextExtPriv<'mir, 'tcx: 'mir>: fn mutex_dequeue_and_lock(&mut self, id: MutexId) -> bool { let this = self.eval_context_mut(); if let Some(thread) = this.machine.threads.sync.mutexes[id].queue.pop_front() { - this.unblock_thread(thread); + this.unblock_thread(thread, BlockReason::Mutex(id)); this.mutex_lock(id, thread); true } else { @@ -321,6 +305,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); let next_index = this.machine.threads.sync.mutexes.next_index(); if let Some(old) = existing(this, next_index)? { + if this.machine.threads.sync.mutexes.get(old).is_none() { + throw_ub_format!("mutex has invalid ID"); + } Ok(old) } else { let new_index = this.machine.threads.sync.mutexes.push(Default::default()); @@ -358,7 +345,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } mutex.lock_count = mutex.lock_count.checked_add(1).unwrap(); if let Some(data_race) = &this.machine.data_race { - data_race.validate_lock_acquire(&mutex.data_race, thread); + data_race.acquire_clock(&mutex.clock, thread); } } @@ -385,11 +372,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // The mutex is completely unlocked. Try transferring ownership // to another thread. if let Some(data_race) = &this.machine.data_race { - data_race.validate_lock_release( - &mut mutex.data_race, - current_owner, - current_span, - ); + mutex.clock.clone_from(&data_race.release_clock(current_owner, current_span)); } this.mutex_dequeue_and_lock(id); } @@ -406,7 +389,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); assert!(this.mutex_is_locked(id), "queing on unlocked mutex"); this.machine.threads.sync.mutexes[id].queue.push_back(thread); - this.block_thread(thread); + this.block_thread(thread, BlockReason::Mutex(id)); } /// Provides the closure with the next RwLockId. Creates that RwLock if the closure returns None, @@ -419,6 +402,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); let next_index = this.machine.threads.sync.rwlocks.next_index(); if let Some(old) = existing(this, next_index)? { + if this.machine.threads.sync.rwlocks.get(old).is_none() { + throw_ub_format!("rwlock has invalid ID"); + } Ok(old) } else { let new_index = this.machine.threads.sync.rwlocks.push(Default::default()); @@ -460,7 +446,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let count = rwlock.readers.entry(reader).or_insert(0); *count = count.checked_add(1).expect("the reader counter overflowed"); if let Some(data_race) = &this.machine.data_race { - data_race.validate_lock_acquire(&rwlock.data_race, reader); + data_race.acquire_clock(&rwlock.clock_unlocked, reader); } } @@ -486,20 +472,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } if let Some(data_race) = &this.machine.data_race { // Add this to the shared-release clock of all concurrent readers. - data_race.validate_lock_release_shared( - &mut rwlock.data_race_reader, - reader, - current_span, - ); + rwlock.clock_current_readers.join(&data_race.release_clock(reader, current_span)); } // The thread was a reader. If the lock is not held any more, give it to a writer. if this.rwlock_is_locked(id).not() { // All the readers are finished, so set the writer data-race handle to the value - // of the union of all reader data race handles, since the set of readers - // happen-before the writers + // of the union of all reader data race handles, since the set of readers + // happen-before the writers let rwlock = &mut this.machine.threads.sync.rwlocks[id]; - rwlock.data_race.clone_from(&rwlock.data_race_reader); + rwlock.clock_unlocked.clone_from(&rwlock.clock_current_readers); this.rwlock_dequeue_and_lock_writer(id); } true @@ -511,7 +493,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); assert!(this.rwlock_is_write_locked(id), "read-queueing on not write locked rwlock"); this.machine.threads.sync.rwlocks[id].reader_queue.push_back(reader); - this.block_thread(reader); + this.block_thread(reader, BlockReason::RwLock(id)); } /// Lock by setting the writer that owns the lock. @@ -523,7 +505,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let rwlock = &mut this.machine.threads.sync.rwlocks[id]; rwlock.writer = Some(writer); if let Some(data_race) = &this.machine.data_race { - data_race.validate_lock_acquire(&rwlock.data_race, writer); + data_race.acquire_clock(&rwlock.clock_unlocked, writer); } } @@ -542,11 +524,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { trace!("rwlock_writer_unlock: {:?} unlocked by {:?}", id, expected_writer); // Release memory to next lock holder. if let Some(data_race) = &this.machine.data_race { - data_race.validate_lock_release( - &mut rwlock.data_race, - current_writer, - current_span, - ); + rwlock + .clock_unlocked + .clone_from(&*data_race.release_clock(current_writer, current_span)); } // The thread was a writer. // @@ -573,7 +553,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); assert!(this.rwlock_is_locked(id), "write-queueing on unlocked rwlock"); this.machine.threads.sync.rwlocks[id].writer_queue.push_back(writer); - this.block_thread(writer); + this.block_thread(writer, BlockReason::RwLock(id)); } /// Provides the closure with the next CondvarId. Creates that Condvar if the closure returns None, @@ -589,6 +569,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); let next_index = this.machine.threads.sync.condvars.next_index(); if let Some(old) = existing(this, next_index)? { + if this.machine.threads.sync.condvars.get(old).is_none() { + throw_ub_format!("condvar has invalid ID"); + } Ok(old) } else { let new_index = this.machine.threads.sync.condvars.push(Default::default()); @@ -605,7 +588,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } /// Mark that the thread is waiting on the conditional variable. - fn condvar_wait(&mut self, id: CondvarId, thread: ThreadId, lock: CondvarLock) { + fn condvar_wait(&mut self, id: CondvarId, thread: ThreadId, lock: MutexId) { let this = self.eval_context_mut(); let waiters = &mut this.machine.threads.sync.condvars[id].waiters; assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting"); @@ -614,7 +597,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { /// Wake up some thread (if there is any) sleeping on the conditional /// variable. - fn condvar_signal(&mut self, id: CondvarId) -> Option<(ThreadId, CondvarLock)> { + fn condvar_signal(&mut self, id: CondvarId) -> Option<(ThreadId, MutexId)> { let this = self.eval_context_mut(); let current_thread = this.get_active_thread(); let current_span = this.machine.current_span(); @@ -623,11 +606,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Each condvar signal happens-before the end of the condvar wake if let Some(data_race) = data_race { - data_race.validate_lock_release(&mut condvar.data_race, current_thread, current_span); + condvar.clock.clone_from(&*data_race.release_clock(current_thread, current_span)); } condvar.waiters.pop_front().map(|waiter| { if let Some(data_race) = data_race { - data_race.validate_lock_acquire(&condvar.data_race, waiter.thread); + data_race.acquire_clock(&condvar.clock, waiter.thread); } (waiter.thread, waiter.lock) }) @@ -657,14 +640,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Each futex-wake happens-before the end of the futex wait if let Some(data_race) = data_race { - data_race.validate_lock_release(&mut futex.data_race, current_thread, current_span); + futex.clock.clone_from(&*data_race.release_clock(current_thread, current_span)); } // Wake up the first thread in the queue that matches any of the bits in the bitset. futex.waiters.iter().position(|w| w.bitset & bitset != 0).map(|i| { let waiter = futex.waiters.remove(i).unwrap(); if let Some(data_race) = data_race { - data_race.validate_lock_acquire(&futex.data_race, waiter.thread); + data_race.acquire_clock(&futex.clock, waiter.thread); } waiter.thread }) diff --git a/src/concurrency/thread.rs b/src/concurrency/thread.rs index d0d73bb1b3..6953ce81c5 100644 --- a/src/concurrency/thread.rs +++ b/src/concurrency/thread.rs @@ -57,6 +57,8 @@ impl ThreadId { pub fn to_u32(self) -> u32 { self.0 } + + pub const MAIN_THREAD: ThreadId = ThreadId(0); } impl Idx for ThreadId { @@ -76,6 +78,13 @@ impl TryFrom for ThreadId { } } +impl TryFrom for ThreadId { + type Error = TryFromIntError; + fn try_from(id: i128) -> Result { + u32::try_from(id).map(Self) + } +} + impl From for ThreadId { fn from(id: u32) -> Self { Self(id) @@ -88,18 +97,33 @@ impl From for u64 { } } +/// Keeps track of what the thread is blocked on. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum BlockReason { + /// The thread tried to join the specified thread and is blocked until that + /// thread terminates. + Join(ThreadId), + /// Waiting for time to pass. + Sleep, + /// Blocked on a mutex. + Mutex(MutexId), + /// Blocked on a condition variable. + Condvar(CondvarId), + /// Blocked on a reader-writer lock. + RwLock(RwLockId), + /// Blocled on a Futex variable. + Futex { addr: u64 }, + /// Blocked on an InitOnce. + InitOnce(InitOnceId), +} + /// The state of a thread. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum ThreadState { /// The thread is enabled and can be executed. Enabled, - /// The thread tried to join the specified thread and is blocked until that - /// thread terminates. - BlockedOnJoin(ThreadId), - /// The thread is blocked on some synchronization primitive. It is the - /// responsibility of the synchronization primitives to track threads that - /// are blocked by them. - BlockedOnSync, + /// The thread is blocked on something. + Blocked(BlockReason), /// The thread has terminated its execution. We do not delete terminated /// threads (FIXME: why?). Terminated, @@ -158,7 +182,7 @@ pub struct Thread<'mir, 'tcx> { } pub type StackEmptyCallback<'mir, 'tcx> = - Box) -> InterpResult<'tcx, Poll<()>>>; + Box) -> InterpResult<'tcx, Poll<()>> + 'tcx>; impl<'mir, 'tcx> Thread<'mir, 'tcx> { /// Get the name of the current thread if it was set. @@ -208,6 +232,12 @@ impl<'mir, 'tcx> Thread<'mir, 'tcx> { // empty stacks. self.top_user_relevant_frame.or_else(|| self.stack.len().checked_sub(1)) } + + pub fn current_span(&self) -> Span { + self.top_user_relevant_frame() + .map(|frame_idx| self.stack[frame_idx].current_span()) + .unwrap_or(rustc_span::DUMMY_SP) + } } impl<'mir, 'tcx> std::fmt::Debug for Thread<'mir, 'tcx> { @@ -296,17 +326,17 @@ impl VisitProvenance for Frame<'_, '_, Provenance, FrameExtra<'_>> { /// A specific moment in time. #[derive(Debug)] -pub enum Time { +pub enum CallbackTime { Monotonic(Instant), RealTime(SystemTime), } -impl Time { +impl CallbackTime { /// How long do we have to wait from now until the specified time? fn get_wait_time(&self, clock: &Clock) -> Duration { match self { - Time::Monotonic(instant) => instant.duration_since(clock.now()), - Time::RealTime(time) => + CallbackTime::Monotonic(instant) => instant.duration_since(clock.now()), + CallbackTime::RealTime(time) => time.duration_since(SystemTime::now()).unwrap_or(Duration::new(0, 0)), } } @@ -318,7 +348,7 @@ impl Time { /// conditional variable, the signal handler deletes the callback. struct TimeoutCallbackInfo<'mir, 'tcx> { /// The callback should be called no earlier than this time. - call_time: Time, + call_time: CallbackTime, /// The called function. callback: TimeoutCallback<'mir, 'tcx>, } @@ -380,7 +410,7 @@ impl<'mir, 'tcx> Default for ThreadManager<'mir, 'tcx> { // Create the main thread and add it to the list of threads. threads.push(Thread::new(Some("main"), None)); Self { - active_thread: ThreadId::new(0), + active_thread: ThreadId::MAIN_THREAD, threads, sync: SynchronizationState::default(), thread_local_alloc_ids: Default::default(), @@ -395,10 +425,12 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { ecx: &mut MiriInterpCx<'mir, 'tcx>, on_main_stack_empty: StackEmptyCallback<'mir, 'tcx>, ) { - ecx.machine.threads.threads[ThreadId::new(0)].on_stack_empty = Some(on_main_stack_empty); + ecx.machine.threads.threads[ThreadId::MAIN_THREAD].on_stack_empty = + Some(on_main_stack_empty); if ecx.tcx.sess.target.os.as_ref() != "windows" { // The main thread can *not* be joined on except on windows. - ecx.machine.threads.threads[ThreadId::new(0)].join_status = ThreadJoinStatus::Detached; + ecx.machine.threads.threads[ThreadId::MAIN_THREAD].join_status = + ThreadJoinStatus::Detached; } } @@ -430,11 +462,10 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { ) -> &mut Vec>> { &mut self.threads[self.active_thread].stack } - pub fn all_stacks( &self, - ) -> impl Iterator>]> { - self.threads.iter().map(|t| &t.stack[..]) + ) -> impl Iterator>])> { + self.threads.iter_enumerated().map(|(id, t)| (id, &t.stack[..])) } /// Create a new thread and returns its id. @@ -539,7 +570,8 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { self.threads[joined_thread_id].join_status = ThreadJoinStatus::Joined; if self.threads[joined_thread_id].state != ThreadState::Terminated { // The joined thread is still running, we need to wait for it. - self.active_thread_mut().state = ThreadState::BlockedOnJoin(joined_thread_id); + self.active_thread_mut().state = + ThreadState::Blocked(BlockReason::Join(joined_thread_id)); trace!( "{:?} blocked on {:?} when trying to join", self.active_thread, @@ -569,10 +601,11 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { throw_ub_format!("trying to join itself"); } + // Sanity check `join_status`. assert!( - self.threads - .iter() - .all(|thread| thread.state != ThreadState::BlockedOnJoin(joined_thread_id)), + self.threads.iter().all(|thread| { + thread.state != ThreadState::Blocked(BlockReason::Join(joined_thread_id)) + }), "this thread already has threads waiting for its termination" ); @@ -594,16 +627,17 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { } /// Put the thread into the blocked state. - fn block_thread(&mut self, thread: ThreadId) { + fn block_thread(&mut self, thread: ThreadId, reason: BlockReason) { let state = &mut self.threads[thread].state; assert_eq!(*state, ThreadState::Enabled); - *state = ThreadState::BlockedOnSync; + *state = ThreadState::Blocked(reason); } /// Put the blocked thread into the enabled state. - fn unblock_thread(&mut self, thread: ThreadId) { + /// Sanity-checks that the thread previously was blocked for the right reason. + fn unblock_thread(&mut self, thread: ThreadId, reason: BlockReason) { let state = &mut self.threads[thread].state; - assert_eq!(*state, ThreadState::BlockedOnSync); + assert_eq!(*state, ThreadState::Blocked(reason)); *state = ThreadState::Enabled; } @@ -622,7 +656,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { fn register_timeout_callback( &mut self, thread: ThreadId, - call_time: Time, + call_time: CallbackTime, callback: TimeoutCallback<'mir, 'tcx>, ) { self.timeout_callbacks @@ -683,7 +717,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { // Check if we need to unblock any threads. let mut joined_threads = vec![]; // store which threads joined, we'll need it for (i, thread) in self.threads.iter_enumerated_mut() { - if thread.state == ThreadState::BlockedOnJoin(self.active_thread) { + if thread.state == ThreadState::Blocked(BlockReason::Join(self.active_thread)) { // The thread has terminated, mark happens-before edge to joining thread if data_race.is_some() { joined_threads.push(i); @@ -999,13 +1033,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } #[inline] - fn block_thread(&mut self, thread: ThreadId) { - self.eval_context_mut().machine.threads.block_thread(thread); + fn block_thread(&mut self, thread: ThreadId, reason: BlockReason) { + self.eval_context_mut().machine.threads.block_thread(thread, reason); } #[inline] - fn unblock_thread(&mut self, thread: ThreadId) { - self.eval_context_mut().machine.threads.unblock_thread(thread); + fn unblock_thread(&mut self, thread: ThreadId, reason: BlockReason) { + self.eval_context_mut().machine.threads.unblock_thread(thread, reason); } #[inline] @@ -1027,11 +1061,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn register_timeout_callback( &mut self, thread: ThreadId, - call_time: Time, + call_time: CallbackTime, callback: TimeoutCallback<'mir, 'tcx>, ) { let this = self.eval_context_mut(); - if !this.machine.communicate() && matches!(call_time, Time::RealTime(..)) { + if !this.machine.communicate() && matches!(call_time, CallbackTime::RealTime(..)) { panic!("cannot have `RealTime` callback with isolation enabled!") } this.machine.threads.register_timeout_callback(thread, call_time, callback); diff --git a/src/concurrency/vector_clock.rs b/src/concurrency/vector_clock.rs index fe719943dc..c3496bc1a0 100644 --- a/src/concurrency/vector_clock.rs +++ b/src/concurrency/vector_clock.rs @@ -4,7 +4,7 @@ use smallvec::SmallVec; use std::{ cmp::Ordering, fmt::Debug, - ops::{Index, IndexMut, Shr}, + ops::{Index, Shr}, }; use super::data_race::NaReadType; @@ -13,15 +13,13 @@ use super::data_race::NaReadType; /// but in some cases one vector index may be shared with /// multiple thread ids if it's safe to do so. #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] -pub struct VectorIdx(u32); +pub(super) struct VectorIdx(u32); impl VectorIdx { #[inline(always)] - pub fn to_u32(self) -> u32 { + fn to_u32(self) -> u32 { self.0 } - - pub const MAX_INDEX: VectorIdx = VectorIdx(u32::MAX); } impl Idx for VectorIdx { @@ -51,7 +49,7 @@ const SMALL_VECTOR: usize = 4; /// a 32-bit unsigned integer which is the actual timestamp, and a `Span` /// so that diagnostics can report what code was responsible for an operation. #[derive(Clone, Copy, Debug)] -pub struct VTimestamp { +pub(super) struct VTimestamp { /// The lowest bit indicates read type, the rest is the time. /// `1` indicates a retag read, `0` a regular read. time_and_read_type: u32, @@ -87,17 +85,17 @@ impl VTimestamp { } #[inline] - pub fn read_type(&self) -> NaReadType { + pub(super) fn read_type(&self) -> NaReadType { if self.time_and_read_type & 1 == 0 { NaReadType::Read } else { NaReadType::Retag } } #[inline] - pub fn set_read_type(&mut self, read_type: NaReadType) { + pub(super) fn set_read_type(&mut self, read_type: NaReadType) { self.time_and_read_type = Self::encode_time_and_read_type(self.time(), read_type); } #[inline] - pub fn span_data(&self) -> SpanData { + pub(super) fn span_data(&self) -> SpanData { self.span.data() } } @@ -138,7 +136,7 @@ pub struct VClock(SmallVec<[VTimestamp; SMALL_VECTOR]>); impl VClock { /// Create a new vector-clock containing all zeros except /// for a value at the given index - pub fn new_with_index(index: VectorIdx, timestamp: VTimestamp) -> VClock { + pub(super) fn new_with_index(index: VectorIdx, timestamp: VTimestamp) -> VClock { let len = index.index() + 1; let mut vec = smallvec::smallvec![VTimestamp::ZERO; len]; vec[index.index()] = timestamp; @@ -147,12 +145,18 @@ impl VClock { /// Load the internal timestamp slice in the vector clock #[inline] - pub fn as_slice(&self) -> &[VTimestamp] { + pub(super) fn as_slice(&self) -> &[VTimestamp] { + debug_assert!(!self.0.last().is_some_and(|t| t.time() == 0)); self.0.as_slice() } + #[inline] + pub(super) fn index_mut(&mut self, index: VectorIdx) -> &mut VTimestamp { + self.0.as_mut_slice().get_mut(index.to_u32() as usize).unwrap() + } + /// Get a mutable slice to the internal vector with minimum `min_len` - /// elements, to preserve invariants this vector must modify + /// elements. To preserve invariants, the caller must modify /// the `min_len`-1 nth element to a non-zero value #[inline] fn get_mut_with_min_len(&mut self, min_len: usize) -> &mut [VTimestamp] { @@ -166,7 +170,7 @@ impl VClock { /// Increment the vector clock at a known index /// this will panic if the vector index overflows #[inline] - pub fn increment_index(&mut self, idx: VectorIdx, current_span: Span) { + pub(super) fn increment_index(&mut self, idx: VectorIdx, current_span: Span) { let idx = idx.index(); let mut_slice = self.get_mut_with_min_len(idx + 1); let idx_ref = &mut mut_slice[idx]; @@ -190,28 +194,36 @@ impl VClock { } } - /// Set the element at the current index of the vector - pub fn set_at_index(&mut self, other: &Self, idx: VectorIdx) { + /// Set the element at the current index of the vector. May only increase elements. + pub(super) fn set_at_index(&mut self, other: &Self, idx: VectorIdx) { + let new_timestamp = other[idx]; + // Setting to 0 is different, since the last element cannot be 0. + if new_timestamp.time() == 0 { + if idx.index() >= self.0.len() { + // This index does not even exist yet in our clock. Just do nothing. + return; + } + // This changes an existing element. Since it can only increase, that + // can never make the last element 0. + } + let mut_slice = self.get_mut_with_min_len(idx.index() + 1); + let mut_timestamp = &mut mut_slice[idx.index()]; - let prev_span = mut_slice[idx.index()].span; + let prev_span = mut_timestamp.span; - mut_slice[idx.index()] = other[idx]; + assert!(*mut_timestamp <= new_timestamp, "set_at_index: may only increase the timestamp"); + *mut_timestamp = new_timestamp; - let span = &mut mut_slice[idx.index()].span; + let span = &mut mut_timestamp.span; *span = span.substitute_dummy(prev_span); } /// Set the vector to the all-zero vector #[inline] - pub fn set_zero_vector(&mut self) { + pub(super) fn set_zero_vector(&mut self) { self.0.clear(); } - - /// Return if this vector is the all-zero vector - pub fn is_zero_vector(&self) -> bool { - self.0.is_empty() - } } impl Clone for VClock { @@ -407,13 +419,6 @@ impl Index for VClock { } } -impl IndexMut for VClock { - #[inline] - fn index_mut(&mut self, index: VectorIdx) -> &mut VTimestamp { - self.0.as_mut_slice().get_mut(index.to_u32() as usize).unwrap() - } -} - /// Test vector clock ordering operations /// data-race detection is tested in the external /// test suite @@ -553,4 +558,15 @@ mod tests { "Invalid alt (>=):\n l: {l:?}\n r: {r:?}" ); } + + #[test] + fn set_index_to_0() { + let mut clock1 = from_slice(&[0, 1, 2, 3]); + let clock2 = from_slice(&[0, 2, 3, 4, 0, 5]); + // Naively, this would extend clock1 with a new index and set it to 0, making + // the last index 0. Make sure that does not happen. + clock1.set_at_index(&clock2, VectorIdx(4)); + // This must not have made the last element 0. + assert!(clock1.0.last().unwrap().time() != 0); + } } diff --git a/src/concurrency/weak_memory.rs b/src/concurrency/weak_memory.rs index 9ebb64afd3..574962c48d 100644 --- a/src/concurrency/weak_memory.rs +++ b/src/concurrency/weak_memory.rs @@ -270,7 +270,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer { ) { let store_elem = self.buffer.back(); if let Some(store_elem) = store_elem { - let (index, clocks) = global.current_thread_state(thread_mgr); + let (index, clocks) = global.active_thread_state(thread_mgr); store_elem.load_impl(index, &clocks, is_seqcst); } } @@ -289,7 +289,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer { let (store_elem, recency) = { // The `clocks` we got here must be dropped before calling validate_atomic_load // as the race detector will update it - let (.., clocks) = global.current_thread_state(thread_mgr); + let (.., clocks) = global.active_thread_state(thread_mgr); // Load from a valid entry in the store buffer self.fetch_store(is_seqcst, &clocks, &mut *rng) }; @@ -300,7 +300,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer { // requires access to ThreadClockSet.clock, which is updated by the race detector validate()?; - let (index, clocks) = global.current_thread_state(thread_mgr); + let (index, clocks) = global.active_thread_state(thread_mgr); let loaded = store_elem.load_impl(index, &clocks, is_seqcst); Ok((loaded, recency)) } @@ -312,7 +312,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer { thread_mgr: &ThreadManager<'_, '_>, is_seqcst: bool, ) -> InterpResult<'tcx> { - let (index, clocks) = global.current_thread_state(thread_mgr); + let (index, clocks) = global.active_thread_state(thread_mgr); self.store_impl(val, index, &clocks.clock, is_seqcst); Ok(()) @@ -520,7 +520,9 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: validate, )?; if global.track_outdated_loads && recency == LoadRecency::Outdated { - this.emit_diagnostic(NonHaltingDiagnostic::WeakMemoryOutdatedLoad); + this.emit_diagnostic(NonHaltingDiagnostic::WeakMemoryOutdatedLoad { + ptr: place.ptr(), + }); } return Ok(loaded); diff --git a/src/diagnostics.rs b/src/diagnostics.rs index ca04efdc97..189c4a20bf 100644 --- a/src/diagnostics.rs +++ b/src/diagnostics.rs @@ -48,6 +48,7 @@ pub enum TerminationInfo { extra: Option<&'static str>, retag_explain: bool, }, + UnsupportedForeignItem(String), } pub struct RacingOp { @@ -85,6 +86,7 @@ impl fmt::Display for TerminationInfo { op2.action, op2.thread_info ), + UnsupportedForeignItem(msg) => write!(f, "{msg}"), } } } @@ -115,7 +117,7 @@ pub enum NonHaltingDiagnostic { /// This `Item` was popped from the borrow stack. The string explains the reason. PoppedPointerTag(Item, String), CreatedCallId(CallId), - CreatedAlloc(AllocId, Size, Align, MemoryKind), + CreatedAlloc(AllocId, Size, Align, MemoryKind), FreedAlloc(AllocId), AccessedAlloc(AllocId, AccessKind), RejectedIsolatedOp(String), @@ -125,7 +127,9 @@ pub enum NonHaltingDiagnostic { Int2Ptr { details: bool, }, - WeakMemoryOutdatedLoad, + WeakMemoryOutdatedLoad { + ptr: Pointer>, + }, } /// Level of Miri specific diagnostics @@ -212,7 +216,7 @@ pub fn report_error<'tcx, 'mir>( let title = match info { Exit { code, leak_check } => return Some((*code, *leak_check)), Abort(_) => Some("abnormal termination"), - UnsupportedInIsolation(_) | Int2PtrWithStrictProvenance => + UnsupportedInIsolation(_) | Int2PtrWithStrictProvenance | UnsupportedForeignItem(_) => Some("unsupported operation"), StackedBorrowsUb { .. } | TreeBorrowsUb { .. } | DataRace { .. } => Some("Undefined Behavior"), @@ -226,6 +230,12 @@ pub fn report_error<'tcx, 'mir>( (None, format!("pass the flag `-Zmiri-disable-isolation` to disable isolation;")), (None, format!("or pass `-Zmiri-isolation-error=warn` to configure Miri to return an error code from isolated operations (if supported for that operation) and continue with a warning")), ], + UnsupportedForeignItem(_) => { + vec![ + (None, format!("if this is a basic API commonly used on this target, please report an issue with Miri")), + (None, format!("however, note that Miri does not aim to support every FFI function out there; for instance, we will not support APIs for things such as GUIs, scripting languages, or databases")), + ] + } StackedBorrowsUb { help, history, .. } => { msg.extend(help.clone()); let mut helps = vec![ @@ -291,7 +301,7 @@ pub fn report_error<'tcx, 'mir>( ValidationErrorKind::PointerAsInt { .. } | ValidationErrorKind::PartialPointer ) => { - ecx.handle_ice(); // print interpreter backtrace + ecx.handle_ice(); // print interpreter backtrace (this is outside the eval `catch_unwind`) bug!( "This validation error should be impossible in Miri: {}", format_interp_error(ecx.tcx.dcx(), e) @@ -308,7 +318,7 @@ pub fn report_error<'tcx, 'mir>( InvalidProgramInfo::AlreadyReported(_) | InvalidProgramInfo::Layout(..), ) => "post-monomorphization error", _ => { - ecx.handle_ice(); // print interpreter backtrace + ecx.handle_ice(); // print interpreter backtrace (this is outside the eval `catch_unwind`) bug!( "This error should be impossible in Miri: {}", format_interp_error(ecx.tcx.dcx(), e) @@ -318,7 +328,9 @@ pub fn report_error<'tcx, 'mir>( #[rustfmt::skip] let helps = match e.kind() { Unsupported(_) => - vec![(None, format!("this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support"))], + vec![ + (None, format!("this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support")), + ], UndefinedBehavior(AlignmentCheckFailed { .. }) if ecx.machine.check_alignment == AlignmentCheck::Symbolic => @@ -342,7 +354,7 @@ pub fn report_error<'tcx, 'mir>( } AbiMismatchArgument { .. } | AbiMismatchReturn { .. } => { helps.push((None, format!("this means these two types are not *guaranteed* to be ABI-compatible across all targets"))); - helps.push((None, format!("if you think this code should be accepted anyway, please report an issue"))); + helps.push((None, format!("if you think this code should be accepted anyway, please report an issue with Miri"))); } _ => {}, } @@ -361,9 +373,12 @@ pub fn report_error<'tcx, 'mir>( }; let stacktrace = ecx.generate_stacktrace(); - let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine); + let (stacktrace, mut any_pruned) = prune_stacktrace(stacktrace, &ecx.machine); + + let mut show_all_threads = false; - // We want to dump the allocation if this is `InvalidUninitBytes`. Since `format_error` consumes `e`, we compute the outut early. + // We want to dump the allocation if this is `InvalidUninitBytes`. + // Since `format_interp_error` consumes `e`, we compute the outut early. let mut extra = String::new(); match e.kind() { UndefinedBehavior(InvalidUninitBytes(Some((alloc_id, access)))) => { @@ -375,6 +390,15 @@ pub fn report_error<'tcx, 'mir>( .unwrap(); writeln!(extra, "{:?}", ecx.dump_alloc(*alloc_id)).unwrap(); } + MachineStop(info) => { + let info = info.downcast_ref::().expect("invalid MachineStop payload"); + match info { + TerminationInfo::Deadlock => { + show_all_threads = true; + } + _ => {} + } + } _ => {} } @@ -387,18 +411,39 @@ pub fn report_error<'tcx, 'mir>( vec![], helps, &stacktrace, + Some(ecx.get_active_thread()), &ecx.machine, ); + eprint!("{extra}"); // newlines are already in the string + + if show_all_threads { + for (thread, stack) in ecx.machine.threads.all_stacks() { + if thread != ecx.get_active_thread() { + let stacktrace = Frame::generate_stacktrace_from_stack(stack); + let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine); + any_pruned |= was_pruned; + report_msg( + DiagLevel::Error, + format!("deadlock: the evaluated program deadlocked"), + vec![format!("the evaluated program deadlocked")], + vec![], + vec![], + &stacktrace, + Some(thread), + &ecx.machine, + ) + } + } + } + // Include a note like `std` does when we omit frames from a backtrace - if was_pruned { + if any_pruned { ecx.tcx.dcx().note( "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace", ); } - eprint!("{extra}"); // newlines are already in the string - // Debug-dump all locals. for (i, frame) in ecx.active_thread_stack().iter().enumerate() { trace!("-------------------"); @@ -414,11 +459,7 @@ pub fn report_error<'tcx, 'mir>( pub fn report_leaks<'mir, 'tcx>( ecx: &InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, - leaks: Vec<( - AllocId, - MemoryKind, - Allocation, MiriAllocBytes>, - )>, + leaks: Vec<(AllocId, MemoryKind, Allocation, MiriAllocBytes>)>, ) { let mut any_pruned = false; for (id, kind, mut alloc) in leaks { @@ -439,6 +480,7 @@ pub fn report_leaks<'mir, 'tcx>( vec![], vec![], &backtrace, + None, // we don't know the thread this is from &ecx.machine, ); } @@ -461,6 +503,7 @@ pub fn report_msg<'tcx>( notes: Vec<(Option, String)>, helps: Vec<(Option, String)>, stacktrace: &[FrameInfo<'tcx>], + thread: Option, machine: &MiriMachine<'_, 'tcx>, ) { let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span); @@ -510,12 +553,13 @@ pub fn report_msg<'tcx>( if extra_span { write!(backtrace_title, " (of the first span)").unwrap(); } - let thread_name = - machine.threads.get_thread_display_name(machine.threads.get_active_thread_id()); - if thread_name != "main" { - // Only print thread name if it is not `main`. - write!(backtrace_title, " on thread `{thread_name}`").unwrap(); - }; + if let Some(thread) = thread { + let thread_name = machine.threads.get_thread_display_name(thread); + if thread_name != "main" { + // Only print thread name if it is not `main`. + write!(backtrace_title, " on thread `{thread_name}`").unwrap(); + }; + } write!(backtrace_title, ":").unwrap(); err.note(backtrace_title); for (idx, frame_info) in stacktrace.iter().enumerate() { @@ -551,7 +595,8 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { | AccessedAlloc(..) | FreedAlloc(..) | ProgressReport { .. } - | WeakMemoryOutdatedLoad => ("tracking was triggered".to_string(), DiagLevel::Note), + | WeakMemoryOutdatedLoad { .. } => + ("tracking was triggered".to_string(), DiagLevel::Note), }; let msg = match &e { @@ -578,8 +623,8 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { ProgressReport { .. } => format!("progress report: current operation being executed is here"), Int2Ptr { .. } => format!("integer-to-pointer cast"), - WeakMemoryOutdatedLoad => - format!("weak memory emulation: outdated value returned from load"), + WeakMemoryOutdatedLoad { ptr } => + format!("weak memory emulation: outdated value returned from load at {ptr}"), }; let notes = match &e { @@ -632,7 +677,16 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { _ => vec![], }; - report_msg(diag_level, title, vec![msg], notes, helps, &stacktrace, self); + report_msg( + diag_level, + title, + vec![msg], + notes, + helps, + &stacktrace, + Some(self.threads.get_active_thread_id()), + self, + ); } } @@ -658,6 +712,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { vec![], vec![], &stacktrace, + Some(this.get_active_thread()), &this.machine, ); } diff --git a/src/eval.rs b/src/eval.rs index f7edbdb9c5..78c092faf9 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -9,7 +9,7 @@ use std::thread; use crate::concurrency::thread::TlsAllocAction; use crate::diagnostics::report_leaks; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def::Namespace; use rustc_hir::def_id::DefId; use rustc_middle::ty::{ @@ -100,6 +100,8 @@ pub struct MiriConfig { pub ignore_leaks: bool, /// Environment variables that should always be forwarded from the host. pub forwarded_env_vars: Vec, + /// Additional environment variables that should be set in the interpreted program. + pub set_env_vars: FxHashMap, /// Command-line arguments passed to the interpreted program. pub args: Vec, /// The seed to use when non-determinism or randomness are required (e.g. ptr-to-int cast, `getrandom()`). @@ -140,8 +142,8 @@ pub struct MiriConfig { /// Whether Stacked Borrows and Tree Borrows retagging should recurse into fields of datatypes. pub retag_fields: RetagFields, /// The location of a shared object file to load when calling external functions - /// FIXME! consider allowing users to specify paths to multiple SO files, or to a directory - pub external_so_file: Option, + /// FIXME! consider allowing users to specify paths to multiple files, or to a directory + pub native_lib: Option, /// Run a garbage collector for BorTags every N basic blocks. pub gc_interval: u32, /// The number of CPUs to be reported by miri. @@ -150,6 +152,10 @@ pub struct MiriConfig { pub page_size: Option, /// Whether to collect a backtrace when each allocation is created, just in case it leaks. pub collect_leak_backtraces: bool, + /// Probability for address reuse. + pub address_reuse_rate: f64, + /// Probability for address reuse across threads. + pub address_reuse_cross_thread_rate: f64, } impl Default for MiriConfig { @@ -163,6 +169,7 @@ impl Default for MiriConfig { isolated_op: IsolatedOp::Reject(RejectOpWith::Abort), ignore_leaks: false, forwarded_env_vars: vec![], + set_env_vars: FxHashMap::default(), args: vec![], seed: None, tracked_pointer_tags: FxHashSet::default(), @@ -181,29 +188,31 @@ impl Default for MiriConfig { preemption_rate: 0.01, // 1% report_progress: None, retag_fields: RetagFields::Yes, - external_so_file: None, + native_lib: None, gc_interval: 10_000, num_cpus: 1, page_size: None, collect_leak_backtraces: true, + address_reuse_rate: 0.5, + address_reuse_cross_thread_rate: 0.1, } } } /// The state of the main thread. Implementation detail of `on_main_stack_empty`. #[derive(Default, Debug)] -enum MainThreadState { +enum MainThreadState<'tcx> { #[default] Running, - TlsDtors(tls::TlsDtorsState), + TlsDtors(tls::TlsDtorsState<'tcx>), Yield { remaining: u32, }, Done, } -impl MainThreadState { - fn on_main_stack_empty<'tcx>( +impl<'tcx> MainThreadState<'tcx> { + fn on_main_stack_empty( &mut self, this: &mut MiriInterpCx<'_, 'tcx>, ) -> InterpResult<'tcx, Poll<()>> { @@ -377,10 +386,9 @@ pub fn create_ecx<'mir, 'tcx: 'mir>( let main_ptr = ecx.fn_ptr(FnVal::Instance(entry_instance)); - // Inlining of `DEFAULT` from - // https://github.com/rust-lang/rust/blob/master/compiler/rustc_session/src/config/sigpipe.rs. // Always using DEFAULT is okay since we don't support signals in Miri anyway. - let sigpipe = 2; + // (This means we are effectively ignoring `-Zon-broken-pipe`.) + let sigpipe = rustc_session::config::sigpipe::DEFAULT; ecx.call_function( start_instance, diff --git a/src/helpers.rs b/src/helpers.rs index c12fe0e086..4050ae3c4b 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -9,16 +9,21 @@ use rand::RngCore; use rustc_apfloat::ieee::{Double, Single}; use rustc_apfloat::Float; -use rustc_hir::def::{DefKind, Namespace}; -use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX}; +use rustc_hir::{ + def::{DefKind, Namespace}, + def_id::{CrateNum, DefId, CRATE_DEF_INDEX, LOCAL_CRATE}, +}; use rustc_index::IndexVec; +use rustc_middle::middle::dependency_format::Linkage; +use rustc_middle::middle::exported_symbols::ExportedSymbol; use rustc_middle::mir; use rustc_middle::ty::{ self, layout::{LayoutOf, TyAndLayout}, FloatTy, IntTy, Ty, TyCtxt, UintTy, }; -use rustc_span::{def_id::CrateNum, sym, Span, Symbol}; +use rustc_session::config::CrateType; +use rustc_span::{sym, Span, Symbol}; use rustc_target::abi::{Align, FieldIdx, FieldsShape, Size, Variants}; use rustc_target::spec::abi::Abi; @@ -78,6 +83,17 @@ const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { ("EAGAIN", WouldBlock), ] }; +// This mapping should match `decode_error_kind` in +// . +const WINDOWS_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { + use std::io::ErrorKind::*; + // FIXME: this is still incomplete. + &[ + ("ERROR_ACCESS_DENIED", PermissionDenied), + ("ERROR_FILE_NOT_FOUND", NotFound), + ("ERROR_INVALID_PARAMETER", InvalidInput), + ] +}; /// Gets an instance for a path. /// @@ -105,24 +121,73 @@ fn try_resolve_did(tcx: TyCtxt<'_>, path: &[&str], namespace: Option) (path, None) }; - // First find the crate. - let krate = - tcx.crates(()).iter().find(|&&krate| tcx.crate_name(krate).as_str() == crate_name)?; - let mut cur_item = DefId { krate: *krate, index: CRATE_DEF_INDEX }; - // Then go over the modules. - for &segment in modules { - cur_item = find_children(tcx, cur_item, segment) - .find(|item| tcx.def_kind(item) == DefKind::Mod)?; - } - // Finally, look up the desired item in this module, if any. - match item { - Some((item_name, namespace)) => - Some( - find_children(tcx, cur_item, item_name) - .find(|item| tcx.def_kind(item).ns() == Some(namespace))?, - ), - None => Some(cur_item), + // There may be more than one crate with this name. We try them all. + // (This is particularly relevant when running `std` tests as then there are two `std` crates: + // the one in the sysroot and the one locally built by `cargo test`.) + // FIXME: can we prefer the one from the sysroot? + 'crates: for krate in + tcx.crates(()).iter().filter(|&&krate| tcx.crate_name(krate).as_str() == crate_name) + { + let mut cur_item = DefId { krate: *krate, index: CRATE_DEF_INDEX }; + // Go over the modules. + for &segment in modules { + let Some(next_item) = find_children(tcx, cur_item, segment) + .find(|item| tcx.def_kind(item) == DefKind::Mod) + else { + continue 'crates; + }; + cur_item = next_item; + } + // Finally, look up the desired item in this module, if any. + match item { + Some((item_name, namespace)) => { + let Some(item) = find_children(tcx, cur_item, item_name) + .find(|item| tcx.def_kind(item).ns() == Some(namespace)) + else { + continue 'crates; + }; + return Some(item); + } + None => { + // Just return the module. + return Some(cur_item); + } + } + } + // Item not found in any of the crates with the right name. + None +} + +/// Call `f` for each exported symbol. +pub fn iter_exported_symbols<'tcx>( + tcx: TyCtxt<'tcx>, + mut f: impl FnMut(CrateNum, DefId) -> InterpResult<'tcx>, +) -> InterpResult<'tcx> { + // `dependency_formats` includes all the transitive informations needed to link a crate, + // which is what we need here since we need to dig out `exported_symbols` from all transitive + // dependencies. + let dependency_formats = tcx.dependency_formats(()); + let dependency_format = dependency_formats + .iter() + .find(|(crate_type, _)| *crate_type == CrateType::Executable) + .expect("interpreting a non-executable crate"); + for cnum in iter::once(LOCAL_CRATE).chain(dependency_format.1.iter().enumerate().filter_map( + |(num, &linkage)| { + // We add 1 to the number because that's what rustc also does everywhere it + // calls `CrateNum::new`... + #[allow(clippy::arithmetic_side_effects)] + (linkage != Linkage::NotLinked).then_some(CrateNum::new(num + 1)) + }, + )) { + // We can ignore `_export_info` here: we are a Rust crate, and everything is exported + // from a Rust crate. + for &(symbol, _export_info) in tcx.exported_symbols(cnum) { + if let ExportedSymbol::NonGeneric(def_id) = symbol { + f(cnum, def_id)?; + } + } } + Ok(()) } /// Convert a softfloat type to its corresponding hostfloat type. @@ -190,14 +255,19 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } /// Evaluates the scalar at the specified path. - fn eval_path_scalar(&self, path: &[&str]) -> Scalar { + fn eval_path(&self, path: &[&str]) -> OpTy<'tcx, Provenance> { let this = self.eval_context_ref(); let instance = this.resolve_path(path, Namespace::ValueNS); // We don't give a span -- this isn't actually used directly by the program anyway. let const_val = this.eval_global(instance).unwrap_or_else(|err| { panic!("failed to evaluate required Rust item: {path:?}\n{err:?}") }); - this.read_scalar(&const_val) + const_val.into() + } + fn eval_path_scalar(&self, path: &[&str]) -> Scalar { + let this = self.eval_context_ref(); + let val = this.eval_path(path); + this.read_scalar(&val) .unwrap_or_else(|err| panic!("failed to read required Rust item: {path:?}\n{err:?}")) } @@ -681,34 +751,23 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { /// This function tries to produce the most similar OS error from the `std::io::ErrorKind` /// as a platform-specific errnum. - fn io_error_to_errnum( - &self, - err_kind: std::io::ErrorKind, - ) -> InterpResult<'tcx, Scalar> { + fn io_error_to_errnum(&self, err: std::io::Error) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_ref(); let target = &this.tcx.sess.target; if target.families.iter().any(|f| f == "unix") { for &(name, kind) in UNIX_IO_ERROR_TABLE { - if err_kind == kind { + if err.kind() == kind { return Ok(this.eval_libc(name)); } } - throw_unsup_format!("io error {:?} cannot be translated into a raw os error", err_kind) + throw_unsup_format!("unsupported io error: {err}") } else if target.families.iter().any(|f| f == "windows") { - // FIXME: we have to finish implementing the Windows equivalent of this. - use std::io::ErrorKind::*; - Ok(this.eval_windows( - "c", - match err_kind { - NotFound => "ERROR_FILE_NOT_FOUND", - PermissionDenied => "ERROR_ACCESS_DENIED", - _ => - throw_unsup_format!( - "io error {:?} cannot be translated into a raw os error", - err_kind - ), - }, - )) + for &(name, kind) in WINDOWS_IO_ERROR_TABLE { + if err.kind() == kind { + return Ok(this.eval_windows("c", name)); + } + } + throw_unsup_format!("unsupported io error: {err}"); } else { throw_unsup_format!( "converting io::Error into errnum is unsupported for OS {}", @@ -732,8 +791,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { return Ok(Some(kind)); } } - // Our table is as complete as the mapping in std, so we are okay with saying "that's a - // strange one" here. + return Ok(None); + } else if target.families.iter().any(|f| f == "windows") { + let errnum = errnum.to_u32()?; + for &(name, kind) in WINDOWS_IO_ERROR_TABLE { + if errnum == this.eval_windows("c", name).to_u32()? { + return Ok(Some(kind)); + } + } return Ok(None); } else { throw_unsup_format!( @@ -744,8 +809,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } /// Sets the last OS error using a `std::io::ErrorKind`. - fn set_last_error_from_io_error(&mut self, err_kind: std::io::ErrorKind) -> InterpResult<'tcx> { - self.set_last_error(self.io_error_to_errnum(err_kind)?) + fn set_last_error_from_io_error(&mut self, err: std::io::Error) -> InterpResult<'tcx> { + self.set_last_error(self.io_error_to_errnum(err)?) } /// Helper function that consumes an `std::io::Result` and returns an @@ -761,7 +826,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { match result { Ok(ok) => Ok(ok), Err(e) => { - self.eval_context_mut().set_last_error_from_io_error(e.kind())?; + self.eval_context_mut().set_last_error_from_io_error(e)?; Ok((-1).into()) } } @@ -849,10 +914,25 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { }) } + /// Read bytes from a byte slice. + fn read_byte_slice<'a>( + &'a self, + slice: &ImmTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, &'a [u8]> + where + 'mir: 'a, + { + let this = self.eval_context_ref(); + let (ptr, len) = slice.to_scalar_pair(); + let ptr = ptr.to_pointer(this)?; + let len = len.to_target_usize(this)?; + let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?; + Ok(bytes) + } + /// Read a sequence of bytes until the first null terminator. fn read_c_str<'a>(&'a self, ptr: Pointer>) -> InterpResult<'tcx, &'a [u8]> where - 'tcx: 'a, 'mir: 'a, { let this = self.eval_context_ref(); @@ -899,29 +979,46 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Ok((true, string_length)) } - /// Read a sequence of u16 until the first null terminator. - fn read_wide_str(&self, mut ptr: Pointer>) -> InterpResult<'tcx, Vec> { + /// Helper function to read a sequence of unsigned integers of the given size and alignment + /// until the first null terminator. + fn read_c_str_with_char_size( + &self, + mut ptr: Pointer>, + size: Size, + align: Align, + ) -> InterpResult<'tcx, Vec> + where + T: TryFrom, + >::Error: std::fmt::Debug, + { + assert_ne!(size, Size::ZERO); + let this = self.eval_context_ref(); - let size2 = Size::from_bytes(2); - this.check_ptr_align(ptr, Align::from_bytes(2).unwrap())?; + + this.check_ptr_align(ptr, align)?; let mut wchars = Vec::new(); loop { // FIXME: We are re-getting the allocation each time around the loop. // Would be nice if we could somehow "extend" an existing AllocRange. - let alloc = this.get_ptr_alloc(ptr, size2)?.unwrap(); // not a ZST, so we will get a result - let wchar = alloc.read_integer(alloc_range(Size::ZERO, size2))?.to_u16()?; - if wchar == 0 { + let alloc = this.get_ptr_alloc(ptr, size)?.unwrap(); // not a ZST, so we will get a result + let wchar_int = alloc.read_integer(alloc_range(Size::ZERO, size))?.to_bits(size)?; + if wchar_int == 0 { break; } else { - wchars.push(wchar); - ptr = ptr.offset(size2, this)?; + wchars.push(wchar_int.try_into().unwrap()); + ptr = ptr.offset(size, this)?; } } Ok(wchars) } + /// Read a sequence of u16 until the first null terminator. + fn read_wide_str(&self, ptr: Pointer>) -> InterpResult<'tcx, Vec> { + self.read_c_str_with_char_size(ptr, Size::from_bytes(2), Align::from_bytes(2).unwrap()) + } + /// Helper function to write a sequence of u16 with an added 0x0000-terminator, which is what /// the Windows APIs usually handle. This function returns `Ok((false, length))` without trying /// to write if `size` is not large enough to fit the contents of `os_string` plus a null @@ -954,6 +1051,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Ok((true, string_length)) } + /// Read a sequence of wchar_t until the first null terminator. + /// Always returns a `Vec` no matter the size of `wchar_t`. + fn read_wchar_t_str(&self, ptr: Pointer>) -> InterpResult<'tcx, Vec> { + let this = self.eval_context_ref(); + let wchar_t = this.libc_ty_layout("wchar_t"); + self.read_c_str_with_char_size(ptr, wchar_t.size, wchar_t.align.abi) + } + /// Check that the ABI is what we expect. fn check_abi<'a>(&self, abi: Abi, exp_abi: Abi) -> InterpResult<'a, ()> { if abi != exp_abi { @@ -968,10 +1073,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn frame_in_std(&self) -> bool { let this = self.eval_context_ref(); - let Some(start_fn) = this.tcx.lang_items().start_fn() else { - // no_std situations - return false; - }; let frame = this.frame(); // Make an attempt to get at the instance of the function this is inlined from. let instance: Option<_> = try { @@ -982,29 +1083,29 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { }; // Fall back to the instance of the function itself. let instance = instance.unwrap_or(frame.instance); - // Now check if this is in the same crate as start_fn. - // As a special exception we also allow unit tests from - // to call these - // shims. + // Now check the crate it is in. We could try to be clever here and e.g. check if this is + // the same crate as `start_fn`, but that would not work for running std tests in Miri, so + // we'd need some more hacks anyway. So we just check the name of the crate. If someone + // calls their crate `std` then we'll just let them keep the pieces. let frame_crate = this.tcx.def_path(instance.def_id()).krate; - frame_crate == this.tcx.def_path(start_fn).krate - || this.tcx.crate_name(frame_crate).as_str() == "std_miri_test" + let crate_name = this.tcx.crate_name(frame_crate); + let crate_name = crate_name.as_str(); + // On miri-test-libstd, the name of the crate is different. + crate_name == "std" || crate_name == "std_miri_test" } - /// Handler that should be called when unsupported functionality is encountered. + /// Handler that should be called when an unsupported foreign item is encountered. /// This function will either panic within the context of the emulated application /// or return an error in the Miri process context - /// - /// Return value of `Ok(bool)` indicates whether execution should continue. - fn handle_unsupported>(&mut self, error_msg: S) -> InterpResult<'tcx, ()> { + fn handle_unsupported_foreign_item(&mut self, error_msg: String) -> InterpResult<'tcx, ()> { let this = self.eval_context_mut(); if this.machine.panic_on_unsupported { // message is slightly different here to make automated analysis easier - let error_msg = format!("unsupported Miri functionality: {}", error_msg.as_ref()); + let error_msg = format!("unsupported Miri functionality: {error_msg}"); this.start_panic(error_msg.as_ref(), mir::UnwindAction::Continue)?; Ok(()) } else { - throw_unsup_format!("{}", error_msg.as_ref()); + throw_machine_stop!(TerminationInfo::UnsupportedForeignItem(error_msg)); } } @@ -1165,6 +1266,37 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } Ok(()) } + + /// Lookup an array of immediates stored as a linker section of name `name`. + fn lookup_link_section( + &mut self, + name: &str, + ) -> InterpResult<'tcx, Vec>> { + let this = self.eval_context_mut(); + let tcx = this.tcx.tcx; + + let mut array = vec![]; + + iter_exported_symbols(tcx, |_cnum, def_id| { + let attrs = tcx.codegen_fn_attrs(def_id); + let Some(link_section) = attrs.link_section else { + return Ok(()); + }; + if link_section.as_str() == name { + let instance = ty::Instance::mono(tcx, def_id); + let const_val = this.eval_global(instance).unwrap_or_else(|err| { + panic!( + "failed to evaluate static in required link_section: {def_id:?}\n{err:?}" + ) + }); + let val = this.read_immediate(&const_val)?; + array.push(val); + } + Ok(()) + })?; + + Ok(array) + } } impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { @@ -1173,9 +1305,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { /// This function is backed by a cache, and can be assumed to be very fast. /// It will work even when the stack is empty. pub fn current_span(&self) -> Span { - self.top_user_relevant_frame() - .map(|frame_idx| self.stack()[frame_idx].current_span()) - .unwrap_or(rustc_span::DUMMY_SP) + self.threads.active_thread_ref().current_span() } /// Returns the span of the *caller* of the current operation, again @@ -1187,7 +1317,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { // We need to go down at least to the caller (len - 2), or however // far we have to go to find a frame in a local crate which is also not #[track_caller]. let frame_idx = self.top_user_relevant_frame().unwrap(); - let frame_idx = cmp::min(frame_idx, self.stack().len().checked_sub(2).unwrap()); + let frame_idx = cmp::min(frame_idx, self.stack().len().saturating_sub(2)); self.stack()[frame_idx].current_span() } @@ -1261,3 +1391,18 @@ pub(crate) fn simd_element_to_bool(elem: ImmTy<'_, Provenance>) -> InterpResult< _ => throw_ub_format!("each element of a SIMD mask must be all-0-bits or all-1-bits"), }) } + +/// Check whether an operation that writes to a target buffer was successful. +/// Accordingly select return value. +/// Local helper function to be used in Windows shims. +pub(crate) fn windows_check_buffer_size((success, len): (bool, u64)) -> u32 { + if success { + // If the function succeeds, the return value is the number of characters stored in the target buffer, + // not including the terminating null character. + u32::try_from(len.checked_sub(1).unwrap()).unwrap() + } else { + // If the target buffer was not large enough to hold the data, the return value is the buffer size, in characters, + // required to hold the string and its terminating null character. + u32::try_from(len).unwrap() + } +} diff --git a/src/shims/intrinsics/atomic.rs b/src/intrinsics/atomic.rs similarity index 87% rename from src/shims/intrinsics/atomic.rs rename to src/intrinsics/atomic.rs index 865886a7fc..0c212c45db 100644 --- a/src/shims/intrinsics/atomic.rs +++ b/src/intrinsics/atomic.rs @@ -14,108 +14,109 @@ pub enum AtomicOp { impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { /// Calls the atomic intrinsic `intrinsic`; the `atomic_` prefix has already been removed. + /// Returns `Ok(true)` if the intrinsic was handled. fn emulate_atomic_intrinsic( &mut self, intrinsic_name: &str, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx> { + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); let intrinsic_structure: Vec<_> = intrinsic_name.split('_').collect(); - fn read_ord<'tcx>(ord: &str) -> InterpResult<'tcx, AtomicReadOrd> { - Ok(match ord { + fn read_ord(ord: &str) -> AtomicReadOrd { + match ord { "seqcst" => AtomicReadOrd::SeqCst, "acquire" => AtomicReadOrd::Acquire, "relaxed" => AtomicReadOrd::Relaxed, - _ => throw_unsup_format!("unsupported read ordering `{ord}`"), - }) + _ => panic!("invalid read ordering `{ord}`"), + } } - fn write_ord<'tcx>(ord: &str) -> InterpResult<'tcx, AtomicWriteOrd> { - Ok(match ord { + fn write_ord(ord: &str) -> AtomicWriteOrd { + match ord { "seqcst" => AtomicWriteOrd::SeqCst, "release" => AtomicWriteOrd::Release, "relaxed" => AtomicWriteOrd::Relaxed, - _ => throw_unsup_format!("unsupported write ordering `{ord}`"), - }) + _ => panic!("invalid write ordering `{ord}`"), + } } - fn rw_ord<'tcx>(ord: &str) -> InterpResult<'tcx, AtomicRwOrd> { - Ok(match ord { + fn rw_ord(ord: &str) -> AtomicRwOrd { + match ord { "seqcst" => AtomicRwOrd::SeqCst, "acqrel" => AtomicRwOrd::AcqRel, "acquire" => AtomicRwOrd::Acquire, "release" => AtomicRwOrd::Release, "relaxed" => AtomicRwOrd::Relaxed, - _ => throw_unsup_format!("unsupported read-write ordering `{ord}`"), - }) + _ => panic!("invalid read-write ordering `{ord}`"), + } } - fn fence_ord<'tcx>(ord: &str) -> InterpResult<'tcx, AtomicFenceOrd> { - Ok(match ord { + fn fence_ord(ord: &str) -> AtomicFenceOrd { + match ord { "seqcst" => AtomicFenceOrd::SeqCst, "acqrel" => AtomicFenceOrd::AcqRel, "acquire" => AtomicFenceOrd::Acquire, "release" => AtomicFenceOrd::Release, - _ => throw_unsup_format!("unsupported fence ordering `{ord}`"), - }) + _ => panic!("invalid fence ordering `{ord}`"), + } } match &*intrinsic_structure { - ["load", ord] => this.atomic_load(args, dest, read_ord(ord)?)?, - ["store", ord] => this.atomic_store(args, write_ord(ord)?)?, + ["load", ord] => this.atomic_load(args, dest, read_ord(ord))?, + ["store", ord] => this.atomic_store(args, write_ord(ord))?, - ["fence", ord] => this.atomic_fence_intrinsic(args, fence_ord(ord)?)?, - ["singlethreadfence", ord] => this.compiler_fence_intrinsic(args, fence_ord(ord)?)?, + ["fence", ord] => this.atomic_fence_intrinsic(args, fence_ord(ord))?, + ["singlethreadfence", ord] => this.compiler_fence_intrinsic(args, fence_ord(ord))?, - ["xchg", ord] => this.atomic_exchange(args, dest, rw_ord(ord)?)?, + ["xchg", ord] => this.atomic_exchange(args, dest, rw_ord(ord))?, ["cxchg", ord1, ord2] => - this.atomic_compare_exchange(args, dest, rw_ord(ord1)?, read_ord(ord2)?)?, + this.atomic_compare_exchange(args, dest, rw_ord(ord1), read_ord(ord2))?, ["cxchgweak", ord1, ord2] => - this.atomic_compare_exchange_weak(args, dest, rw_ord(ord1)?, read_ord(ord2)?)?, + this.atomic_compare_exchange_weak(args, dest, rw_ord(ord1), read_ord(ord2))?, ["or", ord] => - this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitOr, false), rw_ord(ord)?)?, + this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitOr, false), rw_ord(ord))?, ["xor", ord] => - this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitXor, false), rw_ord(ord)?)?, + this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitXor, false), rw_ord(ord))?, ["and", ord] => - this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, false), rw_ord(ord)?)?, + this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, false), rw_ord(ord))?, ["nand", ord] => - this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, true), rw_ord(ord)?)?, + this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, true), rw_ord(ord))?, ["xadd", ord] => - this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::Add, false), rw_ord(ord)?)?, + this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::Add, false), rw_ord(ord))?, ["xsub", ord] => - this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::Sub, false), rw_ord(ord)?)?, + this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::Sub, false), rw_ord(ord))?, ["min", ord] => { // Later we will use the type to indicate signed vs unsigned, // so make sure it matches the intrinsic name. assert!(matches!(args[1].layout.ty.kind(), ty::Int(_))); - this.atomic_rmw_op(args, dest, AtomicOp::Min, rw_ord(ord)?)?; + this.atomic_rmw_op(args, dest, AtomicOp::Min, rw_ord(ord))?; } ["umin", ord] => { // Later we will use the type to indicate signed vs unsigned, // so make sure it matches the intrinsic name. assert!(matches!(args[1].layout.ty.kind(), ty::Uint(_))); - this.atomic_rmw_op(args, dest, AtomicOp::Min, rw_ord(ord)?)?; + this.atomic_rmw_op(args, dest, AtomicOp::Min, rw_ord(ord))?; } ["max", ord] => { // Later we will use the type to indicate signed vs unsigned, // so make sure it matches the intrinsic name. assert!(matches!(args[1].layout.ty.kind(), ty::Int(_))); - this.atomic_rmw_op(args, dest, AtomicOp::Max, rw_ord(ord)?)?; + this.atomic_rmw_op(args, dest, AtomicOp::Max, rw_ord(ord))?; } ["umax", ord] => { // Later we will use the type to indicate signed vs unsigned, // so make sure it matches the intrinsic name. assert!(matches!(args[1].layout.ty.kind(), ty::Uint(_))); - this.atomic_rmw_op(args, dest, AtomicOp::Max, rw_ord(ord)?)?; + this.atomic_rmw_op(args, dest, AtomicOp::Max, rw_ord(ord))?; } - _ => throw_unsup_format!("unimplemented intrinsic: `atomic_{intrinsic_name}`"), + _ => return Ok(EmulateItemResult::NotSupported), } - Ok(()) + Ok(EmulateItemResult::NeedsJumping) } } diff --git a/src/shims/intrinsics/mod.rs b/src/intrinsics/mod.rs similarity index 79% rename from src/shims/intrinsics/mod.rs rename to src/intrinsics/mod.rs index d16d5d99e9..7344846d6d 100644 --- a/src/shims/intrinsics/mod.rs +++ b/src/intrinsics/mod.rs @@ -10,6 +10,7 @@ use rustc_middle::{ mir, ty::{self, FloatTy}, }; +use rustc_span::{sym, Symbol}; use rustc_target::abi::Size; use crate::*; @@ -26,54 +27,60 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { dest: &MPlaceTy<'tcx, Provenance>, ret: Option, _unwind: mir::UnwindAction, - ) -> InterpResult<'tcx> { + ) -> InterpResult<'tcx, Option>> { let this = self.eval_context_mut(); // See if the core engine can handle this intrinsic. if this.emulate_intrinsic(instance, args, dest, ret)? { - return Ok(()); + return Ok(None); } let intrinsic_name = this.tcx.item_name(instance.def_id()); let intrinsic_name = intrinsic_name.as_str(); - // Handle intrinsics without return place. - match intrinsic_name { - "abort" => { - throw_machine_stop!(TerminationInfo::Abort( - "the program aborted execution".to_owned() - )) + match this.emulate_intrinsic_by_name(intrinsic_name, instance.args, args, dest, ret)? { + EmulateItemResult::NotSupported => { + // We haven't handled the intrinsic, let's see if we can use a fallback body. + if this.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden { + throw_unsup_format!("unimplemented intrinsic: `{intrinsic_name}`") + } + let intrinsic_fallback_checks_ub = Symbol::intern("intrinsic_fallback_checks_ub"); + if this + .tcx + .get_attrs_by_path( + instance.def_id(), + &[sym::miri, intrinsic_fallback_checks_ub], + ) + .next() + .is_none() + { + throw_unsup_format!( + "miri can only use intrinsic fallback bodies that check UB. After verifying that `{intrinsic_name}` does so, add the `#[miri::intrinsic_fallback_checks_ub]` attribute to it; also ping @rust-lang/miri when you do that" + ); + } + Ok(Some(ty::Instance { + def: ty::InstanceDef::Item(instance.def_id()), + args: instance.args, + })) } - _ => {} - } - - // All remaining supported intrinsics have a return place. - let ret = match ret { - None => throw_unsup_format!("unimplemented (diverging) intrinsic: `{intrinsic_name}`"), - Some(p) => p, - }; - - // Some intrinsics are special and need the "ret". - match intrinsic_name { - "catch_unwind" => return this.handle_catch_unwind(args, dest, ret), - _ => {} + EmulateItemResult::NeedsJumping => { + trace!("{:?}", this.dump_place(&dest.clone().into())); + this.return_to_block(ret)?; + Ok(None) + } + EmulateItemResult::AlreadyJumped => Ok(None), } - - // The rest jumps to `ret` immediately. - this.emulate_intrinsic_by_name(intrinsic_name, instance.args, args, dest)?; - - trace!("{:?}", this.dump_place(&dest.clone().into())); - this.go_to_block(ret); - Ok(()) } /// Emulates a Miri-supported intrinsic (not supported by the core engine). + /// Returns `Ok(true)` if the intrinsic was handled. fn emulate_intrinsic_by_name( &mut self, intrinsic_name: &str, generic_args: ty::GenericArgsRef<'tcx>, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx> { + ret: Option, + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); if let Some(name) = intrinsic_name.strip_prefix("atomic_") { @@ -84,22 +91,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } match intrinsic_name { - // Miri overwriting CTFE intrinsics. - "ptr_guaranteed_cmp" => { - let [left, right] = check_arg_count(args)?; - let left = this.read_immediate(left)?; - let right = this.read_immediate(right)?; - let val = this.wrapping_binary_op(mir::BinOp::Eq, &left, &right)?; - // We're type punning a bool as an u8 here. - this.write_scalar(val.to_scalar(), dest)?; - } - "const_allocate" => { - // For now, for compatibility with the run-time implementation of this, we just return null. - // See . - this.write_null(dest)?; + // Basic control flow + "abort" => { + throw_machine_stop!(TerminationInfo::Abort( + "the program aborted execution".to_owned() + )); } - "const_deallocate" => { - // complete NOP + "catch_unwind" => { + this.handle_catch_unwind(args, dest, ret)?; + // This pushed a stack frame, don't jump to `ret`. + return Ok(EmulateItemResult::AlreadyJumped); } // Raw memory accesses @@ -116,7 +117,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { "write_bytes" | "volatile_set_memory" => { let [ptr, val_byte, count] = check_arg_count(args)?; - let ty = ptr.layout.ty.builtin_deref(true).unwrap().ty; + let ty = ptr.layout.ty.builtin_deref(true).unwrap(); let ty_layout = this.layout_of(ty)?; let val_byte = this.read_scalar(val_byte)?.to_u8()?; let ptr = this.read_pointer(ptr)?; @@ -166,6 +167,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // This is a "bitwise" operation, so there's no NaN non-determinism. this.write_scalar(Scalar::from_f64(f.abs()), dest)?; } + "floorf32" | "ceilf32" | "truncf32" | "roundf32" | "rintf32" => { let [f] = check_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; @@ -181,6 +183,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } + "floorf64" | "ceilf64" | "truncf64" | "roundf64" | "rintf64" => { + let [f] = check_arg_count(args)?; + let f = this.read_scalar(f)?.to_f64()?; + let mode = match intrinsic_name { + "floorf64" => Round::TowardNegative, + "ceilf64" => Round::TowardPositive, + "truncf64" => Round::TowardZero, + "roundf64" => Round::NearestTiesToAway, + "rintf64" => Round::NearestTiesToEven, + _ => bug!(), + }; + let res = f.round_to_integral(mode).value; + let res = this.adjust_nan(res, &[f]); + this.write_scalar(res, dest)?; + } + #[rustfmt::skip] | "sinf32" | "cosf32" @@ -193,12 +211,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { => { let [f] = check_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; - // FIXME: Using host floats. + // Using host floats (but it's fine, these operations do not have guaranteed precision). let f_host = f.to_host(); let res = match intrinsic_name { "sinf32" => f_host.sin(), "cosf32" => f_host.cos(), - "sqrtf32" => f_host.sqrt(), + "sqrtf32" => f_host.sqrt(), // FIXME Using host floats, this should use full-precision soft-floats "expf32" => f_host.exp(), "exp2f32" => f_host.exp2(), "logf32" => f_host.ln(), @@ -210,22 +228,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } - - "floorf64" | "ceilf64" | "truncf64" | "roundf64" | "rintf64" => { - let [f] = check_arg_count(args)?; - let f = this.read_scalar(f)?.to_f64()?; - let mode = match intrinsic_name { - "floorf64" => Round::TowardNegative, - "ceilf64" => Round::TowardPositive, - "truncf64" => Round::TowardZero, - "roundf64" => Round::NearestTiesToAway, - "rintf64" => Round::NearestTiesToEven, - _ => bug!(), - }; - let res = f.round_to_integral(mode).value; - let res = this.adjust_nan(res, &[f]); - this.write_scalar(res, dest)?; - } #[rustfmt::skip] | "sinf64" | "cosf64" @@ -238,12 +240,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { => { let [f] = check_arg_count(args)?; let f = this.read_scalar(f)?.to_f64()?; - // FIXME: Using host floats. + // Using host floats (but it's fine, these operations do not have guaranteed precision). let f_host = f.to_host(); let res = match intrinsic_name { "sinf64" => f_host.sin(), "cosf64" => f_host.cos(), - "sqrtf64" => f_host.sqrt(), + "sqrtf64" => f_host.sqrt(), // FIXME Using host floats, this should use full-precision soft-floats "expf64" => f_host.exp(), "exp2f64" => f_host.exp2(), "logf64" => f_host.ln(), @@ -256,61 +258,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_scalar(res, dest)?; } - #[rustfmt::skip] - | "fadd_fast" - | "fsub_fast" - | "fmul_fast" - | "fdiv_fast" - | "frem_fast" - => { - let [a, b] = check_arg_count(args)?; - let a = this.read_immediate(a)?; - let b = this.read_immediate(b)?; - let op = match intrinsic_name { - "fadd_fast" => mir::BinOp::Add, - "fsub_fast" => mir::BinOp::Sub, - "fmul_fast" => mir::BinOp::Mul, - "fdiv_fast" => mir::BinOp::Div, - "frem_fast" => mir::BinOp::Rem, - _ => bug!(), - }; - let float_finite = |x: &ImmTy<'tcx, _>| -> InterpResult<'tcx, bool> { - let ty::Float(fty) = x.layout.ty.kind() else { - bug!("float_finite: non-float input type {}", x.layout.ty) - }; - Ok(match fty { - FloatTy::F16 => unimplemented!("f16_f128"), - FloatTy::F32 => x.to_scalar().to_f32()?.is_finite(), - FloatTy::F64 => x.to_scalar().to_f64()?.is_finite(), - FloatTy::F128 => unimplemented!("f16_f128"), - }) - }; - match (float_finite(&a)?, float_finite(&b)?) { - (false, false) => throw_ub_format!( - "`{intrinsic_name}` intrinsic called with non-finite value as both parameters", - ), - (false, _) => throw_ub_format!( - "`{intrinsic_name}` intrinsic called with non-finite value as first parameter", - ), - (_, false) => throw_ub_format!( - "`{intrinsic_name}` intrinsic called with non-finite value as second parameter", - ), - _ => {} - } - let res = this.wrapping_binary_op(op, &a, &b)?; - if !float_finite(&res)? { - throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result"); - } - // This cannot be a NaN so we also don't have to apply any non-determinism. - // (Also, `wrapping_binary_op` already called `generate_nan` if needed.) - this.write_immediate(*res, dest)?; - } - - #[rustfmt::skip] - | "minnumf32" - | "maxnumf32" - | "copysignf32" - => { + "minnumf32" | "maxnumf32" | "copysignf32" => { let [a, b] = check_arg_count(args)?; let a = this.read_scalar(a)?.to_f32()?; let b = this.read_scalar(b)?.to_f32()?; @@ -322,12 +270,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { }; this.write_scalar(Scalar::from_f32(res), dest)?; } - - #[rustfmt::skip] - | "minnumf64" - | "maxnumf64" - | "copysignf64" - => { + "minnumf64" | "maxnumf64" | "copysignf64" => { let [a, b] = check_arg_count(args)?; let a = this.read_scalar(a)?.to_f64()?; let b = this.read_scalar(b)?.to_f64()?; @@ -350,7 +293,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let res = this.adjust_nan(res, &[a, b, c]); this.write_scalar(res, dest)?; } - "fmaf64" => { let [a, b, c] = check_arg_count(args)?; let a = this.read_scalar(a)?.to_f64()?; @@ -366,17 +308,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let [f1, f2] = check_arg_count(args)?; let f1 = this.read_scalar(f1)?.to_f32()?; let f2 = this.read_scalar(f2)?.to_f32()?; - // FIXME: Using host floats. + // Using host floats (but it's fine, this operation does not have guaranteed precision). let res = f1.to_host().powf(f2.to_host()).to_soft(); let res = this.adjust_nan(res, &[f1, f2]); this.write_scalar(res, dest)?; } - "powf64" => { let [f1, f2] = check_arg_count(args)?; let f1 = this.read_scalar(f1)?.to_f64()?; let f2 = this.read_scalar(f2)?.to_f64()?; - // FIXME: Using host floats. + // Using host floats (but it's fine, this operation does not have guaranteed precision). let res = f1.to_host().powf(f2.to_host()).to_soft(); let res = this.adjust_nan(res, &[f1, f2]); this.write_scalar(res, dest)?; @@ -386,22 +327,94 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let [f, i] = check_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; let i = this.read_scalar(i)?.to_i32()?; - // FIXME: Using host floats. + // Using host floats (but it's fine, this operation does not have guaranteed precision). let res = f.to_host().powi(i).to_soft(); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } - "powif64" => { let [f, i] = check_arg_count(args)?; let f = this.read_scalar(f)?.to_f64()?; let i = this.read_scalar(i)?.to_i32()?; - // FIXME: Using host floats. + // Using host floats (but it's fine, this operation does not have guaranteed precision). let res = f.to_host().powi(i).to_soft(); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } + #[rustfmt::skip] + | "fadd_algebraic" + | "fsub_algebraic" + | "fmul_algebraic" + | "fdiv_algebraic" + | "frem_algebraic" + => { + let [a, b] = check_arg_count(args)?; + let a = this.read_immediate(a)?; + let b = this.read_immediate(b)?; + let op = match intrinsic_name { + "fadd_algebraic" => mir::BinOp::Add, + "fsub_algebraic" => mir::BinOp::Sub, + "fmul_algebraic" => mir::BinOp::Mul, + "fdiv_algebraic" => mir::BinOp::Div, + "frem_algebraic" => mir::BinOp::Rem, + _ => bug!(), + }; + let res = this.wrapping_binary_op(op, &a, &b)?; + // `wrapping_binary_op` already called `generate_nan` if necessary. + this.write_immediate(*res, dest)?; + } + + #[rustfmt::skip] + | "fadd_fast" + | "fsub_fast" + | "fmul_fast" + | "fdiv_fast" + | "frem_fast" + => { + let [a, b] = check_arg_count(args)?; + let a = this.read_immediate(a)?; + let b = this.read_immediate(b)?; + let op = match intrinsic_name { + "fadd_fast" => mir::BinOp::Add, + "fsub_fast" => mir::BinOp::Sub, + "fmul_fast" => mir::BinOp::Mul, + "fdiv_fast" => mir::BinOp::Div, + "frem_fast" => mir::BinOp::Rem, + _ => bug!(), + }; + let float_finite = |x: &ImmTy<'tcx, _>| -> InterpResult<'tcx, bool> { + let ty::Float(fty) = x.layout.ty.kind() else { + bug!("float_finite: non-float input type {}", x.layout.ty) + }; + Ok(match fty { + FloatTy::F16 => unimplemented!("f16_f128"), + FloatTy::F32 => x.to_scalar().to_f32()?.is_finite(), + FloatTy::F64 => x.to_scalar().to_f64()?.is_finite(), + FloatTy::F128 => unimplemented!("f16_f128"), + }) + }; + match (float_finite(&a)?, float_finite(&b)?) { + (false, false) => throw_ub_format!( + "`{intrinsic_name}` intrinsic called with non-finite value as both parameters", + ), + (false, _) => throw_ub_format!( + "`{intrinsic_name}` intrinsic called with non-finite value as first parameter", + ), + (_, false) => throw_ub_format!( + "`{intrinsic_name}` intrinsic called with non-finite value as second parameter", + ), + _ => {} + } + let res = this.wrapping_binary_op(op, &a, &b)?; + if !float_finite(&res)? { + throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result"); + } + // This cannot be a NaN so we also don't have to apply any non-determinism. + // (Also, `wrapping_binary_op` already called `generate_nan` if needed.) + this.write_immediate(*res, dest)?; + } + "float_to_int_unchecked" => { let [val] = check_arg_count(args)?; let val = this.read_immediate(val)?; @@ -425,9 +438,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { throw_machine_stop!(TerminationInfo::Abort(format!("trace/breakpoint trap"))) } - name => throw_unsup_format!("unimplemented intrinsic: `{name}`"), + _ => return Ok(EmulateItemResult::NotSupported), } - Ok(()) + Ok(EmulateItemResult::NeedsJumping) } } diff --git a/src/shims/intrinsics/simd.rs b/src/intrinsics/simd.rs similarity index 98% rename from src/shims/intrinsics/simd.rs rename to src/intrinsics/simd.rs index 9d268f09ed..d0a78429ca 100644 --- a/src/shims/intrinsics/simd.rs +++ b/src/intrinsics/simd.rs @@ -16,13 +16,14 @@ pub(crate) enum MinMax { impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { /// Calls the simd intrinsic `intrinsic`; the `simd_` prefix has already been removed. + /// Returns `Ok(true)` if the intrinsic was handled. fn emulate_simd_intrinsic( &mut self, intrinsic_name: &str, generic_args: ty::GenericArgsRef<'tcx>, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx> { + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); match intrinsic_name { #[rustfmt::skip] @@ -99,14 +100,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let ty::Float(float_ty) = op.layout.ty.kind() else { span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name) }; - // FIXME using host floats + // Using host floats (but it's fine, these operations do not have guaranteed precision). match float_ty { FloatTy::F16 => unimplemented!("f16_f128"), FloatTy::F32 => { let f = op.to_scalar().to_f32()?; let f_host = f.to_host(); let res = match host_op { - "fsqrt" => f_host.sqrt(), + "fsqrt" => f_host.sqrt(), // FIXME Using host floats, this should use full-precision soft-floats "fsin" => f_host.sin(), "fcos" => f_host.cos(), "fexp" => f_host.exp(), @@ -163,7 +164,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } Op::Numeric(name) => { - this.numeric_intrinsic(name, op.to_scalar(), op.layout)? + this.numeric_intrinsic(name, op.to_scalar(), op.layout, op.layout)? } }; this.write_scalar(val, &dest)?; @@ -266,7 +267,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Op::WrappingOffset => { let ptr = left.to_scalar().to_pointer(this)?; let offset_count = right.to_scalar().to_target_isize(this)?; - let pointee_ty = left.layout.ty.builtin_deref(true).unwrap().ty; + let pointee_ty = left.layout.ty.builtin_deref(true).unwrap(); let pointee_size = i64::try_from(this.layout_of(pointee_ty)?.size.bytes()).unwrap(); let offset_bytes = offset_count.wrapping_mul(pointee_size); @@ -514,7 +515,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { dest.transmute(this.machine.layouts.uint(dest.layout.size).unwrap(), this)?; this.write_int(res, &dest)?; } - "cast" | "as" | "cast_ptr" | "expose_addr" | "with_exposed_provenance" => { + "cast" | "as" | "cast_ptr" | "expose_provenance" | "with_exposed_provenance" => { let [op] = check_arg_count(args)?; let (op, op_len) = this.operand_to_simd(op)?; let (dest, dest_len) = this.mplace_to_simd(dest)?; @@ -524,7 +525,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let unsafe_cast = intrinsic_name == "cast"; let safe_cast = intrinsic_name == "as"; let ptr_cast = intrinsic_name == "cast_ptr"; - let expose_cast = intrinsic_name == "expose_addr"; + let expose_cast = intrinsic_name == "expose_provenance"; let from_exposed_cast = intrinsic_name == "with_exposed_provenance"; for i in 0..dest_len { @@ -557,7 +558,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.ptr_to_ptr(&op, dest.layout)?, // Ptr/Int casts (ty::RawPtr(..), ty::Int(_) | ty::Uint(_)) if expose_cast => - this.pointer_expose_address_cast(&op, dest.layout)?, + this.pointer_expose_provenance_cast(&op, dest.layout)?, (ty::Int(_) | ty::Uint(_), ty::RawPtr(..)) if from_exposed_cast => this.pointer_with_exposed_provenance_cast(&op, dest.layout)?, // Error otherwise @@ -743,9 +744,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } - name => throw_unsup_format!("unimplemented intrinsic: `simd_{name}`"), + _ => return Ok(EmulateItemResult::NotSupported), } - Ok(()) + Ok(EmulateItemResult::NeedsJumping) } fn fminmax_op( diff --git a/src/lib.rs b/src/lib.rs index 423eac1603..d23a44c376 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,6 @@ #![feature(cell_update)] #![feature(const_option)] #![feature(float_gamma)] -#![feature(generic_nonzero)] #![feature(map_try_insert)] #![feature(never_type)] #![feature(try_blocks)] @@ -13,6 +12,7 @@ #![feature(let_chains)] #![feature(lint_reasons)] #![feature(trait_upcasting)] +#![feature(strict_overflow_ops)] // Configure clippy and other lints #![allow( clippy::collapsible_else_if, @@ -81,6 +81,7 @@ mod concurrency; mod diagnostics; mod eval; mod helpers; +mod intrinsics; mod machine; mod mono_hash_map; mod operator; @@ -91,17 +92,20 @@ mod shims; // Establish a "crate-wide prelude": we often import `crate::*`. // Make all those symbols available in the same place as our own. +#[doc(no_inline)] pub use rustc_const_eval::interpret::*; // Resolve ambiguity. +#[doc(no_inline)] pub use rustc_const_eval::interpret::{self, AllocMap, PlaceTy, Provenance as _}; +pub use crate::intrinsics::EvalContextExt as _; pub use crate::shims::env::{EnvVars, EvalContextExt as _}; pub use crate::shims::foreign_items::{DynSym, EvalContextExt as _}; -pub use crate::shims::intrinsics::EvalContextExt as _; pub use crate::shims::os_str::EvalContextExt as _; pub use crate::shims::panic::{CatchUnwindData, EvalContextExt as _}; pub use crate::shims::time::EvalContextExt as _; pub use crate::shims::tls::TlsData; +pub use crate::shims::EmulateItemResult; pub use crate::alloc_addresses::{EvalContextExt as _, ProvenanceMode}; pub use crate::alloc_bytes::MiriAllocBytes; @@ -117,7 +121,9 @@ pub use crate::concurrency::{ data_race::{AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, EvalContextExt as _}, init_once::{EvalContextExt as _, InitOnceId}, sync::{CondvarId, EvalContextExt as _, MutexId, RwLockId, SyncId}, - thread::{EvalContextExt as _, StackEmptyCallback, ThreadId, ThreadManager, Time}, + thread::{ + BlockReason, CallbackTime, EvalContextExt as _, StackEmptyCallback, ThreadId, ThreadManager, + }, }; pub use crate::diagnostics::{ report_error, EvalContextExt as _, NonHaltingDiagnostic, TerminationInfo, @@ -127,7 +133,7 @@ pub use crate::eval::{ }; pub use crate::helpers::{AccessKind, EvalContextExt as _}; pub use crate::machine::{ - AllocExtra, FrameExtra, MiriInterpCx, MiriInterpCxExt, MiriMachine, MiriMemoryKind, + AllocExtra, FrameExtra, MemoryKind, MiriInterpCx, MiriInterpCxExt, MiriMachine, MiriMemoryKind, PrimitiveLayouts, Provenance, ProvenanceExtra, }; pub use crate::mono_hash_map::MonoHashMap; diff --git a/src/machine.rs b/src/machine.rs index 90902ab8e2..1309bfccb3 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -31,7 +31,7 @@ use rustc_target::spec::abi::Abi; use crate::{ concurrency::{data_race, weak_memory}, - shims::unix::FdTable, + shims::unix, *, }; @@ -135,9 +135,9 @@ pub enum MiriMemoryKind { Mmap, } -impl From for MemoryKind { +impl From for MemoryKind { #[inline(always)] - fn from(kind: MiriMemoryKind) -> MemoryKind { + fn from(kind: MiriMemoryKind) -> MemoryKind { MemoryKind::Machine(kind) } } @@ -185,6 +185,8 @@ impl fmt::Display for MiriMemoryKind { } } +pub type MemoryKind = interpret::MemoryKind; + /// Pointer provenance. #[derive(Clone, Copy)] pub enum Provenance { @@ -239,12 +241,12 @@ pub enum ProvenanceExtra { Wildcard, } -#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] +#[cfg(target_pointer_width = "64")] static_assert_size!(Pointer, 24); // FIXME: this would with in 24bytes but layout optimizations are not smart enough -// #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] +// #[cfg(target_pointer_width = "64")] //static_assert_size!(Pointer>, 24); -#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] +#[cfg(target_pointer_width = "64")] static_assert_size!(Scalar, 32); impl fmt::Debug for Provenance { @@ -378,7 +380,7 @@ impl<'mir, 'tcx: 'mir> PrimitiveLayouts<'tcx> { let mut_raw_ptr = Ty::new_mut_ptr(tcx, tcx.types.unit); let const_raw_ptr = Ty::new_imm_ptr(tcx, tcx.types.unit); Ok(Self { - unit: layout_cx.layout_of(Ty::new_unit(tcx))?, + unit: layout_cx.layout_of(tcx.types.unit)?, i8: layout_cx.layout_of(tcx.types.i8)?, i16: layout_cx.layout_of(tcx.types.i16)?, i32: layout_cx.layout_of(tcx.types.i32)?, @@ -437,8 +439,7 @@ pub struct MiriMachine<'mir, 'tcx> { /// Ptr-int-cast module global data. pub alloc_addresses: alloc_addresses::GlobalState, - /// Environment variables set by `setenv`. - /// Miri does not expose env vars from the host to the emulated program. + /// Environment variables. pub(crate) env_vars: EnvVars<'tcx>, /// Return place of the main function. @@ -463,9 +464,9 @@ pub struct MiriMachine<'mir, 'tcx> { pub(crate) validate: bool, /// The table of file descriptors. - pub(crate) fds: shims::unix::FdTable, + pub(crate) fds: unix::FdTable, /// The table of directory descriptors. - pub(crate) dirs: shims::unix::DirTable, + pub(crate) dirs: unix::DirTable, /// This machine's monotone clock. pub(crate) clock: Clock, @@ -501,7 +502,7 @@ pub struct MiriMachine<'mir, 'tcx> { /// Crates which are considered local for the purposes of error reporting. pub(crate) local_crates: Vec, - /// Mapping extern static names to their base pointer. + /// Mapping extern static names to their pointer. extern_statics: FxHashMap>, /// The random number generator used for resolving non-determinism. @@ -534,11 +535,11 @@ pub struct MiriMachine<'mir, 'tcx> { // The total number of blocks that have been executed. pub(crate) basic_block_count: u64, - /// Handle of the optional shared object file for external functions. + /// Handle of the optional shared object file for native functions. #[cfg(target_os = "linux")] - pub external_so_lib: Option<(libloading::Library, std::path::PathBuf)>, + pub native_lib: Option<(libloading::Library, std::path::PathBuf)>, #[cfg(not(target_os = "linux"))] - pub external_so_lib: Option, + pub native_lib: Option, /// Run a garbage collector for BorTags every N basic blocks. pub(crate) gc_interval: u32, @@ -640,7 +641,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { tls: TlsData::default(), isolated_op: config.isolated_op, validate: config.validate, - fds: FdTable::new(config.mute_stdout_stderr), + fds: unix::FdTable::new(config.mute_stdout_stderr), dirs: Default::default(), layouts, threads: ThreadManager::default(), @@ -664,7 +665,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { basic_block_count: 0, clock: Clock::new(config.isolated_op == IsolatedOp::Allow), #[cfg(target_os = "linux")] - external_so_lib: config.external_so_file.as_ref().map(|lib_file_path| { + native_lib: config.native_lib.as_ref().map(|lib_file_path| { let target_triple = layout_cx.tcx.sess.opts.target_triple.triple(); // Check if host target == the session target. if env!("TARGET") != target_triple { @@ -686,7 +687,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { ) }), #[cfg(not(target_os = "linux"))] - external_so_lib: config.external_so_file.as_ref().map(|_| { + native_lib: config.native_lib.as_ref().map(|_| { panic!("loading external .so files is only supported on Linux") }), gc_interval: config.gc_interval, @@ -801,7 +802,7 @@ impl VisitProvenance for MiriMachine<'_, '_> { preemption_rate: _, report_progress: _, basic_block_count: _, - external_so_lib: _, + native_lib: _, gc_interval: _, since_gc: _, num_cpus: _, @@ -863,10 +864,8 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { type ProvenanceExtra = ProvenanceExtra; type Bytes = MiriAllocBytes; - type MemoryMap = MonoHashMap< - AllocId, - (MemoryKind, Allocation), - >; + type MemoryMap = + MonoHashMap)>; const GLOBAL_KIND: Option = Some(MiriMemoryKind::Global); @@ -987,7 +986,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { dest: &MPlaceTy<'tcx, Provenance>, ret: Option, unwind: mir::UnwindAction, - ) -> InterpResult<'tcx> { + ) -> InterpResult<'tcx, Option>> { ecx.call_intrinsic(instance, args, dest, ret, unwind) } @@ -1042,14 +1041,14 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { ecx.generate_nan(inputs) } - fn thread_local_static_base_pointer( + fn thread_local_static_pointer( ecx: &mut MiriInterpCx<'mir, 'tcx>, def_id: DefId, ) -> InterpResult<'tcx, Pointer> { ecx.get_or_create_thread_local_alloc(def_id) } - fn extern_static_base_pointer( + fn extern_static_pointer( ecx: &MiriInterpCx<'mir, 'tcx>, def_id: DefId, ) -> InterpResult<'tcx, Pointer> { @@ -1066,7 +1065,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { let extern_decl_layout = ecx.tcx.layout_of(ty::ParamEnv::empty().and(def_ty)).unwrap(); if extern_decl_layout.size != shim_size || extern_decl_layout.align.abi != shim_align { throw_unsup_format!( - "`extern` static `{name}` from crate `{krate}` has been declared \ + "extern static `{link_name}` has been declared as `{krate}::{name}` \ with a size of {decl_size} bytes and alignment of {decl_align} bytes, \ but Miri emulates it via an extern static shim \ with a size of {shim_size} bytes and alignment of {shim_align} bytes", @@ -1080,11 +1079,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { } Ok(ptr) } else { - throw_unsup_format!( - "`extern` static `{name}` from crate `{krate}` is not supported by Miri", - name = ecx.tcx.def_path_str(def_id), - krate = ecx.tcx.crate_name(def_id.krate), - ) + throw_unsup_format!("extern static `{link_name}` is not supported by Miri",) } } @@ -1092,7 +1087,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { ecx: &MiriInterpCx<'mir, 'tcx>, id: AllocId, alloc: Cow<'b, Allocation>, - kind: Option>, + kind: Option, ) -> InterpResult<'tcx, Cow<'b, Allocation>> { let kind = kind.expect("we set our STATIC_KIND so this cannot be None"); @@ -1140,7 +1135,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { weak_memory: buffer_alloc, backtrace, }, - |ptr| ecx.global_base_pointer(ptr), + |ptr| ecx.global_root_pointer(ptr), )?; if matches!(kind, MemoryKind::Machine(kind) if kind.should_save_allocation_span()) { @@ -1153,31 +1148,33 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { Ok(Cow::Owned(alloc)) } - fn adjust_alloc_base_pointer( + fn adjust_alloc_root_pointer( ecx: &MiriInterpCx<'mir, 'tcx>, ptr: Pointer, + kind: Option, ) -> InterpResult<'tcx, Pointer> { + let kind = kind.expect("we set our GLOBAL_KIND so this cannot be None"); let alloc_id = ptr.provenance.alloc_id(); if cfg!(debug_assertions) { // The machine promises to never call us on thread-local or extern statics. match ecx.tcx.try_get_global_alloc(alloc_id) { Some(GlobalAlloc::Static(def_id)) if ecx.tcx.is_thread_local_static(def_id) => { - panic!("adjust_alloc_base_pointer called on thread-local static") + panic!("adjust_alloc_root_pointer called on thread-local static") } Some(GlobalAlloc::Static(def_id)) if ecx.tcx.is_foreign_item(def_id) => { - panic!("adjust_alloc_base_pointer called on extern static") + panic!("adjust_alloc_root_pointer called on extern static") } _ => {} } } // FIXME: can we somehow preserve the immutability of `ptr`? let tag = if let Some(borrow_tracker) = &ecx.machine.borrow_tracker { - borrow_tracker.borrow_mut().base_ptr_tag(alloc_id, &ecx.machine) + borrow_tracker.borrow_mut().root_ptr_tag(alloc_id, &ecx.machine) } else { // Value does not matter, SB is disabled BorTag::default() }; - ecx.ptr_from_rel_ptr(ptr, tag) + ecx.adjust_alloc_root_pointer(ptr, tag, kind) } /// Called on `usize as ptr` casts. @@ -1285,6 +1282,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { (alloc_id, prove_extra): (AllocId, Self::ProvenanceExtra), size: Size, align: Align, + kind: MemoryKind, ) -> InterpResult<'tcx> { if machine.tracked_alloc_ids.contains(&alloc_id) { machine.emit_diagnostic(NonHaltingDiagnostic::FreedAlloc(alloc_id)); @@ -1305,12 +1303,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { { *deallocated_at = Some(machine.current_span()); } - machine.alloc_addresses.get_mut().free_alloc_id( - machine.rng.get_mut(), - alloc_id, - size, - align, - ); + machine.free_alloc_id(alloc_id, size, align, kind); Ok(()) } diff --git a/src/shims/alloc.rs b/src/shims/alloc.rs new file mode 100644 index 0000000000..1deb9a5654 --- /dev/null +++ b/src/shims/alloc.rs @@ -0,0 +1,149 @@ +use std::iter; + +use rustc_ast::expand::allocator::AllocatorKind; +use rustc_target::abi::{Align, Size}; + +use crate::*; + +/// Check some basic requirements for this allocation request: +/// non-zero size, power-of-two alignment. +pub(super) fn check_alloc_request<'tcx>(size: u64, align: u64) -> InterpResult<'tcx> { + if size == 0 { + throw_ub_format!("creating allocation with size 0"); + } + if !align.is_power_of_two() { + throw_ub_format!("creating allocation with non-power-of-two alignment {}", align); + } + Ok(()) +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Returns the alignment that `malloc` would guarantee for requests of the given size. + fn malloc_align(&self, size: u64) -> Align { + let this = self.eval_context_ref(); + // The C standard says: "The pointer returned if the allocation succeeds is suitably aligned + // so that it may be assigned to a pointer to any type of object with a fundamental + // alignment requirement and size less than or equal to the size requested." + // So first we need to figure out what the limits are for "fundamental alignment". + // This is given by `alignof(max_align_t)`. The following list is taken from + // `library/std/src/sys/pal/common/alloc.rs` (where this is called `MIN_ALIGN`) and should + // be kept in sync. + let max_fundamental_align = match this.tcx.sess.target.arch.as_ref() { + "x86" | "arm" | "mips" | "mips32r6" | "powerpc" | "powerpc64" | "wasm32" => 8, + "x86_64" | "aarch64" | "mips64" | "mips64r6" | "s390x" | "sparc64" | "loongarch64" => + 16, + arch => bug!("unsupported target architecture for malloc: `{}`", arch), + }; + // The C standard only requires sufficient alignment for any *type* with size less than or + // equal to the size requested. Types one can define in standard C seem to never have an alignment + // bigger than their size. So if the size is 2, then only alignment 2 is guaranteed, even if + // `max_fundamental_align` is bigger. + // This matches what some real-world implementations do, see e.g. + // - https://github.com/jemalloc/jemalloc/issues/1533 + // - https://github.com/llvm/llvm-project/issues/53540 + // - https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2293.htm + if size >= max_fundamental_align { + return Align::from_bytes(max_fundamental_align).unwrap(); + } + // C doesn't have zero-sized types, so presumably nothing is guaranteed here. + if size == 0 { + return Align::ONE; + } + // We have `size < min_align`. Round `size` *down* to the next power of two and use that. + fn prev_power_of_two(x: u64) -> u64 { + let next_pow2 = x.next_power_of_two(); + if next_pow2 == x { + // x *is* a power of two, just use that. + x + } else { + // x is between two powers, so next = 2*prev. + next_pow2 / 2 + } + } + Align::from_bytes(prev_power_of_two(size)).unwrap() + } + + /// Emulates calling the internal __rust_* allocator functions + fn emulate_allocator( + &mut self, + default: impl FnOnce(&mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx>, + ) -> InterpResult<'tcx, EmulateItemResult> { + let this = self.eval_context_mut(); + + let Some(allocator_kind) = this.tcx.allocator_kind(()) else { + // in real code, this symbol does not exist without an allocator + return Ok(EmulateItemResult::NotSupported); + }; + + match allocator_kind { + AllocatorKind::Global => { + // When `#[global_allocator]` is used, `__rust_*` is defined by the macro expansion + // of this attribute. As such we have to call an exported Rust function, + // and not execute any Miri shim. Somewhat unintuitively doing so is done + // by returning `NotSupported`, which triggers the `lookup_exported_symbol` + // fallback case in `emulate_foreign_item`. + return Ok(EmulateItemResult::NotSupported); + } + AllocatorKind::Default => { + default(this)?; + Ok(EmulateItemResult::NeedsJumping) + } + } + } + + fn malloc( + &mut self, + size: u64, + zero_init: bool, + ) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + let align = this.malloc_align(size); + let ptr = this.allocate_ptr(Size::from_bytes(size), align, MiriMemoryKind::C.into())?; + if zero_init { + // We just allocated this, the access is definitely in-bounds and fits into our address space. + this.write_bytes_ptr( + ptr.into(), + iter::repeat(0u8).take(usize::try_from(size).unwrap()), + ) + .unwrap(); + } + Ok(ptr.into()) + } + + fn free(&mut self, ptr: Pointer>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + if !this.ptr_is_null(ptr)? { + this.deallocate_ptr(ptr, None, MiriMemoryKind::C.into())?; + } + Ok(()) + } + + fn realloc( + &mut self, + old_ptr: Pointer>, + new_size: u64, + ) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + let new_align = this.malloc_align(new_size); + if this.ptr_is_null(old_ptr)? { + // Here we must behave like `malloc`. + self.malloc(new_size, /*zero_init*/ false) + } else { + if new_size == 0 { + // C, in their infinite wisdom, made this UB. + // + throw_ub_format!("`realloc` with a size of zero"); + } else { + let new_ptr = this.reallocate_ptr( + old_ptr, + None, + Size::from_bytes(new_size), + new_align, + MiriMemoryKind::C.into(), + )?; + Ok(new_ptr.into()) + } + } + } +} diff --git a/src/shims/backtrace.rs b/src/shims/backtrace.rs index abfa7143a7..294ad50c33 100644 --- a/src/shims/backtrace.rs +++ b/src/shims/backtrace.rs @@ -2,7 +2,7 @@ use crate::*; use rustc_ast::ast::Mutability; use rustc_middle::ty::layout::LayoutOf as _; use rustc_middle::ty::{self, Instance, Ty}; -use rustc_span::{BytePos, Loc, Symbol}; +use rustc_span::{hygiene, BytePos, Loc, Symbol}; use rustc_target::{abi::Size, spec::abi::Abi}; impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} @@ -45,12 +45,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let mut data = Vec::new(); for frame in this.active_thread_stack().iter().rev() { - let mut span = frame.current_span(); - // Match the behavior of runtime backtrace spans - // by using a non-macro span in our backtrace. See `FunctionCx::debug_loc`. - if span.from_expansion() && !tcx.sess.opts.unstable_opts.debug_macros { - span = rustc_span::hygiene::walk_chain(span, frame.body.span.ctxt()) - } + // Match behavior of debuginfo (`FunctionCx::adjusted_span_and_dbg_scope`). + let span = hygiene::walk_chain_collapsed(frame.current_span(), frame.body.span); data.push((frame.instance, span.lo())); } diff --git a/src/shims/env.rs b/src/shims/env.rs index 9e8239cdba..695d113875 100644 --- a/src/shims/env.rs +++ b/src/shims/env.rs @@ -1,47 +1,24 @@ -use std::env; use std::ffi::{OsStr, OsString}; -use std::io::ErrorKind; -use std::mem; use rustc_data_structures::fx::FxHashMap; -use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::Ty; -use rustc_target::abi::Size; use crate::*; - -/// Check whether an operation that writes to a target buffer was successful. -/// Accordingly select return value. -/// Local helper function to be used in Windows shims. -fn windows_check_buffer_size((success, len): (bool, u64)) -> u32 { - if success { - // If the function succeeds, the return value is the number of characters stored in the target buffer, - // not including the terminating null character. - u32::try_from(len.checked_sub(1).unwrap()).unwrap() - } else { - // If the target buffer was not large enough to hold the data, the return value is the buffer size, in characters, - // required to hold the string and its terminating null character. - u32::try_from(len).unwrap() - } -} +use shims::{unix::UnixEnvVars, windows::WindowsEnvVars}; #[derive(Default)] -pub struct EnvVars<'tcx> { - /// Stores pointers to the environment variables. These variables must be stored as - /// null-terminated target strings (c_str or wide_str) with the `"{name}={value}"` format. - map: FxHashMap>>, - - /// Place where the `environ` static is stored. Lazily initialized, but then never changes. - pub(crate) environ: Option>, +pub enum EnvVars<'tcx> { + #[default] + Uninit, + Unix(UnixEnvVars<'tcx>), + Windows(WindowsEnvVars), } impl VisitProvenance for EnvVars<'_> { fn visit_provenance(&self, visit: &mut VisitWith<'_>) { - let EnvVars { map, environ } = self; - - environ.visit_provenance(visit); - for ptr in map.values() { - ptr.visit_provenance(visit); + match self { + EnvVars::Uninit => {} + EnvVars::Unix(env) => env.visit_provenance(visit), + EnvVars::Windows(env) => env.visit_provenance(visit), } } } @@ -53,449 +30,84 @@ impl<'tcx> EnvVars<'tcx> { ) -> InterpResult<'tcx> { // Initialize the `env_vars` map. // Skip the loop entirely if we don't want to forward anything. + let mut env_vars = FxHashMap::default(); if ecx.machine.communicate() || !config.forwarded_env_vars.is_empty() { for (name, value) in &config.env { let forward = ecx.machine.communicate() || config.forwarded_env_vars.iter().any(|v| **v == *name); if forward { - let var_ptr = match ecx.tcx.sess.target.os.as_ref() { - _ if ecx.target_os_is_unix() => - alloc_env_var_as_c_str(name.as_ref(), value.as_ref(), ecx)?, - "windows" => alloc_env_var_as_wide_str(name.as_ref(), value.as_ref(), ecx)?, - unsupported => - throw_unsup_format!( - "environment support for target OS `{}` not yet available", - unsupported - ), - }; - ecx.machine.env_vars.map.insert(name.clone(), var_ptr); + env_vars.insert(OsString::from(name), OsString::from(value)); } } } - // Initialize the `environ` pointer when needed. - if ecx.target_os_is_unix() { - // This is memory backing an extern static, hence `ExternStatic`, not `Env`. - let layout = ecx.machine.layouts.mut_raw_ptr; - let place = ecx.allocate(layout, MiriMemoryKind::ExternStatic.into())?; - ecx.write_null(&place)?; - ecx.machine.env_vars.environ = Some(place); - ecx.update_environ()?; + for (name, value) in &config.set_env_vars { + env_vars.insert(OsString::from(name), OsString::from(value)); } + + let env_vars = if ecx.target_os_is_unix() { + EnvVars::Unix(UnixEnvVars::new(ecx, env_vars)?) + } else if ecx.tcx.sess.target.os == "windows" { + EnvVars::Windows(WindowsEnvVars::new(ecx, env_vars)?) + } else { + // Used e.g. for wasi + EnvVars::Uninit + }; + ecx.machine.env_vars = env_vars; + Ok(()) } pub(crate) fn cleanup<'mir>( ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, ) -> InterpResult<'tcx> { - // Deallocate individual env vars. - let env_vars = mem::take(&mut ecx.machine.env_vars.map); - for (_name, ptr) in env_vars { - ecx.deallocate_ptr(ptr, None, MiriMemoryKind::Runtime.into())?; - } - // Deallocate environ var list. - if ecx.target_os_is_unix() { - let environ = ecx.machine.env_vars.environ.as_ref().unwrap(); - let old_vars_ptr = ecx.read_pointer(environ)?; - ecx.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?; - } - Ok(()) - } -} - -fn alloc_env_var_as_c_str<'mir, 'tcx>( - name: &OsStr, - value: &OsStr, - ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, -) -> InterpResult<'tcx, Pointer>> { - let mut name_osstring = name.to_os_string(); - name_osstring.push("="); - name_osstring.push(value); - ecx.alloc_os_str_as_c_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into()) -} - -fn alloc_env_var_as_wide_str<'mir, 'tcx>( - name: &OsStr, - value: &OsStr, - ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, -) -> InterpResult<'tcx, Pointer>> { - let mut name_osstring = name.to_os_string(); - name_osstring.push("="); - name_osstring.push(value); - ecx.alloc_os_str_as_wide_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into()) -} - -impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} -pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { - fn getenv( - &mut self, - name_op: &OpTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, Pointer>> { - let this = self.eval_context_mut(); - this.assert_target_os_is_unix("getenv"); - - let name_ptr = this.read_pointer(name_op)?; - let name = this.read_os_str_from_c_str(name_ptr)?; - this.read_environ()?; - Ok(match this.machine.env_vars.map.get(name) { - Some(var_ptr) => { - // The offset is used to strip the "{name}=" part of the string. - var_ptr.offset( - Size::from_bytes(u64::try_from(name.len()).unwrap().checked_add(1).unwrap()), - this, - )? - } - None => Pointer::null(), - }) - } - - #[allow(non_snake_case)] - fn GetEnvironmentVariableW( - &mut self, - name_op: &OpTy<'tcx, Provenance>, // LPCWSTR - buf_op: &OpTy<'tcx, Provenance>, // LPWSTR - size_op: &OpTy<'tcx, Provenance>, // DWORD - ) -> InterpResult<'tcx, Scalar> { - // ^ Returns DWORD (u32 on Windows) - - let this = self.eval_context_mut(); - this.assert_target_os("windows", "GetEnvironmentVariableW"); - - let name_ptr = this.read_pointer(name_op)?; - let name = this.read_os_str_from_wide_str(name_ptr)?; - Ok(match this.machine.env_vars.map.get(&name) { - Some(var_ptr) => { - // The offset is used to strip the "{name}=" part of the string. - #[rustfmt::skip] - let name_offset_bytes = u64::try_from(name.len()).unwrap() - .checked_add(1).unwrap() - .checked_mul(2).unwrap(); - let var_ptr = var_ptr.offset(Size::from_bytes(name_offset_bytes), this)?; - let var = this.read_os_str_from_wide_str(var_ptr)?; - - let buf_ptr = this.read_pointer(buf_op)?; - // `buf_size` represents the size in characters. - let buf_size = u64::from(this.read_scalar(size_op)?.to_u32()?); - Scalar::from_u32(windows_check_buffer_size( - this.write_os_str_to_wide_str( - &var, buf_ptr, buf_size, /*truncate*/ false, - )?, - )) - } - None => { - let envvar_not_found = this.eval_windows("c", "ERROR_ENVVAR_NOT_FOUND"); - this.set_last_error(envvar_not_found)?; - Scalar::from_u32(0) // return zero upon failure - } - }) - } - - #[allow(non_snake_case)] - fn GetEnvironmentStringsW(&mut self) -> InterpResult<'tcx, Pointer>> { - let this = self.eval_context_mut(); - this.assert_target_os("windows", "GetEnvironmentStringsW"); - - // Info on layout of environment blocks in Windows: - // https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables - let mut env_vars = std::ffi::OsString::new(); - for &item in this.machine.env_vars.map.values() { - let env_var = this.read_os_str_from_wide_str(item)?; - env_vars.push(env_var); - env_vars.push("\0"); - } - // Allocate environment block & Store environment variables to environment block. - // Final null terminator(block terminator) is added by `alloc_os_str_to_wide_str`. - let envblock_ptr = - this.alloc_os_str_as_wide_str(&env_vars, MiriMemoryKind::Runtime.into())?; - // If the function succeeds, the return value is a pointer to the environment block of the current process. - Ok(envblock_ptr) - } - - #[allow(non_snake_case)] - fn FreeEnvironmentStringsW( - &mut self, - env_block_op: &OpTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); - this.assert_target_os("windows", "FreeEnvironmentStringsW"); - - let env_block_ptr = this.read_pointer(env_block_op)?; - let result = this.deallocate_ptr(env_block_ptr, None, MiriMemoryKind::Runtime.into()); - // If the function succeeds, the return value is nonzero. - Ok(Scalar::from_i32(i32::from(result.is_ok()))) - } - - fn setenv( - &mut self, - name_op: &OpTy<'tcx, Provenance>, - value_op: &OpTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, i32> { - let this = self.eval_context_mut(); - this.assert_target_os_is_unix("setenv"); - - let name_ptr = this.read_pointer(name_op)?; - let value_ptr = this.read_pointer(value_op)?; - - let mut new = None; - if !this.ptr_is_null(name_ptr)? { - let name = this.read_os_str_from_c_str(name_ptr)?; - if !name.is_empty() && !name.to_string_lossy().contains('=') { - let value = this.read_os_str_from_c_str(value_ptr)?; - new = Some((name.to_owned(), value.to_owned())); - } - } - if let Some((name, value)) = new { - let var_ptr = alloc_env_var_as_c_str(&name, &value, this)?; - if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) { - this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; - } - this.update_environ()?; - Ok(0) // return zero on success - } else { - // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character. - let einval = this.eval_libc("EINVAL"); - this.set_last_error(einval)?; - Ok(-1) - } - } - - #[allow(non_snake_case)] - fn SetEnvironmentVariableW( - &mut self, - name_op: &OpTy<'tcx, Provenance>, // LPCWSTR - value_op: &OpTy<'tcx, Provenance>, // LPCWSTR - ) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); - this.assert_target_os("windows", "SetEnvironmentVariableW"); - - let name_ptr = this.read_pointer(name_op)?; - let value_ptr = this.read_pointer(value_op)?; - - if this.ptr_is_null(name_ptr)? { - // ERROR CODE is not clearly explained in docs.. For now, throw UB instead. - throw_ub_format!("pointer to environment variable name is NULL"); - } - - let name = this.read_os_str_from_wide_str(name_ptr)?; - if name.is_empty() { - throw_unsup_format!("environment variable name is an empty string"); - } else if name.to_string_lossy().contains('=') { - throw_unsup_format!("environment variable name contains '='"); - } else if this.ptr_is_null(value_ptr)? { - // Delete environment variable `{name}` - if let Some(var) = this.machine.env_vars.map.remove(&name) { - this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; - } - Ok(this.eval_windows("c", "TRUE")) - } else { - let value = this.read_os_str_from_wide_str(value_ptr)?; - let var_ptr = alloc_env_var_as_wide_str(&name, &value, this)?; - if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) { - this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; - } - Ok(this.eval_windows("c", "TRUE")) + let this = ecx.eval_context_mut(); + match this.machine.env_vars { + EnvVars::Unix(_) => UnixEnvVars::cleanup(this), + EnvVars::Windows(_) => Ok(()), // no cleanup needed + EnvVars::Uninit => Ok(()), } } - fn unsetenv(&mut self, name_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { - let this = self.eval_context_mut(); - this.assert_target_os_is_unix("unsetenv"); - - let name_ptr = this.read_pointer(name_op)?; - let mut success = None; - if !this.ptr_is_null(name_ptr)? { - let name = this.read_os_str_from_c_str(name_ptr)?.to_owned(); - if !name.is_empty() && !name.to_string_lossy().contains('=') { - success = Some(this.machine.env_vars.map.remove(&name)); - } - } - if let Some(old) = success { - if let Some(var) = old { - this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; - } - this.update_environ()?; - Ok(0) - } else { - // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character. - let einval = this.eval_libc("EINVAL"); - this.set_last_error(einval)?; - Ok(-1) + pub(crate) fn unix(&self) -> &UnixEnvVars<'tcx> { + match self { + EnvVars::Unix(env) => env, + _ => unreachable!(), } } - fn getcwd( - &mut self, - buf_op: &OpTy<'tcx, Provenance>, - size_op: &OpTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, Pointer>> { - let this = self.eval_context_mut(); - this.assert_target_os_is_unix("getcwd"); - - let buf = this.read_pointer(buf_op)?; - let size = this.read_target_usize(size_op)?; - - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`getcwd`", reject_with)?; - this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; - return Ok(Pointer::null()); + pub(crate) fn unix_mut(&mut self) -> &mut UnixEnvVars<'tcx> { + match self { + EnvVars::Unix(env) => env, + _ => unreachable!(), } - - // If we cannot get the current directory, we return null - match env::current_dir() { - Ok(cwd) => { - if this.write_path_to_c_str(&cwd, buf, size)?.0 { - return Ok(buf); - } - let erange = this.eval_libc("ERANGE"); - this.set_last_error(erange)?; - } - Err(e) => this.set_last_error_from_io_error(e.kind())?, - } - - Ok(Pointer::null()) } - #[allow(non_snake_case)] - fn GetCurrentDirectoryW( - &mut self, - size_op: &OpTy<'tcx, Provenance>, // DWORD - buf_op: &OpTy<'tcx, Provenance>, // LPTSTR - ) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); - this.assert_target_os("windows", "GetCurrentDirectoryW"); - - let size = u64::from(this.read_scalar(size_op)?.to_u32()?); - let buf = this.read_pointer(buf_op)?; - - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`GetCurrentDirectoryW`", reject_with)?; - this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; - return Ok(Scalar::from_u32(0)); - } - - // If we cannot get the current directory, we return 0 - match env::current_dir() { - Ok(cwd) => - return Ok(Scalar::from_u32(windows_check_buffer_size( - this.write_path_to_wide_str(&cwd, buf, size, /*truncate*/ false)?, - ))), - Err(e) => this.set_last_error_from_io_error(e.kind())?, + pub(crate) fn windows(&self) -> &WindowsEnvVars { + match self { + EnvVars::Windows(env) => env, + _ => unreachable!(), } - Ok(Scalar::from_u32(0)) } - fn chdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { - let this = self.eval_context_mut(); - this.assert_target_os_is_unix("chdir"); - - let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?; - - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`chdir`", reject_with)?; - this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; - - return Ok(-1); - } - - match env::set_current_dir(path) { - Ok(()) => Ok(0), - Err(e) => { - this.set_last_error_from_io_error(e.kind())?; - Ok(-1) - } + pub(crate) fn windows_mut(&mut self) -> &mut WindowsEnvVars { + match self { + EnvVars::Windows(env) => env, + _ => unreachable!(), } } +} - #[allow(non_snake_case)] - fn SetCurrentDirectoryW( - &mut self, - path_op: &OpTy<'tcx, Provenance>, // LPCTSTR - ) -> InterpResult<'tcx, Scalar> { - // ^ Returns BOOL (i32 on Windows) - - let this = self.eval_context_mut(); - this.assert_target_os("windows", "SetCurrentDirectoryW"); - - let path = this.read_path_from_wide_str(this.read_pointer(path_op)?)?; - - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`SetCurrentDirectoryW`", reject_with)?; - this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; - - return Ok(this.eval_windows("c", "FALSE")); - } - - match env::set_current_dir(path) { - Ok(()) => Ok(this.eval_windows("c", "TRUE")), - Err(e) => { - this.set_last_error_from_io_error(e.kind())?; - Ok(this.eval_windows("c", "FALSE")) - } - } - } - - /// Updates the `environ` static. - /// The first time it gets called, also initializes `extra.environ`. - fn update_environ(&mut self) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); - // Deallocate the old environ list, if any. - let environ = this.machine.env_vars.environ.as_ref().unwrap().clone(); - let old_vars_ptr = this.read_pointer(&environ)?; - if !this.ptr_is_null(old_vars_ptr)? { - this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?; - } - - // Collect all the pointers to each variable in a vector. - let mut vars: Vec>> = - this.machine.env_vars.map.values().copied().collect(); - // Add the trailing null pointer. - vars.push(Pointer::null()); - // Make an array with all these pointers inside Miri. - let tcx = this.tcx; - let vars_layout = this.layout_of(Ty::new_array( - tcx.tcx, - this.machine.layouts.mut_raw_ptr.ty, - u64::try_from(vars.len()).unwrap(), - ))?; - let vars_place = this.allocate(vars_layout, MiriMemoryKind::Runtime.into())?; - for (idx, var) in vars.into_iter().enumerate() { - let place = this.project_field(&vars_place, idx)?; - this.write_pointer(var, &place)?; - } - this.write_pointer(vars_place.ptr(), &environ)?; - - Ok(()) - } - - /// Reads from the `environ` static. - /// We don't actually care about the result, but we care about this potentially causing a data race. - fn read_environ(&self) -> InterpResult<'tcx> { +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Try to get an environment variable from the interpreted program's environment. This is + /// useful for implementing shims which are documented to read from the environment. + fn get_env_var(&mut self, name: &OsStr) -> InterpResult<'tcx, Option> { let this = self.eval_context_ref(); - let environ = this.machine.env_vars.environ.as_ref().unwrap(); - let _vars_ptr = this.read_pointer(environ)?; - Ok(()) - } - - fn getpid(&mut self) -> InterpResult<'tcx, i32> { - let this = self.eval_context_mut(); - this.assert_target_os_is_unix("getpid"); - - this.check_no_isolation("`getpid`")?; - - // The reason we need to do this wacky of a conversion is because - // `libc::getpid` returns an i32, however, `std::process::id()` return an u32. - // So we un-do the conversion that stdlib does and turn it back into an i32. - #[allow(clippy::cast_possible_wrap)] - Ok(std::process::id() as i32) - } - - #[allow(non_snake_case)] - fn GetCurrentProcessId(&mut self) -> InterpResult<'tcx, u32> { - let this = self.eval_context_mut(); - this.assert_target_os("windows", "GetCurrentProcessId"); - - this.check_no_isolation("`GetCurrentProcessId`")?; - - Ok(std::process::id()) + match &this.machine.env_vars { + EnvVars::Uninit => return Ok(None), + EnvVars::Unix(vars) => vars.get(this, name), + EnvVars::Windows(vars) => vars.get(name), + } } } diff --git a/src/shims/extern_static.rs b/src/shims/extern_static.rs index 0284e5b606..12fede39e2 100644 --- a/src/shims/extern_static.rs +++ b/src/shims/extern_static.rs @@ -16,8 +16,8 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { /// Zero-initialized pointer-sized extern statics are pretty common. /// Most of them are for weak symbols, which we all set to null (indicating that the - /// symbol is not supported, and triggering fallback code which ends up calling a - /// syscall that we do support). + /// symbol is not supported, and triggering fallback code which ends up calling + /// some other shim that we do support). fn null_ptr_extern_statics( this: &mut MiriInterpCx<'mir, 'tcx>, names: &[&str], @@ -29,42 +29,56 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { Ok(()) } + /// Extern statics that are initialized with function pointers to the symbols of the same name. + fn weak_symbol_extern_statics( + this: &mut MiriInterpCx<'mir, 'tcx>, + names: &[&str], + ) -> InterpResult<'tcx> { + for name in names { + assert!(this.is_dyn_sym(name), "{name} is not a dynamic symbol"); + let layout = this.machine.layouts.const_raw_ptr; + let ptr = this.fn_ptr(FnVal::Other(DynSym::from_str(name))); + let val = ImmTy::from_scalar(Scalar::from_pointer(ptr, this), layout); + Self::alloc_extern_static(this, name, val)?; + } + Ok(()) + } + /// Sets up the "extern statics" for this machine. pub fn init_extern_statics(this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> { // "__rust_no_alloc_shim_is_unstable" - let val = ImmTy::from_int(0, this.machine.layouts.u8); + let val = ImmTy::from_int(0, this.machine.layouts.u8); // always 0, value does not matter Self::alloc_extern_static(this, "__rust_no_alloc_shim_is_unstable", val)?; + // "__rust_alloc_error_handler_should_panic" + let val = this.tcx.sess.opts.unstable_opts.oom.should_panic(); + let val = ImmTy::from_int(val, this.machine.layouts.u8); + Self::alloc_extern_static(this, "__rust_alloc_error_handler_should_panic", val)?; + match this.tcx.sess.target.os.as_ref() { "linux" => { Self::null_ptr_extern_statics( this, - &["__cxa_thread_atexit_impl", "getrandom", "statx", "__clock_gettime64"], + &["__cxa_thread_atexit_impl", "__clock_gettime64"], )?; + Self::weak_symbol_extern_statics(this, &["getrandom", "statx"])?; // "environ" - Self::add_extern_static( - this, - "environ", - this.machine.env_vars.environ.as_ref().unwrap().ptr(), - ); + let environ = this.machine.env_vars.unix().environ(); + Self::add_extern_static(this, "environ", environ); } "freebsd" => { Self::null_ptr_extern_statics(this, &["__cxa_thread_atexit_impl"])?; // "environ" - Self::add_extern_static( - this, - "environ", - this.machine.env_vars.environ.as_ref().unwrap().ptr(), - ); + let environ = this.machine.env_vars.unix().environ(); + Self::add_extern_static(this, "environ", environ); } "android" => { Self::null_ptr_extern_statics(this, &["bsd_signal"])?; - // "signal" -- just needs a non-zero pointer value (function does not even get called), - // but we arrange for this to call the `signal` function anyway. - let layout = this.machine.layouts.const_raw_ptr; - let ptr = this.fn_ptr(FnVal::Other(DynSym::from_str("signal"))); - let val = ImmTy::from_scalar(Scalar::from_pointer(ptr, this), layout); - Self::alloc_extern_static(this, "signal", val)?; + Self::weak_symbol_extern_statics(this, &["signal"])?; + } + "solaris" | "illumos" => { + let environ = this.machine.env_vars.unix().environ(); + Self::add_extern_static(this, "environ", environ); } "windows" => { // "_tls_used" diff --git a/src/shims/ffi_support.rs b/src/shims/ffi_support.rs deleted file mode 100644 index 6da119e5fa..0000000000 --- a/src/shims/ffi_support.rs +++ /dev/null @@ -1,289 +0,0 @@ -use libffi::{high::call as ffi, low::CodePtr}; -use std::ops::Deref; - -use rustc_middle::ty::{self as ty, IntTy, Ty, UintTy}; -use rustc_span::Symbol; -use rustc_target::abi::HasDataLayout; - -use crate::*; - -impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} - -pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { - /// Extract the scalar value from the result of reading a scalar from the machine, - /// and convert it to a `CArg`. - fn scalar_to_carg( - k: Scalar, - arg_type: Ty<'tcx>, - cx: &impl HasDataLayout, - ) -> InterpResult<'tcx, CArg> { - match arg_type.kind() { - // If the primitive provided can be converted to a type matching the type pattern - // then create a `CArg` of this primitive value with the corresponding `CArg` constructor. - // the ints - ty::Int(IntTy::I8) => { - return Ok(CArg::Int8(k.to_i8()?)); - } - ty::Int(IntTy::I16) => { - return Ok(CArg::Int16(k.to_i16()?)); - } - ty::Int(IntTy::I32) => { - return Ok(CArg::Int32(k.to_i32()?)); - } - ty::Int(IntTy::I64) => { - return Ok(CArg::Int64(k.to_i64()?)); - } - ty::Int(IntTy::Isize) => { - // This will fail if host != target, but then the entire FFI thing probably won't work well - // in that situation. - return Ok(CArg::ISize(k.to_target_isize(cx)?.try_into().unwrap())); - } - // the uints - ty::Uint(UintTy::U8) => { - return Ok(CArg::UInt8(k.to_u8()?)); - } - ty::Uint(UintTy::U16) => { - return Ok(CArg::UInt16(k.to_u16()?)); - } - ty::Uint(UintTy::U32) => { - return Ok(CArg::UInt32(k.to_u32()?)); - } - ty::Uint(UintTy::U64) => { - return Ok(CArg::UInt64(k.to_u64()?)); - } - ty::Uint(UintTy::Usize) => { - // This will fail if host != target, but then the entire FFI thing probably won't work well - // in that situation. - return Ok(CArg::USize(k.to_target_usize(cx)?.try_into().unwrap())); - } - _ => {} - } - // If no primitives were returned then we have an unsupported type. - throw_unsup_format!( - "unsupported scalar argument type to external C function: {:?}", - arg_type - ); - } - - /// Call external C function and - /// store output, depending on return type in the function signature. - fn call_external_c_and_store_return<'a>( - &mut self, - link_name: Symbol, - dest: &MPlaceTy<'tcx, Provenance>, - ptr: CodePtr, - libffi_args: Vec>, - ) -> InterpResult<'tcx, ()> { - let this = self.eval_context_mut(); - - // Unsafe because of the call to external C code. - // Because this is calling a C function it is not necessarily sound, - // but there is no way around this and we've checked as much as we can. - unsafe { - // If the return type of a function is a primitive integer type, - // then call the function (`ptr`) with arguments `libffi_args`, store the return value as the specified - // primitive integer type, and then write this value out to the miri memory as an integer. - match dest.layout.ty.kind() { - // ints - ty::Int(IntTy::I8) => { - let x = ffi::call::(ptr, libffi_args.as_slice()); - this.write_int(x, dest)?; - return Ok(()); - } - ty::Int(IntTy::I16) => { - let x = ffi::call::(ptr, libffi_args.as_slice()); - this.write_int(x, dest)?; - return Ok(()); - } - ty::Int(IntTy::I32) => { - let x = ffi::call::(ptr, libffi_args.as_slice()); - this.write_int(x, dest)?; - return Ok(()); - } - ty::Int(IntTy::I64) => { - let x = ffi::call::(ptr, libffi_args.as_slice()); - this.write_int(x, dest)?; - return Ok(()); - } - ty::Int(IntTy::Isize) => { - let x = ffi::call::(ptr, libffi_args.as_slice()); - // `isize` doesn't `impl Into`, so convert manually. - // Convert to `i64` since this covers both 32- and 64-bit machines. - this.write_int(i64::try_from(x).unwrap(), dest)?; - return Ok(()); - } - // uints - ty::Uint(UintTy::U8) => { - let x = ffi::call::(ptr, libffi_args.as_slice()); - this.write_int(x, dest)?; - return Ok(()); - } - ty::Uint(UintTy::U16) => { - let x = ffi::call::(ptr, libffi_args.as_slice()); - this.write_int(x, dest)?; - return Ok(()); - } - ty::Uint(UintTy::U32) => { - let x = ffi::call::(ptr, libffi_args.as_slice()); - this.write_int(x, dest)?; - return Ok(()); - } - ty::Uint(UintTy::U64) => { - let x = ffi::call::(ptr, libffi_args.as_slice()); - this.write_int(x, dest)?; - return Ok(()); - } - ty::Uint(UintTy::Usize) => { - let x = ffi::call::(ptr, libffi_args.as_slice()); - // `usize` doesn't `impl Into`, so convert manually. - // Convert to `u64` since this covers both 32- and 64-bit machines. - this.write_int(u64::try_from(x).unwrap(), dest)?; - return Ok(()); - } - // Functions with no declared return type (i.e., the default return) - // have the output_type `Tuple([])`. - ty::Tuple(t_list) => - if t_list.len() == 0 { - ffi::call::<()>(ptr, libffi_args.as_slice()); - return Ok(()); - }, - _ => {} - } - // FIXME ellen! deal with all the other return types - throw_unsup_format!("unsupported return type to external C function: {:?}", link_name); - } - } - - /// Get the pointer to the function of the specified name in the shared object file, - /// if it exists. The function must be in the shared object file specified: we do *not* - /// return pointers to functions in dependencies of the library. - fn get_func_ptr_explicitly_from_lib(&mut self, link_name: Symbol) -> Option { - let this = self.eval_context_mut(); - // Try getting the function from the shared library. - // On windows `_lib_path` will be unused, hence the name starting with `_`. - let (lib, _lib_path) = this.machine.external_so_lib.as_ref().unwrap(); - let func: libloading::Symbol<'_, unsafe extern "C" fn()> = unsafe { - match lib.get(link_name.as_str().as_bytes()) { - Ok(x) => x, - Err(_) => { - return None; - } - } - }; - - // FIXME: this is a hack! - // The `libloading` crate will automatically load system libraries like `libc`. - // On linux `libloading` is based on `dlsym`: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#202 - // and `dlsym`(https://linux.die.net/man/3/dlsym) looks through the dependency tree of the - // library if it can't find the symbol in the library itself. - // So, in order to check if the function was actually found in the specified - // `machine.external_so_lib` we need to check its `dli_fname` and compare it to - // the specified SO file path. - // This code is a reimplementation of the mechanism for getting `dli_fname` in `libloading`, - // from: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#411 - // using the `libc` crate where this interface is public. - // No `libc::dladdr` on windows. - let mut info = std::mem::MaybeUninit::::uninit(); - unsafe { - if libc::dladdr(*func.deref() as *const _, info.as_mut_ptr()) != 0 { - if std::ffi::CStr::from_ptr(info.assume_init().dli_fname).to_str().unwrap() - != _lib_path.to_str().unwrap() - { - return None; - } - } - } - // Return a pointer to the function. - Some(CodePtr(*func.deref() as *mut _)) - } - - /// Call specified external C function, with supplied arguments. - /// Need to convert all the arguments from their hir representations to - /// a form compatible with C (through `libffi` call). - /// Then, convert return from the C call into a corresponding form that - /// can be stored in Miri internal memory. - fn call_external_c_fct( - &mut self, - link_name: Symbol, - dest: &MPlaceTy<'tcx, Provenance>, - args: &[OpTy<'tcx, Provenance>], - ) -> InterpResult<'tcx, bool> { - // Get the pointer to the function in the shared object file if it exists. - let code_ptr = match self.get_func_ptr_explicitly_from_lib(link_name) { - Some(ptr) => ptr, - None => { - // Shared object file does not export this function -- try the shims next. - return Ok(false); - } - }; - - let this = self.eval_context_mut(); - - // Get the function arguments, and convert them to `libffi`-compatible form. - let mut libffi_args = Vec::::with_capacity(args.len()); - for cur_arg in args.iter() { - libffi_args.push(Self::scalar_to_carg( - this.read_scalar(cur_arg)?, - cur_arg.layout.ty, - this, - )?); - } - - // Convert them to `libffi::high::Arg` type. - let libffi_args = libffi_args - .iter() - .map(|cur_arg| cur_arg.arg_downcast()) - .collect::>>(); - - // Call the function and store output, depending on return type in the function signature. - self.call_external_c_and_store_return(link_name, dest, code_ptr, libffi_args)?; - Ok(true) - } -} - -#[derive(Debug, Clone)] -/// Enum of supported arguments to external C functions. -// We introduce this enum instead of just calling `ffi::arg` and storing a list -// of `libffi::high::Arg` directly, because the `libffi::high::Arg` just wraps a reference -// to the value it represents: https://docs.rs/libffi/latest/libffi/high/call/struct.Arg.html -// and we need to store a copy of the value, and pass a reference to this copy to C instead. -pub enum CArg { - /// 8-bit signed integer. - Int8(i8), - /// 16-bit signed integer. - Int16(i16), - /// 32-bit signed integer. - Int32(i32), - /// 64-bit signed integer. - Int64(i64), - /// isize. - ISize(isize), - /// 8-bit unsigned integer. - UInt8(u8), - /// 16-bit unsigned integer. - UInt16(u16), - /// 32-bit unsigned integer. - UInt32(u32), - /// 64-bit unsigned integer. - UInt64(u64), - /// usize. - USize(usize), -} - -impl<'a> CArg { - /// Convert a `CArg` to a `libffi` argument type. - fn arg_downcast(&'a self) -> libffi::high::Arg<'a> { - match self { - CArg::Int8(i) => ffi::arg(i), - CArg::Int16(i) => ffi::arg(i), - CArg::Int32(i) => ffi::arg(i), - CArg::Int64(i) => ffi::arg(i), - CArg::ISize(i) => ffi::arg(i), - CArg::UInt8(i) => ffi::arg(i), - CArg::UInt16(i) => ffi::arg(i), - CArg::UInt32(i) => ffi::arg(i), - CArg::UInt64(i) => ffi::arg(i), - CArg::USize(i) => ffi::arg(i), - } - } -} diff --git a/src/shims/foreign_items.rs b/src/shims/foreign_items.rs index a25d377f3a..d431c28d55 100644 --- a/src/shims/foreign_items.rs +++ b/src/shims/foreign_items.rs @@ -1,24 +1,18 @@ use std::{collections::hash_map::Entry, io::Write, iter, path::Path}; use rustc_apfloat::Float; -use rustc_ast::expand::allocator::AllocatorKind; -use rustc_hir::{ - def::DefKind, - def_id::{CrateNum, LOCAL_CRATE}, -}; -use rustc_middle::middle::{ - codegen_fn_attrs::CodegenFnAttrFlags, dependency_format::Linkage, - exported_symbols::ExportedSymbol, -}; +use rustc_ast::expand::allocator::alloc_error_handler_name; +use rustc_hir::{def::DefKind, def_id::CrateNum}; +use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::mir; use rustc_middle::ty; -use rustc_session::config::CrateType; use rustc_span::Symbol; use rustc_target::{ abi::{Align, Size}, spec::abi::Abi, }; +use super::alloc::{check_alloc_request, EvalContextExt as _}; use super::backtrace::EvalContextExt as _; use crate::*; use helpers::{ToHost, ToSoft}; @@ -34,16 +28,6 @@ impl DynSym { } } -/// Returned by `emulate_foreign_item_inner`. -pub enum EmulateForeignItemResult { - /// The caller is expected to jump to the return block. - NeedsJumping, - /// Jumping has already been taken care of. - AlreadyJumped, - /// The item is not supported. - NotSupported, -} - impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { /// Emulates calling a foreign item, failing if the item is not supported. @@ -64,75 +48,52 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); let tcx = this.tcx.tcx; - // First: functions that diverge. - let ret = match ret { - None => - match link_name.as_str() { - "miri_start_unwind" => { - // `check_shim` happens inside `handle_miri_start_unwind`. - this.handle_miri_start_unwind(abi, link_name, args, unwind)?; - return Ok(None); - } - // This matches calls to the foreign item `panic_impl`. - // The implementation is provided by the function with the `#[panic_handler]` attribute. - "panic_impl" => { - // We don't use `check_shim` here because we are just forwarding to the lang - // item. Argument count checking will be performed when the returned `Body` is - // called. - this.check_abi_and_shim_symbol_clash(abi, Abi::Rust, link_name)?; - let panic_impl_id = tcx.lang_items().panic_impl().unwrap(); - let panic_impl_instance = ty::Instance::mono(tcx, panic_impl_id); - return Ok(Some(( - this.load_mir(panic_impl_instance.def, None)?, - panic_impl_instance, - ))); - } - #[rustfmt::skip] - | "exit" - | "ExitProcess" - => { - let exp_abi = if link_name.as_str() == "exit" { - Abi::C { unwind: false } - } else { - Abi::System { unwind: false } - }; - let [code] = this.check_shim(abi, exp_abi, link_name, args)?; - // it's really u32 for ExitProcess, but we have to put it into the `Exit` variant anyway - let code = this.read_scalar(code)?.to_i32()?; - throw_machine_stop!(TerminationInfo::Exit { code: code.into(), leak_check: false }); - } - "abort" => { - let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - throw_machine_stop!(TerminationInfo::Abort( - "the program aborted execution".to_owned() - )) - } - _ => { - if let Some(body) = this.lookup_exported_symbol(link_name)? { - return Ok(Some(body)); - } - this.handle_unsupported(format!( - "can't call (diverging) foreign function: {link_name}" - ))?; - return Ok(None); - } - }, - Some(p) => p, - }; + // Some shims forward to other MIR bodies. + match link_name.as_str() { + // This matches calls to the foreign item `panic_impl`. + // The implementation is provided by the function with the `#[panic_handler]` attribute. + "panic_impl" => { + // We don't use `check_shim` here because we are just forwarding to the lang + // item. Argument count checking will be performed when the returned `Body` is + // called. + this.check_abi_and_shim_symbol_clash(abi, Abi::Rust, link_name)?; + let panic_impl_id = tcx.lang_items().panic_impl().unwrap(); + let panic_impl_instance = ty::Instance::mono(tcx, panic_impl_id); + return Ok(Some(( + this.load_mir(panic_impl_instance.def, None)?, + panic_impl_instance, + ))); + } + "__rust_alloc_error_handler" => { + // Forward to the right symbol that implements this function. + let Some(handler_kind) = this.tcx.alloc_error_handler_kind(()) else { + // in real code, this symbol does not exist without an allocator + throw_unsup_format!( + "`__rust_alloc_error_handler` cannot be called when no alloc error handler is set" + ); + }; + let name = alloc_error_handler_name(handler_kind); + let handler = this + .lookup_exported_symbol(Symbol::intern(name))? + .expect("missing alloc error handler symbol"); + return Ok(Some(handler)); + } + _ => {} + } - // Second: functions that return immediately. - match this.emulate_foreign_item_inner(link_name, abi, args, dest)? { - EmulateForeignItemResult::NeedsJumping => { + // The rest either implements the logic, or falls back to `lookup_exported_symbol`. + match this.emulate_foreign_item_inner(link_name, abi, args, dest, unwind)? { + EmulateItemResult::NeedsJumping => { trace!("{:?}", this.dump_place(&dest.clone().into())); - this.go_to_block(ret); + this.return_to_block(ret)?; } - EmulateForeignItemResult::AlreadyJumped => (), - EmulateForeignItemResult::NotSupported => { + EmulateItemResult::AlreadyJumped => (), + EmulateItemResult::NotSupported => { if let Some(body) = this.lookup_exported_symbol(link_name)? { return Ok(Some(body)); } - this.handle_unsupported(format!( + this.handle_unsupported_foreign_item(format!( "can't call foreign function `{link_name}` on OS `{os}`", os = this.tcx.sess.target.os, ))?; @@ -143,6 +104,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Ok(None) } + fn is_dyn_sym(&self, name: &str) -> bool { + let this = self.eval_context_ref(); + match this.tcx.sess.target.os.as_ref() { + os if this.target_os_is_unix() => shims::unix::foreign_items::is_dyn_sym(name, os), + "windows" => shims::windows::foreign_items::is_dyn_sym(name), + _ => false, + } + } + /// Emulates a call to a `DynSym`. fn emulate_dyn_sym( &mut self, @@ -174,74 +144,48 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Entry::Vacant(e) => { // Find it if it was not cached. let mut instance_and_crate: Option<(ty::Instance<'_>, CrateNum)> = None; - // `dependency_formats` includes all the transitive informations needed to link a crate, - // which is what we need here since we need to dig out `exported_symbols` from all transitive - // dependencies. - let dependency_formats = tcx.dependency_formats(()); - let dependency_format = dependency_formats - .iter() - .find(|(crate_type, _)| *crate_type == CrateType::Executable) - .expect("interpreting a non-executable crate"); - for cnum in iter::once(LOCAL_CRATE).chain( - dependency_format.1.iter().enumerate().filter_map(|(num, &linkage)| { - // We add 1 to the number because that's what rustc also does everywhere it - // calls `CrateNum::new`... - #[allow(clippy::arithmetic_side_effects)] - (linkage != Linkage::NotLinked).then_some(CrateNum::new(num + 1)) - }), - ) { - // We can ignore `_export_info` here: we are a Rust crate, and everything is exported - // from a Rust crate. - for &(symbol, _export_info) in tcx.exported_symbols(cnum) { - if let ExportedSymbol::NonGeneric(def_id) = symbol { - let attrs = tcx.codegen_fn_attrs(def_id); - let symbol_name = if let Some(export_name) = attrs.export_name { - export_name - } else if attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE) { - tcx.item_name(def_id) + helpers::iter_exported_symbols(tcx, |cnum, def_id| { + let attrs = tcx.codegen_fn_attrs(def_id); + let symbol_name = if let Some(export_name) = attrs.export_name { + export_name + } else if attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE) { + tcx.item_name(def_id) + } else { + // Skip over items without an explicitly defined symbol name. + return Ok(()); + }; + if symbol_name == link_name { + if let Some((original_instance, original_cnum)) = instance_and_crate { + // Make sure we are consistent wrt what is 'first' and 'second'. + let original_span = tcx.def_span(original_instance.def_id()).data(); + let span = tcx.def_span(def_id).data(); + if original_span < span { + throw_machine_stop!(TerminationInfo::MultipleSymbolDefinitions { + link_name, + first: original_span, + first_crate: tcx.crate_name(original_cnum), + second: span, + second_crate: tcx.crate_name(cnum), + }); } else { - // Skip over items without an explicitly defined symbol name. - continue; - }; - if symbol_name == link_name { - if let Some((original_instance, original_cnum)) = instance_and_crate - { - // Make sure we are consistent wrt what is 'first' and 'second'. - let original_span = - tcx.def_span(original_instance.def_id()).data(); - let span = tcx.def_span(def_id).data(); - if original_span < span { - throw_machine_stop!( - TerminationInfo::MultipleSymbolDefinitions { - link_name, - first: original_span, - first_crate: tcx.crate_name(original_cnum), - second: span, - second_crate: tcx.crate_name(cnum), - } - ); - } else { - throw_machine_stop!( - TerminationInfo::MultipleSymbolDefinitions { - link_name, - first: span, - first_crate: tcx.crate_name(cnum), - second: original_span, - second_crate: tcx.crate_name(original_cnum), - } - ); - } - } - if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) { - throw_ub_format!( - "attempt to call an exported symbol that is not defined as a function" - ); - } - instance_and_crate = Some((ty::Instance::mono(tcx, def_id), cnum)); + throw_machine_stop!(TerminationInfo::MultipleSymbolDefinitions { + link_name, + first: span, + first_crate: tcx.crate_name(cnum), + second: original_span, + second_crate: tcx.crate_name(original_cnum), + }); } } + if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) { + throw_ub_format!( + "attempt to call an exported symbol that is not defined as a function" + ); + } + instance_and_crate = Some((ty::Instance::mono(tcx, def_id), cnum)); } - } + Ok(()) + })?; e.insert(instance_and_crate.map(|ic| ic.0)) } @@ -251,169 +195,29 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Some(instance) => Ok(Some((this.load_mir(instance.def, None)?, instance))), } } - - fn malloc( - &mut self, - size: u64, - zero_init: bool, - kind: MiriMemoryKind, - ) -> InterpResult<'tcx, Pointer>> { - let this = self.eval_context_mut(); - if size == 0 { - Ok(Pointer::null()) - } else { - let align = this.min_align(size, kind); - let ptr = this.allocate_ptr(Size::from_bytes(size), align, kind.into())?; - if zero_init { - // We just allocated this, the access is definitely in-bounds and fits into our address space. - this.write_bytes_ptr( - ptr.into(), - iter::repeat(0u8).take(usize::try_from(size).unwrap()), - ) - .unwrap(); - } - Ok(ptr.into()) - } - } - - fn free( - &mut self, - ptr: Pointer>, - kind: MiriMemoryKind, - ) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); - if !this.ptr_is_null(ptr)? { - this.deallocate_ptr(ptr, None, kind.into())?; - } - Ok(()) - } - - fn realloc( - &mut self, - old_ptr: Pointer>, - new_size: u64, - kind: MiriMemoryKind, - ) -> InterpResult<'tcx, Pointer>> { - let this = self.eval_context_mut(); - let new_align = this.min_align(new_size, kind); - if this.ptr_is_null(old_ptr)? { - if new_size == 0 { - Ok(Pointer::null()) - } else { - let new_ptr = - this.allocate_ptr(Size::from_bytes(new_size), new_align, kind.into())?; - Ok(new_ptr.into()) - } - } else { - if new_size == 0 { - this.deallocate_ptr(old_ptr, None, kind.into())?; - Ok(Pointer::null()) - } else { - let new_ptr = this.reallocate_ptr( - old_ptr, - None, - Size::from_bytes(new_size), - new_align, - kind.into(), - )?; - Ok(new_ptr.into()) - } - } - } } impl<'mir, 'tcx: 'mir> EvalContextExtPriv<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { - /// Read bytes from a `(ptr, len)` argument - fn read_byte_slice<'i>(&'i self, bytes: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, &'i [u8]> - where - 'mir: 'i, - { - let this = self.eval_context_ref(); - let (ptr, len) = this.read_immediate(bytes)?.to_scalar_pair(); - let ptr = ptr.to_pointer(this)?; - let len = len.to_target_usize(this)?; - let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?; - Ok(bytes) - } - - /// Returns the minimum alignment for the target architecture for allocations of the given size. - fn min_align(&self, size: u64, kind: MiriMemoryKind) -> Align { - let this = self.eval_context_ref(); - // List taken from `library/std/src/sys/pal/common/alloc.rs`. - // This list should be kept in sync with the one from libstd. - let min_align = match this.tcx.sess.target.arch.as_ref() { - "x86" | "arm" | "mips" | "mips32r6" | "powerpc" | "powerpc64" | "wasm32" => 8, - "x86_64" | "aarch64" | "mips64" | "mips64r6" | "s390x" | "sparc64" | "loongarch64" => - 16, - arch => bug!("unsupported target architecture for malloc: `{}`", arch), - }; - // Windows always aligns, even small allocations. - // Source: - // But jemalloc does not, so for the C heap we only align if the allocation is sufficiently big. - if kind == MiriMemoryKind::WinHeap || size >= min_align { - return Align::from_bytes(min_align).unwrap(); - } - // We have `size < min_align`. Round `size` *down* to the next power of two and use that. - fn prev_power_of_two(x: u64) -> u64 { - let next_pow2 = x.next_power_of_two(); - if next_pow2 == x { - // x *is* a power of two, just use that. - x - } else { - // x is between two powers, so next = 2*prev. - next_pow2 / 2 - } - } - Align::from_bytes(prev_power_of_two(size)).unwrap() - } - - /// Emulates calling the internal __rust_* allocator functions - fn emulate_allocator( - &mut self, - default: impl FnOnce(&mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx>, - ) -> InterpResult<'tcx, EmulateForeignItemResult> { - let this = self.eval_context_mut(); - - let Some(allocator_kind) = this.tcx.allocator_kind(()) else { - // in real code, this symbol does not exist without an allocator - return Ok(EmulateForeignItemResult::NotSupported); - }; - - match allocator_kind { - AllocatorKind::Global => { - // When `#[global_allocator]` is used, `__rust_*` is defined by the macro expansion - // of this attribute. As such we have to call an exported Rust function, - // and not execute any Miri shim. Somewhat unintuitively doing so is done - // by returning `NotSupported`, which triggers the `lookup_exported_symbol` - // fallback case in `emulate_foreign_item`. - return Ok(EmulateForeignItemResult::NotSupported); - } - AllocatorKind::Default => { - default(this)?; - Ok(EmulateForeignItemResult::NeedsJumping) - } - } - } - fn emulate_foreign_item_inner( &mut self, link_name: Symbol, abi: Abi, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, EmulateForeignItemResult> { + unwind: mir::UnwindAction, + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); // First deal with any external C functions in linked .so file. #[cfg(target_os = "linux")] - if this.machine.external_so_lib.as_ref().is_some() { - use crate::shims::ffi_support::EvalContextExt as _; + if this.machine.native_lib.as_ref().is_some() { + use crate::shims::native_lib::EvalContextExt as _; // An Ok(false) here means that the function being called was not exported // by the specified `.so` file; we should continue and check if it corresponds to // a provided shim. - if this.call_external_c_fct(link_name, dest, args)? { - return Ok(EmulateForeignItemResult::NeedsJumping); + if this.call_native_fn(link_name, dest, args)? { + return Ok(EmulateItemResult::NeedsJumping); } } @@ -457,6 +261,11 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // shim, add it to the corresponding submodule. match link_name.as_str() { // Miri-specific extern functions + "miri_start_unwind" => { + // `check_shim` happens inside `handle_miri_start_unwind`. + this.handle_miri_start_unwind(abi, link_name, args, unwind)?; + return Ok(EmulateItemResult::AlreadyJumped); + } "miri_run_provenance_gc" => { let [] = this.check_shim(abi, Abi::Rust, link_name, args)?; this.run_provenance_gc(); @@ -485,7 +294,9 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let [ptr, nth_parent, name] = this.check_shim(abi, Abi::Rust, link_name, args)?; let ptr = this.read_pointer(ptr)?; let nth_parent = this.read_scalar(nth_parent)?.to_u8()?; - let name = this.read_byte_slice(name)?; + let name = this.read_immediate(name)?; + + let name = this.read_byte_slice(&name)?; // We must make `name` owned because we need to // end the shared borrow from `read_byte_slice` before we can // start the mutable borrow for `give_pointer_debug_name`. @@ -519,34 +330,30 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Return value: 0 on success, otherwise the size it would have needed. this.write_int(if success { 0 } else { needed_size }, dest)?; } - // Obtains the size of a Miri backtrace. See the README for details. "miri_backtrace_size" => { this.handle_miri_backtrace_size(abi, link_name, args, dest)?; } - // Obtains a Miri backtrace. See the README for details. "miri_get_backtrace" => { // `check_shim` happens inside `handle_miri_get_backtrace`. this.handle_miri_get_backtrace(abi, link_name, args, dest)?; } - // Resolves a Miri backtrace frame. See the README for details. "miri_resolve_frame" => { // `check_shim` happens inside `handle_miri_resolve_frame`. this.handle_miri_resolve_frame(abi, link_name, args, dest)?; } - // Writes the function and file names of a Miri backtrace frame into a user provided buffer. See the README for details. "miri_resolve_frame_names" => { this.handle_miri_resolve_frame_names(abi, link_name, args)?; } - // Writes some bytes to the interpreter's stdout/stderr. See the // README for details. "miri_write_to_stdout" | "miri_write_to_stderr" => { let [msg] = this.check_shim(abi, Abi::Rust, link_name, args)?; - let msg = this.read_byte_slice(msg)?; + let msg = this.read_immediate(msg)?; + let msg = this.read_byte_slice(&msg)?; // Note: we're ignoring errors writing to host stdout/stderr. let _ignore = match link_name.as_str() { "miri_write_to_stdout" => std::io::stdout().write_all(msg), @@ -554,7 +361,6 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { _ => unreachable!(), }; } - // Promises that a pointer has a given symbolic alignment. "miri_promise_symbolic_alignment" => { use rustc_target::abi::AlignFromBytesError; @@ -598,11 +404,24 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } + // Aborting the process. + "exit" => { + let [code] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let code = this.read_scalar(code)?.to_i32()?; + throw_machine_stop!(TerminationInfo::Exit { code: code.into(), leak_check: false }); + } + "abort" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + throw_machine_stop!(TerminationInfo::Abort( + "the program aborted execution".to_owned() + )) + } + // Standard C allocation "malloc" => { let [size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let size = this.read_target_usize(size)?; - let res = this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::C)?; + let res = this.malloc(size, /*zero_init:*/ false)?; this.write_pointer(res, dest)?; } "calloc" => { @@ -613,20 +432,20 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let size = items .checked_mul(len) .ok_or_else(|| err_ub_format!("overflow during calloc size computation"))?; - let res = this.malloc(size, /*zero_init:*/ true, MiriMemoryKind::C)?; + let res = this.malloc(size, /*zero_init:*/ true)?; this.write_pointer(res, dest)?; } "free" => { let [ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let ptr = this.read_pointer(ptr)?; - this.free(ptr, MiriMemoryKind::C)?; + this.free(ptr)?; } "realloc" => { let [old_ptr, new_size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let old_ptr = this.read_pointer(old_ptr)?; let new_size = this.read_target_usize(new_size)?; - let res = this.realloc(old_ptr, new_size, MiriMemoryKind::C)?; + let res = this.realloc(old_ptr, new_size)?; this.write_pointer(res, dest)?; } @@ -639,7 +458,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let size = this.read_target_usize(size)?; let align = this.read_target_usize(align)?; - Self::check_alloc_request(size, align)?; + check_alloc_request(size, align)?; let memory_kind = match link_name.as_str() { "__rust_alloc" => MiriMemoryKind::Rust, @@ -660,7 +479,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { "__rust_alloc" => return this.emulate_allocator(default), "miri_alloc" => { default(this)?; - return Ok(EmulateForeignItemResult::NeedsJumping); + return Ok(EmulateItemResult::NeedsJumping); } _ => unreachable!(), } @@ -673,7 +492,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let size = this.read_target_usize(size)?; let align = this.read_target_usize(align)?; - Self::check_alloc_request(size, align)?; + check_alloc_request(size, align)?; let ptr = this.allocate_ptr( Size::from_bytes(size), @@ -720,7 +539,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } "miri_dealloc" => { default(this)?; - return Ok(EmulateForeignItemResult::NeedsJumping); + return Ok(EmulateItemResult::NeedsJumping); } _ => unreachable!(), } @@ -737,7 +556,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let new_size = this.read_target_usize(new_size)?; // No need to check old_size; we anyway check that they match the allocation. - Self::check_alloc_request(new_size, align)?; + check_alloc_request(new_size, align)?; let align = Align::from_bytes(align).unwrap(); let new_ptr = this.reallocate_ptr( @@ -838,6 +657,16 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { dest, )?; } + "wcslen" => { + let [ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let ptr = this.read_pointer(ptr)?; + // This reads at least 1 byte, so we are already enforcing that this is a valid pointer. + let n = this.read_wchar_t_str(ptr)?.len(); + this.write_scalar( + Scalar::from_target_usize(u64::try_from(n).unwrap(), this), + dest, + )?; + } "memcpy" => { let [ptr_dest, ptr_src, n] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; @@ -886,7 +715,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { => { let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let f = this.read_scalar(f)?.to_f32()?; - // FIXME: Using host floats. + // Using host floats (but it's fine, these operations do not have guaranteed precision). let f_host = f.to_host(); let res = match link_name.as_str() { "cbrtf" => f_host.cbrt(), @@ -917,7 +746,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let f2 = this.read_scalar(f2)?.to_f32()?; // underscore case for windows, here and below // (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019) - // FIXME: Using host floats. + // Using host floats (but it's fine, these operations do not have guaranteed precision). let res = match link_name.as_str() { "_hypotf" | "hypotf" => f1.to_host().hypot(f2.to_host()).to_soft(), "atan2f" => f1.to_host().atan2(f2.to_host()).to_soft(), @@ -943,7 +772,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { => { let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let f = this.read_scalar(f)?.to_f64()?; - // FIXME: Using host floats. + // Using host floats (but it's fine, these operations do not have guaranteed precision). let f_host = f.to_host(); let res = match link_name.as_str() { "cbrt" => f_host.cbrt(), @@ -974,7 +803,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let f2 = this.read_scalar(f2)?.to_f64()?; // underscore case for windows, here and below // (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019) - // FIXME: Using host floats. + // Using host floats (but it's fine, these operations do not have guaranteed precision). let res = match link_name.as_str() { "_hypot" | "hypot" => f1.to_host().hypot(f2.to_host()).to_soft(), "atan2" => f1.to_host().atan2(f2.to_host()).to_soft(), @@ -1004,7 +833,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let x = this.read_scalar(x)?.to_f32()?; let signp = this.deref_pointer(signp)?; - // FIXME: Using host floats. + // Using host floats (but it's fine, these operations do not have guaranteed precision). let (res, sign) = x.to_host().ln_gamma(); this.write_int(sign, &signp)?; let res = this.adjust_nan(res.to_soft(), &[x]); @@ -1015,7 +844,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let x = this.read_scalar(x)?.to_f64()?; let signp = this.deref_pointer(signp)?; - // FIXME: Using host floats. + // Using host floats (but it's fine, these operations do not have guaranteed precision). let (res, sign) = x.to_host().ln_gamma(); this.write_int(sign, &signp)?; let res = this.adjust_nan(res.to_soft(), &[x]); @@ -1049,36 +878,6 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { throw_unsup_format!("unsupported `llvm.prefetch` type argument: {}", ty); } } - // FIXME: Move these to an `arm` submodule. - "llvm.aarch64.isb" if this.tcx.sess.target.arch == "aarch64" => { - let [arg] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?; - let arg = this.read_scalar(arg)?.to_i32()?; - match arg { - // SY ("full system scope") - 15 => { - this.yield_active_thread(); - } - _ => { - throw_unsup_format!("unsupported llvm.aarch64.isb argument {}", arg); - } - } - } - "llvm.arm.hint" if this.tcx.sess.target.arch == "arm" => { - let [arg] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?; - let arg = this.read_scalar(arg)?.to_i32()?; - // Note that different arguments might have different target feature requirements. - match arg { - // YIELD - 1 => { - this.expect_target_feature_for_intrinsic(link_name, "v6")?; - this.yield_active_thread(); - } - _ => { - throw_unsup_format!("unsupported llvm.arm.hint argument {}", arg); - } - } - } - // Used to implement the x86 `_mm{,256,512}_popcnt_epi{8,16,32,64}` and wasm // `{i,u}8x16_popcnt` functions. name if name.starts_with("llvm.ctpop.v") => { @@ -1102,6 +901,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } + // Target-specific shims name if name.starts_with("llvm.x86.") && (this.tcx.sess.target.arch == "x86" || this.tcx.sess.target.arch == "x86_64") => @@ -1110,6 +910,35 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this, link_name, abi, args, dest, ); } + // FIXME: Move these to an `arm` submodule. + "llvm.aarch64.isb" if this.tcx.sess.target.arch == "aarch64" => { + let [arg] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?; + let arg = this.read_scalar(arg)?.to_i32()?; + match arg { + // SY ("full system scope") + 15 => { + this.yield_active_thread(); + } + _ => { + throw_unsup_format!("unsupported llvm.aarch64.isb argument {}", arg); + } + } + } + "llvm.arm.hint" if this.tcx.sess.target.arch == "arm" => { + let [arg] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?; + let arg = this.read_scalar(arg)?.to_i32()?; + // Note that different arguments might have different target feature requirements. + match arg { + // YIELD + 1 => { + this.expect_target_feature_for_intrinsic(link_name, "v6")?; + this.yield_active_thread(); + } + _ => { + throw_unsup_format!("unsupported llvm.arm.hint argument {}", arg); + } + } + } // Platform-specific shims _ => @@ -1122,23 +951,11 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { shims::windows::foreign_items::EvalContextExt::emulate_foreign_item_inner( this, link_name, abi, args, dest, ), - _ => Ok(EmulateForeignItemResult::NotSupported), + _ => Ok(EmulateItemResult::NotSupported), }, }; // We only fall through to here if we did *not* hit the `_` arm above, // i.e., if we actually emulated the function with one of the shims. - Ok(EmulateForeignItemResult::NeedsJumping) - } - - /// Check some basic requirements for this allocation request: - /// non-zero size, power-of-two alignment. - fn check_alloc_request(size: u64, align: u64) -> InterpResult<'tcx> { - if size == 0 { - throw_ub_format!("creating allocation with size 0"); - } - if !align.is_power_of_two() { - throw_ub_format!("creating allocation with non-power-of-two alignment {}", align); - } - Ok(()) + Ok(EmulateItemResult::NeedsJumping) } } diff --git a/src/shims/mod.rs b/src/shims/mod.rs index ea6120f757..aaa3c69b92 100644 --- a/src/shims/mod.rs +++ b/src/shims/mod.rs @@ -1,10 +1,10 @@ #![warn(clippy::arithmetic_side_effects)] +mod alloc; mod backtrace; -#[cfg(target_os = "linux")] -pub mod ffi_support; pub mod foreign_items; -pub mod intrinsics; +#[cfg(target_os = "linux")] +pub mod native_lib; pub mod unix; pub mod windows; mod x86; @@ -15,3 +15,13 @@ pub mod os_str; pub mod panic; pub mod time; pub mod tls; + +/// What needs to be done after emulating an item (a shim or an intrinsic) is done. +pub enum EmulateItemResult { + /// The caller is expected to jump to the return block. + NeedsJumping, + /// Jumping has already been taken care of. + AlreadyJumped, + /// The item is not supported. + NotSupported, +} diff --git a/src/shims/native_lib.rs b/src/shims/native_lib.rs new file mode 100644 index 0000000000..f9b8563b4b --- /dev/null +++ b/src/shims/native_lib.rs @@ -0,0 +1,242 @@ +//! Implements calling functions from a native library. +use libffi::{high::call as ffi, low::CodePtr}; +use std::ops::Deref; + +use rustc_middle::ty::{self as ty, IntTy, UintTy}; +use rustc_span::Symbol; +use rustc_target::abi::{Abi, HasDataLayout}; + +use crate::*; + +impl<'mir, 'tcx: 'mir> EvalContextExtPriv<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Call native host function and return the output as an immediate. + fn call_native_with_args<'a>( + &mut self, + link_name: Symbol, + dest: &MPlaceTy<'tcx, Provenance>, + ptr: CodePtr, + libffi_args: Vec>, + ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> { + let this = self.eval_context_mut(); + + // Call the function (`ptr`) with arguments `libffi_args`, and obtain the return value + // as the specified primitive integer type + let scalar = match dest.layout.ty.kind() { + // ints + ty::Int(IntTy::I8) => { + // Unsafe because of the call to native code. + // Because this is calling a C function it is not necessarily sound, + // but there is no way around this and we've checked as much as we can. + let x = unsafe { ffi::call::(ptr, libffi_args.as_slice()) }; + Scalar::from_i8(x) + } + ty::Int(IntTy::I16) => { + let x = unsafe { ffi::call::(ptr, libffi_args.as_slice()) }; + Scalar::from_i16(x) + } + ty::Int(IntTy::I32) => { + let x = unsafe { ffi::call::(ptr, libffi_args.as_slice()) }; + Scalar::from_i32(x) + } + ty::Int(IntTy::I64) => { + let x = unsafe { ffi::call::(ptr, libffi_args.as_slice()) }; + Scalar::from_i64(x) + } + ty::Int(IntTy::Isize) => { + let x = unsafe { ffi::call::(ptr, libffi_args.as_slice()) }; + Scalar::from_target_isize(x.try_into().unwrap(), this) + } + // uints + ty::Uint(UintTy::U8) => { + let x = unsafe { ffi::call::(ptr, libffi_args.as_slice()) }; + Scalar::from_u8(x) + } + ty::Uint(UintTy::U16) => { + let x = unsafe { ffi::call::(ptr, libffi_args.as_slice()) }; + Scalar::from_u16(x) + } + ty::Uint(UintTy::U32) => { + let x = unsafe { ffi::call::(ptr, libffi_args.as_slice()) }; + Scalar::from_u32(x) + } + ty::Uint(UintTy::U64) => { + let x = unsafe { ffi::call::(ptr, libffi_args.as_slice()) }; + Scalar::from_u64(x) + } + ty::Uint(UintTy::Usize) => { + let x = unsafe { ffi::call::(ptr, libffi_args.as_slice()) }; + Scalar::from_target_usize(x.try_into().unwrap(), this) + } + // Functions with no declared return type (i.e., the default return) + // have the output_type `Tuple([])`. + ty::Tuple(t_list) if t_list.len() == 0 => { + unsafe { ffi::call::<()>(ptr, libffi_args.as_slice()) }; + return Ok(ImmTy::uninit(dest.layout)); + } + _ => throw_unsup_format!("unsupported return type for native call: {:?}", link_name), + }; + Ok(ImmTy::from_scalar(scalar, dest.layout)) + } + + /// Get the pointer to the function of the specified name in the shared object file, + /// if it exists. The function must be in the shared object file specified: we do *not* + /// return pointers to functions in dependencies of the library. + fn get_func_ptr_explicitly_from_lib(&mut self, link_name: Symbol) -> Option { + let this = self.eval_context_mut(); + // Try getting the function from the shared library. + // On windows `_lib_path` will be unused, hence the name starting with `_`. + let (lib, _lib_path) = this.machine.native_lib.as_ref().unwrap(); + let func: libloading::Symbol<'_, unsafe extern "C" fn()> = unsafe { + match lib.get(link_name.as_str().as_bytes()) { + Ok(x) => x, + Err(_) => { + return None; + } + } + }; + + // FIXME: this is a hack! + // The `libloading` crate will automatically load system libraries like `libc`. + // On linux `libloading` is based on `dlsym`: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#202 + // and `dlsym`(https://linux.die.net/man/3/dlsym) looks through the dependency tree of the + // library if it can't find the symbol in the library itself. + // So, in order to check if the function was actually found in the specified + // `machine.external_so_lib` we need to check its `dli_fname` and compare it to + // the specified SO file path. + // This code is a reimplementation of the mechanism for getting `dli_fname` in `libloading`, + // from: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#411 + // using the `libc` crate where this interface is public. + let mut info = std::mem::MaybeUninit::::uninit(); + unsafe { + if libc::dladdr(*func.deref() as *const _, info.as_mut_ptr()) != 0 { + if std::ffi::CStr::from_ptr(info.assume_init().dli_fname).to_str().unwrap() + != _lib_path.to_str().unwrap() + { + return None; + } + } + } + // Return a pointer to the function. + Some(CodePtr(*func.deref() as *mut _)) + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Call the native host function, with supplied arguments. + /// Needs to convert all the arguments from their Miri representations to + /// a native form (through `libffi` call). + /// Then, convert the return value from the native form into something that + /// can be stored in Miri's internal memory. + fn call_native_fn( + &mut self, + link_name: Symbol, + dest: &MPlaceTy<'tcx, Provenance>, + args: &[OpTy<'tcx, Provenance>], + ) -> InterpResult<'tcx, bool> { + let this = self.eval_context_mut(); + // Get the pointer to the function in the shared object file if it exists. + let code_ptr = match this.get_func_ptr_explicitly_from_lib(link_name) { + Some(ptr) => ptr, + None => { + // Shared object file does not export this function -- try the shims next. + return Ok(false); + } + }; + + // Get the function arguments, and convert them to `libffi`-compatible form. + let mut libffi_args = Vec::::with_capacity(args.len()); + for arg in args.iter() { + if !matches!(arg.layout.abi, Abi::Scalar(_)) { + throw_unsup_format!("only scalar argument types are support for native calls") + } + libffi_args.push(imm_to_carg(this.read_immediate(arg)?, this)?); + } + + // Convert them to `libffi::high::Arg` type. + let libffi_args = libffi_args + .iter() + .map(|arg| arg.arg_downcast()) + .collect::>>(); + + // Call the function and store output, depending on return type in the function signature. + let ret = this.call_native_with_args(link_name, dest, code_ptr, libffi_args)?; + this.write_immediate(*ret, dest)?; + Ok(true) + } +} + +#[derive(Debug, Clone)] +/// Enum of supported arguments to external C functions. +// We introduce this enum instead of just calling `ffi::arg` and storing a list +// of `libffi::high::Arg` directly, because the `libffi::high::Arg` just wraps a reference +// to the value it represents: https://docs.rs/libffi/latest/libffi/high/call/struct.Arg.html +// and we need to store a copy of the value, and pass a reference to this copy to C instead. +enum CArg { + /// 8-bit signed integer. + Int8(i8), + /// 16-bit signed integer. + Int16(i16), + /// 32-bit signed integer. + Int32(i32), + /// 64-bit signed integer. + Int64(i64), + /// isize. + ISize(isize), + /// 8-bit unsigned integer. + UInt8(u8), + /// 16-bit unsigned integer. + UInt16(u16), + /// 32-bit unsigned integer. + UInt32(u32), + /// 64-bit unsigned integer. + UInt64(u64), + /// usize. + USize(usize), +} + +impl<'a> CArg { + /// Convert a `CArg` to a `libffi` argument type. + fn arg_downcast(&'a self) -> libffi::high::Arg<'a> { + match self { + CArg::Int8(i) => ffi::arg(i), + CArg::Int16(i) => ffi::arg(i), + CArg::Int32(i) => ffi::arg(i), + CArg::Int64(i) => ffi::arg(i), + CArg::ISize(i) => ffi::arg(i), + CArg::UInt8(i) => ffi::arg(i), + CArg::UInt16(i) => ffi::arg(i), + CArg::UInt32(i) => ffi::arg(i), + CArg::UInt64(i) => ffi::arg(i), + CArg::USize(i) => ffi::arg(i), + } + } +} + +/// Extract the scalar value from the result of reading a scalar from the machine, +/// and convert it to a `CArg`. +fn imm_to_carg<'tcx>( + v: ImmTy<'tcx, Provenance>, + cx: &impl HasDataLayout, +) -> InterpResult<'tcx, CArg> { + Ok(match v.layout.ty.kind() { + // If the primitive provided can be converted to a type matching the type pattern + // then create a `CArg` of this primitive value with the corresponding `CArg` constructor. + // the ints + ty::Int(IntTy::I8) => CArg::Int8(v.to_scalar().to_i8()?), + ty::Int(IntTy::I16) => CArg::Int16(v.to_scalar().to_i16()?), + ty::Int(IntTy::I32) => CArg::Int32(v.to_scalar().to_i32()?), + ty::Int(IntTy::I64) => CArg::Int64(v.to_scalar().to_i64()?), + ty::Int(IntTy::Isize) => + CArg::ISize(v.to_scalar().to_target_isize(cx)?.try_into().unwrap()), + // the uints + ty::Uint(UintTy::U8) => CArg::UInt8(v.to_scalar().to_u8()?), + ty::Uint(UintTy::U16) => CArg::UInt16(v.to_scalar().to_u16()?), + ty::Uint(UintTy::U32) => CArg::UInt32(v.to_scalar().to_u32()?), + ty::Uint(UintTy::U64) => CArg::UInt64(v.to_scalar().to_u64()?), + ty::Uint(UintTy::Usize) => + CArg::USize(v.to_scalar().to_target_usize(cx)?.try_into().unwrap()), + _ => throw_unsup_format!("unsupported argument type for native call: {}", v.layout.ty), + }) +} diff --git a/src/shims/os_str.rs b/src/shims/os_str.rs index 62ce2ee58a..5fcea9ced6 100644 --- a/src/shims/os_str.rs +++ b/src/shims/os_str.rs @@ -72,11 +72,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { u16vec_to_osstring(u16_vec) } - /// Helper function to write an OsStr as a null-terminated sequence of bytes, which is what - /// the Unix APIs usually handle. This function returns `Ok((false, length))` without trying - /// to write if `size` is not large enough to fit the contents of `os_string` plus a null - /// terminator. It returns `Ok((true, length))` if the writing process was successful. The - /// string length returned does include the null terminator. + /// Helper function to write an OsStr as a null-terminated sequence of bytes, which is what the + /// Unix APIs usually handle. Returns `(success, full_len)`, where length includes the null + /// terminator. On failure, nothing is written. fn write_os_str_to_c_str( &mut self, os_str: &OsStr, @@ -87,18 +85,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { self.eval_context_mut().write_c_str(bytes, ptr, size) } - /// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, which is what the - /// Windows APIs usually handle. - /// - /// If `truncate == false` (the usual mode of operation), this function returns `Ok((false, - /// length))` without trying to write if `size` is not large enough to fit the contents of - /// `os_string` plus a null terminator. It returns `Ok((true, length))` if the writing process - /// was successful. The string length returned does include the null terminator. Length is - /// measured in units of `u16.` - /// - /// If `truncate == true`, then in case `size` is not large enough it *will* write the first - /// `size.saturating_sub(1)` many items, followed by a null terminator (if `size > 0`). - fn write_os_str_to_wide_str( + /// Internal helper to share code between `write_os_str_to_wide_str` and + /// `write_os_str_to_wide_str_truncated`. + fn write_os_str_to_wide_str_helper( &mut self, os_str: &OsStr, ptr: Pointer>, @@ -132,11 +121,34 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Ok((written, size_needed)) } + /// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, which is what the + /// Windows APIs usually handle. Returns `(success, full_len)`, where length is measured + /// in units of `u16` and includes the null terminator. On failure, nothing is written. + fn write_os_str_to_wide_str( + &mut self, + os_str: &OsStr, + ptr: Pointer>, + size: u64, + ) -> InterpResult<'tcx, (bool, u64)> { + self.write_os_str_to_wide_str_helper(os_str, ptr, size, /*truncate*/ false) + } + + /// Like `write_os_str_to_wide_str`, but on failure as much as possible is written into + /// the buffer (always with a null terminator). + fn write_os_str_to_wide_str_truncated( + &mut self, + os_str: &OsStr, + ptr: Pointer>, + size: u64, + ) -> InterpResult<'tcx, (bool, u64)> { + self.write_os_str_to_wide_str_helper(os_str, ptr, size, /*truncate*/ true) + } + /// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of bytes. fn alloc_os_str_as_c_str( &mut self, os_str: &OsStr, - memkind: MemoryKind, + memkind: MemoryKind, ) -> InterpResult<'tcx, Pointer>> { let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0` terminator. let this = self.eval_context_mut(); @@ -152,16 +164,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn alloc_os_str_as_wide_str( &mut self, os_str: &OsStr, - memkind: MemoryKind, + memkind: MemoryKind, ) -> InterpResult<'tcx, Pointer>> { let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0x0000` terminator. let this = self.eval_context_mut(); let arg_type = Ty::new_array(this.tcx.tcx, this.tcx.types.u16, size); let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind)?; - let (written, _) = self - .write_os_str_to_wide_str(os_str, arg_place.ptr(), size, /*truncate*/ false) - .unwrap(); + let (written, _) = self.write_os_str_to_wide_str(os_str, arg_place.ptr(), size).unwrap(); assert!(written); Ok(arg_place.ptr()) } @@ -216,12 +226,25 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { path: &Path, ptr: Pointer>, size: u64, - truncate: bool, ) -> InterpResult<'tcx, (bool, u64)> { let this = self.eval_context_mut(); let os_str = this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget); - this.write_os_str_to_wide_str(&os_str, ptr, size, truncate) + this.write_os_str_to_wide_str(&os_str, ptr, size) + } + + /// Write a Path to the machine memory (as a null-terminated sequence of `u16`s), + /// adjusting path separators if needed. + fn write_path_to_wide_str_truncated( + &mut self, + path: &Path, + ptr: Pointer>, + size: u64, + ) -> InterpResult<'tcx, (bool, u64)> { + let this = self.eval_context_mut(); + let os_str = + this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget); + this.write_os_str_to_wide_str_truncated(&os_str, ptr, size) } /// Allocate enough memory to store a Path as a null-terminated sequence of bytes, @@ -229,7 +252,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn alloc_path_as_c_str( &mut self, path: &Path, - memkind: MemoryKind, + memkind: MemoryKind, ) -> InterpResult<'tcx, Pointer>> { let this = self.eval_context_mut(); let os_str = @@ -242,7 +265,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn alloc_path_as_wide_str( &mut self, path: &Path, - memkind: MemoryKind, + memkind: MemoryKind, ) -> InterpResult<'tcx, Pointer>> { let this = self.eval_context_mut(); let os_str = @@ -250,7 +273,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.alloc_os_str_as_wide_str(&os_str, memkind) } - #[allow(clippy::get_first)] fn convert_path<'a>( &self, os_str: Cow<'a, OsStr>, @@ -259,82 +281,97 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_ref(); let target_os = &this.tcx.sess.target.os; + /// Adjust a Windows path to Unix conventions such that it un-does everything that + /// `unix_to_windows` did, and such that if the Windows input path was absolute, then the + /// Unix output path is absolute. + fn windows_to_unix(path: &mut Vec) + where + T: From + Copy + Eq, + { + let sep = T::from(b'/'); + // Make sure all path separators are `/`. + for c in path.iter_mut() { + if *c == b'\\'.into() { + *c = sep; + } + } + // If this starts with `//?/`, it was probably produced by `unix_to_windows`` and we + // remove the `//?` that got added to get the Unix path back out. + if path.get(0..4) == Some(&[sep, sep, b'?'.into(), sep]) { + // Remove first 3 characters. It still starts with `/` so it is absolute on Unix. + path.splice(0..3, std::iter::empty()); + } + // If it starts with a drive letter (`X:/`), convert it to an absolute Unix path. + else if path.get(1..3) == Some(&[b':'.into(), sep]) { + // We add a `/` at the beginning, to store the absolute Windows + // path in something that looks like an absolute Unix path. + path.insert(0, sep); + } + } + + /// Adjust a Unix path to Windows conventions such that it un-does everything that + /// `windows_to_unix` did, and such that if the Unix input path was absolute, then the + /// Windows output path is absolute. + fn unix_to_windows(path: &mut Vec) + where + T: From + Copy + Eq, + { + let sep = T::from(b'\\'); + // Make sure all path separators are `\`. + for c in path.iter_mut() { + if *c == b'/'.into() { + *c = sep; + } + } + // If the path is `\X:\`, the leading separator was probably added by `windows_to_unix` + // and we should get rid of it again. + if path.get(2..4) == Some(&[b':'.into(), sep]) && path[0] == sep { + // The new path is still absolute on Windows. + path.remove(0); + } + // If this starts withs a `\` but not a `\\`, then this was absolute on Unix but is + // relative on Windows (relative to "the root of the current directory", e.g. the + // drive letter). + else if path.first() == Some(&sep) && path.get(1) != Some(&sep) { + // We add `\\?` so it starts with `\\?\` which is some magic path on Windows + // that *is* considered absolute. This way we store the absolute Unix path + // in something that looks like an absolute Windows path. + path.splice(0..0, [sep, sep, b'?'.into()]); + } + } + + // Below we assume that everything non-Windows works like Unix, at least + // when it comes to file system path conventions. #[cfg(windows)] return if target_os == "windows" { // Windows-on-Windows, all fine. os_str } else { // Unix target, Windows host. - let (from, to) = match direction { - PathConversion::HostToTarget => ('\\', '/'), - PathConversion::TargetToHost => ('/', '\\'), - }; - let mut converted = os_str - .encode_wide() - .map(|wchar| if wchar == from as u16 { to as u16 } else { wchar }) - .collect::>(); - // We also have to ensure that absolute paths remain absolute. + let mut path: Vec = os_str.encode_wide().collect(); match direction { PathConversion::HostToTarget => { - // If this is an absolute Windows path that starts with a drive letter (`C:/...` - // after separator conversion), it would not be considered absolute by Unix - // target code. - if converted.get(1).copied() == Some(b':' as u16) - && converted.get(2).copied() == Some(b'/' as u16) - { - // We add a `/` at the beginning, to store the absolute Windows - // path in something that looks like an absolute Unix path. - converted.insert(0, b'/' as u16); - } + windows_to_unix(&mut path); } PathConversion::TargetToHost => { - // If the path is `\C:\`, the leading backslash was probably added by the above code - // and we should get rid of it again. - if converted.get(0).copied() == Some(b'\\' as u16) - && converted.get(2).copied() == Some(b':' as u16) - && converted.get(3).copied() == Some(b'\\' as u16) - { - converted.remove(0); - } + unix_to_windows(&mut path); } } - Cow::Owned(OsString::from_wide(&converted)) + Cow::Owned(OsString::from_wide(&path)) }; #[cfg(unix)] return if target_os == "windows" { // Windows target, Unix host. - let (from, to) = match direction { - PathConversion::HostToTarget => (b'/', b'\\'), - PathConversion::TargetToHost => (b'\\', b'/'), - }; - let mut converted = os_str - .as_bytes() - .iter() - .map(|&wchar| if wchar == from { to } else { wchar }) - .collect::>(); - // We also have to ensure that absolute paths remain absolute. + let mut path: Vec = os_str.into_owned().into_encoded_bytes(); match direction { PathConversion::HostToTarget => { - // If this start withs a `\`, we add `\\?` so it starts with `\\?\` which is - // some magic path on Windows that *is* considered absolute. - if converted.get(0).copied() == Some(b'\\') { - converted.splice(0..0, b"\\\\?".iter().copied()); - } + unix_to_windows(&mut path); } PathConversion::TargetToHost => { - // If this starts with `//?/`, it was probably produced by the above code and we - // remove the `//?` that got added to get the Unix path back out. - if converted.get(0).copied() == Some(b'/') - && converted.get(1).copied() == Some(b'/') - && converted.get(2).copied() == Some(b'?') - && converted.get(3).copied() == Some(b'/') - { - // Remove first 3 characters - converted.splice(0..3, std::iter::empty()); - } + windows_to_unix(&mut path); } } - Cow::Owned(OsString::from_vec(converted)) + Cow::Owned(OsString::from_vec(path)) } else { // Unix-on-Unix, all is fine. os_str diff --git a/src/shims/panic.rs b/src/shims/panic.rs index bb31ef733c..4444d29746 100644 --- a/src/shims/panic.rs +++ b/src/shims/panic.rs @@ -30,7 +30,7 @@ pub struct CatchUnwindData<'tcx> { /// The return place from the original call to `try`. dest: MPlaceTy<'tcx, Provenance>, /// The return block from the original call to `try`. - ret: mir::BasicBlock, + ret: Option, } impl VisitProvenance for CatchUnwindData<'_> { @@ -73,7 +73,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { &mut self, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ret: mir::BasicBlock, + ret: Option, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); @@ -103,7 +103,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { &[data.into()], None, // Directly return to caller. - StackPopCleanup::Goto { ret: Some(ret), unwind: mir::UnwindAction::Continue }, + StackPopCleanup::Goto { ret, unwind: mir::UnwindAction::Continue }, )?; // We ourselves will return `0`, eventually (will be overwritten if we catch a panic). @@ -155,7 +155,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { None, // Directly return to caller of `try`. StackPopCleanup::Goto { - ret: Some(catch_unwind.ret), + ret: catch_unwind.ret, unwind: mir::UnwindAction::Continue, }, )?; diff --git a/src/shims/time.rs b/src/shims/time.rs index 4535bcf6df..8d1f07f916 100644 --- a/src/shims/time.rs +++ b/src/shims/time.rs @@ -1,5 +1,11 @@ +use std::ffi::{OsStr, OsString}; +use std::fmt::Write; +use std::str::FromStr; use std::time::{Duration, SystemTime}; +use chrono::{DateTime, Datelike, Offset, Timelike, Utc}; +use chrono_tz::Tz; + use crate::concurrency::thread::MachineCallback; use crate::*; @@ -107,6 +113,88 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Ok(0) } + // The localtime() function shall convert the time in seconds since the Epoch pointed to by + // timer into a broken-down time, expressed as a local time. + // https://linux.die.net/man/3/localtime_r + fn localtime_r( + &mut self, + timep: &OpTy<'tcx, Provenance>, + result_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + + this.assert_target_os_is_unix("localtime_r"); + this.check_no_isolation("`localtime_r`")?; + + let timep = this.deref_pointer(timep)?; + let result = this.deref_pointer_as(result_op, this.libc_ty_layout("tm"))?; + + // The input "represents the number of seconds elapsed since the Epoch, + // 1970-01-01 00:00:00 +0000 (UTC)". + let sec_since_epoch: i64 = this + .read_scalar(&timep)? + .to_int(this.libc_ty_layout("time_t").size)? + .try_into() + .unwrap(); + let dt_utc: DateTime = + DateTime::from_timestamp(sec_since_epoch, 0).expect("Invalid timestamp"); + + // Figure out what time zone is in use + let tz = this.get_env_var(OsStr::new("TZ"))?.unwrap_or_else(|| OsString::from("UTC")); + let tz = match tz.into_string() { + Ok(tz) => Tz::from_str(&tz).unwrap_or(Tz::UTC), + _ => Tz::UTC, + }; + + // Convert that to local time, then return the broken-down time value. + let dt: DateTime = dt_utc.with_timezone(&tz); + + // This value is always set to -1, because there is no way to know if dst is in effect with + // chrono crate yet. + // This may not be consistent with libc::localtime_r's result. + let tm_isdst = -1; + + // tm_zone represents the timezone value in the form of: +0730, +08, -0730 or -08. + // This may not be consistent with libc::localtime_r's result. + let offset_in_seconds = dt.offset().fix().local_minus_utc(); + let tm_gmtoff = offset_in_seconds; + let mut tm_zone = String::new(); + if offset_in_seconds < 0 { + tm_zone.push('-'); + } else { + tm_zone.push('+'); + } + let offset_hour = offset_in_seconds.abs() / 3600; + write!(tm_zone, "{:02}", offset_hour).unwrap(); + let offset_min = (offset_in_seconds.abs() % 3600) / 60; + if offset_min != 0 { + write!(tm_zone, "{:02}", offset_min).unwrap(); + } + + // FIXME: String de-duplication is needed so that we only allocate this string only once + // even when there are multiple calls to this function. + let tm_zone_ptr = + this.alloc_os_str_as_c_str(&OsString::from(tm_zone), MiriMemoryKind::Machine.into())?; + + this.write_pointer(tm_zone_ptr, &this.project_field_named(&result, "tm_zone")?)?; + this.write_int_fields_named( + &[ + ("tm_sec", dt.second().into()), + ("tm_min", dt.minute().into()), + ("tm_hour", dt.hour().into()), + ("tm_mday", dt.day().into()), + ("tm_mon", dt.month0().into()), + ("tm_year", dt.year().checked_sub(1900).unwrap().into()), + ("tm_wday", dt.weekday().num_days_from_sunday().into()), + ("tm_yday", dt.ordinal0().into()), + ("tm_isdst", tm_isdst), + ("tm_gmtoff", tm_gmtoff.into()), + ], + &result, + )?; + + Ok(result.ptr()) + } #[allow(non_snake_case, clippy::arithmetic_side_effects)] fn GetSystemTimeAsFileTime( &mut self, @@ -236,11 +324,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { .unwrap_or_else(|| now.checked_add(Duration::from_secs(3600)).unwrap()); let active_thread = this.get_active_thread(); - this.block_thread(active_thread); + this.block_thread(active_thread, BlockReason::Sleep); this.register_timeout_callback( active_thread, - Time::Monotonic(timeout_time), + CallbackTime::Monotonic(timeout_time), Box::new(UnblockCallback { thread_to_unblock: active_thread }), ); @@ -259,11 +347,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let timeout_time = this.machine.clock.now().checked_add(duration).unwrap(); let active_thread = this.get_active_thread(); - this.block_thread(active_thread); + this.block_thread(active_thread, BlockReason::Sleep); this.register_timeout_callback( active_thread, - Time::Monotonic(timeout_time), + CallbackTime::Monotonic(timeout_time), Box::new(UnblockCallback { thread_to_unblock: active_thread }), ); @@ -281,7 +369,7 @@ impl VisitProvenance for UnblockCallback { impl<'mir, 'tcx: 'mir> MachineCallback<'mir, 'tcx> for UnblockCallback { fn call(&self, ecx: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> { - ecx.unblock_thread(self.thread_to_unblock); + ecx.unblock_thread(self.thread_to_unblock, BlockReason::Sleep); Ok(()) } } diff --git a/src/shims/tls.rs b/src/shims/tls.rs index 7f929d9a91..0dec12f0b6 100644 --- a/src/shims/tls.rs +++ b/src/shims/tls.rs @@ -219,61 +219,76 @@ impl VisitProvenance for TlsData<'_> { } #[derive(Debug, Default)] -pub struct TlsDtorsState(TlsDtorsStatePriv); +pub struct TlsDtorsState<'tcx>(TlsDtorsStatePriv<'tcx>); #[derive(Debug, Default)] -enum TlsDtorsStatePriv { +enum TlsDtorsStatePriv<'tcx> { #[default] Init, PthreadDtors(RunningDtorState), + /// For Windows Dtors, we store the list of functions that we still have to call. + /// These are functions from the magic `.CRT$XLB` linker section. + WindowsDtors(Vec>), Done, } -impl TlsDtorsState { - pub fn on_stack_empty<'tcx>( +impl<'tcx> TlsDtorsState<'tcx> { + pub fn on_stack_empty( &mut self, this: &mut MiriInterpCx<'_, 'tcx>, ) -> InterpResult<'tcx, Poll<()>> { use TlsDtorsStatePriv::*; - match &mut self.0 { - Init => { - match this.tcx.sess.target.os.as_ref() { - "linux" | "freebsd" | "android" => { - // Run the pthread dtors. - self.0 = PthreadDtors(Default::default()); + let new_state = 'new_state: { + match &mut self.0 { + Init => { + match this.tcx.sess.target.os.as_ref() { + "macos" => { + // The macOS thread wide destructor runs "before any TLS slots get + // freed", so do that first. + this.schedule_macos_tls_dtor()?; + // When that destructor is done, go on with the pthread dtors. + break 'new_state PthreadDtors(Default::default()); + } + _ if this.target_os_is_unix() => { + // All other Unixes directly jump to running the pthread dtors. + break 'new_state PthreadDtors(Default::default()); + } + "windows" => { + // Determine which destructors to run. + let dtors = this.lookup_windows_tls_dtors()?; + // And move to the next state, that runs them. + break 'new_state WindowsDtors(dtors); + } + _ => { + // No TLS dtor support. + // FIXME: should we do something on wasi? + break 'new_state Done; + } } - "macos" => { - // The macOS thread wide destructor runs "before any TLS slots get - // freed", so do that first. - this.schedule_macos_tls_dtor()?; - // When the stack is empty again, go on with the pthread dtors. - self.0 = PthreadDtors(Default::default()); - } - "windows" => { - // Run the special magic hook. - this.schedule_windows_tls_dtors()?; - // And move to the final state. - self.0 = Done; + } + PthreadDtors(state) => { + match this.schedule_next_pthread_tls_dtor(state)? { + Poll::Pending => return Ok(Poll::Pending), // just keep going + Poll::Ready(()) => break 'new_state Done, } - _ => { - // No TLS dtor support. - // FIXME: should we do something on wasi? - self.0 = Done; + } + WindowsDtors(dtors) => { + if let Some(dtor) = dtors.pop() { + this.schedule_windows_tls_dtor(dtor)?; + return Ok(Poll::Pending); // we stay in this state (but `dtors` got shorter) + } else { + // No more destructors to run. + break 'new_state Done; } } - } - PthreadDtors(state) => { - match this.schedule_next_pthread_tls_dtor(state)? { - Poll::Pending => {} // just keep going - Poll::Ready(()) => self.0 = Done, + Done => { + this.machine.tls.delete_all_thread_tls(this.get_active_thread()); + return Ok(Poll::Ready(())); } } - Done => { - this.machine.tls.delete_all_thread_tls(this.get_active_thread()); - return Ok(Poll::Ready(())); - } - } + }; + self.0 = new_state; Ok(Poll::Pending) } } @@ -282,22 +297,19 @@ impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for crate::MiriInterpCx<'m trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { /// Schedule TLS destructors for Windows. /// On windows, TLS destructors are managed by std. - fn schedule_windows_tls_dtors(&mut self) -> InterpResult<'tcx> { + fn lookup_windows_tls_dtors(&mut self) -> InterpResult<'tcx, Vec>> { let this = self.eval_context_mut(); // Windows has a special magic linker section that is run on certain events. - // Instead of searching for that section and supporting arbitrary hooks in there - // (that would be basically https://github.com/rust-lang/miri/issues/450), - // we specifically look up the static in libstd that we know is placed - // in that section. - if !this.have_module(&["std"]) { - // Looks like we are running in a `no_std` crate. - // That also means no TLS dtors callback to call. - return Ok(()); - } - let thread_callback = - this.eval_windows("thread_local_key", "p_thread_callback").to_pointer(this)?; - let thread_callback = this.get_ptr_fn(thread_callback)?.as_instance()?; + // We don't support most of that, but just enough to make thread-local dtors in `std` work. + Ok(this.lookup_link_section(".CRT$XLB")?) + } + + fn schedule_windows_tls_dtor(&mut self, dtor: ImmTy<'tcx, Provenance>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let dtor = dtor.to_scalar().to_pointer(this)?; + let thread_callback = this.get_ptr_fn(dtor)?.as_instance()?; // FIXME: Technically, the reason should be `DLL_PROCESS_DETACH` when the main thread exits // but std treats both the same. @@ -305,7 +317,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // The signature of this function is `unsafe extern "system" fn(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID)`. // FIXME: `h` should be a handle to the current module and what `pv` should be is unknown - // but both are ignored by std + // but both are ignored by std. this.call_function( thread_callback, Abi::System { unwind: false }, diff --git a/src/shims/unix/env.rs b/src/shims/unix/env.rs new file mode 100644 index 0000000000..6fcfb69391 --- /dev/null +++ b/src/shims/unix/env.rs @@ -0,0 +1,300 @@ +use std::env; +use std::ffi::{OsStr, OsString}; +use std::io::ErrorKind; +use std::mem; + +use rustc_data_structures::fx::FxHashMap; +use rustc_middle::ty::layout::LayoutOf; +use rustc_middle::ty::Ty; +use rustc_target::abi::Size; + +use crate::*; + +pub struct UnixEnvVars<'tcx> { + /// Stores pointers to the environment variables. These variables must be stored as + /// null-terminated target strings (c_str or wide_str) with the `"{name}={value}"` format. + map: FxHashMap>>, + + /// Place where the `environ` static is stored. Lazily initialized, but then never changes. + environ: MPlaceTy<'tcx, Provenance>, +} + +impl VisitProvenance for UnixEnvVars<'_> { + fn visit_provenance(&self, visit: &mut VisitWith<'_>) { + let UnixEnvVars { map, environ } = self; + + environ.visit_provenance(visit); + for ptr in map.values() { + ptr.visit_provenance(visit); + } + } +} + +impl<'tcx> UnixEnvVars<'tcx> { + pub(crate) fn new<'mir>( + ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + env_vars: FxHashMap, + ) -> InterpResult<'tcx, Self> { + // Allocate memory for all these env vars. + let mut env_vars_machine = FxHashMap::default(); + for (name, val) in env_vars.into_iter() { + let ptr = alloc_env_var(ecx, &name, &val)?; + env_vars_machine.insert(name, ptr); + } + + // This is memory backing an extern static, hence `ExternStatic`, not `Env`. + let layout = ecx.machine.layouts.mut_raw_ptr; + let environ = ecx.allocate(layout, MiriMemoryKind::ExternStatic.into())?; + let environ_block = alloc_environ_block(ecx, env_vars_machine.values().copied().collect())?; + ecx.write_pointer(environ_block, &environ)?; + + Ok(UnixEnvVars { map: env_vars_machine, environ }) + } + + pub(crate) fn cleanup<'mir>( + ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + ) -> InterpResult<'tcx> { + // Deallocate individual env vars. + let env_vars = mem::take(&mut ecx.machine.env_vars.unix_mut().map); + for (_name, ptr) in env_vars { + ecx.deallocate_ptr(ptr, None, MiriMemoryKind::Runtime.into())?; + } + // Deallocate environ var list. + let environ = &ecx.machine.env_vars.unix().environ; + let old_vars_ptr = ecx.read_pointer(environ)?; + ecx.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?; + + Ok(()) + } + + pub(crate) fn environ(&self) -> Pointer> { + self.environ.ptr() + } + + fn get_ptr<'mir>( + &self, + ecx: &InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + name: &OsStr, + ) -> InterpResult<'tcx, Option>>> { + // We don't care about the value as we have the `map` to keep track of everything, + // but we do want to do this read so it shows up as a data race. + let _vars_ptr = ecx.read_pointer(&self.environ)?; + let Some(var_ptr) = self.map.get(name) else { + return Ok(None); + }; + // The offset is used to strip the "{name}=" part of the string. + let var_ptr = var_ptr.offset( + Size::from_bytes(u64::try_from(name.len()).unwrap().checked_add(1).unwrap()), + ecx, + )?; + Ok(Some(var_ptr)) + } + + /// Implementation detail for [`InterpCx::get_env_var`]. This basically does `getenv`, complete + /// with the reads of the environment, but returns an [`OsString`] instead of a pointer. + pub(crate) fn get<'mir>( + &self, + ecx: &InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + name: &OsStr, + ) -> InterpResult<'tcx, Option> { + let var_ptr = self.get_ptr(ecx, name)?; + if let Some(ptr) = var_ptr { + let var = ecx.read_os_str_from_c_str(ptr)?; + Ok(Some(var.to_owned())) + } else { + Ok(None) + } + } +} + +fn alloc_env_var<'mir, 'tcx>( + ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + name: &OsStr, + value: &OsStr, +) -> InterpResult<'tcx, Pointer>> { + let mut name_osstring = name.to_os_string(); + name_osstring.push("="); + name_osstring.push(value); + ecx.alloc_os_str_as_c_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into()) +} + +/// Allocates an `environ` block with the given list of pointers. +fn alloc_environ_block<'mir, 'tcx>( + ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + mut vars: Vec>>, +) -> InterpResult<'tcx, Pointer>> { + // Add trailing null. + vars.push(Pointer::null()); + // Make an array with all these pointers inside Miri. + let vars_layout = ecx.layout_of(Ty::new_array( + *ecx.tcx, + ecx.machine.layouts.mut_raw_ptr.ty, + u64::try_from(vars.len()).unwrap(), + ))?; + let vars_place = ecx.allocate(vars_layout, MiriMemoryKind::Runtime.into())?; + for (idx, var) in vars.into_iter().enumerate() { + let place = ecx.project_field(&vars_place, idx)?; + ecx.write_pointer(var, &place)?; + } + Ok(vars_place.ptr()) +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn getenv( + &mut self, + name_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("getenv"); + + let name_ptr = this.read_pointer(name_op)?; + let name = this.read_os_str_from_c_str(name_ptr)?; + + let var_ptr = this.machine.env_vars.unix().get_ptr(this, name)?; + Ok(var_ptr.unwrap_or_else(Pointer::null)) + } + + fn setenv( + &mut self, + name_op: &OpTy<'tcx, Provenance>, + value_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("setenv"); + + let name_ptr = this.read_pointer(name_op)?; + let value_ptr = this.read_pointer(value_op)?; + + let mut new = None; + if !this.ptr_is_null(name_ptr)? { + let name = this.read_os_str_from_c_str(name_ptr)?; + if !name.is_empty() && !name.to_string_lossy().contains('=') { + let value = this.read_os_str_from_c_str(value_ptr)?; + new = Some((name.to_owned(), value.to_owned())); + } + } + if let Some((name, value)) = new { + let var_ptr = alloc_env_var(this, &name, &value)?; + if let Some(var) = this.machine.env_vars.unix_mut().map.insert(name, var_ptr) { + this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; + } + this.update_environ()?; + Ok(0) // return zero on success + } else { + // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character. + let einval = this.eval_libc("EINVAL"); + this.set_last_error(einval)?; + Ok(-1) + } + } + + fn unsetenv(&mut self, name_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("unsetenv"); + + let name_ptr = this.read_pointer(name_op)?; + let mut success = None; + if !this.ptr_is_null(name_ptr)? { + let name = this.read_os_str_from_c_str(name_ptr)?.to_owned(); + if !name.is_empty() && !name.to_string_lossy().contains('=') { + success = Some(this.machine.env_vars.unix_mut().map.remove(&name)); + } + } + if let Some(old) = success { + if let Some(var) = old { + this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; + } + this.update_environ()?; + Ok(0) + } else { + // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character. + let einval = this.eval_libc("EINVAL"); + this.set_last_error(einval)?; + Ok(-1) + } + } + + fn getcwd( + &mut self, + buf_op: &OpTy<'tcx, Provenance>, + size_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("getcwd"); + + let buf = this.read_pointer(buf_op)?; + let size = this.read_target_usize(size_op)?; + + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`getcwd`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; + return Ok(Pointer::null()); + } + + // If we cannot get the current directory, we return null + match env::current_dir() { + Ok(cwd) => { + if this.write_path_to_c_str(&cwd, buf, size)?.0 { + return Ok(buf); + } + let erange = this.eval_libc("ERANGE"); + this.set_last_error(erange)?; + } + Err(e) => this.set_last_error_from_io_error(e)?, + } + + Ok(Pointer::null()) + } + + fn chdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("chdir"); + + let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?; + + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`chdir`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; + + return Ok(-1); + } + + match env::set_current_dir(path) { + Ok(()) => Ok(0), + Err(e) => { + this.set_last_error_from_io_error(e)?; + Ok(-1) + } + } + } + + /// Updates the `environ` static. + fn update_environ(&mut self) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + // Deallocate the old environ list. + let environ = this.machine.env_vars.unix().environ.clone(); + let old_vars_ptr = this.read_pointer(&environ)?; + this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?; + + // Write the new list. + let vals = this.machine.env_vars.unix().map.values().copied().collect(); + let environ_block = alloc_environ_block(this, vals)?; + this.write_pointer(environ_block, &environ)?; + + Ok(()) + } + + fn getpid(&mut self) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("getpid"); + + this.check_no_isolation("`getpid`")?; + + // The reason we need to do this wacky of a conversion is because + // `libc::getpid` returns an i32, however, `std::process::id()` return an u32. + // So we un-do the conversion that stdlib does and turn it back into an i32. + #[allow(clippy::cast_possible_wrap)] + Ok(std::process::id() as i32) + } +} diff --git a/src/shims/unix/fd.rs b/src/shims/unix/fd.rs index a5fe38b902..a53cd607ef 100644 --- a/src/shims/unix/fd.rs +++ b/src/shims/unix/fd.rs @@ -2,8 +2,10 @@ //! standard file descriptors (stdin/stdout/stderr). use std::any::Any; +use std::cell::{Ref, RefCell, RefMut}; use std::collections::BTreeMap; use std::io::{self, ErrorKind, IsTerminal, Read, SeekFrom, Write}; +use std::rc::Rc; use rustc_middle::ty::TyCtxt; use rustc_target::abi::Size; @@ -12,9 +14,10 @@ use crate::shims::unix::*; use crate::*; /// Represents an open file descriptor. -pub trait FileDescriptor: std::fmt::Debug + Any { +pub trait FileDescription: std::fmt::Debug + Any { fn name(&self) -> &'static str; + /// Reads as much as possible into the given buffer, and returns the number of bytes read. fn read<'tcx>( &mut self, _communicate_allowed: bool, @@ -24,8 +27,9 @@ pub trait FileDescriptor: std::fmt::Debug + Any { throw_unsup_format!("cannot read from {}", self.name()); } + /// Writes as much as possible from the given buffer, and returns the number of bytes written. fn write<'tcx>( - &self, + &mut self, _communicate_allowed: bool, _bytes: &[u8], _tcx: TyCtxt<'tcx>, @@ -33,6 +37,8 @@ pub trait FileDescriptor: std::fmt::Debug + Any { throw_unsup_format!("cannot write to {}", self.name()); } + /// Seeks to the given offset (which can be relative to the beginning, end, or current position). + /// Returns the new position from the start of the stream. fn seek<'tcx>( &mut self, _communicate_allowed: bool, @@ -44,13 +50,10 @@ pub trait FileDescriptor: std::fmt::Debug + Any { fn close<'tcx>( self: Box, _communicate_allowed: bool, - ) -> InterpResult<'tcx, io::Result> { + ) -> InterpResult<'tcx, io::Result<()>> { throw_unsup_format!("cannot close {}", self.name()); } - /// Return a new file descriptor *that refers to the same underlying object*. - fn dup(&mut self) -> io::Result>; - fn is_tty(&self, _communicate_allowed: bool) -> bool { // Most FDs are not tty's and the consequence of a wrong `false` are minor, // so we use a default impl here. @@ -58,7 +61,7 @@ pub trait FileDescriptor: std::fmt::Debug + Any { } } -impl dyn FileDescriptor { +impl dyn FileDescription { #[inline(always)] pub fn downcast_ref(&self) -> Option<&T> { (self as &dyn Any).downcast_ref() @@ -70,7 +73,7 @@ impl dyn FileDescriptor { } } -impl FileDescriptor for io::Stdin { +impl FileDescription for io::Stdin { fn name(&self) -> &'static str { "stdin" } @@ -88,28 +91,24 @@ impl FileDescriptor for io::Stdin { Ok(Read::read(self, bytes)) } - fn dup(&mut self) -> io::Result> { - Ok(Box::new(io::stdin())) - } - fn is_tty(&self, communicate_allowed: bool) -> bool { communicate_allowed && self.is_terminal() } } -impl FileDescriptor for io::Stdout { +impl FileDescription for io::Stdout { fn name(&self) -> &'static str { "stdout" } fn write<'tcx>( - &self, + &mut self, _communicate_allowed: bool, bytes: &[u8], _tcx: TyCtxt<'tcx>, ) -> InterpResult<'tcx, io::Result> { // We allow writing to stderr even with isolation enabled. - let result = Write::write(&mut { self }, bytes); + let result = Write::write(self, bytes); // Stdout is buffered, flush to make sure it appears on the // screen. This is the write() syscall of the interpreted // program, we want it to correspond to a write() syscall on @@ -120,22 +119,18 @@ impl FileDescriptor for io::Stdout { Ok(result) } - fn dup(&mut self) -> io::Result> { - Ok(Box::new(io::stdout())) - } - fn is_tty(&self, communicate_allowed: bool) -> bool { communicate_allowed && self.is_terminal() } } -impl FileDescriptor for io::Stderr { +impl FileDescription for io::Stderr { fn name(&self) -> &'static str { "stderr" } fn write<'tcx>( - &self, + &mut self, _communicate_allowed: bool, bytes: &[u8], _tcx: TyCtxt<'tcx>, @@ -145,10 +140,6 @@ impl FileDescriptor for io::Stderr { Ok(Write::write(&mut { self }, bytes)) } - fn dup(&mut self) -> io::Result> { - Ok(Box::new(io::stderr())) - } - fn is_tty(&self, communicate_allowed: bool) -> bool { communicate_allowed && self.is_terminal() } @@ -158,13 +149,13 @@ impl FileDescriptor for io::Stderr { #[derive(Debug)] pub struct NullOutput; -impl FileDescriptor for NullOutput { +impl FileDescription for NullOutput { fn name(&self) -> &'static str { "stderr and stdout" } fn write<'tcx>( - &self, + &mut self, _communicate_allowed: bool, bytes: &[u8], _tcx: TyCtxt<'tcx>, @@ -172,16 +163,30 @@ impl FileDescriptor for NullOutput { // We just don't write anything, but report to the user that we did. Ok(Ok(bytes.len())) } +} + +#[derive(Clone, Debug)] +pub struct FileDescriptor(Rc>>); + +impl FileDescriptor { + pub fn new(fd: T) -> Self { + FileDescriptor(Rc::new(RefCell::new(Box::new(fd)))) + } - fn dup(&mut self) -> io::Result> { - Ok(Box::new(NullOutput)) + pub fn close<'ctx>(self, communicate_allowed: bool) -> InterpResult<'ctx, io::Result<()>> { + // Destroy this `Rc` using `into_inner` so we can call `close` instead of + // implicitly running the destructor of the file description. + match Rc::into_inner(self.0) { + Some(fd) => RefCell::into_inner(fd).close(communicate_allowed), + None => Ok(Ok(())), + } } } /// The file descriptor table #[derive(Debug)] pub struct FdTable { - pub fds: BTreeMap>, + pub fds: BTreeMap, } impl VisitProvenance for FdTable { @@ -192,28 +197,24 @@ impl VisitProvenance for FdTable { impl FdTable { pub(crate) fn new(mute_stdout_stderr: bool) -> FdTable { - let mut fds: BTreeMap<_, Box> = BTreeMap::new(); - fds.insert(0i32, Box::new(io::stdin())); + let mut fds: BTreeMap<_, FileDescriptor> = BTreeMap::new(); + fds.insert(0i32, FileDescriptor::new(io::stdin())); if mute_stdout_stderr { - fds.insert(1i32, Box::new(NullOutput)); - fds.insert(2i32, Box::new(NullOutput)); + fds.insert(1i32, FileDescriptor::new(NullOutput)); + fds.insert(2i32, FileDescriptor::new(NullOutput)); } else { - fds.insert(1i32, Box::new(io::stdout())); - fds.insert(2i32, Box::new(io::stderr())); + fds.insert(1i32, FileDescriptor::new(io::stdout())); + fds.insert(2i32, FileDescriptor::new(io::stderr())); } FdTable { fds } } - pub fn insert_fd(&mut self, file_handle: Box) -> i32 { + pub fn insert_fd(&mut self, file_handle: FileDescriptor) -> i32 { self.insert_fd_with_min_fd(file_handle, 0) } /// Insert a new FD that is at least `min_fd`. - pub fn insert_fd_with_min_fd( - &mut self, - file_handle: Box, - min_fd: i32, - ) -> i32 { + pub fn insert_fd_with_min_fd(&mut self, file_handle: FileDescriptor, min_fd: i32) -> i32 { // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in // between used FDs, the find_map combinator will return it. If the first such unused FD // is after all other used FDs, the find_map combinator will return None, and we will use @@ -239,15 +240,22 @@ impl FdTable { new_fd } - pub fn get(&self, fd: i32) -> Option<&dyn FileDescriptor> { - Some(&**self.fds.get(&fd)?) + pub fn get(&self, fd: i32) -> Option> { + let fd = self.fds.get(&fd)?; + Some(Ref::map(fd.0.borrow(), |fd| fd.as_ref())) + } + + pub fn get_mut(&self, fd: i32) -> Option> { + let fd = self.fds.get(&fd)?; + Some(RefMut::map(fd.0.borrow_mut(), |fd| fd.as_mut())) } - pub fn get_mut(&mut self, fd: i32) -> Option<&mut dyn FileDescriptor> { - Some(&mut **self.fds.get_mut(&fd)?) + pub fn dup(&self, fd: i32) -> Option { + let fd = self.fds.get(&fd)?; + Some(fd.clone()) } - pub fn remove(&mut self, fd: i32) -> Option> { + pub fn remove(&mut self, fd: i32) -> Option { self.fds.remove(&fd) } @@ -296,24 +304,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } let start = this.read_scalar(&args[2])?.to_i32()?; - match this.machine.fds.get_mut(fd) { - Some(file_descriptor) => { - let dup_result = file_descriptor.dup(); - match dup_result { - Ok(dup_fd) => Ok(this.machine.fds.insert_fd_with_min_fd(dup_fd, start)), - Err(e) => { - this.set_last_error_from_io_error(e.kind())?; - Ok(-1) - } - } - } + match this.machine.fds.dup(fd) { + Some(dup_fd) => Ok(this.machine.fds.insert_fd_with_min_fd(dup_fd, start)), None => this.fd_not_found(), } } else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC") { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`fcntl`", reject_with)?; - this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; return Ok(-1); } @@ -330,6 +329,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Ok(Scalar::from_i32(if let Some(file_descriptor) = this.machine.fds.remove(fd) { let result = file_descriptor.close(this.machine.communicate())?; + // return `0` if close is successful + let result = result.map(|()| 0i32); this.try_unwrap_io_result(result)? } else { this.fd_not_found()? @@ -369,32 +370,33 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { .min(u64::try_from(isize::MAX).unwrap()); let communicate = this.machine.communicate(); - if let Some(file_descriptor) = this.machine.fds.get_mut(fd) { - trace!("read: FD mapped to {:?}", file_descriptor); - // We want to read at most `count` bytes. We are sure that `count` is not negative - // because it was a target's `usize`. Also we are sure that its smaller than - // `usize::MAX` because it is bounded by the host's `isize`. - let mut bytes = vec![0; usize::try_from(count).unwrap()]; - // `File::read` never returns a value larger than `count`, - // so this cannot fail. - let result = file_descriptor - .read(communicate, &mut bytes, *this.tcx)? - .map(|c| i64::try_from(c).unwrap()); - - match result { - Ok(read_bytes) => { - // If reading to `bytes` did not fail, we write those bytes to the buffer. - this.write_bytes_ptr(buf, bytes)?; - Ok(read_bytes) - } - Err(e) => { - this.set_last_error_from_io_error(e.kind())?; - Ok(-1) - } - } - } else { + let Some(mut file_descriptor) = this.machine.fds.get_mut(fd) else { trace!("read: FD not found"); - this.fd_not_found() + return this.fd_not_found(); + }; + + trace!("read: FD mapped to {:?}", file_descriptor); + // We want to read at most `count` bytes. We are sure that `count` is not negative + // because it was a target's `usize`. Also we are sure that its smaller than + // `usize::MAX` because it is bounded by the host's `isize`. + let mut bytes = vec![0; usize::try_from(count).unwrap()]; + // `File::read` never returns a value larger than `count`, + // so this cannot fail. + let result = file_descriptor + .read(communicate, &mut bytes, *this.tcx)? + .map(|c| i64::try_from(c).unwrap()); + drop(file_descriptor); + + match result { + Ok(read_bytes) => { + // If reading to `bytes` did not fail, we write those bytes to the buffer. + this.write_bytes_ptr(buf, bytes)?; + Ok(read_bytes) + } + Err(e) => { + this.set_last_error_from_io_error(e)?; + Ok(-1) + } } } @@ -418,14 +420,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { .min(u64::try_from(isize::MAX).unwrap()); let communicate = this.machine.communicate(); - if let Some(file_descriptor) = this.machine.fds.get(fd) { - let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?; - let result = file_descriptor - .write(communicate, bytes, *this.tcx)? - .map(|c| i64::try_from(c).unwrap()); - this.try_unwrap_io_result(result) - } else { - this.fd_not_found() - } + let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?.to_owned(); + let Some(mut file_descriptor) = this.machine.fds.get_mut(fd) else { + return this.fd_not_found(); + }; + + let result = file_descriptor + .write(communicate, &bytes, *this.tcx)? + .map(|c| i64::try_from(c).unwrap()); + drop(file_descriptor); + + this.try_unwrap_io_result(result) } } diff --git a/src/shims/unix/foreign_items.rs b/src/shims/unix/foreign_items.rs index b1e1aec588..5434951d9e 100644 --- a/src/shims/unix/foreign_items.rs +++ b/src/shims/unix/foreign_items.rs @@ -6,20 +6,16 @@ use rustc_span::Symbol; use rustc_target::abi::{Align, Size}; use rustc_target::spec::abi::Abi; +use crate::shims::alloc::EvalContextExt as _; +use crate::shims::unix::*; use crate::*; -use shims::foreign_items::EmulateForeignItemResult; -use shims::unix::fd::EvalContextExt as _; -use shims::unix::fs::EvalContextExt as _; -use shims::unix::mem::EvalContextExt as _; -use shims::unix::socket::EvalContextExt as _; -use shims::unix::sync::EvalContextExt as _; -use shims::unix::thread::EvalContextExt as _; use shims::unix::freebsd::foreign_items as freebsd; use shims::unix::linux::foreign_items as linux; use shims::unix::macos::foreign_items as macos; +use shims::unix::solarish::foreign_items as solarish; -fn is_dyn_sym(name: &str, target_os: &str) -> bool { +pub fn is_dyn_sym(name: &str, target_os: &str) -> bool { match name { // Used for tests. "isatty" => true, @@ -27,13 +23,14 @@ fn is_dyn_sym(name: &str, target_os: &str) -> bool { // well allow it in `dlsym`. "signal" => true, // needed at least on macOS to avoid file-based fallback in getrandom - "getentropy" => true, + "getentropy" | "getrandom" => true, // Give specific OSes a chance to allow their symbols. _ => match target_os { "freebsd" => freebsd::is_dyn_sym(name), "linux" => linux::is_dyn_sym(name), "macos" => macos::is_dyn_sym(name), + "solaris" | "illumos" => solarish::is_dyn_sym(name), _ => false, }, } @@ -47,7 +44,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { abi: Abi, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, EmulateForeignItemResult> { + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern. @@ -238,6 +235,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let result = this.gettimeofday(tv, tz)?; this.write_scalar(Scalar::from_i32(result), dest)?; } + "localtime_r" => { + let [timep, result_op] = this.check_shim(abi, Abi::C {unwind: false}, link_name, args)?; + let result = this.localtime_r(timep, result_op)?; + this.write_pointer(result, dest)?; + } "clock_gettime" => { let [clk_id, tp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; @@ -257,16 +259,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let einval = this.eval_libc_i32("EINVAL"); this.write_int(einval, dest)?; } else { - if size == 0 { - this.write_null(&ret)?; - } else { - let ptr = this.allocate_ptr( - Size::from_bytes(size), - Align::from_bytes(align).unwrap(), - MiriMemoryKind::C.into(), - )?; - this.write_pointer(ptr, &ret)?; - } + let ptr = this.allocate_ptr( + Size::from_bytes(size), + Align::from_bytes(align).unwrap(), + MiriMemoryKind::C.into(), + )?; + this.write_pointer(ptr, &ret)?; this.write_null(dest)?; } } @@ -308,7 +306,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_null(dest)?; } Some(len) => { - let res = this.realloc(ptr, len, MiriMemoryKind::C)?; + let res = this.realloc(ptr, len)?; this.write_pointer(res, dest)?; } } @@ -376,8 +374,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { .builtin_deref(true) .ok_or_else(|| err_ub_format!( "wrong signature used for `pthread_key_create`: first argument must be a raw pointer." - ))? - .ty; + ))?; let key_layout = this.layout_of(key_type)?; // Create key and write it into the memory where `key_ptr` wants it. @@ -488,6 +485,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let result = this.pthread_condattr_init(attr)?; this.write_scalar(Scalar::from_i32(result), dest)?; } + "pthread_condattr_setclock" => { + let [attr, clock_id] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_condattr_setclock(attr, clock_id)?; + this.write_scalar(result, dest)?; + } + "pthread_condattr_getclock" => { + let [attr, clock_id] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_condattr_getclock(attr, clock_id)?; + this.write_scalar(result, dest)?; + } "pthread_condattr_destroy" => { let [attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let result = this.pthread_condattr_destroy(attr)?; @@ -591,8 +600,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } "getentropy" => { // This function is non-standard but exists with the same signature and behavior on - // Linux, macOS, and FreeBSD. - if !matches!(&*this.tcx.sess.target.os, "linux" | "macos" | "freebsd") { + // Linux, macOS, FreeBSD and Solaris/Illumos. + if !matches!(&*this.tcx.sess.target.os, "linux" | "macos" | "freebsd" | "illumos" | "solaris") { throw_unsup_format!( "`getentropy` is not supported on {}", this.tcx.sess.target.os @@ -608,6 +617,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // FreeBSD: https://man.freebsd.org/cgi/man.cgi?query=getentropy&sektion=3&format=html // Linux: https://man7.org/linux/man-pages/man3/getentropy.3.html // macOS: https://keith.github.io/xcode-man-pages/getentropy.2.html + // Solaris/Illumos: https://illumos.org/man/3C/getentropy if bufsize > 256 { let err = this.eval_libc("EIO"); this.set_last_error(err)?; @@ -617,6 +627,24 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_scalar(Scalar::from_i32(0), dest)?; } } + "getrandom" => { + // This function is non-standard but exists with the same signature and behavior on + // Linux, FreeBSD and Solaris/Illumos. + if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "illumos" | "solaris") { + throw_unsup_format!( + "`getentropy` is not supported on {}", + this.tcx.sess.target.os + ); + } + let [ptr, len, flags] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let ptr = this.read_pointer(ptr)?; + let len = this.read_target_usize(len)?; + let _flags = this.read_scalar(flags)?.to_i32()?; + // We ignore the flags, just always use the same PRNG / host RNG. + this.gen_random(ptr, len)?; + this.write_scalar(Scalar::from_target_usize(len, this), dest)?; + } // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. @@ -686,8 +714,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_int(super::UID, dest)?; } - "getpwuid_r" + "getpwuid_r" | "__posix_getpwuid_r" if this.frame_in_std() => { + // getpwuid_r is the standard name, __posix_getpwuid_r is used on solarish let [uid, pwd, buf, buflen, result] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; this.check_no_isolation("`getpwuid_r`")?; @@ -723,25 +752,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } - "sched_getaffinity" => { - // FreeBSD supports it as well since 13.1 (as a wrapper of cpuset_getaffinity) - if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd") { - throw_unsup_format!( - "`sched_getaffinity` is not supported on {}", - this.tcx.sess.target.os - ); - } - let [pid, cpusetsize, mask] = - this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - this.read_scalar(pid)?.to_i32()?; - this.read_target_usize(cpusetsize)?; - this.deref_pointer_as(mask, this.libc_ty_layout("cpu_set_t"))?; - // FIXME: we just return an error; `num_cpus` then falls back to `sysconf`. - let einval = this.eval_libc("EINVAL"); - this.set_last_error(einval)?; - this.write_scalar(Scalar::from_i32(-1), dest)?; - } - // Platform-specific shims _ => { let target_os = &*this.tcx.sess.target.os; @@ -749,11 +759,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { "freebsd" => freebsd::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest), "linux" => linux::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest), "macos" => macos::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest), - _ => Ok(EmulateForeignItemResult::NotSupported), + "solaris" | "illumos" => solarish::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest), + _ => Ok(EmulateItemResult::NotSupported), }; } }; - Ok(EmulateForeignItemResult::NeedsJumping) + Ok(EmulateItemResult::NeedsJumping) } } diff --git a/src/shims/unix/freebsd/foreign_items.rs b/src/shims/unix/freebsd/foreign_items.rs index 6814e0d428..e70cd35dda 100644 --- a/src/shims/unix/freebsd/foreign_items.rs +++ b/src/shims/unix/freebsd/foreign_items.rs @@ -1,10 +1,8 @@ use rustc_span::Symbol; use rustc_target::spec::abi::Abi; +use crate::shims::unix::*; use crate::*; -use shims::foreign_items::EmulateForeignItemResult; -use shims::unix::fs::EvalContextExt as _; -use shims::unix::thread::EvalContextExt as _; pub fn is_dyn_sym(_name: &str) -> bool { false @@ -18,15 +16,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { abi: Abi, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, EmulateForeignItemResult> { + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); match link_name.as_str() { // Threading - "pthread_attr_get_np" if this.frame_in_std() => { - let [_thread, _attr] = - this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - this.write_null(dest)?; - } "pthread_set_name_np" => { let [thread, name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; @@ -48,21 +41,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.read_scalar(len)?, )?; } - "getrandom" => { - let [ptr, len, flags] = - this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let ptr = this.read_pointer(ptr)?; - let len = this.read_target_usize(len)?; - let _flags = this.read_scalar(flags)?.to_i32()?; - // flags on freebsd does not really matter - // in practice, GRND_RANDOM does not particularly draw from /dev/random - // since it is the same as to /dev/urandom. - // GRND_INSECURE is only an alias of GRND_NONBLOCK, which - // does not affect the RNG. - // https://man.freebsd.org/cgi/man.cgi?query=getrandom&sektion=2&n=1 - this.gen_random(ptr, len)?; - this.write_scalar(Scalar::from_target_usize(len, this), dest)?; - } // File related shims // For those, we both intercept `func` and `call@FBSD_1.0` symbols cases @@ -91,15 +69,23 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_scalar(result, dest)?; } - // errno + // Miscellaneous "__error" => { let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } - _ => return Ok(EmulateForeignItemResult::NotSupported), + // Incomplete shims that we "stub out" just to get pre-main initialization code to work. + // These shims are enabled only when the caller is in the standard library. + "pthread_attr_get_np" if this.frame_in_std() => { + let [_thread, _attr] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.write_null(dest)?; + } + + _ => return Ok(EmulateItemResult::NotSupported), } - Ok(EmulateForeignItemResult::NeedsJumping) + Ok(EmulateItemResult::NeedsJumping) } } diff --git a/src/shims/unix/fs.rs b/src/shims/unix/fs.rs index 31076fdfaf..eb241556a5 100644 --- a/src/shims/unix/fs.rs +++ b/src/shims/unix/fs.rs @@ -17,15 +17,17 @@ use crate::shims::unix::*; use crate::*; use shims::time::system_time_to_duration; +use self::fd::FileDescriptor; + #[derive(Debug)] struct FileHandle { file: File, writable: bool, } -impl FileDescriptor for FileHandle { +impl FileDescription for FileHandle { fn name(&self) -> &'static str { - "FILE" + "file" } fn read<'tcx>( @@ -39,13 +41,13 @@ impl FileDescriptor for FileHandle { } fn write<'tcx>( - &self, + &mut self, communicate_allowed: bool, bytes: &[u8], _tcx: TyCtxt<'tcx>, ) -> InterpResult<'tcx, io::Result> { assert!(communicate_allowed, "isolation should have prevented even opening a file"); - Ok((&mut &self.file).write(bytes)) + Ok(self.file.write(bytes)) } fn seek<'tcx>( @@ -60,16 +62,14 @@ impl FileDescriptor for FileHandle { fn close<'tcx>( self: Box, communicate_allowed: bool, - ) -> InterpResult<'tcx, io::Result> { + ) -> InterpResult<'tcx, io::Result<()>> { assert!(communicate_allowed, "isolation should have prevented even opening a file"); // We sync the file if it was opened in a mode different than read-only. if self.writable { // `File::sync_all` does the checks that are done when closing a file. We do this to // to handle possible errors correctly. - let result = self.file.sync_all().map(|_| 0i32); - // Now we actually close the file. - drop(self); - // And return the result. + let result = self.file.sync_all(); + // Now we actually close the file and return the result. Ok(result) } else { // We drop the file, this closes it but ignores any errors @@ -78,16 +78,10 @@ impl FileDescriptor for FileHandle { // `/dev/urandom` which are read-only. Check // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439 // for a deeper discussion. - drop(self); - Ok(Ok(0)) + Ok(Ok(())) } } - fn dup(&mut self) -> io::Result> { - let duplicated = self.file.try_clone()?; - Ok(Box::new(FileHandle { file: duplicated, writable: self.writable })) - } - fn is_tty(&self, communicate_allowed: bool) -> bool { communicate_allowed && self.file.is_terminal() } @@ -143,37 +137,28 @@ trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx &mut self, file_type: std::io::Result, ) -> InterpResult<'tcx, i32> { + #[cfg(unix)] + use std::os::unix::fs::FileTypeExt; + let this = self.eval_context_mut(); match file_type { Ok(file_type) => { - if file_type.is_dir() { - Ok(this.eval_libc("DT_DIR").to_u8()?.into()) - } else if file_type.is_file() { - Ok(this.eval_libc("DT_REG").to_u8()?.into()) - } else if file_type.is_symlink() { - Ok(this.eval_libc("DT_LNK").to_u8()?.into()) - } else { + match () { + _ if file_type.is_dir() => Ok(this.eval_libc("DT_DIR").to_u8()?.into()), + _ if file_type.is_file() => Ok(this.eval_libc("DT_REG").to_u8()?.into()), + _ if file_type.is_symlink() => Ok(this.eval_libc("DT_LNK").to_u8()?.into()), // Certain file types are only supported when the host is a Unix system. - // (i.e. devices and sockets) If it is, check those cases, if not, fall back to - // DT_UNKNOWN sooner. - #[cfg(unix)] - { - use std::os::unix::fs::FileTypeExt; - if file_type.is_block_device() { - Ok(this.eval_libc("DT_BLK").to_u8()?.into()) - } else if file_type.is_char_device() { - Ok(this.eval_libc("DT_CHR").to_u8()?.into()) - } else if file_type.is_fifo() { - Ok(this.eval_libc("DT_FIFO").to_u8()?.into()) - } else if file_type.is_socket() { - Ok(this.eval_libc("DT_SOCK").to_u8()?.into()) - } else { - Ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()) - } - } - #[cfg(not(unix))] - Ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()) + _ if file_type.is_block_device() => + Ok(this.eval_libc("DT_BLK").to_u8()?.into()), + #[cfg(unix)] + _ if file_type.is_char_device() => Ok(this.eval_libc("DT_CHR").to_u8()?.into()), + #[cfg(unix)] + _ if file_type.is_fifo() => Ok(this.eval_libc("DT_FIFO").to_u8()?.into()), + #[cfg(unix)] + _ if file_type.is_socket() => Ok(this.eval_libc("DT_SOCK").to_u8()?.into()), + // Fallback + _ => Ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()), } } Err(e) => @@ -196,13 +181,12 @@ struct OpenDir { read_dir: ReadDir, /// The most recent entry returned by readdir(). /// Will be freed by the next call. - entry: Pointer>, + entry: Option>>, } impl OpenDir { fn new(read_dir: ReadDir) -> Self { - // We rely on `free` being a NOP on null pointers. - Self { read_dir, entry: Pointer::null() } + Self { read_dir, entry: None } } } @@ -394,13 +378,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`open`", reject_with)?; - this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; return Ok(-1); } let fd = options.open(path).map(|file| { let fh = &mut this.machine.fds; - fh.insert_fd(Box::new(FileHandle { file, writable })) + fh.insert_fd(FileDescriptor::new(FileHandle { file, writable })) }); this.try_unwrap_io_result(fd) @@ -429,14 +413,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { }; let communicate = this.machine.communicate(); - Ok(Scalar::from_i64(if let Some(file_descriptor) = this.machine.fds.get_mut(fd) { - let result = file_descriptor - .seek(communicate, seek_from)? - .map(|offset| i64::try_from(offset).unwrap()); - this.try_unwrap_io_result(result)? - } else { - this.fd_not_found()? - })) + + let Some(mut file_descriptor) = this.machine.fds.get_mut(fd) else { + return Ok(Scalar::from_i64(this.fd_not_found()?)); + }; + let result = file_descriptor + .seek(communicate, seek_from)? + .map(|offset| i64::try_from(offset).unwrap()); + drop(file_descriptor); + + let result = this.try_unwrap_io_result(result)?; + Ok(Scalar::from_i64(result)) } fn unlink(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { @@ -447,7 +434,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`unlink`", reject_with)?; - this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; return Ok(-1); } @@ -478,7 +465,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`symlink`", reject_with)?; - this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; return Ok(-1); } @@ -779,7 +766,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`rename`", reject_with)?; - this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; return Ok(-1); } @@ -807,7 +794,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`mkdir`", reject_with)?; - this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; return Ok(-1); } @@ -835,7 +822,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`rmdir`", reject_with)?; - this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; return Ok(-1); } @@ -872,7 +859,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Ok(Scalar::from_target_usize(id, this)) } Err(e) => { - this.set_last_error_from_io_error(e.kind())?; + this.set_last_error_from_io_error(e)?; Ok(Scalar::null_ptr(this)) } } @@ -924,8 +911,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let d_name_offset = dirent64_layout.fields.offset(4 /* d_name */).bytes(); let size = d_name_offset.checked_add(name_len).unwrap(); - let entry = - this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::Runtime)?; + let entry = this.allocate_ptr( + Size::from_bytes(size), + dirent64_layout.align.abi, + MiriMemoryKind::Runtime.into(), + )?; + let entry: Pointer> = entry.into(); // If the host is a Unix system, fill in the inode number with its real value. // If not, use 0 as a fallback value. @@ -949,23 +940,25 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let name_ptr = entry.offset(Size::from_bytes(d_name_offset), this)?; this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?; - entry + Some(entry) } None => { // end of stream: return NULL - Pointer::null() + None } Some(Err(e)) => { - this.set_last_error_from_io_error(e.kind())?; - Pointer::null() + this.set_last_error_from_io_error(e)?; + None } }; let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap(); let old_entry = std::mem::replace(&mut open_dir.entry, entry); - this.free(old_entry, MiriMemoryKind::Runtime)?; + if let Some(old_entry) = old_entry { + this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?; + } - Ok(Scalar::from_maybe_pointer(entry, this)) + Ok(Scalar::from_maybe_pointer(entry.unwrap_or_else(Pointer::null), this)) } fn macos_fbsd_readdir_r( @@ -1106,7 +1099,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } if let Some(open_dir) = this.machine.dirs.streams.remove(&dirp) { - this.free(open_dir.entry, MiriMemoryKind::Runtime)?; + if let Some(entry) = open_dir.entry { + this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?; + } drop(open_dir); Ok(0) } else { @@ -1124,32 +1119,35 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { return Ok(Scalar::from_i32(this.fd_not_found()?)); } - Ok(Scalar::from_i32(if let Some(file_descriptor) = this.machine.fds.get_mut(fd) { - // FIXME: Support ftruncate64 for all FDs - let FileHandle { file, writable } = - file_descriptor.downcast_ref::().ok_or_else(|| { - err_unsup_format!( - "`ftruncate64` is only supported on file-backed file descriptors" - ) - })?; - if *writable { - if let Ok(length) = length.try_into() { - let result = file.set_len(length); - this.try_unwrap_io_result(result.map(|_| 0i32))? - } else { - let einval = this.eval_libc("EINVAL"); - this.set_last_error(einval)?; - -1 - } + let Some(file_descriptor) = this.machine.fds.get(fd) else { + return Ok(Scalar::from_i32(this.fd_not_found()?)); + }; + + // FIXME: Support ftruncate64 for all FDs + let FileHandle { file, writable } = + file_descriptor.downcast_ref::().ok_or_else(|| { + err_unsup_format!("`ftruncate64` is only supported on file-backed file descriptors") + })?; + + if *writable { + if let Ok(length) = length.try_into() { + let result = file.set_len(length); + drop(file_descriptor); + let result = this.try_unwrap_io_result(result.map(|_| 0i32))?; + Ok(Scalar::from_i32(result)) } else { - // The file is not writable + drop(file_descriptor); let einval = this.eval_libc("EINVAL"); this.set_last_error(einval)?; - -1 + Ok(Scalar::from_i32(-1)) } } else { - this.fd_not_found()? - })) + drop(file_descriptor); + // The file is not writable + let einval = this.eval_libc("EINVAL"); + this.set_last_error(einval)?; + Ok(Scalar::from_i32(-1)) + } } fn fsync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { @@ -1183,6 +1181,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { err_unsup_format!("`fsync` is only supported on file-backed file descriptors") })?; let io_result = maybe_sync_file(file, *writable, File::sync_all); + drop(file_descriptor); this.try_unwrap_io_result(io_result) } @@ -1207,6 +1206,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors") })?; let io_result = maybe_sync_file(file, *writable, File::sync_data); + drop(file_descriptor); this.try_unwrap_io_result(io_result) } @@ -1256,6 +1256,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) })?; let io_result = maybe_sync_file(file, *writable, File::sync_data); + drop(file_descriptor); Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?)) } @@ -1298,13 +1299,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Ok(path_bytes.len().try_into().unwrap()) } Err(e) => { - this.set_last_error_from_io_error(e.kind())?; + this.set_last_error_from_io_error(e)?; Ok(-1) } } } - #[cfg_attr(not(unix), allow(unused))] fn isatty( &mut self, miri_fd: &OpTy<'tcx, Provenance>, @@ -1382,7 +1382,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Ok(Scalar::from_maybe_pointer(dest, this)) } Err(e) => { - this.set_last_error_from_io_error(e.kind())?; + this.set_last_error_from_io_error(e)?; Ok(Scalar::from_target_usize(0, this)) } } @@ -1457,8 +1457,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { #[cfg(unix)] { use std::os::unix::fs::OpenOptionsExt; - fopts.mode(0o600); // Do not allow others to read or modify this file. + fopts.mode(0o600); fopts.custom_flags(libc::O_EXCL); } #[cfg(windows)] @@ -1491,7 +1491,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { match file { Ok(f) => { let fh = &mut this.machine.fds; - let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true })); + let fd = + fh.insert_fd(FileDescriptor::new(FileHandle { file: f, writable: true })); return Ok(fd); } Err(e) => @@ -1502,7 +1503,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { _ => { // "On error, -1 is returned, and errno is set to // indicate the error" - this.set_last_error_from_io_error(e.kind())?; + this.set_last_error_from_io_error(e)?; return Ok(-1); } }, @@ -1556,21 +1557,21 @@ impl FileMetadata { ecx: &mut MiriInterpCx<'_, 'tcx>, fd: i32, ) -> InterpResult<'tcx, Option> { - let option = ecx.machine.fds.get(fd); - let file = match option { - Some(file_descriptor) => - &file_descriptor - .downcast_ref::() - .ok_or_else(|| { - err_unsup_format!( - "obtaining metadata is only supported on file-backed file descriptors" - ) - })? - .file, - None => return ecx.fd_not_found().map(|_: i32| None), + let Some(file_descriptor) = ecx.machine.fds.get(fd) else { + return ecx.fd_not_found().map(|_: i32| None); }; - let metadata = file.metadata(); + let file = &file_descriptor + .downcast_ref::() + .ok_or_else(|| { + err_unsup_format!( + "obtaining metadata is only supported on file-backed file descriptors" + ) + })? + .file; + + let metadata = file.metadata(); + drop(file_descriptor); FileMetadata::from_meta(ecx, metadata) } @@ -1581,7 +1582,7 @@ impl FileMetadata { let metadata = match metadata { Ok(metadata) => metadata, Err(e) => { - ecx.set_last_error_from_io_error(e.kind())?; + ecx.set_last_error_from_io_error(e)?; return Ok(None); } }; diff --git a/src/shims/unix/linux/epoll.rs b/src/shims/unix/linux/epoll.rs index 5161d91ca3..48a0ba0197 100644 --- a/src/shims/unix/linux/epoll.rs +++ b/src/shims/unix/linux/epoll.rs @@ -5,6 +5,8 @@ use rustc_data_structures::fx::FxHashMap; use crate::shims::unix::*; use crate::*; +use self::shims::unix::fd::FileDescriptor; + /// An `Epoll` file descriptor connects file handles and epoll events #[derive(Clone, Debug, Default)] struct Epoll { @@ -29,22 +31,16 @@ struct EpollEvent { data: Scalar, } -impl FileDescriptor for Epoll { +impl FileDescription for Epoll { fn name(&self) -> &'static str { "epoll" } - fn dup(&mut self) -> io::Result> { - // FIXME: this is probably wrong -- check if the `dup`ed descriptor truly uses an - // independent event set. - Ok(Box::new(self.clone())) - } - fn close<'tcx>( self: Box, _communicate_allowed: bool, - ) -> InterpResult<'tcx, io::Result> { - Ok(Ok(0)) + ) -> InterpResult<'tcx, io::Result<()>> { + Ok(Ok(())) } } @@ -70,7 +66,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { throw_unsup_format!("epoll_create1 flags {flags} are not implemented"); } - let fd = this.machine.fds.insert_fd(Box::new(Epoll::default())); + let fd = this.machine.fds.insert_fd(FileDescriptor::new(Epoll::default())); Ok(Scalar::from_i32(fd)) } @@ -114,27 +110,25 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let data = this.read_scalar(&data)?; let event = EpollEvent { events, data }; - if let Some(epfd) = this.machine.fds.get_mut(epfd) { - let epfd = epfd - .downcast_mut::() - .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?; + let Some(mut epfd) = this.machine.fds.get_mut(epfd) else { + return Ok(Scalar::from_i32(this.fd_not_found()?)); + }; + let epfd = epfd + .downcast_mut::() + .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?; - epfd.file_descriptors.insert(fd, event); - Ok(Scalar::from_i32(0)) - } else { - Ok(Scalar::from_i32(this.fd_not_found()?)) - } + epfd.file_descriptors.insert(fd, event); + Ok(Scalar::from_i32(0)) } else if op == epoll_ctl_del { - if let Some(epfd) = this.machine.fds.get_mut(epfd) { - let epfd = epfd - .downcast_mut::() - .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?; - - epfd.file_descriptors.remove(&fd); - Ok(Scalar::from_i32(0)) - } else { - Ok(Scalar::from_i32(this.fd_not_found()?)) - } + let Some(mut epfd) = this.machine.fds.get_mut(epfd) else { + return Ok(Scalar::from_i32(this.fd_not_found()?)); + }; + let epfd = epfd + .downcast_mut::() + .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?; + + epfd.file_descriptors.remove(&fd); + Ok(Scalar::from_i32(0)) } else { let einval = this.eval_libc("EINVAL"); this.set_last_error(einval)?; @@ -185,15 +179,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let _maxevents = this.read_scalar(maxevents)?.to_i32()?; let _timeout = this.read_scalar(timeout)?.to_i32()?; - if let Some(epfd) = this.machine.fds.get_mut(epfd) { - let _epfd = epfd - .downcast_mut::() - .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?; + let Some(mut epfd) = this.machine.fds.get_mut(epfd) else { + return Ok(Scalar::from_i32(this.fd_not_found()?)); + }; + let _epfd = epfd + .downcast_mut::() + .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?; - // FIXME return number of events ready when scheme for marking events ready exists - throw_unsup_format!("returning ready events from epoll_wait is not yet implemented"); - } else { - Ok(Scalar::from_i32(this.fd_not_found()?)) - } + // FIXME return number of events ready when scheme for marking events ready exists + throw_unsup_format!("returning ready events from epoll_wait is not yet implemented"); } } diff --git a/src/shims/unix/linux/eventfd.rs b/src/shims/unix/linux/eventfd.rs index 0f28b69ac4..a865f2efff 100644 --- a/src/shims/unix/linux/eventfd.rs +++ b/src/shims/unix/linux/eventfd.rs @@ -1,6 +1,5 @@ //! Linux `eventfd` implementation. //! Currently just a stub. -use std::cell::Cell; use std::io; use rustc_middle::ty::TyCtxt; @@ -9,6 +8,8 @@ use rustc_target::abi::Endian; use crate::shims::unix::*; use crate::*; +use self::shims::unix::fd::FileDescriptor; + /// A kind of file descriptor created by `eventfd`. /// The `Event` type isn't currently written to by `eventfd`. /// The interface is meant to keep track of objects associated @@ -20,24 +21,19 @@ use crate::*; struct Event { /// The object contains an unsigned 64-bit integer (uint64_t) counter that is maintained by the /// kernel. This counter is initialized with the value specified in the argument initval. - val: Cell, + val: u64, } -impl FileDescriptor for Event { +impl FileDescription for Event { fn name(&self) -> &'static str { "event" } - fn dup(&mut self) -> io::Result> { - // FIXME: this is wrong, the new and old FD should refer to the same event object! - Ok(Box::new(Event { val: self.val.clone() })) - } - fn close<'tcx>( self: Box, _communicate_allowed: bool, - ) -> InterpResult<'tcx, io::Result> { - Ok(Ok(0)) + ) -> InterpResult<'tcx, io::Result<()>> { + Ok(Ok(())) } /// A write call adds the 8-byte integer value supplied in @@ -53,12 +49,11 @@ impl FileDescriptor for Event { /// supplied buffer is less than 8 bytes, or if an attempt is /// made to write the value 0xffffffffffffffff. fn write<'tcx>( - &self, + &mut self, _communicate_allowed: bool, bytes: &[u8], tcx: TyCtxt<'tcx>, ) -> InterpResult<'tcx, io::Result> { - let v1 = self.val.get(); let bytes: [u8; 8] = bytes.try_into().unwrap(); // FIXME fail gracefully when this has the wrong size // Convert from target endianness to host endianness. let num = match tcx.sess.target.endian { @@ -67,9 +62,7 @@ impl FileDescriptor for Event { }; // FIXME handle blocking when addition results in exceeding the max u64 value // or fail with EAGAIN if the file descriptor is nonblocking. - let v2 = v1.checked_add(num).unwrap(); - self.val.set(v2); - assert_eq!(8, bytes.len()); + self.val = self.val.checked_add(num).unwrap(); Ok(Ok(8)) } } @@ -119,7 +112,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { throw_unsup_format!("eventfd: EFD_SEMAPHORE is unsupported"); } - let fd = this.machine.fds.insert_fd(Box::new(Event { val: Cell::new(val.into()) })); + let fd = this.machine.fds.insert_fd(FileDescriptor::new(Event { val: val.into() })); Ok(Scalar::from_i32(fd)) } } diff --git a/src/shims/unix/linux/foreign_items.rs b/src/shims/unix/linux/foreign_items.rs index 2aaec3f5a0..7cd749a410 100644 --- a/src/shims/unix/linux/foreign_items.rs +++ b/src/shims/unix/linux/foreign_items.rs @@ -5,14 +5,13 @@ use crate::machine::SIGRTMAX; use crate::machine::SIGRTMIN; use crate::shims::unix::*; use crate::*; -use shims::foreign_items::EmulateForeignItemResult; use shims::unix::linux::epoll::EvalContextExt as _; use shims::unix::linux::eventfd::EvalContextExt as _; use shims::unix::linux::mem::EvalContextExt as _; use shims::unix::linux::sync::futex; pub fn is_dyn_sym(name: &str) -> bool { - matches!(name, "getrandom") + matches!(name, "statx") } impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} @@ -23,40 +22,32 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { abi: Abi, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, EmulateForeignItemResult> { + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern. match link_name.as_str() { - // errno - "__errno_location" => { - let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let errno_place = this.last_error_place()?; - this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; - } - - // File related shims (but also see "syscall" below for statx) + // File related shims "readdir64" => { let [dirp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let result = this.linux_readdir64(dirp)?; this.write_scalar(result, dest)?; } - "mmap64" => { - let [addr, length, prot, flags, fd, offset] = - this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let offset = this.read_scalar(offset)?.to_i64()?; - let ptr = this.mmap(addr, length, prot, flags, fd, offset.into())?; - this.write_scalar(ptr, dest)?; - } - - // Linux-only "sync_file_range" => { let [fd, offset, nbytes, flags] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let result = this.sync_file_range(fd, offset, nbytes, flags)?; this.write_scalar(result, dest)?; } + "statx" => { + let [dirfd, pathname, flags, mask, statxbuf] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.linux_statx(dirfd, pathname, flags, mask, statxbuf)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + + // epoll, eventfd "epoll_create1" => { let [flag] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let result = this.epoll_create1(flag)?; @@ -80,43 +71,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let result = this.eventfd(val, flag)?; this.write_scalar(result, dest)?; } - "mremap" => { - let [old_address, old_size, new_size, flags] = - this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let ptr = this.mremap(old_address, old_size, new_size, flags)?; - this.write_scalar(ptr, dest)?; - } - "socketpair" => { - let [domain, type_, protocol, sv] = - this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - - let result = this.socketpair(domain, type_, protocol, sv)?; - this.write_scalar(result, dest)?; - } - "__libc_current_sigrtmin" => { - let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - - this.write_scalar(Scalar::from_i32(SIGRTMIN), dest)?; - } - "__libc_current_sigrtmax" => { - let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - - this.write_scalar(Scalar::from_i32(SIGRTMAX), dest)?; - } // Threading - "pthread_condattr_setclock" => { - let [attr, clock_id] = - this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let result = this.pthread_condattr_setclock(attr, clock_id)?; - this.write_scalar(result, dest)?; - } - "pthread_condattr_getclock" => { - let [attr, clock_id] = - this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let result = this.pthread_condattr_getclock(attr, clock_id)?; - this.write_scalar(result, dest)?; - } "pthread_setname_np" => { let [thread, name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; @@ -150,9 +106,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // have the right type. let sys_getrandom = this.eval_libc("SYS_getrandom").to_target_usize(this)?; - - let sys_statx = this.eval_libc("SYS_statx").to_target_usize(this)?; - let sys_futex = this.eval_libc("SYS_futex").to_target_usize(this)?; if args.is_empty() { @@ -164,6 +117,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)` // is called if a `HashMap` is created the regular way (e.g. HashMap). id if id == sys_getrandom => { + // Used by getrandom 0.1 // The first argument is the syscall id, so skip over it. if args.len() < 4 { throw_ub_format!( @@ -171,38 +125,71 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { args.len() ); } - getrandom(this, &args[1], &args[2], &args[3], dest)?; - } - // `statx` is used by `libstd` to retrieve metadata information on `linux` - // instead of using `stat`,`lstat` or `fstat` as on `macos`. - id if id == sys_statx => { - // The first argument is the syscall id, so skip over it. - if args.len() < 6 { - throw_ub_format!( - "incorrect number of arguments for `statx` syscall: got {}, expected at least 6", - args.len() - ); - } - let result = - this.linux_statx(&args[1], &args[2], &args[3], &args[4], &args[5])?; - this.write_scalar(Scalar::from_target_isize(result.into(), this), dest)?; + + let ptr = this.read_pointer(&args[1])?; + let len = this.read_target_usize(&args[2])?; + // The only supported flags are GRND_RANDOM and GRND_NONBLOCK, + // neither of which have any effect on our current PRNG. + // See for a discussion of argument sizes. + let _flags = this.read_scalar(&args[3])?.to_i32(); + + this.gen_random(ptr, len)?; + this.write_scalar(Scalar::from_target_usize(len, this), dest)?; } // `futex` is used by some synchronization primitives. id if id == sys_futex => { futex(this, &args[1..], dest)?; } id => { - this.handle_unsupported(format!("can't execute syscall with ID {id}"))?; - return Ok(EmulateForeignItemResult::AlreadyJumped); + this.handle_unsupported_foreign_item(format!( + "can't execute syscall with ID {id}" + ))?; + return Ok(EmulateItemResult::AlreadyJumped); } } } // Miscellaneous - "getrandom" => { - let [ptr, len, flags] = + "mmap64" => { + let [addr, length, prot, flags, fd, offset] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - getrandom(this, ptr, len, flags, dest)?; + let offset = this.read_scalar(offset)?.to_i64()?; + let ptr = this.mmap(addr, length, prot, flags, fd, offset.into())?; + this.write_scalar(ptr, dest)?; + } + "mremap" => { + let [old_address, old_size, new_size, flags] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let ptr = this.mremap(old_address, old_size, new_size, flags)?; + this.write_scalar(ptr, dest)?; + } + "__errno_location" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let errno_place = this.last_error_place()?; + this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; + } + "__libc_current_sigrtmin" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + this.write_scalar(Scalar::from_i32(SIGRTMIN), dest)?; + } + "__libc_current_sigrtmax" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + this.write_scalar(Scalar::from_i32(SIGRTMAX), dest)?; + } + "sched_getaffinity" => { + // This shim isn't useful, aside from the fact that it makes `num_cpus` + // fall back to `sysconf` where it will successfully determine the number of CPUs. + let [pid, cpusetsize, mask] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.read_scalar(pid)?.to_i32()?; + this.read_target_usize(cpusetsize)?; + this.deref_pointer_as(mask, this.libc_ty_layout("cpu_set_t"))?; + // FIXME: we just return an error. + let einval = this.eval_libc("EINVAL"); + this.set_last_error(einval)?; + this.write_scalar(Scalar::from_i32(-1), dest)?; } // Incomplete shims that we "stub out" just to get pre-main initialization code to work. @@ -213,30 +200,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_null(dest)?; } - _ => return Ok(EmulateForeignItemResult::NotSupported), + _ => return Ok(EmulateItemResult::NotSupported), }; - Ok(EmulateForeignItemResult::NeedsJumping) + Ok(EmulateItemResult::NeedsJumping) } } - -// Shims the linux `getrandom` syscall. -fn getrandom<'tcx>( - this: &mut MiriInterpCx<'_, 'tcx>, - ptr: &OpTy<'tcx, Provenance>, - len: &OpTy<'tcx, Provenance>, - flags: &OpTy<'tcx, Provenance>, - dest: &MPlaceTy<'tcx, Provenance>, -) -> InterpResult<'tcx> { - let ptr = this.read_pointer(ptr)?; - let len = this.read_target_usize(len)?; - - // The only supported flags are GRND_RANDOM and GRND_NONBLOCK, - // neither of which have any effect on our current PRNG. - // See for a discussion of argument sizes. - let _flags = this.read_scalar(flags)?.to_i32(); - - this.gen_random(ptr, len)?; - this.write_scalar(Scalar::from_target_usize(len, this), dest)?; - Ok(()) -} diff --git a/src/shims/unix/linux/mem.rs b/src/shims/unix/linux/mem.rs index ec2922d027..3948216f72 100644 --- a/src/shims/unix/linux/mem.rs +++ b/src/shims/unix/linux/mem.rs @@ -23,7 +23,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // old_address must be a multiple of the page size #[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero if old_address.addr().bytes() % this.machine.page_size != 0 || new_size == 0 { - this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + this.set_last_error(this.eval_libc("EINVAL"))?; return Ok(this.eval_libc("MAP_FAILED")); } @@ -37,7 +37,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { if flags & this.eval_libc_i32("MREMAP_MAYMOVE") == 0 { // We only support MREMAP_MAYMOVE, so not passing the flag is just a failure - this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + this.set_last_error(this.eval_libc("EINVAL"))?; return Ok(this.eval_libc("MAP_FAILED")); } diff --git a/src/shims/unix/linux/sync.rs b/src/shims/unix/linux/sync.rs index ed27066aa6..d4a6cd96f4 100644 --- a/src/shims/unix/linux/sync.rs +++ b/src/shims/unix/linux/sync.rs @@ -107,16 +107,22 @@ pub fn futex<'tcx>( Some(if wait_bitset { // FUTEX_WAIT_BITSET uses an absolute timestamp. if realtime { - Time::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap()) + CallbackTime::RealTime( + SystemTime::UNIX_EPOCH.checked_add(duration).unwrap(), + ) } else { - Time::Monotonic(this.machine.clock.anchor().checked_add(duration).unwrap()) + CallbackTime::Monotonic( + this.machine.clock.anchor().checked_add(duration).unwrap(), + ) } } else { // FUTEX_WAIT uses a relative timestamp. if realtime { - Time::RealTime(SystemTime::now().checked_add(duration).unwrap()) + CallbackTime::RealTime(SystemTime::now().checked_add(duration).unwrap()) } else { - Time::Monotonic(this.machine.clock.now().checked_add(duration).unwrap()) + CallbackTime::Monotonic( + this.machine.clock.now().checked_add(duration).unwrap(), + ) } }) }; @@ -169,7 +175,7 @@ pub fn futex<'tcx>( let futex_val = this.read_scalar_atomic(&addr, AtomicReadOrd::Relaxed)?.to_i32()?; if val == futex_val { // The value still matches, so we block the thread make it wait for FUTEX_WAKE. - this.block_thread(thread); + this.block_thread(thread, BlockReason::Futex { addr: addr_usize }); this.futex_wait(addr_usize, thread, bitset); // Succesfully waking up from FUTEX_WAIT always returns zero. this.write_scalar(Scalar::from_target_isize(0, this), dest)?; @@ -191,7 +197,10 @@ pub fn futex<'tcx>( impl<'mir, 'tcx: 'mir> MachineCallback<'mir, 'tcx> for Callback<'tcx> { fn call(&self, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> { - this.unblock_thread(self.thread); + this.unblock_thread( + self.thread, + BlockReason::Futex { addr: self.addr_usize }, + ); this.futex_remove_waiter(self.addr_usize, self.thread); let etimedout = this.eval_libc("ETIMEDOUT"); this.set_last_error(etimedout)?; @@ -249,7 +258,7 @@ pub fn futex<'tcx>( #[allow(clippy::arithmetic_side_effects)] for _ in 0..val { if let Some(thread) = this.futex_wake(addr_usize, bitset) { - this.unblock_thread(thread); + this.unblock_thread(thread, BlockReason::Futex { addr: addr_usize }); this.unregister_timeout_callback_if_exists(thread); n += 1; } else { diff --git a/src/shims/unix/macos/foreign_items.rs b/src/shims/unix/macos/foreign_items.rs index 53a02bf5e0..912623b722 100644 --- a/src/shims/unix/macos/foreign_items.rs +++ b/src/shims/unix/macos/foreign_items.rs @@ -3,7 +3,6 @@ use rustc_target::spec::abi::Abi; use crate::shims::unix::*; use crate::*; -use shims::foreign_items::EmulateForeignItemResult; pub fn is_dyn_sym(_name: &str) -> bool { false @@ -17,7 +16,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { abi: Abi, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, EmulateForeignItemResult> { + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern. @@ -74,15 +73,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Environment related shims "_NSGetEnviron" => { let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - this.write_pointer( - this.machine - .env_vars - .environ - .as_ref() - .expect("machine must be initialized") - .ptr(), - dest, - )?; + let environ = this.machine.env_vars.unix().environ(); + this.write_pointer(environ, dest)?; } // Time related shims @@ -182,9 +174,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_scalar(res, dest)?; } - _ => return Ok(EmulateForeignItemResult::NotSupported), + _ => return Ok(EmulateItemResult::NotSupported), }; - Ok(EmulateForeignItemResult::NeedsJumping) + Ok(EmulateItemResult::NeedsJumping) } } diff --git a/src/shims/unix/mem.rs b/src/shims/unix/mem.rs index d3470893db..0254735ac1 100644 --- a/src/shims/unix/mem.rs +++ b/src/shims/unix/mem.rs @@ -42,9 +42,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let map_shared = this.eval_libc_i32("MAP_SHARED"); let map_fixed = this.eval_libc_i32("MAP_FIXED"); - // This is a horrible hack, but on MacOS the guard page mechanism uses mmap + // This is a horrible hack, but on MacOS and Solaris the guard page mechanism uses mmap // in a way we do not support. We just give it the return value it expects. - if this.frame_in_std() && this.tcx.sess.target.os == "macos" && (flags & map_fixed) != 0 { + if this.frame_in_std() + && matches!(&*this.tcx.sess.target.os, "macos" | "solaris") + && (flags & map_fixed) != 0 + { return Ok(Scalar::from_maybe_pointer(Pointer::from_addr_invalid(addr), this)); } @@ -53,11 +56,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // First, we do some basic argument validation as required by mmap if (flags & (map_private | map_shared)).count_ones() != 1 { - this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + this.set_last_error(this.eval_libc("EINVAL"))?; return Ok(this.eval_libc("MAP_FAILED")); } if length == 0 { - this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + this.set_last_error(this.eval_libc("EINVAL"))?; return Ok(this.eval_libc("MAP_FAILED")); } @@ -77,7 +80,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // // Miri doesn't support MAP_FIXED or any any protections other than PROT_READ|PROT_WRITE. if flags & map_fixed != 0 || prot != prot_read | prot_write { - this.set_last_error(Scalar::from_i32(this.eval_libc_i32("ENOTSUP")))?; + this.set_last_error(this.eval_libc("ENOTSUP"))?; return Ok(this.eval_libc("MAP_FAILED")); } @@ -96,11 +99,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let align = this.machine.page_align(); let Some(map_length) = length.checked_next_multiple_of(this.machine.page_size) else { - this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + this.set_last_error(this.eval_libc("EINVAL"))?; return Ok(this.eval_libc("MAP_FAILED")); }; if map_length > this.target_usize_max() { - this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + this.set_last_error(this.eval_libc("EINVAL"))?; return Ok(this.eval_libc("MAP_FAILED")); } @@ -131,16 +134,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // as a dealloc. #[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero if addr.addr().bytes() % this.machine.page_size != 0 { - this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + this.set_last_error(this.eval_libc("EINVAL"))?; return Ok(Scalar::from_i32(-1)); } let Some(length) = length.checked_next_multiple_of(this.machine.page_size) else { - this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + this.set_last_error(this.eval_libc("EINVAL"))?; return Ok(Scalar::from_i32(-1)); }; if length > this.target_usize_max() { - this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + this.set_last_error(this.eval_libc("EINVAL"))?; return Ok(this.eval_libc("MAP_FAILED")); } diff --git a/src/shims/unix/mod.rs b/src/shims/unix/mod.rs index 2bc41e1a62..6dee30d895 100644 --- a/src/shims/unix/mod.rs +++ b/src/shims/unix/mod.rs @@ -1,5 +1,6 @@ pub mod foreign_items; +mod env; mod fd; mod fs; mod mem; @@ -10,10 +11,13 @@ mod thread; mod freebsd; mod linux; mod macos; +mod solarish; -pub use fd::{FdTable, FileDescriptor}; +pub use env::UnixEnvVars; +pub use fd::{FdTable, FileDescription}; pub use fs::DirTable; -// All the unix-specific extension traits +// All the Unix-specific extension traits +pub use env::EvalContextExt as _; pub use fd::EvalContextExt as _; pub use fs::EvalContextExt as _; pub use mem::EvalContextExt as _; diff --git a/src/shims/unix/socket.rs b/src/shims/unix/socket.rs index 84ddd746fb..11fd83f57e 100644 --- a/src/shims/unix/socket.rs +++ b/src/shims/unix/socket.rs @@ -3,26 +3,24 @@ use std::io; use crate::shims::unix::*; use crate::*; +use self::fd::FileDescriptor; + /// Pair of connected sockets. /// /// We currently don't allow sending any data through this pair, so this can be just a dummy. #[derive(Debug)] struct SocketPair; -impl FileDescriptor for SocketPair { +impl FileDescription for SocketPair { fn name(&self) -> &'static str { "socketpair" } - fn dup(&mut self) -> io::Result> { - Ok(Box::new(SocketPair)) - } - fn close<'tcx>( self: Box, _communicate_allowed: bool, - ) -> InterpResult<'tcx, io::Result> { - Ok(Ok(0)) + ) -> InterpResult<'tcx, io::Result<()>> { + Ok(Ok(())) } } @@ -52,9 +50,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // FIXME: fail on unsupported inputs let fds = &mut this.machine.fds; - let sv0 = fds.insert_fd(Box::new(SocketPair)); + let sv0 = fds.insert_fd(FileDescriptor::new(SocketPair)); let sv0 = Scalar::try_from_int(sv0, sv.layout.size).unwrap(); - let sv1 = fds.insert_fd(Box::new(SocketPair)); + let sv1 = fds.insert_fd(FileDescriptor::new(SocketPair)); let sv1 = Scalar::try_from_int(sv1, sv.layout.size).unwrap(); this.write_scalar(sv0, &sv)?; diff --git a/src/shims/unix/solarish/foreign_items.rs b/src/shims/unix/solarish/foreign_items.rs new file mode 100644 index 0000000000..c216d8afd7 --- /dev/null +++ b/src/shims/unix/solarish/foreign_items.rs @@ -0,0 +1,50 @@ +use rustc_span::Symbol; +use rustc_target::spec::abi::Abi; + +use crate::*; + +pub fn is_dyn_sym(_name: &str) -> bool { + false +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn emulate_foreign_item_inner( + &mut self, + link_name: Symbol, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &MPlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, EmulateItemResult> { + let this = self.eval_context_mut(); + match link_name.as_str() { + // Miscellaneous + "___errno" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let errno_place = this.last_error_place()?; + this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; + } + + "stack_getbounds" => { + let [stack] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let stack = this.deref_pointer_as(stack, this.libc_ty_layout("stack_t"))?; + + this.write_int_fields_named( + &[ + ("ss_sp", this.machine.stack_addr.into()), + ("ss_size", this.machine.stack_size.into()), + // field set to 0 means not in an alternate signal stack + // https://docs.oracle.com/cd/E86824_01/html/E54766/stack-getbounds-3c.html + ("ss_flags", 0), + ], + &stack, + )?; + + this.write_null(dest)?; + } + + _ => return Ok(EmulateItemResult::NotSupported), + } + Ok(EmulateItemResult::NeedsJumping) + } +} diff --git a/src/shims/unix/solarish/mod.rs b/src/shims/unix/solarish/mod.rs new file mode 100644 index 0000000000..09c6507b24 --- /dev/null +++ b/src/shims/unix/solarish/mod.rs @@ -0,0 +1 @@ +pub mod foreign_items; diff --git a/src/shims/unix/sync.rs b/src/shims/unix/sync.rs index dd301f9ee6..f24f279ab0 100644 --- a/src/shims/unix/sync.rs +++ b/src/shims/unix/sync.rs @@ -1,35 +1,23 @@ +use std::sync::atomic::{AtomicBool, Ordering}; use std::time::SystemTime; -use crate::concurrency::sync::CondvarLock; +use rustc_target::abi::Size; + use crate::concurrency::thread::MachineCallback; use crate::*; // pthread_mutexattr_t is either 4 or 8 bytes, depending on the platform. +// We ignore the platform layout and store our own fields: +// - kind: i32 -// Our chosen memory layout for emulation (does not have to match the platform layout!): -// store an i32 in the first four bytes equal to the corresponding libc mutex kind constant -// (e.g. PTHREAD_MUTEX_NORMAL). - -/// A flag that allows to distinguish `PTHREAD_MUTEX_NORMAL` from -/// `PTHREAD_MUTEX_DEFAULT`. Since in `glibc` they have the same numeric values, -/// but different behaviour, we need a way to distinguish them. We do this by -/// setting this bit flag to the `PTHREAD_MUTEX_NORMAL` mutexes. See the comment -/// in `pthread_mutexattr_settype` function. -const PTHREAD_MUTEX_NORMAL_FLAG: i32 = 0x8000000; - -fn is_mutex_kind_default<'mir, 'tcx: 'mir>( - ecx: &MiriInterpCx<'mir, 'tcx>, - kind: i32, -) -> InterpResult<'tcx, bool> { - Ok(kind == ecx.eval_libc_i32("PTHREAD_MUTEX_DEFAULT")) -} - -fn is_mutex_kind_normal<'mir, 'tcx: 'mir>( +#[inline] +fn mutexattr_kind_offset<'mir, 'tcx: 'mir>( ecx: &MiriInterpCx<'mir, 'tcx>, - kind: i32, -) -> InterpResult<'tcx, bool> { - let mutex_normal_kind = ecx.eval_libc_i32("PTHREAD_MUTEX_NORMAL"); - Ok(kind == (mutex_normal_kind | PTHREAD_MUTEX_NORMAL_FLAG)) +) -> InterpResult<'tcx, u64> { + Ok(match &*ecx.tcx.sess.target.os { + "linux" | "illumos" | "solaris" | "macos" => 0, + os => throw_unsup_format!("`pthread_mutexattr` is not supported on {os}"), + }) } fn mutexattr_get_kind<'mir, 'tcx: 'mir>( @@ -38,7 +26,7 @@ fn mutexattr_get_kind<'mir, 'tcx: 'mir>( ) -> InterpResult<'tcx, i32> { ecx.deref_pointer_and_read( attr_op, - 0, + mutexattr_kind_offset(ecx)?, ecx.libc_ty_layout("pthread_mutexattr_t"), ecx.machine.layouts.i32, )? @@ -52,27 +40,99 @@ fn mutexattr_set_kind<'mir, 'tcx: 'mir>( ) -> InterpResult<'tcx, ()> { ecx.deref_pointer_and_write( attr_op, - 0, + mutexattr_kind_offset(ecx)?, Scalar::from_i32(kind), ecx.libc_ty_layout("pthread_mutexattr_t"), ecx.machine.layouts.i32, ) } +/// A flag that allows to distinguish `PTHREAD_MUTEX_NORMAL` from +/// `PTHREAD_MUTEX_DEFAULT`. Since in `glibc` they have the same numeric values, +/// but different behaviour, we need a way to distinguish them. We do this by +/// setting this bit flag to the `PTHREAD_MUTEX_NORMAL` mutexes. See the comment +/// in `pthread_mutexattr_settype` function. +const PTHREAD_MUTEX_NORMAL_FLAG: i32 = 0x8000000; + +fn is_mutex_kind_default<'mir, 'tcx: 'mir>( + ecx: &MiriInterpCx<'mir, 'tcx>, + kind: i32, +) -> InterpResult<'tcx, bool> { + Ok(kind == ecx.eval_libc_i32("PTHREAD_MUTEX_DEFAULT")) +} + +fn is_mutex_kind_normal<'mir, 'tcx: 'mir>( + ecx: &MiriInterpCx<'mir, 'tcx>, + kind: i32, +) -> InterpResult<'tcx, bool> { + let mutex_normal_kind = ecx.eval_libc_i32("PTHREAD_MUTEX_NORMAL"); + Ok(kind == (mutex_normal_kind | PTHREAD_MUTEX_NORMAL_FLAG)) +} + // pthread_mutex_t is between 24 and 48 bytes, depending on the platform. +// We ignore the platform layout and store our own fields: +// - id: u32 +// - kind: i32 + +fn mutex_id_offset<'mir, 'tcx: 'mir>(ecx: &MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx, u64> { + let offset = match &*ecx.tcx.sess.target.os { + "linux" | "illumos" | "solaris" => 0, + // macOS stores a signature in the first bytes, so we have to move to offset 4. + "macos" => 4, + os => throw_unsup_format!("`pthread_mutex` is not supported on {os}"), + }; + + // Sanity-check this against PTHREAD_MUTEX_INITIALIZER (but only once): + // the id must start out as 0. + static SANITY: AtomicBool = AtomicBool::new(false); + if !SANITY.swap(true, Ordering::Relaxed) { + let static_initializer = ecx.eval_path(&["libc", "PTHREAD_MUTEX_INITIALIZER"]); + let id_field = static_initializer + .offset(Size::from_bytes(offset), ecx.machine.layouts.u32, ecx) + .unwrap(); + let id = ecx.read_scalar(&id_field).unwrap().to_u32().unwrap(); + assert_eq!( + id, 0, + "PTHREAD_MUTEX_INITIALIZER is incompatible with our pthread_mutex layout: id is not 0" + ); + } -// Our chosen memory layout for the emulated mutex (does not have to match the platform layout!): -// bytes 0-3: reserved for signature on macOS -// (need to avoid this because it is set by static initializer macros) -// bytes 4-7: mutex id as u32 or 0 if id is not assigned yet. -// bytes 12-15 or 16-19 (depending on platform): mutex kind, as an i32 -// (the kind has to be at its offset for compatibility with static initializer macros) + Ok(offset) +} + +fn mutex_kind_offset<'mir, 'tcx: 'mir>(ecx: &MiriInterpCx<'mir, 'tcx>) -> u64 { + // These offsets are picked for compatibility with Linux's static initializer + // macros, e.g. PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP.) + let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 }; + + // Sanity-check this against PTHREAD_MUTEX_INITIALIZER (but only once): + // the kind must start out as PTHREAD_MUTEX_DEFAULT. + static SANITY: AtomicBool = AtomicBool::new(false); + if !SANITY.swap(true, Ordering::Relaxed) { + let static_initializer = ecx.eval_path(&["libc", "PTHREAD_MUTEX_INITIALIZER"]); + let kind_field = static_initializer + .offset(Size::from_bytes(mutex_kind_offset(ecx)), ecx.machine.layouts.i32, ecx) + .unwrap(); + let kind = ecx.read_scalar(&kind_field).unwrap().to_i32().unwrap(); + assert_eq!( + kind, + ecx.eval_libc_i32("PTHREAD_MUTEX_DEFAULT"), + "PTHREAD_MUTEX_INITIALIZER is incompatible with our pthread_mutex layout: kind is not PTHREAD_MUTEX_DEFAULT" + ); + } + + offset +} fn mutex_get_id<'mir, 'tcx: 'mir>( ecx: &mut MiriInterpCx<'mir, 'tcx>, mutex_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx, MutexId> { - ecx.mutex_get_or_create_id(mutex_op, ecx.libc_ty_layout("pthread_mutex_t"), 4) + ecx.mutex_get_or_create_id( + mutex_op, + ecx.libc_ty_layout("pthread_mutex_t"), + mutex_id_offset(ecx)?, + ) } fn mutex_reset_id<'mir, 'tcx: 'mir>( @@ -81,8 +141,8 @@ fn mutex_reset_id<'mir, 'tcx: 'mir>( ) -> InterpResult<'tcx, ()> { ecx.deref_pointer_and_write( mutex_op, - 4, - Scalar::from_i32(0), + mutex_id_offset(ecx)?, + Scalar::from_u32(0), ecx.libc_ty_layout("pthread_mutex_t"), ecx.machine.layouts.u32, ) @@ -92,10 +152,9 @@ fn mutex_get_kind<'mir, 'tcx: 'mir>( ecx: &MiriInterpCx<'mir, 'tcx>, mutex_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx, i32> { - let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 }; ecx.deref_pointer_and_read( mutex_op, - offset, + mutex_kind_offset(ecx), ecx.libc_ty_layout("pthread_mutex_t"), ecx.machine.layouts.i32, )? @@ -107,10 +166,9 @@ fn mutex_set_kind<'mir, 'tcx: 'mir>( mutex_op: &OpTy<'tcx, Provenance>, kind: i32, ) -> InterpResult<'tcx, ()> { - let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 }; ecx.deref_pointer_and_write( mutex_op, - offset, + mutex_kind_offset(ecx), Scalar::from_i32(kind), ecx.libc_ty_layout("pthread_mutex_t"), ecx.machine.layouts.i32, @@ -118,24 +176,60 @@ fn mutex_set_kind<'mir, 'tcx: 'mir>( } // pthread_rwlock_t is between 32 and 56 bytes, depending on the platform. +// We ignore the platform layout and store our own fields: +// - id: u32 + +fn rwlock_id_offset<'mir, 'tcx: 'mir>(ecx: &MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx, u64> { + let offset = match &*ecx.tcx.sess.target.os { + "linux" | "illumos" | "solaris" => 0, + // macOS stores a signature in the first bytes, so we have to move to offset 4. + "macos" => 4, + os => throw_unsup_format!("`pthread_rwlock` is not supported on {os}"), + }; + + // Sanity-check this against PTHREAD_RWLOCK_INITIALIZER (but only once): + // the id must start out as 0. + static SANITY: AtomicBool = AtomicBool::new(false); + if !SANITY.swap(true, Ordering::Relaxed) { + let static_initializer = ecx.eval_path(&["libc", "PTHREAD_RWLOCK_INITIALIZER"]); + let id_field = static_initializer + .offset(Size::from_bytes(offset), ecx.machine.layouts.u32, ecx) + .unwrap(); + let id = ecx.read_scalar(&id_field).unwrap().to_u32().unwrap(); + assert_eq!( + id, 0, + "PTHREAD_RWLOCK_INITIALIZER is incompatible with our pthread_rwlock layout: id is not 0" + ); + } -// Our chosen memory layout for the emulated rwlock (does not have to match the platform layout!): -// bytes 0-3: reserved for signature on macOS -// (need to avoid this because it is set by static initializer macros) -// bytes 4-7: rwlock id as u32 or 0 if id is not assigned yet. + Ok(offset) +} fn rwlock_get_id<'mir, 'tcx: 'mir>( ecx: &mut MiriInterpCx<'mir, 'tcx>, rwlock_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx, RwLockId> { - ecx.rwlock_get_or_create_id(rwlock_op, ecx.libc_ty_layout("pthread_rwlock_t"), 4) + ecx.rwlock_get_or_create_id( + rwlock_op, + ecx.libc_ty_layout("pthread_rwlock_t"), + rwlock_id_offset(ecx)?, + ) } -// pthread_condattr_t +// pthread_condattr_t. +// We ignore the platform layout and store our own fields: +// - clock: i32 -// Our chosen memory layout for emulation (does not have to match the platform layout!): -// store an i32 in the first four bytes equal to the corresponding libc clock id constant -// (e.g. CLOCK_REALTIME). +#[inline] +fn condattr_clock_offset<'mir, 'tcx: 'mir>( + ecx: &MiriInterpCx<'mir, 'tcx>, +) -> InterpResult<'tcx, u64> { + Ok(match &*ecx.tcx.sess.target.os { + "linux" | "illumos" | "solaris" => 0, + // macOS does not have a clock attribute. + os => throw_unsup_format!("`pthread_condattr` clock field is not supported on {os}"), + }) +} fn condattr_get_clock_id<'mir, 'tcx: 'mir>( ecx: &MiriInterpCx<'mir, 'tcx>, @@ -143,7 +237,7 @@ fn condattr_get_clock_id<'mir, 'tcx: 'mir>( ) -> InterpResult<'tcx, i32> { ecx.deref_pointer_and_read( attr_op, - 0, + condattr_clock_offset(ecx)?, ecx.libc_ty_layout("pthread_condattr_t"), ecx.machine.layouts.i32, )? @@ -157,27 +251,86 @@ fn condattr_set_clock_id<'mir, 'tcx: 'mir>( ) -> InterpResult<'tcx, ()> { ecx.deref_pointer_and_write( attr_op, - 0, + condattr_clock_offset(ecx)?, Scalar::from_i32(clock_id), ecx.libc_ty_layout("pthread_condattr_t"), ecx.machine.layouts.i32, ) } -// pthread_cond_t +// pthread_cond_t. +// We ignore the platform layout and store our own fields: +// - id: u32 +// - clock: i32 + +fn cond_id_offset<'mir, 'tcx: 'mir>(ecx: &MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx, u64> { + let offset = match &*ecx.tcx.sess.target.os { + "linux" | "illumos" | "solaris" => 0, + // macOS stores a signature in the first bytes, so we have to move to offset 4. + "macos" => 4, + os => throw_unsup_format!("`pthread_cond` is not supported on {os}"), + }; + + // Sanity-check this against PTHREAD_COND_INITIALIZER (but only once): + // the id must start out as 0. + static SANITY: AtomicBool = AtomicBool::new(false); + if !SANITY.swap(true, Ordering::Relaxed) { + let static_initializer = ecx.eval_path(&["libc", "PTHREAD_COND_INITIALIZER"]); + let id_field = static_initializer + .offset(Size::from_bytes(offset), ecx.machine.layouts.u32, ecx) + .unwrap(); + let id = ecx.read_scalar(&id_field).unwrap().to_u32().unwrap(); + assert_eq!( + id, 0, + "PTHREAD_COND_INITIALIZER is incompatible with our pthread_cond layout: id is not 0" + ); + } + + Ok(offset) +} + +/// Determines whether this clock represents the real-time clock, CLOCK_REALTIME. +fn is_cond_clock_realtime<'mir, 'tcx: 'mir>(ecx: &MiriInterpCx<'mir, 'tcx>, clock_id: i32) -> bool { + // To ensure compatibility with PTHREAD_COND_INITIALIZER on all platforms, + // we can't just compare with CLOCK_REALTIME: on Solarish, PTHREAD_COND_INITIALIZER + // makes the clock 0 but CLOCK_REALTIME is 3. + // However, we need to always be able to distinguish this from CLOCK_MONOTONIC. + clock_id == ecx.eval_libc_i32("CLOCK_REALTIME") + || (clock_id == 0 && clock_id != ecx.eval_libc_i32("CLOCK_MONOTONIC")) +} -// Our chosen memory layout for the emulated conditional variable (does not have -// to match the platform layout!): +fn cond_clock_offset<'mir, 'tcx: 'mir>(ecx: &MiriInterpCx<'mir, 'tcx>) -> u64 { + // macOS doesn't have a clock attribute, but to keep the code uniform we store + // a clock ID in the pthread_cond_t anyway. There's enough space. + let offset = 8; + + // Sanity-check this against PTHREAD_COND_INITIALIZER (but only once): + // the clock must start out as CLOCK_REALTIME. + static SANITY: AtomicBool = AtomicBool::new(false); + if !SANITY.swap(true, Ordering::Relaxed) { + let static_initializer = ecx.eval_path(&["libc", "PTHREAD_COND_INITIALIZER"]); + let id_field = static_initializer + .offset(Size::from_bytes(offset), ecx.machine.layouts.i32, ecx) + .unwrap(); + let id = ecx.read_scalar(&id_field).unwrap().to_i32().unwrap(); + assert!( + is_cond_clock_realtime(ecx, id), + "PTHREAD_COND_INITIALIZER is incompatible with our pthread_cond layout: clock is not CLOCK_REALTIME" + ); + } -// bytes 0-3: reserved for signature on macOS -// bytes 4-7: the conditional variable id as u32 or 0 if id is not assigned yet. -// bytes 8-11: the clock id constant as i32 + offset +} fn cond_get_id<'mir, 'tcx: 'mir>( ecx: &mut MiriInterpCx<'mir, 'tcx>, cond_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx, CondvarId> { - ecx.condvar_get_or_create_id(cond_op, ecx.libc_ty_layout("pthread_cond_t"), 4) + ecx.condvar_get_or_create_id( + cond_op, + ecx.libc_ty_layout("pthread_cond_t"), + cond_id_offset(ecx)?, + ) } fn cond_reset_id<'mir, 'tcx: 'mir>( @@ -186,7 +339,7 @@ fn cond_reset_id<'mir, 'tcx: 'mir>( ) -> InterpResult<'tcx, ()> { ecx.deref_pointer_and_write( cond_op, - 4, + cond_id_offset(ecx)?, Scalar::from_i32(0), ecx.libc_ty_layout("pthread_cond_t"), ecx.machine.layouts.u32, @@ -199,7 +352,7 @@ fn cond_get_clock_id<'mir, 'tcx: 'mir>( ) -> InterpResult<'tcx, i32> { ecx.deref_pointer_and_read( cond_op, - 8, + cond_clock_offset(ecx), ecx.libc_ty_layout("pthread_cond_t"), ecx.machine.layouts.i32, )? @@ -213,7 +366,7 @@ fn cond_set_clock_id<'mir, 'tcx: 'mir>( ) -> InterpResult<'tcx, ()> { ecx.deref_pointer_and_write( cond_op, - 8, + cond_clock_offset(ecx), Scalar::from_i32(clock_id), ecx.libc_ty_layout("pthread_cond_t"), ecx.machine.layouts.i32, @@ -225,9 +378,10 @@ fn cond_set_clock_id<'mir, 'tcx: 'mir>( fn reacquire_cond_mutex<'mir, 'tcx: 'mir>( ecx: &mut MiriInterpCx<'mir, 'tcx>, thread: ThreadId, + condvar: CondvarId, mutex: MutexId, ) -> InterpResult<'tcx> { - ecx.unblock_thread(thread); + ecx.unblock_thread(thread, BlockReason::Condvar(condvar)); if ecx.mutex_is_locked(mutex) { ecx.mutex_enqueue_and_block(mutex, thread); } else { @@ -242,9 +396,10 @@ fn reacquire_cond_mutex<'mir, 'tcx: 'mir>( fn post_cond_signal<'mir, 'tcx: 'mir>( ecx: &mut MiriInterpCx<'mir, 'tcx>, thread: ThreadId, + condvar: CondvarId, mutex: MutexId, ) -> InterpResult<'tcx> { - reacquire_cond_mutex(ecx, thread, mutex)?; + reacquire_cond_mutex(ecx, thread, condvar, mutex)?; // Waiting for the mutex is not included in the waiting time because we need // to acquire the mutex always even if we get a timeout. ecx.unregister_timeout_callback_if_exists(thread); @@ -256,6 +411,7 @@ fn post_cond_signal<'mir, 'tcx: 'mir>( fn release_cond_mutex_and_block<'mir, 'tcx: 'mir>( ecx: &mut MiriInterpCx<'mir, 'tcx>, active_thread: ThreadId, + condvar: CondvarId, mutex: MutexId, ) -> InterpResult<'tcx> { if let Some(old_locked_count) = ecx.mutex_unlock(mutex, active_thread) { @@ -265,7 +421,7 @@ fn release_cond_mutex_and_block<'mir, 'tcx: 'mir>( } else { throw_ub_format!("awaiting on unlocked or owned by a different thread mutex"); } - ecx.block_thread(active_thread); + ecx.block_thread(active_thread, BlockReason::Condvar(condvar)); Ok(()) } @@ -277,13 +433,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); - if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") { - throw_unsup_format!( - "`pthread_mutexattr_init` is not supported on {}", - this.tcx.sess.target.os - ); - } - let default_kind = this.eval_libc_i32("PTHREAD_MUTEX_DEFAULT"); mutexattr_set_kind(this, attr_op, default_kind)?; @@ -366,13 +515,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); - if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") { - throw_unsup_format!( - "`pthread_mutex_init` is not supported on {}", - this.tcx.sess.target.os - ); - } - let attr = this.read_pointer(attr_op)?; let kind = if this.ptr_is_null(attr)? { this.eval_libc_i32("PTHREAD_MUTEX_DEFAULT") @@ -527,13 +669,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); - if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") { - throw_unsup_format!( - "`pthread_rwlock_rdlock` is not supported on {}", - this.tcx.sess.target.os - ); - } - let id = rwlock_get_id(this, rwlock_op)?; let active_thread = this.get_active_thread(); @@ -552,13 +687,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); - if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") { - throw_unsup_format!( - "`pthread_rwlock_tryrdlock` is not supported on {}", - this.tcx.sess.target.os - ); - } - let id = rwlock_get_id(this, rwlock_op)?; let active_thread = this.get_active_thread(); @@ -576,13 +704,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); - if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") { - throw_unsup_format!( - "`pthread_rwlock_wrlock` is not supported on {}", - this.tcx.sess.target.os - ); - } - let id = rwlock_get_id(this, rwlock_op)?; let active_thread = this.get_active_thread(); @@ -613,13 +734,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); - if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") { - throw_unsup_format!( - "`pthread_rwlock_trywrlock` is not supported on {}", - this.tcx.sess.target.os - ); - } - let id = rwlock_get_id(this, rwlock_op)?; let active_thread = this.get_active_thread(); @@ -637,13 +751,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); - if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") { - throw_unsup_format!( - "`pthread_rwlock_unlock` is not supported on {}", - this.tcx.sess.target.os - ); - } - let id = rwlock_get_id(this, rwlock_op)?; let active_thread = this.get_active_thread(); @@ -663,13 +770,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); - if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") { - throw_unsup_format!( - "`pthread_rwlock_destroy` is not supported on {}", - this.tcx.sess.target.os - ); - } - let id = rwlock_get_id(this, rwlock_op)?; if this.rwlock_is_locked(id) { @@ -694,19 +794,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); - if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") { - throw_unsup_format!( - "`pthread_condattr_init` is not supported on {}", - this.tcx.sess.target.os - ); + // no clock attribute on macOS + if this.tcx.sess.target.os != "macos" { + // The default value of the clock attribute shall refer to the system + // clock. + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_condattr_setclock.html + let default_clock_id = this.eval_libc_i32("CLOCK_REALTIME"); + condattr_set_clock_id(this, attr_op, default_clock_id)?; } - // The default value of the clock attribute shall refer to the system - // clock. - // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_condattr_setclock.html - let default_clock_id = this.eval_libc_i32("CLOCK_REALTIME"); - condattr_set_clock_id(this, attr_op, default_clock_id)?; - Ok(0) } @@ -750,8 +846,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); // Destroying an uninit pthread_condattr is UB, so check to make sure it's not uninit. - condattr_get_clock_id(this, attr_op)?; + // There's no clock attribute on macOS. + if this.tcx.sess.target.os != "macos" { + condattr_get_clock_id(this, attr_op)?; + } + // De-init the entire thing. // This might lead to false positives, see comment in pthread_mutexattr_destroy this.write_uninit( &this.deref_pointer_as(attr_op, this.libc_ty_layout("pthread_condattr_t"))?, @@ -767,15 +867,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); - if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") { - throw_unsup_format!( - "`pthread_cond_init` is not supported on {}", - this.tcx.sess.target.os - ); - } - let attr = this.read_pointer(attr_op)?; - let clock_id = if this.ptr_is_null(attr)? { + // Default clock if `attr` is null, and on macOS where there is no clock attribute. + let clock_id = if this.ptr_is_null(attr)? || this.tcx.sess.target.os == "macos" { this.eval_libc_i32("CLOCK_REALTIME") } else { condattr_get_clock_id(this, attr_op)? @@ -792,12 +886,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn pthread_cond_signal(&mut self, cond_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); let id = cond_get_id(this, cond_op)?; - if let Some((thread, lock)) = this.condvar_signal(id) { - if let CondvarLock::Mutex(mutex) = lock { - post_cond_signal(this, thread, mutex)?; - } else { - panic!("condvar should not have an rwlock on unix"); - } + if let Some((thread, mutex)) = this.condvar_signal(id) { + post_cond_signal(this, thread, id, mutex)?; } Ok(0) @@ -810,12 +900,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); let id = cond_get_id(this, cond_op)?; - while let Some((thread, lock)) = this.condvar_signal(id) { - if let CondvarLock::Mutex(mutex) = lock { - post_cond_signal(this, thread, mutex)?; - } else { - panic!("condvar should not have an rwlock on unix"); - } + while let Some((thread, mutex)) = this.condvar_signal(id) { + post_cond_signal(this, thread, id, mutex)?; } Ok(0) @@ -832,8 +918,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let mutex_id = mutex_get_id(this, mutex_op)?; let active_thread = this.get_active_thread(); - release_cond_mutex_and_block(this, active_thread, mutex_id)?; - this.condvar_wait(id, active_thread, CondvarLock::Mutex(mutex_id)); + release_cond_mutex_and_block(this, active_thread, id, mutex_id)?; + this.condvar_wait(id, active_thread, mutex_id); Ok(0) } @@ -864,17 +950,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } }; - let timeout_time = if clock_id == this.eval_libc_i32("CLOCK_REALTIME") { + let timeout_time = if is_cond_clock_realtime(this, clock_id) { this.check_no_isolation("`pthread_cond_timedwait` with `CLOCK_REALTIME`")?; - Time::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap()) + CallbackTime::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap()) } else if clock_id == this.eval_libc_i32("CLOCK_MONOTONIC") { - Time::Monotonic(this.machine.clock.anchor().checked_add(duration).unwrap()) + CallbackTime::Monotonic(this.machine.clock.anchor().checked_add(duration).unwrap()) } else { throw_unsup_format!("unsupported clock id: {}", clock_id); }; - release_cond_mutex_and_block(this, active_thread, mutex_id)?; - this.condvar_wait(id, active_thread, CondvarLock::Mutex(mutex_id)); + release_cond_mutex_and_block(this, active_thread, id, mutex_id)?; + this.condvar_wait(id, active_thread, mutex_id); // We return success for now and override it in the timeout callback. this.write_scalar(Scalar::from_i32(0), dest)?; @@ -897,7 +983,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn call(&self, ecx: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> { // We are not waiting for the condvar any more, wait for the // mutex instead. - reacquire_cond_mutex(ecx, self.active_thread, self.mutex_id)?; + reacquire_cond_mutex(ecx, self.active_thread, self.id, self.mutex_id)?; // Remove the thread from the conditional variable. ecx.condvar_remove_waiter(self.id, self.active_thread); diff --git a/src/shims/unix/thread.rs b/src/shims/unix/thread.rs index 2a56cd35dc..9e09401a81 100644 --- a/src/shims/unix/thread.rs +++ b/src/shims/unix/thread.rs @@ -42,7 +42,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { throw_unsup_format!("Miri supports pthread_join only with retval==NULL"); } - let thread_id = this.read_target_usize(thread)?; + let thread_id = this.read_scalar(thread)?.to_int(this.libc_ty_layout("pthread_t").size)?; this.join_thread_exclusive(thread_id.try_into().expect("thread ID should fit in u32"))?; Ok(0) @@ -51,7 +51,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn pthread_detach(&mut self, thread: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); - let thread_id = this.read_target_usize(thread)?; + let thread_id = this.read_scalar(thread)?.to_int(this.libc_ty_layout("pthread_t").size)?; this.detach_thread( thread_id.try_into().expect("thread ID should fit in u32"), /*allow_terminated_joined*/ false, @@ -64,7 +64,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); let thread_id = this.get_active_thread(); - Ok(Scalar::from_target_usize(thread_id.into(), this)) + Ok(Scalar::from_uint(thread_id.to_u32(), this.libc_ty_layout("pthread_t").size)) } /// Set the name of the current thread. `max_name_len` is the maximal length of the name @@ -77,7 +77,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - let thread = ThreadId::try_from(thread.to_target_usize(this)?).unwrap(); + let thread = thread.to_int(this.libc_ty_layout("pthread_t").size)?; + let thread = ThreadId::try_from(thread).unwrap(); let name = name.to_pointer(this)?; let name = this.read_c_str(name)?.to_owned(); @@ -100,7 +101,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - let thread = ThreadId::try_from(thread.to_target_usize(this)?).unwrap(); + let thread = thread.to_int(this.libc_ty_layout("pthread_t").size)?; + let thread = ThreadId::try_from(thread).unwrap(); let name_out = name_out.to_pointer(this)?; let len = len.to_target_usize(this)?; diff --git a/src/shims/windows/env.rs b/src/shims/windows/env.rs new file mode 100644 index 0000000000..0e52959b76 --- /dev/null +++ b/src/shims/windows/env.rs @@ -0,0 +1,262 @@ +use std::env; +use std::ffi::{OsStr, OsString}; +use std::io::ErrorKind; + +use rustc_data_structures::fx::FxHashMap; + +use crate::*; +use helpers::windows_check_buffer_size; + +#[derive(Default)] +pub struct WindowsEnvVars { + /// Stores the environment variables. + map: FxHashMap, +} + +impl VisitProvenance for WindowsEnvVars { + fn visit_provenance(&self, _visit: &mut VisitWith<'_>) { + let WindowsEnvVars { map: _ } = self; + } +} + +impl WindowsEnvVars { + pub(crate) fn new<'mir, 'tcx>( + _ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + env_vars: FxHashMap, + ) -> InterpResult<'tcx, Self> { + Ok(Self { map: env_vars }) + } + + /// Implementation detail for [`InterpCx::get_env_var`]. + pub(crate) fn get<'tcx>(&self, name: &OsStr) -> InterpResult<'tcx, Option> { + Ok(self.map.get(name).cloned()) + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + #[allow(non_snake_case)] + fn GetEnvironmentVariableW( + &mut self, + name_op: &OpTy<'tcx, Provenance>, // LPCWSTR + buf_op: &OpTy<'tcx, Provenance>, // LPWSTR + size_op: &OpTy<'tcx, Provenance>, // DWORD + ) -> InterpResult<'tcx, Scalar> { + // ^ Returns DWORD (u32 on Windows) + + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetEnvironmentVariableW"); + + let name_ptr = this.read_pointer(name_op)?; + let buf_ptr = this.read_pointer(buf_op)?; + let buf_size = this.read_scalar(size_op)?.to_u32()?; // in characters + + let name = this.read_os_str_from_wide_str(name_ptr)?; + Ok(match this.machine.env_vars.windows().map.get(&name).cloned() { + Some(val) => { + Scalar::from_u32(windows_check_buffer_size(this.write_os_str_to_wide_str( + &val, + buf_ptr, + buf_size.into(), + )?)) + // This can in fact return 0. It is up to the caller to set last_error to 0 + // beforehand and check it afterwards to exclude that case. + } + None => { + let envvar_not_found = this.eval_windows("c", "ERROR_ENVVAR_NOT_FOUND"); + this.set_last_error(envvar_not_found)?; + Scalar::from_u32(0) // return zero upon failure + } + }) + } + + #[allow(non_snake_case)] + fn GetEnvironmentStringsW(&mut self) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetEnvironmentStringsW"); + + // Info on layout of environment blocks in Windows: + // https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables + let mut env_vars = std::ffi::OsString::new(); + for (name, value) in this.machine.env_vars.windows().map.iter() { + env_vars.push(name); + env_vars.push("="); + env_vars.push(value); + env_vars.push("\0"); + } + // Allocate environment block & Store environment variables to environment block. + // Final null terminator(block terminator) is added by `alloc_os_str_to_wide_str`. + let envblock_ptr = + this.alloc_os_str_as_wide_str(&env_vars, MiriMemoryKind::Runtime.into())?; + // If the function succeeds, the return value is a pointer to the environment block of the current process. + Ok(envblock_ptr) + } + + #[allow(non_snake_case)] + fn FreeEnvironmentStringsW( + &mut self, + env_block_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "FreeEnvironmentStringsW"); + + let env_block_ptr = this.read_pointer(env_block_op)?; + this.deallocate_ptr(env_block_ptr, None, MiriMemoryKind::Runtime.into())?; + // If the function succeeds, the return value is nonzero. + Ok(Scalar::from_i32(1)) + } + + #[allow(non_snake_case)] + fn SetEnvironmentVariableW( + &mut self, + name_op: &OpTy<'tcx, Provenance>, // LPCWSTR + value_op: &OpTy<'tcx, Provenance>, // LPCWSTR + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "SetEnvironmentVariableW"); + + let name_ptr = this.read_pointer(name_op)?; + let value_ptr = this.read_pointer(value_op)?; + + if this.ptr_is_null(name_ptr)? { + // ERROR CODE is not clearly explained in docs.. For now, throw UB instead. + throw_ub_format!("pointer to environment variable name is NULL"); + } + + let name = this.read_os_str_from_wide_str(name_ptr)?; + if name.is_empty() { + throw_unsup_format!("environment variable name is an empty string"); + } else if name.to_string_lossy().contains('=') { + throw_unsup_format!("environment variable name contains '='"); + } else if this.ptr_is_null(value_ptr)? { + // Delete environment variable `{name}` if it exists. + this.machine.env_vars.windows_mut().map.remove(&name); + Ok(this.eval_windows("c", "TRUE")) + } else { + let value = this.read_os_str_from_wide_str(value_ptr)?; + this.machine.env_vars.windows_mut().map.insert(name, value); + Ok(this.eval_windows("c", "TRUE")) + } + } + + #[allow(non_snake_case)] + fn GetCurrentDirectoryW( + &mut self, + size_op: &OpTy<'tcx, Provenance>, // DWORD + buf_op: &OpTy<'tcx, Provenance>, // LPTSTR + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetCurrentDirectoryW"); + + let size = u64::from(this.read_scalar(size_op)?.to_u32()?); + let buf = this.read_pointer(buf_op)?; + + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`GetCurrentDirectoryW`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; + return Ok(Scalar::from_u32(0)); + } + + // If we cannot get the current directory, we return 0 + match env::current_dir() { + Ok(cwd) => { + // This can in fact return 0. It is up to the caller to set last_error to 0 + // beforehand and check it afterwards to exclude that case. + return Ok(Scalar::from_u32(windows_check_buffer_size( + this.write_path_to_wide_str(&cwd, buf, size)?, + ))); + } + Err(e) => this.set_last_error_from_io_error(e)?, + } + Ok(Scalar::from_u32(0)) + } + + #[allow(non_snake_case)] + fn SetCurrentDirectoryW( + &mut self, + path_op: &OpTy<'tcx, Provenance>, // LPCTSTR + ) -> InterpResult<'tcx, Scalar> { + // ^ Returns BOOL (i32 on Windows) + + let this = self.eval_context_mut(); + this.assert_target_os("windows", "SetCurrentDirectoryW"); + + let path = this.read_path_from_wide_str(this.read_pointer(path_op)?)?; + + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`SetCurrentDirectoryW`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; + + return Ok(this.eval_windows("c", "FALSE")); + } + + match env::set_current_dir(path) { + Ok(()) => Ok(this.eval_windows("c", "TRUE")), + Err(e) => { + this.set_last_error_from_io_error(e)?; + Ok(this.eval_windows("c", "FALSE")) + } + } + } + + #[allow(non_snake_case)] + fn GetCurrentProcessId(&mut self) -> InterpResult<'tcx, u32> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetCurrentProcessId"); + this.check_no_isolation("`GetCurrentProcessId`")?; + + Ok(std::process::id()) + } + + #[allow(non_snake_case)] + fn GetUserProfileDirectoryW( + &mut self, + token: &OpTy<'tcx, Provenance>, // HANDLE + buf: &OpTy<'tcx, Provenance>, // LPWSTR + size: &OpTy<'tcx, Provenance>, // LPDWORD + ) -> InterpResult<'tcx, Scalar> // returns BOOL + { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetUserProfileDirectoryW"); + this.check_no_isolation("`GetUserProfileDirectoryW`")?; + + let token = this.read_target_isize(token)?; + let buf = this.read_pointer(buf)?; + let size = this.deref_pointer(size)?; + + if token != -4 { + throw_unsup_format!( + "GetUserProfileDirectoryW: only CURRENT_PROCESS_TOKEN is supported" + ); + } + + // See for docs. + Ok(match directories::UserDirs::new() { + Some(dirs) => { + let home = dirs.home_dir(); + let size_avail = if this.ptr_is_null(size.ptr())? { + 0 // if the buf pointer is null, we can't write to it; `size` will be updated to the required length + } else { + this.read_scalar(&size)?.to_u32()? + }; + // Of course we cannot use `windows_check_buffer_size` here since this uses + // a different method for dealing with a too-small buffer than the other functions... + let (success, len) = this.write_path_to_wide_str(home, buf, size_avail.into())?; + // The Windows docs just say that this is written on failure. But std + // seems to rely on it always being written. + this.write_scalar(Scalar::from_u32(len.try_into().unwrap()), &size)?; + if success { + Scalar::from_i32(1) // return TRUE + } else { + this.set_last_error(this.eval_windows("c", "ERROR_INSUFFICIENT_BUFFER"))?; + Scalar::from_i32(0) // return FALSE + } + } + None => { + // We have to pick some error code. + this.set_last_error(this.eval_windows("c", "ERROR_BAD_USER_PROFILE"))?; + Scalar::from_i32(0) // return FALSE + } + }) + } +} diff --git a/src/shims/windows/foreign_items.rs b/src/shims/windows/foreign_items.rs index 9c6994cec7..086abf19c5 100644 --- a/src/shims/windows/foreign_items.rs +++ b/src/shims/windows/foreign_items.rs @@ -1,18 +1,19 @@ +use std::ffi::OsStr; +use std::io; use std::iter; +use std::path::{self, Path, PathBuf}; use std::str; use rustc_span::Symbol; -use rustc_target::abi::Size; +use rustc_target::abi::{Align, Size}; use rustc_target::spec::abi::Abi; use crate::shims::os_str::bytes_to_os_str; +use crate::shims::windows::*; use crate::*; -use shims::foreign_items::EmulateForeignItemResult; -use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle}; -use shims::windows::sync::EvalContextExt as _; -use shims::windows::thread::EvalContextExt as _; +use shims::windows::handle::{Handle, PseudoHandle}; -fn is_dyn_sym(name: &str) -> bool { +pub fn is_dyn_sym(name: &str) -> bool { // std does dynamic detection for these symbols matches!( name, @@ -20,6 +21,61 @@ fn is_dyn_sym(name: &str) -> bool { ) } +#[cfg(windows)] +fn win_absolute<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result> { + // We are on Windows so we can simply lte the host do this. + return Ok(path::absolute(path)); +} + +#[cfg(unix)] +#[allow(clippy::get_first, clippy::arithmetic_side_effects)] +fn win_absolute<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result> { + // We are on Unix, so we need to implement parts of the logic ourselves. + let bytes = path.as_os_str().as_encoded_bytes(); + // If it starts with `//` (these were backslashes but are already converted) + // then this is a magic special path, we just leave it unchanged. + if bytes.get(0).copied() == Some(b'/') && bytes.get(1).copied() == Some(b'/') { + return Ok(Ok(path.into())); + }; + // Special treatment for Windows' magic filenames: they are treated as being relative to `\\.\`. + let magic_filenames = &[ + "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", + "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", + ]; + if magic_filenames.iter().any(|m| m.as_bytes() == bytes) { + let mut result: Vec = br"//./".into(); + result.extend(bytes); + return Ok(Ok(bytes_to_os_str(&result)?.into())); + } + // Otherwise we try to do something kind of close to what Windows does, but this is probably not + // right in all cases. We iterate over the components between `/`, and remove trailing `.`, + // except that trailing `..` remain unchanged. + let mut result = vec![]; + let mut bytes = bytes; // the remaining bytes to process + loop { + let len = bytes.iter().position(|&b| b == b'/').unwrap_or(bytes.len()); + let mut component = &bytes[..len]; + if len >= 2 && component[len - 1] == b'.' && component[len - 2] != b'.' { + // Strip trailing `.` + component = &component[..len - 1]; + } + // Add this component to output. + result.extend(component); + // Prepare next iteration. + if len < bytes.len() { + // There's a component after this; add `/` and process remaining bytes. + result.push(b'/'); + bytes = &bytes[len + 1..]; + continue; + } else { + // This was the last component and it did not have a trailing `/`. + break; + } + } + // Let the host `absolute` function do working-dir handling + Ok(path::absolute(bytes_to_os_str(&result)?)) +} + impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn emulate_foreign_item_inner( @@ -28,7 +84,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { abi: Abi, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, EmulateForeignItemResult> { + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern. @@ -76,6 +132,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let result = this.SetCurrentDirectoryW(path)?; this.write_scalar(result, dest)?; } + "GetUserProfileDirectoryW" => { + let [token, buf, size] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let result = this.GetUserProfileDirectoryW(token, buf, size)?; + this.write_scalar(result, dest)?; + } // File related shims "NtWriteFile" => { @@ -111,7 +173,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let written = if handle == -11 || handle == -12 { // stdout/stderr - use std::io::{self, Write}; + use io::Write; let buf_cont = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(u64::from(n)))?; @@ -145,6 +207,36 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { dest, )?; } + "GetFullPathNameW" => { + let [filename, size, buffer, filepart] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + this.check_no_isolation("`GetFullPathNameW`")?; + + let filename = this.read_pointer(filename)?; + let size = this.read_scalar(size)?.to_u32()?; + let buffer = this.read_pointer(buffer)?; + let filepart = this.read_pointer(filepart)?; + + if !this.ptr_is_null(filepart)? { + throw_unsup_format!("GetFullPathNameW: non-null `lpFilePart` is not supported"); + } + + let filename = this.read_path_from_wide_str(filename)?; + let result = match win_absolute(&filename)? { + Err(err) => { + this.set_last_error_from_io_error(err)?; + Scalar::from_u32(0) // return zero upon failure + } + Ok(abs_filename) => { + Scalar::from_u32(helpers::windows_check_buffer_size( + this.write_path_to_wide_str(&abs_filename, buffer, size.into())?, + )) + // This can in fact return 0. It is up to the caller to set last_error to 0 + // beforehand and check it afterwards to exclude that case. + } + }; + this.write_scalar(result, dest)?; + } // Allocation "HeapAlloc" => { @@ -155,8 +247,21 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let size = this.read_target_usize(size)?; let heap_zero_memory = 0x00000008; // HEAP_ZERO_MEMORY let zero_init = (flags & heap_zero_memory) == heap_zero_memory; - let res = this.malloc(size, zero_init, MiriMemoryKind::WinHeap)?; - this.write_pointer(res, dest)?; + // Alignment is twice the pointer size. + // Source: + let align = this.tcx.pointer_size().bytes().strict_mul(2); + let ptr = this.allocate_ptr( + Size::from_bytes(size), + Align::from_bytes(align).unwrap(), + MiriMemoryKind::WinHeap.into(), + )?; + if zero_init { + this.write_bytes_ptr( + ptr.into(), + iter::repeat(0u8).take(usize::try_from(size).unwrap()), + )?; + } + this.write_pointer(ptr, dest)?; } "HeapFree" => { let [handle, flags, ptr] = @@ -164,23 +269,41 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.read_target_isize(handle)?; this.read_scalar(flags)?.to_u32()?; let ptr = this.read_pointer(ptr)?; - this.free(ptr, MiriMemoryKind::WinHeap)?; + // "This pointer can be NULL." It doesn't say what happens then, but presumably nothing. + // (https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapfree) + if !this.ptr_is_null(ptr)? { + this.deallocate_ptr(ptr, None, MiriMemoryKind::WinHeap.into())?; + } this.write_scalar(Scalar::from_i32(1), dest)?; } "HeapReAlloc" => { - let [handle, flags, ptr, size] = + let [handle, flags, old_ptr, size] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; this.read_target_isize(handle)?; this.read_scalar(flags)?.to_u32()?; - let ptr = this.read_pointer(ptr)?; + let old_ptr = this.read_pointer(old_ptr)?; let size = this.read_target_usize(size)?; - let res = this.realloc(ptr, size, MiriMemoryKind::WinHeap)?; - this.write_pointer(res, dest)?; + let align = this.tcx.pointer_size().bytes().strict_mul(2); // same as above + // The docs say that `old_ptr` must come from an earlier HeapAlloc or HeapReAlloc, + // so unlike C `realloc` we do *not* allow a NULL here. + // (https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heaprealloc) + let new_ptr = this.reallocate_ptr( + old_ptr, + None, + Size::from_bytes(size), + Align::from_bytes(align).unwrap(), + MiriMemoryKind::WinHeap.into(), + )?; + this.write_pointer(new_ptr, dest)?; } "LocalFree" => { let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; let ptr = this.read_pointer(ptr)?; - this.free(ptr, MiriMemoryKind::WinLocal)?; + // "If the hMem parameter is NULL, LocalFree ignores the parameter and returns NULL." + // (https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-localfree) + if !this.ptr_is_null(ptr)? { + this.deallocate_ptr(ptr, None, MiriMemoryKind::WinLocal.into())?; + } this.write_null(dest)?; } @@ -413,7 +536,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } // Miscellaneous + "ExitProcess" => { + let [code] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let code = this.read_scalar(code)?.to_u32()?; + throw_machine_stop!(TerminationInfo::Exit { code: code.into(), leak_check: false }); + } "SystemFunction036" => { + // used by getrandom 0.1 // This is really 'RtlGenRandom'. let [ptr, len] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; @@ -423,6 +553,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_scalar(Scalar::from_bool(true), dest)?; } "ProcessPrng" => { + // used by `std` let [ptr, len] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; let ptr = this.read_pointer(ptr)?; @@ -431,6 +562,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_scalar(Scalar::from_i32(1), dest)?; } "BCryptGenRandom" => { + // used by getrandom 0.2 let [algorithm, ptr, len, flags] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; let algorithm = this.read_scalar(algorithm)?; @@ -508,15 +640,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Using the host current_exe is a bit off, but consistent with Linux // (where stdlib reads /proc/self/exe). - // Unfortunately this Windows function has a crazy behavior so we can't just use - // `write_path_to_wide_str`... let path = std::env::current_exe().unwrap(); - let (all_written, size_needed) = this.write_path_to_wide_str( - &path, - filename, - size.into(), - /*truncate*/ true, - )?; + let (all_written, size_needed) = + this.write_path_to_wide_str_truncated(&path, filename, size.into())?; if all_written { // If the function succeeds, the return value is the length of the string that @@ -533,6 +659,39 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.set_last_error(insufficient_buffer)?; } } + "FormatMessageW" => { + let [flags, module, message_id, language_id, buffer, size, arguments] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + + let flags = this.read_scalar(flags)?.to_u32()?; + let _module = this.read_pointer(module)?; // seems to contain a module name + let message_id = this.read_scalar(message_id)?; + let _language_id = this.read_scalar(language_id)?.to_u32()?; + let buffer = this.read_pointer(buffer)?; + let size = this.read_scalar(size)?.to_u32()?; + let _arguments = this.read_pointer(arguments)?; + + // We only support `FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS` + // This also means `arguments` can be ignored. + if flags != 4096u32 | 512u32 { + throw_unsup_format!("FormatMessageW: unsupported flags {flags:#x}"); + } + + let error = this.try_errnum_to_io_error(message_id)?; + let formatted = match error { + Some(err) => format!("{err}"), + None => format!(""), + }; + let (complete, length) = + this.write_os_str_to_wide_str(OsStr::new(&formatted), buffer, size.into())?; + if !complete { + // The API docs don't say what happens when the buffer is not big enough... + // Let's just bail. + throw_unsup_format!("FormatMessageW: buffer not big enough"); + } + // The return value is the number of characters stored *excluding* the null terminator. + this.write_int(length.checked_sub(1).unwrap(), dest)?; + } // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. @@ -600,9 +759,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_null(dest)?; } - _ => return Ok(EmulateForeignItemResult::NotSupported), + _ => return Ok(EmulateItemResult::NotSupported), } - Ok(EmulateForeignItemResult::NeedsJumping) + Ok(EmulateItemResult::NeedsJumping) } } diff --git a/src/shims/windows/mod.rs b/src/shims/windows/mod.rs index 7688abe412..65f682b9da 100644 --- a/src/shims/windows/mod.rs +++ b/src/shims/windows/mod.rs @@ -1,5 +1,13 @@ pub mod foreign_items; +mod env; mod handle; mod sync; mod thread; + +pub use env::WindowsEnvVars; +// All the Windows-specific extension traits +pub use env::EvalContextExt as _; +pub use handle::EvalContextExt as _; +pub use sync::EvalContextExt as _; +pub use thread::EvalContextExt as _; diff --git a/src/shims/windows/sync.rs b/src/shims/windows/sync.rs index f02939f888..836b9e9259 100644 --- a/src/shims/windows/sync.rs +++ b/src/shims/windows/sync.rs @@ -170,7 +170,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { None } else { let duration = Duration::from_millis(timeout_ms.into()); - Some(Time::Monotonic(this.machine.clock.now().checked_add(duration).unwrap())) + Some(CallbackTime::Monotonic(this.machine.clock.now().checked_add(duration).unwrap())) }; // See the Linux futex implementation for why this fence exists. @@ -183,7 +183,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { if futex_val == compare_val { // If the values are the same, we have to block. - this.block_thread(thread); + this.block_thread(thread, BlockReason::Futex { addr }); this.futex_wait(addr, thread, u32::MAX); if let Some(timeout_time) = timeout_time { @@ -202,7 +202,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { impl<'mir, 'tcx: 'mir> MachineCallback<'mir, 'tcx> for Callback<'tcx> { fn call(&self, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> { - this.unblock_thread(self.thread); + this.unblock_thread(self.thread, BlockReason::Futex { addr: self.addr }); this.futex_remove_waiter(self.addr, self.thread); let error_timeout = this.eval_windows("c", "ERROR_TIMEOUT"); this.set_last_error(error_timeout)?; @@ -233,8 +233,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // See the Linux futex implementation for why this fence exists. this.atomic_fence(AtomicFenceOrd::SeqCst)?; - if let Some(thread) = this.futex_wake(ptr.addr().bytes(), u32::MAX) { - this.unblock_thread(thread); + let addr = ptr.addr().bytes(); + if let Some(thread) = this.futex_wake(addr, u32::MAX) { + this.unblock_thread(thread, BlockReason::Futex { addr }); this.unregister_timeout_callback_if_exists(thread); } @@ -248,8 +249,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // See the Linux futex implementation for why this fence exists. this.atomic_fence(AtomicFenceOrd::SeqCst)?; - while let Some(thread) = this.futex_wake(ptr.addr().bytes(), u32::MAX) { - this.unblock_thread(thread); + let addr = ptr.addr().bytes(); + while let Some(thread) = this.futex_wake(addr, u32::MAX) { + this.unblock_thread(thread, BlockReason::Futex { addr }); this.unregister_timeout_callback_if_exists(thread); } diff --git a/src/shims/x86/aesni.rs b/src/shims/x86/aesni.rs index 6c090d877e..8dc3748a12 100644 --- a/src/shims/x86/aesni.rs +++ b/src/shims/x86/aesni.rs @@ -4,7 +4,6 @@ use rustc_span::Symbol; use rustc_target::spec::abi::Abi; use crate::*; -use shims::foreign_items::EmulateForeignItemResult; impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: @@ -16,7 +15,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: abi: Abi, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, EmulateForeignItemResult> { + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); this.expect_target_feature_for_intrinsic(link_name, "aes")?; // Prefix should have already been checked. @@ -126,9 +125,9 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: } // TODO: Implement the `llvm.x86.aesni.aeskeygenassist` when possible // with an external crate. - _ => return Ok(EmulateForeignItemResult::NotSupported), + _ => return Ok(EmulateItemResult::NotSupported), } - Ok(EmulateForeignItemResult::NeedsJumping) + Ok(EmulateItemResult::NeedsJumping) } } diff --git a/src/shims/x86/avx.rs b/src/shims/x86/avx.rs index 23c78647b9..86ef180a44 100644 --- a/src/shims/x86/avx.rs +++ b/src/shims/x86/avx.rs @@ -7,10 +7,10 @@ use rustc_target::spec::abi::Abi; use super::{ bin_op_simd_float_all, conditional_dot_product, convert_float_to_int, horizontal_bin_op, - round_all, test_bits_masked, test_high_bits_masked, unary_op_ps, FloatBinOp, FloatUnaryOp, + mask_load, mask_store, round_all, test_bits_masked, test_high_bits_masked, unary_op_ps, + FloatBinOp, FloatUnaryOp, }; use crate::*; -use shims::foreign_items::EmulateForeignItemResult; impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: @@ -22,7 +22,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: abi: Abi, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, EmulateForeignItemResult> { + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); this.expect_target_feature_for_intrinsic(link_name, "avx")?; // Prefix should have already been checked. @@ -342,76 +342,8 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: this.write_scalar(Scalar::from_i32(res.into()), dest)?; } - _ => return Ok(EmulateForeignItemResult::NotSupported), + _ => return Ok(EmulateItemResult::NotSupported), } - Ok(EmulateForeignItemResult::NeedsJumping) + Ok(EmulateItemResult::NeedsJumping) } } - -/// Conditionally loads from `ptr` according the high bit of each -/// element of `mask`. `ptr` does not need to be aligned. -fn mask_load<'tcx>( - this: &mut crate::MiriInterpCx<'_, 'tcx>, - ptr: &OpTy<'tcx, Provenance>, - mask: &OpTy<'tcx, Provenance>, - dest: &MPlaceTy<'tcx, Provenance>, -) -> InterpResult<'tcx, ()> { - let (mask, mask_len) = this.operand_to_simd(mask)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - assert_eq!(dest_len, mask_len); - - let mask_item_size = mask.layout.field(this, 0).size; - let high_bit_offset = mask_item_size.bits().checked_sub(1).unwrap(); - - let ptr = this.read_pointer(ptr)?; - for i in 0..dest_len { - let mask = this.project_index(&mask, i)?; - let dest = this.project_index(&dest, i)?; - - if this.read_scalar(&mask)?.to_uint(mask_item_size)? >> high_bit_offset != 0 { - // Size * u64 is implemented as always checked - #[allow(clippy::arithmetic_side_effects)] - let ptr = ptr.wrapping_offset(dest.layout.size * i, &this.tcx); - // Unaligned copy, which is what we want. - this.mem_copy(ptr, dest.ptr(), dest.layout.size, /*nonoverlapping*/ true)?; - } else { - this.write_scalar(Scalar::from_int(0, dest.layout.size), &dest)?; - } - } - - Ok(()) -} - -/// Conditionally stores into `ptr` according the high bit of each -/// element of `mask`. `ptr` does not need to be aligned. -fn mask_store<'tcx>( - this: &mut crate::MiriInterpCx<'_, 'tcx>, - ptr: &OpTy<'tcx, Provenance>, - mask: &OpTy<'tcx, Provenance>, - value: &OpTy<'tcx, Provenance>, -) -> InterpResult<'tcx, ()> { - let (mask, mask_len) = this.operand_to_simd(mask)?; - let (value, value_len) = this.operand_to_simd(value)?; - - assert_eq!(value_len, mask_len); - - let mask_item_size = mask.layout.field(this, 0).size; - let high_bit_offset = mask_item_size.bits().checked_sub(1).unwrap(); - - let ptr = this.read_pointer(ptr)?; - for i in 0..value_len { - let mask = this.project_index(&mask, i)?; - let value = this.project_index(&value, i)?; - - if this.read_scalar(&mask)?.to_uint(mask_item_size)? >> high_bit_offset != 0 { - // Size * u64 is implemented as always checked - #[allow(clippy::arithmetic_side_effects)] - let ptr = ptr.wrapping_offset(value.layout.size * i, &this.tcx); - // Unaligned copy, which is what we want. - this.mem_copy(value.ptr(), ptr, value.layout.size, /*nonoverlapping*/ true)?; - } - } - - Ok(()) -} diff --git a/src/shims/x86/avx2.rs b/src/shims/x86/avx2.rs new file mode 100644 index 0000000000..adecf7b892 --- /dev/null +++ b/src/shims/x86/avx2.rs @@ -0,0 +1,445 @@ +use rustc_middle::mir; +use rustc_middle::ty::layout::LayoutOf as _; +use rustc_middle::ty::Ty; +use rustc_span::Symbol; +use rustc_target::spec::abi::Abi; + +use super::{ + horizontal_bin_op, int_abs, mask_load, mask_store, mpsadbw, packssdw, packsswb, packusdw, + packuswb, pmulhrsw, psign, shift_simd_by_scalar, shift_simd_by_simd, ShiftOp, +}; +use crate::*; + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: + crate::MiriInterpCxExt<'mir, 'tcx> +{ + fn emulate_x86_avx2_intrinsic( + &mut self, + link_name: Symbol, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &MPlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, EmulateItemResult> { + let this = self.eval_context_mut(); + this.expect_target_feature_for_intrinsic(link_name, "avx2")?; + // Prefix should have already been checked. + let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.avx2.").unwrap(); + + match unprefixed_name { + // Used to implement the _mm256_abs_epi{8,16,32} functions. + // Calculates the absolute value of packed 8/16/32-bit integers. + "pabs.b" | "pabs.w" | "pabs.d" => { + let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + int_abs(this, op, dest)?; + } + // Used to implement the _mm256_h{add,adds,sub}_epi{16,32} functions. + // Horizontally add / add with saturation / subtract adjacent 16/32-bit + // integer values in `left` and `right`. + "phadd.w" | "phadd.sw" | "phadd.d" | "phsub.w" | "phsub.sw" | "phsub.d" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let (which, saturating) = match unprefixed_name { + "phadd.w" | "phadd.d" => (mir::BinOp::Add, false), + "phadd.sw" => (mir::BinOp::Add, true), + "phsub.w" | "phsub.d" => (mir::BinOp::Sub, false), + "phsub.sw" => (mir::BinOp::Sub, true), + _ => unreachable!(), + }; + + horizontal_bin_op(this, which, saturating, left, right, dest)?; + } + // Used to implement `_mm{,_mask}_{i32,i64}gather_{epi32,epi64,pd,ps}` functions + // Gathers elements from `slice` using `offsets * scale` as indices. + // When the highest bit of the corresponding element of `mask` is 0, + // the value is copied from `src` instead. + "gather.d.d" | "gather.d.d.256" | "gather.d.q" | "gather.d.q.256" | "gather.q.d" + | "gather.q.d.256" | "gather.q.q" | "gather.q.q.256" | "gather.d.pd" + | "gather.d.pd.256" | "gather.q.pd" | "gather.q.pd.256" | "gather.d.ps" + | "gather.d.ps.256" | "gather.q.ps" | "gather.q.ps.256" => { + let [src, slice, offsets, mask, scale] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + assert_eq!(dest.layout, src.layout); + + let (src, _) = this.operand_to_simd(src)?; + let (offsets, offsets_len) = this.operand_to_simd(offsets)?; + let (mask, mask_len) = this.operand_to_simd(mask)?; + let (dest, dest_len) = this.mplace_to_simd(dest)?; + + // There are cases like dest: i32x4, offsets: i64x2 + // If dest has more elements than offset, extra dest elements are filled with zero. + // If offsets has more elements than dest, extra offsets are ignored. + let actual_len = dest_len.min(offsets_len); + + assert_eq!(dest_len, mask_len); + + let mask_item_size = mask.layout.field(this, 0).size; + let high_bit_offset = mask_item_size.bits().checked_sub(1).unwrap(); + + let scale = this.read_scalar(scale)?.to_i8()?; + if !matches!(scale, 1 | 2 | 4 | 8) { + panic!("invalid gather scale {scale}"); + } + let scale = i64::from(scale); + + let slice = this.read_pointer(slice)?; + for i in 0..actual_len { + let mask = this.project_index(&mask, i)?; + let dest = this.project_index(&dest, i)?; + + if this.read_scalar(&mask)?.to_uint(mask_item_size)? >> high_bit_offset != 0 { + let offset = this.project_index(&offsets, i)?; + let offset = + i64::try_from(this.read_scalar(&offset)?.to_int(offset.layout.size)?) + .unwrap(); + let ptr = slice + .wrapping_signed_offset(offset.checked_mul(scale).unwrap(), &this.tcx); + // Unaligned copy, which is what we want. + this.mem_copy( + ptr, + dest.ptr(), + dest.layout.size, + /*nonoverlapping*/ true, + )?; + } else { + this.copy_op(&this.project_index(&src, i)?, &dest)?; + } + } + for i in actual_len..dest_len { + let dest = this.project_index(&dest, i)?; + this.write_scalar(Scalar::from_int(0, dest.layout.size), &dest)?; + } + } + // Used to implement the _mm256_madd_epi16 function. + // Multiplies packed signed 16-bit integers in `left` and `right`, producing + // intermediate signed 32-bit integers. Horizontally add adjacent pairs of + // intermediate 32-bit integers, and pack the results in `dest`. + "pmadd.wd" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let (left, left_len) = this.operand_to_simd(left)?; + let (right, right_len) = this.operand_to_simd(right)?; + let (dest, dest_len) = this.mplace_to_simd(dest)?; + + assert_eq!(left_len, right_len); + assert_eq!(dest_len.checked_mul(2).unwrap(), left_len); + + for i in 0..dest_len { + let j1 = i.checked_mul(2).unwrap(); + let left1 = this.read_scalar(&this.project_index(&left, j1)?)?.to_i16()?; + let right1 = this.read_scalar(&this.project_index(&right, j1)?)?.to_i16()?; + + let j2 = j1.checked_add(1).unwrap(); + let left2 = this.read_scalar(&this.project_index(&left, j2)?)?.to_i16()?; + let right2 = this.read_scalar(&this.project_index(&right, j2)?)?.to_i16()?; + + let dest = this.project_index(&dest, i)?; + + // Multiplications are i16*i16->i32, which will not overflow. + let mul1 = i32::from(left1).checked_mul(right1.into()).unwrap(); + let mul2 = i32::from(left2).checked_mul(right2.into()).unwrap(); + // However, this addition can overflow in the most extreme case + // (-0x8000)*(-0x8000)+(-0x8000)*(-0x8000) = 0x80000000 + let res = mul1.wrapping_add(mul2); + + this.write_scalar(Scalar::from_i32(res), &dest)?; + } + } + // Used to implement the _mm256_maddubs_epi16 function. + // Multiplies packed 8-bit unsigned integers from `left` and packed + // signed 8-bit integers from `right` into 16-bit signed integers. Then, + // the saturating sum of the products with indices `2*i` and `2*i+1` + // produces the output at index `i`. + "pmadd.ub.sw" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let (left, left_len) = this.operand_to_simd(left)?; + let (right, right_len) = this.operand_to_simd(right)?; + let (dest, dest_len) = this.mplace_to_simd(dest)?; + + assert_eq!(left_len, right_len); + assert_eq!(dest_len.checked_mul(2).unwrap(), left_len); + + for i in 0..dest_len { + let j1 = i.checked_mul(2).unwrap(); + let left1 = this.read_scalar(&this.project_index(&left, j1)?)?.to_u8()?; + let right1 = this.read_scalar(&this.project_index(&right, j1)?)?.to_i8()?; + + let j2 = j1.checked_add(1).unwrap(); + let left2 = this.read_scalar(&this.project_index(&left, j2)?)?.to_u8()?; + let right2 = this.read_scalar(&this.project_index(&right, j2)?)?.to_i8()?; + + let dest = this.project_index(&dest, i)?; + + // Multiplication of a u8 and an i8 into an i16 cannot overflow. + let mul1 = i16::from(left1).checked_mul(right1.into()).unwrap(); + let mul2 = i16::from(left2).checked_mul(right2.into()).unwrap(); + let res = mul1.saturating_add(mul2); + + this.write_scalar(Scalar::from_i16(res), &dest)?; + } + } + // Used to implement the _mm_maskload_epi32, _mm_maskload_epi64, + // _mm256_maskload_epi32 and _mm256_maskload_epi64 functions. + // For the element `i`, if the high bit of the `i`-th element of `mask` + // is one, it is loaded from `ptr.wrapping_add(i)`, otherwise zero is + // loaded. + "maskload.d" | "maskload.q" | "maskload.d.256" | "maskload.q.256" => { + let [ptr, mask] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + mask_load(this, ptr, mask, dest)?; + } + // Used to implement the _mm_maskstore_epi32, _mm_maskstore_epi64, + // _mm256_maskstore_epi32 and _mm256_maskstore_epi64 functions. + // For the element `i`, if the high bit of the element `i`-th of `mask` + // is one, it is stored into `ptr.wapping_add(i)`. + // Unlike SSE2's _mm_maskmoveu_si128, these are not non-temporal stores. + "maskstore.d" | "maskstore.q" | "maskstore.d.256" | "maskstore.q.256" => { + let [ptr, mask, value] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + mask_store(this, ptr, mask, value)?; + } + // Used to implement the _mm256_mpsadbw_epu8 function. + // Compute the sum of absolute differences of quadruplets of unsigned + // 8-bit integers in `left` and `right`, and store the 16-bit results + // in `right`. Quadruplets are selected from `left` and `right` with + // offsets specified in `imm`. + // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_mpsadbw_epu8 + "mpsadbw" => { + let [left, right, imm] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + mpsadbw(this, left, right, imm, dest)?; + } + // Used to implement the _mm256_mulhrs_epi16 function. + // Multiplies packed 16-bit signed integer values, truncates the 32-bit + // product to the 18 most significant bits by right-shifting, and then + // divides the 18-bit value by 2 (rounding to nearest) by first adding + // 1 and then taking the bits `1..=16`. + // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_mulhrs_epi16 + "pmul.hr.sw" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + pmulhrsw(this, left, right, dest)?; + } + // Used to implement the _mm256_packs_epi16 function. + // Converts two 16-bit integer vectors to a single 8-bit integer + // vector with signed saturation. + "packsswb" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + packsswb(this, left, right, dest)?; + } + // Used to implement the _mm256_packs_epi32 function. + // Converts two 32-bit integer vectors to a single 16-bit integer + // vector with signed saturation. + "packssdw" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + packssdw(this, left, right, dest)?; + } + // Used to implement the _mm256_packus_epi16 function. + // Converts two 16-bit signed integer vectors to a single 8-bit + // unsigned integer vector with saturation. + "packuswb" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + packuswb(this, left, right, dest)?; + } + // Used to implement the _mm256_packus_epi32 function. + // Concatenates two 32-bit signed integer vectors and converts + // the result to a 16-bit unsigned integer vector with saturation. + "packusdw" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + packusdw(this, left, right, dest)?; + } + // Used to implement the _mm256_permutevar8x32_epi32 and + // _mm256_permutevar8x32_ps function. + // Shuffles `left` using the three low bits of each element of `right` + // as indices. + "permd" | "permps" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let (left, left_len) = this.operand_to_simd(left)?; + let (right, right_len) = this.operand_to_simd(right)?; + let (dest, dest_len) = this.mplace_to_simd(dest)?; + + assert_eq!(dest_len, left_len); + assert_eq!(dest_len, right_len); + + for i in 0..dest_len { + let dest = this.project_index(&dest, i)?; + let right = this.read_scalar(&this.project_index(&right, i)?)?.to_u32()?; + let left = this.project_index(&left, (right & 0b111).into())?; + + this.copy_op(&left, &dest)?; + } + } + // Used to implement the _mm256_permute2x128_si256 function. + // Shuffles 128-bit blocks of `a` and `b` using `imm` as pattern. + "vperm2i128" => { + let [left, right, imm] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + assert_eq!(left.layout.size.bits(), 256); + assert_eq!(right.layout.size.bits(), 256); + assert_eq!(dest.layout.size.bits(), 256); + + // Transmute to `[i128; 2]` + + let array_layout = + this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.i128, 2))?; + let left = left.transmute(array_layout, this)?; + let right = right.transmute(array_layout, this)?; + let dest = dest.transmute(array_layout, this)?; + + let imm = this.read_scalar(imm)?.to_u8()?; + + for i in 0..2 { + let dest = this.project_index(&dest, i)?; + let src = match (imm >> i.checked_mul(4).unwrap()) & 0b11 { + 0 => this.project_index(&left, 0)?, + 1 => this.project_index(&left, 1)?, + 2 => this.project_index(&right, 0)?, + 3 => this.project_index(&right, 1)?, + _ => unreachable!(), + }; + + this.copy_op(&src, &dest)?; + } + } + // Used to implement the _mm256_sad_epu8 function. + // Compute the absolute differences of packed unsigned 8-bit integers + // in `left` and `right`, then horizontally sum each consecutive 8 + // differences to produce four unsigned 16-bit integers, and pack + // these unsigned 16-bit integers in the low 16 bits of 64-bit elements + // in `dest`. + // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_sad_epu8 + "psad.bw" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let (left, left_len) = this.operand_to_simd(left)?; + let (right, right_len) = this.operand_to_simd(right)?; + let (dest, dest_len) = this.mplace_to_simd(dest)?; + + assert_eq!(left_len, right_len); + assert_eq!(left_len, dest_len.checked_mul(8).unwrap()); + + for i in 0..dest_len { + let dest = this.project_index(&dest, i)?; + + let mut acc: u16 = 0; + for j in 0..8 { + let src_index = i.checked_mul(8).unwrap().checked_add(j).unwrap(); + + let left = this.project_index(&left, src_index)?; + let left = this.read_scalar(&left)?.to_u8()?; + + let right = this.project_index(&right, src_index)?; + let right = this.read_scalar(&right)?.to_u8()?; + + acc = acc.checked_add(left.abs_diff(right).into()).unwrap(); + } + + this.write_scalar(Scalar::from_u64(acc.into()), &dest)?; + } + } + // Used to implement the _mm256_shuffle_epi8 intrinsic. + // Shuffles bytes from `left` using `right` as pattern. + // Each 128-bit block is shuffled independently. + "pshuf.b" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let (left, left_len) = this.operand_to_simd(left)?; + let (right, right_len) = this.operand_to_simd(right)?; + let (dest, dest_len) = this.mplace_to_simd(dest)?; + + assert_eq!(dest_len, left_len); + assert_eq!(dest_len, right_len); + + for i in 0..dest_len { + let right = this.read_scalar(&this.project_index(&right, i)?)?.to_u8()?; + let dest = this.project_index(&dest, i)?; + + let res = if right & 0x80 == 0 { + // Shuffle each 128-bit (16-byte) block independently. + let j = u64::from(right % 16).checked_add(i & !15).unwrap(); + this.read_scalar(&this.project_index(&left, j)?)? + } else { + // If the highest bit in `right` is 1, write zero. + Scalar::from_u8(0) + }; + + this.write_scalar(res, &dest)?; + } + } + // Used to implement the _mm256_sign_epi{8,16,32} functions. + // Negates elements from `left` when the corresponding element in + // `right` is negative. If an element from `right` is zero, zero + // is writen to the corresponding output element. + // Basically, we multiply `left` with `right.signum()`. + "psign.b" | "psign.w" | "psign.d" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + psign(this, left, right, dest)?; + } + // Used to implement the _mm256_{sll,srl,sra}_epi{16,32,64} functions + // (except _mm256_sra_epi64, which is not available in AVX2). + // Shifts N-bit packed integers in left by the amount in right. + // `right` is as 128-bit vector. but it is interpreted as a single + // 64-bit integer (remaining bits are ignored). + // For logic shifts, when right is larger than N - 1, zero is produced. + // For arithmetic shifts, when right is larger than N - 1, the sign bit + // is copied to remaining bits. + "psll.w" | "psrl.w" | "psra.w" | "psll.d" | "psrl.d" | "psra.d" | "psll.q" + | "psrl.q" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let which = match unprefixed_name { + "psll.w" | "psll.d" | "psll.q" => ShiftOp::Left, + "psrl.w" | "psrl.d" | "psrl.q" => ShiftOp::RightLogic, + "psra.w" | "psra.d" => ShiftOp::RightArith, + _ => unreachable!(), + }; + + shift_simd_by_scalar(this, left, right, which, dest)?; + } + // Used to implement the _mm{,256}_{sllv,srlv,srav}_epi{32,64} functions + // (except _mm{,256}_srav_epi64, which are not available in AVX2). + "psllv.d" | "psllv.d.256" | "psllv.q" | "psllv.q.256" | "psrlv.d" | "psrlv.d.256" + | "psrlv.q" | "psrlv.q.256" | "psrav.d" | "psrav.d.256" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let which = match unprefixed_name { + "psllv.d" | "psllv.d.256" | "psllv.q" | "psllv.q.256" => ShiftOp::Left, + "psrlv.d" | "psrlv.d.256" | "psrlv.q" | "psrlv.q.256" => ShiftOp::RightLogic, + "psrav.d" | "psrav.d.256" => ShiftOp::RightArith, + _ => unreachable!(), + }; + + shift_simd_by_simd(this, left, right, which, dest)?; + } + _ => return Ok(EmulateItemResult::NotSupported), + } + Ok(EmulateItemResult::NeedsJumping) + } +} diff --git a/src/shims/x86/mod.rs b/src/shims/x86/mod.rs index 7b7921219e..58d6db1886 100644 --- a/src/shims/x86/mod.rs +++ b/src/shims/x86/mod.rs @@ -10,10 +10,10 @@ use rustc_target::spec::abi::Abi; use crate::*; use helpers::bool_to_simd_element; -use shims::foreign_items::EmulateForeignItemResult; mod aesni; mod avx; +mod avx2; mod sse; mod sse2; mod sse3; @@ -30,7 +30,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: abi: Abi, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, EmulateForeignItemResult> { + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); // Prefix should have already been checked. let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.").unwrap(); @@ -42,7 +42,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: // https://www.intel.com/content/www/us/en/docs/cpp-compiler/developer-guide-reference/2021-8/addcarry-u32-addcarry-u64.html "addcarry.32" | "addcarry.64" => { if unprefixed_name == "addcarry.64" && this.tcx.sess.target.arch != "x86_64" { - return Ok(EmulateForeignItemResult::NotSupported); + return Ok(EmulateItemResult::NotSupported); } let [c_in, a, b] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?; @@ -68,7 +68,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: // https://www.intel.com/content/www/us/en/docs/cpp-compiler/developer-guide-reference/2021-8/subborrow-u32-subborrow-u64.html "subborrow.32" | "subborrow.64" => { if unprefixed_name == "subborrow.64" && this.tcx.sess.target.arch != "x86_64" { - return Ok(EmulateForeignItemResult::NotSupported); + return Ok(EmulateItemResult::NotSupported); } let [b_in, a, b] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?; @@ -136,10 +136,15 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: this, link_name, abi, args, dest, ); } + name if name.starts_with("avx2.") => { + return avx2::EvalContextExt::emulate_x86_avx2_intrinsic( + this, link_name, abi, args, dest, + ); + } - _ => return Ok(EmulateForeignItemResult::NotSupported), + _ => return Ok(EmulateItemResult::NotSupported), } - Ok(EmulateForeignItemResult::NeedsJumping) + Ok(EmulateItemResult::NeedsJumping) } } @@ -195,7 +200,7 @@ impl FloatBinOp { ) -> InterpResult<'tcx, Self> { // Only bits 0..=4 are used, remaining should be zero. if imm & !0b1_1111 != 0 { - throw_unsup_format!("invalid `imm` parameter of {intrinsic}: 0x{imm:x}"); + panic!("invalid `imm` parameter of {intrinsic}: 0x{imm:x}"); } // Bit 4 specifies whether the operation is quiet or signaling, which // we do not care in Miri. @@ -468,6 +473,141 @@ fn unary_op_ps<'tcx>( Ok(()) } +enum ShiftOp { + /// Shift left, logically (shift in zeros) -- same as shift left, arithmetically + Left, + /// Shift right, logically (shift in zeros) + RightLogic, + /// Shift right, arithmetically (shift in sign) + RightArith, +} + +/// Shifts each element of `left` by a scalar amount. The shift amount +/// is determined by the lowest 64 bits of `right` (which is a 128-bit vector). +/// +/// For logic shifts, when right is larger than BITS - 1, zero is produced. +/// For arithmetic right-shifts, when right is larger than BITS - 1, the sign +/// bit is copied to all bits. +fn shift_simd_by_scalar<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + left: &OpTy<'tcx, Provenance>, + right: &OpTy<'tcx, Provenance>, + which: ShiftOp, + dest: &MPlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + let (left, left_len) = this.operand_to_simd(left)?; + let (dest, dest_len) = this.mplace_to_simd(dest)?; + + assert_eq!(dest_len, left_len); + // `right` may have a different length, and we only care about its + // lowest 64bit anyway. + + // Get the 64-bit shift operand and convert it to the type expected + // by checked_{shl,shr} (u32). + // It is ok to saturate the value to u32::MAX because any value + // above BITS - 1 will produce the same result. + let shift = u32::try_from(extract_first_u64(this, right)?).unwrap_or(u32::MAX); + + for i in 0..dest_len { + let left = this.read_scalar(&this.project_index(&left, i)?)?; + let dest = this.project_index(&dest, i)?; + + let res = match which { + ShiftOp::Left => { + let left = left.to_uint(dest.layout.size)?; + let res = left.checked_shl(shift).unwrap_or(0); + // `truncate` is needed as left-shift can make the absolute value larger. + Scalar::from_uint(dest.layout.size.truncate(res), dest.layout.size) + } + ShiftOp::RightLogic => { + let left = left.to_uint(dest.layout.size)?; + let res = left.checked_shr(shift).unwrap_or(0); + // No `truncate` needed as right-shift can only make the absolute value smaller. + Scalar::from_uint(res, dest.layout.size) + } + ShiftOp::RightArith => { + let left = left.to_int(dest.layout.size)?; + // On overflow, copy the sign bit to the remaining bits + let res = left.checked_shr(shift).unwrap_or(left >> 127); + // No `truncate` needed as right-shift can only make the absolute value smaller. + Scalar::from_int(res, dest.layout.size) + } + }; + this.write_scalar(res, &dest)?; + } + + Ok(()) +} + +/// Shifts each element of `left` by the corresponding element of `right`. +/// +/// For logic shifts, when right is larger than BITS - 1, zero is produced. +/// For arithmetic right-shifts, when right is larger than BITS - 1, the sign +/// bit is copied to all bits. +fn shift_simd_by_simd<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + left: &OpTy<'tcx, Provenance>, + right: &OpTy<'tcx, Provenance>, + which: ShiftOp, + dest: &MPlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + let (left, left_len) = this.operand_to_simd(left)?; + let (right, right_len) = this.operand_to_simd(right)?; + let (dest, dest_len) = this.mplace_to_simd(dest)?; + + assert_eq!(dest_len, left_len); + assert_eq!(dest_len, right_len); + + for i in 0..dest_len { + let left = this.read_scalar(&this.project_index(&left, i)?)?; + let right = this.read_scalar(&this.project_index(&right, i)?)?; + let dest = this.project_index(&dest, i)?; + + // It is ok to saturate the value to u32::MAX because any value + // above BITS - 1 will produce the same result. + let shift = u32::try_from(right.to_uint(dest.layout.size)?).unwrap_or(u32::MAX); + + let res = match which { + ShiftOp::Left => { + let left = left.to_uint(dest.layout.size)?; + let res = left.checked_shl(shift).unwrap_or(0); + // `truncate` is needed as left-shift can make the absolute value larger. + Scalar::from_uint(dest.layout.size.truncate(res), dest.layout.size) + } + ShiftOp::RightLogic => { + let left = left.to_uint(dest.layout.size)?; + let res = left.checked_shr(shift).unwrap_or(0); + // No `truncate` needed as right-shift can only make the absolute value smaller. + Scalar::from_uint(res, dest.layout.size) + } + ShiftOp::RightArith => { + let left = left.to_int(dest.layout.size)?; + // On overflow, copy the sign bit to the remaining bits + let res = left.checked_shr(shift).unwrap_or(left >> 127); + // No `truncate` needed as right-shift can only make the absolute value smaller. + Scalar::from_int(res, dest.layout.size) + } + }; + this.write_scalar(res, &dest)?; + } + + Ok(()) +} + +/// Takes a 128-bit vector, transmutes it to `[u64; 2]` and extracts +/// the first value. +fn extract_first_u64<'tcx>( + this: &crate::MiriInterpCx<'_, 'tcx>, + op: &OpTy<'tcx, Provenance>, +) -> InterpResult<'tcx, u64> { + // Transmute vector to `[u64; 2]` + let array_layout = this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u64, 2))?; + let op = op.transmute(array_layout, this)?; + + // Get the first u64 from the array + this.read_scalar(&this.project_index(&op, 0)?)?.to_u64() +} + // Rounds the first element of `right` according to `rounding` // and copies the remaining elements from `left`. fn round_first<'tcx, F: rustc_apfloat::Float>( @@ -543,7 +683,7 @@ fn rounding_from_imm<'tcx>(rounding: i32) -> InterpResult<'tcx, rustc_apfloat::R // SSE status register. Since we do not support modifying it from // Miri (or Rust), we assume it to be at its default mode (round-to-nearest). 0b100..=0b111 => Ok(rustc_apfloat::Round::NearestTiesToEven), - rounding => throw_unsup_format!("unsupported rounding mode 0x{rounding:02x}"), + rounding => panic!("invalid rounding mode 0x{rounding:02x}"), } } @@ -570,7 +710,7 @@ fn convert_float_to_int<'tcx>( let dest = this.project_index(&dest, i)?; let res = this.float_to_int_checked(&op, dest.layout, rnd)?.unwrap_or_else(|| { - // Fallback to minimum acording to SSE/AVX semantics. + // Fallback to minimum according to SSE/AVX semantics. ImmTy::from_int(dest.layout.size.signed_int_min(), dest.layout) }); this.write_immediate(*res, &dest)?; @@ -584,54 +724,68 @@ fn convert_float_to_int<'tcx>( Ok(()) } -/// Splits `left`, `right` and `dest` (which must be SIMD vectors) -/// into 128-bit chuncks. +/// Calculates absolute value of integers in `op` and stores the result in `dest`. /// -/// `left`, `right` and `dest` cannot have different types. +/// In case of overflow (when the operand is the minimum value), the operation +/// will wrap around. +fn int_abs<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + op: &OpTy<'tcx, Provenance>, + dest: &MPlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + let (op, op_len) = this.operand_to_simd(op)?; + let (dest, dest_len) = this.mplace_to_simd(dest)?; + + assert_eq!(op_len, dest_len); + + let zero = ImmTy::from_int(0, op.layout.field(this, 0)); + + for i in 0..dest_len { + let op = this.read_immediate(&this.project_index(&op, i)?)?; + let dest = this.project_index(&dest, i)?; + + let lt_zero = this.wrapping_binary_op(mir::BinOp::Lt, &op, &zero)?; + let res = if lt_zero.to_scalar().to_bool()? { + this.wrapping_unary_op(mir::UnOp::Neg, &op)? + } else { + op + }; + + this.write_immediate(*res, &dest)?; + } + + Ok(()) +} + +/// Splits `op` (which must be a SIMD vector) into 128-bit chuncks. /// /// Returns a tuple where: /// * The first element is the number of 128-bit chunks (let's call it `N`). /// * The second element is the number of elements per chunk (let's call it `M`). -/// * The third element is the `left` vector split into chunks, i.e, it's -/// type is `[[T; M]; N]`. -/// * The fourth element is the `right` vector split into chunks. -/// * The fifth element is the `dest` vector split into chunks. -fn split_simd_to_128bit_chunks<'tcx>( +/// * The third element is the `op` vector split into chunks, i.e, it's +/// type is `[[T; M]; N]` where `T` is the element type of `op`. +fn split_simd_to_128bit_chunks<'tcx, P: Projectable<'tcx, Provenance>>( this: &mut crate::MiriInterpCx<'_, 'tcx>, - left: &OpTy<'tcx, Provenance>, - right: &OpTy<'tcx, Provenance>, - dest: &MPlaceTy<'tcx, Provenance>, -) -> InterpResult< - 'tcx, - (u64, u64, MPlaceTy<'tcx, Provenance>, MPlaceTy<'tcx, Provenance>, MPlaceTy<'tcx, Provenance>), -> { - assert_eq!(dest.layout, left.layout); - assert_eq!(dest.layout, right.layout); + op: &P, +) -> InterpResult<'tcx, (u64, u64, P)> { + let simd_layout = op.layout(); + let (simd_len, element_ty) = simd_layout.ty.simd_size_and_type(this.tcx.tcx); - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - assert_eq!(dest_len, left_len); - assert_eq!(dest_len, right_len); - - assert_eq!(dest.layout.size.bits() % 128, 0); - let num_chunks = dest.layout.size.bits() / 128; - assert_eq!(dest_len.checked_rem(num_chunks), Some(0)); - let items_per_chunk = dest_len.checked_div(num_chunks).unwrap(); + assert_eq!(simd_layout.size.bits() % 128, 0); + let num_chunks = simd_layout.size.bits() / 128; + let items_per_chunk = simd_len.checked_div(num_chunks).unwrap(); // Transmute to `[[T; items_per_chunk]; num_chunks]` - let element_layout = left.layout.field(this, 0); - let chunked_layout = this.layout_of(Ty::new_array( - this.tcx.tcx, - Ty::new_array(this.tcx.tcx, element_layout.ty, items_per_chunk), - num_chunks, - ))?; - let left = left.transmute(chunked_layout, this)?; - let right = right.transmute(chunked_layout, this)?; - let dest = dest.transmute(chunked_layout, this)?; - - Ok((num_chunks, items_per_chunk, left, right, dest)) + let chunked_layout = this + .layout_of(Ty::new_array( + this.tcx.tcx, + Ty::new_array(this.tcx.tcx, element_ty, items_per_chunk), + num_chunks, + )) + .unwrap(); + let chunked_op = op.transmute(chunked_layout, this)?; + + Ok((num_chunks, items_per_chunk, chunked_op)) } /// Horizontaly performs `which` operation on adjacent values of @@ -651,8 +805,12 @@ fn horizontal_bin_op<'tcx>( right: &OpTy<'tcx, Provenance>, dest: &MPlaceTy<'tcx, Provenance>, ) -> InterpResult<'tcx, ()> { - let (num_chunks, items_per_chunk, left, right, dest) = - split_simd_to_128bit_chunks(this, left, right, dest)?; + assert_eq!(left.layout, dest.layout); + assert_eq!(right.layout, dest.layout); + + let (num_chunks, items_per_chunk, left) = split_simd_to_128bit_chunks(this, left)?; + let (_, _, right) = split_simd_to_128bit_chunks(this, right)?; + let (_, _, dest) = split_simd_to_128bit_chunks(this, dest)?; let middle = items_per_chunk / 2; for i in 0..num_chunks { @@ -699,8 +857,12 @@ fn conditional_dot_product<'tcx>( imm: &OpTy<'tcx, Provenance>, dest: &MPlaceTy<'tcx, Provenance>, ) -> InterpResult<'tcx, ()> { - let (num_chunks, items_per_chunk, left, right, dest) = - split_simd_to_128bit_chunks(this, left, right, dest)?; + assert_eq!(left.layout, dest.layout); + assert_eq!(right.layout, dest.layout); + + let (num_chunks, items_per_chunk, left) = split_simd_to_128bit_chunks(this, left)?; + let (_, _, right) = split_simd_to_128bit_chunks(this, right)?; + let (_, _, dest) = split_simd_to_128bit_chunks(this, dest)?; let element_layout = left.layout.field(this, 0).field(this, 0); assert!(items_per_chunk <= 4); @@ -805,3 +967,323 @@ fn test_high_bits_masked<'tcx>( Ok((direct, negated)) } + +/// Conditionally loads from `ptr` according the high bit of each +/// element of `mask`. `ptr` does not need to be aligned. +fn mask_load<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + ptr: &OpTy<'tcx, Provenance>, + mask: &OpTy<'tcx, Provenance>, + dest: &MPlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + let (mask, mask_len) = this.operand_to_simd(mask)?; + let (dest, dest_len) = this.mplace_to_simd(dest)?; + + assert_eq!(dest_len, mask_len); + + let mask_item_size = mask.layout.field(this, 0).size; + let high_bit_offset = mask_item_size.bits().checked_sub(1).unwrap(); + + let ptr = this.read_pointer(ptr)?; + for i in 0..dest_len { + let mask = this.project_index(&mask, i)?; + let dest = this.project_index(&dest, i)?; + + if this.read_scalar(&mask)?.to_uint(mask_item_size)? >> high_bit_offset != 0 { + let ptr = ptr.wrapping_offset(dest.layout.size * i, &this.tcx); + // Unaligned copy, which is what we want. + this.mem_copy(ptr, dest.ptr(), dest.layout.size, /*nonoverlapping*/ true)?; + } else { + this.write_scalar(Scalar::from_int(0, dest.layout.size), &dest)?; + } + } + + Ok(()) +} + +/// Conditionally stores into `ptr` according the high bit of each +/// element of `mask`. `ptr` does not need to be aligned. +fn mask_store<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + ptr: &OpTy<'tcx, Provenance>, + mask: &OpTy<'tcx, Provenance>, + value: &OpTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + let (mask, mask_len) = this.operand_to_simd(mask)?; + let (value, value_len) = this.operand_to_simd(value)?; + + assert_eq!(value_len, mask_len); + + let mask_item_size = mask.layout.field(this, 0).size; + let high_bit_offset = mask_item_size.bits().checked_sub(1).unwrap(); + + let ptr = this.read_pointer(ptr)?; + for i in 0..value_len { + let mask = this.project_index(&mask, i)?; + let value = this.project_index(&value, i)?; + + if this.read_scalar(&mask)?.to_uint(mask_item_size)? >> high_bit_offset != 0 { + let ptr = ptr.wrapping_offset(value.layout.size * i, &this.tcx); + // Unaligned copy, which is what we want. + this.mem_copy(value.ptr(), ptr, value.layout.size, /*nonoverlapping*/ true)?; + } + } + + Ok(()) +} + +/// Compute the sum of absolute differences of quadruplets of unsigned +/// 8-bit integers in `left` and `right`, and store the 16-bit results +/// in `right`. Quadruplets are selected from `left` and `right` with +/// offsets specified in `imm`. +/// +/// +/// +/// +/// Each 128-bit chunk is treated independently (i.e., the value for +/// the is i-th 128-bit chunk of `dest` is calculated with the i-th +/// 128-bit chunks of `left` and `right`). +fn mpsadbw<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + left: &OpTy<'tcx, Provenance>, + right: &OpTy<'tcx, Provenance>, + imm: &OpTy<'tcx, Provenance>, + dest: &MPlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + assert_eq!(left.layout, right.layout); + assert_eq!(left.layout.size, dest.layout.size); + + let (num_chunks, op_items_per_chunk, left) = split_simd_to_128bit_chunks(this, left)?; + let (_, _, right) = split_simd_to_128bit_chunks(this, right)?; + let (_, dest_items_per_chunk, dest) = split_simd_to_128bit_chunks(this, dest)?; + + assert_eq!(op_items_per_chunk, dest_items_per_chunk.checked_mul(2).unwrap()); + + let imm = this.read_scalar(imm)?.to_uint(imm.layout.size)?; + // Bit 2 of `imm` specifies the offset for indices of `left`. + // The offset is 0 when the bit is 0 or 4 when the bit is 1. + let left_offset = u64::try_from((imm >> 2) & 1).unwrap().checked_mul(4).unwrap(); + // Bits 0..=1 of `imm` specify the offset for indices of + // `right` in blocks of 4 elements. + let right_offset = u64::try_from(imm & 0b11).unwrap().checked_mul(4).unwrap(); + + for i in 0..num_chunks { + let left = this.project_index(&left, i)?; + let right = this.project_index(&right, i)?; + let dest = this.project_index(&dest, i)?; + + for j in 0..dest_items_per_chunk { + let left_offset = left_offset.checked_add(j).unwrap(); + let mut res: u16 = 0; + for k in 0..4 { + let left = this + .read_scalar(&this.project_index(&left, left_offset.checked_add(k).unwrap())?)? + .to_u8()?; + let right = this + .read_scalar( + &this.project_index(&right, right_offset.checked_add(k).unwrap())?, + )? + .to_u8()?; + res = res.checked_add(left.abs_diff(right).into()).unwrap(); + } + this.write_scalar(Scalar::from_u16(res), &this.project_index(&dest, j)?)?; + } + } + + Ok(()) +} + +/// Multiplies packed 16-bit signed integer values, truncates the 32-bit +/// product to the 18 most significant bits by right-shifting, and then +/// divides the 18-bit value by 2 (rounding to nearest) by first adding +/// 1 and then taking the bits `1..=16`. +/// +/// +/// +fn pmulhrsw<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + left: &OpTy<'tcx, Provenance>, + right: &OpTy<'tcx, Provenance>, + dest: &MPlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + let (left, left_len) = this.operand_to_simd(left)?; + let (right, right_len) = this.operand_to_simd(right)?; + let (dest, dest_len) = this.mplace_to_simd(dest)?; + + assert_eq!(dest_len, left_len); + assert_eq!(dest_len, right_len); + + for i in 0..dest_len { + let left = this.read_scalar(&this.project_index(&left, i)?)?.to_i16()?; + let right = this.read_scalar(&this.project_index(&right, i)?)?.to_i16()?; + let dest = this.project_index(&dest, i)?; + + let res = + (i32::from(left).checked_mul(right.into()).unwrap() >> 14).checked_add(1).unwrap() >> 1; + + // The result of this operation can overflow a signed 16-bit integer. + // When `left` and `right` are -0x8000, the result is 0x8000. + #[allow(clippy::cast_possible_truncation)] + let res = res as i16; + + this.write_scalar(Scalar::from_i16(res), &dest)?; + } + + Ok(()) +} + +/// Packs two N-bit integer vectors to a single N/2-bit integers. +/// +/// The conversion from N-bit to N/2-bit should be provided by `f`. +/// +/// Each 128-bit chunk is treated independently (i.e., the value for +/// the is i-th 128-bit chunk of `dest` is calculated with the i-th +/// 128-bit chunks of `left` and `right`). +fn pack_generic<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + left: &OpTy<'tcx, Provenance>, + right: &OpTy<'tcx, Provenance>, + dest: &MPlaceTy<'tcx, Provenance>, + f: impl Fn(Scalar) -> InterpResult<'tcx, Scalar>, +) -> InterpResult<'tcx, ()> { + assert_eq!(left.layout, right.layout); + assert_eq!(left.layout.size, dest.layout.size); + + let (num_chunks, op_items_per_chunk, left) = split_simd_to_128bit_chunks(this, left)?; + let (_, _, right) = split_simd_to_128bit_chunks(this, right)?; + let (_, dest_items_per_chunk, dest) = split_simd_to_128bit_chunks(this, dest)?; + + assert_eq!(dest_items_per_chunk, op_items_per_chunk.checked_mul(2).unwrap()); + + for i in 0..num_chunks { + let left = this.project_index(&left, i)?; + let right = this.project_index(&right, i)?; + let dest = this.project_index(&dest, i)?; + + for j in 0..op_items_per_chunk { + let left = this.read_scalar(&this.project_index(&left, j)?)?; + let right = this.read_scalar(&this.project_index(&right, j)?)?; + let left_dest = this.project_index(&dest, j)?; + let right_dest = + this.project_index(&dest, j.checked_add(op_items_per_chunk).unwrap())?; + + let left_res = f(left)?; + let right_res = f(right)?; + + this.write_scalar(left_res, &left_dest)?; + this.write_scalar(right_res, &right_dest)?; + } + } + + Ok(()) +} + +/// Converts two 16-bit integer vectors to a single 8-bit integer +/// vector with signed saturation. +/// +/// Each 128-bit chunk is treated independently (i.e., the value for +/// the is i-th 128-bit chunk of `dest` is calculated with the i-th +/// 128-bit chunks of `left` and `right`). +fn packsswb<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + left: &OpTy<'tcx, Provenance>, + right: &OpTy<'tcx, Provenance>, + dest: &MPlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + pack_generic(this, left, right, dest, |op| { + let op = op.to_i16()?; + let res = i8::try_from(op).unwrap_or(if op < 0 { i8::MIN } else { i8::MAX }); + Ok(Scalar::from_i8(res)) + }) +} + +/// Converts two 16-bit signed integer vectors to a single 8-bit +/// unsigned integer vector with saturation. +/// +/// Each 128-bit chunk is treated independently (i.e., the value for +/// the is i-th 128-bit chunk of `dest` is calculated with the i-th +/// 128-bit chunks of `left` and `right`). +fn packuswb<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + left: &OpTy<'tcx, Provenance>, + right: &OpTy<'tcx, Provenance>, + dest: &MPlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + pack_generic(this, left, right, dest, |op| { + let op = op.to_i16()?; + let res = u8::try_from(op).unwrap_or(if op < 0 { 0 } else { u8::MAX }); + Ok(Scalar::from_u8(res)) + }) +} + +/// Converts two 32-bit integer vectors to a single 16-bit integer +/// vector with signed saturation. +/// +/// Each 128-bit chunk is treated independently (i.e., the value for +/// the is i-th 128-bit chunk of `dest` is calculated with the i-th +/// 128-bit chunks of `left` and `right`). +fn packssdw<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + left: &OpTy<'tcx, Provenance>, + right: &OpTy<'tcx, Provenance>, + dest: &MPlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + pack_generic(this, left, right, dest, |op| { + let op = op.to_i32()?; + let res = i16::try_from(op).unwrap_or(if op < 0 { i16::MIN } else { i16::MAX }); + Ok(Scalar::from_i16(res)) + }) +} + +/// Converts two 32-bit integer vectors to a single 16-bit integer +/// vector with unsigned saturation. +/// +/// Each 128-bit chunk is treated independently (i.e., the value for +/// the is i-th 128-bit chunk of `dest` is calculated with the i-th +/// 128-bit chunks of `left` and `right`). +fn packusdw<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + left: &OpTy<'tcx, Provenance>, + right: &OpTy<'tcx, Provenance>, + dest: &MPlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + pack_generic(this, left, right, dest, |op| { + let op = op.to_i32()?; + let res = u16::try_from(op).unwrap_or(if op < 0 { 0 } else { u16::MAX }); + Ok(Scalar::from_u16(res)) + }) +} + +/// Negates elements from `left` when the corresponding element in +/// `right` is negative. If an element from `right` is zero, zero +/// is writen to the corresponding output element. +/// In other words, multiplies `left` with `right.signum()`. +fn psign<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + left: &OpTy<'tcx, Provenance>, + right: &OpTy<'tcx, Provenance>, + dest: &MPlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + let (left, left_len) = this.operand_to_simd(left)?; + let (right, right_len) = this.operand_to_simd(right)?; + let (dest, dest_len) = this.mplace_to_simd(dest)?; + + assert_eq!(dest_len, left_len); + assert_eq!(dest_len, right_len); + + for i in 0..dest_len { + let dest = this.project_index(&dest, i)?; + let left = this.read_immediate(&this.project_index(&left, i)?)?; + let right = this.read_scalar(&this.project_index(&right, i)?)?.to_int(dest.layout.size)?; + + let res = this.wrapping_binary_op( + mir::BinOp::Mul, + &left, + &ImmTy::from_int(right.signum(), dest.layout), + )?; + + this.write_immediate(*res, &dest)?; + } + + Ok(()) +} diff --git a/src/shims/x86/sse.rs b/src/shims/x86/sse.rs index b8c0dfb1c7..ea967a8cf7 100644 --- a/src/shims/x86/sse.rs +++ b/src/shims/x86/sse.rs @@ -8,7 +8,6 @@ use super::{ FloatUnaryOp, }; use crate::*; -use shims::foreign_items::EmulateForeignItemResult; impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: @@ -20,7 +19,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: abi: Abi, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, EmulateForeignItemResult> { + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); this.expect_target_feature_for_intrinsic(link_name, "sse")?; // Prefix should have already been checked. @@ -182,7 +181,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: }; let res = this.float_to_int_checked(&op, dest.layout, rnd)?.unwrap_or_else(|| { - // Fallback to minimum acording to SSE semantics. + // Fallback to minimum according to SSE semantics. ImmTy::from_int(dest.layout.size.signed_int_min(), dest.layout) }); @@ -211,8 +210,8 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: this.copy_op(&this.project_index(&left, i)?, &this.project_index(&dest, i)?)?; } } - _ => return Ok(EmulateForeignItemResult::NotSupported), + _ => return Ok(EmulateItemResult::NotSupported), } - Ok(EmulateForeignItemResult::NeedsJumping) + Ok(EmulateItemResult::NeedsJumping) } } diff --git a/src/shims/x86/sse2.rs b/src/shims/x86/sse2.rs index eb2cc9d37c..31fa66a496 100644 --- a/src/shims/x86/sse2.rs +++ b/src/shims/x86/sse2.rs @@ -1,12 +1,12 @@ use rustc_apfloat::ieee::Double; -use rustc_middle::ty::layout::LayoutOf as _; -use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::spec::abi::Abi; -use super::{bin_op_simd_float_all, bin_op_simd_float_first, convert_float_to_int, FloatBinOp}; +use super::{ + bin_op_simd_float_all, bin_op_simd_float_first, convert_float_to_int, packssdw, packsswb, + packuswb, shift_simd_by_scalar, FloatBinOp, ShiftOp, +}; use crate::*; -use shims::foreign_items::EmulateForeignItemResult; impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: @@ -18,7 +18,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: abi: Abi, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, EmulateForeignItemResult> { + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); this.expect_target_feature_for_intrinsic(link_name, "sse2")?; // Prefix should have already been checked. @@ -109,156 +109,27 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: this.write_scalar(Scalar::from_u64(res.into()), &dest)?; } } - // Used to implement the _mm_{sll,srl,sra}_epi16 functions. - // Shifts 16-bit packed integers in left by the amount in right. - // Both operands are vectors of 16-bit integers. However, right is - // interpreted as a single 64-bit integer (remaining bits are ignored). - // For logic shifts, when right is larger than 15, zero is produced. - // For arithmetic shifts, when right is larger than 15, the sign bit + // Used to implement the _mm_{sll,srl,sra}_epi{16,32,64} functions + // (except _mm_sra_epi64, which is not available in SSE2). + // Shifts N-bit packed integers in left by the amount in right. + // Both operands are 128-bit vectors. However, right is interpreted as + // a single 64-bit integer (remaining bits are ignored). + // For logic shifts, when right is larger than N - 1, zero is produced. + // For arithmetic shifts, when right is larger than N - 1, the sign bit // is copied to remaining bits. - "psll.w" | "psrl.w" | "psra.w" => { + "psll.w" | "psrl.w" | "psra.w" | "psll.d" | "psrl.d" | "psra.d" | "psll.q" + | "psrl.q" => { let [left, right] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - assert_eq!(dest_len, left_len); - assert_eq!(dest_len, right_len); - - enum ShiftOp { - Sll, - Srl, - Sra, - } - let which = match unprefixed_name { - "psll.w" => ShiftOp::Sll, - "psrl.w" => ShiftOp::Srl, - "psra.w" => ShiftOp::Sra, - _ => unreachable!(), - }; - - // Get the 64-bit shift operand and convert it to the type expected - // by checked_{shl,shr} (u32). - // It is ok to saturate the value to u32::MAX because any value - // above 15 will produce the same result. - let shift = extract_first_u64(this, &right)?.try_into().unwrap_or(u32::MAX); - - for i in 0..dest_len { - let left = this.read_scalar(&this.project_index(&left, i)?)?.to_u16()?; - let dest = this.project_index(&dest, i)?; - - let res = match which { - ShiftOp::Sll => left.checked_shl(shift).unwrap_or(0), - ShiftOp::Srl => left.checked_shr(shift).unwrap_or(0), - #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] - ShiftOp::Sra => { - // Convert u16 to i16 to use arithmetic shift - let left = left as i16; - // Copy the sign bit to the remaining bits - left.checked_shr(shift).unwrap_or(left >> 15) as u16 - } - }; - - this.write_scalar(Scalar::from_u16(res), &dest)?; - } - } - // Used to implement the _mm_{sll,srl,sra}_epi32 functions. - // 32-bit equivalent to the shift functions above. - "psll.d" | "psrl.d" | "psra.d" => { - let [left, right] = - this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - assert_eq!(dest_len, left_len); - assert_eq!(dest_len, right_len); - - enum ShiftOp { - Sll, - Srl, - Sra, - } let which = match unprefixed_name { - "psll.d" => ShiftOp::Sll, - "psrl.d" => ShiftOp::Srl, - "psra.d" => ShiftOp::Sra, + "psll.w" | "psll.d" | "psll.q" => ShiftOp::Left, + "psrl.w" | "psrl.d" | "psrl.q" => ShiftOp::RightLogic, + "psra.w" | "psra.d" => ShiftOp::RightArith, _ => unreachable!(), }; - // Get the 64-bit shift operand and convert it to the type expected - // by checked_{shl,shr} (u32). - // It is ok to saturate the value to u32::MAX because any value - // above 31 will produce the same result. - let shift = extract_first_u64(this, &right)?.try_into().unwrap_or(u32::MAX); - - for i in 0..dest_len { - let left = this.read_scalar(&this.project_index(&left, i)?)?.to_u32()?; - let dest = this.project_index(&dest, i)?; - - let res = match which { - ShiftOp::Sll => left.checked_shl(shift).unwrap_or(0), - ShiftOp::Srl => left.checked_shr(shift).unwrap_or(0), - #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] - ShiftOp::Sra => { - // Convert u32 to i32 to use arithmetic shift - let left = left as i32; - // Copy the sign bit to the remaining bits - left.checked_shr(shift).unwrap_or(left >> 31) as u32 - } - }; - - this.write_scalar(Scalar::from_u32(res), &dest)?; - } - } - // Used to implement the _mm_{sll,srl}_epi64 functions. - // 64-bit equivalent to the shift functions above, except _mm_sra_epi64, - // which is not available in SSE2. - "psll.q" | "psrl.q" => { - let [left, right] = - this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - assert_eq!(dest_len, left_len); - assert_eq!(dest_len, right_len); - - enum ShiftOp { - Sll, - Srl, - } - let which = match unprefixed_name { - "psll.q" => ShiftOp::Sll, - "psrl.q" => ShiftOp::Srl, - _ => unreachable!(), - }; - - // Get the 64-bit shift operand and convert it to the type expected - // by checked_{shl,shr} (u32). - // It is ok to saturate the value to u32::MAX because any value - // above 63 will produce the same result. - let shift = this - .read_scalar(&this.project_index(&right, 0)?)? - .to_u64()? - .try_into() - .unwrap_or(u32::MAX); - - for i in 0..dest_len { - let left = this.read_scalar(&this.project_index(&left, i)?)?.to_u64()?; - let dest = this.project_index(&dest, i)?; - - let res = match which { - ShiftOp::Sll => left.checked_shl(shift).unwrap_or(0), - ShiftOp::Srl => left.checked_shr(shift).unwrap_or(0), - }; - - this.write_scalar(Scalar::from_u64(res), &dest)?; - } + shift_simd_by_scalar(this, left, right, which, dest)?; } // Used to implement the _mm_cvtps_epi32, _mm_cvttps_epi32, _mm_cvtpd_epi32 // and _mm_cvttpd_epi32 functions. @@ -304,29 +175,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: let [left, right] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - // left and right are i16x8, dest is i8x16 - assert_eq!(left_len, 8); - assert_eq!(right_len, 8); - assert_eq!(dest_len, 16); - - for i in 0..left_len { - let left = this.read_scalar(&this.project_index(&left, i)?)?.to_i16()?; - let right = this.read_scalar(&this.project_index(&right, i)?)?.to_i16()?; - let left_dest = this.project_index(&dest, i)?; - let right_dest = this.project_index(&dest, i.checked_add(left_len).unwrap())?; - - let left_res = - i8::try_from(left).unwrap_or(if left < 0 { i8::MIN } else { i8::MAX }); - let right_res = - i8::try_from(right).unwrap_or(if right < 0 { i8::MIN } else { i8::MAX }); - - this.write_scalar(Scalar::from_i8(left_res), &left_dest)?; - this.write_scalar(Scalar::from_i8(right_res), &right_dest)?; - } + packsswb(this, left, right, dest)?; } // Used to implement the _mm_packus_epi16 function. // Converts two 16-bit signed integer vectors to a single 8-bit @@ -335,28 +184,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: let [left, right] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - // left and right are i16x8, dest is u8x16 - assert_eq!(left_len, 8); - assert_eq!(right_len, 8); - assert_eq!(dest_len, 16); - - for i in 0..left_len { - let left = this.read_scalar(&this.project_index(&left, i)?)?.to_i16()?; - let right = this.read_scalar(&this.project_index(&right, i)?)?.to_i16()?; - let left_dest = this.project_index(&dest, i)?; - let right_dest = this.project_index(&dest, i.checked_add(left_len).unwrap())?; - - let left_res = u8::try_from(left).unwrap_or(if left < 0 { 0 } else { u8::MAX }); - let right_res = - u8::try_from(right).unwrap_or(if right < 0 { 0 } else { u8::MAX }); - - this.write_scalar(Scalar::from_u8(left_res), &left_dest)?; - this.write_scalar(Scalar::from_u8(right_res), &right_dest)?; - } + packuswb(this, left, right, dest)?; } // Used to implement the _mm_packs_epi32 function. // Converts two 32-bit integer vectors to a single 16-bit integer @@ -365,29 +193,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: let [left, right] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - // left and right are i32x4, dest is i16x8 - assert_eq!(left_len, 4); - assert_eq!(right_len, 4); - assert_eq!(dest_len, 8); - - for i in 0..left_len { - let left = this.read_scalar(&this.project_index(&left, i)?)?.to_i32()?; - let right = this.read_scalar(&this.project_index(&right, i)?)?.to_i32()?; - let left_dest = this.project_index(&dest, i)?; - let right_dest = this.project_index(&dest, i.checked_add(left_len).unwrap())?; - - let left_res = - i16::try_from(left).unwrap_or(if left < 0 { i16::MIN } else { i16::MAX }); - let right_res = - i16::try_from(right).unwrap_or(if right < 0 { i16::MIN } else { i16::MAX }); - - this.write_scalar(Scalar::from_i16(left_res), &left_dest)?; - this.write_scalar(Scalar::from_i16(right_res), &right_dest)?; - } + packssdw(this, left, right, dest)?; } // Used to implement _mm_min_sd and _mm_max_sd functions. // Note that the semantics are a bit different from Rust simd_min @@ -548,7 +354,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: }; let res = this.float_to_int_checked(&op, dest.layout, rnd)?.unwrap_or_else(|| { - // Fallback to minimum acording to SSE semantics. + // Fallback to minimum according to SSE semantics. ImmTy::from_int(dest.layout.size.signed_int_min(), dest.layout) }); @@ -575,27 +381,13 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: let res0 = this.float_to_float_or_int(&right0, dest0.layout)?; this.write_immediate(*res0, &dest0)?; - // Copy remianing from `left` + // Copy remaining from `left` for i in 1..dest_len { this.copy_op(&this.project_index(&left, i)?, &this.project_index(&dest, i)?)?; } } - _ => return Ok(EmulateForeignItemResult::NotSupported), + _ => return Ok(EmulateItemResult::NotSupported), } - Ok(EmulateForeignItemResult::NeedsJumping) + Ok(EmulateItemResult::NeedsJumping) } } - -/// Takes a 128-bit vector, transmutes it to `[u64; 2]` and extracts -/// the first value. -fn extract_first_u64<'tcx>( - this: &crate::MiriInterpCx<'_, 'tcx>, - op: &MPlaceTy<'tcx, Provenance>, -) -> InterpResult<'tcx, u64> { - // Transmute vector to `[u64; 2]` - let u64_array_layout = this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u64, 2))?; - let op = op.transmute(u64_array_layout, this)?; - - // Get the first u64 from the array - this.read_scalar(&this.project_index(&op, 0)?)?.to_u64() -} diff --git a/src/shims/x86/sse3.rs b/src/shims/x86/sse3.rs index 5ac30dca79..8de339eb9e 100644 --- a/src/shims/x86/sse3.rs +++ b/src/shims/x86/sse3.rs @@ -4,7 +4,6 @@ use rustc_target::spec::abi::Abi; use super::horizontal_bin_op; use crate::*; -use shims::foreign_items::EmulateForeignItemResult; impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: @@ -16,7 +15,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: abi: Abi, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, EmulateForeignItemResult> { + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); this.expect_target_feature_for_intrinsic(link_name, "sse3")?; // Prefix should have already been checked. @@ -50,8 +49,8 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: this.mem_copy(src_ptr, dest.ptr(), dest.layout.size, /*nonoverlapping*/ true)?; } - _ => return Ok(EmulateForeignItemResult::NotSupported), + _ => return Ok(EmulateItemResult::NotSupported), } - Ok(EmulateForeignItemResult::NeedsJumping) + Ok(EmulateItemResult::NeedsJumping) } } diff --git a/src/shims/x86/sse41.rs b/src/shims/x86/sse41.rs index 16a82eed99..011a7a16c6 100644 --- a/src/shims/x86/sse41.rs +++ b/src/shims/x86/sse41.rs @@ -1,9 +1,8 @@ use rustc_span::Symbol; use rustc_target::spec::abi::Abi; -use super::{conditional_dot_product, round_all, round_first, test_bits_masked}; +use super::{conditional_dot_product, mpsadbw, packusdw, round_all, round_first, test_bits_masked}; use crate::*; -use shims::foreign_items::EmulateForeignItemResult; impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: @@ -15,7 +14,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: abi: Abi, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, EmulateForeignItemResult> { + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); this.expect_target_feature_for_intrinsic(link_name, "sse4.1")?; // Prefix should have already been checked. @@ -68,27 +67,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: let [left, right] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - assert_eq!(left_len, right_len); - assert_eq!(dest_len, left_len.checked_mul(2).unwrap()); - - for i in 0..left_len { - let left = this.read_scalar(&this.project_index(&left, i)?)?.to_i32()?; - let right = this.read_scalar(&this.project_index(&right, i)?)?.to_i32()?; - let left_dest = this.project_index(&dest, i)?; - let right_dest = this.project_index(&dest, i.checked_add(left_len).unwrap())?; - - let left_res = - u16::try_from(left).unwrap_or(if left < 0 { 0 } else { u16::MAX }); - let right_res = - u16::try_from(right).unwrap_or(if right < 0 { 0 } else { u16::MAX }); - - this.write_scalar(Scalar::from_u16(left_res), &left_dest)?; - this.write_scalar(Scalar::from_u16(right_res), &right_dest)?; - } + packusdw(this, left, right, dest)?; } // Used to implement the _mm_dp_ps and _mm_dp_pd functions. // Conditionally multiplies the packed floating-point elements in @@ -176,40 +155,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: let [left, right, imm] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - assert_eq!(left_len, right_len); - assert_eq!(left_len, dest_len.checked_mul(2).unwrap()); - - let imm = this.read_scalar(imm)?.to_u8()?; - // Bit 2 of `imm` specifies the offset for indices of `left`. - // The offset is 0 when the bit is 0 or 4 when the bit is 1. - let left_offset = u64::from((imm >> 2) & 1).checked_mul(4).unwrap(); - // Bits 0..=1 of `imm` specify the offset for indices of - // `right` in blocks of 4 elements. - let right_offset = u64::from(imm & 0b11).checked_mul(4).unwrap(); - - for i in 0..dest_len { - let left_offset = left_offset.checked_add(i).unwrap(); - let mut res: u16 = 0; - for j in 0..4 { - let left = this - .read_scalar( - &this.project_index(&left, left_offset.checked_add(j).unwrap())?, - )? - .to_u8()?; - let right = this - .read_scalar( - &this - .project_index(&right, right_offset.checked_add(j).unwrap())?, - )? - .to_u8()?; - res = res.checked_add(left.abs_diff(right).into()).unwrap(); - } - this.write_scalar(Scalar::from_u16(res), &this.project_index(&dest, i)?)?; - } + mpsadbw(this, left, right, imm, dest)?; } // Used to implement the _mm_testz_si128, _mm_testc_si128 // and _mm_testnzc_si128 functions. @@ -228,8 +174,8 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: this.write_scalar(Scalar::from_i32(res.into()), dest)?; } - _ => return Ok(EmulateForeignItemResult::NotSupported), + _ => return Ok(EmulateItemResult::NotSupported), } - Ok(EmulateForeignItemResult::NeedsJumping) + Ok(EmulateItemResult::NeedsJumping) } } diff --git a/src/shims/x86/ssse3.rs b/src/shims/x86/ssse3.rs index dd5d064b20..d30de43008 100644 --- a/src/shims/x86/ssse3.rs +++ b/src/shims/x86/ssse3.rs @@ -2,9 +2,8 @@ use rustc_middle::mir; use rustc_span::Symbol; use rustc_target::spec::abi::Abi; -use super::horizontal_bin_op; +use super::{horizontal_bin_op, int_abs, pmulhrsw, psign}; use crate::*; -use shims::foreign_items::EmulateForeignItemResult; impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: @@ -16,7 +15,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: abi: Abi, args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, EmulateForeignItemResult> { + ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); this.expect_target_feature_for_intrinsic(link_name, "ssse3")?; // Prefix should have already been checked. @@ -28,20 +27,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: "pabs.b.128" | "pabs.w.128" | "pabs.d.128" => { let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let (op, op_len) = this.operand_to_simd(op)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - assert_eq!(op_len, dest_len); - - for i in 0..dest_len { - let op = this.read_scalar(&this.project_index(&op, i)?)?; - let dest = this.project_index(&dest, i)?; - - // Converting to a host "i128" works since the input is always signed. - let res = op.to_int(dest.layout.size)?.unsigned_abs(); - - this.write_scalar(Scalar::from_uint(res, dest.layout.size), &dest)?; - } + int_abs(this, op, dest)?; } // Used to implement the _mm_shuffle_epi8 intrinsic. // Shuffles bytes from `left` using `right` as pattern. @@ -136,30 +122,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: let [left, right] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - assert_eq!(dest_len, left_len); - assert_eq!(dest_len, right_len); - - for i in 0..dest_len { - let left = this.read_scalar(&this.project_index(&left, i)?)?.to_i16()?; - let right = this.read_scalar(&this.project_index(&right, i)?)?.to_i16()?; - let dest = this.project_index(&dest, i)?; - - let res = (i32::from(left).checked_mul(right.into()).unwrap() >> 14) - .checked_add(1) - .unwrap() - >> 1; - - // The result of this operation can overflow a signed 16-bit integer. - // When `left` and `right` are -0x8000, the result is 0x8000. - #[allow(clippy::cast_possible_truncation)] - let res = res as i16; - - this.write_scalar(Scalar::from_i16(res), &dest)?; - } + pmulhrsw(this, left, right, dest)?; } // Used to implement the _mm_sign_epi{8,16,32} functions. // Negates elements from `left` when the corresponding element in @@ -170,31 +133,10 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: let [left, right] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - assert_eq!(dest_len, left_len); - assert_eq!(dest_len, right_len); - - for i in 0..dest_len { - let dest = this.project_index(&dest, i)?; - let left = this.read_immediate(&this.project_index(&left, i)?)?; - let right = this - .read_scalar(&this.project_index(&right, i)?)? - .to_int(dest.layout.size)?; - - let res = this.wrapping_binary_op( - mir::BinOp::Mul, - &left, - &ImmTy::from_int(right.signum(), dest.layout), - )?; - - this.write_immediate(*res, &dest)?; - } + psign(this, left, right, dest)?; } - _ => return Ok(EmulateForeignItemResult::NotSupported), + _ => return Ok(EmulateItemResult::NotSupported), } - Ok(EmulateForeignItemResult::NeedsJumping) + Ok(EmulateItemResult::NeedsJumping) } } diff --git a/test-cargo-miri/run-test.py b/test-cargo-miri/run-test.py index cac11dff77..83f3e4c919 100755 --- a/test-cargo-miri/run-test.py +++ b/test-cargo-miri/run-test.py @@ -10,6 +10,7 @@ import re import subprocess import sys +import argparse CGREEN = '\33[32m' CBOLD = '\33[1m' @@ -25,8 +26,8 @@ def cargo_miri(cmd, quiet = True): args = ["cargo", "miri", cmd] + CARGO_EXTRA_FLAGS if quiet: args += ["-q"] - if 'MIRI_TEST_TARGET' in os.environ: - args += ["--target", os.environ['MIRI_TEST_TARGET']] + if ARGS.target: + args += ["--target", ARGS.target] return args def normalize_stdout(str): @@ -34,12 +35,8 @@ def normalize_stdout(str): str = re.sub("finished in \\d+\\.\\d\\ds", "finished in $TIME", str) # the time keeps changing, obviously return str -def normalize_stderr(str): - str = re.sub("Preparing a sysroot for Miri \\(target: [a-z0-9_-]+\\)\\.\\.\\. done\n", "", str) # remove leading cargo-miri setup output - return str - def check_output(actual, path, name): - if os.environ.get("RUSTC_BLESS", "0") != "0": + if ARGS.bless: # Write the output only if bless is set open(path, mode='w').write(actual) return True @@ -69,7 +66,7 @@ def test(name, cmd, stdout_ref, stderr_ref, stdin=b'', env=None): ) (stdout, stderr) = p.communicate(input=stdin) stdout = normalize_stdout(stdout.decode("UTF-8")) - stderr = normalize_stderr(stderr.decode("UTF-8")) + stderr = stderr.decode("UTF-8") stdout_matches = check_output(stdout, stdout_ref, "stdout") stderr_matches = check_output(stderr, stderr_ref, "stderr") @@ -137,7 +134,7 @@ def test_cargo_miri_run(): def test_cargo_miri_test(): # rustdoc is not run on foreign targets - is_foreign = 'MIRI_TEST_TARGET' in os.environ + is_foreign = ARGS.target is not None default_ref = "test.cross-target.stdout.ref" if is_foreign else "test.default.stdout.ref" filter_ref = "test.filter.cross-target.stdout.ref" if is_foreign else "test.filter.stdout.ref" @@ -186,16 +183,22 @@ def test_cargo_miri_test(): env={'MIRIFLAGS': "-Zmiri-permissive-provenance"}, ) +args_parser = argparse.ArgumentParser(description='`cargo miri` testing') +args_parser.add_argument('--target', help='the target to test') +args_parser.add_argument('--bless', help='bless the reference files', action='store_true') +ARGS = args_parser.parse_args() + os.chdir(os.path.dirname(os.path.realpath(__file__))) os.environ["CARGO_TARGET_DIR"] = "target" # this affects the location of the target directory that we need to check os.environ["RUST_TEST_NOCAPTURE"] = "0" # this affects test output, so make sure it is not set os.environ["RUST_TEST_THREADS"] = "1" # avoid non-deterministic output due to concurrent test runs -target_str = " for target {}".format(os.environ['MIRI_TEST_TARGET']) if 'MIRI_TEST_TARGET' in os.environ else "" +target_str = " for target {}".format(ARGS.target) if ARGS.target else "" print(CGREEN + CBOLD + "## Running `cargo miri` tests{}".format(target_str) + CEND) test_cargo_miri_run() test_cargo_miri_test() + # Ensure we did not create anything outside the expected target dir. for target_dir in ["target", "custom-run", "custom-test", "config-cli"]: if os.listdir(target_dir) != ["miri"]: diff --git a/test-cargo-miri/test.bin-target.stdout.ref b/test-cargo-miri/test.bin-target.stdout.ref index 5264530160..6f48025996 100644 --- a/test-cargo-miri/test.bin-target.stdout.ref +++ b/test-cargo-miri/test.bin-target.stdout.ref @@ -3,5 +3,5 @@ running 2 tests test test::dev_dependency ... ok test test::exported_symbol ... ok -test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME diff --git a/test-cargo-miri/test.cross-target.stdout.ref b/test-cargo-miri/test.cross-target.stdout.ref index 8c543e479f..2ef124e4de 100644 --- a/test-cargo-miri/test.cross-target.stdout.ref +++ b/test-cargo-miri/test.cross-target.stdout.ref @@ -1,11 +1,11 @@ running 2 tests .. -test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME imported main running 6 tests ...i.. -test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out +test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in $TIME diff --git a/test-cargo-miri/test.default.stdout.ref b/test-cargo-miri/test.default.stdout.ref index 922d2120be..2d74d82f76 100644 --- a/test-cargo-miri/test.default.stdout.ref +++ b/test-cargo-miri/test.default.stdout.ref @@ -1,13 +1,13 @@ running 2 tests .. -test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME imported main running 6 tests ...i.. -test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out +test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in $TIME running 5 tests diff --git a/test-cargo-miri/test.filter.cross-target.stdout.ref b/test-cargo-miri/test.filter.cross-target.stdout.ref index bb0282d6c9..59b4deb1ff 100644 --- a/test-cargo-miri/test.filter.cross-target.stdout.ref +++ b/test-cargo-miri/test.filter.cross-target.stdout.ref @@ -1,12 +1,12 @@ running 0 tests -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in $TIME imported main running 1 test test simple ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 5 filtered out +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 5 filtered out; finished in $TIME diff --git a/test-cargo-miri/test.filter.stdout.ref b/test-cargo-miri/test.filter.stdout.ref index 5c819dd532..b68bc98327 100644 --- a/test-cargo-miri/test.filter.stdout.ref +++ b/test-cargo-miri/test.filter.stdout.ref @@ -1,14 +1,14 @@ running 0 tests -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in $TIME imported main running 1 test test simple ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 5 filtered out +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 5 filtered out; finished in $TIME running 0 tests diff --git a/test-cargo-miri/test.subcrate.stdout.ref b/test-cargo-miri/test.subcrate.stdout.ref index 67e5c7f8e9..e50838ebc8 100644 --- a/test-cargo-miri/test.subcrate.stdout.ref +++ b/test-cargo-miri/test.subcrate.stdout.ref @@ -1,6 +1,6 @@ running 0 tests -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME subcrate testing diff --git a/test-cargo-miri/test.test-target.stdout.ref b/test-cargo-miri/test.test-target.stdout.ref index dd59b32b78..38b3f5c098 100644 --- a/test-cargo-miri/test.test-target.stdout.ref +++ b/test-cargo-miri/test.test-target.stdout.ref @@ -7,5 +7,5 @@ test does_not_work_on_miri ... ignored test fail_index_check - should panic ... ok test simple ... ok -test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out +test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in $TIME diff --git a/test_dependencies/Cargo.lock b/test_dependencies/Cargo.lock index 11d430bad1..c73d13a462 100644 --- a/test_dependencies/Cargo.lock +++ b/test_dependencies/Cargo.lock @@ -17,17 +17,11 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -40,36 +34,21 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.3.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bumpalo" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" - -[[package]] -name = "bytes" -version = "1.5.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" [[package]] name = "cfg-if" @@ -89,9 +68,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "getrandom" @@ -106,9 +85,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "js-sys", @@ -125,58 +104,48 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.151" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" - -[[package]] -name = "lock_api" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" -dependencies = [ - "autocfg", - "scopeguard", -] +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[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 = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] @@ -197,11 +166,10 @@ name = "miri-test-deps" version = "0.1.0" dependencies = [ "getrandom 0.1.16", - "getrandom 0.2.11", + "getrandom 0.2.14", "libc", "num_cpus", "page_size", - "rand", "tempfile", "tokio", "windows-sys 0.52.0", @@ -242,98 +210,30 @@ dependencies = [ "winapi", ] -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.48.5", -] - [[package]] name = "pin-project-lite" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "proc-macro2" -version = "1.0.76" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.11", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -342,53 +242,32 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.1", + "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - -[[package]] -name = "smallvec" -version = "1.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" - [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "syn" -version = "2.0.48" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -397,31 +276,27 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", "windows-sys 0.52.0", ] [[package]] name = "tokio" -version = "1.35.1" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", - "bytes", "libc", "mio", "num_cpus", - "parking_lot", "pin-project-lite", - "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.48.0", @@ -458,9 +333,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -468,9 +343,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", @@ -483,9 +358,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -493,9 +368,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", @@ -506,9 +381,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "winapi" @@ -547,7 +422,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.5", ] [[package]] @@ -567,17 +442,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -588,9 +464,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -600,9 +476,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -612,9 +488,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -624,9 +506,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -636,9 +518,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -648,9 +530,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -660,6 +542,6 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/test_dependencies/Cargo.toml b/test_dependencies/Cargo.toml index 9f28e4d169..1894f53ce4 100644 --- a/test_dependencies/Cargo.toml +++ b/test_dependencies/Cargo.toml @@ -15,11 +15,10 @@ tempfile = "3" getrandom_01 = { package = "getrandom", version = "0.1" } getrandom_02 = { package = "getrandom", version = "0.2", features = ["js"] } -rand = { version = "0.8", features = ["small_rng"] } [target.'cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))'.dependencies] page_size = "0.6" -tokio = { version = "1.24", features = ["full"] } +tokio = { version = "1.24", features = ["macros", "rt-multi-thread", "time", "net"] } [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.52", features = [ "Win32_Foundation", "Win32_System_Threading" ] } diff --git a/tests/fail-dep/shims/sync/libc_pthread_cond_double_destroy.rs b/tests/fail-dep/concurrency/libc_pthread_cond_double_destroy.rs similarity index 93% rename from tests/fail-dep/shims/sync/libc_pthread_cond_double_destroy.rs rename to tests/fail-dep/concurrency/libc_pthread_cond_double_destroy.rs index 94ca3496ed..f22f17be0d 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_cond_double_destroy.rs +++ b/tests/fail-dep/concurrency/libc_pthread_cond_double_destroy.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows /// Test that destroying a pthread_cond twice fails, even without a check for number validity diff --git a/tests/fail-dep/shims/sync/libc_pthread_cond_double_destroy.stderr b/tests/fail-dep/concurrency/libc_pthread_cond_double_destroy.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_cond_double_destroy.stderr rename to tests/fail-dep/concurrency/libc_pthread_cond_double_destroy.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_condattr_double_destroy.rs b/tests/fail-dep/concurrency/libc_pthread_condattr_double_destroy.rs similarity index 79% rename from tests/fail-dep/shims/sync/libc_pthread_condattr_double_destroy.rs rename to tests/fail-dep/concurrency/libc_pthread_condattr_double_destroy.rs index 13e639a867..d0ccab4de5 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_condattr_double_destroy.rs +++ b/tests/fail-dep/concurrency/libc_pthread_condattr_double_destroy.rs @@ -1,4 +1,5 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows +//@ignore-target-apple: Our macOS condattr don't have any fields so we do not notice this. /// Test that destroying a pthread_condattr twice fails, even without a check for number validity diff --git a/tests/fail-dep/shims/sync/libc_pthread_condattr_double_destroy.stderr b/tests/fail-dep/concurrency/libc_pthread_condattr_double_destroy.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_condattr_double_destroy.stderr rename to tests/fail-dep/concurrency/libc_pthread_condattr_double_destroy.stderr diff --git a/tests/fail-dep/concurrency/libc_pthread_create_main_terminate.rs b/tests/fail-dep/concurrency/libc_pthread_create_main_terminate.rs index 7e6f490bb3..9cd0a35d36 100644 --- a/tests/fail-dep/concurrency/libc_pthread_create_main_terminate.rs +++ b/tests/fail-dep/concurrency/libc_pthread_create_main_terminate.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows //@error-in-other-file: the main thread terminated without waiting for all remaining threads // Check that we terminate the program when the main thread terminates. diff --git a/tests/fail-dep/concurrency/libc_pthread_create_too_few_args.rs b/tests/fail-dep/concurrency/libc_pthread_create_too_few_args.rs index e1d3704af7..96dd99e884 100644 --- a/tests/fail-dep/concurrency/libc_pthread_create_too_few_args.rs +++ b/tests/fail-dep/concurrency/libc_pthread_create_too_few_args.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows //! The thread function must have exactly one argument. diff --git a/tests/fail-dep/concurrency/libc_pthread_create_too_many_args.rs b/tests/fail-dep/concurrency/libc_pthread_create_too_many_args.rs index 7408634db5..d8fbc68d34 100644 --- a/tests/fail-dep/concurrency/libc_pthread_create_too_many_args.rs +++ b/tests/fail-dep/concurrency/libc_pthread_create_too_many_args.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows //! The thread function must have exactly one argument. diff --git a/tests/fail-dep/concurrency/libc_pthread_join_detached.rs b/tests/fail-dep/concurrency/libc_pthread_join_detached.rs index 0b810dc8c7..e89d7a9f02 100644 --- a/tests/fail-dep/concurrency/libc_pthread_join_detached.rs +++ b/tests/fail-dep/concurrency/libc_pthread_join_detached.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows // Joining a detached thread is undefined behavior. diff --git a/tests/fail-dep/concurrency/libc_pthread_join_joined.rs b/tests/fail-dep/concurrency/libc_pthread_join_joined.rs index 04ca4bbb3f..cbad743ca5 100644 --- a/tests/fail-dep/concurrency/libc_pthread_join_joined.rs +++ b/tests/fail-dep/concurrency/libc_pthread_join_joined.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows // Joining an already joined thread is undefined behavior. diff --git a/tests/fail-dep/concurrency/libc_pthread_join_main.rs b/tests/fail-dep/concurrency/libc_pthread_join_main.rs index 7576518216..002498e6c8 100644 --- a/tests/fail-dep/concurrency/libc_pthread_join_main.rs +++ b/tests/fail-dep/concurrency/libc_pthread_join_main.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows // Joining the main thread is undefined behavior. diff --git a/tests/fail-dep/concurrency/libc_pthread_join_multiple.rs b/tests/fail-dep/concurrency/libc_pthread_join_multiple.rs index 966f416eea..f5b687a623 100644 --- a/tests/fail-dep/concurrency/libc_pthread_join_multiple.rs +++ b/tests/fail-dep/concurrency/libc_pthread_join_multiple.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows // Joining the same thread from multiple threads is undefined behavior. diff --git a/tests/fail-dep/concurrency/libc_pthread_join_self.rs b/tests/fail-dep/concurrency/libc_pthread_join_self.rs index 0c25c690f3..4bc1c82a25 100644 --- a/tests/fail-dep/concurrency/libc_pthread_join_self.rs +++ b/tests/fail-dep/concurrency/libc_pthread_join_self.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows // We are making scheduler assumptions here. //@compile-flags: -Zmiri-preemption-rate=0 diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_NULL_deadlock.rs b/tests/fail-dep/concurrency/libc_pthread_mutex_NULL_deadlock.rs similarity index 90% rename from tests/fail-dep/shims/sync/libc_pthread_mutex_NULL_deadlock.rs rename to tests/fail-dep/concurrency/libc_pthread_mutex_NULL_deadlock.rs index 8b25107338..0a494c53b4 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_mutex_NULL_deadlock.rs +++ b/tests/fail-dep/concurrency/libc_pthread_mutex_NULL_deadlock.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows // // Check that if we pass NULL attribute, then we get the default mutex type. diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_NULL_deadlock.stderr b/tests/fail-dep/concurrency/libc_pthread_mutex_NULL_deadlock.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_mutex_NULL_deadlock.stderr rename to tests/fail-dep/concurrency/libc_pthread_mutex_NULL_deadlock.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.rs b/tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.rs similarity index 88% rename from tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.rs rename to tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.rs index 6c3cb738e2..0328115c63 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.rs +++ b/tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.rs @@ -1,4 +1,5 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows +//@error-in-other-file: deadlock use std::cell::UnsafeCell; use std::sync::Arc; diff --git a/tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.stderr b/tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.stderr new file mode 100644 index 0000000000..987d0fc4c2 --- /dev/null +++ b/tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.stderr @@ -0,0 +1,32 @@ +error: deadlock: the evaluated program deadlocked + --> $DIR/libc_pthread_mutex_deadlock.rs:LL:CC + | +LL | assert_eq!(libc::pthread_mutex_lock(lock_copy.0.get() as *mut _), 0); + | ^ the evaluated program deadlocked + | + = note: BACKTRACE on thread `unnamed-ID`: + = note: inside closure at $DIR/libc_pthread_mutex_deadlock.rs:LL:CC + +error: deadlock: the evaluated program deadlocked + --> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + | +LL | let ret = libc::pthread_join(self.id, ptr::null_mut()); + | ^ the evaluated program deadlocked + | + = note: BACKTRACE: + = note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> $DIR/libc_pthread_mutex_deadlock.rs:LL:CC + | +LL | / thread::spawn(move || { +LL | | assert_eq!(libc::pthread_mutex_lock(lock_copy.0.get() as *mut _), 0); +LL | | }) +LL | | .join() + | |_______________^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 2 previous errors + diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_default_deadlock.rs b/tests/fail-dep/concurrency/libc_pthread_mutex_default_deadlock.rs similarity index 91% rename from tests/fail-dep/shims/sync/libc_pthread_mutex_default_deadlock.rs rename to tests/fail-dep/concurrency/libc_pthread_mutex_default_deadlock.rs index f443768819..1038b8988f 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_mutex_default_deadlock.rs +++ b/tests/fail-dep/concurrency/libc_pthread_mutex_default_deadlock.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows // // Check that if we do not set the mutex type, it is the default. diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_default_deadlock.stderr b/tests/fail-dep/concurrency/libc_pthread_mutex_default_deadlock.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_mutex_default_deadlock.stderr rename to tests/fail-dep/concurrency/libc_pthread_mutex_default_deadlock.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_destroy_locked.rs b/tests/fail-dep/concurrency/libc_pthread_mutex_destroy_locked.rs similarity index 92% rename from tests/fail-dep/shims/sync/libc_pthread_mutex_destroy_locked.rs rename to tests/fail-dep/concurrency/libc_pthread_mutex_destroy_locked.rs index ec3965c757..e474712cfd 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_mutex_destroy_locked.rs +++ b/tests/fail-dep/concurrency/libc_pthread_mutex_destroy_locked.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows fn main() { unsafe { diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_destroy_locked.stderr b/tests/fail-dep/concurrency/libc_pthread_mutex_destroy_locked.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_mutex_destroy_locked.stderr rename to tests/fail-dep/concurrency/libc_pthread_mutex_destroy_locked.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_double_destroy.rs b/tests/fail-dep/concurrency/libc_pthread_mutex_double_destroy.rs similarity index 93% rename from tests/fail-dep/shims/sync/libc_pthread_mutex_double_destroy.rs rename to tests/fail-dep/concurrency/libc_pthread_mutex_double_destroy.rs index 622c3eaeae..46f0c5f8d7 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_mutex_double_destroy.rs +++ b/tests/fail-dep/concurrency/libc_pthread_mutex_double_destroy.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows /// Test that destroying a pthread_mutex twice fails, even without a check for number validity diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_double_destroy.stderr b/tests/fail-dep/concurrency/libc_pthread_mutex_double_destroy.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_mutex_double_destroy.stderr rename to tests/fail-dep/concurrency/libc_pthread_mutex_double_destroy.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_normal_deadlock.rs b/tests/fail-dep/concurrency/libc_pthread_mutex_normal_deadlock.rs similarity index 92% rename from tests/fail-dep/shims/sync/libc_pthread_mutex_normal_deadlock.rs rename to tests/fail-dep/concurrency/libc_pthread_mutex_normal_deadlock.rs index 5ea09fa5aa..f311934e28 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_mutex_normal_deadlock.rs +++ b/tests/fail-dep/concurrency/libc_pthread_mutex_normal_deadlock.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows fn main() { unsafe { diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_normal_deadlock.stderr b/tests/fail-dep/concurrency/libc_pthread_mutex_normal_deadlock.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_mutex_normal_deadlock.stderr rename to tests/fail-dep/concurrency/libc_pthread_mutex_normal_deadlock.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.rs b/tests/fail-dep/concurrency/libc_pthread_mutex_normal_unlock_unlocked.rs similarity index 92% rename from tests/fail-dep/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.rs rename to tests/fail-dep/concurrency/libc_pthread_mutex_normal_unlock_unlocked.rs index 8ce7542edb..2b5886dc16 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.rs +++ b/tests/fail-dep/concurrency/libc_pthread_mutex_normal_unlock_unlocked.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows fn main() { unsafe { diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.stderr b/tests/fail-dep/concurrency/libc_pthread_mutex_normal_unlock_unlocked.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.stderr rename to tests/fail-dep/concurrency/libc_pthread_mutex_normal_unlock_unlocked.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_wrong_owner.rs b/tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.rs similarity index 93% rename from tests/fail-dep/shims/sync/libc_pthread_mutex_wrong_owner.rs rename to tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.rs index b56775252e..686a394f7c 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_mutex_wrong_owner.rs +++ b/tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows use std::cell::UnsafeCell; use std::sync::Arc; diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_wrong_owner.stderr b/tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_mutex_wrong_owner.stderr rename to tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutexattr_double_destroy.rs b/tests/fail-dep/concurrency/libc_pthread_mutexattr_double_destroy.rs similarity index 91% rename from tests/fail-dep/shims/sync/libc_pthread_mutexattr_double_destroy.rs rename to tests/fail-dep/concurrency/libc_pthread_mutexattr_double_destroy.rs index 474a277516..51f00d62b7 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_mutexattr_double_destroy.rs +++ b/tests/fail-dep/concurrency/libc_pthread_mutexattr_double_destroy.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows /// Test that destroying a pthread_mutexattr twice fails, even without a check for number validity diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutexattr_double_destroy.stderr b/tests/fail-dep/concurrency/libc_pthread_mutexattr_double_destroy.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_mutexattr_double_destroy.stderr rename to tests/fail-dep/concurrency/libc_pthread_mutexattr_double_destroy.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_destroy_read_locked.rs b/tests/fail-dep/concurrency/libc_pthread_rwlock_destroy_read_locked.rs similarity index 83% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_destroy_read_locked.rs rename to tests/fail-dep/concurrency/libc_pthread_rwlock_destroy_read_locked.rs index 603580ff58..fa4575bc1d 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_rwlock_destroy_read_locked.rs +++ b/tests/fail-dep/concurrency/libc_pthread_rwlock_destroy_read_locked.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows fn main() { let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_destroy_read_locked.stderr b/tests/fail-dep/concurrency/libc_pthread_rwlock_destroy_read_locked.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_destroy_read_locked.stderr rename to tests/fail-dep/concurrency/libc_pthread_rwlock_destroy_read_locked.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_destroy_write_locked.rs b/tests/fail-dep/concurrency/libc_pthread_rwlock_destroy_write_locked.rs similarity index 83% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_destroy_write_locked.rs rename to tests/fail-dep/concurrency/libc_pthread_rwlock_destroy_write_locked.rs index ae44f22d14..e734a62bca 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_rwlock_destroy_write_locked.rs +++ b/tests/fail-dep/concurrency/libc_pthread_rwlock_destroy_write_locked.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows fn main() { let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_destroy_write_locked.stderr b/tests/fail-dep/concurrency/libc_pthread_rwlock_destroy_write_locked.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_destroy_write_locked.stderr rename to tests/fail-dep/concurrency/libc_pthread_rwlock_destroy_write_locked.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_double_destroy.rs b/tests/fail-dep/concurrency/libc_pthread_rwlock_double_destroy.rs similarity index 89% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_double_destroy.rs rename to tests/fail-dep/concurrency/libc_pthread_rwlock_double_destroy.rs index 800986f750..e96f7fc680 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_rwlock_double_destroy.rs +++ b/tests/fail-dep/concurrency/libc_pthread_rwlock_double_destroy.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows /// Test that destroying a pthread_rwlock twice fails, even without a check for number validity diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_double_destroy.stderr b/tests/fail-dep/concurrency/libc_pthread_rwlock_double_destroy.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_double_destroy.stderr rename to tests/fail-dep/concurrency/libc_pthread_rwlock_double_destroy.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.rs b/tests/fail-dep/concurrency/libc_pthread_rwlock_read_write_deadlock_single_thread.rs similarity index 82% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.rs rename to tests/fail-dep/concurrency/libc_pthread_rwlock_read_write_deadlock_single_thread.rs index 782c95b6d2..dffeee2b79 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.rs +++ b/tests/fail-dep/concurrency/libc_pthread_rwlock_read_write_deadlock_single_thread.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows fn main() { let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.stderr b/tests/fail-dep/concurrency/libc_pthread_rwlock_read_write_deadlock_single_thread.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.stderr rename to tests/fail-dep/concurrency/libc_pthread_rwlock_read_write_deadlock_single_thread.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_read_wrong_owner.rs b/tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.rs similarity index 93% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_read_wrong_owner.rs rename to tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.rs index 1b498ad8fc..328372b22e 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_rwlock_read_wrong_owner.rs +++ b/tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows use std::cell::UnsafeCell; use std::sync::Arc; diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_read_wrong_owner.stderr b/tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_read_wrong_owner.stderr rename to tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_unlock_unlocked.rs b/tests/fail-dep/concurrency/libc_pthread_rwlock_unlock_unlocked.rs similarity index 78% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_unlock_unlocked.rs rename to tests/fail-dep/concurrency/libc_pthread_rwlock_unlock_unlocked.rs index 05f7e7a06c..ced6b7a4f6 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_rwlock_unlock_unlocked.rs +++ b/tests/fail-dep/concurrency/libc_pthread_rwlock_unlock_unlocked.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows fn main() { let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_unlock_unlocked.stderr b/tests/fail-dep/concurrency/libc_pthread_rwlock_unlock_unlocked.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_unlock_unlocked.stderr rename to tests/fail-dep/concurrency/libc_pthread_rwlock_unlock_unlocked.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock.rs b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.rs similarity index 88% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock.rs rename to tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.rs index 201844615e..4174751926 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock.rs +++ b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.rs @@ -1,4 +1,5 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows +//@error-in-other-file: deadlock use std::cell::UnsafeCell; use std::sync::Arc; diff --git a/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.stderr b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.stderr new file mode 100644 index 0000000000..bc9b15f293 --- /dev/null +++ b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.stderr @@ -0,0 +1,32 @@ +error: deadlock: the evaluated program deadlocked + --> $DIR/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC + | +LL | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0); + | ^ the evaluated program deadlocked + | + = note: BACKTRACE on thread `unnamed-ID`: + = note: inside closure at $DIR/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC + +error: deadlock: the evaluated program deadlocked + --> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + | +LL | let ret = libc::pthread_join(self.id, ptr::null_mut()); + | ^ the evaluated program deadlocked + | + = note: BACKTRACE: + = note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> $DIR/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC + | +LL | / thread::spawn(move || { +LL | | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0); +LL | | }) +LL | | .join() + | |_______________^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 2 previous errors + diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.rs b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock_single_thread.rs similarity index 82% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.rs rename to tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock_single_thread.rs index 538f14ef89..099b8dcd10 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.rs +++ b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock_single_thread.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows fn main() { let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.stderr b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock_single_thread.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.stderr rename to tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock_single_thread.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock.rs b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.rs similarity index 88% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock.rs rename to tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.rs index b1d7e0492e..43b3ab09bb 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock.rs +++ b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.rs @@ -1,4 +1,5 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows +//@error-in-other-file: deadlock use std::cell::UnsafeCell; use std::sync::Arc; diff --git a/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.stderr b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.stderr new file mode 100644 index 0000000000..66c142bbc5 --- /dev/null +++ b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.stderr @@ -0,0 +1,32 @@ +error: deadlock: the evaluated program deadlocked + --> $DIR/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC + | +LL | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0); + | ^ the evaluated program deadlocked + | + = note: BACKTRACE on thread `unnamed-ID`: + = note: inside closure at $DIR/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC + +error: deadlock: the evaluated program deadlocked + --> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + | +LL | let ret = libc::pthread_join(self.id, ptr::null_mut()); + | ^ the evaluated program deadlocked + | + = note: BACKTRACE: + = note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> $DIR/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC + | +LL | / thread::spawn(move || { +LL | | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0); +LL | | }) +LL | | .join() + | |_______________^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 2 previous errors + diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.rs b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock_single_thread.rs similarity index 82% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.rs rename to tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock_single_thread.rs index 2c963d3651..2704ff1544 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.rs +++ b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock_single_thread.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows fn main() { let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.stderr b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock_single_thread.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.stderr rename to tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock_single_thread.stderr diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_wrong_owner.rs b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_wrong_owner.rs similarity index 93% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_write_wrong_owner.rs rename to tests/fail-dep/concurrency/libc_pthread_rwlock_write_wrong_owner.rs index dd099474d8..9a2cd09f08 100644 --- a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_wrong_owner.rs +++ b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_wrong_owner.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows use std::cell::UnsafeCell; use std::sync::Arc; diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_wrong_owner.stderr b/tests/fail-dep/concurrency/libc_pthread_rwlock_write_wrong_owner.stderr similarity index 100% rename from tests/fail-dep/shims/sync/libc_pthread_rwlock_write_wrong_owner.stderr rename to tests/fail-dep/concurrency/libc_pthread_rwlock_write_wrong_owner.stderr diff --git a/tests/fail-dep/concurrency/windows_join_main.rs b/tests/fail-dep/concurrency/windows_join_main.rs index 910e06222e..532bda2013 100644 --- a/tests/fail-dep/concurrency/windows_join_main.rs +++ b/tests/fail-dep/concurrency/windows_join_main.rs @@ -1,6 +1,7 @@ //@only-target-windows: Uses win32 api functions // We are making scheduler assumptions here. //@compile-flags: -Zmiri-preemption-rate=0 +//@error-in-other-file: deadlock // On windows, joining main is not UB, but it will block a thread forever. diff --git a/tests/fail-dep/concurrency/windows_join_main.stderr b/tests/fail-dep/concurrency/windows_join_main.stderr index d9137ee743..12f35fdeb0 100644 --- a/tests/fail-dep/concurrency/windows_join_main.stderr +++ b/tests/fail-dep/concurrency/windows_join_main.stderr @@ -8,7 +8,28 @@ LL | assert_eq!(WaitForSingleObject(MAIN_THREAD, INFINITE), WAIT_OBJ = note: inside closure at RUSTLIB/core/src/macros/mod.rs:LL:CC = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info) +error: deadlock: the evaluated program deadlocked + --> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + | +LL | let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE) }; + | ^ the evaluated program deadlocked + | + = note: BACKTRACE: + = note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> $DIR/windows_join_main.rs:LL:CC + | +LL | / thread::spawn(|| { +LL | | unsafe { +LL | | assert_eq!(WaitForSingleObject(MAIN_THREAD, INFINITE), WAIT_OBJECT_0); +LL | | } +LL | | }) +LL | | .join() + | |___________^ + note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace -error: aborting due to 1 previous error +error: aborting due to 2 previous errors diff --git a/tests/fail-dep/concurrency/windows_join_self.rs b/tests/fail-dep/concurrency/windows_join_self.rs index a7c8faf5a9..a64265ca0c 100644 --- a/tests/fail-dep/concurrency/windows_join_self.rs +++ b/tests/fail-dep/concurrency/windows_join_self.rs @@ -1,6 +1,7 @@ //@only-target-windows: Uses win32 api functions // We are making scheduler assumptions here. //@compile-flags: -Zmiri-preemption-rate=0 +//@error-in-other-file: deadlock // On windows, a thread joining itself is not UB, but it will deadlock. diff --git a/tests/fail-dep/concurrency/windows_join_self.stderr b/tests/fail-dep/concurrency/windows_join_self.stderr index 74699a0317..8d26c35de8 100644 --- a/tests/fail-dep/concurrency/windows_join_self.stderr +++ b/tests/fail-dep/concurrency/windows_join_self.stderr @@ -7,7 +7,29 @@ LL | assert_eq!(WaitForSingleObject(native, INFINITE), WAIT_OBJECT_0 = note: BACKTRACE on thread `unnamed-ID`: = note: inside closure at $DIR/windows_join_self.rs:LL:CC +error: deadlock: the evaluated program deadlocked + --> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + | +LL | let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE) }; + | ^ the evaluated program deadlocked + | + = note: BACKTRACE: + = note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> $DIR/windows_join_self.rs:LL:CC + | +LL | / thread::spawn(|| { +LL | | unsafe { +LL | | let native = GetCurrentThread(); +LL | | assert_eq!(WaitForSingleObject(native, INFINITE), WAIT_OBJECT_0); +LL | | } +LL | | }) +LL | | .join() + | |___________^ + note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace -error: aborting due to 1 previous error +error: aborting due to 2 previous errors diff --git a/tests/fail-dep/shims/env-set_var-data-race.rs b/tests/fail-dep/libc/env-set_var-data-race.rs similarity index 90% rename from tests/fail-dep/shims/env-set_var-data-race.rs rename to tests/fail-dep/libc/env-set_var-data-race.rs index 2b9e7a34d6..a1895feb95 100644 --- a/tests/fail-dep/shims/env-set_var-data-race.rs +++ b/tests/fail-dep/libc/env-set_var-data-race.rs @@ -1,5 +1,5 @@ //@compile-flags: -Zmiri-disable-isolation -Zmiri-preemption-rate=0 -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No libc env support on Windows use std::env; use std::thread; diff --git a/tests/fail-dep/shims/env-set_var-data-race.stderr b/tests/fail-dep/libc/env-set_var-data-race.stderr similarity index 100% rename from tests/fail-dep/shims/env-set_var-data-race.stderr rename to tests/fail-dep/libc/env-set_var-data-race.stderr diff --git a/tests/fail-dep/shims/fs/close_stdout.rs b/tests/fail-dep/libc/fs/close_stdout.rs similarity index 82% rename from tests/fail-dep/shims/fs/close_stdout.rs rename to tests/fail-dep/libc/fs/close_stdout.rs index 09da8509af..42b7e2b783 100644 --- a/tests/fail-dep/shims/fs/close_stdout.rs +++ b/tests/fail-dep/libc/fs/close_stdout.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No libc IO on Windows //@compile-flags: -Zmiri-disable-isolation // FIXME: standard handles cannot be closed (https://github.com/rust-lang/rust/issues/40032) diff --git a/tests/fail-dep/shims/fs/close_stdout.stderr b/tests/fail-dep/libc/fs/close_stdout.stderr similarity index 85% rename from tests/fail-dep/shims/fs/close_stdout.stderr rename to tests/fail-dep/libc/fs/close_stdout.stderr index 7547ec4171..e1b1b053bb 100644 --- a/tests/fail-dep/shims/fs/close_stdout.stderr +++ b/tests/fail-dep/libc/fs/close_stdout.stderr @@ -4,7 +4,7 @@ error: unsupported operation: cannot close stdout LL | libc::close(1); | ^^^^^^^^^^^^^^ cannot close stdout | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support = note: BACKTRACE: = note: inside `main` at $DIR/close_stdout.rs:LL:CC diff --git a/tests/fail-dep/shims/fs/isolated_stdin.rs b/tests/fail-dep/libc/fs/isolated_stdin.rs similarity index 83% rename from tests/fail-dep/shims/fs/isolated_stdin.rs rename to tests/fail-dep/libc/fs/isolated_stdin.rs index a45f805696..3c62015a05 100644 --- a/tests/fail-dep/shims/fs/isolated_stdin.rs +++ b/tests/fail-dep/libc/fs/isolated_stdin.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No libc IO on Windows fn main() -> std::io::Result<()> { let mut bytes = [0u8; 512]; diff --git a/tests/fail-dep/shims/fs/isolated_stdin.stderr b/tests/fail-dep/libc/fs/isolated_stdin.stderr similarity index 100% rename from tests/fail-dep/shims/fs/isolated_stdin.stderr rename to tests/fail-dep/libc/fs/isolated_stdin.stderr diff --git a/tests/fail-dep/shims/fs/mkstemp_immutable_arg.rs b/tests/fail-dep/libc/fs/mkstemp_immutable_arg.rs similarity index 86% rename from tests/fail-dep/shims/fs/mkstemp_immutable_arg.rs rename to tests/fail-dep/libc/fs/mkstemp_immutable_arg.rs index ba9f404d7c..6d951a3a7b 100644 --- a/tests/fail-dep/shims/fs/mkstemp_immutable_arg.rs +++ b/tests/fail-dep/libc/fs/mkstemp_immutable_arg.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No mkstemp on Windows //@compile-flags: -Zmiri-disable-isolation fn main() { diff --git a/tests/fail-dep/shims/fs/mkstemp_immutable_arg.stderr b/tests/fail-dep/libc/fs/mkstemp_immutable_arg.stderr similarity index 100% rename from tests/fail-dep/shims/fs/mkstemp_immutable_arg.stderr rename to tests/fail-dep/libc/fs/mkstemp_immutable_arg.stderr diff --git a/tests/fail-dep/shims/fs/read_from_stdout.rs b/tests/fail-dep/libc/fs/read_from_stdout.rs similarity index 83% rename from tests/fail-dep/shims/fs/read_from_stdout.rs rename to tests/fail-dep/libc/fs/read_from_stdout.rs index 073fca4712..624f584a0c 100644 --- a/tests/fail-dep/shims/fs/read_from_stdout.rs +++ b/tests/fail-dep/libc/fs/read_from_stdout.rs @@ -1,5 +1,5 @@ //@compile-flags: -Zmiri-disable-isolation -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No libc IO on Windows fn main() -> std::io::Result<()> { let mut bytes = [0u8; 512]; diff --git a/tests/fail-dep/shims/fs/read_from_stdout.stderr b/tests/fail-dep/libc/fs/read_from_stdout.stderr similarity index 87% rename from tests/fail-dep/shims/fs/read_from_stdout.stderr rename to tests/fail-dep/libc/fs/read_from_stdout.stderr index 355e16d5c3..baa6eb5ad6 100644 --- a/tests/fail-dep/shims/fs/read_from_stdout.stderr +++ b/tests/fail-dep/libc/fs/read_from_stdout.stderr @@ -4,7 +4,7 @@ error: unsupported operation: cannot read from stdout LL | libc::read(1, bytes.as_mut_ptr() as *mut libc::c_void, 512); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot read from stdout | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support = note: BACKTRACE: = note: inside `main` at $DIR/read_from_stdout.rs:LL:CC diff --git a/tests/fail-dep/shims/fs/unix_open_missing_required_mode.rs b/tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs similarity index 89% rename from tests/fail-dep/shims/fs/unix_open_missing_required_mode.rs rename to tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs index ae231d4be6..d783967f95 100644 --- a/tests/fail-dep/shims/fs/unix_open_missing_required_mode.rs +++ b/tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No libc IO on Windows //@compile-flags: -Zmiri-disable-isolation fn main() { diff --git a/tests/fail-dep/shims/fs/unix_open_missing_required_mode.stderr b/tests/fail-dep/libc/fs/unix_open_missing_required_mode.stderr similarity index 100% rename from tests/fail-dep/shims/fs/unix_open_missing_required_mode.stderr rename to tests/fail-dep/libc/fs/unix_open_missing_required_mode.stderr diff --git a/tests/fail-dep/shims/fs/write_to_stdin.rs b/tests/fail-dep/libc/fs/write_to_stdin.rs similarity index 80% rename from tests/fail-dep/shims/fs/write_to_stdin.rs rename to tests/fail-dep/libc/fs/write_to_stdin.rs index d039ad718d..683c55e90e 100644 --- a/tests/fail-dep/shims/fs/write_to_stdin.rs +++ b/tests/fail-dep/libc/fs/write_to_stdin.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No libc IO on Windows fn main() -> std::io::Result<()> { let bytes = b"hello"; diff --git a/tests/fail-dep/shims/fs/write_to_stdin.stderr b/tests/fail-dep/libc/fs/write_to_stdin.stderr similarity index 87% rename from tests/fail-dep/shims/fs/write_to_stdin.stderr rename to tests/fail-dep/libc/fs/write_to_stdin.stderr index e2ebe234b0..37323faf56 100644 --- a/tests/fail-dep/shims/fs/write_to_stdin.stderr +++ b/tests/fail-dep/libc/fs/write_to_stdin.stderr @@ -4,7 +4,7 @@ error: unsupported operation: cannot write to stdin LL | libc::write(0, bytes.as_ptr() as *const libc::c_void, 5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot write to stdin | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support = note: BACKTRACE: = note: inside `main` at $DIR/write_to_stdin.rs:LL:CC diff --git a/tests/fail-dep/libc/malloc_zero_double_free.rs b/tests/fail-dep/libc/malloc_zero_double_free.rs new file mode 100644 index 0000000000..3298d61c8e --- /dev/null +++ b/tests/fail-dep/libc/malloc_zero_double_free.rs @@ -0,0 +1,7 @@ +fn main() { + unsafe { + let ptr = libc::malloc(0); + libc::free(ptr); + libc::free(ptr); //~ERROR: dangling + } +} diff --git a/tests/fail-dep/libc/malloc_zero_double_free.stderr b/tests/fail-dep/libc/malloc_zero_double_free.stderr new file mode 100644 index 0000000000..6437c9dbeb --- /dev/null +++ b/tests/fail-dep/libc/malloc_zero_double_free.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling + --> $DIR/malloc_zero_double_free.rs:LL:CC + | +LL | libc::free(ptr); + | ^^^^^^^^^^^^^^^ memory access failed: ALLOC has been freed, so this pointer is dangling + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> $DIR/malloc_zero_double_free.rs:LL:CC + | +LL | let ptr = libc::malloc(0); + | ^^^^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> $DIR/malloc_zero_double_free.rs:LL:CC + | +LL | libc::free(ptr); + | ^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/malloc_zero_double_free.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/tests/fail-dep/libc/malloc_zero_memory_leak.rs b/tests/fail-dep/libc/malloc_zero_memory_leak.rs new file mode 100644 index 0000000000..b09a74f41d --- /dev/null +++ b/tests/fail-dep/libc/malloc_zero_memory_leak.rs @@ -0,0 +1,5 @@ +fn main() { + unsafe { + let _ptr = libc::malloc(0); //~ERROR: memory leak + } +} diff --git a/tests/fail-dep/libc/malloc_zero_memory_leak.stderr b/tests/fail-dep/libc/malloc_zero_memory_leak.stderr new file mode 100644 index 0000000000..65ce0dcdcd --- /dev/null +++ b/tests/fail-dep/libc/malloc_zero_memory_leak.stderr @@ -0,0 +1,15 @@ +error: memory leaked: ALLOC (C heap, size: 0, align: 1), allocated here: + --> $DIR/malloc_zero_memory_leak.rs:LL:CC + | +LL | let _ptr = libc::malloc(0); + | ^^^^^^^^^^^^^^^ + | + = note: BACKTRACE: + = note: inside `main` at $DIR/malloc_zero_memory_leak.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +note: the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check + +error: aborting due to 1 previous error + diff --git a/tests/fail-dep/shims/memchr_null.rs b/tests/fail-dep/libc/memchr_null.rs similarity index 77% rename from tests/fail-dep/shims/memchr_null.rs rename to tests/fail-dep/libc/memchr_null.rs index 6bc7af7e6b..672cc10cd6 100644 --- a/tests/fail-dep/shims/memchr_null.rs +++ b/tests/fail-dep/libc/memchr_null.rs @@ -1,5 +1,3 @@ -//@ignore-target-windows: No libc on Windows - use std::ptr; // null is explicitly called out as UB in the C docs. diff --git a/tests/fail-dep/shims/memchr_null.stderr b/tests/fail-dep/libc/memchr_null.stderr similarity index 100% rename from tests/fail-dep/shims/memchr_null.stderr rename to tests/fail-dep/libc/memchr_null.stderr diff --git a/tests/fail-dep/shims/memcmp_null.rs b/tests/fail-dep/libc/memcmp_null.rs similarity index 78% rename from tests/fail-dep/shims/memcmp_null.rs rename to tests/fail-dep/libc/memcmp_null.rs index a4e0034c40..066af4a8ae 100644 --- a/tests/fail-dep/shims/memcmp_null.rs +++ b/tests/fail-dep/libc/memcmp_null.rs @@ -1,5 +1,3 @@ -//@ignore-target-windows: No libc on Windows - use std::ptr; // null is explicitly called out as UB in the C docs. diff --git a/tests/fail-dep/shims/memcmp_null.stderr b/tests/fail-dep/libc/memcmp_null.stderr similarity index 100% rename from tests/fail-dep/shims/memcmp_null.stderr rename to tests/fail-dep/libc/memcmp_null.stderr diff --git a/tests/fail-dep/shims/memcmp_zero.rs b/tests/fail-dep/libc/memcmp_zero.rs similarity index 90% rename from tests/fail-dep/shims/memcmp_zero.rs rename to tests/fail-dep/libc/memcmp_zero.rs index f2ddc20056..e28aa26270 100644 --- a/tests/fail-dep/shims/memcmp_zero.rs +++ b/tests/fail-dep/libc/memcmp_zero.rs @@ -1,4 +1,3 @@ -//@ignore-target-windows: No libc on Windows //@compile-flags: -Zmiri-permissive-provenance // C says that passing "invalid" pointers is UB for all string functions. diff --git a/tests/fail-dep/shims/memcmp_zero.stderr b/tests/fail-dep/libc/memcmp_zero.stderr similarity index 100% rename from tests/fail-dep/shims/memcmp_zero.stderr rename to tests/fail-dep/libc/memcmp_zero.stderr diff --git a/tests/fail-dep/shims/memcpy_zero.rs b/tests/fail-dep/libc/memcpy_zero.rs similarity index 88% rename from tests/fail-dep/shims/memcpy_zero.rs rename to tests/fail-dep/libc/memcpy_zero.rs index 5283fea4cb..b37ed7df74 100644 --- a/tests/fail-dep/shims/memcpy_zero.rs +++ b/tests/fail-dep/libc/memcpy_zero.rs @@ -1,4 +1,3 @@ -//@ignore-target-windows: No libc on Windows //@compile-flags: -Zmiri-permissive-provenance // C's memcpy is 0 bytes is UB for some pointers that are allowed in Rust's `copy_nonoverlapping`. diff --git a/tests/fail-dep/shims/memcpy_zero.stderr b/tests/fail-dep/libc/memcpy_zero.stderr similarity index 100% rename from tests/fail-dep/shims/memcpy_zero.stderr rename to tests/fail-dep/libc/memcpy_zero.stderr diff --git a/tests/fail-dep/shims/memrchr_null.rs b/tests/fail-dep/libc/memrchr_null.rs similarity index 81% rename from tests/fail-dep/shims/memrchr_null.rs rename to tests/fail-dep/libc/memrchr_null.rs index b6707d558d..f06336b129 100644 --- a/tests/fail-dep/shims/memrchr_null.rs +++ b/tests/fail-dep/libc/memrchr_null.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No `memrchr` on Windows //@ignore-target-apple: No `memrchr` on some apple targets use std::ptr; diff --git a/tests/fail-dep/shims/memrchr_null.stderr b/tests/fail-dep/libc/memrchr_null.stderr similarity index 100% rename from tests/fail-dep/shims/memrchr_null.stderr rename to tests/fail-dep/libc/memrchr_null.stderr diff --git a/tests/fail-dep/shims/mmap_invalid_dealloc.rs b/tests/fail-dep/libc/mmap_invalid_dealloc.rs similarity index 90% rename from tests/fail-dep/shims/mmap_invalid_dealloc.rs rename to tests/fail-dep/libc/mmap_invalid_dealloc.rs index 70f7a6a7ce..9d55a49355 100644 --- a/tests/fail-dep/shims/mmap_invalid_dealloc.rs +++ b/tests/fail-dep/libc/mmap_invalid_dealloc.rs @@ -1,5 +1,5 @@ //@compile-flags: -Zmiri-disable-isolation -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No mmap on Windows #![feature(rustc_private)] diff --git a/tests/fail-dep/shims/mmap_invalid_dealloc.stderr b/tests/fail-dep/libc/mmap_invalid_dealloc.stderr similarity index 100% rename from tests/fail-dep/shims/mmap_invalid_dealloc.stderr rename to tests/fail-dep/libc/mmap_invalid_dealloc.stderr diff --git a/tests/fail-dep/shims/mmap_use_after_munmap.rs b/tests/fail-dep/libc/mmap_use_after_munmap.rs similarity index 90% rename from tests/fail-dep/shims/mmap_use_after_munmap.rs rename to tests/fail-dep/libc/mmap_use_after_munmap.rs index c97b013ba5..05ac232f5d 100644 --- a/tests/fail-dep/shims/mmap_use_after_munmap.rs +++ b/tests/fail-dep/libc/mmap_use_after_munmap.rs @@ -1,5 +1,5 @@ //@compile-flags: -Zmiri-disable-isolation -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No mmap on Windows #![feature(rustc_private)] diff --git a/tests/fail-dep/shims/mmap_use_after_munmap.stderr b/tests/fail-dep/libc/mmap_use_after_munmap.stderr similarity index 100% rename from tests/fail-dep/shims/mmap_use_after_munmap.stderr rename to tests/fail-dep/libc/mmap_use_after_munmap.stderr diff --git a/tests/fail-dep/shims/munmap_partial.rs b/tests/fail-dep/libc/munmap_partial.rs similarity index 94% rename from tests/fail-dep/shims/munmap_partial.rs rename to tests/fail-dep/libc/munmap_partial.rs index d7aef47235..4386dc71af 100644 --- a/tests/fail-dep/shims/munmap_partial.rs +++ b/tests/fail-dep/libc/munmap_partial.rs @@ -1,7 +1,7 @@ //! The man pages for mmap/munmap suggest that it is possible to partly unmap a previously-mapped //! region of address space, but to LLVM that would be partial deallocation, which LLVM does not //! support. So even though the man pages say this sort of use is possible, we must report UB. -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No mmap on Windows //@normalize-stderr-test: "size [0-9]+ and alignment" -> "size SIZE and alignment" fn main() { diff --git a/tests/fail-dep/shims/munmap_partial.stderr b/tests/fail-dep/libc/munmap_partial.stderr similarity index 100% rename from tests/fail-dep/shims/munmap_partial.stderr rename to tests/fail-dep/libc/munmap_partial.stderr diff --git a/tests/fail-dep/libc/posix_memalign_size_zero_double_free.rs b/tests/fail-dep/libc/posix_memalign_size_zero_double_free.rs new file mode 100644 index 0000000000..b6b7b007f2 --- /dev/null +++ b/tests/fail-dep/libc/posix_memalign_size_zero_double_free.rs @@ -0,0 +1,14 @@ +//@ignore-target-windows: No posix_memalign on Windows + +use std::ptr; + +fn main() { + let mut ptr: *mut libc::c_void = ptr::null_mut(); + let align = 64; + let size = 0; + unsafe { + let _ = libc::posix_memalign(&mut ptr, align, size); + libc::free(ptr); + libc::free(ptr); //~ERROR: dangling + } +} diff --git a/tests/fail-dep/libc/posix_memalign_size_zero_double_free.stderr b/tests/fail-dep/libc/posix_memalign_size_zero_double_free.stderr new file mode 100644 index 0000000000..3ed117c5a0 --- /dev/null +++ b/tests/fail-dep/libc/posix_memalign_size_zero_double_free.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling + --> $DIR/posix_memalign_size_zero_double_free.rs:LL:CC + | +LL | libc::free(ptr); + | ^^^^^^^^^^^^^^^ memory access failed: ALLOC has been freed, so this pointer is dangling + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> $DIR/posix_memalign_size_zero_double_free.rs:LL:CC + | +LL | let _ = libc::posix_memalign(&mut ptr, align, size); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> $DIR/posix_memalign_size_zero_double_free.rs:LL:CC + | +LL | libc::free(ptr); + | ^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/posix_memalign_size_zero_double_free.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/tests/fail-dep/libc/posix_memalign_size_zero_leak.rs b/tests/fail-dep/libc/posix_memalign_size_zero_leak.rs new file mode 100644 index 0000000000..1a4c9605fe --- /dev/null +++ b/tests/fail-dep/libc/posix_memalign_size_zero_leak.rs @@ -0,0 +1,10 @@ +//@ignore-target-windows: No posix_memalign on Windows + +use std::ptr; + +fn main() { + let mut ptr: *mut libc::c_void = ptr::null_mut(); + let align = 64; + let size = 0; + let _ = unsafe { libc::posix_memalign(&mut ptr, align, size) }; //~ERROR: memory leak +} diff --git a/tests/fail-dep/libc/posix_memalign_size_zero_leak.stderr b/tests/fail-dep/libc/posix_memalign_size_zero_leak.stderr new file mode 100644 index 0000000000..7ea0fa3146 --- /dev/null +++ b/tests/fail-dep/libc/posix_memalign_size_zero_leak.stderr @@ -0,0 +1,15 @@ +error: memory leaked: ALLOC (C heap, size: 0, align: 64), allocated here: + --> $DIR/posix_memalign_size_zero_leak.rs:LL:CC + | +LL | let _ = unsafe { libc::posix_memalign(&mut ptr, align, size) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: BACKTRACE: + = note: inside `main` at $DIR/posix_memalign_size_zero_leak.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +note: the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check + +error: aborting due to 1 previous error + diff --git a/tests/fail-dep/libc/realloc-zero.rs b/tests/fail-dep/libc/realloc-zero.rs new file mode 100644 index 0000000000..0e210f3135 --- /dev/null +++ b/tests/fail-dep/libc/realloc-zero.rs @@ -0,0 +1,8 @@ +fn main() { + unsafe { + let p1 = libc::malloc(20); + // C made this UB... + let p2 = libc::realloc(p1, 0); //~ERROR: `realloc` with a size of zero + assert!(p2.is_null()); + } +} diff --git a/tests/fail-dep/libc/realloc-zero.stderr b/tests/fail-dep/libc/realloc-zero.stderr new file mode 100644 index 0000000000..749a61f739 --- /dev/null +++ b/tests/fail-dep/libc/realloc-zero.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `realloc` with a size of zero + --> $DIR/realloc-zero.rs:LL:CC + | +LL | let p2 = libc::realloc(p1, 0); + | ^^^^^^^^^^^^^^^^^^^^ `realloc` with a size of zero + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/realloc-zero.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/tests/fail-dep/unsupported_incomplete_function.rs b/tests/fail-dep/libc/unsupported_incomplete_function.rs similarity index 87% rename from tests/fail-dep/unsupported_incomplete_function.rs rename to tests/fail-dep/libc/unsupported_incomplete_function.rs index 6ef842c9cc..cd8422f4af 100644 --- a/tests/fail-dep/unsupported_incomplete_function.rs +++ b/tests/fail-dep/libc/unsupported_incomplete_function.rs @@ -1,6 +1,6 @@ //! `signal()` is special on Linux and macOS that it's only supported within libstd. //! The implementation is not complete enough to permit user code to call it. -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No `libc::signal` on Windows //@normalize-stderr-test: "OS `.*`" -> "$$OS" fn main() { diff --git a/tests/fail-dep/unsupported_incomplete_function.stderr b/tests/fail-dep/libc/unsupported_incomplete_function.stderr similarity index 65% rename from tests/fail-dep/unsupported_incomplete_function.stderr rename to tests/fail-dep/libc/unsupported_incomplete_function.stderr index fabe3bbf12..f62622e29b 100644 --- a/tests/fail-dep/unsupported_incomplete_function.stderr +++ b/tests/fail-dep/libc/unsupported_incomplete_function.stderr @@ -4,7 +4,8 @@ error: unsupported operation: can't call foreign function `signal` on $OS LL | libc::signal(libc::SIGPIPE, libc::SIG_IGN); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ can't call foreign function `signal` on $OS | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: if this is a basic API commonly used on this target, please report an issue with Miri + = help: however, note that Miri does not aim to support every FFI function out there; for instance, we will not support APIs for things such as GUIs, scripting languages, or databases = note: BACKTRACE: = note: inside `main` at $DIR/unsupported_incomplete_function.rs:LL:CC diff --git a/tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.stderr b/tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.stderr deleted file mode 100644 index 76b1d26bd3..0000000000 --- a/tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error: deadlock: the evaluated program deadlocked - --> $DIR/libc_pthread_mutex_deadlock.rs:LL:CC - | -LL | assert_eq!(libc::pthread_mutex_lock(lock_copy.0.get() as *mut _), 0); - | ^ the evaluated program deadlocked - | - = note: BACKTRACE on thread `unnamed-ID`: - = note: inside closure at $DIR/libc_pthread_mutex_deadlock.rs:LL:CC - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to 1 previous error - diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock.stderr b/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock.stderr deleted file mode 100644 index 5501dab81a..0000000000 --- a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error: deadlock: the evaluated program deadlocked - --> $DIR/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC - | -LL | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0); - | ^ the evaluated program deadlocked - | - = note: BACKTRACE on thread `unnamed-ID`: - = note: inside closure at $DIR/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to 1 previous error - diff --git a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock.stderr b/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock.stderr deleted file mode 100644 index 815d85af50..0000000000 --- a/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error: deadlock: the evaluated program deadlocked - --> $DIR/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC - | -LL | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0); - | ^ the evaluated program deadlocked - | - = note: BACKTRACE on thread `unnamed-ID`: - = note: inside closure at $DIR/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to 1 previous error - diff --git a/tests/fail-dep/tokio/sleep.stderr b/tests/fail-dep/tokio/sleep.stderr index 59179478c3..4b12729bbf 100644 --- a/tests/fail-dep/tokio/sleep.stderr +++ b/tests/fail-dep/tokio/sleep.stderr @@ -9,7 +9,7 @@ LL | | timeout, LL | | )) | |__________^ returning ready events from epoll_wait is not yet implemented | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support error: aborting due to 1 previous error diff --git a/tests/fail/alloc/alloc_error_handler.rs b/tests/fail/alloc/alloc_error_handler.rs new file mode 100644 index 0000000000..2097126e16 --- /dev/null +++ b/tests/fail/alloc/alloc_error_handler.rs @@ -0,0 +1,10 @@ +//@error-in-other-file: aborted +//@normalize-stderr-test: "unsafe \{ libc::abort\(\) \}|crate::intrinsics::abort\(\);" -> "ABORT();" +//@normalize-stderr-test: "\| +\^+" -> "| ^" +#![feature(allocator_api)] + +use std::alloc::*; + +fn main() { + handle_alloc_error(Layout::for_value(&0)); +} diff --git a/tests/fail/alloc/alloc_error_handler.stderr b/tests/fail/alloc/alloc_error_handler.stderr new file mode 100644 index 0000000000..d1731a0f42 --- /dev/null +++ b/tests/fail/alloc/alloc_error_handler.stderr @@ -0,0 +1,24 @@ +memory allocation of 4 bytes failed +error: abnormal termination: the program aborted execution + --> RUSTLIB/std/src/sys/pal/PLATFORM/mod.rs:LL:CC + | +LL | ABORT(); + | ^ the program aborted execution + | + = note: BACKTRACE: + = note: inside `std::sys::pal::PLATFORM::abort_internal` at RUSTLIB/std/src/sys/pal/PLATFORM/mod.rs:LL:CC + = note: inside `std::process::abort` at RUSTLIB/std/src/process.rs:LL:CC + = note: inside `std::alloc::rust_oom` at RUSTLIB/std/src/alloc.rs:LL:CC + = note: inside `std::alloc::_::__rg_oom` at RUSTLIB/std/src/alloc.rs:LL:CC + = note: inside `std::alloc::handle_alloc_error::rt_error` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `std::alloc::handle_alloc_error` at RUSTLIB/alloc/src/alloc.rs:LL:CC +note: inside `main` + --> $DIR/alloc_error_handler.rs:LL:CC + | +LL | handle_alloc_error(Layout::for_value(&0)); + | ^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/tests/fail/alloc/alloc_error_handler_custom.rs b/tests/fail/alloc/alloc_error_handler_custom.rs new file mode 100644 index 0000000000..babdb73f09 --- /dev/null +++ b/tests/fail/alloc/alloc_error_handler_custom.rs @@ -0,0 +1,49 @@ +//@compile-flags: -Cpanic=abort +#![feature(start, core_intrinsics)] +#![feature(alloc_error_handler)] +#![feature(allocator_api)] +#![no_std] + +extern crate alloc; + +use alloc::alloc::*; +use core::fmt::Write; + +#[path = "../../utils/mod.no_std.rs"] +mod utils; + +#[alloc_error_handler] +fn alloc_error_handler(layout: Layout) -> ! { + let _ = writeln!(utils::MiriStderr, "custom alloc error handler: {layout:?}"); + core::intrinsics::abort(); //~ERROR: aborted +} + +// rustc requires us to provide some more things that aren't actually used by this test +mod plumbing { + use super::*; + + #[panic_handler] + fn panic_handler(_: &core::panic::PanicInfo) -> ! { + loop {} + } + + struct NoAlloc; + + unsafe impl GlobalAlloc for NoAlloc { + unsafe fn alloc(&self, _: Layout) -> *mut u8 { + unreachable!(); + } + + unsafe fn dealloc(&self, _: *mut u8, _: Layout) { + unreachable!(); + } + } + + #[global_allocator] + static GLOBAL: NoAlloc = NoAlloc; +} + +#[start] +fn start(_: isize, _: *const *const u8) -> isize { + handle_alloc_error(Layout::for_value(&0)); +} diff --git a/tests/fail/alloc/alloc_error_handler_custom.stderr b/tests/fail/alloc/alloc_error_handler_custom.stderr new file mode 100644 index 0000000000..5d9c2e2fb4 --- /dev/null +++ b/tests/fail/alloc/alloc_error_handler_custom.stderr @@ -0,0 +1,27 @@ +custom alloc error handler: Layout { size: 4, align: 4 (1 << 2) } +error: abnormal termination: the program aborted execution + --> $DIR/alloc_error_handler_custom.rs:LL:CC + | +LL | core::intrinsics::abort(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ the program aborted execution + | + = note: BACKTRACE: + = note: inside `alloc_error_handler` at $DIR/alloc_error_handler_custom.rs:LL:CC +note: inside `_::__rg_oom` + --> $DIR/alloc_error_handler_custom.rs:LL:CC + | +LL | #[alloc_error_handler] + | ---------------------- in this procedural macro expansion +LL | fn alloc_error_handler(layout: Layout) -> ! { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: inside `alloc::alloc::handle_alloc_error::rt_error` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `alloc::alloc::handle_alloc_error` at RUSTLIB/alloc/src/alloc.rs:LL:CC +note: inside `start` + --> $DIR/alloc_error_handler_custom.rs:LL:CC + | +LL | handle_alloc_error(Layout::for_value(&0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the attribute macro `alloc_error_handler` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + diff --git a/tests/fail/alloc/alloc_error_handler_no_std.rs b/tests/fail/alloc/alloc_error_handler_no_std.rs new file mode 100644 index 0000000000..18a8a61f22 --- /dev/null +++ b/tests/fail/alloc/alloc_error_handler_no_std.rs @@ -0,0 +1,47 @@ +//@compile-flags: -Cpanic=abort +#![feature(start, core_intrinsics)] +#![feature(alloc_error_handler)] +#![feature(allocator_api)] +#![no_std] + +extern crate alloc; + +use alloc::alloc::*; +use core::fmt::Write; + +#[path = "../../utils/mod.no_std.rs"] +mod utils; + +// The default no_std alloc_error_handler is a panic. + +#[panic_handler] +fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! { + let _ = writeln!(utils::MiriStderr, "custom panic handler called!"); + let _ = writeln!(utils::MiriStderr, "{panic_info}"); + core::intrinsics::abort(); //~ERROR: aborted +} + +// rustc requires us to provide some more things that aren't actually used by this test +mod plumbing { + use super::*; + + struct NoAlloc; + + unsafe impl GlobalAlloc for NoAlloc { + unsafe fn alloc(&self, _: Layout) -> *mut u8 { + unreachable!(); + } + + unsafe fn dealloc(&self, _: *mut u8, _: Layout) { + unreachable!(); + } + } + + #[global_allocator] + static GLOBAL: NoAlloc = NoAlloc; +} + +#[start] +fn start(_: isize, _: *const *const u8) -> isize { + handle_alloc_error(Layout::for_value(&0)); +} diff --git a/tests/fail/alloc/alloc_error_handler_no_std.stderr b/tests/fail/alloc/alloc_error_handler_no_std.stderr new file mode 100644 index 0000000000..6b98f6f75d --- /dev/null +++ b/tests/fail/alloc/alloc_error_handler_no_std.stderr @@ -0,0 +1,24 @@ +custom panic handler called! +panicked at RUSTLIB/alloc/src/alloc.rs:LL:CC: +memory allocation of 4 bytes failed +error: abnormal termination: the program aborted execution + --> $DIR/alloc_error_handler_no_std.rs:LL:CC + | +LL | core::intrinsics::abort(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ the program aborted execution + | + = note: BACKTRACE: + = note: inside `panic_handler` at $DIR/alloc_error_handler_no_std.rs:LL:CC + = note: inside `alloc::alloc::__alloc_error_handler::__rdl_oom` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `alloc::alloc::handle_alloc_error::rt_error` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `alloc::alloc::handle_alloc_error` at RUSTLIB/alloc/src/alloc.rs:LL:CC +note: inside `start` + --> $DIR/alloc_error_handler_no_std.rs:LL:CC + | +LL | handle_alloc_error(Layout::for_value(&0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/tests/fail/alloc/no_global_allocator.stderr b/tests/fail/alloc/no_global_allocator.stderr index bd2a6c628f..82bcb48cbe 100644 --- a/tests/fail/alloc/no_global_allocator.stderr +++ b/tests/fail/alloc/no_global_allocator.stderr @@ -4,7 +4,8 @@ error: unsupported operation: can't call foreign function `__rust_alloc` on $OS LL | __rust_alloc(1, 1); | ^^^^^^^^^^^^^^^^^^ can't call foreign function `__rust_alloc` on $OS | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: if this is a basic API commonly used on this target, please report an issue with Miri + = help: however, note that Miri does not aim to support every FFI function out there; for instance, we will not support APIs for things such as GUIs, scripting languages, or databases = note: BACKTRACE: = note: inside `start` at $DIR/no_global_allocator.rs:LL:CC diff --git a/tests/fail/both_borrows/aliasing_mut4.rs b/tests/fail/both_borrows/aliasing_mut4.rs index e188a1f0c3..c656a50964 100644 --- a/tests/fail/both_borrows/aliasing_mut4.rs +++ b/tests/fail/both_borrows/aliasing_mut4.rs @@ -8,7 +8,7 @@ use std::mem; pub fn safe(x: &i32, y: &mut Cell) { //~[stack]^ ERROR: protect y.set(1); - let _ = *x; + let _load = *x; } fn main() { diff --git a/tests/fail/both_borrows/invalidate_against_protector3.stack.stderr b/tests/fail/both_borrows/invalidate_against_protector3.stack.stderr index f6eeef33e9..8426f56004 100644 --- a/tests/fail/both_borrows/invalidate_against_protector3.stack.stderr +++ b/tests/fail/both_borrows/invalidate_against_protector3.stack.stderr @@ -6,7 +6,7 @@ LL | unsafe { *x = 0 }; | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information -help: was created here, as the base tag for ALLOC +help: was created here, as the root tag for ALLOC --> $DIR/invalidate_against_protector3.rs:LL:CC | LL | let ptr = alloc(Layout::for_value(&0i32)) as *mut i32; diff --git a/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr b/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr index 867907e98e..c26c7f397b 100644 --- a/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr +++ b/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr @@ -6,7 +6,7 @@ LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc) | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information -help: was created by a Unique retag at offsets [0x0..0x4] +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] --> $DIR/newtype_pair_retagging.rs:LL:CC | LL | let ptr = Box::into_raw(Box::new(0i32)); diff --git a/tests/fail/both_borrows/newtype_retagging.stack.stderr b/tests/fail/both_borrows/newtype_retagging.stack.stderr index 56715938e9..ae54da70fe 100644 --- a/tests/fail/both_borrows/newtype_retagging.stack.stderr +++ b/tests/fail/both_borrows/newtype_retagging.stack.stderr @@ -6,7 +6,7 @@ LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc) | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information -help: was created by a Unique retag at offsets [0x0..0x4] +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] --> $DIR/newtype_retagging.rs:LL:CC | LL | let ptr = Box::into_raw(Box::new(0i32)); diff --git a/tests/fail/both_borrows/retag_data_race_write.rs b/tests/fail/both_borrows/retag_data_race_write.rs index 3edaf10f3d..0061679eaa 100644 --- a/tests/fail/both_borrows/retag_data_race_write.rs +++ b/tests/fail/both_borrows/retag_data_race_write.rs @@ -1,6 +1,8 @@ //! Make sure that a retag acts like a write for the data race model. //@revisions: stack tree //@compile-flags: -Zmiri-preemption-rate=0 +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 //@[tree]compile-flags: -Zmiri-tree-borrows #[derive(Copy, Clone)] struct SendPtr(*mut u8); diff --git a/tests/fail/coroutine-pinned-moved.rs b/tests/fail/coroutine-pinned-moved.rs index 005ae7e913..46ec58810a 100644 --- a/tests/fail/coroutine-pinned-moved.rs +++ b/tests/fail/coroutine-pinned-moved.rs @@ -1,5 +1,5 @@ //@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows -#![feature(coroutines, coroutine_trait)] +#![feature(coroutines, coroutine_trait, stmt_expr_attributes)] use std::{ ops::{Coroutine, CoroutineState}, @@ -7,6 +7,7 @@ use std::{ }; fn firstn() -> impl Coroutine { + #[coroutine] static move || { let mut num = 0; let num = &mut num; diff --git a/tests/fail/dangling_pointers/storage_dead_dangling.rs b/tests/fail/dangling_pointers/storage_dead_dangling.rs index f9983f48c6..f434928680 100644 --- a/tests/fail/dangling_pointers/storage_dead_dangling.rs +++ b/tests/fail/dangling_pointers/storage_dead_dangling.rs @@ -10,7 +10,7 @@ fn fill(v: &mut i32) { } fn evil() { - let _ = unsafe { &mut *(LEAK as *mut i32) }; //~ ERROR: is a dangling pointer + let _ref = unsafe { &mut *(LEAK as *mut i32) }; //~ ERROR: is a dangling pointer } fn main() { diff --git a/tests/fail/dangling_pointers/storage_dead_dangling.stderr b/tests/fail/dangling_pointers/storage_dead_dangling.stderr index 27e5a86506..73c3ff1ee0 100644 --- a/tests/fail/dangling_pointers/storage_dead_dangling.stderr +++ b/tests/fail/dangling_pointers/storage_dead_dangling.stderr @@ -1,8 +1,8 @@ error: Undefined Behavior: out-of-bounds pointer use: $HEX[noalloc] is a dangling pointer (it has no provenance) --> $DIR/storage_dead_dangling.rs:LL:CC | -LL | let _ = unsafe { &mut *(LEAK as *mut i32) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: $HEX[noalloc] is a dangling pointer (it has no provenance) +LL | let _ref = unsafe { &mut *(LEAK as *mut i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: $HEX[noalloc] is a dangling pointer (it has no provenance) | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/tests/fail/data_race/alloc_read_race.rs b/tests/fail/data_race/alloc_read_race.rs index 2cf3660690..c85c0ebe24 100644 --- a/tests/fail/data_race/alloc_read_race.rs +++ b/tests/fail/data_race/alloc_read_race.rs @@ -1,4 +1,6 @@ //@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 #![feature(new_uninit)] use std::mem::MaybeUninit; diff --git a/tests/fail/data_race/alloc_write_race.rs b/tests/fail/data_race/alloc_write_race.rs index e95e0e1a84..9e2a430dd9 100644 --- a/tests/fail/data_race/alloc_write_race.rs +++ b/tests/fail/data_race/alloc_write_race.rs @@ -1,4 +1,6 @@ //@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 #![feature(new_uninit)] use std::ptr::null_mut; diff --git a/tests/fail/data_race/atomic_read_na_write_race1.rs b/tests/fail/data_race/atomic_read_na_write_race1.rs index a256267bcd..4003892f0a 100644 --- a/tests/fail/data_race/atomic_read_na_write_race1.rs +++ b/tests/fail/data_race/atomic_read_na_write_race1.rs @@ -1,5 +1,7 @@ // We want to control preemption here. Stacked borrows interferes by having its own accesses. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread::spawn; diff --git a/tests/fail/data_race/atomic_read_na_write_race2.rs b/tests/fail/data_race/atomic_read_na_write_race2.rs index cc6a0742c2..8bceba9380 100644 --- a/tests/fail/data_race/atomic_read_na_write_race2.rs +++ b/tests/fail/data_race/atomic_read_na_write_race2.rs @@ -1,5 +1,7 @@ // We want to control preemption here. Stacked borrows interferes by having its own accesses. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; diff --git a/tests/fail/data_race/atomic_write_na_read_race1.rs b/tests/fail/data_race/atomic_write_na_read_race1.rs index 7392781e6c..1a2746a26f 100644 --- a/tests/fail/data_race/atomic_write_na_read_race1.rs +++ b/tests/fail/data_race/atomic_write_na_read_race1.rs @@ -1,5 +1,7 @@ // We want to control preemption here. Stacked borrows interferes by having its own accesses. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; diff --git a/tests/fail/data_race/atomic_write_na_read_race2.rs b/tests/fail/data_race/atomic_write_na_read_race2.rs index f681ce0c05..e0876a93fd 100644 --- a/tests/fail/data_race/atomic_write_na_read_race2.rs +++ b/tests/fail/data_race/atomic_write_na_read_race2.rs @@ -1,5 +1,7 @@ // We want to control preemption here. Stacked borrows interferes by having its own accesses. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread::spawn; diff --git a/tests/fail/data_race/atomic_write_na_write_race1.rs b/tests/fail/data_race/atomic_write_na_write_race1.rs index 47a3ef5d16..1010216a49 100644 --- a/tests/fail/data_race/atomic_write_na_write_race1.rs +++ b/tests/fail/data_race/atomic_write_na_write_race1.rs @@ -1,5 +1,7 @@ // We want to control preemption here. Stacked borrows interferes by having its own accesses. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread::spawn; diff --git a/tests/fail/data_race/atomic_write_na_write_race2.rs b/tests/fail/data_race/atomic_write_na_write_race2.rs index 8bba4a8892..b494bd3a00 100644 --- a/tests/fail/data_race/atomic_write_na_write_race2.rs +++ b/tests/fail/data_race/atomic_write_na_write_race2.rs @@ -1,5 +1,7 @@ // We want to control preemption here. Stacked borrows interferes by having its own accesses. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; diff --git a/tests/fail/data_race/dangling_thread_async_race.rs b/tests/fail/data_race/dangling_thread_async_race.rs index 5b9005606e..dffafe3cfa 100644 --- a/tests/fail/data_race/dangling_thread_async_race.rs +++ b/tests/fail/data_race/dangling_thread_async_race.rs @@ -1,5 +1,7 @@ // We want to control preemption here. Stacked borrows interferes by having its own accesses. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::mem; use std::thread::{sleep, spawn}; diff --git a/tests/fail/data_race/dangling_thread_race.rs b/tests/fail/data_race/dangling_thread_race.rs index 91c1191e03..8dc35c7ea7 100644 --- a/tests/fail/data_race/dangling_thread_race.rs +++ b/tests/fail/data_race/dangling_thread_race.rs @@ -1,5 +1,7 @@ // We want to control preemption here. Stacked borrows interferes by having its own accesses. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::mem; use std::thread::{sleep, spawn}; diff --git a/tests/fail/data_race/dealloc_read_race1.rs b/tests/fail/data_race/dealloc_read_race1.rs index 5928e47176..f174909e9d 100644 --- a/tests/fail/data_race/dealloc_read_race1.rs +++ b/tests/fail/data_race/dealloc_read_race1.rs @@ -1,5 +1,7 @@ // We want to control preemption here. Stacked borrows interferes by having its own accesses. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::thread::spawn; diff --git a/tests/fail/data_race/dealloc_read_race2.rs b/tests/fail/data_race/dealloc_read_race2.rs index c5f82cc9a7..1edfbf5e61 100644 --- a/tests/fail/data_race/dealloc_read_race2.rs +++ b/tests/fail/data_race/dealloc_read_race2.rs @@ -1,5 +1,7 @@ // We want to control preemption here. Stacked borrows interferes by having its own accesses. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::thread::spawn; diff --git a/tests/fail/data_race/dealloc_read_race_stack.rs b/tests/fail/data_race/dealloc_read_race_stack.rs index 1095f1e4e8..c67e03d362 100644 --- a/tests/fail/data_race/dealloc_read_race_stack.rs +++ b/tests/fail/data_race/dealloc_read_race_stack.rs @@ -1,4 +1,6 @@ //@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::ptr::null_mut; use std::sync::atomic::{AtomicPtr, Ordering}; diff --git a/tests/fail/data_race/dealloc_write_race1.rs b/tests/fail/data_race/dealloc_write_race1.rs index b5911e5111..7605f1911d 100644 --- a/tests/fail/data_race/dealloc_write_race1.rs +++ b/tests/fail/data_race/dealloc_write_race1.rs @@ -1,5 +1,7 @@ // We want to control preemption here. Stacked borrows interferes by having its own accesses. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::thread::spawn; diff --git a/tests/fail/data_race/dealloc_write_race2.rs b/tests/fail/data_race/dealloc_write_race2.rs index 7a2c882f7e..4f3819bd63 100644 --- a/tests/fail/data_race/dealloc_write_race2.rs +++ b/tests/fail/data_race/dealloc_write_race2.rs @@ -1,5 +1,7 @@ // We want to control preemption here. Stacked borrows interferes by having its own accesses. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::thread::spawn; diff --git a/tests/fail/data_race/dealloc_write_race_stack.rs b/tests/fail/data_race/dealloc_write_race_stack.rs index 5ee4cc04a8..8e63bc1dc7 100644 --- a/tests/fail/data_race/dealloc_write_race_stack.rs +++ b/tests/fail/data_race/dealloc_write_race_stack.rs @@ -1,4 +1,6 @@ //@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::ptr::null_mut; use std::sync::atomic::{AtomicPtr, Ordering}; diff --git a/tests/fail/data_race/enable_after_join_to_main.rs b/tests/fail/data_race/enable_after_join_to_main.rs index f2da45d727..53050608d2 100644 --- a/tests/fail/data_race/enable_after_join_to_main.rs +++ b/tests/fail/data_race/enable_after_join_to_main.rs @@ -1,5 +1,7 @@ // We want to control preemption here. Stacked borrows interferes by having its own accesses. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::thread::spawn; diff --git a/tests/fail/data_race/fence_after_load.rs b/tests/fail/data_race/fence_after_load.rs index 683e3b9c7a..92cb4ccccf 100644 --- a/tests/fail/data_race/fence_after_load.rs +++ b/tests/fail/data_race/fence_after_load.rs @@ -1,5 +1,8 @@ // We want to control preemption here. Stacked borrows interferes by having its own accesses. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 + use std::sync::atomic::{fence, AtomicUsize, Ordering}; use std::sync::Arc; use std::thread; diff --git a/tests/fail/data_race/mixed_size_read.rs b/tests/fail/data_race/mixed_size_read.rs index 091a47070b..61af972b3d 100644 --- a/tests/fail/data_race/mixed_size_read.rs +++ b/tests/fail/data_race/mixed_size_read.rs @@ -1,4 +1,7 @@ //@compile-flags: -Zmiri-preemption-rate=0.0 -Zmiri-disable-weak-memory-emulation +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 + use std::sync::atomic::{AtomicU16, AtomicU8, Ordering}; use std::thread; diff --git a/tests/fail/data_race/mixed_size_write.rs b/tests/fail/data_race/mixed_size_write.rs index 49fb6c1d5c..12e51bb942 100644 --- a/tests/fail/data_race/mixed_size_write.rs +++ b/tests/fail/data_race/mixed_size_write.rs @@ -1,4 +1,7 @@ //@compile-flags: -Zmiri-preemption-rate=0.0 -Zmiri-disable-weak-memory-emulation +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 + use std::sync::atomic::{AtomicU16, AtomicU8, Ordering}; use std::thread; diff --git a/tests/fail/data_race/read_read_race1.rs b/tests/fail/data_race/read_read_race1.rs index f66b5ca3d5..02aa4e4b71 100644 --- a/tests/fail/data_race/read_read_race1.rs +++ b/tests/fail/data_race/read_read_race1.rs @@ -1,4 +1,7 @@ //@compile-flags: -Zmiri-preemption-rate=0.0 +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 + use std::sync::atomic::{AtomicU16, Ordering}; use std::thread; diff --git a/tests/fail/data_race/read_read_race2.rs b/tests/fail/data_race/read_read_race2.rs index d87b667d91..3b94c9143f 100644 --- a/tests/fail/data_race/read_read_race2.rs +++ b/tests/fail/data_race/read_read_race2.rs @@ -1,4 +1,7 @@ //@compile-flags: -Zmiri-preemption-rate=0.0 +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 + use std::sync::atomic::{AtomicU16, Ordering}; use std::thread; diff --git a/tests/fail/data_race/read_write_race.rs b/tests/fail/data_race/read_write_race.rs index 70971b59ff..adf19dda9d 100644 --- a/tests/fail/data_race/read_write_race.rs +++ b/tests/fail/data_race/read_write_race.rs @@ -1,5 +1,7 @@ // We want to control preemption here. Stacked borrows interferes by having its own accesses. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::thread::spawn; diff --git a/tests/fail/data_race/read_write_race_stack.rs b/tests/fail/data_race/read_write_race_stack.rs index 9fec3ceee0..f411767f7b 100644 --- a/tests/fail/data_race/read_write_race_stack.rs +++ b/tests/fail/data_race/read_write_race_stack.rs @@ -1,4 +1,6 @@ //@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::ptr::null_mut; use std::sync::atomic::{AtomicPtr, Ordering}; diff --git a/tests/fail/data_race/relax_acquire_race.rs b/tests/fail/data_race/relax_acquire_race.rs index be4450794c..c4f9438082 100644 --- a/tests/fail/data_race/relax_acquire_race.rs +++ b/tests/fail/data_race/relax_acquire_race.rs @@ -1,4 +1,6 @@ //@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread::spawn; diff --git a/tests/fail/data_race/release_seq_race.rs b/tests/fail/data_race/release_seq_race.rs index 9810832413..f03ab3efa0 100644 --- a/tests/fail/data_race/release_seq_race.rs +++ b/tests/fail/data_race/release_seq_race.rs @@ -1,4 +1,6 @@ //@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread::{sleep, spawn}; diff --git a/tests/fail/data_race/release_seq_race_same_thread.rs b/tests/fail/data_race/release_seq_race_same_thread.rs index 93cbc2a57d..88ae01b3ca 100644 --- a/tests/fail/data_race/release_seq_race_same_thread.rs +++ b/tests/fail/data_race/release_seq_race_same_thread.rs @@ -1,4 +1,6 @@ //@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread::spawn; diff --git a/tests/fail/data_race/rmw_race.rs b/tests/fail/data_race/rmw_race.rs index 982e9c1c41..d738caa105 100644 --- a/tests/fail/data_race/rmw_race.rs +++ b/tests/fail/data_race/rmw_race.rs @@ -1,4 +1,6 @@ //@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread::spawn; diff --git a/tests/fail/data_race/stack_pop_race.rs b/tests/fail/data_race/stack_pop_race.rs index 68d82bc30a..762a8e51f6 100644 --- a/tests/fail/data_race/stack_pop_race.rs +++ b/tests/fail/data_race/stack_pop_race.rs @@ -1,4 +1,7 @@ //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 + use std::thread; #[derive(Copy, Clone)] diff --git a/tests/fail/data_race/write_write_race.rs b/tests/fail/data_race/write_write_race.rs index e8924702af..993d8d25b4 100644 --- a/tests/fail/data_race/write_write_race.rs +++ b/tests/fail/data_race/write_write_race.rs @@ -1,5 +1,7 @@ // We want to control preemption here. //@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::thread::spawn; diff --git a/tests/fail/data_race/write_write_race_stack.rs b/tests/fail/data_race/write_write_race_stack.rs index 984ae2ee83..8070a7f4fc 100644 --- a/tests/fail/data_race/write_write_race_stack.rs +++ b/tests/fail/data_race/write_write_race_stack.rs @@ -1,4 +1,6 @@ //@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows +// Avoid accidental synchronization via address reuse inside `thread::spawn`. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 use std::ptr::null_mut; use std::sync::atomic::{AtomicPtr, Ordering}; diff --git a/tests/fail/dyn-call-trait-mismatch.rs b/tests/fail/dyn-call-trait-mismatch.rs index 13f913454d..f71df9a1c9 100644 --- a/tests/fail/dyn-call-trait-mismatch.rs +++ b/tests/fail/dyn-call-trait-mismatch.rs @@ -1,3 +1,6 @@ +// Validation stops this too early. +//@compile-flags: -Zmiri-disable-validation + trait T1 { #[allow(dead_code)] fn method1(self: Box); @@ -13,5 +16,5 @@ impl T1 for i32 { fn main() { let r = Box::new(0) as Box; let r2: Box = unsafe { std::mem::transmute(r) }; - r2.method2(); //~ERROR: call on a pointer whose vtable does not match its type + r2.method2(); //~ERROR: using vtable for trait `T1` but trait `T2` was expected } diff --git a/tests/fail/dyn-call-trait-mismatch.stderr b/tests/fail/dyn-call-trait-mismatch.stderr index 365186bcc4..019a55bcdc 100644 --- a/tests/fail/dyn-call-trait-mismatch.stderr +++ b/tests/fail/dyn-call-trait-mismatch.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: `dyn` call on a pointer whose vtable does not match its type +error: Undefined Behavior: using vtable for trait `T1` but trait `T2` was expected --> $DIR/dyn-call-trait-mismatch.rs:LL:CC | LL | r2.method2(); - | ^^^^^^^^^^^^ `dyn` call on a pointer whose vtable does not match its type + | ^^^^^^^^^^^^ using vtable for trait `T1` but trait `T2` was expected | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/tests/fail/dyn-upcast-nop-wrong-trait.rs b/tests/fail/dyn-upcast-nop-wrong-trait.rs new file mode 100644 index 0000000000..dff5a21c4c --- /dev/null +++ b/tests/fail/dyn-upcast-nop-wrong-trait.rs @@ -0,0 +1,15 @@ +// This upcast is currently forbidden because it involves an invalid value. +// However, if in the future we relax the validity requirements for raw pointer vtables, +// we could consider allowing this again -- the cast itself isn't doing anything wrong, +// only the transmutes needed to set up the testcase are wrong. + +use std::fmt; + +fn main() { + // vtable_mismatch_nop_cast + let ptr: &dyn fmt::Display = &0; + let ptr: *const (dyn fmt::Debug + Send + Sync) = unsafe { std::mem::transmute(ptr) }; //~ERROR: wrong trait + // Even though the vtable is for the wrong trait, this cast doesn't actually change the needed + // vtable so it should still be allowed -- if we ever allow the line above. + let _ptr2 = ptr as *const dyn fmt::Debug; +} diff --git a/tests/fail/dyn-upcast-nop-wrong-trait.stderr b/tests/fail/dyn-upcast-nop-wrong-trait.stderr new file mode 100644 index 0000000000..4165d5ea15 --- /dev/null +++ b/tests/fail/dyn-upcast-nop-wrong-trait.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: wrong trait in wide pointer vtable: expected `std::fmt::Debug + std::marker::Send + std::marker::Sync`, but encountered `std::fmt::Display` + --> $DIR/dyn-upcast-nop-wrong-trait.rs:LL:CC + | +LL | let ptr: *const (dyn fmt::Debug + Send + Sync) = unsafe { std::mem::transmute(ptr) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: wrong trait in wide pointer vtable: expected `std::fmt::Debug + std::marker::Send + std::marker::Sync`, but encountered `std::fmt::Display` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/dyn-upcast-nop-wrong-trait.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/tests/fail/dyn-upcast-trait-mismatch.rs b/tests/fail/dyn-upcast-trait-mismatch.rs index 982f33b0a3..1d6b677703 100644 --- a/tests/fail/dyn-upcast-trait-mismatch.rs +++ b/tests/fail/dyn-upcast-trait-mismatch.rs @@ -1,3 +1,6 @@ +// Validation stops this too early. +//@compile-flags: -Zmiri-disable-validation + #![feature(trait_upcasting)] #![allow(incomplete_features)] @@ -57,7 +60,7 @@ impl Baz for i32 { fn main() { let baz: &dyn Baz = &1; - let baz_fake: &dyn Bar = unsafe { std::mem::transmute(baz) }; - let _err = baz_fake as &dyn Foo; - //~^ERROR: upcast on a pointer whose vtable does not match its type + let baz_fake: *const dyn Bar = unsafe { std::mem::transmute(baz) }; + let _err = baz_fake as *const dyn Foo; + //~^ERROR: using vtable for trait `Baz` but trait `Bar` was expected } diff --git a/tests/fail/dyn-upcast-trait-mismatch.stderr b/tests/fail/dyn-upcast-trait-mismatch.stderr index 8bac908b86..6a2415cf57 100644 --- a/tests/fail/dyn-upcast-trait-mismatch.stderr +++ b/tests/fail/dyn-upcast-trait-mismatch.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: upcast on a pointer whose vtable does not match its type +error: Undefined Behavior: using vtable for trait `Baz` but trait `Bar` was expected --> $DIR/dyn-upcast-trait-mismatch.rs:LL:CC | -LL | let _err = baz_fake as &dyn Foo; - | ^^^^^^^^ upcast on a pointer whose vtable does not match its type +LL | let _err = baz_fake as *const dyn Foo; + | ^^^^^^^^ using vtable for trait `Baz` but trait `Bar` was expected | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/tests/fail/enum-set-discriminant-niche-variant-wrong.rs b/tests/fail/enum-set-discriminant-niche-variant-wrong.rs index 428f371ca5..eca6d908b4 100644 --- a/tests/fail/enum-set-discriminant-niche-variant-wrong.rs +++ b/tests/fail/enum-set-discriminant-niche-variant-wrong.rs @@ -2,7 +2,7 @@ #![feature(custom_mir)] use std::intrinsics::mir::*; -use std::num::NonZeroI32; +use std::num::NonZero; // We define our own option type so that we can control the variant indices. #[allow(unused)] @@ -13,7 +13,7 @@ enum Option { use Option::*; #[custom_mir(dialect = "runtime", phase = "optimized")] -fn set_discriminant(ptr: &mut Option) { +fn set_discriminant(ptr: &mut Option>) { mir! { { // We set the discriminant to `Some`, which is a NOP since this is the niched variant. diff --git a/tests/fail/environ-gets-deallocated.rs b/tests/fail/environ-gets-deallocated.rs index 6dcf1fdb1c..08545a7256 100644 --- a/tests/fail/environ-gets-deallocated.rs +++ b/tests/fail/environ-gets-deallocated.rs @@ -1,6 +1,11 @@ //@ignore-target-windows: Windows does not have a global environ list that the program can access directly -#[cfg(any(target_os = "linux", target_os = "freebsd"))] +#[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "solaris", + target_os = "illumos" +))] fn get_environ() -> *const *const u8 { extern "C" { static mut environ: *const *const u8; diff --git a/tests/fail/extern-type-field-offset.stderr b/tests/fail/extern-type-field-offset.stderr index cbbf1b3836..e0d6e9ebf1 100644 --- a/tests/fail/extern-type-field-offset.stderr +++ b/tests/fail/extern-type-field-offset.stderr @@ -4,7 +4,7 @@ error: unsupported operation: `extern type` does not have a known offset LL | let _field = &x.a; | ^^^^ `extern type` does not have a known offset | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support = note: BACKTRACE: = note: inside `main` at $DIR/extern-type-field-offset.rs:LL:CC diff --git a/tests/fail/extern_static.stderr b/tests/fail/extern_static.stderr index c3de4dadb0..21759f9601 100644 --- a/tests/fail/extern_static.stderr +++ b/tests/fail/extern_static.stderr @@ -1,10 +1,10 @@ -error: unsupported operation: `extern` static `FOO` from crate `extern_static` is not supported by Miri +error: unsupported operation: extern static `FOO` is not supported by Miri --> $DIR/extern_static.rs:LL:CC | LL | let _val = unsafe { std::ptr::addr_of!(FOO) }; - | ^^^ `extern` static `FOO` from crate `extern_static` is not supported by Miri + | ^^^ extern static `FOO` is not supported by Miri | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support = note: BACKTRACE: = note: inside `main` at $DIR/extern_static.rs:LL:CC diff --git a/tests/fail/extern_static_in_const.stderr b/tests/fail/extern_static_in_const.stderr index 23f775f258..aa524c0646 100644 --- a/tests/fail/extern_static_in_const.stderr +++ b/tests/fail/extern_static_in_const.stderr @@ -1,10 +1,10 @@ -error: unsupported operation: `extern` static `E` from crate `extern_static_in_const` is not supported by Miri +error: unsupported operation: extern static `E` is not supported by Miri --> $DIR/extern_static_in_const.rs:LL:CC | LL | let _val = X; - | ^ `extern` static `E` from crate `extern_static_in_const` is not supported by Miri + | ^ extern static `E` is not supported by Miri | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support = note: BACKTRACE: = note: inside `main` at $DIR/extern_static_in_const.rs:LL:CC diff --git a/tests/fail/extern_static_wrong_size.rs b/tests/fail/extern_static_wrong_size.rs index 17061f0e5c..fee3c38c25 100644 --- a/tests/fail/extern_static_wrong_size.rs +++ b/tests/fail/extern_static_wrong_size.rs @@ -6,5 +6,5 @@ extern "C" { } fn main() { - let _val = unsafe { environ }; //~ ERROR: /has been declared with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of [48] bytes and alignment of [48] bytes/ + let _val = unsafe { environ }; //~ ERROR: /with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of [48] bytes and alignment of [48] bytes/ } diff --git a/tests/fail/extern_static_wrong_size.stderr b/tests/fail/extern_static_wrong_size.stderr index c935a548f8..3c013a5d15 100644 --- a/tests/fail/extern_static_wrong_size.stderr +++ b/tests/fail/extern_static_wrong_size.stderr @@ -1,10 +1,10 @@ -error: unsupported operation: `extern` static `environ` from crate `extern_static_wrong_size` has been declared with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes +error: unsupported operation: extern static `environ` has been declared as `extern_static_wrong_size::environ` with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes --> $DIR/extern_static_wrong_size.rs:LL:CC | LL | let _val = unsafe { environ }; - | ^^^^^^^ `extern` static `environ` from crate `extern_static_wrong_size` has been declared with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes + | ^^^^^^^ extern static `environ` has been declared as `extern_static_wrong_size::environ` with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support = note: BACKTRACE: = note: inside `main` at $DIR/extern_static_wrong_size.rs:LL:CC diff --git a/tests/fail/function_calls/exported_symbol_bad_unwind1.stderr b/tests/fail/function_calls/exported_symbol_bad_unwind1.stderr index ab32883a03..42beed4ecd 100644 --- a/tests/fail/function_calls/exported_symbol_bad_unwind1.stderr +++ b/tests/fail/function_calls/exported_symbol_bad_unwind1.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at $DIR/exported_symbol_bad_unwind1.rs:LL:CC: explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding --> $DIR/exported_symbol_bad_unwind1.rs:LL:CC | diff --git a/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr b/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr index 9774e1e1a7..d88781ed22 100644 --- a/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr +++ b/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at $DIR/exported_symbol_bad_unwind2.rs:LL:CC: explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect thread 'main' panicked at RUSTLIB/core/src/panicking.rs:LL:CC: panic in a function that cannot unwind stack backtrace: diff --git a/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr b/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr index 9774e1e1a7..d88781ed22 100644 --- a/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr +++ b/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at $DIR/exported_symbol_bad_unwind2.rs:LL:CC: explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect thread 'main' panicked at RUSTLIB/core/src/panicking.rs:LL:CC: panic in a function that cannot unwind stack backtrace: diff --git a/tests/fail/function_calls/exported_symbol_bad_unwind2.extern_block.stderr b/tests/fail/function_calls/exported_symbol_bad_unwind2.extern_block.stderr index f89cee0b86..bc3e485871 100644 --- a/tests/fail/function_calls/exported_symbol_bad_unwind2.extern_block.stderr +++ b/tests/fail/function_calls/exported_symbol_bad_unwind2.extern_block.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at $DIR/exported_symbol_bad_unwind2.rs:LL:CC: explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC | diff --git a/tests/fail/function_calls/return_pointer_on_unwind.stderr b/tests/fail/function_calls/return_pointer_on_unwind.stderr index 7f035eb5e8..a2fa4c1d59 100644 --- a/tests/fail/function_calls/return_pointer_on_unwind.stderr +++ b/tests/fail/function_calls/return_pointer_on_unwind.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at $DIR/return_pointer_on_unwind.rs:LL:CC: explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory --> $DIR/return_pointer_on_unwind.rs:LL:CC | diff --git a/tests/fail/function_pointers/abi_mismatch_array_vs_struct.stderr b/tests/fail/function_pointers/abi_mismatch_array_vs_struct.stderr index 595235088f..2b2a898ce7 100644 --- a/tests/fail/function_pointers/abi_mismatch_array_vs_struct.stderr +++ b/tests/fail/function_pointers/abi_mismatch_array_vs_struct.stderr @@ -7,7 +7,7 @@ LL | g(Default::default()) = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets - = help: if you think this code should be accepted anyway, please report an issue + = help: if you think this code should be accepted anyway, please report an issue with Miri = note: BACKTRACE: = note: inside `main` at $DIR/abi_mismatch_array_vs_struct.rs:LL:CC diff --git a/tests/fail/function_pointers/abi_mismatch_int_vs_float.stderr b/tests/fail/function_pointers/abi_mismatch_int_vs_float.stderr index a8b1cf40c0..752e17116d 100644 --- a/tests/fail/function_pointers/abi_mismatch_int_vs_float.stderr +++ b/tests/fail/function_pointers/abi_mismatch_int_vs_float.stderr @@ -7,7 +7,7 @@ LL | g(42) = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets - = help: if you think this code should be accepted anyway, please report an issue + = help: if you think this code should be accepted anyway, please report an issue with Miri = note: BACKTRACE: = note: inside `main` at $DIR/abi_mismatch_int_vs_float.rs:LL:CC diff --git a/tests/fail/function_pointers/abi_mismatch_raw_pointer.stderr b/tests/fail/function_pointers/abi_mismatch_raw_pointer.stderr index 60dd3814bf..907a8e50c4 100644 --- a/tests/fail/function_pointers/abi_mismatch_raw_pointer.stderr +++ b/tests/fail/function_pointers/abi_mismatch_raw_pointer.stderr @@ -7,7 +7,7 @@ LL | g(&42 as *const i32) = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets - = help: if you think this code should be accepted anyway, please report an issue + = help: if you think this code should be accepted anyway, please report an issue with Miri = note: BACKTRACE: = note: inside `main` at $DIR/abi_mismatch_raw_pointer.rs:LL:CC diff --git a/tests/fail/function_pointers/abi_mismatch_repr_C.rs b/tests/fail/function_pointers/abi_mismatch_repr_C.rs index 2cf4e04477..c5900489b4 100644 --- a/tests/fail/function_pointers/abi_mismatch_repr_C.rs +++ b/tests/fail/function_pointers/abi_mismatch_repr_C.rs @@ -1,7 +1,7 @@ -use std::num::*; +use std::num::NonZero; #[repr(C)] -struct S1(NonZeroI32); +struct S1(NonZero); #[repr(C)] struct S2(i32); @@ -11,6 +11,6 @@ fn callee(_s: S2) {} fn main() { let fnptr: fn(S2) = callee; let fnptr: fn(S1) = unsafe { std::mem::transmute(fnptr) }; - fnptr(S1(NonZeroI32::new(1).unwrap())); + fnptr(S1(NonZero::new(1).unwrap())); //~^ ERROR: calling a function with argument of type S2 passing data of type S1 } diff --git a/tests/fail/function_pointers/abi_mismatch_repr_C.stderr b/tests/fail/function_pointers/abi_mismatch_repr_C.stderr index 6d42bea9da..8ec19db813 100644 --- a/tests/fail/function_pointers/abi_mismatch_repr_C.stderr +++ b/tests/fail/function_pointers/abi_mismatch_repr_C.stderr @@ -1,13 +1,13 @@ error: Undefined Behavior: calling a function with argument of type S2 passing data of type S1 --> $DIR/abi_mismatch_repr_C.rs:LL:CC | -LL | fnptr(S1(NonZeroI32::new(1).unwrap())); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ calling a function with argument of type S2 passing data of type S1 +LL | fnptr(S1(NonZero::new(1).unwrap())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ calling a function with argument of type S2 passing data of type S1 | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets - = help: if you think this code should be accepted anyway, please report an issue + = help: if you think this code should be accepted anyway, please report an issue with Miri = note: BACKTRACE: = note: inside `main` at $DIR/abi_mismatch_repr_C.rs:LL:CC diff --git a/tests/fail/function_pointers/abi_mismatch_return_type.stderr b/tests/fail/function_pointers/abi_mismatch_return_type.stderr index 198896b0dd..3793590f84 100644 --- a/tests/fail/function_pointers/abi_mismatch_return_type.stderr +++ b/tests/fail/function_pointers/abi_mismatch_return_type.stderr @@ -7,7 +7,7 @@ LL | g() = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets - = help: if you think this code should be accepted anyway, please report an issue + = help: if you think this code should be accepted anyway, please report an issue with Miri = note: BACKTRACE: = note: inside `main` at $DIR/abi_mismatch_return_type.rs:LL:CC diff --git a/tests/fail/function_pointers/abi_mismatch_simple.stderr b/tests/fail/function_pointers/abi_mismatch_simple.stderr index daf216a142..0c533c1417 100644 --- a/tests/fail/function_pointers/abi_mismatch_simple.stderr +++ b/tests/fail/function_pointers/abi_mismatch_simple.stderr @@ -7,7 +7,7 @@ LL | g(42) = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets - = help: if you think this code should be accepted anyway, please report an issue + = help: if you think this code should be accepted anyway, please report an issue with Miri = note: BACKTRACE: = note: inside `main` at $DIR/abi_mismatch_simple.rs:LL:CC diff --git a/tests/fail/function_pointers/abi_mismatch_vector.stderr b/tests/fail/function_pointers/abi_mismatch_vector.stderr index 50f4474cc0..ef4b60b83b 100644 --- a/tests/fail/function_pointers/abi_mismatch_vector.stderr +++ b/tests/fail/function_pointers/abi_mismatch_vector.stderr @@ -7,7 +7,7 @@ LL | g(Default::default()) = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets - = help: if you think this code should be accepted anyway, please report an issue + = help: if you think this code should be accepted anyway, please report an issue with Miri = note: BACKTRACE: = note: inside `main` at $DIR/abi_mismatch_vector.rs:LL:CC diff --git a/tests/fail/intrinsic_fallback_checks_ub.rs b/tests/fail/intrinsic_fallback_checks_ub.rs new file mode 100644 index 0000000000..93c9d3d781 --- /dev/null +++ b/tests/fail/intrinsic_fallback_checks_ub.rs @@ -0,0 +1,14 @@ +#![feature(rustc_attrs, effects)] + +#[rustc_intrinsic] +#[rustc_nounwind] +#[rustc_do_not_const_check] +#[inline] +pub const fn ptr_guaranteed_cmp(ptr: *const T, other: *const T) -> u8 { + (ptr == other) as u8 +} + +fn main() { + ptr_guaranteed_cmp::<()>(std::ptr::null(), std::ptr::null()); + //~^ ERROR: can only use intrinsic fallback bodies that check UB. +} diff --git a/tests/fail/intrinsic_fallback_checks_ub.stderr b/tests/fail/intrinsic_fallback_checks_ub.stderr new file mode 100644 index 0000000000..699dda5209 --- /dev/null +++ b/tests/fail/intrinsic_fallback_checks_ub.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: miri can only use intrinsic fallback bodies that check UB. After verifying that `ptr_guaranteed_cmp` does so, add the `#[miri::intrinsic_fallback_checks_ub]` attribute to it; also ping @rust-lang/miri when you do that + --> $DIR/intrinsic_fallback_checks_ub.rs:LL:CC + | +LL | ptr_guaranteed_cmp::<()>(std::ptr::null(), std::ptr::null()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ miri can only use intrinsic fallback bodies that check UB. After verifying that `ptr_guaranteed_cmp` does so, add the `#[miri::intrinsic_fallback_checks_ub]` attribute to it; also ping @rust-lang/miri when you do that + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/intrinsic_fallback_checks_ub.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/tests/fail/intrinsics/ctlz_nonzero.rs b/tests/fail/intrinsics/ctlz_nonzero.rs index c26cd4cadb..0b34afc609 100644 --- a/tests/fail/intrinsics/ctlz_nonzero.rs +++ b/tests/fail/intrinsics/ctlz_nonzero.rs @@ -2,7 +2,7 @@ mod rusti { extern "rust-intrinsic" { - pub fn ctlz_nonzero(x: T) -> T; + pub fn ctlz_nonzero(x: T) -> u32; } } diff --git a/tests/fail/intrinsics/cttz_nonzero.rs b/tests/fail/intrinsics/cttz_nonzero.rs index 25a0501fdd..e220411f58 100644 --- a/tests/fail/intrinsics/cttz_nonzero.rs +++ b/tests/fail/intrinsics/cttz_nonzero.rs @@ -2,7 +2,7 @@ mod rusti { extern "rust-intrinsic" { - pub fn cttz_nonzero(x: T) -> T; + pub fn cttz_nonzero(x: T) -> u32; } } diff --git a/tests/fail/shims/intrinsic_target_feature.rs b/tests/fail/intrinsics/intrinsic_target_feature.rs similarity index 100% rename from tests/fail/shims/intrinsic_target_feature.rs rename to tests/fail/intrinsics/intrinsic_target_feature.rs diff --git a/tests/fail/shims/intrinsic_target_feature.stderr b/tests/fail/intrinsics/intrinsic_target_feature.stderr similarity index 100% rename from tests/fail/shims/intrinsic_target_feature.stderr rename to tests/fail/intrinsics/intrinsic_target_feature.stderr diff --git a/tests/fail/intrinsics/ptr_offset_from_different_ints.rs b/tests/fail/intrinsics/ptr_offset_from_different_ints.rs new file mode 100644 index 0000000000..57b4fd0ded --- /dev/null +++ b/tests/fail/intrinsics/ptr_offset_from_different_ints.rs @@ -0,0 +1,21 @@ +#![feature(strict_provenance)] +use core::ptr; + +fn main() { + unsafe { + let base = ptr::without_provenance::<()>(10); + let unit = &*base; + let p1 = unit as *const (); + + let base = ptr::without_provenance::<()>(11); + let unit = &*base; + let p2 = unit as *const (); + + // Seems to work because they are same pointer + // even though it's dangling. + let _ = p1.byte_offset_from(p1); + + // UB because different pointers. + let _ = p1.byte_offset_from(p2); //~ERROR: different pointers without provenance + } +} diff --git a/tests/fail/intrinsics/ptr_offset_from_different_ints.stderr b/tests/fail/intrinsics/ptr_offset_from_different_ints.stderr new file mode 100644 index 0000000000..6e9e5633fe --- /dev/null +++ b/tests/fail/intrinsics/ptr_offset_from_different_ints.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `ptr_offset_from` called on different pointers without provenance (i.e., without an associated allocation) + --> $DIR/ptr_offset_from_different_ints.rs:LL:CC + | +LL | let _ = p1.byte_offset_from(p2); + | ^^^^^^^^^^^^^^^^^^^^^^^ `ptr_offset_from` called on different pointers without provenance (i.e., without an associated allocation) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/ptr_offset_from_different_ints.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/tests/fail/intrinsics/uninit_uninhabited_type.stderr b/tests/fail/intrinsics/uninit_uninhabited_type.stderr index 4723eddaa6..447f7cae6c 100644 --- a/tests/fail/intrinsics/uninit_uninhabited_type.stderr +++ b/tests/fail/intrinsics/uninit_uninhabited_type.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at RUSTLIB/core/src/panicking.rs:LL:CC: aborted execution: attempted to instantiate uninhabited type `!` note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect thread caused non-unwinding panic. aborting. error: abnormal termination: the program aborted execution --> RUSTLIB/std/src/sys/pal/PLATFORM/mod.rs:LL:CC diff --git a/tests/fail/intrinsics/zero_fn_ptr.stderr b/tests/fail/intrinsics/zero_fn_ptr.stderr index 9c6dd10079..bae3414980 100644 --- a/tests/fail/intrinsics/zero_fn_ptr.stderr +++ b/tests/fail/intrinsics/zero_fn_ptr.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at RUSTLIB/core/src/panicking.rs:LL:CC: aborted execution: attempted to zero-initialize type `fn()`, which is invalid note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect thread caused non-unwinding panic. aborting. error: abnormal termination: the program aborted execution --> RUSTLIB/std/src/sys/pal/PLATFORM/mod.rs:LL:CC diff --git a/tests/fail/issue-miri-3288-ice-symbolic-alignment-extern-static.stderr b/tests/fail/issue-miri-3288-ice-symbolic-alignment-extern-static.stderr index a4249d2e88..4064d7fe4e 100644 --- a/tests/fail/issue-miri-3288-ice-symbolic-alignment-extern-static.stderr +++ b/tests/fail/issue-miri-3288-ice-symbolic-alignment-extern-static.stderr @@ -1,10 +1,10 @@ -error: unsupported operation: `extern` static `_dispatch_queue_attr_concurrent` from crate `issue_miri_3288_ice_symbolic_alignment_extern_static` is not supported by Miri +error: unsupported operation: extern static `_dispatch_queue_attr_concurrent` is not supported by Miri --> $DIR/issue-miri-3288-ice-symbolic-alignment-extern-static.rs:LL:CC | LL | let _val = *DISPATCH_QUEUE_CONCURRENT; - | ^^^^^^^^^^^^^^^^^^^^^^^^^ `extern` static `_dispatch_queue_attr_concurrent` from crate `issue_miri_3288_ice_symbolic_alignment_extern_static` is not supported by Miri + | ^^^^^^^^^^^^^^^^^^^^^^^^^ extern static `_dispatch_queue_attr_concurrent` is not supported by Miri | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support = note: BACKTRACE: = note: inside `main` at $DIR/issue-miri-3288-ice-symbolic-alignment-extern-static.rs:LL:CC diff --git a/tests/fail/panic/bad_unwind.stderr b/tests/fail/panic/bad_unwind.stderr index e4af01feb8..230e8337c7 100644 --- a/tests/fail/panic/bad_unwind.stderr +++ b/tests/fail/panic/bad_unwind.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at $DIR/bad_unwind.rs:LL:CC: explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding --> $DIR/bad_unwind.rs:LL:CC | diff --git a/tests/fail/panic/double_panic.stderr b/tests/fail/panic/double_panic.stderr index e3cacbd27b..3b7a1511fa 100644 --- a/tests/fail/panic/double_panic.stderr +++ b/tests/fail/panic/double_panic.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at $DIR/double_panic.rs:LL:CC: first note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect thread 'main' panicked at $DIR/double_panic.rs:LL:CC: second stack backtrace: diff --git a/tests/fail/panic/no_std.rs b/tests/fail/panic/no_std.rs index bad425804d..4d32b6d746 100644 --- a/tests/fail/panic/no_std.rs +++ b/tests/fail/panic/no_std.rs @@ -1,31 +1,11 @@ +//@compile-flags: -Cpanic=abort #![feature(start, core_intrinsics)] #![no_std] -//@compile-flags: -Cpanic=abort -// windows tls dtors go through libstd right now, thus this test -// cannot pass. When windows tls dtors go through the special magic -// windows linker section, we can run this test on windows again. -//@ignore-target-windows: no-std not supported on Windows - -// Plumbing to let us use `writeln!` to host stderr: - -extern "Rust" { - fn miri_write_to_stderr(bytes: &[u8]); -} - -struct HostErr; use core::fmt::Write; -impl Write for HostErr { - fn write_str(&mut self, s: &str) -> core::fmt::Result { - unsafe { - miri_write_to_stderr(s.as_bytes()); - } - Ok(()) - } -} - -// Aaaand the test: +#[path = "../../utils/mod.no_std.rs"] +mod utils; #[start] fn start(_: isize, _: *const *const u8) -> isize { @@ -34,6 +14,6 @@ fn start(_: isize, _: *const *const u8) -> isize { #[panic_handler] fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! { - writeln!(HostErr, "{panic_info}").ok(); + writeln!(utils::MiriStderr, "{panic_info}").ok(); core::intrinsics::abort(); //~ ERROR: the program aborted execution } diff --git a/tests/fail/panic/panic_abort1.stderr b/tests/fail/panic/panic_abort1.stderr index 6045569361..7694cc70b2 100644 --- a/tests/fail/panic/panic_abort1.stderr +++ b/tests/fail/panic/panic_abort1.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at $DIR/panic_abort1.rs:LL:CC: panicking from libstd note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect error: abnormal termination: the program aborted execution --> RUSTLIB/panic_abort/src/lib.rs:LL:CC | diff --git a/tests/fail/panic/panic_abort2.stderr b/tests/fail/panic/panic_abort2.stderr index 7bb27e4baa..e6a4380ea5 100644 --- a/tests/fail/panic/panic_abort2.stderr +++ b/tests/fail/panic/panic_abort2.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at $DIR/panic_abort2.rs:LL:CC: 42-panicking from libstd note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect error: abnormal termination: the program aborted execution --> RUSTLIB/panic_abort/src/lib.rs:LL:CC | diff --git a/tests/fail/panic/panic_abort3.stderr b/tests/fail/panic/panic_abort3.stderr index b46e8c2079..23e2021eee 100644 --- a/tests/fail/panic/panic_abort3.stderr +++ b/tests/fail/panic/panic_abort3.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at $DIR/panic_abort3.rs:LL:CC: panicking from libcore note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect error: abnormal termination: the program aborted execution --> RUSTLIB/panic_abort/src/lib.rs:LL:CC | diff --git a/tests/fail/panic/panic_abort4.stderr b/tests/fail/panic/panic_abort4.stderr index b15f720e43..20a0ddb901 100644 --- a/tests/fail/panic/panic_abort4.stderr +++ b/tests/fail/panic/panic_abort4.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at $DIR/panic_abort4.rs:LL:CC: 42-panicking from libcore note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect error: abnormal termination: the program aborted execution --> RUSTLIB/panic_abort/src/lib.rs:LL:CC | diff --git a/tests/fail/provenance/ptr_invalid.rs b/tests/fail/provenance/ptr_invalid.rs index 730859684a..512473cd89 100644 --- a/tests/fail/provenance/ptr_invalid.rs +++ b/tests/fail/provenance/ptr_invalid.rs @@ -4,6 +4,6 @@ fn main() { let x = 42; let xptr = &x as *const i32; - let xptr_invalid = std::ptr::without_provenance::(xptr.expose_addr()); + let xptr_invalid = std::ptr::without_provenance::(xptr.expose_provenance()); let _val = unsafe { *xptr_invalid }; //~ ERROR: is a dangling pointer } diff --git a/tests/fail/shims/backtrace/bad-backtrace-flags.stderr b/tests/fail/shims/backtrace/bad-backtrace-flags.stderr index 2523e5063e..504485e3b3 100644 --- a/tests/fail/shims/backtrace/bad-backtrace-flags.stderr +++ b/tests/fail/shims/backtrace/bad-backtrace-flags.stderr @@ -4,7 +4,7 @@ error: unsupported operation: unknown `miri_get_backtrace` flags 2 LL | miri_get_backtrace(2, std::ptr::null_mut()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unknown `miri_get_backtrace` flags 2 | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support = note: BACKTRACE: = note: inside `main` at $DIR/bad-backtrace-flags.rs:LL:CC diff --git a/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr b/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr index 36446ae283..c1f0ce3d1a 100644 --- a/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr +++ b/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr @@ -4,7 +4,7 @@ error: unsupported operation: unknown `miri_resolve_frame` flags 2 LL | miri_resolve_frame(buf[0], 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unknown `miri_resolve_frame` flags 2 | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support = note: BACKTRACE: = note: inside `main` at $DIR/bad-backtrace-resolve-flags.rs:LL:CC diff --git a/tests/fail/shims/backtrace/bad-backtrace-resolve-names-flags.stderr b/tests/fail/shims/backtrace/bad-backtrace-resolve-names-flags.stderr index ff60ab0917..fc270593e6 100644 --- a/tests/fail/shims/backtrace/bad-backtrace-resolve-names-flags.stderr +++ b/tests/fail/shims/backtrace/bad-backtrace-resolve-names-flags.stderr @@ -4,7 +4,7 @@ error: unsupported operation: unknown `miri_resolve_frame_names` flags 2 LL | ... miri_resolve_frame_names(buf[0], 2, std::ptr::null_mut(), std::ptr::null_mut()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unknown `miri_resolve_frame_names` flags 2 | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support = note: BACKTRACE: = note: inside `main` at $DIR/bad-backtrace-resolve-names-flags.rs:LL:CC diff --git a/tests/fail/shims/backtrace/bad-backtrace-size-flags.stderr b/tests/fail/shims/backtrace/bad-backtrace-size-flags.stderr index 6d4c370050..2d70733334 100644 --- a/tests/fail/shims/backtrace/bad-backtrace-size-flags.stderr +++ b/tests/fail/shims/backtrace/bad-backtrace-size-flags.stderr @@ -4,7 +4,7 @@ error: unsupported operation: unknown `miri_backtrace_size` flags 2 LL | miri_backtrace_size(2); | ^^^^^^^^^^^^^^^^^^^^^^ unknown `miri_backtrace_size` flags 2 | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support = note: BACKTRACE: = note: inside `main` at $DIR/bad-backtrace-size-flags.rs:LL:CC diff --git a/tests/fail/stacked_borrows/exposed_only_ro.rs b/tests/fail/stacked_borrows/exposed_only_ro.rs index aa05649d55..608ab71891 100644 --- a/tests/fail/stacked_borrows/exposed_only_ro.rs +++ b/tests/fail/stacked_borrows/exposed_only_ro.rs @@ -6,7 +6,7 @@ fn main() { let mut x = 0; let _fool = &mut x as *mut i32; // this would have fooled the old untagged pointer logic - let addr = (&x as *const i32).expose_addr(); + let addr = (&x as *const i32).expose_provenance(); let ptr = std::ptr::with_exposed_provenance_mut::(addr); unsafe { *ptr = 0 }; //~ ERROR: /write access using .* no exposed tags have suitable permission in the borrow stack/ } diff --git a/tests/fail/stacked_borrows/retag_data_race_protected_read.rs b/tests/fail/stacked_borrows/retag_data_race_protected_read.rs index 3de517055e..a6ee7b40c3 100644 --- a/tests/fail/stacked_borrows/retag_data_race_protected_read.rs +++ b/tests/fail/stacked_borrows/retag_data_race_protected_read.rs @@ -1,4 +1,5 @@ -//@compile-flags: -Zmiri-preemption-rate=0 +// Avoid accidental synchronization via address reuse. +//@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-address-reuse-cross-thread-rate=0 use std::thread; #[derive(Copy, Clone)] diff --git a/tests/fail/stacked_borrows/retag_data_race_read.rs b/tests/fail/stacked_borrows/retag_data_race_read.rs index 25c92ddf6c..949f659e7e 100644 --- a/tests/fail/stacked_borrows/retag_data_race_read.rs +++ b/tests/fail/stacked_borrows/retag_data_race_read.rs @@ -1,5 +1,6 @@ //! Make sure that a retag acts like a read for the data race model. -//@compile-flags: -Zmiri-preemption-rate=0 +// Avoid accidental synchronization via address reuse. +//@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-address-reuse-cross-thread-rate=0 #[derive(Copy, Clone)] struct SendPtr(*mut u8); diff --git a/tests/fail/terminate-terminator.stderr b/tests/fail/terminate-terminator.stderr index 8dbc802bf5..f737adc561 100644 --- a/tests/fail/terminate-terminator.stderr +++ b/tests/fail/terminate-terminator.stderr @@ -3,6 +3,7 @@ warning: You have explicitly enabled MIR optimizations, overriding Miri's defaul thread 'main' panicked at $DIR/terminate-terminator.rs:LL:CC: explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect thread 'main' panicked at RUSTLIB/core/src/panicking.rs:LL:CC: panic in a function that cannot unwind stack backtrace: diff --git a/tests/fail/tree_borrows/spurious_read.rs b/tests/fail/tree_borrows/spurious_read.rs index 3f39dcb4b7..50ef0ceef9 100644 --- a/tests/fail/tree_borrows/spurious_read.rs +++ b/tests/fail/tree_borrows/spurious_read.rs @@ -89,7 +89,7 @@ fn retagx_retagy_retx_writey_rety() { // - retag `y` protected // - (wait for the other thread to return so that there is no foreign protector when we write) // - attempt a write through `y`. - // - (UB should have occured by now, but the next step would be to + // - (UB should have occurred by now, but the next step would be to // remove `y`'s protector) let thread_y = thread::spawn(move || { let b = (2, by); diff --git a/tests/fail/unaligned_pointers/promise_alignment.call_unaligned_ptr.stderr b/tests/fail/unaligned_pointers/promise_alignment.call_unaligned_ptr.stderr index e23ac5ac2f..6d62db4d3d 100644 --- a/tests/fail/unaligned_pointers/promise_alignment.call_unaligned_ptr.stderr +++ b/tests/fail/unaligned_pointers/promise_alignment.call_unaligned_ptr.stderr @@ -4,7 +4,7 @@ error: unsupported operation: `miri_promise_symbolic_alignment`: pointer is not LL | unsafe { utils::miri_promise_symbolic_alignment(align8.add(1).cast(), 8) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `miri_promise_symbolic_alignment`: pointer is not actually aligned | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support = note: BACKTRACE: = note: inside `main` at $DIR/promise_alignment.rs:LL:CC diff --git a/tests/fail/unaligned_pointers/promise_alignment_zero.stderr b/tests/fail/unaligned_pointers/promise_alignment_zero.stderr index b3327e0493..3f7ced0b40 100644 --- a/tests/fail/unaligned_pointers/promise_alignment_zero.stderr +++ b/tests/fail/unaligned_pointers/promise_alignment_zero.stderr @@ -4,7 +4,7 @@ error: unsupported operation: `miri_promise_symbolic_alignment`: alignment must LL | unsafe { utils::miri_promise_symbolic_alignment(buffer.as_ptr().cast(), 0) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `miri_promise_symbolic_alignment`: alignment must be a power of 2, got 0 | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support = note: BACKTRACE: = note: inside `main` at $DIR/promise_alignment_zero.rs:LL:CC diff --git a/tests/fail/uninit/uninit_alloc_diagnostic.stderr b/tests/fail/uninit/uninit_alloc_diagnostic.stderr index cca17a07ec..960cae9012 100644 --- a/tests/fail/uninit/uninit_alloc_diagnostic.stderr +++ b/tests/fail/uninit/uninit_alloc_diagnostic.stderr @@ -15,13 +15,13 @@ note: inside `main` LL | drop(slice1.cmp(slice2)); | ^^^^^^^^^^^^^^^^^^ -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - Uninitialized memory occurred at ALLOC[0x4..0x10], in this allocation: ALLOC (Rust heap, size: 32, align: 8) { 0x00 │ 41 42 43 44 __ __ __ __ __ __ __ __ __ __ __ __ │ ABCD░░░░░░░░░░░░ 0x10 │ 00 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ │ .░░░░░░░░░░░░░░░ } +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + error: aborting due to 1 previous error diff --git a/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr b/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr index 4dc2d27ead..5439418f26 100644 --- a/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr +++ b/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr @@ -15,8 +15,6 @@ note: inside `main` LL | drop(slice1.cmp(slice2)); | ^^^^^^^^^^^^^^^^^^ -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - Uninitialized memory occurred at ALLOC[0x4..0x8], in this allocation: ALLOC (Rust heap, size: 16, align: 8) { ╾42[ALLOC] (1 ptr byte)╼ 12 13 ╾43[ALLOC] (1 ptr byte)╼ __ __ __ __ __ __ __ __ __ __ __ __ │ ━..━░░░░░░░░░░░░ @@ -28,5 +26,7 @@ ALLOC (global (static or const), size: 1, align: 1) { 00 │ . } +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + error: aborting due to 1 previous error diff --git a/tests/fail/unsized-local.stderr b/tests/fail/unsized-local.stderr index 0ffda9e6d6..df54c98bb0 100644 --- a/tests/fail/unsized-local.stderr +++ b/tests/fail/unsized-local.stderr @@ -4,7 +4,7 @@ error: unsupported operation: unsized locals are not supported LL | let x = *(Box::new(A) as Box); | ^ unsized locals are not supported | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support = note: BACKTRACE: = note: inside `main` at $DIR/unsized-local.rs:LL:CC diff --git a/tests/fail/unsupported_foreign_function.stderr b/tests/fail/unsupported_foreign_function.stderr index 5a2f415bb8..f392e9564c 100644 --- a/tests/fail/unsupported_foreign_function.stderr +++ b/tests/fail/unsupported_foreign_function.stderr @@ -4,7 +4,8 @@ error: unsupported operation: can't call foreign function `foo` on $OS LL | foo(); | ^^^^^ can't call foreign function `foo` on $OS | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: if this is a basic API commonly used on this target, please report an issue with Miri + = help: however, note that Miri does not aim to support every FFI function out there; for instance, we will not support APIs for things such as GUIs, scripting languages, or databases = note: BACKTRACE: = note: inside `main` at $DIR/unsupported_foreign_function.rs:LL:CC diff --git a/tests/fail/unwind-action-terminate.stderr b/tests/fail/unwind-action-terminate.stderr index 1323a39710..7e722f7be3 100644 --- a/tests/fail/unwind-action-terminate.stderr +++ b/tests/fail/unwind-action-terminate.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at $DIR/unwind-action-terminate.rs:LL:CC: explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect thread 'main' panicked at RUSTLIB/core/src/panicking.rs:LL:CC: panic in a function that cannot unwind stack backtrace: diff --git a/tests/fail/validity/cast_fn_ptr_invalid_callee_ret.rs b/tests/fail/validity/cast_fn_ptr_invalid_callee_ret.rs index 7cdc15c609..9fabdd8e86 100644 --- a/tests/fail/validity/cast_fn_ptr_invalid_callee_ret.rs +++ b/tests/fail/validity/cast_fn_ptr_invalid_callee_ret.rs @@ -2,15 +2,15 @@ #![feature(core_intrinsics, custom_mir)] use std::intrinsics::mir::*; -use std::num::NonZeroU32; +use std::num::NonZero; use std::ptr; -// This function supposedly returns a NonZeroU32, but actually returns something invalid in a way that -// never materializes a bad NonZeroU32 value: we take a pointer to the return place and cast the pointer +// This function supposedly returns a `NonZero`, but actually returns something invalid in a way that +// never materializes a bad `NonZero` value: we take a pointer to the return place and cast the pointer // type. That way we never get an "invalid value constructed" error inside the function, it can // only possibly be detected when the return value is passed to the caller. #[custom_mir(dialect = "runtime", phase = "optimized")] -fn f() -> NonZeroU32 { +fn f() -> NonZero { mir! { { let tmp = ptr::addr_of_mut!(RET); @@ -22,7 +22,7 @@ fn f() -> NonZeroU32 { } fn main() { - let f: fn() -> u32 = unsafe { std::mem::transmute(f as fn() -> NonZeroU32) }; - // There's a NonZeroU32-to-u32 transmute happening here + let f: fn() -> u32 = unsafe { std::mem::transmute(f as fn() -> NonZero) }; + // There's a `NonZero` to `u32` transmute happening here. f(); //~ERROR: expected something greater or equal to 1 } diff --git a/tests/fail/validity/cast_fn_ptr_invalid_caller_arg.rs b/tests/fail/validity/cast_fn_ptr_invalid_caller_arg.rs index 3a87bb7867..7ca26bffc1 100644 --- a/tests/fail/validity/cast_fn_ptr_invalid_caller_arg.rs +++ b/tests/fail/validity/cast_fn_ptr_invalid_caller_arg.rs @@ -2,24 +2,24 @@ #![feature(core_intrinsics, custom_mir)] use std::intrinsics::mir::*; -use std::num::NonZeroU32; +use std::num::NonZero; use std::ptr; fn f(c: u32) { println!("{c}"); } -// Call that function in a bad way, with an invalid NonZeroU32, but without -// ever materializing this as a NonZeroU32 value outside the call itself. +// Call that function in a bad way, with an invalid `NonZero`, but without +// ever materializing this as a `NonZero` value outside the call itself. #[custom_mir(dialect = "runtime", phase = "optimized")] -fn call(f: fn(NonZeroU32)) { +fn call(f: fn(NonZero)) { mir! { let _res: (); { let c = 0; let tmp = ptr::addr_of!(c); - let ptr = tmp as *const NonZeroU32; - // The call site now is a NonZeroU32-to-u32 transmute. + let ptr = tmp as *const NonZero; + // The call site now is a `NonZero` to `u32` transmute. Call(_res = f(*ptr), ReturnTo(retblock), UnwindContinue()) //~ERROR: expected something greater or equal to 1 } retblock = { @@ -29,6 +29,6 @@ fn call(f: fn(NonZeroU32)) { } fn main() { - let f: fn(NonZeroU32) = unsafe { std::mem::transmute(f as fn(u32)) }; + let f: fn(NonZero) = unsafe { std::mem::transmute(f as fn(u32)) }; call(f); } diff --git a/tests/fail/validity/wrong-dyn-trait-generic.rs b/tests/fail/validity/wrong-dyn-trait-generic.rs new file mode 100644 index 0000000000..9b1cefc4b1 --- /dev/null +++ b/tests/fail/validity/wrong-dyn-trait-generic.rs @@ -0,0 +1,12 @@ +use std::mem; + +// Make sure we notice the mismatch also if the difference is "only" in the generic +// parameters of the trait. + +trait Trait {} +impl Trait for T {} + +fn main() { + let x: &dyn Trait = &0; + let _y: *const dyn Trait = unsafe { mem::transmute(x) }; //~ERROR: wrong trait +} diff --git a/tests/fail/validity/wrong-dyn-trait-generic.stderr b/tests/fail/validity/wrong-dyn-trait-generic.stderr new file mode 100644 index 0000000000..1219f9f88c --- /dev/null +++ b/tests/fail/validity/wrong-dyn-trait-generic.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: wrong trait in wide pointer vtable: expected `Trait`, but encountered `Trait` + --> $DIR/wrong-dyn-trait-generic.rs:LL:CC + | +LL | let _y: *const dyn Trait = unsafe { mem::transmute(x) }; + | ^^^^^^^^^^^^^^^^^ constructing invalid value: wrong trait in wide pointer vtable: expected `Trait`, but encountered `Trait` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/wrong-dyn-trait-generic.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/tests/fail/validity/wrong-dyn-trait.rs b/tests/fail/validity/wrong-dyn-trait.rs new file mode 100644 index 0000000000..d6049196f2 --- /dev/null +++ b/tests/fail/validity/wrong-dyn-trait.rs @@ -0,0 +1,6 @@ +use std::{fmt, mem}; + +fn main() { + let x: &dyn Send = &0; + let _y: *const dyn fmt::Debug = unsafe { mem::transmute(x) }; //~ERROR: wrong trait +} diff --git a/tests/fail/validity/wrong-dyn-trait.stderr b/tests/fail/validity/wrong-dyn-trait.stderr new file mode 100644 index 0000000000..e3503323b3 --- /dev/null +++ b/tests/fail/validity/wrong-dyn-trait.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: wrong trait in wide pointer vtable: expected `std::fmt::Debug`, but encountered `` + --> $DIR/wrong-dyn-trait.rs:LL:CC + | +LL | let _y: *const dyn fmt::Debug = unsafe { mem::transmute(x) }; + | ^^^^^^^^^^^^^^^^^ constructing invalid value: wrong trait in wide pointer vtable: expected `std::fmt::Debug`, but encountered `` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/wrong-dyn-trait.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/tests/fail/weak_memory/racing_mixed_size.rs b/tests/fail/weak_memory/racing_mixed_size.rs index dfe9397a4c..1193dddc57 100644 --- a/tests/fail/weak_memory/racing_mixed_size.rs +++ b/tests/fail/weak_memory/racing_mixed_size.rs @@ -1,5 +1,6 @@ // We want to control preemption here. -//@compile-flags: -Zmiri-preemption-rate=0 +// Avoid accidental synchronization via address reuse. +//@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-address-reuse-cross-thread-rate=0 #![feature(core_intrinsics)] diff --git a/tests/fail/weak_memory/racing_mixed_size_read.rs b/tests/fail/weak_memory/racing_mixed_size_read.rs index b946a75c3a..0a0e372f1f 100644 --- a/tests/fail/weak_memory/racing_mixed_size_read.rs +++ b/tests/fail/weak_memory/racing_mixed_size_read.rs @@ -1,5 +1,6 @@ // We want to control preemption here. -//@compile-flags: -Zmiri-preemption-rate=0 +// Avoid accidental synchronization via address reuse. +//@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-address-reuse-cross-thread-rate=0 use std::sync::atomic::Ordering::*; use std::sync::atomic::{AtomicU16, AtomicU32}; diff --git a/tests/many-seeds/tls-leak.rs b/tests/many-seeds/tls-leak.rs new file mode 100644 index 0000000000..3b24363343 --- /dev/null +++ b/tests/many-seeds/tls-leak.rs @@ -0,0 +1,26 @@ +//! Regression test for . +use std::thread; + +fn with_thread_local1() { + thread_local! { static X: Box = Box::new(0); } + X.with(|_x| {}) +} + +fn with_thread_local2() { + thread_local! { static Y: Box = Box::new(0); } + Y.with(|_y| {}) +} + +fn main() { + // Here we have two threads racing on initializing the thread-local and adding it to the global + // dtor list (on targets that have such a list, i.e., targets without target_thread_local). + let t = thread::spawn(with_thread_local1); + with_thread_local1(); + t.join().unwrap(); + + // Here we have one thread running the destructors racing with another thread initializing a + // thread-local. The second thread adds a destructor that could be picked up by the first. + let t = thread::spawn(|| { /* immediately just run destructors */ }); + with_thread_local2(); // initialize thread-local + t.join().unwrap(); +} diff --git a/tests/extern-so/fail/function_not_in_so.rs b/tests/native-lib/fail/function_not_in_so.rs similarity index 100% rename from tests/extern-so/fail/function_not_in_so.rs rename to tests/native-lib/fail/function_not_in_so.rs diff --git a/tests/extern-so/fail/function_not_in_so.stderr b/tests/native-lib/fail/function_not_in_so.stderr similarity index 59% rename from tests/extern-so/fail/function_not_in_so.stderr rename to tests/native-lib/fail/function_not_in_so.stderr index c031cb0146..e905d7d039 100644 --- a/tests/extern-so/fail/function_not_in_so.stderr +++ b/tests/native-lib/fail/function_not_in_so.stderr @@ -4,7 +4,8 @@ error: unsupported operation: can't call foreign function `foo` on $OS LL | foo(); | ^^^^^ can't call foreign function `foo` on $OS | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = help: if this is a basic API commonly used on this target, please report an issue with Miri + = help: however, note that Miri does not aim to support every FFI function out there; for instance, we will not support APIs for things such as GUIs, scripting languages, or databases = note: BACKTRACE: = note: inside `main` at $DIR/function_not_in_so.rs:LL:CC diff --git a/tests/extern-so/libtest.map b/tests/native-lib/libtest.map similarity index 100% rename from tests/extern-so/libtest.map rename to tests/native-lib/libtest.map diff --git a/tests/extern-so/pass/call_extern_c_fn.rs b/tests/native-lib/pass/call_extern_c_fn.rs similarity index 100% rename from tests/extern-so/pass/call_extern_c_fn.rs rename to tests/native-lib/pass/call_extern_c_fn.rs diff --git a/tests/extern-so/pass/call_extern_c_fn.stdout b/tests/native-lib/pass/call_extern_c_fn.stdout similarity index 100% rename from tests/extern-so/pass/call_extern_c_fn.stdout rename to tests/native-lib/pass/call_extern_c_fn.stdout diff --git a/tests/extern-so/test.c b/tests/native-lib/test.c similarity index 100% rename from tests/extern-so/test.c rename to tests/native-lib/test.c diff --git a/tests/panic/alloc_error_handler_hook.rs b/tests/panic/alloc_error_handler_hook.rs new file mode 100644 index 0000000000..a1eadb45fd --- /dev/null +++ b/tests/panic/alloc_error_handler_hook.rs @@ -0,0 +1,20 @@ +#![feature(allocator_api, alloc_error_hook)] + +use std::alloc::*; + +struct Bomb; +impl Drop for Bomb { + fn drop(&mut self) { + eprintln!("yes we are unwinding!"); + } +} + +#[allow(unreachable_code, unused_variables)] +fn main() { + // This is a particularly tricky hook, since it unwinds, which the default one does not. + set_alloc_error_hook(|_layout| panic!("alloc error hook called")); + + let bomb = Bomb; + handle_alloc_error(Layout::for_value(&0)); + std::mem::forget(bomb); // defuse unwinding bomb +} diff --git a/tests/panic/alloc_error_handler_hook.stderr b/tests/panic/alloc_error_handler_hook.stderr new file mode 100644 index 0000000000..5b309ed09b --- /dev/null +++ b/tests/panic/alloc_error_handler_hook.stderr @@ -0,0 +1,5 @@ +thread 'main' panicked at $DIR/alloc_error_handler_hook.rs:LL:CC: +alloc error hook called +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect +yes we are unwinding! diff --git a/tests/panic/alloc_error_handler_panic.rs b/tests/panic/alloc_error_handler_panic.rs new file mode 100644 index 0000000000..c434e8d322 --- /dev/null +++ b/tests/panic/alloc_error_handler_panic.rs @@ -0,0 +1,18 @@ +//@compile-flags: -Zoom=panic +#![feature(allocator_api)] + +use std::alloc::*; + +struct Bomb; +impl Drop for Bomb { + fn drop(&mut self) { + eprintln!("yes we are unwinding!"); + } +} + +#[allow(unreachable_code, unused_variables)] +fn main() { + let bomb = Bomb; + handle_alloc_error(Layout::for_value(&0)); + std::mem::forget(bomb); // defuse unwinding bomb +} diff --git a/tests/panic/alloc_error_handler_panic.stderr b/tests/panic/alloc_error_handler_panic.stderr new file mode 100644 index 0000000000..3d5457799f --- /dev/null +++ b/tests/panic/alloc_error_handler_panic.stderr @@ -0,0 +1,5 @@ +thread 'main' panicked at RUSTLIB/std/src/alloc.rs:LL:CC: +memory allocation of 4 bytes failed +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect +yes we are unwinding! diff --git a/tests/panic/div-by-zero-2.stderr b/tests/panic/div-by-zero-2.stderr index 0f088ddd1a..f0b84ea6fd 100644 --- a/tests/panic/div-by-zero-2.stderr +++ b/tests/panic/div-by-zero-2.stderr @@ -1,3 +1,4 @@ thread 'main' panicked at $DIR/div-by-zero-2.rs:LL:CC: attempt to divide by zero note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect diff --git a/tests/panic/function_calls/exported_symbol_good_unwind.stderr b/tests/panic/function_calls/exported_symbol_good_unwind.stderr index acc0e58885..f77f6f0111 100644 --- a/tests/panic/function_calls/exported_symbol_good_unwind.stderr +++ b/tests/panic/function_calls/exported_symbol_good_unwind.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at $DIR/exported_symbol_good_unwind.rs:LL:CC: explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect thread 'main' panicked at $DIR/exported_symbol_good_unwind.rs:LL:CC: explicit panic thread 'main' panicked at $DIR/exported_symbol_good_unwind.rs:LL:CC: diff --git a/tests/panic/mir-validation.rs b/tests/panic/mir-validation.rs new file mode 100644 index 0000000000..f1d0ccc7d0 --- /dev/null +++ b/tests/panic/mir-validation.rs @@ -0,0 +1,26 @@ +//! Ensure that the MIR validator runs on Miri's input. +//@rustc-env:RUSTC_ICE=0 +//@normalize-stderr-test: "\n +[0-9]+:.+" -> "" +//@normalize-stderr-test: "\n +at .+" -> "" +//@normalize-stderr-test: "\n +\[\.\.\. omitted [0-9]+ frames? \.\.\.\].*" -> "" +//@normalize-stderr-test: "\n[ =]*note:.*" -> "" +//@normalize-stderr-test: "DefId\([^()]*\)" -> "DefId" +// Somehow on rustc Windows CI, the "Miri caused an ICE" message is not shown +// and we don't even get a regular panic; rustc aborts with a different exit code instead. +//@ignore-host-windows +#![feature(custom_mir, core_intrinsics)] +use core::intrinsics::mir::*; + +#[custom_mir(dialect = "runtime", phase = "optimized")] +pub fn main() { + mir! { + let x: i32; + let tuple: (*mut i32,); + { + tuple.0 = core::ptr::addr_of_mut!(x); + // Deref at the wrong place! + *(tuple.0) = 1; + Return() + } + } +} diff --git a/tests/panic/mir-validation.stderr b/tests/panic/mir-validation.stderr new file mode 100644 index 0000000000..d158c996dc --- /dev/null +++ b/tests/panic/mir-validation.stderr @@ -0,0 +1,21 @@ +thread 'rustc' panicked at compiler/rustc_const_eval/src/transform/validate.rs:LL:CC: +broken MIR in Item(DefId) (after phase change to runtime-optimized) at bb0[1]: +(*(_2.0: *mut i32)), has deref at the wrong place +stack backtrace: + +error: the compiler unexpectedly panicked. this is a bug. + + + + +query stack during panic: +#0 [optimized_mir] optimizing MIR for `main` +end of query stack + +Miri caused an ICE during evaluation. Here's the interpreter backtrace at the time of the panic: + --> RUSTLIB/core/src/ops/function.rs:LL:CC + | +LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + diff --git a/tests/panic/oob_subslice.stderr b/tests/panic/oob_subslice.stderr index 2bccb60352..c116f8eb52 100644 --- a/tests/panic/oob_subslice.stderr +++ b/tests/panic/oob_subslice.stderr @@ -1,3 +1,4 @@ thread 'main' panicked at $DIR/oob_subslice.rs:LL:CC: range end index 5 out of range for slice of length 4 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect diff --git a/tests/panic/overflowing-lsh-neg.stderr b/tests/panic/overflowing-lsh-neg.stderr index 2cff81c58a..1d057ea5eb 100644 --- a/tests/panic/overflowing-lsh-neg.stderr +++ b/tests/panic/overflowing-lsh-neg.stderr @@ -1,3 +1,4 @@ thread 'main' panicked at $DIR/overflowing-lsh-neg.rs:LL:CC: attempt to shift left with overflow note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect diff --git a/tests/panic/overflowing-rsh-1.stderr b/tests/panic/overflowing-rsh-1.stderr index 13117df5fc..d1a79400bf 100644 --- a/tests/panic/overflowing-rsh-1.stderr +++ b/tests/panic/overflowing-rsh-1.stderr @@ -1,3 +1,4 @@ thread 'main' panicked at $DIR/overflowing-rsh-1.rs:LL:CC: attempt to shift right with overflow note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect diff --git a/tests/panic/overflowing-rsh-2.stderr b/tests/panic/overflowing-rsh-2.stderr index 986a66f899..612b0c0c4c 100644 --- a/tests/panic/overflowing-rsh-2.stderr +++ b/tests/panic/overflowing-rsh-2.stderr @@ -1,3 +1,4 @@ thread 'main' panicked at $DIR/overflowing-rsh-2.rs:LL:CC: attempt to shift right with overflow note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect diff --git a/tests/panic/panic2.stderr b/tests/panic/panic2.stderr index bee1af2bef..792c71346f 100644 --- a/tests/panic/panic2.stderr +++ b/tests/panic/panic2.stderr @@ -1,3 +1,4 @@ thread 'main' panicked at $DIR/panic2.rs:LL:CC: 42-panicking from libstd note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect diff --git a/tests/panic/panic3.stderr b/tests/panic/panic3.stderr index 8dac000d29..f8016bc391 100644 --- a/tests/panic/panic3.stderr +++ b/tests/panic/panic3.stderr @@ -1,3 +1,4 @@ thread 'main' panicked at $DIR/panic3.rs:LL:CC: panicking from libcore note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect diff --git a/tests/panic/panic4.stderr b/tests/panic/panic4.stderr index 13437fe7f0..67410bf3b1 100644 --- a/tests/panic/panic4.stderr +++ b/tests/panic/panic4.stderr @@ -1,3 +1,4 @@ thread 'main' panicked at $DIR/panic4.rs:LL:CC: 42-panicking from libcore note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect diff --git a/tests/panic/transmute_fat2.stderr b/tests/panic/transmute_fat2.stderr index 1f09a2c1a0..2ee01d4693 100644 --- a/tests/panic/transmute_fat2.stderr +++ b/tests/panic/transmute_fat2.stderr @@ -1,3 +1,4 @@ thread 'main' panicked at $DIR/transmute_fat2.rs:LL:CC: index out of bounds: the len is 0 but the index is 0 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect diff --git a/tests/panic/unsupported_foreign_function.stderr b/tests/panic/unsupported_foreign_function.stderr index 4db1e29096..d0a7d8dafc 100644 --- a/tests/panic/unsupported_foreign_function.stderr +++ b/tests/panic/unsupported_foreign_function.stderr @@ -1,3 +1,4 @@ thread 'main' panicked at $DIR/unsupported_foreign_function.rs:LL:CC: unsupported Miri functionality: can't call foreign function `foo` on $OS note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect diff --git a/tests/panic/unsupported_syscall.rs b/tests/panic/unsupported_syscall.rs index 31d666e1d9..30f9da5f80 100644 --- a/tests/panic/unsupported_syscall.rs +++ b/tests/panic/unsupported_syscall.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: no `syscall` on Windows //@ignore-target-apple: `syscall` is not supported on macOS //@compile-flags: -Zmiri-panic-on-unsupported diff --git a/tests/panic/unsupported_syscall.stderr b/tests/panic/unsupported_syscall.stderr index 40ba6671d5..f802159cb1 100644 --- a/tests/panic/unsupported_syscall.stderr +++ b/tests/panic/unsupported_syscall.stderr @@ -1,3 +1,4 @@ thread 'main' panicked at $DIR/unsupported_syscall.rs:LL:CC: unsupported Miri functionality: can't execute syscall with ID 0 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect diff --git a/tests/pass-dep/calloc.rs b/tests/pass-dep/calloc.rs deleted file mode 100644 index 62ab63c5fc..0000000000 --- a/tests/pass-dep/calloc.rs +++ /dev/null @@ -1,22 +0,0 @@ -//@ignore-target-windows: No libc on Windows - -use core::slice; - -fn main() { - unsafe { - let p1 = libc::calloc(0, 0); - assert!(p1.is_null()); - - let p2 = libc::calloc(20, 0); - assert!(p2.is_null()); - - let p3 = libc::calloc(0, 20); - assert!(p3.is_null()); - - let p4 = libc::calloc(4, 8); - assert!(!p4.is_null()); - let slice = slice::from_raw_parts(p4 as *const u8, 4 * 8); - assert_eq!(&slice, &[0_u8; 4 * 8]); - libc::free(p4); - } -} diff --git a/tests/pass-dep/shims/env-cleanup-data-race.rs b/tests/pass-dep/concurrency/env-cleanup-data-race.rs similarity index 93% rename from tests/pass-dep/shims/env-cleanup-data-race.rs rename to tests/pass-dep/concurrency/env-cleanup-data-race.rs index 5c29a1b15a..86a47ba365 100644 --- a/tests/pass-dep/shims/env-cleanup-data-race.rs +++ b/tests/pass-dep/concurrency/env-cleanup-data-race.rs @@ -1,5 +1,5 @@ //@compile-flags: -Zmiri-disable-isolation -Zmiri-preemption-rate=0 -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No libc env support on Windows use std::ffi::CStr; use std::thread; diff --git a/tests/pass-dep/concurrency/libc_pthread_cond_timedwait.rs b/tests/pass-dep/concurrency/libc_pthread_cond_timedwait.rs index f362caa11d..d758168c7c 100644 --- a/tests/pass-dep/concurrency/libc_pthread_cond_timedwait.rs +++ b/tests/pass-dep/concurrency/libc_pthread_cond_timedwait.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows //@ignore-target-apple: pthread_condattr_setclock is not supported on MacOS. //@compile-flags: -Zmiri-disable-isolation diff --git a/tests/pass-dep/concurrency/libc_pthread_cond_timedwait_isolated.rs b/tests/pass-dep/concurrency/libc_pthread_cond_timedwait_isolated.rs index 66c0895a5d..f1a3c5dc10 100644 --- a/tests/pass-dep/concurrency/libc_pthread_cond_timedwait_isolated.rs +++ b/tests/pass-dep/concurrency/libc_pthread_cond_timedwait_isolated.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows //@ignore-target-apple: pthread_condattr_setclock is not supported on MacOS. /// Test that conditional variable timeouts are working properly diff --git a/tests/pass-dep/concurrency/tls_pthread_drop_order.rs b/tests/pass-dep/concurrency/tls_pthread_drop_order.rs index ae874740f2..0eaab96764 100644 --- a/tests/pass-dep/concurrency/tls_pthread_drop_order.rs +++ b/tests/pass-dep/concurrency/tls_pthread_drop_order.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows //! Test that pthread_key destructors are run in the right order. //! Note that these are *not* used by actual `thread_local!` on Linux! Those use //! `thread_local_dtor::register_dtor` from the stdlib instead. In Miri this hits the fallback path diff --git a/tests/pass-dep/extra_fn_ptr_gc.rs b/tests/pass-dep/extra_fn_ptr_gc.rs index 716119a0fc..1198168795 100644 --- a/tests/pass-dep/extra_fn_ptr_gc.rs +++ b/tests/pass-dep/extra_fn_ptr_gc.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No `dlsym` on Windows //@compile-flags: -Zmiri-permissive-provenance #[path = "../utils/mod.rs"] diff --git a/tests/pass-dep/getrandom.rs b/tests/pass-dep/getrandom.rs index c0d9296a9a..53de3af763 100644 --- a/tests/pass-dep/getrandom.rs +++ b/tests/pass-dep/getrandom.rs @@ -1,8 +1,9 @@ // mac-os `getrandom_01` does some pointer shenanigans //@compile-flags: -Zmiri-permissive-provenance +//@revisions: isolation no_isolation +//@[no_isolation]compile-flags: -Zmiri-disable-isolation /// Test direct calls of getrandom 0.1 and 0.2. -/// Make sure they work even with isolation enabled (i.e., we do not hit a file-based fallback path). fn main() { let mut data = vec![0; 16]; getrandom_01::getrandom(&mut data).unwrap(); diff --git a/tests/pass-dep/shims/fcntl_f-fullfsync_apple.rs b/tests/pass-dep/libc/fcntl_f-fullfsync_apple.rs similarity index 100% rename from tests/pass-dep/shims/fcntl_f-fullfsync_apple.rs rename to tests/pass-dep/libc/fcntl_f-fullfsync_apple.rs diff --git a/tests/pass-dep/shims/fcntl_f-fullfsync_apple.stderr b/tests/pass-dep/libc/fcntl_f-fullfsync_apple.stderr similarity index 100% rename from tests/pass-dep/shims/fcntl_f-fullfsync_apple.stderr rename to tests/pass-dep/libc/fcntl_f-fullfsync_apple.stderr diff --git a/tests/pass-dep/libc/libc-fs-readlink.rs b/tests/pass-dep/libc/libc-fs-readlink.rs new file mode 100644 index 0000000000..d72edd7d9e --- /dev/null +++ b/tests/pass-dep/libc/libc-fs-readlink.rs @@ -0,0 +1,51 @@ +// Symlink tests are separate since they don't in general work on a Windows host. +//@ignore-host-windows: creating symlinks requires admin permissions on Windows +//@ignore-target-windows: File handling is not implemented yet +//@compile-flags: -Zmiri-disable-isolation + +use std::ffi::CString; +use std::io::{Error, ErrorKind}; +use std::os::unix::ffi::OsStrExt; + +#[path = "../../utils/mod.rs"] +mod utils; + +fn main() { + let bytes = b"Hello, World!\n"; + let path = utils::prepare_with_content("miri_test_fs_link_target.txt", bytes); + let expected_path = path.as_os_str().as_bytes(); + + let symlink_path = utils::prepare("miri_test_fs_symlink.txt"); + std::os::unix::fs::symlink(&path, &symlink_path).unwrap(); + + // Test that the expected string gets written to a buffer of proper + // length, and that a trailing null byte is not written. + let symlink_c_str = CString::new(symlink_path.as_os_str().as_bytes()).unwrap(); + let symlink_c_ptr = symlink_c_str.as_ptr(); + + // Make the buf one byte larger than it needs to be, + // and check that the last byte is not overwritten. + let mut large_buf = vec![0xFF; expected_path.len() + 1]; + let res = + unsafe { libc::readlink(symlink_c_ptr, large_buf.as_mut_ptr().cast(), large_buf.len()) }; + // Check that the resolved path was properly written into the buf. + assert_eq!(&large_buf[..(large_buf.len() - 1)], expected_path); + assert_eq!(large_buf.last(), Some(&0xFF)); + assert_eq!(res, large_buf.len() as isize - 1); + + // Test that the resolved path is truncated if the provided buffer + // is too small. + let mut small_buf = [0u8; 2]; + let res = + unsafe { libc::readlink(symlink_c_ptr, small_buf.as_mut_ptr().cast(), small_buf.len()) }; + assert_eq!(small_buf, &expected_path[..small_buf.len()]); + assert_eq!(res, small_buf.len() as isize); + + // Test that we report a proper error for a missing path. + let bad_path = CString::new("MIRI_MISSING_FILE_NAME").unwrap(); + let res = unsafe { + libc::readlink(bad_path.as_ptr(), small_buf.as_mut_ptr().cast(), small_buf.len()) + }; + assert_eq!(res, -1); + assert_eq!(Error::last_os_error().kind(), ErrorKind::NotFound); +} diff --git a/tests/pass-dep/shims/libc-fs-with-isolation.rs b/tests/pass-dep/libc/libc-fs-with-isolation.rs similarity index 93% rename from tests/pass-dep/shims/libc-fs-with-isolation.rs rename to tests/pass-dep/libc/libc-fs-with-isolation.rs index 5185db0b0e..088a632427 100644 --- a/tests/pass-dep/shims/libc-fs-with-isolation.rs +++ b/tests/pass-dep/libc/libc-fs-with-isolation.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: no libc on Windows +//@ignore-target-windows: File handling is not implemented yet //@compile-flags: -Zmiri-isolation-error=warn-nobacktrace //@normalize-stderr-test: "(stat(x)?)" -> "$$STAT" diff --git a/tests/pass-dep/shims/libc-fs-with-isolation.stderr b/tests/pass-dep/libc/libc-fs-with-isolation.stderr similarity index 100% rename from tests/pass-dep/shims/libc-fs-with-isolation.stderr rename to tests/pass-dep/libc/libc-fs-with-isolation.stderr diff --git a/tests/pass-dep/shims/libc-fs.rs b/tests/pass-dep/libc/libc-fs.rs similarity index 52% rename from tests/pass-dep/shims/libc-fs.rs rename to tests/pass-dep/libc/libc-fs.rs index 4cfc1843a7..80c9757e9c 100644 --- a/tests/pass-dep/shims/libc-fs.rs +++ b/tests/pass-dep/libc/libc-fs.rs @@ -1,13 +1,14 @@ -//@ignore-target-windows: no libc on Windows +//@ignore-target-windows: File handling is not implemented yet //@compile-flags: -Zmiri-disable-isolation #![feature(io_error_more)] #![feature(io_error_uncategorized)] -use std::ffi::CString; -use std::fs::{canonicalize, remove_dir_all, remove_file, File}; +use std::ffi::{CStr, CString, OsString}; +use std::fs::{canonicalize, remove_file, File}; use std::io::{Error, ErrorKind, Write}; use std::os::unix::ffi::OsStrExt; +use std::os::unix::io::AsRawFd; use std::path::PathBuf; #[path = "../../utils/mod.rs"] @@ -20,42 +21,24 @@ fn main() { test_ftruncate::(libc::ftruncate); #[cfg(target_os = "linux")] test_ftruncate::(libc::ftruncate64); - test_readlink(); test_file_open_unix_allow_two_args(); test_file_open_unix_needs_three_args(); test_file_open_unix_extra_third_arg(); #[cfg(target_os = "linux")] test_o_tmpfile_flag(); test_posix_mkstemp(); -} - -/// Prepare: compute filename and make sure the file does not exist. -fn prepare(filename: &str) -> PathBuf { - let path = utils::tmp().join(filename); - // Clean the paths for robustness. - remove_file(&path).ok(); - path -} - -/// Prepare directory: compute directory name and make sure it does not exist. -#[allow(unused)] -fn prepare_dir(dirname: &str) -> PathBuf { - let path = utils::tmp().join(&dirname); - // Clean the directory for robustness. - remove_dir_all(&path).ok(); - path -} - -/// Prepare like above, and also write some initial content to the file. -fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf { - let path = prepare(filename); - let mut file = File::create(&path).unwrap(); - file.write(content).unwrap(); - path + test_posix_realpath_alloc(); + test_posix_realpath_noalloc(); + test_posix_realpath_errors(); + #[cfg(target_os = "linux")] + test_posix_fadvise(); + #[cfg(target_os = "linux")] + test_sync_file_range(); + test_isatty(); } fn test_file_open_unix_allow_two_args() { - let path = prepare_with_content("test_file_open_unix_allow_two_args.txt", &[]); + let path = utils::prepare_with_content("test_file_open_unix_allow_two_args.txt", &[]); let mut name = path.into_os_string(); name.push("\0"); @@ -64,7 +47,7 @@ fn test_file_open_unix_allow_two_args() { } fn test_file_open_unix_needs_three_args() { - let path = prepare_with_content("test_file_open_unix_needs_three_args.txt", &[]); + let path = utils::prepare_with_content("test_file_open_unix_needs_three_args.txt", &[]); let mut name = path.into_os_string(); name.push("\0"); @@ -73,7 +56,7 @@ fn test_file_open_unix_needs_three_args() { } fn test_file_open_unix_extra_third_arg() { - let path = prepare_with_content("test_file_open_unix_extra_third_arg.txt", &[]); + let path = utils::prepare_with_content("test_file_open_unix_extra_third_arg.txt", &[]); let mut name = path.into_os_string(); name.push("\0"); @@ -97,49 +80,9 @@ fn test_canonicalize_too_long() { assert!(canonicalize(too_long).is_err()); } -fn test_readlink() { - let bytes = b"Hello, World!\n"; - let path = prepare_with_content("miri_test_fs_link_target.txt", bytes); - let expected_path = path.as_os_str().as_bytes(); - - let symlink_path = prepare("miri_test_fs_symlink.txt"); - std::os::unix::fs::symlink(&path, &symlink_path).unwrap(); - - // Test that the expected string gets written to a buffer of proper - // length, and that a trailing null byte is not written. - let symlink_c_str = CString::new(symlink_path.as_os_str().as_bytes()).unwrap(); - let symlink_c_ptr = symlink_c_str.as_ptr(); - - // Make the buf one byte larger than it needs to be, - // and check that the last byte is not overwritten. - let mut large_buf = vec![0xFF; expected_path.len() + 1]; - let res = - unsafe { libc::readlink(symlink_c_ptr, large_buf.as_mut_ptr().cast(), large_buf.len()) }; - // Check that the resolved path was properly written into the buf. - assert_eq!(&large_buf[..(large_buf.len() - 1)], expected_path); - assert_eq!(large_buf.last(), Some(&0xFF)); - assert_eq!(res, large_buf.len() as isize - 1); - - // Test that the resolved path is truncated if the provided buffer - // is too small. - let mut small_buf = [0u8; 2]; - let res = - unsafe { libc::readlink(symlink_c_ptr, small_buf.as_mut_ptr().cast(), small_buf.len()) }; - assert_eq!(small_buf, &expected_path[..small_buf.len()]); - assert_eq!(res, small_buf.len() as isize); - - // Test that we report a proper error for a missing path. - let bad_path = CString::new("MIRI_MISSING_FILE_NAME").unwrap(); - let res = unsafe { - libc::readlink(bad_path.as_ptr(), small_buf.as_mut_ptr().cast(), small_buf.len()) - }; - assert_eq!(res, -1); - assert_eq!(Error::last_os_error().kind(), ErrorKind::NotFound); -} - fn test_rename() { - let path1 = prepare("miri_test_libc_fs_source.txt"); - let path2 = prepare("miri_test_libc_fs_rename_destination.txt"); + let path1 = utils::prepare("miri_test_libc_fs_source.txt"); + let path2 = utils::prepare("miri_test_libc_fs_rename_destination.txt"); let file = File::create(&path1).unwrap(); drop(file); @@ -169,7 +112,7 @@ fn test_ftruncate>( // https://docs.rs/libc/latest/i686-unknown-linux-gnu/libc/type.off_t.html let bytes = b"hello"; - let path = prepare("miri_test_libc_fs_ftruncate.txt"); + let path = utils::prepare("miri_test_libc_fs_ftruncate.txt"); let mut file = File::create(&path).unwrap(); file.write(bytes).unwrap(); file.sync_all().unwrap(); @@ -200,7 +143,7 @@ fn test_ftruncate>( fn test_o_tmpfile_flag() { use std::fs::{create_dir, OpenOptions}; use std::os::unix::fs::OpenOptionsExt; - let dir_path = prepare_dir("miri_test_fs_dir"); + let dir_path = utils::prepare_dir("miri_test_fs_dir"); create_dir(&dir_path).unwrap(); // test that the `O_TMPFILE` custom flag gracefully errors instead of stopping execution assert_eq!( @@ -256,3 +199,166 @@ fn test_posix_mkstemp() { assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); } } + +/// Test allocating variant of `realpath`. +fn test_posix_realpath_alloc() { + use std::os::unix::ffi::OsStrExt; + use std::os::unix::ffi::OsStringExt; + + let buf; + let path = utils::tmp().join("miri_test_libc_posix_realpath_alloc"); + let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); + + // Cleanup before test. + remove_file(&path).ok(); + // Create file. + drop(File::create(&path).unwrap()); + unsafe { + let r = libc::realpath(c_path.as_ptr(), std::ptr::null_mut()); + assert!(!r.is_null()); + buf = CStr::from_ptr(r).to_bytes().to_vec(); + libc::free(r as *mut _); + } + let canonical = PathBuf::from(OsString::from_vec(buf)); + assert_eq!(path.file_name(), canonical.file_name()); + + // Cleanup after test. + remove_file(&path).unwrap(); +} + +/// Test non-allocating variant of `realpath`. +fn test_posix_realpath_noalloc() { + use std::ffi::{CStr, CString}; + use std::os::unix::ffi::OsStrExt; + + let path = utils::tmp().join("miri_test_libc_posix_realpath_noalloc"); + let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); + + let mut v = vec![0; libc::PATH_MAX as usize]; + + // Cleanup before test. + remove_file(&path).ok(); + // Create file. + drop(File::create(&path).unwrap()); + unsafe { + let r = libc::realpath(c_path.as_ptr(), v.as_mut_ptr()); + assert!(!r.is_null()); + } + let c = unsafe { CStr::from_ptr(v.as_ptr()) }; + let canonical = PathBuf::from(c.to_str().expect("CStr to str")); + + assert_eq!(path.file_name(), canonical.file_name()); + + // Cleanup after test. + remove_file(&path).unwrap(); +} + +/// Test failure cases for `realpath`. +fn test_posix_realpath_errors() { + use std::ffi::CString; + use std::io::ErrorKind; + + // Test nonexistent path returns an error. + let c_path = CString::new("./nothing_to_see_here").expect("CString::new failed"); + let r = unsafe { libc::realpath(c_path.as_ptr(), std::ptr::null_mut()) }; + assert!(r.is_null()); + let e = std::io::Error::last_os_error(); + assert_eq!(e.raw_os_error(), Some(libc::ENOENT)); + assert_eq!(e.kind(), ErrorKind::NotFound); +} + +#[cfg(target_os = "linux")] +fn test_posix_fadvise() { + use std::io::Write; + + let path = utils::tmp().join("miri_test_libc_posix_fadvise.txt"); + // Cleanup before test + remove_file(&path).ok(); + + // Set up an open file + let mut file = File::create(&path).unwrap(); + let bytes = b"Hello, World!\n"; + file.write(bytes).unwrap(); + + // Test calling posix_fadvise on a file. + let result = unsafe { + libc::posix_fadvise( + file.as_raw_fd(), + 0, + bytes.len().try_into().unwrap(), + libc::POSIX_FADV_DONTNEED, + ) + }; + drop(file); + remove_file(&path).unwrap(); + assert_eq!(result, 0); +} + +#[cfg(target_os = "linux")] +fn test_sync_file_range() { + use std::io::Write; + + let path = utils::tmp().join("miri_test_libc_sync_file_range.txt"); + // Cleanup before test. + remove_file(&path).ok(); + + // Write to a file. + let mut file = File::create(&path).unwrap(); + let bytes = b"Hello, World!\n"; + file.write(bytes).unwrap(); + + // Test calling sync_file_range on the file. + let result_1 = unsafe { + libc::sync_file_range( + file.as_raw_fd(), + 0, + 0, + libc::SYNC_FILE_RANGE_WAIT_BEFORE + | libc::SYNC_FILE_RANGE_WRITE + | libc::SYNC_FILE_RANGE_WAIT_AFTER, + ) + }; + drop(file); + + // Test calling sync_file_range on a file opened for reading. + let file = File::open(&path).unwrap(); + let result_2 = unsafe { + libc::sync_file_range( + file.as_raw_fd(), + 0, + 0, + libc::SYNC_FILE_RANGE_WAIT_BEFORE + | libc::SYNC_FILE_RANGE_WRITE + | libc::SYNC_FILE_RANGE_WAIT_AFTER, + ) + }; + drop(file); + + remove_file(&path).unwrap(); + assert_eq!(result_1, 0); + assert_eq!(result_2, 0); +} + +fn test_isatty() { + // Testing whether our isatty shim returns the right value would require controlling whether + // these streams are actually TTYs, which is hard. + // For now, we just check that these calls are supported at all. + unsafe { + libc::isatty(libc::STDIN_FILENO); + libc::isatty(libc::STDOUT_FILENO); + libc::isatty(libc::STDERR_FILENO); + + // But when we open a file, it is definitely not a TTY. + let path = utils::tmp().join("notatty.txt"); + // Cleanup before test. + remove_file(&path).ok(); + let file = File::create(&path).unwrap(); + + assert_eq!(libc::isatty(file.as_raw_fd()), 0); + assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), libc::ENOTTY); + + // Cleanup after test. + drop(file); + remove_file(&path).unwrap(); + } +} diff --git a/tests/pass-dep/shims/libc-fs.stderr b/tests/pass-dep/libc/libc-fs.stderr similarity index 100% rename from tests/pass-dep/shims/libc-fs.stderr rename to tests/pass-dep/libc/libc-fs.stderr diff --git a/tests/pass-dep/shims/libc-fs.stdout b/tests/pass-dep/libc/libc-fs.stdout similarity index 100% rename from tests/pass-dep/shims/libc-fs.stdout rename to tests/pass-dep/libc/libc-fs.stdout diff --git a/tests/pass-dep/libc/libc-mem.rs b/tests/pass-dep/libc/libc-mem.rs new file mode 100644 index 0000000000..b36fb436b5 --- /dev/null +++ b/tests/pass-dep/libc/libc-mem.rs @@ -0,0 +1,258 @@ +#![feature(strict_provenance, pointer_is_aligned_to)] +use std::{mem, ptr, slice}; + +fn test_memcpy() { + unsafe { + let src = [1i8, 2, 3]; + let dest = libc::calloc(3, 1); + libc::memcpy(dest, src.as_ptr() as *const libc::c_void, 3); + let slc = std::slice::from_raw_parts(dest as *const i8, 3); + assert_eq!(*slc, [1i8, 2, 3]); + libc::free(dest); + } + + unsafe { + let src = [1i8, 2, 3]; + let dest = libc::calloc(4, 1); + libc::memcpy(dest, src.as_ptr() as *const libc::c_void, 3); + let slc = std::slice::from_raw_parts(dest as *const i8, 4); + assert_eq!(*slc, [1i8, 2, 3, 0]); + libc::free(dest); + } + + unsafe { + let src = 123_i32; + let mut dest = 0_i32; + libc::memcpy( + &mut dest as *mut i32 as *mut libc::c_void, + &src as *const i32 as *const libc::c_void, + mem::size_of::(), + ); + assert_eq!(dest, src); + } + + unsafe { + let src = Some(123); + let mut dest: Option = None; + libc::memcpy( + &mut dest as *mut Option as *mut libc::c_void, + &src as *const Option as *const libc::c_void, + mem::size_of::>(), + ); + assert_eq!(dest, src); + } + + unsafe { + let src = &123; + let mut dest = &42; + libc::memcpy( + &mut dest as *mut &'static i32 as *mut libc::c_void, + &src as *const &'static i32 as *const libc::c_void, + mem::size_of::<&'static i32>(), + ); + assert_eq!(*dest, 123); + } +} + +fn test_strcpy() { + use std::ffi::{CStr, CString}; + + // case: src_size equals dest_size + unsafe { + let src = CString::new("rust").unwrap(); + let size = src.as_bytes_with_nul().len(); + let dest = libc::malloc(size); + libc::strcpy(dest as *mut libc::c_char, src.as_ptr()); + assert_eq!(CStr::from_ptr(dest as *const libc::c_char), src.as_ref()); + libc::free(dest); + } + + // case: src_size is less than dest_size + unsafe { + let src = CString::new("rust").unwrap(); + let size = src.as_bytes_with_nul().len(); + let dest = libc::malloc(size + 1); + libc::strcpy(dest as *mut libc::c_char, src.as_ptr()); + assert_eq!(CStr::from_ptr(dest as *const libc::c_char), src.as_ref()); + libc::free(dest); + } +} + +fn test_malloc() { + // Test that small allocations sometimes *are* not very aligned. + let saw_unaligned = (0..64).any(|_| unsafe { + let p = libc::malloc(3); + libc::free(p); + (p as usize) % 4 != 0 // find any that this is *not* 4-aligned + }); + assert!(saw_unaligned); + + unsafe { + let p1 = libc::malloc(20); + p1.write_bytes(0u8, 20); + + // old size < new size + let p2 = libc::realloc(p1, 40); + let slice = slice::from_raw_parts(p2 as *const u8, 20); + assert_eq!(&slice, &[0_u8; 20]); + + // old size == new size + let p3 = libc::realloc(p2, 40); + let slice = slice::from_raw_parts(p3 as *const u8, 20); + assert_eq!(&slice, &[0_u8; 20]); + + // old size > new size + let p4 = libc::realloc(p3, 10); + let slice = slice::from_raw_parts(p4 as *const u8, 10); + assert_eq!(&slice, &[0_u8; 10]); + + libc::free(p4); + } + + unsafe { + // Realloc with size 0 is okay for the null pointer (and acts like `malloc(0)`) + let p2 = libc::realloc(ptr::null_mut(), 0); + assert!(!p2.is_null()); + libc::free(p2); + } + + unsafe { + let p1 = libc::realloc(ptr::null_mut(), 20); + assert!(!p1.is_null()); + + libc::free(p1); + } +} + +fn test_calloc() { + unsafe { + let p1 = libc::calloc(0, 0); + assert!(!p1.is_null()); + libc::free(p1); + + let p2 = libc::calloc(20, 0); + assert!(!p2.is_null()); + libc::free(p2); + + let p3 = libc::calloc(0, 20); + assert!(!p3.is_null()); + libc::free(p3); + + let p4 = libc::calloc(4, 8); + assert!(!p4.is_null()); + let slice = slice::from_raw_parts(p4 as *const u8, 4 * 8); + assert_eq!(&slice, &[0_u8; 4 * 8]); + libc::free(p4); + } +} + +#[cfg(not(target_os = "windows"))] +fn test_memalign() { + // A normal allocation. + unsafe { + let mut ptr: *mut libc::c_void = ptr::null_mut(); + let align = 8; + let size = 64; + assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0); + assert!(!ptr.is_null()); + assert!(ptr.is_aligned_to(align)); + ptr.cast::().write_bytes(1, size); + libc::free(ptr); + } + + // Align > size. + unsafe { + let mut ptr: *mut libc::c_void = ptr::null_mut(); + let align = 64; + let size = 8; + assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0); + assert!(!ptr.is_null()); + assert!(ptr.is_aligned_to(align)); + ptr.cast::().write_bytes(1, size); + libc::free(ptr); + } + + // Size not multiple of align + unsafe { + let mut ptr: *mut libc::c_void = ptr::null_mut(); + let align = 16; + let size = 31; + assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0); + assert!(!ptr.is_null()); + assert!(ptr.is_aligned_to(align)); + ptr.cast::().write_bytes(1, size); + libc::free(ptr); + } + + // Size == 0 + unsafe { + let mut ptr: *mut libc::c_void = ptr::null_mut(); + let align = 64; + let size = 0; + assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0); + // Non-null pointer is returned if size == 0. + // (This is not a guarantee, it just reflects our current behavior.) + assert!(!ptr.is_null()); + assert!(ptr.is_aligned_to(align)); + libc::free(ptr); + } + + // Non-power of 2 align + unsafe { + let mut ptr: *mut libc::c_void = ptr::without_provenance_mut(0x1234567); + let align = 15; + let size = 8; + assert_eq!(libc::posix_memalign(&mut ptr, align, size), libc::EINVAL); + // The pointer is not modified on failure, posix_memalign(3) says: + // > On Linux (and other systems), posix_memalign() does not modify memptr on failure. + // > A requirement standardizing this behavior was added in POSIX.1-2008 TC2. + assert_eq!(ptr.addr(), 0x1234567); + } + + // Too small align (smaller than ptr) + unsafe { + let mut ptr: *mut libc::c_void = ptr::without_provenance_mut(0x1234567); + let align = std::mem::size_of::() / 2; + let size = 8; + assert_eq!(libc::posix_memalign(&mut ptr, align, size), libc::EINVAL); + // The pointer is not modified on failure, posix_memalign(3) says: + // > On Linux (and other systems), posix_memalign() does not modify memptr on failure. + // > A requirement standardizing this behavior was added in POSIX.1-2008 TC2. + assert_eq!(ptr.addr(), 0x1234567); + } +} + +#[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "illumos", + target_os = "solaris" +)))] +fn test_reallocarray() { + unsafe { + let mut p = libc::reallocarray(std::ptr::null_mut(), 4096, 2); + assert!(!p.is_null()); + libc::free(p); + p = libc::malloc(16); + let r = libc::reallocarray(p, 2, 32); + assert!(!r.is_null()); + libc::free(r); + } +} + +fn main() { + test_malloc(); + test_calloc(); + #[cfg(not(target_os = "windows"))] + test_memalign(); + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "illumos", + target_os = "solaris" + )))] + test_reallocarray(); + + test_memcpy(); + test_strcpy(); +} diff --git a/tests/pass-dep/libc/libc-misc.rs b/tests/pass-dep/libc/libc-misc.rs new file mode 100644 index 0000000000..736e0bf8eb --- /dev/null +++ b/tests/pass-dep/libc/libc-misc.rs @@ -0,0 +1,68 @@ +//@ignore-target-windows: only very limited libc on Windows +//@compile-flags: -Zmiri-disable-isolation +#![feature(io_error_more)] +#![feature(pointer_is_aligned_to)] +#![feature(strict_provenance)] + +use std::mem::transmute; + +/// Tests whether each thread has its own `__errno_location`. +fn test_thread_local_errno() { + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + use libc::___errno as __errno_location; + #[cfg(target_os = "linux")] + use libc::__errno_location; + #[cfg(any(target_os = "macos", target_os = "freebsd"))] + use libc::__error as __errno_location; + + unsafe { + *__errno_location() = 0xBEEF; + std::thread::spawn(|| { + assert_eq!(*__errno_location(), 0); + *__errno_location() = 0xBAD1DEA; + assert_eq!(*__errno_location(), 0xBAD1DEA); + }) + .join() + .unwrap(); + assert_eq!(*__errno_location(), 0xBEEF); + } +} + +#[cfg(target_os = "linux")] +fn test_sigrt() { + let min = libc::SIGRTMIN(); + let max = libc::SIGRTMAX(); + + // "The Linux kernel supports a range of 33 different real-time + // signals, numbered 32 to 64" + assert!(min >= 32); + assert!(max >= 32); + assert!(min <= 64); + assert!(max <= 64); + + // "POSIX.1-2001 requires that an implementation support at least + // _POSIX_RTSIG_MAX (8) real-time signals." + assert!(min < max); + assert!(max - min >= 8) +} + +fn test_dlsym() { + let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, b"notasymbol\0".as_ptr().cast()) }; + assert!(addr as usize == 0); + + let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, b"isatty\0".as_ptr().cast()) }; + assert!(addr as usize != 0); + let isatty: extern "C" fn(i32) -> i32 = unsafe { transmute(addr) }; + assert_eq!(isatty(999), 0); + let errno = std::io::Error::last_os_error().raw_os_error().unwrap(); + assert_eq!(errno, libc::EBADF); +} + +fn main() { + test_thread_local_errno(); + + test_dlsym(); + + #[cfg(target_os = "linux")] + test_sigrt(); +} diff --git a/tests/pass-dep/shims/libc-getrandom.rs b/tests/pass-dep/libc/libc-random.rs similarity index 61% rename from tests/pass-dep/shims/libc-getrandom.rs rename to tests/pass-dep/libc/libc-random.rs index 9c670cbd50..71e3352263 100644 --- a/tests/pass-dep/shims/libc-getrandom.rs +++ b/tests/pass-dep/libc/libc-random.rs @@ -1,9 +1,29 @@ //@ignore-target-windows: no libc -//@ignore-target-apple: no getrandom - -use std::ptr; +//@revisions: isolation no_isolation +//@[no_isolation]compile-flags: -Zmiri-disable-isolation fn main() { + test_getentropy(); + #[cfg(not(target_os = "macos"))] + test_getrandom(); +} + +fn test_getentropy() { + use libc::getentropy; + + let mut buf1 = [0u8; 256]; + let mut buf2 = [0u8; 257]; + unsafe { + assert_eq!(getentropy(buf1.as_mut_ptr() as *mut libc::c_void, buf1.len()), 0); + assert_eq!(getentropy(buf2.as_mut_ptr() as *mut libc::c_void, buf2.len()), -1); + assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), libc::EIO); + } +} + +#[cfg(not(target_os = "macos"))] +fn test_getrandom() { + use std::ptr; + let mut buf = [0u8; 5]; unsafe { #[cfg(target_os = "linux")] diff --git a/tests/pass-dep/libc/libc-time.rs b/tests/pass-dep/libc/libc-time.rs new file mode 100644 index 0000000000..ea1e49a072 --- /dev/null +++ b/tests/pass-dep/libc/libc-time.rs @@ -0,0 +1,93 @@ +//@ignore-target-windows: no libc time APIs on Windows +//@compile-flags: -Zmiri-disable-isolation +use std::ffi::CStr; +use std::{env, mem, ptr}; + +fn main() { + test_clocks(); + test_posix_gettimeofday(); + test_localtime_r(); +} + +/// Tests whether clock support exists at all +fn test_clocks() { + let mut tp = mem::MaybeUninit::::uninit(); + let is_error = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, tp.as_mut_ptr()) }; + assert_eq!(is_error, 0); + let is_error = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, tp.as_mut_ptr()) }; + assert_eq!(is_error, 0); + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + { + let is_error = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME_COARSE, tp.as_mut_ptr()) }; + assert_eq!(is_error, 0); + let is_error = + unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC_COARSE, tp.as_mut_ptr()) }; + assert_eq!(is_error, 0); + } + #[cfg(target_os = "macos")] + { + let is_error = unsafe { libc::clock_gettime(libc::CLOCK_UPTIME_RAW, tp.as_mut_ptr()) }; + assert_eq!(is_error, 0); + } +} + +fn test_posix_gettimeofday() { + let mut tp = mem::MaybeUninit::::uninit(); + let tz = ptr::null_mut::(); + let is_error = unsafe { libc::gettimeofday(tp.as_mut_ptr(), tz.cast()) }; + assert_eq!(is_error, 0); + let tv = unsafe { tp.assume_init() }; + assert!(tv.tv_sec > 0); + assert!(tv.tv_usec >= 0); // Theoretically this could be 0. + + // Test that non-null tz returns an error. + let mut tz = mem::MaybeUninit::::uninit(); + let tz_ptr = tz.as_mut_ptr(); + let is_error = unsafe { libc::gettimeofday(tp.as_mut_ptr(), tz_ptr.cast()) }; + assert_eq!(is_error, -1); +} + +fn test_localtime_r() { + // Set timezone to GMT. + let key = "TZ"; + env::set_var(key, "GMT"); + + const TIME_SINCE_EPOCH: libc::time_t = 1712475836; + let custom_time_ptr = &TIME_SINCE_EPOCH; + let mut tm = libc::tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_gmtoff: 0, + tm_zone: std::ptr::null_mut::(), + }; + let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) }; + + assert_eq!(tm.tm_sec, 56); + assert_eq!(tm.tm_min, 43); + assert_eq!(tm.tm_hour, 7); + assert_eq!(tm.tm_mday, 7); + assert_eq!(tm.tm_mon, 3); + assert_eq!(tm.tm_year, 124); + assert_eq!(tm.tm_wday, 0); + assert_eq!(tm.tm_yday, 97); + assert_eq!(tm.tm_isdst, -1); + #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))] + assert_eq!(tm.tm_gmtoff, 0); + #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))] + unsafe { + assert_eq!(CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00") + }; + + // The returned value is the pointer passed in. + assert!(ptr::eq(res, &mut tm)); + + // Remove timezone setting. + env::remove_var(key); +} diff --git a/tests/pass-dep/shims/mmap.rs b/tests/pass-dep/libc/mmap.rs similarity index 99% rename from tests/pass-dep/shims/mmap.rs rename to tests/pass-dep/libc/mmap.rs index 5acdedc67b..a0787c6890 100644 --- a/tests/pass-dep/shims/mmap.rs +++ b/tests/pass-dep/libc/mmap.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No mmap on Windows //@compile-flags: -Zmiri-disable-isolation -Zmiri-permissive-provenance #![feature(strict_provenance)] diff --git a/tests/pass-dep/shims/pthread-sync.rs b/tests/pass-dep/libc/pthread-sync.rs similarity index 90% rename from tests/pass-dep/shims/pthread-sync.rs rename to tests/pass-dep/libc/pthread-sync.rs index c9d10cb83d..1427526212 100644 --- a/tests/pass-dep/shims/pthread-sync.rs +++ b/tests/pass-dep/libc/pthread-sync.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows // We use `yield` to test specific interleavings, so disable automatic preemption. //@compile-flags: -Zmiri-preemption-rate=0 #![feature(sync_unsafe_cell)] @@ -19,6 +19,7 @@ fn main() { check_rwlock_write(); check_rwlock_read_no_deadlock(); check_cond(); + check_condattr(); } fn test_mutex_libc_init_recursive() { @@ -261,6 +262,31 @@ fn check_cond() { } } +fn check_condattr() { + unsafe { + // Just smoke-testing that these functions can be called. + let mut attr: MaybeUninit = MaybeUninit::uninit(); + assert_eq!(libc::pthread_condattr_init(attr.as_mut_ptr()), 0); + + #[cfg(not(target_os = "macos"))] // setclock-getclock do not exist on macOS + { + let clock_id = libc::CLOCK_MONOTONIC; + assert_eq!(libc::pthread_condattr_setclock(attr.as_mut_ptr(), clock_id), 0); + let mut check_clock_id = MaybeUninit::::uninit(); + assert_eq!( + libc::pthread_condattr_getclock(attr.as_mut_ptr(), check_clock_id.as_mut_ptr()), + 0 + ); + assert_eq!(check_clock_id.assume_init(), clock_id); + } + + let mut cond: MaybeUninit = MaybeUninit::uninit(); + assert_eq!(libc::pthread_cond_init(cond.as_mut_ptr(), attr.as_ptr()), 0); + assert_eq!(libc::pthread_condattr_destroy(attr.as_mut_ptr()), 0); + assert_eq!(libc::pthread_cond_destroy(cond.as_mut_ptr()), 0); + } +} + // std::sync::RwLock does not even used pthread_rwlock any more. // Do some smoke testing of the API surface. fn test_rwlock_libc_static_initializer() { diff --git a/tests/pass-dep/shims/pthread-threadname.rs b/tests/pass-dep/libc/pthread-threadname.rs similarity index 97% rename from tests/pass-dep/shims/pthread-threadname.rs rename to tests/pass-dep/libc/pthread-threadname.rs index bc782044d4..4c4f542dfd 100644 --- a/tests/pass-dep/shims/pthread-threadname.rs +++ b/tests/pass-dep/libc/pthread-threadname.rs @@ -1,4 +1,4 @@ -//@ignore-target-windows: No libc on Windows +//@ignore-target-windows: No pthreads on Windows use std::ffi::CStr; #[cfg(not(target_os = "freebsd"))] use std::ffi::CString; diff --git a/tests/pass-dep/malloc.rs b/tests/pass-dep/malloc.rs deleted file mode 100644 index f5e014c000..0000000000 --- a/tests/pass-dep/malloc.rs +++ /dev/null @@ -1,49 +0,0 @@ -//@ignore-target-windows: No libc on Windows - -use core::{ptr, slice}; - -fn main() { - // Test that small allocations sometimes *are* not very aligned. - let saw_unaligned = (0..64).any(|_| unsafe { - let p = libc::malloc(3); - libc::free(p); - (p as usize) % 4 != 0 // find any that this is *not* 4-aligned - }); - assert!(saw_unaligned); - - unsafe { - // Use calloc for initialized memory - let p1 = libc::calloc(20, 1); - - // old size < new size - let p2 = libc::realloc(p1, 40); - let slice = slice::from_raw_parts(p2 as *const u8, 20); - assert_eq!(&slice, &[0_u8; 20]); - - // old size == new size - let p3 = libc::realloc(p2, 40); - let slice = slice::from_raw_parts(p3 as *const u8, 20); - assert_eq!(&slice, &[0_u8; 20]); - - // old size > new size - let p4 = libc::realloc(p3, 10); - let slice = slice::from_raw_parts(p4 as *const u8, 10); - assert_eq!(&slice, &[0_u8; 10]); - - libc::free(p4); - } - - unsafe { - let p1 = libc::malloc(20); - - let p2 = libc::realloc(p1, 0); - assert!(p2.is_null()); - } - - unsafe { - let p1 = libc::realloc(ptr::null_mut(), 20); - assert!(!p1.is_null()); - - libc::free(p1); - } -} diff --git a/tests/pass-dep/rand.rs b/tests/pass-dep/rand.rs deleted file mode 100644 index 0dce6d86cf..0000000000 --- a/tests/pass-dep/rand.rs +++ /dev/null @@ -1,23 +0,0 @@ -//@compile-flags: -Zmiri-strict-provenance -use rand::prelude::*; - -// Test using the `rand` crate to generate randomness. -fn main() { - // Fully deterministic seeding. - let mut rng = SmallRng::seed_from_u64(42); - let _val = rng.gen::(); - let _val = rng.gen::(); - let _val = rng.gen::(); - - // Try seeding with "real" entropy. - let mut rng = SmallRng::from_entropy(); - let _val = rng.gen::(); - let _val = rng.gen::(); - let _val = rng.gen::(); - - // Also try per-thread RNG. - let mut rng = rand::thread_rng(); - let _val = rng.gen::(); - let _val = rng.gen::(); - let _val = rng.gen::(); -} diff --git a/tests/pass-dep/shims/libc-getentropy.rs b/tests/pass-dep/shims/libc-getentropy.rs deleted file mode 100644 index 4b863f6851..0000000000 --- a/tests/pass-dep/shims/libc-getentropy.rs +++ /dev/null @@ -1,20 +0,0 @@ -//@ignore-target-windows: no libc - -// on macOS this is not in the `libc` crate. -#[cfg(target_os = "macos")] -extern "C" { - fn getentropy(bytes: *mut libc::c_void, count: libc::size_t) -> libc::c_int; -} - -#[cfg(not(target_os = "macos"))] -use libc::getentropy; - -fn main() { - let mut buf1 = [0u8; 256]; - let mut buf2 = [0u8; 257]; - unsafe { - assert_eq!(getentropy(buf1.as_mut_ptr() as *mut libc::c_void, buf1.len()), 0); - assert_eq!(getentropy(buf2.as_mut_ptr() as *mut libc::c_void, buf2.len()), -1); - assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), libc::EIO); - } -} diff --git a/tests/pass-dep/shims/libc-getrandom-without-isolation.rs b/tests/pass-dep/shims/libc-getrandom-without-isolation.rs deleted file mode 100644 index 349b447569..0000000000 --- a/tests/pass-dep/shims/libc-getrandom-without-isolation.rs +++ /dev/null @@ -1,41 +0,0 @@ -//@only-target-linux -//@compile-flags: -Zmiri-disable-isolation - -use std::ptr; - -fn main() { - let mut buf = [0u8; 5]; - unsafe { - assert_eq!( - libc::syscall( - libc::SYS_getrandom, - ptr::null_mut::(), - 0 as libc::size_t, - 0 as libc::c_uint, - ), - 0, - ); - assert_eq!( - libc::syscall( - libc::SYS_getrandom, - buf.as_mut_ptr() as *mut libc::c_void, - 5 as libc::size_t, - 0 as libc::c_uint, - ), - 5, - ); - - assert_eq!( - libc::getrandom(ptr::null_mut::(), 0 as libc::size_t, 0 as libc::c_uint), - 0, - ); - assert_eq!( - libc::getrandom( - buf.as_mut_ptr() as *mut libc::c_void, - 5 as libc::size_t, - 0 as libc::c_uint, - ), - 5, - ); - } -} diff --git a/tests/pass-dep/shims/libc-misc.rs b/tests/pass-dep/shims/libc-misc.rs deleted file mode 100644 index abb384b0a8..0000000000 --- a/tests/pass-dep/shims/libc-misc.rs +++ /dev/null @@ -1,388 +0,0 @@ -//@ignore-target-windows: No libc on Windows -//@compile-flags: -Zmiri-disable-isolation -#![feature(io_error_more)] - -use std::fs::{remove_file, File}; -use std::mem::transmute; -use std::os::unix::io::AsRawFd; -use std::path::PathBuf; - -#[path = "../../utils/mod.rs"] -mod utils; - -/// Test allocating variant of `realpath`. -fn test_posix_realpath_alloc() { - use std::ffi::OsString; - use std::ffi::{CStr, CString}; - use std::os::unix::ffi::OsStrExt; - use std::os::unix::ffi::OsStringExt; - - let buf; - let path = utils::tmp().join("miri_test_libc_posix_realpath_alloc"); - let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); - - // Cleanup before test. - remove_file(&path).ok(); - // Create file. - drop(File::create(&path).unwrap()); - unsafe { - let r = libc::realpath(c_path.as_ptr(), std::ptr::null_mut()); - assert!(!r.is_null()); - buf = CStr::from_ptr(r).to_bytes().to_vec(); - libc::free(r as *mut _); - } - let canonical = PathBuf::from(OsString::from_vec(buf)); - assert_eq!(path.file_name(), canonical.file_name()); - - // Cleanup after test. - remove_file(&path).unwrap(); -} - -/// Test non-allocating variant of `realpath`. -fn test_posix_realpath_noalloc() { - use std::ffi::{CStr, CString}; - use std::os::unix::ffi::OsStrExt; - - let path = utils::tmp().join("miri_test_libc_posix_realpath_noalloc"); - let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); - - let mut v = vec![0; libc::PATH_MAX as usize]; - - // Cleanup before test. - remove_file(&path).ok(); - // Create file. - drop(File::create(&path).unwrap()); - unsafe { - let r = libc::realpath(c_path.as_ptr(), v.as_mut_ptr()); - assert!(!r.is_null()); - } - let c = unsafe { CStr::from_ptr(v.as_ptr()) }; - let canonical = PathBuf::from(c.to_str().expect("CStr to str")); - - assert_eq!(path.file_name(), canonical.file_name()); - - // Cleanup after test. - remove_file(&path).unwrap(); -} - -/// Test failure cases for `realpath`. -fn test_posix_realpath_errors() { - use std::ffi::CString; - use std::io::ErrorKind; - - // Test nonexistent path returns an error. - let c_path = CString::new("./nothing_to_see_here").expect("CString::new failed"); - let r = unsafe { libc::realpath(c_path.as_ptr(), std::ptr::null_mut()) }; - assert!(r.is_null()); - let e = std::io::Error::last_os_error(); - assert_eq!(e.raw_os_error(), Some(libc::ENOENT)); - assert_eq!(e.kind(), ErrorKind::NotFound); -} - -#[cfg(target_os = "linux")] -fn test_posix_fadvise() { - use std::io::Write; - - let path = utils::tmp().join("miri_test_libc_posix_fadvise.txt"); - // Cleanup before test - remove_file(&path).ok(); - - // Set up an open file - let mut file = File::create(&path).unwrap(); - let bytes = b"Hello, World!\n"; - file.write(bytes).unwrap(); - - // Test calling posix_fadvise on a file. - let result = unsafe { - libc::posix_fadvise( - file.as_raw_fd(), - 0, - bytes.len().try_into().unwrap(), - libc::POSIX_FADV_DONTNEED, - ) - }; - drop(file); - remove_file(&path).unwrap(); - assert_eq!(result, 0); -} - -#[cfg(target_os = "linux")] -fn test_sync_file_range() { - use std::io::Write; - - let path = utils::tmp().join("miri_test_libc_sync_file_range.txt"); - // Cleanup before test. - remove_file(&path).ok(); - - // Write to a file. - let mut file = File::create(&path).unwrap(); - let bytes = b"Hello, World!\n"; - file.write(bytes).unwrap(); - - // Test calling sync_file_range on the file. - let result_1 = unsafe { - libc::sync_file_range( - file.as_raw_fd(), - 0, - 0, - libc::SYNC_FILE_RANGE_WAIT_BEFORE - | libc::SYNC_FILE_RANGE_WRITE - | libc::SYNC_FILE_RANGE_WAIT_AFTER, - ) - }; - drop(file); - - // Test calling sync_file_range on a file opened for reading. - let file = File::open(&path).unwrap(); - let result_2 = unsafe { - libc::sync_file_range( - file.as_raw_fd(), - 0, - 0, - libc::SYNC_FILE_RANGE_WAIT_BEFORE - | libc::SYNC_FILE_RANGE_WRITE - | libc::SYNC_FILE_RANGE_WAIT_AFTER, - ) - }; - drop(file); - - remove_file(&path).unwrap(); - assert_eq!(result_1, 0); - assert_eq!(result_2, 0); -} - -/// Tests whether each thread has its own `__errno_location`. -fn test_thread_local_errno() { - #[cfg(target_os = "linux")] - use libc::__errno_location; - #[cfg(any(target_os = "macos", target_os = "freebsd"))] - use libc::__error as __errno_location; - - unsafe { - *__errno_location() = 0xBEEF; - std::thread::spawn(|| { - assert_eq!(*__errno_location(), 0); - *__errno_location() = 0xBAD1DEA; - assert_eq!(*__errno_location(), 0xBAD1DEA); - }) - .join() - .unwrap(); - assert_eq!(*__errno_location(), 0xBEEF); - } -} - -/// Tests whether clock support exists at all -fn test_clocks() { - let mut tp = std::mem::MaybeUninit::::uninit(); - let is_error = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, tp.as_mut_ptr()) }; - assert_eq!(is_error, 0); - let is_error = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, tp.as_mut_ptr()) }; - assert_eq!(is_error, 0); - #[cfg(any(target_os = "linux", target_os = "freebsd"))] - { - let is_error = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME_COARSE, tp.as_mut_ptr()) }; - assert_eq!(is_error, 0); - let is_error = - unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC_COARSE, tp.as_mut_ptr()) }; - assert_eq!(is_error, 0); - } - #[cfg(target_os = "macos")] - { - let is_error = unsafe { libc::clock_gettime(libc::CLOCK_UPTIME_RAW, tp.as_mut_ptr()) }; - assert_eq!(is_error, 0); - } -} - -fn test_posix_gettimeofday() { - let mut tp = std::mem::MaybeUninit::::uninit(); - let tz = std::ptr::null_mut::(); - #[cfg(target_os = "macos")] // `tz` has a different type on macOS - let tz = tz as *mut libc::c_void; - let is_error = unsafe { libc::gettimeofday(tp.as_mut_ptr(), tz) }; - assert_eq!(is_error, 0); - let tv = unsafe { tp.assume_init() }; - assert!(tv.tv_sec > 0); - assert!(tv.tv_usec >= 0); // Theoretically this could be 0. - - // Test that non-null tz returns an error. - let mut tz = std::mem::MaybeUninit::::uninit(); - let tz_ptr = tz.as_mut_ptr(); - #[cfg(target_os = "macos")] // `tz` has a different type on macOS - let tz_ptr = tz_ptr as *mut libc::c_void; - let is_error = unsafe { libc::gettimeofday(tp.as_mut_ptr(), tz_ptr) }; - assert_eq!(is_error, -1); -} - -fn test_isatty() { - // Testing whether our isatty shim returns the right value would require controlling whether - // these streams are actually TTYs, which is hard. - // For now, we just check that these calls are supported at all. - unsafe { - libc::isatty(libc::STDIN_FILENO); - libc::isatty(libc::STDOUT_FILENO); - libc::isatty(libc::STDERR_FILENO); - - // But when we open a file, it is definitely not a TTY. - let path = utils::tmp().join("notatty.txt"); - // Cleanup before test. - remove_file(&path).ok(); - let file = File::create(&path).unwrap(); - - assert_eq!(libc::isatty(file.as_raw_fd()), 0); - assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), libc::ENOTTY); - - // Cleanup after test. - drop(file); - remove_file(&path).unwrap(); - } -} - -fn test_memcpy() { - unsafe { - let src = [1i8, 2, 3]; - let dest = libc::calloc(3, 1); - libc::memcpy(dest, src.as_ptr() as *const libc::c_void, 3); - let slc = std::slice::from_raw_parts(dest as *const i8, 3); - assert_eq!(*slc, [1i8, 2, 3]); - libc::free(dest); - } - - unsafe { - let src = [1i8, 2, 3]; - let dest = libc::calloc(4, 1); - libc::memcpy(dest, src.as_ptr() as *const libc::c_void, 3); - let slc = std::slice::from_raw_parts(dest as *const i8, 4); - assert_eq!(*slc, [1i8, 2, 3, 0]); - libc::free(dest); - } - - unsafe { - let src = 123_i32; - let mut dest = 0_i32; - libc::memcpy( - &mut dest as *mut i32 as *mut libc::c_void, - &src as *const i32 as *const libc::c_void, - std::mem::size_of::(), - ); - assert_eq!(dest, src); - } - - unsafe { - let src = Some(123); - let mut dest: Option = None; - libc::memcpy( - &mut dest as *mut Option as *mut libc::c_void, - &src as *const Option as *const libc::c_void, - std::mem::size_of::>(), - ); - assert_eq!(dest, src); - } - - unsafe { - let src = &123; - let mut dest = &42; - libc::memcpy( - &mut dest as *mut &'static i32 as *mut libc::c_void, - &src as *const &'static i32 as *const libc::c_void, - std::mem::size_of::<&'static i32>(), - ); - assert_eq!(*dest, 123); - } -} - -fn test_strcpy() { - use std::ffi::{CStr, CString}; - - // case: src_size equals dest_size - unsafe { - let src = CString::new("rust").unwrap(); - let size = src.as_bytes_with_nul().len(); - let dest = libc::malloc(size); - libc::strcpy(dest as *mut libc::c_char, src.as_ptr()); - assert_eq!(CStr::from_ptr(dest as *const libc::c_char), src.as_ref()); - libc::free(dest); - } - - // case: src_size is less than dest_size - unsafe { - let src = CString::new("rust").unwrap(); - let size = src.as_bytes_with_nul().len(); - let dest = libc::malloc(size + 1); - libc::strcpy(dest as *mut libc::c_char, src.as_ptr()); - assert_eq!(CStr::from_ptr(dest as *const libc::c_char), src.as_ref()); - libc::free(dest); - } -} - -#[cfg(target_os = "linux")] -fn test_sigrt() { - let min = libc::SIGRTMIN(); - let max = libc::SIGRTMAX(); - - // "The Linux kernel supports a range of 33 different real-time - // signals, numbered 32 to 64" - assert!(min >= 32); - assert!(max >= 32); - assert!(min <= 64); - assert!(max <= 64); - - // "POSIX.1-2001 requires that an implementation support at least - // _POSIX_RTSIG_MAX (8) real-time signals." - assert!(min < max); - assert!(max - min >= 8) -} - -fn test_dlsym() { - let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, b"notasymbol\0".as_ptr().cast()) }; - assert!(addr as usize == 0); - - let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, b"isatty\0".as_ptr().cast()) }; - assert!(addr as usize != 0); - let isatty: extern "C" fn(i32) -> i32 = unsafe { transmute(addr) }; - assert_eq!(isatty(999), 0); - let errno = std::io::Error::last_os_error().raw_os_error().unwrap(); - assert_eq!(errno, libc::EBADF); -} - -#[cfg(not(target_os = "macos"))] -fn test_reallocarray() { - unsafe { - let mut p = libc::reallocarray(std::ptr::null_mut(), 4096, 2); - assert!(!p.is_null()); - libc::free(p); - p = libc::malloc(16); - let r = libc::reallocarray(p, 2, 32); - assert!(!r.is_null()); - libc::free(r); - } -} - -fn main() { - test_posix_gettimeofday(); - - test_posix_realpath_alloc(); - test_posix_realpath_noalloc(); - test_posix_realpath_errors(); - - test_thread_local_errno(); - - test_isatty(); - - test_clocks(); - - test_dlsym(); - - test_memcpy(); - test_strcpy(); - - #[cfg(not(target_os = "macos"))] // reallocarray does not exist on macOS - test_reallocarray(); - - // These are Linux-specific - #[cfg(target_os = "linux")] - { - test_posix_fadvise(); - test_sync_file_range(); - test_sigrt(); - } -} diff --git a/tests/pass-dep/shims/libc-rsfs.stdout b/tests/pass-dep/shims/libc-rsfs.stdout deleted file mode 100644 index b6fa69e3d5..0000000000 --- a/tests/pass-dep/shims/libc-rsfs.stdout +++ /dev/null @@ -1 +0,0 @@ -hello dup fd diff --git a/tests/pass-dep/shims/posix_memalign.rs b/tests/pass-dep/shims/posix_memalign.rs deleted file mode 100644 index db66b21341..0000000000 --- a/tests/pass-dep/shims/posix_memalign.rs +++ /dev/null @@ -1,82 +0,0 @@ -//@ignore-target-windows: No libc on Windows - -#![feature(pointer_is_aligned_to)] -#![feature(strict_provenance)] - -use core::ptr; - -fn main() { - // A normal allocation. - unsafe { - let mut ptr: *mut libc::c_void = ptr::null_mut(); - let align = 8; - let size = 64; - assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0); - assert!(!ptr.is_null()); - assert!(ptr.is_aligned_to(align)); - ptr.cast::().write_bytes(1, size); - libc::free(ptr); - } - - // Align > size. - unsafe { - let mut ptr: *mut libc::c_void = ptr::null_mut(); - let align = 64; - let size = 8; - assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0); - assert!(!ptr.is_null()); - assert!(ptr.is_aligned_to(align)); - ptr.cast::().write_bytes(1, size); - libc::free(ptr); - } - - // Size not multiple of align - unsafe { - let mut ptr: *mut libc::c_void = ptr::null_mut(); - let align = 16; - let size = 31; - assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0); - assert!(!ptr.is_null()); - assert!(ptr.is_aligned_to(align)); - ptr.cast::().write_bytes(1, size); - libc::free(ptr); - } - - // Size == 0 - unsafe { - let mut ptr: *mut libc::c_void = ptr::null_mut(); - let align = 64; - let size = 0; - assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0); - // We are not required to return null if size == 0, but we currently do. - // It's fine to remove this assert if we start returning non-null pointers. - assert!(ptr.is_null()); - assert!(ptr.is_aligned_to(align)); - // Regardless of what we return, it must be `free`able. - libc::free(ptr); - } - - // Non-power of 2 align - unsafe { - let mut ptr: *mut libc::c_void = ptr::without_provenance_mut(0x1234567); - let align = 15; - let size = 8; - assert_eq!(libc::posix_memalign(&mut ptr, align, size), libc::EINVAL); - // The pointer is not modified on failure, posix_memalign(3) says: - // > On Linux (and other systems), posix_memalign() does not modify memptr on failure. - // > A requirement standardizing this behavior was added in POSIX.1-2008 TC2. - assert_eq!(ptr.addr(), 0x1234567); - } - - // Too small align (smaller than ptr) - unsafe { - let mut ptr: *mut libc::c_void = ptr::without_provenance_mut(0x1234567); - let align = std::mem::size_of::() / 2; - let size = 8; - assert_eq!(libc::posix_memalign(&mut ptr, align, size), libc::EINVAL); - // The pointer is not modified on failure, posix_memalign(3) says: - // > On Linux (and other systems), posix_memalign() does not modify memptr on failure. - // > A requirement standardizing this behavior was added in POSIX.1-2008 TC2. - assert_eq!(ptr.addr(), 0x1234567); - } -} diff --git a/tests/pass-dep/wcslen.rs b/tests/pass-dep/wcslen.rs new file mode 100644 index 0000000000..c5c9d99247 --- /dev/null +++ b/tests/pass-dep/wcslen.rs @@ -0,0 +1,20 @@ +fn to_c_wchar_t_str(s: &str) -> Vec { + let mut r = Vec::::new(); + for c in s.bytes() { + if c == 0 { + panic!("can't contain a null character"); + } + if c >= 128 { + panic!("only ASCII supported"); + } + r.push(c.into()); + } + r.push(0); + r +} + +pub fn main() { + let s = to_c_wchar_t_str("Rust"); + let len = unsafe { libc::wcslen(s.as_ptr()) }; + assert_eq!(len, 4); +} diff --git a/tests/pass/adjacent-allocs.rs b/tests/pass/adjacent-allocs.rs index cbf41d68b5..8be4bdac7e 100644 --- a/tests/pass/adjacent-allocs.rs +++ b/tests/pass/adjacent-allocs.rs @@ -30,7 +30,7 @@ fn test1() { // See https://github.com/rust-lang/miri/issues/1866#issuecomment-985770125 { let m = 0u64; - let _ = &m as *const u64; + let _ptr = &m as *const u64; } let iptr = ptr as usize; diff --git a/tests/pass/align_offset_symbolic.rs b/tests/pass/align_offset_symbolic.rs index c32fa2c8f9..dec3d779a7 100644 --- a/tests/pass/align_offset_symbolic.rs +++ b/tests/pass/align_offset_symbolic.rs @@ -62,7 +62,10 @@ fn test_from_utf8() { const N: usize = 10; let vec = vec![0x4141414141414141u64; N]; let content = unsafe { std::slice::from_raw_parts(vec.as_ptr() as *const u8, 8 * N) }; - println!("{:?}", std::str::from_utf8(content).unwrap()); + assert_eq!( + std::str::from_utf8(content).unwrap(), + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + ); } fn test_u64_array() { diff --git a/tests/pass/align_offset_symbolic.stdout b/tests/pass/align_offset_symbolic.stdout deleted file mode 100644 index 66d4399481..0000000000 --- a/tests/pass/align_offset_symbolic.stdout +++ /dev/null @@ -1 +0,0 @@ -"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" diff --git a/tests/pass/alloc-access-tracking.rs b/tests/pass/alloc-access-tracking.rs index 5c782fca2d..a226783155 100644 --- a/tests/pass/alloc-access-tracking.rs +++ b/tests/pass/alloc-access-tracking.rs @@ -1,7 +1,8 @@ #![feature(start)] #![no_std] -//@compile-flags: -Zmiri-track-alloc-id=17 -Zmiri-track-alloc-accesses -Cpanic=abort -//@only-target-linux: alloc IDs differ between OSes for some reason +//@compile-flags: -Zmiri-track-alloc-id=20 -Zmiri-track-alloc-accesses -Cpanic=abort +//@normalize-stderr-test: "id 20" -> "id $$ALLOC" +//@only-target-linux: alloc IDs differ between OSes (due to extern static allocations) extern "Rust" { fn miri_alloc(size: usize, align: usize) -> *mut u8; diff --git a/tests/pass/alloc-access-tracking.stderr b/tests/pass/alloc-access-tracking.stderr index 5e219fa1be..0af6cde833 100644 --- a/tests/pass/alloc-access-tracking.stderr +++ b/tests/pass/alloc-access-tracking.stderr @@ -2,7 +2,7 @@ note: tracking was triggered --> $DIR/alloc-access-tracking.rs:LL:CC | LL | let ptr = miri_alloc(123, 1); - | ^^^^^^^^^^^^^^^^^^ created Miri bare-metal heap allocation of 123 bytes (alignment ALIGN bytes) with id 17 + | ^^^^^^^^^^^^^^^^^^ created Miri bare-metal heap allocation of 123 bytes (alignment ALIGN bytes) with id $ALLOC | = note: BACKTRACE: = note: inside `start` at $DIR/alloc-access-tracking.rs:LL:CC @@ -11,7 +11,7 @@ note: tracking was triggered --> $DIR/alloc-access-tracking.rs:LL:CC | LL | *ptr = 42; // Crucially, only a write is printed here, no read! - | ^^^^^^^^^ write access to allocation with id 17 + | ^^^^^^^^^ write access to allocation with id $ALLOC | = note: BACKTRACE: = note: inside `start` at $DIR/alloc-access-tracking.rs:LL:CC @@ -20,7 +20,7 @@ note: tracking was triggered --> $DIR/alloc-access-tracking.rs:LL:CC | LL | assert_eq!(*ptr, 42); - | ^^^^^^^^^^^^^^^^^^^^ read access to allocation with id 17 + | ^^^^^^^^^^^^^^^^^^^^ read access to allocation with id $ALLOC | = note: BACKTRACE: = note: inside `start` at RUSTLIB/core/src/macros/mod.rs:LL:CC @@ -30,7 +30,7 @@ note: tracking was triggered --> $DIR/alloc-access-tracking.rs:LL:CC | LL | miri_dealloc(ptr, 123, 1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ freed allocation with id 17 + | ^^^^^^^^^^^^^^^^^^^^^^^^^ freed allocation with id $ALLOC | = note: BACKTRACE: = note: inside `start` at $DIR/alloc-access-tracking.rs:LL:CC diff --git a/tests/pass/async-closure-captures.rs b/tests/pass/async-closure-captures.rs new file mode 100644 index 0000000000..cac26bfe14 --- /dev/null +++ b/tests/pass/async-closure-captures.rs @@ -0,0 +1,125 @@ +// Same as rustc's `tests/ui/async-await/async-closures/captures.rs`, keep in sync + +#![feature(async_closure, noop_waker)] + +use std::future::Future; +use std::pin::pin; +use std::task::*; + +pub fn block_on(fut: impl Future) -> T { + let mut fut = pin!(fut); + let ctx = &mut Context::from_waker(Waker::noop()); + + loop { + match fut.as_mut().poll(ctx) { + Poll::Pending => {} + Poll::Ready(t) => break t, + } + } +} + +fn main() { + block_on(async_main()); +} + +async fn call(f: &impl async Fn() -> T) -> T { + f().await +} + +async fn call_once(f: impl async FnOnce() -> T) -> T { + f().await +} + +#[derive(Debug)] +#[allow(unused)] +struct Hello(i32); + +async fn async_main() { + // Capture something by-ref + { + let x = Hello(0); + let c = async || { + println!("{x:?}"); + }; + call(&c).await; + call_once(c).await; + + let x = &Hello(1); + let c = async || { + println!("{x:?}"); + }; + call(&c).await; + call_once(c).await; + } + + // Capture something and consume it (force to `AsyncFnOnce`) + { + let x = Hello(2); + let c = async || { + println!("{x:?}"); + drop(x); + }; + call_once(c).await; + } + + // Capture something with `move`, don't consume it + { + let x = Hello(3); + let c = async move || { + println!("{x:?}"); + }; + call(&c).await; + call_once(c).await; + + let x = &Hello(4); + let c = async move || { + println!("{x:?}"); + }; + call(&c).await; + call_once(c).await; + } + + // Capture something with `move`, also consume it (so `AsyncFnOnce`) + { + let x = Hello(5); + let c = async move || { + println!("{x:?}"); + drop(x); + }; + call_once(c).await; + } + + fn force_fnonce(f: impl async FnOnce() -> T) -> impl async FnOnce() -> T { + f + } + + // Capture something with `move`, but infer to `AsyncFnOnce` + { + let x = Hello(6); + let c = force_fnonce(async move || { + println!("{x:?}"); + }); + call_once(c).await; + + let x = &Hello(7); + let c = force_fnonce(async move || { + println!("{x:?}"); + }); + call_once(c).await; + } + + // Capture something by-ref, but infer to `AsyncFnOnce` + { + let x = Hello(8); + let c = force_fnonce(async || { + println!("{x:?}"); + }); + call_once(c).await; + + let x = &Hello(9); + let c = force_fnonce(async || { + println!("{x:?}"); + }); + call_once(c).await; + } +} diff --git a/tests/pass/async-closure-captures.stdout b/tests/pass/async-closure-captures.stdout new file mode 100644 index 0000000000..42a7999b2d --- /dev/null +++ b/tests/pass/async-closure-captures.stdout @@ -0,0 +1,14 @@ +Hello(0) +Hello(0) +Hello(1) +Hello(1) +Hello(2) +Hello(3) +Hello(3) +Hello(4) +Hello(4) +Hello(5) +Hello(6) +Hello(7) +Hello(8) +Hello(9) diff --git a/tests/pass/async-drop.rs b/tests/pass/async-drop.rs new file mode 100644 index 0000000000..f16206f3db --- /dev/null +++ b/tests/pass/async-drop.rs @@ -0,0 +1,191 @@ +//@revisions: stack tree +//@compile-flags: -Zmiri-strict-provenance +//@[tree]compile-flags: -Zmiri-tree-borrows +#![feature(async_drop, impl_trait_in_assoc_type, noop_waker, async_closure)] +#![allow(incomplete_features, dead_code)] + +// FIXME(zetanumbers): consider AsyncDestruct::async_drop cleanup tests +use core::future::{async_drop_in_place, AsyncDrop, Future}; +use core::hint::black_box; +use core::mem::{self, ManuallyDrop}; +use core::pin::{pin, Pin}; +use core::task::{Context, Poll, Waker}; + +async fn test_async_drop(x: T) { + let mut x = mem::MaybeUninit::new(x); + let dtor = pin!(unsafe { async_drop_in_place(x.as_mut_ptr()) }); + test_idempotency(dtor).await; +} + +fn test_idempotency(mut x: Pin<&mut T>) -> impl Future + '_ +where + T: Future, +{ + core::future::poll_fn(move |cx| { + assert_eq!(x.as_mut().poll(cx), Poll::Ready(())); + assert_eq!(x.as_mut().poll(cx), Poll::Ready(())); + Poll::Ready(()) + }) +} + +fn main() { + let waker = Waker::noop(); + let mut cx = Context::from_waker(&waker); + + let i = 13; + let fut = pin!(async { + test_async_drop(Int(0)).await; + test_async_drop(AsyncInt(0)).await; + test_async_drop([AsyncInt(1), AsyncInt(2)]).await; + test_async_drop((AsyncInt(3), AsyncInt(4))).await; + test_async_drop(5).await; + let j = 42; + test_async_drop(&i).await; + test_async_drop(&j).await; + test_async_drop(AsyncStruct { b: AsyncInt(8), a: AsyncInt(7), i: 6 }).await; + test_async_drop(ManuallyDrop::new(AsyncInt(9))).await; + + let foo = AsyncInt(10); + test_async_drop(AsyncReference { foo: &foo }).await; + + let foo = AsyncInt(11); + test_async_drop(|| { + black_box(foo); + let foo = AsyncInt(10); + foo + }) + .await; + + test_async_drop(AsyncEnum::A(AsyncInt(12))).await; + test_async_drop(AsyncEnum::B(SyncInt(13))).await; + + test_async_drop(SyncInt(14)).await; + test_async_drop(SyncThenAsync { i: 15, a: AsyncInt(16), b: SyncInt(17), c: AsyncInt(18) }) + .await; + + let async_drop_fut = pin!(core::future::async_drop(AsyncInt(19))); + test_idempotency(async_drop_fut).await; + + let foo = AsyncInt(20); + test_async_drop(async || { + black_box(foo); + let foo = AsyncInt(19); + // Await point there, but this is async closure so it's fine + black_box(core::future::ready(())).await; + foo + }) + .await; + + test_async_drop(AsyncUnion { signed: 21 }).await; + }); + let res = fut.poll(&mut cx); + assert_eq!(res, Poll::Ready(())); +} + +struct AsyncInt(i32); + +impl AsyncDrop for AsyncInt { + type Dropper<'a> = impl Future; + + fn async_drop(self: Pin<&mut Self>) -> Self::Dropper<'_> { + async move { + println!("AsyncInt::Dropper::poll: {}", self.0); + } + } +} + +struct SyncInt(i32); + +impl Drop for SyncInt { + fn drop(&mut self) { + println!("SyncInt::drop: {}", self.0); + } +} + +struct SyncThenAsync { + i: i32, + a: AsyncInt, + b: SyncInt, + c: AsyncInt, +} + +impl Drop for SyncThenAsync { + fn drop(&mut self) { + println!("SyncThenAsync::drop: {}", self.i); + } +} + +struct AsyncReference<'a> { + foo: &'a AsyncInt, +} + +impl AsyncDrop for AsyncReference<'_> { + type Dropper<'a> = impl Future where Self: 'a; + + fn async_drop(self: Pin<&mut Self>) -> Self::Dropper<'_> { + async move { + println!("AsyncReference::Dropper::poll: {}", self.foo.0); + } + } +} + +struct Int(i32); + +struct AsyncStruct { + i: i32, + a: AsyncInt, + b: AsyncInt, +} + +impl AsyncDrop for AsyncStruct { + type Dropper<'a> = impl Future; + + fn async_drop(self: Pin<&mut Self>) -> Self::Dropper<'_> { + async move { + println!("AsyncStruct::Dropper::poll: {}", self.i); + } + } +} + +enum AsyncEnum { + A(AsyncInt), + B(SyncInt), +} + +impl AsyncDrop for AsyncEnum { + type Dropper<'a> = impl Future; + + fn async_drop(mut self: Pin<&mut Self>) -> Self::Dropper<'_> { + async move { + let new_self = match &*self { + AsyncEnum::A(foo) => { + println!("AsyncEnum(A)::Dropper::poll: {}", foo.0); + AsyncEnum::B(SyncInt(foo.0)) + } + AsyncEnum::B(foo) => { + println!("AsyncEnum(B)::Dropper::poll: {}", foo.0); + AsyncEnum::A(AsyncInt(foo.0)) + } + }; + mem::forget(mem::replace(&mut *self, new_self)); + } + } +} + +// FIXME(zetanumbers): Disallow types with `AsyncDrop` in unions +union AsyncUnion { + signed: i32, + unsigned: u32, +} + +impl AsyncDrop for AsyncUnion { + type Dropper<'a> = impl Future; + + fn async_drop(self: Pin<&mut Self>) -> Self::Dropper<'_> { + async move { + println!("AsyncUnion::Dropper::poll: {}, {}", unsafe { self.signed }, unsafe { + self.unsigned + }); + } + } +} diff --git a/tests/pass/async-drop.stack.stdout b/tests/pass/async-drop.stack.stdout new file mode 100644 index 0000000000..9cae4331ca --- /dev/null +++ b/tests/pass/async-drop.stack.stdout @@ -0,0 +1,22 @@ +AsyncInt::Dropper::poll: 0 +AsyncInt::Dropper::poll: 1 +AsyncInt::Dropper::poll: 2 +AsyncInt::Dropper::poll: 3 +AsyncInt::Dropper::poll: 4 +AsyncStruct::Dropper::poll: 6 +AsyncInt::Dropper::poll: 7 +AsyncInt::Dropper::poll: 8 +AsyncReference::Dropper::poll: 10 +AsyncInt::Dropper::poll: 11 +AsyncEnum(A)::Dropper::poll: 12 +SyncInt::drop: 12 +AsyncEnum(B)::Dropper::poll: 13 +AsyncInt::Dropper::poll: 13 +SyncInt::drop: 14 +SyncThenAsync::drop: 15 +AsyncInt::Dropper::poll: 16 +SyncInt::drop: 17 +AsyncInt::Dropper::poll: 18 +AsyncInt::Dropper::poll: 19 +AsyncInt::Dropper::poll: 20 +AsyncUnion::Dropper::poll: 21, 21 diff --git a/tests/pass/async-drop.tree.stdout b/tests/pass/async-drop.tree.stdout new file mode 100644 index 0000000000..9cae4331ca --- /dev/null +++ b/tests/pass/async-drop.tree.stdout @@ -0,0 +1,22 @@ +AsyncInt::Dropper::poll: 0 +AsyncInt::Dropper::poll: 1 +AsyncInt::Dropper::poll: 2 +AsyncInt::Dropper::poll: 3 +AsyncInt::Dropper::poll: 4 +AsyncStruct::Dropper::poll: 6 +AsyncInt::Dropper::poll: 7 +AsyncInt::Dropper::poll: 8 +AsyncReference::Dropper::poll: 10 +AsyncInt::Dropper::poll: 11 +AsyncEnum(A)::Dropper::poll: 12 +SyncInt::drop: 12 +AsyncEnum(B)::Dropper::poll: 13 +AsyncInt::Dropper::poll: 13 +SyncInt::drop: 14 +SyncThenAsync::drop: 15 +AsyncInt::Dropper::poll: 16 +SyncInt::drop: 17 +AsyncInt::Dropper::poll: 18 +AsyncInt::Dropper::poll: 19 +AsyncInt::Dropper::poll: 20 +AsyncUnion::Dropper::poll: 21, 21 diff --git a/tests/pass/available-parallelism-miri-num-cpus.rs b/tests/pass/available-parallelism-miri-num-cpus.rs deleted file mode 100644 index 137fa51024..0000000000 --- a/tests/pass/available-parallelism-miri-num-cpus.rs +++ /dev/null @@ -1,8 +0,0 @@ -//@compile-flags: -Zmiri-num-cpus=1024 - -use std::num::NonZeroUsize; -use std::thread::available_parallelism; - -fn main() { - assert_eq!(available_parallelism().unwrap(), NonZeroUsize::new(1024).unwrap()); -} diff --git a/tests/pass/backtrace/backtrace-api-v0.stdout b/tests/pass/backtrace/backtrace-api-v0.stdout index c9cab26816..e6644b4453 100644 --- a/tests/pass/backtrace/backtrace-api-v0.stdout +++ b/tests/pass/backtrace/backtrace-api-v0.stdout @@ -1,5 +1,5 @@ $DIR/backtrace-api-v0.rs:24:14 (func_d) -$DIR/backtrace-api-v0.rs:20:5 (func_c) +$DIR/backtrace-api-v0.rs:14:9 (func_c) $DIR/backtrace-api-v0.rs:9:5 (func_b::) $DIR/backtrace-api-v0.rs:5:5 (func_a) $DIR/backtrace-api-v0.rs:29:18 (main) diff --git a/tests/pass/backtrace/backtrace-api-v1.stdout b/tests/pass/backtrace/backtrace-api-v1.stdout index e145c167e8..11efd2f075 100644 --- a/tests/pass/backtrace/backtrace-api-v1.stdout +++ b/tests/pass/backtrace/backtrace-api-v1.stdout @@ -1,5 +1,5 @@ $DIR/backtrace-api-v1.rs:27:9 (func_d) -$DIR/backtrace-api-v1.rs:20:5 (func_c) +$DIR/backtrace-api-v1.rs:14:9 (func_c) $DIR/backtrace-api-v1.rs:9:5 (func_b::) $DIR/backtrace-api-v1.rs:5:5 (func_a) $DIR/backtrace-api-v1.rs:34:18 (main) diff --git a/tests/pass/cast-rfc0401-vtable-kinds.rs b/tests/pass/cast-rfc0401-vtable-kinds.rs index 2491bda091..adbf5df62c 100644 --- a/tests/pass/cast-rfc0401-vtable-kinds.rs +++ b/tests/pass/cast-rfc0401-vtable-kinds.rs @@ -25,7 +25,7 @@ impl Foo for u32 { impl Bar for () {} unsafe fn round_trip_and_call<'a>(t: *const (dyn Foo + 'a)) -> u32 { - let foo_e: *const dyn Foo = t as *const _; + let foo_e: *const dyn Foo = t as *const _; let r_1 = foo_e as *mut dyn Foo; (&*r_1).foo(0) diff --git a/tests/pass/concurrency/address_reuse_happens_before.rs b/tests/pass/concurrency/address_reuse_happens_before.rs new file mode 100644 index 0000000000..cfc1ef7ae4 --- /dev/null +++ b/tests/pass/concurrency/address_reuse_happens_before.rs @@ -0,0 +1,61 @@ +//! Regression test for : +//! When the address gets reused, there should be a happens-before relation. +//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=1.0 +#![feature(strict_provenance)] +#![feature(sync_unsafe_cell)] + +use std::cell::SyncUnsafeCell; +use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; +use std::thread; + +static ADDR: AtomicUsize = AtomicUsize::new(0); +static VAL: SyncUnsafeCell = SyncUnsafeCell::new(0); + +fn addr() -> usize { + let alloc = Box::new(42); + <*const i32>::addr(&*alloc) +} + +fn thread1() { + unsafe { + VAL.get().write(42); + } + let alloc = addr(); + ADDR.store(alloc, Relaxed); +} + +fn thread2() -> bool { + // We try to get an allocation at the same address as the global `ADDR`. If we fail too often, + // just bail. `main` will try again with a different allocation. + for _ in 0..16 { + let alloc = addr(); + let addr = ADDR.load(Relaxed); + if alloc == addr { + // We got a reuse! + // If the new allocation is at the same address as the old one, there must be a + // happens-before relationship between them. Therefore, we can read VAL without racing + // and must observe the write above. + let val = unsafe { VAL.get().read() }; + assert_eq!(val, 42); + return true; + } + } + + false +} + +fn main() { + let mut success = false; + while !success { + let t1 = thread::spawn(thread1); + let t2 = thread::spawn(thread2); + t1.join().unwrap(); + success = t2.join().unwrap(); + + // Reset everything. + ADDR.store(0, Relaxed); + unsafe { + VAL.get().write(0); + } + } +} diff --git a/tests/pass/concurrency/disable_data_race_detector.rs b/tests/pass/concurrency/disable_data_race_detector.rs index 049b5e7f49..354a4bef93 100644 --- a/tests/pass/concurrency/disable_data_race_detector.rs +++ b/tests/pass/concurrency/disable_data_race_detector.rs @@ -1,4 +1,6 @@ //@compile-flags: -Zmiri-disable-data-race-detector +// Avoid non-determinism +//@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-address-reuse-cross-thread-rate=0 use std::thread::spawn; diff --git a/tests/pass/concurrency/thread_name.rs b/tests/pass/concurrency/threadname.rs similarity index 100% rename from tests/pass/concurrency/thread_name.rs rename to tests/pass/concurrency/threadname.rs diff --git a/tests/pass/const-addrs.rs b/tests/pass/const-addrs.rs index 6c14f0b679..727c67ebfb 100644 --- a/tests/pass/const-addrs.rs +++ b/tests/pass/const-addrs.rs @@ -4,7 +4,7 @@ // deallocated. // In Miri we explicitly store previously-assigned AllocIds for each const and ensure // that we only hand out a finite number of AllocIds per const. -// MIR inlining will put every evaluation of the const we're repeatedly evaluting into the same +// MIR inlining will put every evaluation of the const we're repeatedly evaluating into the same // stack frame, breaking this test. //@compile-flags: -Zinline-mir=no #![feature(strict_provenance)] diff --git a/tests/pass/coroutine.rs b/tests/pass/coroutine.rs index 7e1f64df04..7822c136d9 100644 --- a/tests/pass/coroutine.rs +++ b/tests/pass/coroutine.rs @@ -1,6 +1,6 @@ //@revisions: stack tree //@[tree]compile-flags: -Zmiri-tree-borrows -#![feature(coroutines, coroutine_trait, never_type)] +#![feature(coroutines, coroutine_trait, never_type, stmt_expr_attributes)] use std::fmt::Debug; use std::mem::ManuallyDrop; @@ -43,94 +43,144 @@ fn basic() { panic!() } - finish(1, false, || yield 1); + finish( + 1, + false, + #[coroutine] + || yield 1, + ); - finish(3, false, || { - let mut x = 0; - yield 1; - x += 1; - yield 1; - x += 1; - yield 1; - assert_eq!(x, 2); - }); + finish( + 3, + false, + #[coroutine] + || { + let mut x = 0; + yield 1; + x += 1; + yield 1; + x += 1; + yield 1; + assert_eq!(x, 2); + }, + ); - finish(7 * 8 / 2, false, || { - for i in 0..8 { - yield i; - } - }); + finish( + 7 * 8 / 2, + false, + #[coroutine] + || { + for i in 0..8 { + yield i; + } + }, + ); - finish(1, false, || { - if true { - yield 1; - } else { - } - }); + finish( + 1, + false, + #[coroutine] + || { + if true { + yield 1; + } else { + } + }, + ); - finish(1, false, || { - if false { - } else { - yield 1; - } - }); + finish( + 1, + false, + #[coroutine] + || { + if false { + } else { + yield 1; + } + }, + ); - finish(2, false, || { - if { - yield 1; - false - } { + finish( + 2, + false, + #[coroutine] + || { + if { + yield 1; + false + } { + yield 1; + panic!() + } yield 1; - panic!() - } - yield 1; - }); + }, + ); // also test self-referential coroutines assert_eq!( - finish(5, true, static || { - let mut x = 5; - let y = &mut x; - *y = 5; - yield *y; - *y = 10; - x - }), + finish( + 5, + true, + #[coroutine] + static || { + let mut x = 5; + let y = &mut x; + *y = 5; + yield *y; + *y = 10; + x + } + ), 10 ); assert_eq!( - finish(5, true, || { - let mut x = Box::new(5); - let y = &mut *x; - *y = 5; - yield *y; - *y = 10; - *x - }), + finish( + 5, + true, + #[coroutine] + || { + let mut x = Box::new(5); + let y = &mut *x; + *y = 5; + yield *y; + *y = 10; + *x + } + ), 10 ); let b = true; - finish(1, false, || { - yield 1; - if b { - return; - } - #[allow(unused)] - let x = never(); - #[allow(unreachable_code)] - yield 2; - drop(x); - }); - - finish(3, false, || { - yield 1; - #[allow(unreachable_code)] - let _x: (String, !) = (String::new(), { + finish( + 1, + false, + #[coroutine] + || { + yield 1; + if b { + return; + } + #[allow(unused)] + let x = never(); + #[allow(unreachable_code)] yield 2; - return; - }); - }); + drop(x); + }, + ); + + finish( + 3, + false, + #[coroutine] + || { + yield 1; + #[allow(unreachable_code)] + let _x: (String, !) = (String::new(), { + yield 2; + return; + }); + }, + ); } fn smoke_resume_arg() { @@ -172,7 +222,8 @@ fn smoke_resume_arg() { } drain( - &mut |mut b| { + &mut #[coroutine] + |mut b| { while b != 0 { b = yield (b + 1); } @@ -181,21 +232,35 @@ fn smoke_resume_arg() { vec![(1, Yielded(2)), (-45, Yielded(-44)), (500, Yielded(501)), (0, Complete(-1))], ); - expect_drops(2, || drain(&mut |a| yield a, vec![(DropMe, Yielded(DropMe))])); + expect_drops(2, || { + drain( + &mut #[coroutine] + |a| yield a, + vec![(DropMe, Yielded(DropMe))], + ) + }); expect_drops(6, || { drain( - &mut |a| yield yield a, + &mut #[coroutine] + |a| yield yield a, vec![(DropMe, Yielded(DropMe)), (DropMe, Yielded(DropMe)), (DropMe, Complete(DropMe))], ) }); #[allow(unreachable_code)] - expect_drops(2, || drain(&mut |a| yield return a, vec![(DropMe, Complete(DropMe))])); + expect_drops(2, || { + drain( + &mut #[coroutine] + |a| yield return a, + vec![(DropMe, Complete(DropMe))], + ) + }); expect_drops(2, || { drain( - &mut |a: DropMe| { + &mut #[coroutine] + |a: DropMe| { if false { yield () } else { a } }, vec![(DropMe, Complete(DropMe))], @@ -205,7 +270,8 @@ fn smoke_resume_arg() { expect_drops(4, || { drain( #[allow(unused_assignments, unused_variables)] - &mut |mut a: DropMe| { + &mut #[coroutine] + |mut a: DropMe| { a = yield; a = yield; a = yield; @@ -228,7 +294,8 @@ fn uninit_fields() { } fn run(x: bool, y: bool) { - let mut c = || { + let mut c = #[coroutine] + || { if x { let _a: T; if y { diff --git a/tests/pass/drop_type_without_drop_glue.rs b/tests/pass/drop_type_without_drop_glue.rs new file mode 100644 index 0000000000..43ddc8a4d8 --- /dev/null +++ b/tests/pass/drop_type_without_drop_glue.rs @@ -0,0 +1,21 @@ +#![feature(custom_mir, core_intrinsics, strict_provenance)] +use std::intrinsics::mir::*; + +// The `Drop` terminator on a type with no drop glue should be a NOP. + +#[custom_mir(dialect = "runtime", phase = "optimized")] +fn drop_in_place_with_terminator(ptr: *mut i32) { + mir! { + { + Drop(*ptr, ReturnTo(after_call), UnwindContinue()) + } + after_call = { + Return() + } + } +} + +pub fn main() { + drop_in_place_with_terminator(std::ptr::without_provenance_mut(0)); + drop_in_place_with_terminator(std::ptr::without_provenance_mut(1)); +} diff --git a/tests/pass/dyn-upcast.rs b/tests/pass/dyn-upcast.rs index 529b9c471d..ff995f3819 100644 --- a/tests/pass/dyn-upcast.rs +++ b/tests/pass/dyn-upcast.rs @@ -1,24 +1,26 @@ #![feature(trait_upcasting)] #![allow(incomplete_features)] +use std::fmt; + fn main() { basic(); diamond(); struct_(); replace_vptr(); - vtable_mismatch_nop_cast(); + vtable_nop_cast(); } -fn vtable_mismatch_nop_cast() { - let ptr: &dyn std::fmt::Display = &0; - // Even though the vtable is for the wrong trait, this cast doesn't actually change the needed - // vtable so it should still be allowed. - let ptr: *const (dyn std::fmt::Debug + Send + Sync) = unsafe { std::mem::transmute(ptr) }; - let _ptr2 = ptr as *const dyn std::fmt::Debug; +fn vtable_nop_cast() { + let ptr: &dyn fmt::Debug = &0; + // We transmute things around, but the principal trait does not change, so this is allowed. + let ptr: *const (dyn fmt::Debug + Send + Sync) = unsafe { std::mem::transmute(ptr) }; + // This cast is a NOP and should be allowed. + let _ptr2 = ptr as *const dyn fmt::Debug; } fn basic() { - trait Foo: PartialEq + std::fmt::Debug + Send + Sync { + trait Foo: PartialEq + fmt::Debug + Send + Sync { fn a(&self) -> i32 { 10 } @@ -67,7 +69,7 @@ fn basic() { } let baz: &dyn Baz = &1; - let _: &dyn std::fmt::Debug = baz; + let _up: &dyn fmt::Debug = baz; assert_eq!(*baz, 1); assert_eq!(baz.a(), 100); assert_eq!(baz.b(), 200); @@ -77,7 +79,7 @@ fn basic() { assert_eq!(baz.w(), 21); let bar: &dyn Bar = baz; - let _: &dyn std::fmt::Debug = bar; + let _up: &dyn fmt::Debug = bar; assert_eq!(*bar, 1); assert_eq!(bar.a(), 100); assert_eq!(bar.b(), 200); @@ -86,14 +88,14 @@ fn basic() { assert_eq!(bar.w(), 21); let foo: &dyn Foo = baz; - let _: &dyn std::fmt::Debug = foo; + let _up: &dyn fmt::Debug = foo; assert_eq!(*foo, 1); assert_eq!(foo.a(), 100); assert_eq!(foo.z(), 11); assert_eq!(foo.y(), 12); let foo: &dyn Foo = bar; - let _: &dyn std::fmt::Debug = foo; + let _up: &dyn fmt::Debug = foo; assert_eq!(*foo, 1); assert_eq!(foo.a(), 100); assert_eq!(foo.z(), 11); @@ -101,7 +103,7 @@ fn basic() { } fn diamond() { - trait Foo: PartialEq + std::fmt::Debug + Send + Sync { + trait Foo: PartialEq + fmt::Debug + Send + Sync { fn a(&self) -> i32 { 10 } @@ -166,7 +168,7 @@ fn diamond() { } let baz: &dyn Baz = &1; - let _: &dyn std::fmt::Debug = baz; + let _up: &dyn fmt::Debug = baz; assert_eq!(*baz, 1); assert_eq!(baz.a(), 100); assert_eq!(baz.b(), 200); @@ -178,7 +180,7 @@ fn diamond() { assert_eq!(baz.v(), 31); let bar1: &dyn Bar1 = baz; - let _: &dyn std::fmt::Debug = bar1; + let _up: &dyn fmt::Debug = bar1; assert_eq!(*bar1, 1); assert_eq!(bar1.a(), 100); assert_eq!(bar1.b(), 200); @@ -187,7 +189,7 @@ fn diamond() { assert_eq!(bar1.w(), 21); let bar2: &dyn Bar2 = baz; - let _: &dyn std::fmt::Debug = bar2; + let _up: &dyn fmt::Debug = bar2; assert_eq!(*bar2, 1); assert_eq!(bar2.a(), 100); assert_eq!(bar2.c(), 300); @@ -196,17 +198,17 @@ fn diamond() { assert_eq!(bar2.v(), 31); let foo: &dyn Foo = baz; - let _: &dyn std::fmt::Debug = foo; + let _up: &dyn fmt::Debug = foo; assert_eq!(*foo, 1); assert_eq!(foo.a(), 100); let foo: &dyn Foo = bar1; - let _: &dyn std::fmt::Debug = foo; + let _up: &dyn fmt::Debug = foo; assert_eq!(*foo, 1); assert_eq!(foo.a(), 100); let foo: &dyn Foo = bar2; - let _: &dyn std::fmt::Debug = foo; + let _up: &dyn fmt::Debug = foo; assert_eq!(*foo, 1); assert_eq!(foo.a(), 100); } @@ -215,7 +217,7 @@ fn struct_() { use std::rc::Rc; use std::sync::Arc; - trait Foo: PartialEq + std::fmt::Debug + Send + Sync { + trait Foo: PartialEq + fmt::Debug + Send + Sync { fn a(&self) -> i32 { 10 } diff --git a/tests/pass/float.rs b/tests/pass/float.rs index 1bb44d56bf..8aea9b3e6f 100644 --- a/tests/pass/float.rs +++ b/tests/pass/float.rs @@ -1,5 +1,6 @@ #![feature(stmt_expr_attributes)] #![feature(float_gamma)] +#![feature(core_intrinsics)] #![allow(arithmetic_overflow)] use std::fmt::Debug; @@ -22,6 +23,8 @@ fn main() { rounding(); mul_add(); libm(); + test_fast(); + test_algebraic(); } // Helper function to avoid promotion so that this tests "run-time" casts, not CTFE. @@ -751,3 +754,67 @@ pub fn libm() { assert_approx_eq!(val, (2.0 * f64::consts::PI.sqrt()).ln()); assert_eq!(sign, -1); } + +fn test_fast() { + use std::intrinsics::{fadd_fast, fdiv_fast, fmul_fast, frem_fast, fsub_fast}; + + #[inline(never)] + pub fn test_operations_f64(a: f64, b: f64) { + // make sure they all map to the correct operation + unsafe { + assert_eq!(fadd_fast(a, b), a + b); + assert_eq!(fsub_fast(a, b), a - b); + assert_eq!(fmul_fast(a, b), a * b); + assert_eq!(fdiv_fast(a, b), a / b); + assert_eq!(frem_fast(a, b), a % b); + } + } + + #[inline(never)] + pub fn test_operations_f32(a: f32, b: f32) { + // make sure they all map to the correct operation + unsafe { + assert_eq!(fadd_fast(a, b), a + b); + assert_eq!(fsub_fast(a, b), a - b); + assert_eq!(fmul_fast(a, b), a * b); + assert_eq!(fdiv_fast(a, b), a / b); + assert_eq!(frem_fast(a, b), a % b); + } + } + + test_operations_f64(1., 2.); + test_operations_f64(10., 5.); + test_operations_f32(11., 2.); + test_operations_f32(10., 15.); +} + +fn test_algebraic() { + use std::intrinsics::{ + fadd_algebraic, fdiv_algebraic, fmul_algebraic, frem_algebraic, fsub_algebraic, + }; + + #[inline(never)] + pub fn test_operations_f64(a: f64, b: f64) { + // make sure they all map to the correct operation + assert_eq!(fadd_algebraic(a, b), a + b); + assert_eq!(fsub_algebraic(a, b), a - b); + assert_eq!(fmul_algebraic(a, b), a * b); + assert_eq!(fdiv_algebraic(a, b), a / b); + assert_eq!(frem_algebraic(a, b), a % b); + } + + #[inline(never)] + pub fn test_operations_f32(a: f32, b: f32) { + // make sure they all map to the correct operation + assert_eq!(fadd_algebraic(a, b), a + b); + assert_eq!(fsub_algebraic(a, b), a - b); + assert_eq!(fmul_algebraic(a, b), a * b); + assert_eq!(fdiv_algebraic(a, b), a / b); + assert_eq!(frem_algebraic(a, b), a % b); + } + + test_operations_f64(1., 2.); + test_operations_f64(10., 5.); + test_operations_f32(11., 2.); + test_operations_f32(10., 15.); +} diff --git a/tests/pass/float_fast_math.rs b/tests/pass/float_fast_math.rs deleted file mode 100644 index 52d985667d..0000000000 --- a/tests/pass/float_fast_math.rs +++ /dev/null @@ -1,34 +0,0 @@ -#![feature(core_intrinsics)] - -use std::intrinsics::{fadd_fast, fdiv_fast, fmul_fast, frem_fast, fsub_fast}; - -#[inline(never)] -pub fn test_operations_f64(a: f64, b: f64) { - // make sure they all map to the correct operation - unsafe { - assert_eq!(fadd_fast(a, b), a + b); - assert_eq!(fsub_fast(a, b), a - b); - assert_eq!(fmul_fast(a, b), a * b); - assert_eq!(fdiv_fast(a, b), a / b); - assert_eq!(frem_fast(a, b), a % b); - } -} - -#[inline(never)] -pub fn test_operations_f32(a: f32, b: f32) { - // make sure they all map to the correct operation - unsafe { - assert_eq!(fadd_fast(a, b), a + b); - assert_eq!(fsub_fast(a, b), a - b); - assert_eq!(fmul_fast(a, b), a * b); - assert_eq!(fdiv_fast(a, b), a / b); - assert_eq!(frem_fast(a, b), a % b); - } -} - -fn main() { - test_operations_f64(1., 2.); - test_operations_f64(10., 5.); - test_operations_f32(11., 2.); - test_operations_f32(10., 15.); -} diff --git a/tests/pass/function_calls/abi_compat.rs b/tests/pass/function_calls/abi_compat.rs index 14fd2d333d..136660a305 100644 --- a/tests/pass/function_calls/abi_compat.rs +++ b/tests/pass/function_calls/abi_compat.rs @@ -70,7 +70,7 @@ fn main() { test_abi_compat(0usize, 0u64); test_abi_compat(0isize, 0i64); } - test_abi_compat(42u32, num::NonZeroU32::new(1).unwrap()); + test_abi_compat(42u32, num::NonZero::new(1u32).unwrap()); // - `char` and `u32`. test_abi_compat(42u32, 'x'); // - Reference/pointer types with the same pointee. @@ -86,9 +86,9 @@ fn main() { // - Guaranteed null-pointer-optimizations (RFC 3391). test_abi_compat(&0u32 as *const u32, Some(&0u32)); test_abi_compat(main as fn(), Some(main as fn())); - test_abi_compat(0u32, Some(num::NonZeroU32::new(1).unwrap())); + test_abi_compat(0u32, Some(num::NonZero::new(1u32).unwrap())); test_abi_compat(&0u32 as *const u32, Some(Wrapper(&0u32))); - test_abi_compat(0u32, Some(Wrapper(num::NonZeroU32::new(1).unwrap()))); + test_abi_compat(0u32, Some(Wrapper(num::NonZero::new(1u32).unwrap()))); // These must work for *any* type, since we guarantee that `repr(transparent)` is ABI-compatible // with the wrapped field. @@ -102,7 +102,7 @@ fn main() { test_abi_newtype::<[u32; 2]>(); test_abi_newtype::<[u32; 32]>(); test_abi_newtype::>(); - test_abi_newtype::>(); + test_abi_newtype::>>(); // Extra test for assumptions made by arbitrary-self-dyn-receivers. // This is interesting since these types are not `repr(transparent)`. So this is not part of our diff --git a/tests/pass/hello.rs b/tests/pass/hello.rs index e7a11a969c..2a02a7a9e2 100644 --- a/tests/pass/hello.rs +++ b/tests/pass/hello.rs @@ -1,3 +1,4 @@ fn main() { println!("Hello, world!"); + eprintln!("Hello, error!"); } diff --git a/tests/pass/hello.stderr b/tests/pass/hello.stderr new file mode 100644 index 0000000000..445a1bc8fa --- /dev/null +++ b/tests/pass/hello.stderr @@ -0,0 +1 @@ +Hello, error! diff --git a/tests/pass/integer-ops.rs b/tests/pass/integer-ops.rs index 0ec1f8e9c6..3f8ac34e7d 100644 --- a/tests/pass/integer-ops.rs +++ b/tests/pass/integer-ops.rs @@ -1,7 +1,64 @@ //@compile-flags: -Coverflow-checks=off #![allow(arithmetic_overflow)] +fn basic() { + fn ret() -> i64 { + 1 + } + + fn neg() -> i64 { + -1 + } + + fn add() -> i64 { + 1 + 2 + } + + fn indirect_add() -> i64 { + let x = 1; + let y = 2; + x + y + } + + fn arith() -> i32 { + 3 * 3 + 4 * 4 + } + + fn match_int() -> i16 { + let n = 2; + match n { + 0 => 0, + 1 => 10, + 2 => 20, + 3 => 30, + _ => 100, + } + } + + fn match_int_range() -> i64 { + let n = 42; + match n { + 0..=9 => 0, + 10..=19 => 1, + 20..=29 => 2, + 30..=39 => 3, + 40..=42 => 4, + _ => 5, + } + } + + assert_eq!(ret(), 1); + assert_eq!(neg(), -1); + assert_eq!(add(), 3); + assert_eq!(indirect_add(), 3); + assert_eq!(arith(), 5 * 5); + assert_eq!(match_int(), 20); + assert_eq!(match_int_range(), 4); +} + pub fn main() { + basic(); + // This tests that we do (not) do sign extension properly when loading integers assert_eq!(u32::MAX as i64, 4294967295); assert_eq!(i32::MIN as i64, -2147483648); @@ -152,6 +209,10 @@ pub fn main() { assert_eq!(5i32.overflowing_mul(2), (10, false)); assert_eq!(1_000_000_000i32.overflowing_mul(10), (1410065408, true)); + assert_eq!(i64::MIN.overflowing_mul(-1), (i64::MIN, true)); + assert_eq!(i32::MIN.overflowing_mul(-1), (i32::MIN, true)); + assert_eq!(i16::MIN.overflowing_mul(-1), (i16::MIN, true)); + assert_eq!(i8::MIN.overflowing_mul(-1), (i8::MIN, true)); assert_eq!(5i32.overflowing_div(2), (2, false)); assert_eq!(i32::MIN.overflowing_div(-1), (i32::MIN, true)); diff --git a/tests/pass/intrinsics-integer.rs b/tests/pass/intrinsics/integer.rs similarity index 100% rename from tests/pass/intrinsics-integer.rs rename to tests/pass/intrinsics/integer.rs diff --git a/tests/pass/intrinsics.rs b/tests/pass/intrinsics/intrinsics.rs similarity index 100% rename from tests/pass/intrinsics.rs rename to tests/pass/intrinsics/intrinsics.rs diff --git a/tests/pass/portable-simd-ptrs.rs b/tests/pass/intrinsics/portable-simd-ptrs.rs similarity index 96% rename from tests/pass/portable-simd-ptrs.rs rename to tests/pass/intrinsics/portable-simd-ptrs.rs index 3b2d221bd8..096ec78da1 100644 --- a/tests/pass/portable-simd-ptrs.rs +++ b/tests/pass/intrinsics/portable-simd-ptrs.rs @@ -7,6 +7,6 @@ use std::simd::prelude::*; fn main() { // Pointer casts let _val: Simd<*const u8, 4> = Simd::<*const i32, 4>::splat(ptr::null()).cast(); - let addrs = Simd::<*const i32, 4>::splat(ptr::null()).expose_addr(); + let addrs = Simd::<*const i32, 4>::splat(ptr::null()).expose_provenance(); let _ptrs = Simd::<*const i32, 4>::with_exposed_provenance(addrs); } diff --git a/tests/pass/portable-simd.rs b/tests/pass/intrinsics/portable-simd.rs similarity index 99% rename from tests/pass/portable-simd.rs rename to tests/pass/intrinsics/portable-simd.rs index cdb441b450..1fc713d48d 100644 --- a/tests/pass/portable-simd.rs +++ b/tests/pass/intrinsics/portable-simd.rs @@ -1,5 +1,5 @@ //@compile-flags: -Zmiri-strict-provenance -#![feature(portable_simd, adt_const_params, inline_const, core_intrinsics)] +#![feature(portable_simd, adt_const_params, core_intrinsics)] #![allow(incomplete_features, internal_features)] use std::intrinsics::simd as intrinsics; use std::ptr; diff --git a/tests/pass/volatile.rs b/tests/pass/intrinsics/volatile.rs similarity index 100% rename from tests/pass/volatile.rs rename to tests/pass/intrinsics/volatile.rs diff --git a/tests/pass/ints.rs b/tests/pass/ints.rs deleted file mode 100644 index c04c6921f3..0000000000 --- a/tests/pass/ints.rs +++ /dev/null @@ -1,58 +0,0 @@ -fn ret() -> i64 { - 1 -} - -fn neg() -> i64 { - -1 -} - -fn add() -> i64 { - 1 + 2 -} - -fn indirect_add() -> i64 { - let x = 1; - let y = 2; - x + y -} - -fn arith() -> i32 { - 3 * 3 + 4 * 4 -} - -fn match_int() -> i16 { - let n = 2; - match n { - 0 => 0, - 1 => 10, - 2 => 20, - 3 => 30, - _ => 100, - } -} - -fn match_int_range() -> i64 { - let n = 42; - match n { - 0..=9 => 0, - 10..=19 => 1, - 20..=29 => 2, - 30..=39 => 3, - 40..=42 => 4, - _ => 5, - } -} - -fn main() { - assert_eq!(ret(), 1); - assert_eq!(neg(), -1); - assert_eq!(add(), 3); - assert_eq!(indirect_add(), 3); - assert_eq!(arith(), 5 * 5); - assert_eq!(match_int(), 20); - assert_eq!(match_int_range(), 4); - assert_eq!(i64::MIN.overflowing_mul(-1), (i64::MIN, true)); - assert_eq!(i32::MIN.overflowing_mul(-1), (i32::MIN, true)); - assert_eq!(i16::MIN.overflowing_mul(-1), (i16::MIN, true)); - assert_eq!(i8::MIN.overflowing_mul(-1), (i8::MIN, true)); -} diff --git a/tests/pass/issues/issue-miri-1909.rs b/tests/pass/issues/issue-miri-1909.rs index ce2114e760..8a2e67cdd0 100644 --- a/tests/pass/issues/issue-miri-1909.rs +++ b/tests/pass/issues/issue-miri-1909.rs @@ -9,7 +9,7 @@ use std::alloc::System; /// `ptr` must be valid for writes of `len` bytes unsafe fn volatile_write_zeroize_mem(ptr: *mut u8, len: usize) { for i in 0..len { - // ptr as usize + i can't overlow because `ptr` is valid for writes of `len` + // ptr as usize + i can't overflow because `ptr` is valid for writes of `len` let ptr_new: *mut u8 = ((ptr as usize) + i) as *mut u8; // SAFETY: `ptr` is valid for writes of `len` bytes, so `ptr_new` is valid for a // byte write diff --git a/tests/pass/issues/issue-miri-3473.rs b/tests/pass/issues/issue-miri-3473.rs new file mode 100644 index 0000000000..77b960c1cd --- /dev/null +++ b/tests/pass/issues/issue-miri-3473.rs @@ -0,0 +1,28 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows +use std::cell::UnsafeCell; + +#[repr(C)] +#[derive(Default)] +struct Node { + _meta: UnsafeCell, + value: usize, +} + +impl Node { + fn value(&self) -> &usize { + &self.value + } +} + +/// This used to cause Stacked Borrows errors because of trouble around conversion +/// from Box to raw pointer. +fn main() { + unsafe { + let a = Box::into_raw(Box::new(Node::default())); + let ptr = &*a; + *UnsafeCell::raw_get(a.cast::>()) = 2; + assert_eq!(*ptr.value(), 0); + drop(Box::from_raw(a)); + } +} diff --git a/tests/pass/no_std.rs b/tests/pass/no_std.rs index 3c98ee50aa..fc1c16f5fb 100644 --- a/tests/pass/no_std.rs +++ b/tests/pass/no_std.rs @@ -2,30 +2,14 @@ #![feature(start)] #![no_std] -// Plumbing to let us use `writeln!` to host stdout: - -extern "Rust" { - fn miri_write_to_stdout(bytes: &[u8]); -} - -struct Host; - use core::fmt::Write; -impl Write for Host { - fn write_str(&mut self, s: &str) -> core::fmt::Result { - unsafe { - miri_write_to_stdout(s.as_bytes()); - } - Ok(()) - } -} - -// Aaaand the test: +#[path = "../utils/mod.no_std.rs"] +mod utils; #[start] fn start(_: isize, _: *const *const u8) -> isize { - writeln!(Host, "hello, world!").unwrap(); + writeln!(utils::MiriStdout, "hello, world!").unwrap(); 0 } diff --git a/tests/pass/panic/catch_panic.stderr b/tests/pass/panic/catch_panic.stderr index cbcd626e39..a472a5d80c 100644 --- a/tests/pass/panic/catch_panic.stderr +++ b/tests/pass/panic/catch_panic.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at $DIR/catch_panic.rs:LL:CC: Hello from std::panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect Caught panic message (&str): Hello from std::panic thread 'main' panicked at $DIR/catch_panic.rs:LL:CC: Hello from std::panic: 1 diff --git a/tests/pass/panic/concurrent-panic.stderr b/tests/pass/panic/concurrent-panic.stderr index 0bb3dcd8d2..b2a5cf4922 100644 --- a/tests/pass/panic/concurrent-panic.stderr +++ b/tests/pass/panic/concurrent-panic.stderr @@ -3,6 +3,7 @@ Thread 1 reported it has started thread '' panicked at $DIR/concurrent-panic.rs:LL:CC: panic in thread 2 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect Thread 2 blocking on thread 1 Thread 2 reported it has started Unlocking mutex diff --git a/tests/pass/panic/nested_panic_caught.stderr b/tests/pass/panic/nested_panic_caught.stderr index 4684beb333..3efb4be40f 100644 --- a/tests/pass/panic/nested_panic_caught.stderr +++ b/tests/pass/panic/nested_panic_caught.stderr @@ -1,6 +1,7 @@ thread 'main' panicked at $DIR/nested_panic_caught.rs:LL:CC: once note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect thread 'main' panicked at $DIR/nested_panic_caught.rs:LL:CC: twice stack backtrace: diff --git a/tests/pass/panic/thread_panic.stderr b/tests/pass/panic/thread_panic.stderr index badd409d13..bdfe4f98ba 100644 --- a/tests/pass/panic/thread_panic.stderr +++ b/tests/pass/panic/thread_panic.stderr @@ -1,5 +1,6 @@ thread '' panicked at $DIR/thread_panic.rs:LL:CC: Hello! note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect thread 'childthread' panicked at $DIR/thread_panic.rs:LL:CC: Hello, world! diff --git a/tests/pass/path.rs b/tests/pass/path.rs new file mode 100644 index 0000000000..fe99d38e07 --- /dev/null +++ b/tests/pass/path.rs @@ -0,0 +1,60 @@ +//@compile-flags: -Zmiri-disable-isolation +use std::path::{absolute, Path, PathBuf}; + +#[path = "../utils/mod.rs"] +mod utils; + +#[track_caller] +fn assert_absolute_eq(in_: &str, out: &str) { + assert_eq!(absolute(in_).unwrap().as_os_str(), Path::new(out).as_os_str()); +} + +fn test_absolute() { + if cfg!(unix) { + assert_absolute_eq("/a/b/c", "/a/b/c"); + assert_absolute_eq("/a/b/c", "/a/b/c"); + assert_absolute_eq("/a//b/c", "/a/b/c"); + assert_absolute_eq("//a/b/c", "//a/b/c"); + assert_absolute_eq("///a/b/c", "/a/b/c"); + assert_absolute_eq("/a/b/c/", "/a/b/c/"); + assert_absolute_eq("/a/./b/../c/.././..", "/a/b/../c/../.."); + } else if cfg!(windows) { + // Test that all these are unchanged + assert_absolute_eq(r"C:\path\to\file", r"C:\path\to\file"); + assert_absolute_eq(r"C:\path\to\file\", r"C:\path\to\file\"); + assert_absolute_eq(r"\\server\share\to\file", r"\\server\share\to\file"); + assert_absolute_eq(r"\\server.\share.\to\file", r"\\server.\share.\to\file"); + assert_absolute_eq(r"\\.\PIPE\name", r"\\.\PIPE\name"); + assert_absolute_eq(r"\\.\C:\path\to\COM1", r"\\.\C:\path\to\COM1"); + assert_absolute_eq(r"\\?\C:\path\to\file", r"\\?\C:\path\to\file"); + assert_absolute_eq(r"\\?\UNC\server\share\to\file", r"\\?\UNC\server\share\to\file"); + assert_absolute_eq(r"\\?\PIPE\name", r"\\?\PIPE\name"); + // Verbatim paths are always unchanged, no matter what. + assert_absolute_eq(r"\\?\path.\to/file..", r"\\?\path.\to/file.."); + + assert_absolute_eq(r"C:\path..\to.\file.", r"C:\path..\to\file"); + assert_absolute_eq(r"COM1", r"\\.\COM1"); + } else { + panic!("unsupported OS"); + } +} + +fn buf_smoke(mut p: PathBuf) { + for _c in p.components() {} + + p.push("hello"); + for _c in p.components() {} + + if cfg!(windows) { + p.push(r"C:\mydir"); + } else { + p.push(r"/mydir"); + } + for _c in p.components() {} +} + +fn main() { + buf_smoke(PathBuf::new()); + buf_smoke(utils::tmp()); + test_absolute(); +} diff --git a/tests/pass/ptr_int_from_exposed.rs b/tests/pass/ptr_int_from_exposed.rs index 8555de986f..5690d7865b 100644 --- a/tests/pass/ptr_int_from_exposed.rs +++ b/tests/pass/ptr_int_from_exposed.rs @@ -10,7 +10,7 @@ fn ptr_roundtrip_out_of_bounds() { let x: i32 = 3; let x_ptr = &x as *const i32; - let x_usize = x_ptr.wrapping_offset(128).expose_addr(); + let x_usize = x_ptr.wrapping_offset(128).expose_provenance(); let ptr = ptr::with_exposed_provenance::(x_usize).wrapping_offset(-128); assert_eq!(unsafe { *ptr }, 3); @@ -24,8 +24,8 @@ fn ptr_roundtrip_confusion() { let x_ptr = &x as *const i32; let y_ptr = &y as *const i32; - let x_usize = x_ptr.expose_addr(); - let y_usize = y_ptr.expose_addr(); + let x_usize = x_ptr.expose_provenance(); + let y_usize = y_ptr.expose_provenance(); let ptr = ptr::with_exposed_provenance::(y_usize); let ptr = ptr.with_addr(x_usize); @@ -37,7 +37,7 @@ fn ptr_roundtrip_imperfect() { let x: u8 = 3; let x_ptr = &x as *const u8; - let x_usize = x_ptr.expose_addr() + 128; + let x_usize = x_ptr.expose_provenance() + 128; let ptr = ptr::with_exposed_provenance::(x_usize).wrapping_offset(-128); assert_eq!(unsafe { *ptr }, 3); @@ -48,7 +48,7 @@ fn ptr_roundtrip_null() { let x = &42; let x_ptr = x as *const i32; let x_null_ptr = x_ptr.with_addr(0); // addr 0, but still the provenance of x - let null = x_null_ptr.expose_addr(); + let null = x_null_ptr.expose_provenance(); assert_eq!(null, 0); let x_null_ptr_copy = ptr::with_exposed_provenance::(null); // just a roundtrip, so has provenance of x (angelically) diff --git a/tests/pass/shims/available-parallelism-miri-num-cpus.rs b/tests/pass/shims/available-parallelism-miri-num-cpus.rs new file mode 100644 index 0000000000..0d96bc2f5e --- /dev/null +++ b/tests/pass/shims/available-parallelism-miri-num-cpus.rs @@ -0,0 +1,8 @@ +//@compile-flags: -Zmiri-num-cpus=1024 + +use std::num::NonZero; +use std::thread::available_parallelism; + +fn main() { + assert_eq!(available_parallelism().unwrap(), NonZero::new(1024).unwrap()); +} diff --git a/tests/pass/available-parallelism.rs b/tests/pass/shims/available-parallelism.rs similarity index 100% rename from tests/pass/available-parallelism.rs rename to tests/pass/shims/available-parallelism.rs diff --git a/tests/pass/shims/env/home.rs b/tests/pass/shims/env/home.rs index 9eb9c3af56..c237f9ed9f 100644 --- a/tests/pass/shims/env/home.rs +++ b/tests/pass/shims/env/home.rs @@ -1,9 +1,9 @@ -//@ignore-target-windows: home_dir is not supported on Windows //@compile-flags: -Zmiri-disable-isolation use std::env; fn main() { env::remove_var("HOME"); // make sure we enter the interesting codepath + env::remove_var("USERPROFILE"); // Windows also looks as this env var #[allow(deprecated)] env::home_dir().unwrap(); } diff --git a/tests/pass/shims/env/var-set.rs b/tests/pass/shims/env/var-set.rs new file mode 100644 index 0000000000..2875b6c815 --- /dev/null +++ b/tests/pass/shims/env/var-set.rs @@ -0,0 +1,7 @@ +// Test a value set on the host (MIRI_ENV_VAR_TEST) and one that is not. +//@compile-flags: -Zmiri-env-set=MIRI_ENV_VAR_TEST=test_value_1 -Zmiri-env-set=TEST_VAR_2=test_value_2 + +fn main() { + assert_eq!(std::env::var("MIRI_ENV_VAR_TEST"), Ok("test_value_1".to_owned())); + assert_eq!(std::env::var("TEST_VAR_2"), Ok("test_value_2".to_owned())); +} diff --git a/tests/pass/shims/env/var.rs b/tests/pass/shims/env/var.rs index 23a3724ff7..babaf00578 100644 --- a/tests/pass/shims/env/var.rs +++ b/tests/pass/shims/env/var.rs @@ -1,4 +1,5 @@ use std::env; +use std::thread; fn main() { // Test that miri environment is isolated when communication is disabled. @@ -23,4 +24,11 @@ fn main() { env::remove_var("MIRI_TEST"); assert_eq!(env::var("MIRI_TEST"), Err(env::VarError::NotPresent)); println!("{:#?}", env::vars().collect::>()); + + // Do things concurrently, to make sure there's no data race. + let t = thread::spawn(|| { + env::set_var("MIRI_TEST", "42"); + }); + env::set_var("MIRI_TEST", "42"); + t.join().unwrap(); } diff --git a/tests/pass/shims/fs-symlink.rs b/tests/pass/shims/fs-symlink.rs new file mode 100644 index 0000000000..4d5103b24c --- /dev/null +++ b/tests/pass/shims/fs-symlink.rs @@ -0,0 +1,50 @@ +// Symlink tests are separate since they don't in general work on a Windows host. +//@ignore-host-windows: creating symlinks requires admin permissions on Windows +//@ignore-target-windows: File handling is not implemented yet +//@compile-flags: -Zmiri-disable-isolation + +use std::fs::{read_link, remove_file, File}; +use std::io::{Read, Result}; +use std::path::Path; + +#[path = "../../utils/mod.rs"] +mod utils; + +fn check_metadata(bytes: &[u8], path: &Path) -> Result<()> { + // Test that the file metadata is correct. + let metadata = path.metadata()?; + // `path` should point to a file. + assert!(metadata.is_file()); + // The size of the file must be equal to the number of written bytes. + assert_eq!(bytes.len() as u64, metadata.len()); + Ok(()) +} + +fn main() { + let bytes = b"Hello, World!\n"; + let path = utils::prepare_with_content("miri_test_fs_link_target.txt", bytes); + let symlink_path = utils::prepare("miri_test_fs_symlink.txt"); + + // Creating a symbolic link should succeed. + #[cfg(unix)] + std::os::unix::fs::symlink(&path, &symlink_path).unwrap(); + #[cfg(windows)] + std::os::windows::fs::symlink_file(&path, &symlink_path).unwrap(); + // Test that the symbolic link has the same contents as the file. + let mut symlink_file = File::open(&symlink_path).unwrap(); + let mut contents = Vec::new(); + symlink_file.read_to_end(&mut contents).unwrap(); + assert_eq!(bytes, contents.as_slice()); + + // Test that metadata of a symbolic link (i.e., the file it points to) is correct. + check_metadata(bytes, &symlink_path).unwrap(); + // Test that the metadata of a symbolic link is correct when not following it. + assert!(symlink_path.symlink_metadata().unwrap().file_type().is_symlink()); + // Check that we can follow the link. + assert_eq!(read_link(&symlink_path).unwrap(), path); + // Removing symbolic link should succeed. + remove_file(&symlink_path).unwrap(); + + // Removing file should succeed. + remove_file(&path).unwrap(); +} diff --git a/tests/pass/shims/fs.rs b/tests/pass/shims/fs.rs index d10faebac7..35980fad15 100644 --- a/tests/pass/shims/fs.rs +++ b/tests/pass/shims/fs.rs @@ -1,21 +1,17 @@ //@ignore-target-windows: File handling is not implemented yet //@compile-flags: -Zmiri-disable-isolation -// If this test is failing for you locally, you can try -// 1. Deleting the files `/tmp/miri_*` -// 2. Setting `MIRI_TEMP` or `TMPDIR` to a different directory, without the `miri_*` files - #![feature(io_error_more)] #![feature(io_error_uncategorized)] use std::collections::HashMap; use std::ffi::OsString; use std::fs::{ - canonicalize, create_dir, read_dir, read_link, remove_dir, remove_dir_all, remove_file, rename, - File, OpenOptions, + canonicalize, create_dir, read_dir, remove_dir, remove_dir_all, remove_file, rename, File, + OpenOptions, }; use std::io::{Error, ErrorKind, IsTerminal, Read, Result, Seek, SeekFrom, Write}; -use std::path::{Path, PathBuf}; +use std::path::Path; #[path = "../../utils/mod.rs"] mod utils; @@ -29,7 +25,6 @@ fn main() { test_metadata(); test_file_set_len(); test_file_sync(); - test_symlink(); test_errors(); test_rename(); test_directory(); @@ -37,30 +32,6 @@ fn main() { test_from_raw_os_error(); } -/// Prepare: compute filename and make sure the file does not exist. -fn prepare(filename: &str) -> PathBuf { - let path = utils::tmp().join(filename); - // Clean the paths for robustness. - remove_file(&path).ok(); - path -} - -/// Prepare directory: compute directory name and make sure it does not exist. -fn prepare_dir(dirname: &str) -> PathBuf { - let path = utils::tmp().join(&dirname); - // Clean the directory for robustness. - remove_dir_all(&path).ok(); - path -} - -/// Prepare like above, and also write some initial content to the file. -fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf { - let path = prepare(filename); - let mut file = File::create(&path).unwrap(); - file.write(content).unwrap(); - path -} - fn test_path_conversion() { let tmp = utils::tmp(); assert!(tmp.is_absolute(), "{:?} is not absolute", tmp); @@ -69,7 +40,7 @@ fn test_path_conversion() { fn test_file() { let bytes = b"Hello, World!\n"; - let path = prepare("miri_test_fs_file.txt"); + let path = utils::prepare("miri_test_fs_file.txt"); // Test creating, writing and closing a file (closing is tested when `file` is dropped). let mut file = File::create(&path).unwrap(); @@ -96,7 +67,7 @@ fn test_file() { fn test_file_clone() { let bytes = b"Hello, World!\n"; - let path = prepare_with_content("miri_test_fs_file_clone.txt", bytes); + let path = utils::prepare_with_content("miri_test_fs_file_clone.txt", bytes); // Cloning a file should be successful. let file = File::open(&path).unwrap(); @@ -111,7 +82,7 @@ fn test_file_clone() { } fn test_file_create_new() { - let path = prepare("miri_test_fs_file_create_new.txt"); + let path = utils::prepare("miri_test_fs_file_create_new.txt"); // Creating a new file that doesn't yet exist should succeed. OpenOptions::new().write(true).create_new(true).open(&path).unwrap(); @@ -129,7 +100,7 @@ fn test_file_create_new() { fn test_seek() { let bytes = b"Hello, entire World!\n"; - let path = prepare_with_content("miri_test_fs_seek.txt", bytes); + let path = utils::prepare_with_content("miri_test_fs_seek.txt", bytes); let mut file = File::open(&path).unwrap(); let mut contents = Vec::new(); @@ -168,7 +139,7 @@ fn check_metadata(bytes: &[u8], path: &Path) -> Result<()> { fn test_metadata() { let bytes = b"Hello, meta-World!\n"; - let path = prepare_with_content("miri_test_fs_metadata.txt", bytes); + let path = utils::prepare_with_content("miri_test_fs_metadata.txt", bytes); // Test that metadata of an absolute path is correct. check_metadata(bytes, &path).unwrap(); @@ -182,7 +153,7 @@ fn test_metadata() { fn test_file_set_len() { let bytes = b"Hello, World!\n"; - let path = prepare_with_content("miri_test_fs_set_len.txt", bytes); + let path = utils::prepare_with_content("miri_test_fs_set_len.txt", bytes); // Test extending the file let mut file = OpenOptions::new().read(true).write(true).open(&path).unwrap(); @@ -208,7 +179,7 @@ fn test_file_set_len() { fn test_file_sync() { let bytes = b"Hello, World!\n"; - let path = prepare_with_content("miri_test_fs_sync.txt", bytes); + let path = utils::prepare_with_content("miri_test_fs_sync.txt", bytes); // Test that we can call sync_data and sync_all (can't readily test effects of this operation) let file = OpenOptions::new().write(true).open(&path).unwrap(); @@ -223,44 +194,15 @@ fn test_file_sync() { remove_file(&path).unwrap(); } -fn test_symlink() { - let bytes = b"Hello, World!\n"; - let path = prepare_with_content("miri_test_fs_link_target.txt", bytes); - let symlink_path = prepare("miri_test_fs_symlink.txt"); - - // Creating a symbolic link should succeed. - #[cfg(unix)] - std::os::unix::fs::symlink(&path, &symlink_path).unwrap(); - #[cfg(windows)] - std::os::windows::fs::symlink_file(&path, &symlink_path).unwrap(); - // Test that the symbolic link has the same contents as the file. - let mut symlink_file = File::open(&symlink_path).unwrap(); - let mut contents = Vec::new(); - symlink_file.read_to_end(&mut contents).unwrap(); - assert_eq!(bytes, contents.as_slice()); - - // Test that metadata of a symbolic link (i.e., the file it points to) is correct. - check_metadata(bytes, &symlink_path).unwrap(); - // Test that the metadata of a symbolic link is correct when not following it. - assert!(symlink_path.symlink_metadata().unwrap().file_type().is_symlink()); - // Check that we can follow the link. - assert_eq!(read_link(&symlink_path).unwrap(), path); - // Removing symbolic link should succeed. - remove_file(&symlink_path).unwrap(); - - // Removing file should succeed. - remove_file(&path).unwrap(); -} - fn test_errors() { let bytes = b"Hello, World!\n"; - let path = prepare("miri_test_fs_errors.txt"); + let path = utils::prepare("miri_test_fs_errors.txt"); // The following tests also check that the `__errno_location()` shim is working properly. // Opening a non-existing file should fail with a "not found" error. assert_eq!(ErrorKind::NotFound, File::open(&path).unwrap_err().kind()); // Make sure we can also format this. - format!("{0:?}: {0}", File::open(&path).unwrap_err()); + format!("{0}: {0:?}", File::open(&path).unwrap_err()); // Removing a non-existing file should fail with a "not found" error. assert_eq!(ErrorKind::NotFound, remove_file(&path).unwrap_err().kind()); // Reading the metadata of a non-existing file should fail with a "not found" error. @@ -269,8 +211,8 @@ fn test_errors() { fn test_rename() { // Renaming a file should succeed. - let path1 = prepare("miri_test_fs_rename_source.txt"); - let path2 = prepare("miri_test_fs_rename_destination.txt"); + let path1 = utils::prepare("miri_test_fs_rename_source.txt"); + let path2 = utils::prepare("miri_test_fs_rename_destination.txt"); let file = File::create(&path1).unwrap(); drop(file); @@ -289,7 +231,7 @@ fn test_rename() { } fn test_canonicalize() { - let dir_path = prepare_dir("miri_test_fs_dir"); + let dir_path = utils::prepare_dir("miri_test_fs_dir"); create_dir(&dir_path).unwrap(); let path = dir_path.join("test_file"); drop(File::create(&path).unwrap()); @@ -301,7 +243,7 @@ fn test_canonicalize() { } fn test_directory() { - let dir_path = prepare_dir("miri_test_fs_dir"); + let dir_path = utils::prepare_dir("miri_test_fs_dir"); // Creating a directory should succeed. create_dir(&dir_path).unwrap(); // Test that the metadata of a directory is correct. diff --git a/tests/pass/shims/io.rs b/tests/pass/shims/io.rs index 295723957a..d20fc75b79 100644 --- a/tests/pass/shims/io.rs +++ b/tests/pass/shims/io.rs @@ -1,7 +1,19 @@ -use std::io::IsTerminal; +use std::io::{self, IsTerminal}; fn main() { // We can't really assume that this is truly a terminal, and anyway on Windows Miri will always // return `false` here, but we can check that the call succeeds. - std::io::stdout().is_terminal(); + io::stdout().is_terminal(); + + // Ensure we can format `io::Error` created from OS errors + // (calls OS-specific error formatting functions). + let raw_os_error = if cfg!(unix) { + 22 // EINVAL (on most Unixes, anyway) + } else if cfg!(windows) { + 87 // ERROR_INVALID_PARAMETER + } else { + panic!("unsupported OS") + }; + let err = io::Error::from_raw_os_error(raw_os_error); + format!("{err}: {err:?}"); } diff --git a/tests/pass/intrinsics-x86-aes-vaes.rs b/tests/pass/shims/x86/intrinsics-x86-aes-vaes.rs similarity index 100% rename from tests/pass/intrinsics-x86-aes-vaes.rs rename to tests/pass/shims/x86/intrinsics-x86-aes-vaes.rs diff --git a/tests/pass/intrinsics-x86-avx.rs b/tests/pass/shims/x86/intrinsics-x86-avx.rs similarity index 100% rename from tests/pass/intrinsics-x86-avx.rs rename to tests/pass/shims/x86/intrinsics-x86-avx.rs diff --git a/tests/pass/shims/x86/intrinsics-x86-avx2.rs b/tests/pass/shims/x86/intrinsics-x86-avx2.rs new file mode 100644 index 0000000000..80d125bb85 --- /dev/null +++ b/tests/pass/shims/x86/intrinsics-x86-avx2.rs @@ -0,0 +1,1613 @@ +// Ignore everything except x86 and x86_64 +// Any new targets that are added to CI should be ignored here. +// (We cannot use `cfg`-based tricks here since the `target-feature` flags below only work on x86.) +//@ignore-target-aarch64 +//@ignore-target-arm +//@ignore-target-avr +//@ignore-target-s390x +//@ignore-target-thumbv7em +//@ignore-target-wasm32 +//@compile-flags: -C target-feature=+avx2 + +#[cfg(target_arch = "x86")] +use std::arch::x86::*; +#[cfg(target_arch = "x86_64")] +use std::arch::x86_64::*; +use std::mem::transmute; + +fn main() { + assert!(is_x86_feature_detected!("avx2")); + + unsafe { + test_avx2(); + } +} + +#[target_feature(enable = "avx2")] +unsafe fn test_avx2() { + // Mostly copied from library/stdarch/crates/core_arch/src/x86/avx2.rs + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_abs_epi32() { + #[rustfmt::skip] + let a = _mm256_setr_epi32( + 0, 1, -1, i32::MAX, + i32::MIN, 100, -100, -32, + ); + let r = _mm256_abs_epi32(a); + #[rustfmt::skip] + let e = _mm256_setr_epi32( + 0, 1, 1, i32::MAX, + i32::MAX.wrapping_add(1), 100, 100, 32, + ); + assert_eq_m256i(r, e); + } + test_mm256_abs_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_abs_epi16() { + #[rustfmt::skip] + let a = _mm256_setr_epi16( + 0, 1, -1, 2, -2, 3, -3, 4, + -4, 5, -5, i16::MAX, i16::MIN, 100, -100, -32, + ); + let r = _mm256_abs_epi16(a); + #[rustfmt::skip] + let e = _mm256_setr_epi16( + 0, 1, 1, 2, 2, 3, 3, 4, + 4, 5, 5, i16::MAX, i16::MAX.wrapping_add(1), 100, 100, 32, + ); + assert_eq_m256i(r, e); + } + test_mm256_abs_epi16(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_abs_epi8() { + #[rustfmt::skip] + let a = _mm256_setr_epi8( + 0, 1, -1, 2, -2, 3, -3, 4, + -4, 5, -5, i8::MAX, i8::MIN, 100, -100, -32, + 0, 1, -1, 2, -2, 3, -3, 4, + -4, 5, -5, i8::MAX, i8::MIN, 100, -100, -32, + ); + let r = _mm256_abs_epi8(a); + #[rustfmt::skip] + let e = _mm256_setr_epi8( + 0, 1, 1, 2, 2, 3, 3, 4, + 4, 5, 5, i8::MAX, i8::MAX.wrapping_add(1), 100, 100, 32, + 0, 1, 1, 2, 2, 3, 3, 4, + 4, 5, 5, i8::MAX, i8::MAX.wrapping_add(1), 100, 100, 32, + ); + assert_eq_m256i(r, e); + } + test_mm256_abs_epi8(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_hadd_epi16() { + let a = _mm256_set1_epi16(2); + let b = _mm256_set1_epi16(4); + let r = _mm256_hadd_epi16(a, b); + let e = _mm256_setr_epi16(4, 4, 4, 4, 8, 8, 8, 8, 4, 4, 4, 4, 8, 8, 8, 8); + assert_eq_m256i(r, e); + + // Test wrapping on overflow + let a = _mm256_setr_epi16( + i16::MAX, + 1, + i16::MAX, + 2, + i16::MAX, + 3, + i16::MAX, + 4, + i16::MAX, + 5, + i16::MAX, + 6, + i16::MAX, + 7, + i16::MAX, + 8, + ); + let b = _mm256_setr_epi16( + i16::MIN, + -1, + i16::MIN, + -2, + i16::MIN, + -3, + i16::MIN, + -4, + i16::MIN, + -5, + i16::MIN, + -6, + i16::MIN, + -7, + i16::MIN, + -8, + ); + let expected = _mm256_setr_epi16( + i16::MIN, + i16::MIN + 1, + i16::MIN + 2, + i16::MIN + 3, + i16::MAX, + i16::MAX - 1, + i16::MAX - 2, + i16::MAX - 3, + i16::MIN + 4, + i16::MIN + 5, + i16::MIN + 6, + i16::MIN + 7, + i16::MAX - 4, + i16::MAX - 5, + i16::MAX - 6, + i16::MAX - 7, + ); + let r = _mm256_hadd_epi16(a, b); + assert_eq_m256i(r, expected); + } + test_mm256_hadd_epi16(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_hadd_epi32() { + let a = _mm256_set1_epi32(2); + let b = _mm256_set1_epi32(4); + let r = _mm256_hadd_epi32(a, b); + let e = _mm256_setr_epi32(4, 4, 8, 8, 4, 4, 8, 8); + assert_eq_m256i(r, e); + + // Test wrapping on overflow + let a = _mm256_setr_epi32(i32::MAX, 1, i32::MAX, 2, i32::MAX, 3, i32::MAX, 4); + let b = _mm256_setr_epi32(i32::MIN, -1, i32::MIN, -2, i32::MIN, -3, i32::MIN, -4); + let expected = _mm256_setr_epi32( + i32::MIN, + i32::MIN + 1, + i32::MAX, + i32::MAX - 1, + i32::MIN + 2, + i32::MIN + 3, + i32::MAX - 2, + i32::MAX - 3, + ); + let r = _mm256_hadd_epi32(a, b); + assert_eq_m256i(r, expected); + } + test_mm256_hadd_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_hadds_epi16() { + let a = _mm256_set1_epi16(2); + let a = _mm256_insert_epi16::<0>(a, 0x7fff); + let a = _mm256_insert_epi16::<1>(a, 1); + let b = _mm256_set1_epi16(4); + let r = _mm256_hadds_epi16(a, b); + let e = _mm256_setr_epi16(0x7FFF, 4, 4, 4, 8, 8, 8, 8, 4, 4, 4, 4, 8, 8, 8, 8); + assert_eq_m256i(r, e); + + // Test saturating on overflow + let a = _mm256_setr_epi16( + i16::MAX, + 1, + i16::MAX, + 2, + i16::MAX, + 3, + i16::MAX, + 4, + i16::MAX, + 5, + i16::MAX, + 6, + i16::MAX, + 7, + i16::MAX, + 8, + ); + let b = _mm256_setr_epi16( + i16::MIN, + -1, + i16::MIN, + -2, + i16::MIN, + -3, + i16::MIN, + -4, + i16::MIN, + -5, + i16::MIN, + -6, + i16::MIN, + -7, + i16::MIN, + -8, + ); + let expected = _mm256_setr_epi16( + i16::MAX, + i16::MAX, + i16::MAX, + i16::MAX, + i16::MIN, + i16::MIN, + i16::MIN, + i16::MIN, + i16::MAX, + i16::MAX, + i16::MAX, + i16::MAX, + i16::MIN, + i16::MIN, + i16::MIN, + i16::MIN, + ); + let r = _mm256_hadds_epi16(a, b); + assert_eq_m256i(r, expected); + } + test_mm256_hadds_epi16(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_hsub_epi16() { + let a = _mm256_set1_epi16(2); + let b = _mm256_set1_epi16(4); + let r = _mm256_hsub_epi16(a, b); + let e = _mm256_set1_epi16(0); + assert_eq_m256i(r, e); + + // Test wrapping on overflow + let a = _mm256_setr_epi16( + i16::MAX, + -1, + i16::MAX, + -2, + i16::MAX, + -3, + i16::MAX, + -4, + i16::MAX, + -5, + i16::MAX, + -6, + i16::MAX, + -7, + i16::MAX, + -8, + ); + let b = _mm256_setr_epi16( + i16::MIN, + 1, + i16::MIN, + 2, + i16::MIN, + 3, + i16::MIN, + 4, + i16::MIN, + 5, + i16::MIN, + 6, + i16::MIN, + 7, + i16::MIN, + 8, + ); + let expected = _mm256_setr_epi16( + i16::MIN, + i16::MIN + 1, + i16::MIN + 2, + i16::MIN + 3, + i16::MAX, + i16::MAX - 1, + i16::MAX - 2, + i16::MAX - 3, + i16::MIN + 4, + i16::MIN + 5, + i16::MIN + 6, + i16::MIN + 7, + i16::MAX - 4, + i16::MAX - 5, + i16::MAX - 6, + i16::MAX - 7, + ); + let r = _mm256_hsub_epi16(a, b); + assert_eq_m256i(r, expected); + } + test_mm256_hsub_epi16(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_hsub_epi32() { + let a = _mm256_set1_epi32(2); + let b = _mm256_set1_epi32(4); + let r = _mm256_hsub_epi32(a, b); + let e = _mm256_set1_epi32(0); + assert_eq_m256i(r, e); + + // Test wrapping on overflow + let a = _mm256_setr_epi32(i32::MAX, -1, i32::MAX, -2, i32::MAX, -3, i32::MAX, -4); + let b = _mm256_setr_epi32(i32::MIN, 1, i32::MIN, 2, i32::MIN, 3, i32::MIN, 4); + let expected = _mm256_setr_epi32( + i32::MIN, + i32::MIN + 1, + i32::MAX, + i32::MAX - 1, + i32::MIN + 2, + i32::MIN + 3, + i32::MAX - 2, + i32::MAX - 3, + ); + let r = _mm256_hsub_epi32(a, b); + assert_eq_m256i(r, expected); + } + test_mm256_hsub_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_hsubs_epi16() { + let a = _mm256_set1_epi16(2); + let a = _mm256_insert_epi16::<0>(a, 0x7fff); + let a = _mm256_insert_epi16::<1>(a, -1); + let b = _mm256_set1_epi16(4); + let r = _mm256_hsubs_epi16(a, b); + let e = _mm256_insert_epi16::<0>(_mm256_set1_epi16(0), 0x7FFF); + assert_eq_m256i(r, e); + + // Test saturating on overflow + let a = _mm256_setr_epi16( + i16::MAX, + -1, + i16::MAX, + -2, + i16::MAX, + -3, + i16::MAX, + -4, + i16::MAX, + -5, + i16::MAX, + -6, + i16::MAX, + -7, + i16::MAX, + -8, + ); + let b = _mm256_setr_epi16( + i16::MIN, + 1, + i16::MIN, + 2, + i16::MIN, + 3, + i16::MIN, + 4, + i16::MIN, + 5, + i16::MIN, + 6, + i16::MIN, + 7, + i16::MIN, + 8, + ); + let expected = _mm256_setr_epi16( + i16::MAX, + i16::MAX, + i16::MAX, + i16::MAX, + i16::MIN, + i16::MIN, + i16::MIN, + i16::MIN, + i16::MAX, + i16::MAX, + i16::MAX, + i16::MAX, + i16::MIN, + i16::MIN, + i16::MIN, + i16::MIN, + ); + let r = _mm256_hsubs_epi16(a, b); + assert_eq_m256i(r, expected); + } + test_mm256_hsubs_epi16(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_i32gather_epi32() { + let arr: [i32; 128] = core::array::from_fn(|i| i as i32); + // A multiplier of 4 is word-addressing + let r = _mm_i32gather_epi32::<4>(arr.as_ptr(), _mm_setr_epi32(0, 16, 32, 48)); + assert_eq_m128i(r, _mm_setr_epi32(0, 16, 32, 48)); + } + test_mm_i32gather_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_mask_i32gather_epi32() { + let arr: [i32; 128] = core::array::from_fn(|i| i as i32); + // A multiplier of 4 is word-addressing + let r = _mm_mask_i32gather_epi32::<4>( + _mm_set1_epi32(256), + arr.as_ptr(), + _mm_setr_epi32(0, 16, 64, 96), + _mm_setr_epi32(-1, -1, -1, 0), + ); + assert_eq_m128i(r, _mm_setr_epi32(0, 16, 64, 256)); + } + test_mm_mask_i32gather_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_i32gather_epi32() { + let arr: [i32; 128] = core::array::from_fn(|i| i as i32); + // A multiplier of 4 is word-addressing + let r = + _mm256_i32gather_epi32::<4>(arr.as_ptr(), _mm256_setr_epi32(0, 16, 32, 48, 1, 2, 3, 4)); + assert_eq_m256i(r, _mm256_setr_epi32(0, 16, 32, 48, 1, 2, 3, 4)); + } + test_mm256_i32gather_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_mask_i32gather_epi32() { + let arr: [i32; 128] = core::array::from_fn(|i| i as i32); + // A multiplier of 4 is word-addressing + let r = _mm256_mask_i32gather_epi32::<4>( + _mm256_set1_epi32(256), + arr.as_ptr(), + _mm256_setr_epi32(0, 16, 64, 96, 0, 0, 0, 0), + _mm256_setr_epi32(-1, -1, -1, 0, 0, 0, 0, 0), + ); + assert_eq_m256i(r, _mm256_setr_epi32(0, 16, 64, 256, 256, 256, 256, 256)); + } + test_mm256_mask_i32gather_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_i32gather_ps() { + let arr: [f32; 128] = core::array::from_fn(|i| i as f32); + // A multiplier of 4 is word-addressing for f32s + let r = _mm_i32gather_ps::<4>(arr.as_ptr(), _mm_setr_epi32(0, 16, 32, 48)); + assert_eq_m128(r, _mm_setr_ps(0.0, 16.0, 32.0, 48.0)); + } + test_mm_i32gather_ps(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_mask_i32gather_ps() { + let arr: [f32; 128] = core::array::from_fn(|i| i as f32); + // A multiplier of 4 is word-addressing for f32s + let r = _mm_mask_i32gather_ps::<4>( + _mm_set1_ps(256.0), + arr.as_ptr(), + _mm_setr_epi32(0, 16, 64, 96), + _mm_setr_ps(-1.0, -1.0, -1.0, 0.0), + ); + assert_eq_m128(r, _mm_setr_ps(0.0, 16.0, 64.0, 256.0)); + } + test_mm_mask_i32gather_ps(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_i32gather_ps() { + let arr: [f32; 128] = core::array::from_fn(|i| i as f32); + // A multiplier of 4 is word-addressing for f32s + let r = + _mm256_i32gather_ps::<4>(arr.as_ptr(), _mm256_setr_epi32(0, 16, 32, 48, 1, 2, 3, 4)); + assert_eq_m256(r, _mm256_setr_ps(0.0, 16.0, 32.0, 48.0, 1.0, 2.0, 3.0, 4.0)); + } + test_mm256_i32gather_ps(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_mask_i32gather_ps() { + let arr: [f32; 128] = core::array::from_fn(|i| i as f32); + // A multiplier of 4 is word-addressing for f32s + let r = _mm256_mask_i32gather_ps::<4>( + _mm256_set1_ps(256.0), + arr.as_ptr(), + _mm256_setr_epi32(0, 16, 64, 96, 0, 0, 0, 0), + _mm256_setr_ps(-1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0), + ); + assert_eq_m256(r, _mm256_setr_ps(0.0, 16.0, 64.0, 256.0, 256.0, 256.0, 256.0, 256.0)); + } + test_mm256_mask_i32gather_ps(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_i32gather_epi64() { + let arr: [i64; 128] = core::array::from_fn(|i| i as i64); + // A multiplier of 8 is word-addressing for i64s + let r = _mm_i32gather_epi64::<8>(arr.as_ptr(), _mm_setr_epi32(0, 16, 0, 0)); + assert_eq_m128i(r, _mm_setr_epi64x(0, 16)); + } + test_mm_i32gather_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_mask_i32gather_epi64() { + let arr: [i64; 128] = core::array::from_fn(|i| i as i64); + // A multiplier of 8 is word-addressing for i64s + let r = _mm_mask_i32gather_epi64::<8>( + _mm_set1_epi64x(256), + arr.as_ptr(), + _mm_setr_epi32(16, 16, 16, 16), + _mm_setr_epi64x(-1, 0), + ); + assert_eq_m128i(r, _mm_setr_epi64x(16, 256)); + } + test_mm_mask_i32gather_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_i32gather_epi64() { + let arr: [i64; 128] = core::array::from_fn(|i| i as i64); + // A multiplier of 8 is word-addressing for i64s + let r = _mm256_i32gather_epi64::<8>(arr.as_ptr(), _mm_setr_epi32(0, 16, 32, 48)); + assert_eq_m256i(r, _mm256_setr_epi64x(0, 16, 32, 48)); + } + test_mm256_i32gather_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_mask_i32gather_epi64() { + let arr: [i64; 128] = core::array::from_fn(|i| i as i64); + // A multiplier of 8 is word-addressing for i64s + let r = _mm256_mask_i32gather_epi64::<8>( + _mm256_set1_epi64x(256), + arr.as_ptr(), + _mm_setr_epi32(0, 16, 64, 96), + _mm256_setr_epi64x(-1, -1, -1, 0), + ); + assert_eq_m256i(r, _mm256_setr_epi64x(0, 16, 64, 256)); + } + test_mm256_mask_i32gather_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_i32gather_pd() { + let arr: [f64; 128] = core::array::from_fn(|i| i as f64); + // A multiplier of 8 is word-addressing for f64s + let r = _mm_i32gather_pd::<8>(arr.as_ptr(), _mm_setr_epi32(0, 16, 0, 0)); + assert_eq_m128d(r, _mm_setr_pd(0.0, 16.0)); + } + test_mm_i32gather_pd(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_mask_i32gather_pd() { + let arr: [f64; 128] = core::array::from_fn(|i| i as f64); + // A multiplier of 8 is word-addressing for f64s + let r = _mm_mask_i32gather_pd::<8>( + _mm_set1_pd(256.0), + arr.as_ptr(), + _mm_setr_epi32(16, 16, 16, 16), + _mm_setr_pd(-1.0, 0.0), + ); + assert_eq_m128d(r, _mm_setr_pd(16.0, 256.0)); + } + test_mm_mask_i32gather_pd(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_i32gather_pd() { + let arr: [f64; 128] = core::array::from_fn(|i| i as f64); + // A multiplier of 8 is word-addressing for f64s + let r = _mm256_i32gather_pd::<8>(arr.as_ptr(), _mm_setr_epi32(0, 16, 32, 48)); + assert_eq_m256d(r, _mm256_setr_pd(0.0, 16.0, 32.0, 48.0)); + } + test_mm256_i32gather_pd(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_mask_i32gather_pd() { + let arr: [f64; 128] = core::array::from_fn(|i| i as f64); + // A multiplier of 8 is word-addressing for f64s + let r = _mm256_mask_i32gather_pd::<8>( + _mm256_set1_pd(256.0), + arr.as_ptr(), + _mm_setr_epi32(0, 16, 64, 96), + _mm256_setr_pd(-1.0, -1.0, -1.0, 0.0), + ); + assert_eq_m256d(r, _mm256_setr_pd(0.0, 16.0, 64.0, 256.0)); + } + test_mm256_mask_i32gather_pd(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_i64gather_epi32() { + let arr: [i32; 128] = core::array::from_fn(|i| i as i32); + // A multiplier of 4 is word-addressing + let r = _mm_i64gather_epi32::<4>(arr.as_ptr(), _mm_setr_epi64x(0, 16)); + assert_eq_m128i(r, _mm_setr_epi32(0, 16, 0, 0)); + } + test_mm_i64gather_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_mask_i64gather_epi32() { + let arr: [i32; 128] = core::array::from_fn(|i| i as i32); + // A multiplier of 4 is word-addressing + let r = _mm_mask_i64gather_epi32::<4>( + _mm_set1_epi32(256), + arr.as_ptr(), + _mm_setr_epi64x(0, 16), + _mm_setr_epi32(-1, 0, -1, 0), + ); + assert_eq_m128i(r, _mm_setr_epi32(0, 256, 0, 0)); + } + test_mm_mask_i64gather_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_i64gather_epi32() { + let arr: [i32; 128] = core::array::from_fn(|i| i as i32); + // A multiplier of 4 is word-addressing + let r = _mm256_i64gather_epi32::<4>(arr.as_ptr(), _mm256_setr_epi64x(0, 16, 32, 48)); + assert_eq_m128i(r, _mm_setr_epi32(0, 16, 32, 48)); + } + test_mm256_i64gather_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_mask_i64gather_epi32() { + let arr: [i32; 128] = core::array::from_fn(|i| i as i32); + // A multiplier of 4 is word-addressing + let r = _mm256_mask_i64gather_epi32::<4>( + _mm_set1_epi32(256), + arr.as_ptr(), + _mm256_setr_epi64x(0, 16, 64, 96), + _mm_setr_epi32(-1, -1, -1, 0), + ); + assert_eq_m128i(r, _mm_setr_epi32(0, 16, 64, 256)); + } + test_mm256_mask_i64gather_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_i64gather_ps() { + let arr: [f32; 128] = core::array::from_fn(|i| i as f32); + // A multiplier of 4 is word-addressing for f32s + let r = _mm_i64gather_ps::<4>(arr.as_ptr(), _mm_setr_epi64x(0, 16)); + assert_eq_m128(r, _mm_setr_ps(0.0, 16.0, 0.0, 0.0)); + } + test_mm_i64gather_ps(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_mask_i64gather_ps() { + let arr: [f32; 128] = core::array::from_fn(|i| i as f32); + // A multiplier of 4 is word-addressing for f32s + let r = _mm_mask_i64gather_ps::<4>( + _mm_set1_ps(256.0), + arr.as_ptr(), + _mm_setr_epi64x(0, 16), + _mm_setr_ps(-1.0, 0.0, -1.0, 0.0), + ); + assert_eq_m128(r, _mm_setr_ps(0.0, 256.0, 0.0, 0.0)); + } + test_mm_mask_i64gather_ps(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_i64gather_ps() { + let arr: [f32; 128] = core::array::from_fn(|i| i as f32); + // A multiplier of 4 is word-addressing for f32s + let r = _mm256_i64gather_ps::<4>(arr.as_ptr(), _mm256_setr_epi64x(0, 16, 32, 48)); + assert_eq_m128(r, _mm_setr_ps(0.0, 16.0, 32.0, 48.0)); + } + test_mm256_i64gather_ps(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_mask_i64gather_ps() { + let arr: [f32; 128] = core::array::from_fn(|i| i as f32); + // A multiplier of 4 is word-addressing for f32s + let r = _mm256_mask_i64gather_ps::<4>( + _mm_set1_ps(256.0), + arr.as_ptr(), + _mm256_setr_epi64x(0, 16, 64, 96), + _mm_setr_ps(-1.0, -1.0, -1.0, 0.0), + ); + assert_eq_m128(r, _mm_setr_ps(0.0, 16.0, 64.0, 256.0)); + } + test_mm256_mask_i64gather_ps(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_i64gather_epi64() { + let arr: [i64; 128] = core::array::from_fn(|i| i as i64); + // A multiplier of 8 is word-addressing for i64s + let r = _mm_i64gather_epi64::<8>(arr.as_ptr(), _mm_setr_epi64x(0, 16)); + assert_eq_m128i(r, _mm_setr_epi64x(0, 16)); + } + test_mm_i64gather_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_mask_i64gather_epi64() { + let arr: [i64; 128] = core::array::from_fn(|i| i as i64); + // A multiplier of 8 is word-addressing for i64s + let r = _mm_mask_i64gather_epi64::<8>( + _mm_set1_epi64x(256), + arr.as_ptr(), + _mm_setr_epi64x(16, 16), + _mm_setr_epi64x(-1, 0), + ); + assert_eq_m128i(r, _mm_setr_epi64x(16, 256)); + } + test_mm_mask_i64gather_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_i64gather_epi64() { + let arr: [i64; 128] = core::array::from_fn(|i| i as i64); + // A multiplier of 8 is word-addressing for i64s + let r = _mm256_i64gather_epi64::<8>(arr.as_ptr(), _mm256_setr_epi64x(0, 16, 32, 48)); + assert_eq_m256i(r, _mm256_setr_epi64x(0, 16, 32, 48)); + } + test_mm256_i64gather_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_mask_i64gather_epi64() { + let arr: [i64; 128] = core::array::from_fn(|i| i as i64); + // A multiplier of 8 is word-addressing for i64s + let r = _mm256_mask_i64gather_epi64::<8>( + _mm256_set1_epi64x(256), + arr.as_ptr(), + _mm256_setr_epi64x(0, 16, 64, 96), + _mm256_setr_epi64x(-1, -1, -1, 0), + ); + assert_eq_m256i(r, _mm256_setr_epi64x(0, 16, 64, 256)); + } + test_mm256_mask_i64gather_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_i64gather_pd() { + let arr: [f64; 128] = core::array::from_fn(|i| i as f64); + // A multiplier of 8 is word-addressing for f64s + let r = _mm_i64gather_pd::<8>(arr.as_ptr(), _mm_setr_epi64x(0, 16)); + assert_eq_m128d(r, _mm_setr_pd(0.0, 16.0)); + } + test_mm_i64gather_pd(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_mask_i64gather_pd() { + let arr: [f64; 128] = core::array::from_fn(|i| i as f64); + // A multiplier of 8 is word-addressing for f64s + let r = _mm_mask_i64gather_pd::<8>( + _mm_set1_pd(256.0), + arr.as_ptr(), + _mm_setr_epi64x(16, 16), + _mm_setr_pd(-1.0, 0.0), + ); + assert_eq_m128d(r, _mm_setr_pd(16.0, 256.0)); + } + test_mm_mask_i64gather_pd(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_i64gather_pd() { + let arr: [f64; 128] = core::array::from_fn(|i| i as f64); + // A multiplier of 8 is word-addressing for f64s + let r = _mm256_i64gather_pd::<8>(arr.as_ptr(), _mm256_setr_epi64x(0, 16, 32, 48)); + assert_eq_m256d(r, _mm256_setr_pd(0.0, 16.0, 32.0, 48.0)); + } + test_mm256_i64gather_pd(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_mask_i64gather_pd() { + let arr: [f64; 128] = core::array::from_fn(|i| i as f64); + // A multiplier of 8 is word-addressing for f64s + let r = _mm256_mask_i64gather_pd::<8>( + _mm256_set1_pd(256.0), + arr.as_ptr(), + _mm256_setr_epi64x(0, 16, 64, 96), + _mm256_setr_pd(-1.0, -1.0, -1.0, 0.0), + ); + assert_eq_m256d(r, _mm256_setr_pd(0.0, 16.0, 64.0, 256.0)); + } + test_mm256_mask_i64gather_pd(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_madd_epi16() { + let a = _mm256_set1_epi16(2); + let b = _mm256_set1_epi16(4); + let r = _mm256_madd_epi16(a, b); + let e = _mm256_set1_epi32(16); + assert_eq_m256i(r, e); + } + test_mm256_madd_epi16(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_maddubs_epi16() { + let a = _mm256_set1_epi8(2); + let b = _mm256_set1_epi8(4); + let r = _mm256_maddubs_epi16(a, b); + let e = _mm256_set1_epi16(16); + assert_eq_m256i(r, e); + } + test_mm256_maddubs_epi16(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_maskload_epi32() { + let nums = [1, 2, 3, 4]; + let a = &nums as *const i32; + let mask = _mm_setr_epi32(-1, 0, 0, -1); + let r = _mm_maskload_epi32(a, mask); + let e = _mm_setr_epi32(1, 0, 0, 4); + assert_eq_m128i(r, e); + + // Unaligned pointer + let a = Unaligned::new([1i32, 2, 3, 4]); + let mask = _mm_setr_epi32(0, !0, 0, !0); + let r = _mm_maskload_epi32(a.as_ptr().cast(), mask); + let e = _mm_setr_epi32(0, 2, 0, 4); + assert_eq_m128i(r, e); + + // Only loading first element, so slice can be short. + let a = &[2i32]; + let mask = _mm_setr_epi32(!0, 0, 0, 0); + let r = _mm_maskload_epi32(a.as_ptr(), mask); + let e = _mm_setr_epi32(2, 0, 0, 0); + assert_eq_m128i(r, e); + + // Only loading last element, so slice can be short. + let a = &[2i32]; + let mask = _mm_setr_epi32(0, 0, 0, !0); + let r = _mm_maskload_epi32(a.as_ptr().wrapping_sub(3), mask); + let e = _mm_setr_epi32(0, 0, 0, 2); + assert_eq_m128i(r, e); + } + test_mm_maskload_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_maskload_epi32() { + let nums = [1, 2, 3, 4, 5, 6, 7, 8]; + let a = &nums as *const i32; + let mask = _mm256_setr_epi32(-1, 0, 0, -1, 0, -1, -1, 0); + let r = _mm256_maskload_epi32(a, mask); + let e = _mm256_setr_epi32(1, 0, 0, 4, 0, 6, 7, 0); + assert_eq_m256i(r, e); + + // Unaligned pointer + let a = Unaligned::new([1i32, 2, 3, 4, 5, 6, 7, 8]); + let mask = _mm256_setr_epi32(0, !0, 0, !0, 0, !0, 0, !0); + let r = _mm256_maskload_epi32(a.as_ptr().cast(), mask); + let e = _mm256_setr_epi32(0, 2, 0, 4, 0, 6, 0, 8); + assert_eq_m256i(r, e); + + // Only loading first element, so slice can be short. + let a = &[2i32]; + let mask = _mm256_setr_epi32(!0, 0, 0, 0, 0, 0, 0, 0); + let r = _mm256_maskload_epi32(a.as_ptr(), mask); + let e = _mm256_setr_epi32(2, 0, 0, 0, 0, 0, 0, 0); + assert_eq_m256i(r, e); + + // Only loading last element, so slice can be short. + let a = &[2i32]; + let mask = _mm256_setr_epi32(0, 0, 0, 0, 0, 0, 0, !0); + let r = _mm256_maskload_epi32(a.as_ptr().wrapping_sub(7), mask); + let e = _mm256_setr_epi32(0, 0, 0, 0, 0, 0, 0, 2); + assert_eq_m256i(r, e); + } + test_mm256_maskload_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_maskload_epi64() { + let nums = [1_i64, 2_i64]; + let a = &nums as *const i64; + let mask = _mm_setr_epi64x(0, -1); + let r = _mm_maskload_epi64(a, mask); + let e = _mm_setr_epi64x(0, 2); + assert_eq_m128i(r, e); + + // Unaligned pointer + let a = Unaligned::new([1i64, 2]); + let mask = _mm_setr_epi64x(0, !0); + let r = _mm_maskload_epi64(a.as_ptr().cast(), mask); + let e = _mm_setr_epi64x(0, 2); + assert_eq_m128i(r, e); + + // Only loading first element, so slice can be short. + let a = &[2i64]; + let mask = _mm_setr_epi64x(!0, 0); + let r = _mm_maskload_epi64(a.as_ptr(), mask); + let e = _mm_setr_epi64x(2, 0); + assert_eq_m128i(r, e); + + // Only loading last element, so slice can be short. + let a = &[2i64]; + let mask = _mm_setr_epi64x(0, !0); + let r = _mm_maskload_epi64(a.as_ptr().wrapping_sub(1), mask); + let e = _mm_setr_epi64x(0, 2); + assert_eq_m128i(r, e); + } + test_mm_maskload_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_maskload_epi64() { + let nums = [1_i64, 2_i64, 3_i64, 4_i64]; + let a = &nums as *const i64; + let mask = _mm256_setr_epi64x(0, -1, -1, 0); + let r = _mm256_maskload_epi64(a, mask); + let e = _mm256_setr_epi64x(0, 2, 3, 0); + assert_eq_m256i(r, e); + + // Unaligned pointer + let a = Unaligned::new([1i64, 2, 3, 4]); + let mask = _mm256_setr_epi64x(0, !0, 0, !0); + let r = _mm256_maskload_epi64(a.as_ptr().cast(), mask); + let e = _mm256_setr_epi64x(0, 2, 0, 4); + assert_eq_m256i(r, e); + + // Only loading first element, so slice can be short. + let a = &[2i64]; + let mask = _mm256_setr_epi64x(!0, 0, 0, 0); + let r = _mm256_maskload_epi64(a.as_ptr(), mask); + let e = _mm256_setr_epi64x(2, 0, 0, 0); + assert_eq_m256i(r, e); + + // Only loading last element, so slice can be short. + let a = &[2i64]; + let mask = _mm256_setr_epi64x(0, 0, 0, !0); + let r = _mm256_maskload_epi64(a.as_ptr().wrapping_sub(3), mask); + let e = _mm256_setr_epi64x(0, 0, 0, 2); + assert_eq_m256i(r, e); + } + test_mm256_maskload_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_maskstore_epi32() { + let a = _mm_setr_epi32(1, 2, 3, 4); + let mut arr = [-1, -1, -1, -1]; + let mask = _mm_setr_epi32(-1, 0, 0, -1); + _mm_maskstore_epi32(arr.as_mut_ptr(), mask, a); + let e = [1, -1, -1, 4]; + assert_eq!(arr, e); + + // Unaligned pointer + let mut r = Unaligned::new([0i32; 4]); + let mask = _mm_setr_epi32(0, !0, 0, !0); + let a = _mm_setr_epi32(1, 2, 3, 4); + _mm_maskstore_epi32(r.as_mut_ptr().cast(), mask, a); + let e = [0i32, 2, 0, 4]; + assert_eq!(r.read(), e); + + // Only storing first element, so slice can be short. + let mut r = [0i32]; + let mask = _mm_setr_epi32(!0, 0, 0, 0); + let a = _mm_setr_epi32(1, 2, 3, 4); + _mm_maskstore_epi32(r.as_mut_ptr(), mask, a); + let e = [1i32]; + assert_eq!(r, e); + + // Only storing last element, so slice can be short. + let mut r = [0i32]; + let mask = _mm_setr_epi32(0, 0, 0, !0); + let a = _mm_setr_epi32(1, 2, 3, 4); + _mm_maskstore_epi32(r.as_mut_ptr().wrapping_sub(3), mask, a); + let e = [4i32]; + assert_eq!(r, e); + } + test_mm_maskstore_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_maskstore_epi32() { + let a = _mm256_setr_epi32(1, 0x6d726f, 3, 42, 0x777161, 6, 7, 8); + let mut arr = [-1, -1, -1, 0x776173, -1, 0x68657265, -1, -1]; + let mask = _mm256_setr_epi32(-1, 0, 0, -1, 0, -1, -1, 0); + _mm256_maskstore_epi32(arr.as_mut_ptr(), mask, a); + let e = [1, -1, -1, 42, -1, 6, 7, -1]; + assert_eq!(arr, e); + + // Unaligned pointer + let mut r = Unaligned::new([0i32; 8]); + let mask = _mm256_setr_epi32(0, !0, 0, !0, 0, !0, 0, !0); + let a = _mm256_setr_epi32(1, 2, 3, 4, 5, 6, 7, 8); + _mm256_maskstore_epi32(r.as_mut_ptr().cast(), mask, a); + let e = [0i32, 2, 0, 4, 0, 6, 0, 8]; + assert_eq!(r.read(), e); + + // Only storing first element, so slice can be short. + let mut r = [0i32]; + let mask = _mm256_setr_epi32(!0, 0, 0, 0, 0, 0, 0, 0); + let a = _mm256_setr_epi32(1, 2, 3, 4, 5, 6, 7, 8); + _mm256_maskstore_epi32(r.as_mut_ptr(), mask, a); + let e = [1i32]; + assert_eq!(r, e); + + // Only storing last element, so slice can be short. + let mut r = [0i32]; + let mask = _mm256_setr_epi32(0, 0, 0, 0, 0, 0, 0, !0); + let a = _mm256_setr_epi32(1, 2, 3, 4, 5, 6, 7, 8); + _mm256_maskstore_epi32(r.as_mut_ptr().wrapping_sub(7), mask, a); + let e = [8i32]; + assert_eq!(r, e); + } + test_mm256_maskstore_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_maskstore_epi64() { + let a = _mm_setr_epi64x(1_i64, 2_i64); + let mut arr = [-1_i64, -1_i64]; + let mask = _mm_setr_epi64x(0, -1); + _mm_maskstore_epi64(arr.as_mut_ptr(), mask, a); + let e = [-1, 2]; + assert_eq!(arr, e); + + // Unaligned pointer + let mut r = Unaligned::new([0i64; 2]); + let mask = _mm_setr_epi64x(0, !0); + let a = _mm_setr_epi64x(1, 2); + _mm_maskstore_epi64(r.as_mut_ptr().cast(), mask, a); + let e = [0i64, 2]; + assert_eq!(r.read(), e); + + // Only storing first element, so slice can be short. + let mut r = [0i64]; + let mask = _mm_setr_epi64x(!0, 0); + let a = _mm_setr_epi64x(1, 2); + _mm_maskstore_epi64(r.as_mut_ptr(), mask, a); + let e = [1i64]; + assert_eq!(r, e); + + // Only storing last element, so slice can be short. + let mut r = [0i64]; + let mask = _mm_setr_epi64x(0, !0); + let a = _mm_setr_epi64x(1, 2); + _mm_maskstore_epi64(r.as_mut_ptr().wrapping_sub(1), mask, a); + let e = [2i64]; + assert_eq!(r, e); + } + test_mm_maskstore_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_maskstore_epi64() { + let a = _mm256_setr_epi64x(1_i64, 2_i64, 3_i64, 4_i64); + let mut arr = [-1_i64, -1_i64, -1_i64, -1_i64]; + let mask = _mm256_setr_epi64x(0, -1, -1, 0); + _mm256_maskstore_epi64(arr.as_mut_ptr(), mask, a); + let e = [-1, 2, 3, -1]; + assert_eq!(arr, e); + + // Unaligned pointer + let mut r = Unaligned::new([0i64; 4]); + let mask = _mm256_setr_epi64x(0, !0, 0, !0); + let a = _mm256_setr_epi64x(1, 2, 3, 4); + _mm256_maskstore_epi64(r.as_mut_ptr().cast(), mask, a); + let e = [0i64, 2, 0, 4]; + assert_eq!(r.read(), e); + + // Only storing first element, so slice can be short. + let mut r = [0i64]; + let mask = _mm256_setr_epi64x(!0, 0, 0, 0); + let a = _mm256_setr_epi64x(1, 2, 3, 4); + _mm256_maskstore_epi64(r.as_mut_ptr(), mask, a); + let e = [1i64]; + assert_eq!(r, e); + + // Only storing last element, so slice can be short. + let mut r = [0i64]; + let mask = _mm256_setr_epi64x(0, 0, 0, !0); + let a = _mm256_setr_epi64x(1, 2, 3, 4); + _mm256_maskstore_epi64(r.as_mut_ptr().wrapping_sub(3), mask, a); + let e = [4i64]; + assert_eq!(r, e); + } + test_mm256_maskstore_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_mpsadbw_epu8() { + let a = _mm256_setr_epi8( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 2, 4, 6, 8, 10, 12, 14, 16, + 18, 20, 22, 24, 26, 28, 30, + ); + + let r = _mm256_mpsadbw_epu8::<0b000>(a, a); + let e = _mm256_setr_epi16(0, 4, 8, 12, 16, 20, 24, 28, 0, 8, 16, 24, 32, 40, 48, 56); + assert_eq_m256i(r, e); + + let r = _mm256_mpsadbw_epu8::<0b001>(a, a); + let e = _mm256_setr_epi16(16, 12, 8, 4, 0, 4, 8, 12, 32, 24, 16, 8, 0, 8, 16, 24); + assert_eq_m256i(r, e); + + let r = _mm256_mpsadbw_epu8::<0b100>(a, a); + let e = _mm256_setr_epi16(16, 20, 24, 28, 32, 36, 40, 44, 32, 40, 48, 56, 64, 72, 80, 88); + assert_eq_m256i(r, e); + + let r = _mm256_mpsadbw_epu8::<0b101>(a, a); + let e = _mm256_setr_epi16(0, 4, 8, 12, 16, 20, 24, 28, 0, 8, 16, 24, 32, 40, 48, 56); + assert_eq_m256i(r, e); + + let r = _mm256_mpsadbw_epu8::<0b111>(a, a); + let e = _mm256_setr_epi16(32, 28, 24, 20, 16, 12, 8, 4, 64, 56, 48, 40, 32, 24, 16, 8); + assert_eq_m256i(r, e); + } + test_mm256_mpsadbw_epu8(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_mulhrs_epi16() { + let a = _mm256_set1_epi16(2); + let b = _mm256_set1_epi16(4); + let r = _mm256_mullo_epi16(a, b); + let e = _mm256_set1_epi16(8); + assert_eq_m256i(r, e); + } + test_mm256_mulhrs_epi16(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_packs_epi16() { + let a = _mm256_set1_epi16(2); + let b = _mm256_set1_epi16(4); + let r = _mm256_packs_epi16(a, b); + #[rustfmt::skip] + let e = _mm256_setr_epi8( + 2, 2, 2, 2, 2, 2, 2, 2, + 4, 4, 4, 4, 4, 4, 4, 4, + 2, 2, 2, 2, 2, 2, 2, 2, + 4, 4, 4, 4, 4, 4, 4, 4, + ); + + assert_eq_m256i(r, e); + } + test_mm256_packs_epi16(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_packs_epi32() { + let a = _mm256_set1_epi32(2); + let b = _mm256_set1_epi32(4); + let r = _mm256_packs_epi32(a, b); + let e = _mm256_setr_epi16(2, 2, 2, 2, 4, 4, 4, 4, 2, 2, 2, 2, 4, 4, 4, 4); + + assert_eq_m256i(r, e); + } + test_mm256_packs_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_packus_epi16() { + let a = _mm256_set1_epi16(2); + let b = _mm256_set1_epi16(4); + let r = _mm256_packus_epi16(a, b); + #[rustfmt::skip] + let e = _mm256_setr_epi8( + 2, 2, 2, 2, 2, 2, 2, 2, + 4, 4, 4, 4, 4, 4, 4, 4, + 2, 2, 2, 2, 2, 2, 2, 2, + 4, 4, 4, 4, 4, 4, 4, 4, + ); + + assert_eq_m256i(r, e); + } + test_mm256_packus_epi16(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_packus_epi32() { + let a = _mm256_set1_epi32(2); + let b = _mm256_set1_epi32(4); + let r = _mm256_packus_epi32(a, b); + let e = _mm256_setr_epi16(2, 2, 2, 2, 4, 4, 4, 4, 2, 2, 2, 2, 4, 4, 4, 4); + + assert_eq_m256i(r, e); + } + test_mm256_packus_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_permutevar8x32_epi32() { + let a = _mm256_setr_epi32(100, 200, 300, 400, 500, 600, 700, 800); + let b = _mm256_setr_epi32(5, 0, 5, 1, 7, 6, 3, 4); + let expected = _mm256_setr_epi32(600, 100, 600, 200, 800, 700, 400, 500); + let r = _mm256_permutevar8x32_epi32(a, b); + assert_eq_m256i(r, expected); + } + test_mm256_permutevar8x32_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_permute2x128_si256() { + let a = _mm256_setr_epi64x(100, 200, 500, 600); + let b = _mm256_setr_epi64x(300, 400, 700, 800); + let r = _mm256_permute2x128_si256::<0b00_01_00_11>(a, b); + let e = _mm256_setr_epi64x(700, 800, 500, 600); + assert_eq_m256i(r, e); + } + test_mm256_permute2x128_si256(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_permutevar8x32_ps() { + let a = _mm256_setr_ps(1., 2., 3., 4., 5., 6., 7., 8.); + let b = _mm256_setr_epi32(5, 0, 5, 1, 7, 6, 3, 4); + let r = _mm256_permutevar8x32_ps(a, b); + let e = _mm256_setr_ps(6., 1., 6., 2., 8., 7., 4., 5.); + assert_eq_m256(r, e); + } + test_mm256_permutevar8x32_ps(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_sad_epu8() { + let a = _mm256_set1_epi8(2); + let b = _mm256_set1_epi8(4); + let r = _mm256_sad_epu8(a, b); + let e = _mm256_set1_epi64x(16); + assert_eq_m256i(r, e); + } + test_mm256_sad_epu8(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_shuffle_epi8() { + #[rustfmt::skip] + let a = _mm256_setr_epi8( + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + ); + #[rustfmt::skip] + let b = _mm256_setr_epi8( + 4, 128u8 as i8, 4, 3, 24, 12, 6, 19, + 12, 5, 5, 10, 4, 1, 8, 0, + 4, 128u8 as i8, 4, 3, 24, 12, 6, 19, + 12, 5, 5, 10, 4, 1, 8, 0, + ); + #[rustfmt::skip] + let expected = _mm256_setr_epi8( + 5, 0, 5, 4, 9, 13, 7, 4, + 13, 6, 6, 11, 5, 2, 9, 1, + 21, 0, 21, 20, 25, 29, 23, 20, + 29, 22, 22, 27, 21, 18, 25, 17, + ); + let r = _mm256_shuffle_epi8(a, b); + assert_eq_m256i(r, expected); + } + test_mm256_shuffle_epi8(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_sign_epi16() { + let a = _mm256_set1_epi16(2); + let b = _mm256_set1_epi16(-1); + let r = _mm256_sign_epi16(a, b); + let e = _mm256_set1_epi16(-2); + assert_eq_m256i(r, e); + } + test_mm256_sign_epi16(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_sign_epi32() { + let a = _mm256_set1_epi32(2); + let b = _mm256_set1_epi32(-1); + let r = _mm256_sign_epi32(a, b); + let e = _mm256_set1_epi32(-2); + assert_eq_m256i(r, e); + } + test_mm256_sign_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_sign_epi8() { + let a = _mm256_set1_epi8(2); + let b = _mm256_set1_epi8(-1); + let r = _mm256_sign_epi8(a, b); + let e = _mm256_set1_epi8(-2); + assert_eq_m256i(r, e); + } + test_mm256_sign_epi8(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_sll_epi16() { + let a = _mm256_setr_epi16( + 0x88, -0x88, 0x99, -0x99, 0xAA, -0xAA, 0xBB, -0xBB, 0xCC, -0xCC, 0xDD, -0xDD, 0xEE, + -0xEE, 0xFF, -0xFF, + ); + let r = _mm256_sll_epi16(a, _mm_set_epi64x(0, 4)); + assert_eq_m256i( + r, + _mm256_setr_epi16( + 0x880, -0x880, 0x990, -0x990, 0xAA0, -0xAA0, 0xBB0, -0xBB0, 0xCC0, -0xCC0, 0xDD0, + -0xDD0, 0xEE0, -0xEE0, 0xFF0, -0xFF0, + ), + ); + let r = _mm256_sll_epi16(a, _mm_set_epi64x(4, 0)); + assert_eq_m256i(r, a); + let r = _mm256_sll_epi16(a, _mm_set_epi64x(0, 16)); + assert_eq_m256i(r, _mm256_set1_epi16(0)); + let r = _mm256_sll_epi16(a, _mm_set_epi64x(0, i64::MAX)); + assert_eq_m256i(r, _mm256_set1_epi16(0)); + } + test_mm256_sll_epi16(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_sll_epi32() { + let a = + _mm256_setr_epi32(0xCCCC, -0xCCCC, 0xDDDD, -0xDDDD, 0xEEEE, -0xEEEE, 0xFFFF, -0xFFFF); + let r = _mm256_sll_epi32(a, _mm_set_epi64x(0, 4)); + assert_eq_m256i( + r, + _mm256_setr_epi32( + 0xCCCC0, -0xCCCC0, 0xDDDD0, -0xDDDD0, 0xEEEE0, -0xEEEE0, 0xFFFF0, -0xFFFF0, + ), + ); + let r = _mm256_sll_epi32(a, _mm_set_epi64x(4, 0)); + assert_eq_m256i(r, a); + let r = _mm256_sll_epi32(a, _mm_set_epi64x(0, 32)); + assert_eq_m256i(r, _mm256_set1_epi32(0)); + let r = _mm256_sll_epi32(a, _mm_set_epi64x(0, i64::MAX)); + assert_eq_m256i(r, _mm256_set1_epi32(0)); + } + test_mm256_sll_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_sll_epi64() { + let a = _mm256_set_epi64x(0xEEEEEEEE, -0xEEEEEEEE, 0xFFFFFFFF, -0xFFFFFFFF); + let r = _mm256_sll_epi64(a, _mm_set_epi64x(0, 4)); + assert_eq_m256i(r, _mm256_set_epi64x(0xEEEEEEEE0, -0xEEEEEEEE0, 0xFFFFFFFF0, -0xFFFFFFFF0)); + let r = _mm256_sll_epi64(a, _mm_set_epi64x(4, 0)); + assert_eq_m256i(r, a); + let r = _mm256_sll_epi64(a, _mm_set_epi64x(0, 64)); + assert_eq_m256i(r, _mm256_set1_epi64x(0)); + let r = _mm256_sll_epi64(a, _mm_set_epi64x(0, i64::MAX)); + assert_eq_m256i(r, _mm256_set1_epi64x(0)); + } + test_mm256_sll_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_sra_epi16() { + let a = _mm256_setr_epi16( + 0x88, -0x88, 0x99, -0x99, 0xAA, -0xAA, 0xBB, -0xBB, 0xCC, -0xCC, 0xDD, -0xDD, 0xEE, + -0xEE, 0xFF, -0xFF, + ); + let r = _mm256_sra_epi16(a, _mm_set_epi64x(0, 4)); + assert_eq_m256i( + r, + _mm256_setr_epi16( + 0x8, -0x9, 0x9, -0xA, 0xA, -0xB, 0xB, -0xC, 0xC, -0xD, 0xD, -0xE, 0xE, -0xF, 0xF, + -0x10, + ), + ); + let r = _mm256_sra_epi16(a, _mm_set_epi64x(4, 0)); + assert_eq_m256i(r, a); + let r = _mm256_sra_epi16(a, _mm_set_epi64x(0, 16)); + assert_eq_m256i( + r, + _mm256_setr_epi16(0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1), + ); + let r = _mm256_sra_epi16(a, _mm_set_epi64x(0, i64::MAX)); + assert_eq_m256i( + r, + _mm256_setr_epi16(0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1), + ); + } + test_mm256_sra_epi16(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_sra_epi32() { + let a = + _mm256_setr_epi32(0xCCCC, -0xCCCC, 0xDDDD, -0xDDDD, 0xEEEE, -0xEEEE, 0xFFFF, -0xFFFF); + let r = _mm256_sra_epi32(a, _mm_set_epi64x(0, 4)); + assert_eq_m256i( + r, + _mm256_setr_epi32(0xCCC, -0xCCD, 0xDDD, -0xDDE, 0xEEE, -0xEEF, 0xFFF, -0x1000), + ); + let r = _mm256_sra_epi32(a, _mm_set_epi64x(4, 0)); + assert_eq_m256i(r, a); + let r = _mm256_sra_epi32(a, _mm_set_epi64x(0, 32)); + assert_eq_m256i(r, _mm256_setr_epi32(0, -1, 0, -1, 0, -1, 0, -1)); + let r = _mm256_sra_epi32(a, _mm_set_epi64x(0, i64::MAX)); + assert_eq_m256i(r, _mm256_setr_epi32(0, -1, 0, -1, 0, -1, 0, -1)); + } + test_mm256_sra_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_srl_epi16() { + let a = _mm256_setr_epi16( + 0x88, -0x88, 0x99, -0x99, 0xAA, -0xAA, 0xBB, -0xBB, 0xCC, -0xCC, 0xDD, -0xDD, 0xEE, + -0xEE, 0xFF, -0xFF, + ); + let r = _mm256_srl_epi16(a, _mm_set_epi64x(0, 4)); + assert_eq_m256i( + r, + _mm256_setr_epi16( + 0x8, 0xFF7, 0x9, 0xFF6, 0xA, 0xFF5, 0xB, 0xFF4, 0xC, 0xFF3, 0xD, 0xFF2, 0xE, 0xFF1, + 0xF, 0xFF0, + ), + ); + let r = _mm256_srl_epi16(a, _mm_set_epi64x(4, 0)); + assert_eq_m256i(r, a); + let r = _mm256_srl_epi16(a, _mm_set_epi64x(0, 16)); + assert_eq_m256i(r, _mm256_set1_epi16(0)); + let r = _mm256_srl_epi16(a, _mm_set_epi64x(0, i64::MAX)); + assert_eq_m256i(r, _mm256_set1_epi16(0)); + } + test_mm256_srl_epi16(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_srl_epi32() { + let a = + _mm256_setr_epi32(0xCCCC, -0xCCCC, 0xDDDD, -0xDDDD, 0xEEEE, -0xEEEE, 0xFFFF, -0xFFFF); + let r = _mm256_srl_epi32(a, _mm_set_epi64x(0, 4)); + assert_eq_m256i( + r, + _mm256_setr_epi32( + 0xCCC, 0xFFFF333, 0xDDD, 0xFFFF222, 0xEEE, 0xFFFF111, 0xFFF, 0xFFFF000, + ), + ); + let r = _mm256_srl_epi32(a, _mm_set_epi64x(4, 0)); + assert_eq_m256i(r, a); + let r = _mm256_srl_epi32(a, _mm_set_epi64x(0, 32)); + assert_eq_m256i(r, _mm256_set1_epi32(0)); + let r = _mm256_srl_epi32(a, _mm_set_epi64x(0, i64::MAX)); + assert_eq_m256i(r, _mm256_set1_epi32(0)); + } + test_mm256_srl_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_srl_epi64() { + let a = _mm256_set_epi64x(0xEEEEEEEE, -0xEEEEEEEE, 0xFFFFFFFF, -0xFFFFFFFF); + let r = _mm256_srl_epi64(a, _mm_set_epi64x(0, 4)); + assert_eq_m256i( + r, + _mm256_set_epi64x(0xEEEEEEE, 0xFFFFFFFF1111111, 0xFFFFFFF, 0xFFFFFFFF0000000), + ); + let r = _mm256_srl_epi64(a, _mm_set_epi64x(4, 0)); + assert_eq_m256i(r, a); + let r = _mm256_srl_epi64(a, _mm_set_epi64x(0, 64)); + assert_eq_m256i(r, _mm256_set1_epi64x(0)); + let r = _mm256_srl_epi64(a, _mm_set_epi64x(0, i64::MAX)); + assert_eq_m256i(r, _mm256_set1_epi64x(0)); + } + test_mm256_srl_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_sllv_epi32() { + let a = _mm_set_epi32(1, 2, 3, 4); + let b = _mm_set_epi32(4, 3, 2, 1); + let r = _mm_sllv_epi32(a, b); + let e = _mm_set_epi32(16, 16, 12, 8); + assert_eq_m128i(r, e); + } + test_mm_sllv_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_sllv_epi32() { + let a = _mm256_set_epi32(1, 2, 3, 4, 5, 6, 7, 8); + let b = _mm256_set_epi32(8, 7, 6, 5, 4, 3, 2, 1); + let r = _mm256_sllv_epi32(a, b); + let e = _mm256_set_epi32(256, 256, 192, 128, 80, 48, 28, 16); + assert_eq_m256i(r, e); + } + test_mm256_sllv_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_sllv_epi64() { + let a = _mm_set_epi64x(2, 3); + let b = _mm_set_epi64x(1, 2); + let r = _mm_sllv_epi64(a, b); + let e = _mm_set_epi64x(4, 12); + assert_eq_m128i(r, e); + } + test_mm_sllv_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_sllv_epi64() { + let a = _mm256_set_epi64x(1, 2, 3, 4); + let b = _mm256_set_epi64x(4, 3, 2, 1); + let r = _mm256_sllv_epi64(a, b); + let e = _mm256_set_epi64x(16, 16, 12, 8); + assert_eq_m256i(r, e); + } + test_mm256_sllv_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_srav_epi32() { + let a = _mm_set_epi32(16, -32, 64, -128); + let b = _mm_set_epi32(4, 3, 2, 1); + let r = _mm_srav_epi32(a, b); + let e = _mm_set_epi32(1, -4, 16, -64); + assert_eq_m128i(r, e); + } + test_mm_srav_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_srav_epi32() { + let a = _mm256_set_epi32(256, -512, 1024, -2048, 4096, -8192, 16384, -32768); + let b = _mm256_set_epi32(8, 7, 6, 5, 4, 3, 2, 1); + let r = _mm256_srav_epi32(a, b); + let e = _mm256_set_epi32(1, -4, 16, -64, 256, -1024, 4096, -16384); + assert_eq_m256i(r, e); + } + test_mm256_srav_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_srlv_epi32() { + let a = _mm_set_epi32(16, 32, 64, 128); + let b = _mm_set_epi32(4, 3, 2, 1); + let r = _mm_srlv_epi32(a, b); + let e = _mm_set_epi32(1, 4, 16, 64); + assert_eq_m128i(r, e); + } + test_mm_srlv_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_srlv_epi32() { + let a = _mm256_set_epi32(256, 512, 1024, 2048, 4096, 8192, 16384, 32768); + let b = _mm256_set_epi32(8, 7, 6, 5, 4, 3, 2, 1); + let r = _mm256_srlv_epi32(a, b); + let e = _mm256_set_epi32(1, 4, 16, 64, 256, 1024, 4096, 16384); + assert_eq_m256i(r, e); + } + test_mm256_srlv_epi32(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm_srlv_epi64() { + let a = _mm_set_epi64x(4, 8); + let b = _mm_set_epi64x(2, 1); + let r = _mm_srlv_epi64(a, b); + let e = _mm_set_epi64x(1, 4); + assert_eq_m128i(r, e); + } + test_mm_srlv_epi64(); + + #[target_feature(enable = "avx2")] + unsafe fn test_mm256_srlv_epi64() { + let a = _mm256_set_epi64x(16, 32, 64, 128); + let b = _mm256_set_epi64x(4, 3, 2, 1); + let r = _mm256_srlv_epi64(a, b); + let e = _mm256_set_epi64x(1, 4, 16, 64); + assert_eq_m256i(r, e); + } + test_mm256_srlv_epi64(); +} + +#[target_feature(enable = "sse2")] +unsafe fn _mm_setr_epi64x(a: i64, b: i64) -> __m128i { + _mm_set_epi64x(b, a) +} + +#[track_caller] +#[target_feature(enable = "sse")] +unsafe fn assert_eq_m128(a: __m128, b: __m128) { + let r = _mm_cmpeq_ps(a, b); + if _mm_movemask_ps(r) != 0b1111 { + panic!("{:?} != {:?}", a, b); + } +} + +#[track_caller] +#[target_feature(enable = "sse2")] +unsafe fn assert_eq_m128d(a: __m128d, b: __m128d) { + if _mm_movemask_pd(_mm_cmpeq_pd(a, b)) != 0b11 { + panic!("{:?} != {:?}", a, b); + } +} + +#[track_caller] +#[target_feature(enable = "sse2")] +unsafe fn assert_eq_m128i(a: __m128i, b: __m128i) { + assert_eq!(transmute::<_, [u64; 2]>(a), transmute::<_, [u64; 2]>(b)) +} + +#[track_caller] +#[target_feature(enable = "avx")] +unsafe fn assert_eq_m256(a: __m256, b: __m256) { + let cmp = _mm256_cmp_ps::<_CMP_EQ_OQ>(a, b); + if _mm256_movemask_ps(cmp) != 0b11111111 { + panic!("{:?} != {:?}", a, b); + } +} + +#[track_caller] +#[target_feature(enable = "avx")] +unsafe fn assert_eq_m256d(a: __m256d, b: __m256d) { + let cmp = _mm256_cmp_pd::<_CMP_EQ_OQ>(a, b); + if _mm256_movemask_pd(cmp) != 0b1111 { + panic!("{:?} != {:?}", a, b); + } +} + +#[track_caller] +#[target_feature(enable = "avx")] +unsafe fn assert_eq_m256i(a: __m256i, b: __m256i) { + assert_eq!(transmute::<_, [u64; 4]>(a), transmute::<_, [u64; 4]>(b)) +} + +/// Stores `T` in an unaligned address +struct Unaligned { + buf: Vec, + offset: bool, + _marker: std::marker::PhantomData, +} + +impl Unaligned { + fn new(value: T) -> Self { + // Allocate extra byte for unalignment headroom + let len = std::mem::size_of::(); + let mut buf = Vec::::with_capacity(len + 1); + // Force the address to be a non-multiple of 2, so it is as unaligned as it can get. + let offset = (buf.as_ptr() as usize % 2) == 0; + let value_ptr: *const T = &value; + unsafe { + buf.as_mut_ptr().add(offset.into()).copy_from_nonoverlapping(value_ptr.cast(), len); + } + Self { buf, offset, _marker: std::marker::PhantomData } + } + + fn as_ptr(&self) -> *const T { + unsafe { self.buf.as_ptr().add(self.offset.into()).cast() } + } + + fn as_mut_ptr(&mut self) -> *mut T { + unsafe { self.buf.as_mut_ptr().add(self.offset.into()).cast() } + } + + fn read(&self) -> T { + unsafe { self.as_ptr().read_unaligned() } + } +} diff --git a/tests/pass/intrinsics-x86-avx512.rs b/tests/pass/shims/x86/intrinsics-x86-avx512.rs similarity index 100% rename from tests/pass/intrinsics-x86-avx512.rs rename to tests/pass/shims/x86/intrinsics-x86-avx512.rs diff --git a/tests/pass/intrinsics-x86-pause-without-sse2.rs b/tests/pass/shims/x86/intrinsics-x86-pause-without-sse2.rs similarity index 100% rename from tests/pass/intrinsics-x86-pause-without-sse2.rs rename to tests/pass/shims/x86/intrinsics-x86-pause-without-sse2.rs diff --git a/tests/pass/intrinsics-x86-sse.rs b/tests/pass/shims/x86/intrinsics-x86-sse.rs similarity index 100% rename from tests/pass/intrinsics-x86-sse.rs rename to tests/pass/shims/x86/intrinsics-x86-sse.rs diff --git a/tests/pass/intrinsics-x86-sse2.rs b/tests/pass/shims/x86/intrinsics-x86-sse2.rs similarity index 100% rename from tests/pass/intrinsics-x86-sse2.rs rename to tests/pass/shims/x86/intrinsics-x86-sse2.rs diff --git a/tests/pass/intrinsics-x86-sse3-ssse3.rs b/tests/pass/shims/x86/intrinsics-x86-sse3-ssse3.rs similarity index 100% rename from tests/pass/intrinsics-x86-sse3-ssse3.rs rename to tests/pass/shims/x86/intrinsics-x86-sse3-ssse3.rs diff --git a/tests/pass/intrinsics-x86-sse41.rs b/tests/pass/shims/x86/intrinsics-x86-sse41.rs similarity index 100% rename from tests/pass/intrinsics-x86-sse41.rs rename to tests/pass/shims/x86/intrinsics-x86-sse41.rs diff --git a/tests/pass/intrinsics-x86.rs b/tests/pass/shims/x86/intrinsics-x86.rs similarity index 100% rename from tests/pass/intrinsics-x86.rs rename to tests/pass/shims/x86/intrinsics-x86.rs diff --git a/tests/pass/stacked-borrows/coroutine-self-referential.rs b/tests/pass/stacked-borrows/coroutine-self-referential.rs index c4b15c8758..259fc72d39 100644 --- a/tests/pass/stacked-borrows/coroutine-self-referential.rs +++ b/tests/pass/stacked-borrows/coroutine-self-referential.rs @@ -1,6 +1,6 @@ // See https://github.com/rust-lang/unsafe-code-guidelines/issues/148: // this fails when Stacked Borrows is strictly applied even to `!Unpin` types. -#![feature(coroutines, coroutine_trait)] +#![feature(coroutines, coroutine_trait, stmt_expr_attributes)] use std::{ ops::{Coroutine, CoroutineState}, @@ -8,6 +8,7 @@ use std::{ }; fn firstn() -> impl Coroutine { + #[coroutine] static move || { let mut num = 0; let num = &mut num; diff --git a/tests/pass/stacked-borrows/int-to-ptr.rs b/tests/pass/stacked-borrows/int-to-ptr.rs index 5622bf1865..c89d79b42e 100644 --- a/tests/pass/stacked-borrows/int-to-ptr.rs +++ b/tests/pass/stacked-borrows/int-to-ptr.rs @@ -17,7 +17,7 @@ fn example(variant: bool) { unsafe { fn not_so_innocent(x: &mut u32) -> usize { let x_raw4 = x as *mut u32; - x_raw4.expose_addr() + x_raw4.expose_provenance() } let mut c = 42u32; @@ -26,7 +26,7 @@ fn example(variant: bool) { // stack: [..., Unique(1)] let x_raw2 = x_unique1 as *mut u32; - let x_raw2_addr = x_raw2.expose_addr(); + let x_raw2_addr = x_raw2.expose_provenance(); // stack: [..., Unique(1), SharedRW(2)] let x_unique3 = &mut *x_raw2; diff --git a/tests/pass/stacked-borrows/stacked-borrows.rs b/tests/pass/stacked-borrows/stacked-borrows.rs index 734411ccc7..c75824d7f9 100644 --- a/tests/pass/stacked-borrows/stacked-borrows.rs +++ b/tests/pass/stacked-borrows/stacked-borrows.rs @@ -20,6 +20,7 @@ fn main() { wide_raw_ptr_in_tuple(); not_unpin_not_protected(); write_does_not_invalidate_all_aliases(); + box_into_raw_allows_interior_mutable_alias(); } // Make sure that reading from an `&mut` does, like reborrowing to `&`, @@ -263,3 +264,16 @@ fn write_does_not_invalidate_all_aliases() { other::lib2(); assert_eq!(*x, 1337); // oops, the value changed! I guess not all pointers were invalidated } + +fn box_into_raw_allows_interior_mutable_alias() { + unsafe { + let b = Box::new(std::cell::Cell::new(42)); + let raw = Box::into_raw(b); + let c = &*raw; + let d = raw.cast::(); // bypassing `Cell` -- only okay in Miri tests + // `c` and `d` should permit arbitrary aliasing with each other now. + *d = 1; + c.set(2); + drop(Box::from_raw(raw)); + } +} diff --git a/tests/pass/stacked-borrows/unknown-bottom-gc.rs b/tests/pass/stacked-borrows/unknown-bottom-gc.rs index 6e177a6e4a..55356814a1 100644 --- a/tests/pass/stacked-borrows/unknown-bottom-gc.rs +++ b/tests/pass/stacked-borrows/unknown-bottom-gc.rs @@ -9,7 +9,7 @@ fn main() { // Expose the allocation and use the exposed pointer, creating an unknown bottom unsafe { - let p: *mut u8 = ptr::with_exposed_provenance::(ptr.expose_addr()) as *mut u8; + let p: *mut u8 = ptr::with_exposed_provenance::(ptr.expose_provenance()) as *mut u8; *p = 1; } diff --git a/tests/pass/track-caller-attribute.rs b/tests/pass/track-caller-attribute.rs index d88bcc9885..c3803af3cc 100644 --- a/tests/pass/track-caller-attribute.rs +++ b/tests/pass/track-caller-attribute.rs @@ -232,7 +232,7 @@ fn test_coroutine() { } #[rustfmt::skip] - let coroutine = #[track_caller] |arg: String| { + let coroutine = #[track_caller] #[coroutine] |arg: String| { yield ("first", arg.clone(), Location::caller()); yield ("second", arg.clone(), Location::caller()); }; @@ -255,7 +255,7 @@ fn test_coroutine() { assert_eq!(mono_loc.column(), 42); #[rustfmt::skip] - let non_tracked_coroutine = || { yield Location::caller(); }; + let non_tracked_coroutine = #[coroutine] || { yield Location::caller(); }; let non_tracked_line = line!() - 1; // This is the line of the coroutine, not its caller let non_tracked_loc = match Box::pin(non_tracked_coroutine).as_mut().resume(()) { CoroutineState::Yielded(val) => val, @@ -263,7 +263,7 @@ fn test_coroutine() { }; assert_eq!(non_tracked_loc.file(), file!()); assert_eq!(non_tracked_loc.line(), non_tracked_line); - assert_eq!(non_tracked_loc.column(), 44); + assert_eq!(non_tracked_loc.column(), 57); } fn main() { diff --git a/tests/pass/tree_borrows/reserved.rs b/tests/pass/tree_borrows/reserved.rs index 87ce91a809..f93cac8361 100644 --- a/tests/pass/tree_borrows/reserved.rs +++ b/tests/pass/tree_borrows/reserved.rs @@ -27,9 +27,8 @@ fn main() { } } -unsafe fn print(msg: &str) { - utils::miri_write_to_stderr(msg.as_bytes()); - utils::miri_write_to_stderr("\n".as_bytes()); +fn print(msg: &str) { + eprintln!("{msg}"); } unsafe fn read_second(x: &mut T, y: *mut u8) { diff --git a/tests/pass/vecdeque.rs b/tests/pass/vecdeque.rs index ccecf3d30a..77c4ca5a04 100644 --- a/tests/pass/vecdeque.rs +++ b/tests/pass/vecdeque.rs @@ -31,8 +31,8 @@ fn main() { } // Regression test for Debug impl's - println!("{:?} {:?}", dst, dst.iter()); - println!("{:?}", VecDeque::::new().iter()); + format!("{:?} {:?}", dst, dst.iter()); + format!("{:?}", VecDeque::::new().iter()); for a in dst { assert_eq!(*a, 2); diff --git a/tests/pass/vecdeque.stack.stdout b/tests/pass/vecdeque.stack.stdout deleted file mode 100644 index 63de960ee2..0000000000 --- a/tests/pass/vecdeque.stack.stdout +++ /dev/null @@ -1,2 +0,0 @@ -[2, 2] Iter([2, 2], []) -Iter([], []) diff --git a/tests/pass/vecdeque.tree.stdout b/tests/pass/vecdeque.tree.stdout deleted file mode 100644 index 63de960ee2..0000000000 --- a/tests/pass/vecdeque.tree.stdout +++ /dev/null @@ -1,2 +0,0 @@ -[2, 2] Iter([2, 2], []) -Iter([], []) diff --git a/tests/pass/vecdeque.tree_uniq.stdout b/tests/pass/vecdeque.tree_uniq.stdout deleted file mode 100644 index 63de960ee2..0000000000 --- a/tests/pass/vecdeque.tree_uniq.stdout +++ /dev/null @@ -1,2 +0,0 @@ -[2, 2] Iter([2, 2], []) -Iter([], []) diff --git a/tests/pass/weak_memory/weak.rs b/tests/pass/weak_memory/weak.rs index e10ccc277f..dac63eeeb0 100644 --- a/tests/pass/weak_memory/weak.rs +++ b/tests/pass/weak_memory/weak.rs @@ -37,6 +37,8 @@ fn relaxed() -> bool { let x = static_atomic(0); let j1 = spawn(move || { x.store(1, Relaxed); + // Preemption is disabled, so the store above will never be the + // latest store visible to another thread. x.store(2, Relaxed); }); @@ -138,6 +140,7 @@ fn faa_replaced_by_load() -> bool { } /// Asserts that the function returns true at least once in 100 runs +#[track_caller] fn assert_once(f: fn() -> bool) { assert!(std::iter::repeat_with(|| f()).take(100).any(|x| x)); } diff --git a/tests/ui.rs b/tests/ui.rs index 7e8d140118..3a8b098ccb 100644 --- a/tests/ui.rs +++ b/tests/ui.rs @@ -1,6 +1,7 @@ use std::ffi::OsString; -use std::num::NonZeroUsize; +use std::num::NonZero; use std::path::{Path, PathBuf}; +use std::sync::OnceLock; use std::{env, process::Command}; use colored::*; @@ -26,11 +27,12 @@ pub fn flagsplit(flags: &str) -> Vec { flags.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string).collect() } -// Build the shared object file for testing external C function calls. -fn build_so_for_c_ffi_tests() -> PathBuf { +// Build the shared object file for testing native function calls. +fn build_native_lib() -> PathBuf { let cc = option_env!("CC").unwrap_or("cc"); // Target directory that we can write to. - let so_target_dir = Path::new(&env::var_os("CARGO_TARGET_DIR").unwrap()).join("miri-extern-so"); + let so_target_dir = + Path::new(&env::var_os("CARGO_TARGET_DIR").unwrap()).join("miri-native-lib"); // Create the directory if it does not already exist. std::fs::create_dir_all(&so_target_dir) .expect("Failed to create directory for shared object file"); @@ -40,18 +42,18 @@ fn build_so_for_c_ffi_tests() -> PathBuf { "-shared", "-o", so_file_path.to_str().unwrap(), - "tests/extern-so/test.c", + "tests/native-lib/test.c", // Only add the functions specified in libcode.version to the shared object file. // This is to avoid automatically adding `malloc`, etc. // Source: https://anadoxin.org/blog/control-over-symbol-exports-in-gcc.html/ "-fPIC", - "-Wl,--version-script=tests/extern-so/libtest.map", + "-Wl,--version-script=tests/native-lib/libtest.map", ]) .output() - .expect("failed to generate shared object file for testing external C function calls"); + .expect("failed to generate shared object file for testing native function calls"); if !cc_output.status.success() { panic!( - "error in generating shared object file for testing external C function calls:\n{}", + "error generating shared object file for testing native function calls:\n{}", String::from_utf8_lossy(&cc_output.stderr), ); } @@ -67,15 +69,15 @@ fn miri_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) -> let mut config = Config { target: Some(target.to_owned()), - stderr_filters: STDERR.clone(), - stdout_filters: STDOUT.clone(), + stderr_filters: stderr_filters().into(), + stdout_filters: stdout_filters().into(), mode, program, out_dir: PathBuf::from(std::env::var_os("CARGO_TARGET_DIR").unwrap()).join("ui"), edition: Some("2021".into()), // keep in sync with `./miri run` threads: std::env::var("MIRI_TEST_THREADS") .ok() - .map(|threads| NonZeroUsize::new(threads.parse().unwrap()).unwrap()), + .map(|threads| NonZero::new(threads.parse().unwrap()).unwrap()), ..Config::rustc(path) }; @@ -112,6 +114,13 @@ fn run_tests( config.program.envs.push(("RUST_BACKTRACE".into(), Some("1".into()))); // Add some flags we always want. + config.program.args.push( + format!( + "--sysroot={}", + env::var("MIRI_SYSROOT").expect("MIRI_SYSROOT must be set to run the ui test suite") + ) + .into(), + ); config.program.args.push("-Dwarnings".into()); config.program.args.push("-Dunused".into()); config.program.args.push("-Ainternal_features".into()); @@ -124,13 +133,12 @@ fn run_tests( config.program.args.push("--target".into()); config.program.args.push(target.into()); - // If we're testing the extern-so functionality, then build the shared object file for testing + // If we're testing the native-lib functionality, then build the shared object file for testing // external C function calls and push the relevant compiler flag. - if path.starts_with("tests/extern-so/") { - assert!(cfg!(target_os = "linux")); - let so_file_path = build_so_for_c_ffi_tests(); - let mut flag = std::ffi::OsString::from("-Zmiri-extern-so-file="); - flag.push(so_file_path.into_os_string()); + if path.starts_with("tests/native-lib/") { + let native_lib = build_native_lib(); + let mut flag = std::ffi::OsString::from("-Zmiri-native-lib="); + flag.push(native_lib.into_os_string()); config.program.args.push(flag); } @@ -167,15 +175,18 @@ fn run_tests( } macro_rules! regexes { - ($name:ident: $($regex:expr => $replacement:expr,)*) => {lazy_static::lazy_static! { - static ref $name: Vec<(Match, &'static [u8])> = vec![ - $((Regex::new($regex).unwrap().into(), $replacement.as_bytes()),)* - ]; - }}; + ($name:ident: $($regex:expr => $replacement:expr,)*) => { + fn $name() -> &'static [(Match, &'static [u8])] { + static S: OnceLock> = OnceLock::new(); + S.get_or_init(|| vec![ + $((Regex::new($regex).unwrap().into(), $replacement.as_bytes()),)* + ]) + } + }; } regexes! { - STDOUT: + stdout_filters: // Windows file paths r"\\" => "/", // erase borrow tags @@ -184,7 +195,7 @@ regexes! { } regexes! { - STDERR: + stderr_filters: // erase line and column info r"\.rs:[0-9]+:[0-9]+(: [0-9]+:[0-9]+)?" => ".rs:LL:CC", // erase alloc ids @@ -281,10 +292,10 @@ fn main() -> Result<()> { tmpdir.path(), )?; if cfg!(target_os = "linux") { - ui(Mode::Pass, "tests/extern-so/pass", &target, WithoutDependencies, tmpdir.path())?; + ui(Mode::Pass, "tests/native-lib/pass", &target, WithoutDependencies, tmpdir.path())?; ui( Mode::Fail { require_patterns: true, rustfix: RustfixMode::Disabled }, - "tests/extern-so/fail", + "tests/native-lib/fail", &target, WithoutDependencies, tmpdir.path(), @@ -296,12 +307,13 @@ fn main() -> Result<()> { fn run_dep_mode(target: String, mut args: impl Iterator) -> Result<()> { let path = args.next().expect("./miri run-dep must be followed by a file name"); - let config = miri_config( + let mut config = miri_config( &target, "", Mode::Yolo { rustfix: RustfixMode::Disabled }, /* with dependencies */ true, ); + config.program.args.clear(); // remove the `--error-format` that ui_test adds by default let dep_args = config.build_dependencies()?; let mut cmd = config.program.build(&config.out_dir); diff --git a/tests/utils/fs.rs b/tests/utils/fs.rs index 0242a22806..7340908626 100644 --- a/tests/utils/fs.rs +++ b/tests/utils/fs.rs @@ -1,4 +1,5 @@ use std::ffi::OsString; +use std::fs; use std::path::PathBuf; use super::miri_extern; @@ -27,3 +28,26 @@ pub fn tmp() -> PathBuf { // These are host paths. We need to convert them to the target. host_to_target_path(path) } + +/// Prepare: compute filename and make sure the file does not exist. +pub fn prepare(filename: &str) -> PathBuf { + let path = tmp().join(filename); + // Clean the paths for robustness. + fs::remove_file(&path).ok(); + path +} + +/// Prepare like above, and also write some initial content to the file. +pub fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf { + let path = prepare(filename); + fs::write(&path, content).unwrap(); + path +} + +/// Prepare directory: compute directory name and make sure it does not exist. +pub fn prepare_dir(dirname: &str) -> PathBuf { + let path = tmp().join(&dirname); + // Clean the directory for robustness. + fs::remove_dir_all(&path).ok(); + path +} diff --git a/tests/utils/io.rs b/tests/utils/io.rs new file mode 100644 index 0000000000..e3eaa6c468 --- /dev/null +++ b/tests/utils/io.rs @@ -0,0 +1,25 @@ +use core::fmt::{self, Write}; + +use super::miri_extern; + +pub struct MiriStderr; + +impl Write for MiriStderr { + fn write_str(&mut self, s: &str) -> fmt::Result { + unsafe { + miri_extern::miri_write_to_stderr(s.as_bytes()); + } + Ok(()) + } +} + +pub struct MiriStdout; + +impl Write for MiriStdout { + fn write_str(&mut self, s: &str) -> fmt::Result { + unsafe { + miri_extern::miri_write_to_stdout(s.as_bytes()); + } + Ok(()) + } +} diff --git a/tests/utils/miri_extern.rs b/tests/utils/miri_extern.rs index e2983f6c71..d6c43b1882 100644 --- a/tests/utils/miri_extern.rs +++ b/tests/utils/miri_extern.rs @@ -133,8 +133,8 @@ extern "Rust" { /// with a null terminator. /// Returns 0 if the `out` buffer was large enough, and the required size otherwise. pub fn miri_host_to_target_path( - path: *const std::ffi::c_char, - out: *mut std::ffi::c_char, + path: *const core::ffi::c_char, + out: *mut core::ffi::c_char, out_size: usize, ) -> usize; diff --git a/tests/utils/mod.no_std.rs b/tests/utils/mod.no_std.rs new file mode 100644 index 0000000000..aaf2bf50c4 --- /dev/null +++ b/tests/utils/mod.no_std.rs @@ -0,0 +1,11 @@ +#![allow(dead_code)] +#![allow(unused_imports)] + +#[macro_use] +mod macros; + +mod io; +mod miri_extern; + +pub use self::io::*; +pub use self::miri_extern::*; diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index cb9380f575..138ada4e20 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -5,9 +5,11 @@ mod macros; mod fs; +mod io; mod miri_extern; pub use self::fs::*; +pub use self::io::*; pub use self::miri_extern::*; pub fn run_provenance_gc() { diff --git a/triagebot.toml b/triagebot.toml index 3b767b3e62..addb36418d 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -10,5 +10,10 @@ allow-unauthenticated = [ # Gives us the commands 'ready', 'author', 'blocked' [shortcut] +# Enables assigning users to issues and PRs. +[assign] +warn_non_default_branch = true +contributing_url = "https://github.com/rust-lang/miri/blob/master/CONTRIBUTING.md" + [no-merges] exclude_titles = ["Rustup"]