Add 'src/tools/miri/' from commit '75dd959a3a40eb5b4574f8d2e23aa6efbeb33573'

git-subtree-dir: src/tools/miri
git-subtree-mainline: 3f3167fb59
git-subtree-split: 75dd959a3a
This commit is contained in:
Oli Scherer 2022-09-21 15:36:26 +00:00
commit f45b570e08
1196 changed files with 54796 additions and 0 deletions

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4
[*.rs]
indent_style = space
indent_size = 4
[*.toml]
indent_style = space
indent_size = 4
[*.md]
trim_trailing_whitespace = false

6
src/tools/miri/.gitattributes vendored Normal file
View File

@ -0,0 +1,6 @@
* text=auto eol=lf
# Older git versions try to fix line endings on images, this prevents it.
*.png binary
*.jpg binary
*.ico binary

156
src/tools/miri/.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,156 @@
name: CI
on:
push:
# Run in PRs and for bors, but not on master.
branches:
- 'auto'
- 'try'
pull_request:
branches:
- 'master'
schedule:
- cron: '5 15 * * *' # At 15:05 UTC every day.
jobs:
build:
runs-on: ${{ matrix.os }}
env:
RUST_BACKTRACE: 1
HOST_TARGET: ${{ matrix.host_target }}
strategy:
matrix:
build: [linux64, macos, win32]
include:
- build: linux64
os: ubuntu-latest
host_target: x86_64-unknown-linux-gnu
- build: macos
os: macos-latest
host_target: x86_64-apple-darwin
- build: win32
os: windows-latest
host_target: i686-pc-windows-msvc
steps:
- uses: actions/checkout@v3
- name: Set the tag GC interval to 1 on linux
if: runner.os == 'Linux'
run: echo "MIRIFLAGS=-Zmiri-tag-gc=1" >> $GITHUB_ENV
# We install gnu-tar because BSD tar is buggy on macOS builders of GHA.
# See <https://github.com/actions/cache/issues/403>.
- name: Install GNU tar
if: runner.os == 'macOS'
run: |
brew install gnu-tar
echo "/usr/local/opt/gnu-tar/libexec/gnubin" >> $GITHUB_PATH
# Cache the global cargo directory, but NOT the local `target` directory which
# we cannot reuse anyway when the nightly changes (and it grows quite large
# over time).
- name: Add cache for cargo
id: cache
uses: actions/cache@v3
with:
path: |
# Taken from <https://doc.rust-lang.org/nightly/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci>.
~/.cargo/bin
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
# contains package information of crates installed via `cargo install`.
~/.cargo/.crates.toml
~/.cargo/.crates2.json
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock', 'cargo-miri/src/version.rs') }}
restore-keys: ${{ runner.os }}-cargo
- name: Install rustup-toolchain-install-master and xargo
if: ${{ steps.cache.outputs.cache-hit == 'false' }}
shell: bash
run: |
cargo install rustup-toolchain-install-master
cargo install xargo
- name: Install "master" toolchain
shell: bash
run: |
if [[ ${{ github.event_name }} == 'schedule' ]]; then
./rustup-toolchain HEAD --host ${{ matrix.host_target }}
else
./rustup-toolchain "" --host ${{ matrix.host_target }}
fi
- name: Show Rust version
run: |
rustup show
rustc -Vv
cargo -V
- name: Test
run: bash ./ci.sh
style:
name: style checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install required toolchain
# We need a toolchain that can actually build Miri, just a nightly won't do.
run: |
cargo install rustup-toolchain-install-master # TODO: cache this?
./rustup-toolchain "" -c clippy
- name: rustfmt
run: ./miri fmt --check
- name: clippy
run: ./miri clippy -- -D warnings
- name: rustdoc
run: RUSTDOCFLAGS="-Dwarnings" cargo doc --document-private-items
# These jobs doesn't actually test anything, but they're only used to tell
# bors the build completed, as there is no practical way to detect when a
# workflow is successful listening to webhooks only.
#
# ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
# (`fmt` is deliberately not listed, we want bors to ignore it.)
end-success:
name: bors build finished
runs-on: ubuntu-latest
needs: [build, style]
if: github.event.pusher.name == 'bors' && success()
steps:
- name: mark the job as a success
run: exit 0
end-failure:
name: bors build finished
runs-on: ubuntu-latest
needs: [build, style]
if: github.event.pusher.name == 'bors' && (failure() || cancelled())
steps:
- name: mark the job as a failure
run: exit 1
# Send a Zulip notification when a cron job fails
cron-fail-notify:
name: cronjob failure notification
runs-on: ubuntu-latest
needs: [build, style]
if: github.event_name == 'schedule' && (failure() || cancelled())
steps:
- name: Install zulip-send
run: pip3 install zulip
- name: Send Zulip notification
shell: bash
env:
ZULIP_BOT_EMAIL: ${{ secrets.ZULIP_BOT_EMAIL }}
ZULIP_API_TOKEN: ${{ secrets.ZULIP_API_TOKEN }}
run: |
~/.local/bin/zulip-send --stream miri --subject "Cron Job Failure (miri, $(date -u +%Y-%m))" \
--message 'Dear @*T-miri*,
It would appear that the Miri cron job build failed. Would you mind investigating this issue?
Thanks in advance!
Sincerely,
The Miri Cronjobs Bot' \
--user $ZULIP_BOT_EMAIL --api-key $ZULIP_API_TOKEN --site https://rust-lang.zulipchat.com

13
src/tools/miri/.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
target
/doc
tex/*/out
*.dot
*.out
*.rs.bk
.vscode
*.mm_profdata
perf.data
perf.data.old
flamegraph.svg
tests/extern-so/libtestlib.so
.auto-*

View File

@ -0,0 +1,9 @@
image: ubuntu:latest
tasks:
- before: echo "..."
init: |
cargo install rustup-toolchain-install-master
./rustup-toolchain
./miri build
command: echo "Run tests with ./miri test"

View File

@ -0,0 +1,277 @@
# Contribution Guide
If you want to hack on Miri yourself, great! Here are some resources you might
find useful.
## Getting started
Check out the issues on this GitHub repository for some ideas. In particular,
look for the green `E-*` labels which mark issues that should be rather
well-suited for onboarding. For more ideas or help with hacking on Miri, you can
contact us (`oli-obk` and `RalfJ`) on the [Rust Zulip].
[Rust Zulip]: https://rust-lang.zulipchat.com
## Preparing the build environment
Miri heavily relies on internal and unstable rustc interfaces to execute MIR,
which means it is important that you install a version of rustc that Miri
actually works with.
The `rust-version` file contains the commit hash of rustc that Miri is currently
tested against. Other versions will likely not work. After installing
[`rustup-toolchain-install-master`], you can run the following command to
install that exact version of rustc as a toolchain:
```
./rustup-toolchain
```
This will set up a rustup toolchain called `miri` and set it as an override for
the current directory.
You can also create a `.auto-everything` file (contents don't matter, can be empty), which
will cause any `./miri` command to automatically call `rustup-toolchain`, `clippy` and `rustfmt`
for you. If you don't want all of these to happen, you can add individual `.auto-toolchain`,
`.auto-clippy` and `.auto-fmt` files respectively.
[`rustup-toolchain-install-master`]: https://github.com/kennytm/rustup-toolchain-install-master
## Building and testing Miri
Invoking Miri requires getting a bunch of flags right and setting up a custom
sysroot with xargo. The `miri` script takes care of that for you. With the
build environment prepared, compiling Miri is just one command away:
```
./miri build
```
Run `./miri` without arguments to see the other commands our build tool
supports.
### Testing the Miri driver
The Miri driver compiled from `src/bin/miri.rs` is the "heart" of Miri: it is
basically a version of `rustc` that, instead of compiling your code, runs it.
It accepts all the same flags as `rustc` (though the ones only affecting code
generation and linking obviously will have no effect) [and more][miri-flags].
[miri-flags]: README.md#miri--z-flags-and-environment-variables
For example, you can (cross-)run the driver on a particular file by doing
```sh
./miri run tests/pass/format.rs
./miri run tests/pass/hello.rs --target i686-unknown-linux-gnu
```
and you can (cross-)run the entire test suite using:
```
./miri test
MIRI_TEST_TARGET=i686-unknown-linux-gnu ./miri test
```
If your target doesn't support libstd, you can run miri with
```
MIRI_NO_STD=1 MIRI_TEST_TARGET=thumbv7em-none-eabihf ./miri test tests/fail/alloc/no_global_allocator.rs
MIRI_NO_STD=1 ./miri run tests/pass/no_std.rs --target thumbv7em-none-eabihf
```
to avoid attempting (and failing) to build libstd. Note that almost no tests will pass
this way, but you can run individual tests.
`./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).
You can get a trace of which MIR statements are being executed by setting the
`MIRI_LOG` environment variable. For example:
```sh
MIRI_LOG=info ./miri run tests/pass/vec.rs
```
Setting `MIRI_LOG` like this will configure logging for Miri itself as well as
the `rustc_middle::mir::interpret` and `rustc_mir::interpret` modules in rustc. You
can also do more targeted configuration, e.g. the following helps debug the
stacked borrows implementation:
```sh
MIRI_LOG=rustc_mir::interpret=info,miri::stacked_borrows ./miri run tests/pass/vec.rs
```
In addition, you can set `MIRI_BACKTRACE=1` to get a backtrace of where an
evaluation error was originally raised.
#### UI testing
We use ui-testing in Miri, meaning we generate `.stderr` and `.stdout` files for the output
produced by Miri. You can use `./miri bless` to automatically (re)generate these files when
you add new tests or change how Miri presents certain output.
Note that when you also use `MIRIFLAGS` to change optimizations and similar, the ui output
will change in unexpected ways. In order to still be able
to run the other checks while ignoring the ui output, use `MIRI_SKIP_UI_CHECKS=1 ./miri test`.
For more info on how to configure ui tests see [the documentation on the ui test crate][ui_test]
[ui_test]: ui_test/README.md
### Testing `cargo miri`
Working with the driver directly gives you full control, but you also lose all
the convenience provided by cargo. Once your test case depends on a crate, it
is probably easier to test it with the cargo wrapper. You can install your
development version of Miri using
```
./miri install
```
and then you can use it as if it was installed by `rustup`. Make sure you use
the same toolchain when calling `cargo miri` that you used when installing Miri!
Usually this means you have to write `cargo +miri miri ...` to select the `miri`
toolchain that was installed by `./rustup-toolchain`.
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.
### Using a modified standard library
Miri re-builds the standard library into a custom sysroot, so it is fairly easy
to test Miri against a modified standard library -- you do not even have to
build Miri yourself, the Miri shipped by `rustup` will work. All you have to do
is set the `MIRI_LIB_SRC` environment variable to the `library` folder of a
`rust-lang/rust` repository checkout. Note that changing files in that directory
does not automatically trigger a re-build of the standard library; you have to
clear the Miri build cache manually (on Linux, `rm -rf ~/.cache/miri`;
and on Windows, `rmdir /S "%LOCALAPPDATA%\rust-lang\miri\cache"`).
### Benchmarking
Miri comes with a few benchmarks; you can run `./miri bench` to run them with the locally built
Miri. Note: this will run `./miri install` as a side-effect. Also requires `hyperfine` to be
installed (`cargo install hyperfine`).
## Configuring `rust-analyzer`
To configure `rust-analyzer` and VS Code for working on Miri, save the following
to `.vscode/settings.json` in your local Miri clone:
```json
{
"rust-analyzer.rustc.source": "discover",
"rust-analyzer.linkedProjects": [
"./Cargo.toml",
"./cargo-miri/Cargo.toml"
],
"rust-analyzer.checkOnSave.overrideCommand": [
"env",
"MIRI_AUTO_OPS=no",
"./miri",
"cargo",
"clippy", // make this `check` when working with a locally built rustc
"--message-format=json"
],
// Contrary to what the name suggests, this also affects proc macros.
"rust-analyzer.cargo.buildScripts.overrideCommand": [
"env",
"MIRI_AUTO_OPS=no",
"./miri",
"cargo",
"check",
"--message-format=json",
],
}
```
> #### Note
>
> If you are [building Miri with a locally built rustc][], set
> `rust-analyzer.rustcSource` to the relative path from your Miri clone to the
> root `Cargo.toml` of the locally built rustc. For example, the path might look
> like `../rust/Cargo.toml`.
See the rustc-dev-guide's docs on ["Configuring `rust-analyzer` for `rustc`"][rdg-r-a]
for more information about configuring VS Code and `rust-analyzer`.
[rdg-r-a]: https://rustc-dev-guide.rust-lang.org/building/suggested.html#configuring-rust-analyzer-for-rustc
## Advanced topic: other build environments
We described above the simplest way to get a working build environment for Miri,
which is to use the version of rustc indicated by `rustc-version`. But
sometimes, that is not enough.
### Updating `rustc-version`
The `rustc-version` file is regularly updated to keep Miri close to the latest
version of rustc. Usually, new contributors do not have to worry about this. But
sometimes a newer rustc is needed for a patch, and sometimes Miri needs fixing
for changes in rustc. In both cases, `rustc-version` needs updating.
To update the `rustc-version` file and install the latest rustc, you can run:
```
./rustup-toolchain HEAD
```
Now edit Miri until `./miri test` passes, and submit a PR. Generally, it is
preferred to separate updating `rustc-version` and doing what it takes to get
Miri working again, from implementing new features that rely on the updated
rustc. This avoids blocking all Miri development on landing a big PR.
### Building Miri with a locally built rustc
[building Miri with a locally built rustc]: #building-miri-with-a-locally-built-rustc
A big part of the Miri driver lives in rustc, so working on Miri will sometimes
require using a locally built rustc. The bug you want to fix may actually be on
the rustc side, or you just need to get more detailed trace of the execution
than what is possible with release builds -- in both cases, you should develop
Miri against a rustc you compiled yourself, with debug assertions (and hence
tracing) enabled.
The setup for a local rustc works as follows:
```sh
# Clone the rust-lang/rust repo.
git clone https://github.com/rust-lang/rust rustc
cd rustc
# Create a config.toml with defaults for working on Miri.
./x.py setup compiler
# Now edit `config.toml` and under `[rust]` set `debug-assertions = true`.
# Build a stage 2 rustc, and build the rustc libraries with that rustc.
# This step can take 30 minutes or more.
./x.py build --stage 2 compiler/rustc
# If you change something, you can get a faster rebuild by doing
./x.py build --keep-stage 0 --stage 2 compiler/rustc
# You may have to change the architecture in the next command
rustup toolchain link stage2 build/x86_64-unknown-linux-gnu/stage2
# Now cd to your Miri directory, then configure rustup
rustup override set stage2
```
Note: When you are working with a locally built rustc or any other toolchain that
is not the same as the one in `rust-version`, you should not have `.auto-everything` or
`.auto-toolchain` as that will keep resetting your toolchain.
```
rm -f .auto-everything .auto-toolchain
```
Important: You need to delete the Miri cache when you change the stdlib; otherwise the
old, chached version will be used. On Linux, the cache is located at `~/.cache/miri`,
and on Windows, it is located at `%LOCALAPPDATA%\rust-lang\miri\cache`; the exact
location is printed after the library build: "A libstd for Miri is now available in ...".
Note: `./x.py --stage 2 compiler/rustc` currently errors with `thread 'main'
panicked at 'fs::read(stamp) failed with No such file or directory (os error 2)`,
you can simply ignore that error; Miri will build anyway.
For more information about building and configuring a local compiler,
see <https://rustc-dev-guide.rust-lang.org/building/how-to-build-and-run.html>.
With this, you should now have a working development setup! See
[above](#building-and-testing-miri) for how to proceed working on Miri.

813
src/tools/miri/Cargo.lock Normal file
View File

@ -0,0 +1,813 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[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.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "camino"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412"
dependencies = [
"serde",
]
[[package]]
name = "cargo-platform"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27"
dependencies = [
"serde",
]
[[package]]
name = "cargo_metadata"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3abb7553d5b9b8421c6de7cb02606ff15e0c6eea7d8eadd75ef013fd636bec36"
dependencies = [
"camino",
"cargo-platform",
"semver",
"serde",
"serde_json",
]
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "color-eyre"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ebf286c900a6d5867aeff75cfee3192857bb7f24b547d4f0df2ed6baa812c90"
dependencies = [
"backtrace",
"color-spantrace",
"eyre",
"indenter",
"once_cell",
"owo-colors",
"tracing-error",
]
[[package]]
name = "color-spantrace"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce"
dependencies = [
"once_cell",
"owo-colors",
"tracing-core",
"tracing-error",
]
[[package]]
name = "colored"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
dependencies = [
"atty",
"lazy_static",
"winapi",
]
[[package]]
name = "crossbeam"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845"
dependencies = [
"cfg-if",
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"lazy_static",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
dependencies = [
"cfg-if",
"lazy_static",
]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "env_logger"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "eyre"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb"
dependencies = [
"indenter",
"once_cell",
]
[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "indenter"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "itoa"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
[[package]]
name = "libffi"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e454b3efb16fba3b17810ae5e41df02b649e564ab3c5a34b3b93ed07ad287e6"
dependencies = [
"libc",
"libffi-sys",
]
[[package]]
name = "libffi-sys"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab4106b7f09d7b87d021334d5618fac1dfcfb824d4c5fe111ff0074dfd242e15"
dependencies = [
"cc",
]
[[package]]
name = "libloading"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "lock_api"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "measureme"
version = "10.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbdc226fa10994e8f66a4d2f6f000148bc563a1c671b6dcd2135737018033d8a"
dependencies = [
"log",
"memmap2",
"parking_lot",
"perf-event-open-sys",
"rustc-hash",
"smallvec",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memmap2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4"
dependencies = [
"libc",
]
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "miniz_oxide"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
dependencies = [
"adler",
]
[[package]]
name = "miri"
version = "0.1.0"
dependencies = [
"colored",
"env_logger",
"getrandom",
"lazy_static",
"libc",
"libffi",
"libloading",
"log",
"measureme",
"rand",
"regex",
"rustc-workspace-hack",
"shell-escape",
"smallvec",
"ui_test",
]
[[package]]
name = "object"
version = "0.28.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
[[package]]
name = "owo-colors"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "perf-event-open-sys"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce9bedf5da2c234fdf2391ede2b90fabf585355f33100689bc364a3ea558561a"
dependencies = [
"libc",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "ppv-lite86"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]]
name = "proc-macro2"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[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.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core",
]
[[package]]
name = "redox_syscall"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-workspace-hack"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "ryu"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd"
dependencies = [
"serde",
]
[[package]]
name = "serde"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]]
name = "shell-escape"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
[[package]]
name = "smallvec"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
[[package]]
name = "syn"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "thread_local"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
dependencies = [
"once_cell",
]
[[package]]
name = "tracing"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-error"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
dependencies = [
"tracing",
"tracing-subscriber",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59"
dependencies = [
"sharded-slab",
"thread_local",
"tracing-core",
]
[[package]]
name = "ui_test"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d1f546a5883ae78da735bba529ec1116661e2f73582f23920d994dc97da3a22"
dependencies = [
"cargo_metadata",
"color-eyre",
"colored",
"crossbeam",
"diff",
"lazy_static",
"regex",
"rustc_version",
"serde",
"serde_json",
]
[[package]]
name = "unicode-ident"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[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.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

61
src/tools/miri/Cargo.toml Normal file
View File

@ -0,0 +1,61 @@
[package]
authors = ["Miri Team"]
description = "An experimental interpreter for Rust MIR (core driver)."
license = "MIT OR Apache-2.0"
name = "miri"
repository = "https://github.com/rust-lang/miri"
version = "0.1.0"
default-run = "miri"
edition = "2021"
[lib]
test = true # we have unit tests
doctest = false # but no doc tests
[[bin]]
name = "miri"
test = false # we have no unit tests
doctest = false # and no doc tests
[dependencies]
getrandom = { version = "0.2", features = ["std"] }
env_logger = "0.9"
log = "0.4"
shell-escape = "0.1.4"
rand = "0.8"
smallvec = "1.7"
# A noop dependency that changes in the Rust repository, it's a bit of a hack.
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
# for more information.
rustc-workspace-hack = "1.0.0"
measureme = "10.0.0"
[target."cfg(unix)".dependencies]
libc = "0.2"
libffi = "3.0.0"
libloading = "0.7"
[dev-dependencies]
colored = "2"
ui_test = "0.3.1"
# Features chosen to match those required by env_logger, to avoid rebuilds
regex = { version = "1.5.5", default-features = false, features = ["perf", "std"] }
lazy_static = "1.4.0"
[package.metadata.rust-analyzer]
# This crate uses #[feature(rustc_private)].
# See https://github.com/rust-analyzer/rust-analyzer/pull/7891
rustc_private = true
[[test]]
name = "compiletest"
harness = false
[features]
default = ["stack-cache"]
stack-cache = []
# Be aware that this file is inside a workspace when used via the
# submodule in the rustc repo. That means there are many cargo features
# we cannot use, such as profiles.

View File

@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

639
src/tools/miri/README.md Normal file
View File

@ -0,0 +1,639 @@
# Miri
[![Actions build status][actions-badge]][actions-url]
[actions-badge]: https://github.com/rust-lang/miri/workflows/CI/badge.svg?branch=master
[actions-url]: https://github.com/rust-lang/miri/actions
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:
* 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,
or an invalid enum discriminant)
* **Experimental**: Violations of the [Stacked Borrows] rules governing aliasing
for reference types
* **Experimental**: Data races
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
found a bug with Miri, we'd appreciate if you tell us and we'll add it to the
list!
By default, Miri ensures a fully deterministic execution and isolates the
program from the host system. Some APIs that would usually access the host, such
as gathering entropy for random number generators, environment variables, and
clocks, are replaced by deterministic "fake" implementations. Set
`MIRIFLAGS="-Zmiri-disable-isolation"` to access the real system APIs instead.
(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:
* 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.
In particular, Miri does currently 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.
* 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.
* 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
basic file system access) but most have not: for example, Miri currently does
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
cannot produce all behaviors possibly observable on real hardware.
[rust]: https://www.rust-lang.org/
[mir]: https://github.com/rust-lang/rfcs/blob/master/text/1211-mir.md
[`unreachable_unchecked`]: https://doc.rust-lang.org/stable/std/hint/fn.unreachable_unchecked.html
[`copy_nonoverlapping`]: https://doc.rust-lang.org/stable/std/ptr/fn.copy_nonoverlapping.html
[Stacked Borrows]: https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md
## Using Miri
Install Miri on Rust nightly via `rustup`:
```sh
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.)
Now you can run your project in Miri:
1. Run `cargo clean` to eliminate any cached dependencies. Miri needs your
dependencies to be compiled the right way, that would not happen if they have
previously already been compiled.
2. To run all tests in your project through Miri, use `cargo miri test`.
3. If you have a binary project, you can run it through Miri using `cargo miri run`.
The first time you run Miri, it will perform some extra setup and install some
dependencies. It will ask you for confirmation before installing anything.
`cargo miri run/test` supports the exact same flags as `cargo run/test`. For
example, `cargo miri test filter` only runs the tests containing `filter` in
their name.
You can pass arguments 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
under Miri because they do things Miri does not support:
```rust
#[test]
#[cfg_attr(miri, ignore)]
fn does_not_work_on_miri() {
tokio::run(futures::future::ok::<_, ()>(()));
}
```
There is no way to list all the infinite things Miri cannot do, but the
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
```
### Cross-interpretation: running for different targets
Miri can not only run a binary or test suite for your host target, it can also
perform cross-interpretation for arbitrary foreign targets: `cargo miri run
--target x86_64-unknown-linux-gnu` will run your program as if it was a Linux
program, no matter your host OS. This is particularly useful if you are using
Windows, as the Linux target is much better supported than Windows targets.
You can also use this to test platforms with different properties than your host
platform. For example `cargo miri test --target mips64-unknown-linux-gnuabi64`
will run your test suite on a big-endian target, which is useful for testing
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:
```sh
rustup toolchain install nightly --component miri
rustup override set nightly
cargo miri test
```
Here is an example job for GitHub Actions:
```yaml
miri:
name: "Miri"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Miri
run: |
rustup toolchain install nightly --component miri
rustup override set nightly
cargo miri setup
- name: Test with Miri
run: cargo miri test
```
The explicit `cargo miri setup` helps to keep the output of the actual test step
clean.
### Testing for alignment issues
Miri can sometimes miss misaligned accesses since allocations can "happen to be"
aligned just right. You can use `-Zmiri-symbolic-alignment-check` to definitely
catch all such issues, but that flag will also cause false positives when code
does manual pointer arithmetic to account for alignment. Another alternative is
to call Miri with various values for `-Zmiri-seed`; that will alter the
randomness that is used to determine allocation base addresses. The following
snippet calls Miri in a loop with different values for the seed:
```
for SEED in $({ echo obase=16; seq 0 255; } | bc); do
echo "Trying seed: $SEED"
MIRIFLAGS=-Zmiri-seed=$SEED cargo miri test || { echo "Failing seed: $SEED"; break; };
done
```
### Supported targets
Miri does not support all targets supported by Rust. The good news, however, is
that no matter your host OS/platform, it is easy to run code for *any* target
using `--target`!
The following targets are tested on CI and thus should always work (to the
degree documented below):
- The best-supported target is `x86_64-unknown-linux-gnu`. Miri releases are
blocked on things working with this target. Most other Linux targets should
also work well; we do run the test suite on `i686-unknown-linux-gnu` as a
32bit target and `mips64-unknown-linux-gnuabi64` as a big-endian target.
- `x86_64-apple-darwin` should work basically as well as Linux. We also test
`aarch64-apple-darwin`. However, we might ship Miri with a nightly even when
some features on these targets regress.
- `x86_64-pc-windows-msvc` works, but supports fewer features than the Linux and
Apple targets. For example, file system access and concurrency are not
supported on Windows. We also test `i686-pc-windows-msvc`, with the same
reduced feature set. We might ship Miri with a nightly even when some features
on these targets regress.
### Common Problems
When using the above instructions, you may encounter a number of confusing compiler
errors.
#### "note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace"
You may see this when trying to get Miri to display a backtrace. By default, Miri
doesn't expose any environment to the program, so running
`RUST_BACKTRACE=1 cargo miri test` will not do what you expect.
To get a backtrace, you need to disable isolation
[using `-Zmiri-disable-isolation`][miri-flags]:
```sh
RUST_BACKTRACE=1 MIRIFLAGS="-Zmiri-disable-isolation" cargo miri test
```
#### "found possibly newer version of crate `std` which `<dependency>` 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 deleting `~/.cache/miri`.
#### "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
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-compare-exchange-weak-failure-rate=<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
will always fail and `0.0` means it will never fail. Note than setting it to
`1.0` will likely cause hangs, since it means programs using
`compare_exchange_weak` cannot make progress.
* `-Zmiri-disable-isolation` disables host isolation. As a consequence,
the program has access to host resources such as environment variables, file
systems, and randomness.
* `-Zmiri-isolation-error=<action>` configures Miri's response to operations
requiring host access while isolation is enabled. `abort`, `hide`, `warn`,
and `warn-nobacktrace` are the supported actions. The default is to `abort`,
which halts the machine. Some (but not all) operations also support continuing
execution with a "permission denied" error being returned to the program.
`warn` prints a full backtrace when that happens; `warn-nobacktrace` is less
verbose. `hide` hides the warning entirely.
* `-Zmiri-env-forward=<var>` 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-ignore-leaks` disables the memory leak checker, and also allows some
remaining threads to exist when the main thread exits.
* `-Zmiri-permissive-provenance` disables the warning for integer-to-pointer casts and
[`ptr::from_exposed_addr`](https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html).
This will necessarily miss some bugs as those operations are not efficiently and accurately
implementable in a sanitizer, but it will only miss bugs that concern memory/pointers which is
subject to these operations.
* `-Zmiri-preemption-rate` configures the probability that at the end of a basic block, the active
thread will be preempted. The default is `0.01` (i.e., 1%). Setting this to `0` disables
preemption.
* `-Zmiri-report-progress` makes Miri print the current stacktrace every now and then, so you can
tell what it is doing when a program just keeps running. You can customize how frequently the
report is printed via `-Zmiri-report-progress=<blocks>`, which prints the report every N basic
blocks.
* `-Zmiri-seed=<hex>` configures the seed of the RNG that Miri uses to resolve non-determinism. This
RNG is used to pick base addresses for allocations, to determine preemption and failure of
`compare_exchange_weak`, and to control store buffering for weak memory emulation. When isolation
is enabled (the default), this is also used to emulate system entropy. The default seed is 0. You
can increase test coverage by running Miri multiple times with different seeds.
* `-Zmiri-strict-provenance` enables [strict
provenance](https://github.com/rust-lang/rust/issues/95228) checking in Miri. This means that
casting an integer to a pointer yields a result with 'invalid' provenance, i.e., with provenance
that cannot be used for any memory access.
* `-Zmiri-symbolic-alignment-check` makes the alignment check more strict. By default, alignment is
checked by casting the pointer to an integer, and making sure that is a multiple of the alignment.
This can lead to cases where a program passes the alignment check by pure chance, because things
"happened to be" sufficiently aligned -- there is no UB in this execution but there would be UB in
others. To avoid such cases, the symbolic alignment check only takes into account the requested
alignment of the relevant allocation, and the offset into that allocation. This avoids missing
such bugs, but it also incurs some false positives when the code does manual integer arithmetic to
ensure alignment. (The standard library `align_to` method works fine in both modes; under
symbolic alignment it only fills the middle slice when the allocation guarantees sufficient
alignment.)
* `-Zmiri-tag-gc=<blocks>` configures how often the pointer tag garbage collector runs. The default
is to search for and remove unreachable tags once every `10,000` basic blocks. Setting this to
`0` disables the garbage collector, which causes some programs to have explosive memory usage
and/or super-linear runtime.
The remaining flags are for advanced use only, and more likely to change or be removed.
Some of these are **unsound**, which means they can lead
to Miri failing to detect cases of undefined behavior in a program.
* `-Zmiri-disable-abi-check` disables checking [function ABI]. Using this flag
is **unsound**.
* `-Zmiri-disable-alignment-check` disables checking pointer alignment, so you
can focus on other failures, but it means Miri can miss bugs in your program.
Using this flag is **unsound**.
* `-Zmiri-disable-data-race-detector` disables checking for data races. Using
this flag is **unsound**. This implies `-Zmiri-disable-weak-memory-emulation`.
* `-Zmiri-disable-stacked-borrows` disables checking the experimental
[Stacked Borrows] aliasing rules. This can make Miri run faster, but it also
means no aliasing violations will be detected. Using this flag is **unsound**
(but the affected soundness rules are experimental).
* `-Zmiri-disable-validation` disables enforcing validity invariants, which are
enforced by default. This is mostly useful to focus on other failures (such
as out-of-bounds accesses) first. Setting this flag means Miri can miss bugs
in your program. However, this can also help to make Miri run faster. Using
this flag is **unsound**.
* `-Zmiri-disable-weak-memory-emulation` disables the emulation of some C++11 weak
memory effects.
* `-Zmiri-extern-so-file=<path to a shared object 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.
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).
* `-Zmiri-measureme=<name>` 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 with the prefix `<name>`, and can be processed
using the tools in the repository https://github.com/rust-lang/measureme.
* `-Zmiri-mute-stdout-stderr` silently ignores all writes to stdout and stderr,
but reports to the program that it did actually write. This is useful when you
are not interested in the actual program's output, but only want to see Miri's
errors and warnings.
* `-Zmiri-panic-on-unsupported` will makes some forms of unsupported functionality,
such as FFI and unsupported syscalls, panic within the context of the emulated
application instead of raising an error within the context of Miri (and halting
execution). Note that code might not expect these operations to ever panic, so
this flag can lead to strange (mis)behavior.
* `-Zmiri-retag-fields` changes Stacked Borrows retagging to recurse into fields.
This means that references in fields of structs/enums/tuples/arrays/... are retagged,
and in particular, they are protected when passed as function arguments.
* `-Zmiri-track-alloc-id=<id1>,<id2>,...` shows a backtrace when the given allocations are
being allocated or freed. This helps in debugging memory leaks and
use after free bugs. Specifying this argument multiple times does not overwrite the previous
values, instead it appends its values to the list. Listing an id multiple times has no effect.
* `-Zmiri-track-call-id=<id1>,<id2>,...` shows a backtrace when the given call ids are
assigned to a stack frame. This helps in debugging UB related to Stacked
Borrows "protectors". Specifying this argument multiple times does not overwrite the previous
values, instead it appends its values to the list. Listing an id multiple times has no effect.
* `-Zmiri-track-pointer-tag=<tag1>,<tag2>,...` shows a backtrace when a given pointer tag
is created and when (if ever) it is popped from a borrow stack (which is where the tag becomes invalid
and any future use of it will error). This helps you in finding out why UB is
happening and where in your code would be a good place to look for it.
Specifying this argument multiple times does not overwrite the previous
values, instead it appends its values to the list. Listing a tag multiple times has no effect.
* `-Zmiri-track-weak-memory-loads` shows a backtrace when weak memory emulation returns an outdated
value from a load. This can help diagnose problems that disappear under
`-Zmiri-disable-weak-memory-emulation`.
[function ABI]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier
Some native rustc `-Z` flags are also very relevant for Miri:
* `-Zmir-opt-level` controls how many MIR optimizations are performed. Miri
overrides the default to be `0`; be advised that using any higher level can
make Miri miss bugs in your program because they got optimized away.
* `-Zalways-encode-mir` makes rustc dump MIR even for completely monomorphic
functions. This is needed so that Miri can execute such functions, so Miri
sets this flag per default.
* `-Zmir-emit-retag` controls whether `Retag` statements are emitted. Miri
enables this per default because it is needed for [Stacked Borrows].
Moreover, Miri recognizes some environment variables:
* `MIRI_AUTO_OPS` indicates whether the automatic execution of rustfmt, clippy and rustup-toolchain
should be skipped. If it is set to any value, they are skipped. This is used for avoiding
infinite recursion in `./miri` and 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. Note that changing files in that directory does not automatically
trigger a re-build of the standard library; you have to clear the Miri build
cache manually (on Linux, `rm -rf ~/.cache/miri`).
* `MIRI_SYSROOT` (recognized by `cargo miri` and the Miri driver) indicates the sysroot to use. When
using `cargo miri`, 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.
* `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_NO_STD` (recognized by `cargo miri` and the test suite) makes sure that the target's
sysroot is built without libstd. This allows testing and running no_std programs.
* `MIRI_BLESS` (recognized by the test suite) 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
`stderr` or `stdout` files match the actual output. Useful for the rustc test suite
which has subtle differences that we don't care about.
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_XARGO` is set during the Miri-induced `xargo` 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_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
Miri provides some `extern` functions that programs can import to access
Miri-specific functionality:
```rust
#[cfg(miri)]
extern "Rust" {
/// Miri-provided extern function to mark the block `ptr` points to as a "root"
/// for some static memory. This memory and everything reachable by it is not
/// considered leaking even if it still exists when the program terminates.
///
/// `ptr` has to point to the beginning of an allocated block.
fn miri_static_root(ptr: *const u8);
// Miri-provided extern function to get the amount of frames in the current backtrace.
// The `flags` argument must be `0`.
fn miri_backtrace_size(flags: u64) -> usize;
/// Miri-provided extern function to obtain a backtrace of the current call stack.
/// This writes a slice of pointers into `buf` - each pointer is an opaque value
/// that is only useful when passed to `miri_resolve_frame`.
/// `buf` must have `miri_backtrace_size(0) * pointer_size` bytes of space.
/// The `flags` argument must be `1`.
fn miri_get_backtrace(flags: u64, buf: *mut *mut ());
/// Miri-provided extern function to resolve a frame pointer obtained
/// from `miri_get_backtrace`. The `flags` argument must be `1`,
/// and `MiriFrame` should be declared as follows:
///
/// ```rust
/// #[repr(C)]
/// struct MiriFrame {
/// // The size of the name of the function being executed, encoded in UTF-8
/// name_len: usize,
/// // The size of filename of the function being executed, encoded in UTF-8
/// filename_len: usize,
/// // The line number currently being executed in `filename`, starting from '1'.
/// lineno: u32,
/// // The column number currently being executed in `filename`, starting from '1'.
/// colno: u32,
/// // The function pointer to the function currently being executed.
/// // This can be compared against function pointers obtained by
/// // casting a function (e.g. `my_fn as *mut ()`)
/// fn_ptr: *mut ()
/// }
/// ```
///
/// The fields must be declared in exactly the same order as they appear in `MiriFrame` above.
/// This function can be called on any thread (not just the one which obtained `frame`).
fn miri_resolve_frame(frame: *mut (), flags: u64) -> MiriFrame;
/// Miri-provided extern function to get the name and filename of the frame provided by `miri_resolve_frame`.
/// `name_buf` and `filename_buf` should be allocated with the `name_len` and `filename_len` fields of `MiriFrame`.
/// The flags argument must be `0`.
fn miri_resolve_frame_names(ptr: *mut (), flags: u64, name_buf: *mut u8, filename_buf: *mut u8);
/// Miri-provided extern function to begin unwinding with the given payload.
///
/// This is internal and unstable and should not be used; we give it here
/// just to be complete.
fn miri_start_panic(payload: *mut u8) -> !;
}
```
## Contributing and getting help
If you want to contribute to Miri, great! Please check out our
[contribution guide](CONTRIBUTING.md).
For help with running Miri, you can open an issue here on
GitHub or use the [Miri stream on the Rust Zulip][zulip].
[zulip]: https://rust-lang.zulipchat.com/#narrow/stream/269128-miri
## History
This project began as part of an undergraduate research course in 2015 by
@solson at the [University of Saskatchewan][usask]. There are [slides] and a
[report] available from that project. In 2016, @oli-obk joined to prepare Miri
for eventually being used as const evaluator in the Rust compiler itself
(basically, for `const` and `static` stuff), replacing the old evaluator that
worked directly on the AST. In 2017, @RalfJung did an internship with Mozilla
and began developing Miri towards a tool for detecting undefined behavior, and
also using Miri as a way to explore the consequences of various possible
definitions for undefined behavior in Rust. @oli-obk's move of the Miri engine
into the compiler finally came to completion in early 2018. Meanwhile, later
that year, @RalfJung did a second internship, developing Miri further with
support for checking basic type invariants and verifying that references are
used according to their aliasing restrictions.
[usask]: https://www.usask.ca/
[slides]: https://solson.me/miri-slides.pdf
[report]: https://solson.me/miri-report.pdf
## Bugs found by Miri
Miri has already found a number of bugs in the Rust standard library and beyond, which we collect here.
Definite bugs found:
* [`Debug for vec_deque::Iter` accessing uninitialized memory](https://github.com/rust-lang/rust/issues/53566)
* [`Vec::into_iter` doing an unaligned ZST read](https://github.com/rust-lang/rust/pull/53804)
* [`From<&[T]> for Rc` creating a not sufficiently aligned reference](https://github.com/rust-lang/rust/issues/54908)
* [`BTreeMap` creating a shared reference pointing to a too small allocation](https://github.com/rust-lang/rust/issues/54957)
* [`Vec::append` creating a dangling reference](https://github.com/rust-lang/rust/pull/61082)
* [Futures turning a shared reference into a mutable one](https://github.com/rust-lang/rust/pull/56319)
* [`str` turning a shared reference into a mutable one](https://github.com/rust-lang/rust/pull/58200)
* [`rand` performing unaligned reads](https://github.com/rust-random/rand/issues/779)
* [The Unix allocator calling `posix_memalign` in an invalid way](https://github.com/rust-lang/rust/issues/62251)
* [`getrandom` calling the `getrandom` syscall in an invalid way](https://github.com/rust-random/getrandom/pull/73)
* [`Vec`](https://github.com/rust-lang/rust/issues/69770) and [`BTreeMap`](https://github.com/rust-lang/rust/issues/69769) leaking memory under some (panicky) conditions
* [`beef` leaking memory](https://github.com/maciejhirsz/beef/issues/12)
* [`EbrCell` using uninitialized memory incorrectly](https://github.com/Firstyear/concread/commit/b15be53b6ec076acb295a5c0483cdb4bf9be838f#diff-6282b2fc8e98bd089a1f0c86f648157cR229)
* [TiKV performing an unaligned pointer access](https://github.com/tikv/tikv/issues/7613)
* [`servo_arc` creating a dangling shared reference](https://github.com/servo/servo/issues/26357)
* [TiKV constructing out-of-bounds pointers (and overlapping mutable references)](https://github.com/tikv/tikv/pull/7751)
* [`encoding_rs` doing out-of-bounds pointer arithmetic](https://github.com/hsivonen/encoding_rs/pull/53)
* [TiKV using `Vec::from_raw_parts` incorrectly](https://github.com/tikv/agatedb/pull/24)
* Incorrect doctests for [`AtomicPtr`](https://github.com/rust-lang/rust/pull/84052) and [`Box::from_raw_in`](https://github.com/rust-lang/rust/pull/84053)
* [Insufficient alignment in `ThinVec`](https://github.com/Gankra/thin-vec/pull/27)
* [`crossbeam-epoch` calling `assume_init` on a partly-initialized `MaybeUninit`](https://github.com/crossbeam-rs/crossbeam/pull/779)
* [`integer-encoding` dereferencing a misaligned pointer](https://github.com/dermesser/integer-encoding-rs/pull/23)
* [`rkyv` constructing a `Box<[u8]>` from an overaligned allocation](https://github.com/rkyv/rkyv/commit/a9417193a34757e12e24263178be8b2eebb72456)
* [Data race in `thread::scope`](https://github.com/rust-lang/rust/issues/98498)
* [`regex` incorrectly handling unaligned `Vec<u8>` buffers](https://www.reddit.com/r/rust/comments/vq3mmu/comment/ienc7t0?context=3)
* [Incorrect use of `compare_exchange_weak` in `once_cell`](https://github.com/matklad/once_cell/issues/186)
Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment):
* [`VecDeque::drain` creating overlapping mutable references](https://github.com/rust-lang/rust/pull/56161)
* Various `BTreeMap` problems
* [`BTreeMap` iterators creating mutable references that overlap with shared references](https://github.com/rust-lang/rust/pull/58431)
* [`BTreeMap::iter_mut` creating overlapping mutable references](https://github.com/rust-lang/rust/issues/73915)
* [`BTreeMap` node insertion using raw pointers outside their valid memory area](https://github.com/rust-lang/rust/issues/78477)
* [`LinkedList` cursor insertion creating overlapping mutable references](https://github.com/rust-lang/rust/pull/60072)
* [`Vec::push` invalidating existing references into the vector](https://github.com/rust-lang/rust/issues/60847)
* [`align_to_mut` violating uniqueness of mutable references](https://github.com/rust-lang/rust/issues/68549)
* [`sized-chunks` creating aliasing mutable references](https://github.com/bodil/sized-chunks/issues/8)
* [`String::push_str` invalidating existing references into the string](https://github.com/rust-lang/rust/issues/70301)
* [`ryu` using raw pointers outside their valid memory area](https://github.com/dtolnay/ryu/issues/24)
* [ink! creating overlapping mutable references](https://github.com/rust-lang/miri/issues/1364)
* [TiKV creating overlapping mutable reference and raw pointer](https://github.com/tikv/tikv/pull/7709)
* [Windows `Env` iterator using a raw pointer outside its valid memory area](https://github.com/rust-lang/rust/pull/70479)
* [`VecDeque::iter_mut` creating overlapping mutable references](https://github.com/rust-lang/rust/issues/74029)
* [Various standard library aliasing issues involving raw pointers](https://github.com/rust-lang/rust/pull/78602)
* [`<[T]>::copy_within` using a loan after invalidating it](https://github.com/rust-lang/rust/pull/85610)
## Scientific papers employing Miri
* [Stacked Borrows: An Aliasing Model for Rust](https://plv.mpi-sws.org/rustbelt/stacked-borrows/)
* [Using Lightweight Formal Methods to Validate a Key-Value Storage Node in Amazon S3](https://www.amazon.science/publications/using-lightweight-formal-methods-to-validate-a-key-value-storage-node-in-amazon-s3)
* [SyRust: Automatic Testing of Rust Libraries with Semantic-Aware Program Synthesis](https://dl.acm.org/doi/10.1145/3453483.3454084)
## License
Licensed under either of
* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you shall be dual licensed as above, without any
additional terms or conditions.

View File

@ -0,0 +1,94 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "backtrace"
version = "0.3.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "backtraces"
version = "0.1.0"
dependencies = [
"backtrace",
]
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "gimli"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
[[package]]
name = "libc"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "miniz_oxide"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
dependencies = [
"adler",
]
[[package]]
name = "object"
version = "0.28.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424"
dependencies = [
"memchr",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"

View File

@ -0,0 +1,9 @@
[package]
name = "backtraces"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
backtrace = "0.3.65"

View File

@ -0,0 +1,29 @@
//! Extracted from the backtrace crate's test test_frame_conversion
use backtrace::{Backtrace, BacktraceFrame};
use std::fmt::Write;
fn main() {
let mut frames = vec![];
backtrace::trace(|frame| {
let converted = BacktraceFrame::from(frame.clone());
frames.push(converted);
true
});
let mut manual = Backtrace::from(frames);
manual.resolve();
let frames = manual.frames();
let mut output = String::new();
for frame in frames {
// Originally these were println! but we'd prefer our benchmarks to not emit a lot of
// output to stdout/stderr. Unfortunately writeln! to a String is faster, but we still
// manage to exercise interesting code paths in Miri.
writeln!(output, "{:?}", frame.ip()).unwrap();
writeln!(output, "{:?}", frame.symbol_address()).unwrap();
writeln!(output, "{:?}", frame.module_base_address()).unwrap();
writeln!(output, "{:?}", frame.symbols()).unwrap();
}
drop(output);
}

View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "mse"
version = "0.1.0"

View File

@ -0,0 +1,7 @@
[package]
name = "mse"
version = "0.1.0"
authors = ["Ralf Jung <post@ralfj.de>"]
edition = "2018"
[dependencies]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,89 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "cargo-miri-test"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "itoa"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
[[package]]
name = "proc-macro2"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
[[package]]
name = "serde"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"

View File

@ -0,0 +1,9 @@
[package]
name = "cargo-miri-test"
version = "0.1.0"
authors = ["Oliver Schneider <git-spam-no-reply9815368754983@oli-obk.de>"]
edition = "2018"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,89 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "cargo-miri-test"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "itoa"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
[[package]]
name = "proc-macro2"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
[[package]]
name = "serde"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"

View File

@ -0,0 +1,9 @@
[package]
name = "cargo-miri-test"
version = "0.1.0"
authors = ["Oliver Schneider <git-spam-no-reply9815368754983@oli-obk.de>"]
edition = "2018"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "slice-get-unchecked"
version = "0.1.0"

View File

@ -0,0 +1,8 @@
[package]
name = "slice-get-unchecked"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@ -0,0 +1,12 @@
//! This is a stripped-down version of the code pattern that causes runtime blowup when printing
//! backtraces in a failed test under cargo miri test with -Zmiri-disable-isolation.
//! See https://github.com/rust-lang/miri/issues/2273
fn main() {
let x = vec![0u8; 4096];
let mut i = 0;
while i < x.len() {
let _element = unsafe { *x.get_unchecked(i) };
i += 1;
}
}

View File

@ -0,0 +1,16 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "unicode"
version = "0.1.0"
dependencies = [
"unicode-xid",
]
[[package]]
name = "unicode-xid"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"

View File

@ -0,0 +1,9 @@
[package]
name = "unicode"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
unicode-xid = "0.2.3"

View File

@ -0,0 +1,20 @@
//! Extracted from the unicode-xid exhaustive test all_valid_chars_do_not_panic_for_is_xid_continue
use unicode_xid::UnicodeXID;
/// A `char` in Rust is a Unicode Scalar Value
///
/// See: http://www.unicode.org/glossary/#unicode_scalar_value
fn all_valid_chars() -> impl Iterator<Item = char> {
(0u32..=0xD7FF).chain(0xE000u32..=0x10FFFF).map(|u| {
core::convert::TryFrom::try_from(u)
.expect("The selected range should be infallible if the docs match impl")
})
}
fn main() {
// Take only the first few chars because we don't want to wait all day
for c in all_valid_chars().take(1_500) {
let _ = UnicodeXID::is_xid_continue(c);
}
}

8
src/tools/miri/build.rs Normal file
View File

@ -0,0 +1,8 @@
fn main() {
// Don't rebuild miri when nothing changed.
println!("cargo:rerun-if-changed=build.rs");
// Re-export the TARGET environment variable so it can
// be accessed by miri.
let target = std::env::var("TARGET").unwrap();
println!("cargo:rustc-env=TARGET={}", target);
}

View File

@ -0,0 +1,554 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203"
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "camino"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412"
dependencies = [
"serde",
]
[[package]]
name = "cargo-miri"
version = "0.1.0"
dependencies = [
"cargo_metadata",
"directories",
"rustc-workspace-hack",
"rustc_version",
"serde",
"serde_json",
"vergen",
]
[[package]]
name = "cargo-platform"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27"
dependencies = [
"serde",
]
[[package]]
name = "cargo_metadata"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3abb7553d5b9b8421c6de7cb02606ff15e0c6eea7d8eadd75ef013fd636bec36"
dependencies = [
"camino",
"cargo-platform",
"semver",
"serde",
"serde_json",
]
[[package]]
name = "cc"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
dependencies = [
"jobserver",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"time",
"winapi",
]
[[package]]
name = "directories"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e69600ff1703123957937708eb27f7a564e48885c537782722ed0ba3189ce1d7"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "enum-iterator"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6"
dependencies = [
"enum-iterator-derive",
]
[[package]]
name = "enum-iterator-derive"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "form_urlencoded"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
dependencies = [
"matches",
"percent-encoding",
]
[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "getset"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "git2"
version = "0.13.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6"
dependencies = [
"bitflags",
"libc",
"libgit2-sys",
"log",
"url",
]
[[package]]
name = "idna"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]]
name = "jobserver"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
dependencies = [
"libc",
]
[[package]]
name = "libc"
version = "0.2.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
[[package]]
name = "libgit2-sys"
version = "0.12.26+1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494"
dependencies = [
"cc",
"libc",
"libz-sys",
"pkg-config",
]
[[package]]
name = "libz-sys"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "matches"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pkg-config"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom",
"redox_syscall",
]
[[package]]
name = "rustc-workspace-hack"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "rustversion"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f"
[[package]]
name = "ryu"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]]
name = "semver"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1"
dependencies = [
"serde",
]
[[package]]
name = "serde"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "tinyvec"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "unicode-bidi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
[[package]]
name = "unicode-ident"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
[[package]]
name = "unicode-normalization"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
dependencies = [
"tinyvec",
]
[[package]]
name = "url"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
dependencies = [
"form_urlencoded",
"idna",
"matches",
"percent-encoding",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vergen"
version = "5.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cf88d94e969e7956d924ba70741316796177fa0c79a2c9f4ab04998d96e966e"
dependencies = [
"anyhow",
"cfg-if",
"chrono",
"enum-iterator",
"getset",
"git2",
"rustversion",
"thiserror",
]
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[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-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -0,0 +1,32 @@
[package]
authors = ["Miri Team"]
description = "An experimental interpreter for Rust MIR (cargo wrapper)."
license = "MIT OR Apache-2.0"
name = "cargo-miri"
repository = "https://github.com/rust-lang/miri"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "cargo-miri"
path = "src/main.rs"
test = false # we have no unit tests
doctest = false # and no doc tests
[dependencies]
directories = "3"
rustc_version = "0.4"
serde_json = "1.0.40"
cargo_metadata = "0.15.0"
# A noop dependency that changes in the Rust repository, it's a bit of a hack.
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
# for more information.
rustc-workspace-hack = "1.0.0"
# Enable some feature flags that dev-dependencies need but dependencies
# do not. This makes `./miri install` after `./miri build` faster.
serde = { version = "*", features = ["derive"] }
[build-dependencies]
vergen = { version = "5", default_features = false, features = ["git"] }

View File

@ -0,0 +1,11 @@
use vergen::vergen;
fn main() {
// Don't rebuild miri when nothing changed.
println!("cargo:rerun-if-changed=build.rs");
// vergen
let mut gen_config = vergen::Config::default();
*gen_config.git_mut().sha_kind_mut() = vergen::ShaKind::Short;
*gen_config.git_mut().commit_timestamp_kind_mut() = vergen::TimestampKind::DateOnly;
vergen(gen_config).ok(); // Ignore failure (in case we are built outside a git repo)
}

4
src/tools/miri/cargo-miri/miri Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
# RA invokes `./miri cargo ...` for each workspace, so we need to forward that to the main `miri`
# script. See <https://github.com/rust-analyzer/rust-analyzer/issues/10793>.
exec "$(dirname "$0")"/../miri "$@"

View File

@ -0,0 +1,134 @@
//! Utilities for dealing with argument flags
use std::borrow::Cow;
use std::env;
/// Determines whether a `--flag` is present.
pub fn has_arg_flag(name: &str) -> bool {
num_arg_flag(name) > 0
}
/// Determines how many times a `--flag` is present.
pub fn num_arg_flag(name: &str) -> usize {
env::args().take_while(|val| val != "--").filter(|val| val == name).count()
}
/// Yields all values of command line flag `name` as `Ok(arg)`, and all other arguments except
/// the flag as `Err(arg)`. (The flag `name` itself is not yielded at all, only its values are.)
pub struct ArgSplitFlagValue<'a, I> {
args: Option<I>,
name: &'a str,
}
impl<'a, I: Iterator> ArgSplitFlagValue<'a, I> {
fn new(args: I, name: &'a str) -> Self {
Self { args: Some(args), name }
}
}
impl<'s, I: Iterator<Item = Cow<'s, str>>> Iterator for ArgSplitFlagValue<'_, I> {
// If the original iterator was all `Owned`, then we will only ever yield `Owned`
// (so `into_owned()` is cheap).
type Item = Result<Cow<'s, str>, Cow<'s, str>>;
fn next(&mut self) -> Option<Self::Item> {
let Some(args) = self.args.as_mut() else {
// We already canceled this iterator.
return None;
};
let arg = args.next()?;
if arg == "--" {
// Stop searching at `--`.
self.args = None;
return None;
}
// These branches cannot be merged if we want to avoid the allocation in the `Borrowed` branch.
match &arg {
Cow::Borrowed(arg) =>
if let Some(suffix) = arg.strip_prefix(self.name) {
// Strip leading `name`.
if suffix.is_empty() {
// This argument is exactly `name`; the next one is the value.
return args.next().map(Ok);
} else if let Some(suffix) = suffix.strip_prefix('=') {
// This argument is `name=value`; get the value.
return Some(Ok(Cow::Borrowed(suffix)));
}
},
Cow::Owned(arg) =>
if let Some(suffix) = arg.strip_prefix(self.name) {
// Strip leading `name`.
if suffix.is_empty() {
// This argument is exactly `name`; the next one is the value.
return args.next().map(Ok);
} else if let Some(suffix) = suffix.strip_prefix('=') {
// This argument is `name=value`; get the value. We need to do an allocation
// here as a `String` cannot be subsliced (what would the lifetime be?).
return Some(Ok(Cow::Owned(suffix.to_owned())));
}
},
}
Some(Err(arg))
}
}
impl<'a, I: Iterator<Item = String> + 'a> ArgSplitFlagValue<'a, I> {
pub fn from_string_iter(
args: I,
name: &'a str,
) -> impl Iterator<Item = Result<String, String>> + 'a {
ArgSplitFlagValue::new(args.map(Cow::Owned), name).map(|x| {
match x {
Ok(Cow::Owned(s)) => Ok(s),
Err(Cow::Owned(s)) => Err(s),
_ => panic!("iterator converted owned to borrowed"),
}
})
}
}
impl<'x: 'a, 'a, I: Iterator<Item = &'x str> + 'a> ArgSplitFlagValue<'a, I> {
pub fn from_str_iter(
args: I,
name: &'a str,
) -> impl Iterator<Item = Result<&'x str, &'x str>> + 'a {
ArgSplitFlagValue::new(args.map(Cow::Borrowed), name).map(|x| {
match x {
Ok(Cow::Borrowed(s)) => Ok(s),
Err(Cow::Borrowed(s)) => Err(s),
_ => panic!("iterator converted borrowed to owned"),
}
})
}
}
/// Yields all values of command line flag `name`.
pub struct ArgFlagValueIter;
impl ArgFlagValueIter {
pub fn from_string_iter<'a, I: Iterator<Item = String> + 'a>(
args: I,
name: &'a str,
) -> impl Iterator<Item = String> + 'a {
ArgSplitFlagValue::from_string_iter(args, name).filter_map(Result::ok)
}
}
impl ArgFlagValueIter {
pub fn from_str_iter<'x: 'a, 'a, I: Iterator<Item = &'x str> + 'a>(
args: I,
name: &'a str,
) -> impl Iterator<Item = &'x str> + 'a {
ArgSplitFlagValue::from_str_iter(args, name).filter_map(Result::ok)
}
}
/// Gets the values of a `--flag`.
pub fn get_arg_flag_values(name: &str) -> impl Iterator<Item = String> + '_ {
ArgFlagValueIter::from_string_iter(env::args(), name)
}
/// Gets the value of a `--flag`.
pub fn get_arg_flag_value(name: &str) -> Option<String> {
get_arg_flag_values(name).next()
}

View File

@ -0,0 +1,97 @@
#![allow(clippy::useless_format, clippy::derive_partial_eq_without_eq, rustc::internal)]
#[macro_use]
mod util;
mod arg;
mod phases;
mod setup;
mod version;
use std::{env, iter};
use crate::phases::*;
fn main() {
// Rustc does not support non-UTF-8 arguments so we make no attempt either.
// (We do support non-UTF-8 environment variables though.)
let mut args = std::env::args();
// Skip binary name.
args.next().unwrap();
// Dispatch to `cargo-miri` phase. Here is a rough idea of "who calls who".
//
// Initially, we are invoked as `cargo-miri miri run/test`. We first run the setup phase:
// - We call `xargo`, and set `RUSTC` back to us, together with `MIRI_CALLED_FROM_XARGO`,
// so that xargo's rustc invocations end up in `phase_rustc` with `RustcPhase::Setup`.
// There we then call the Miri driver with `MIRI_BE_RUSTC` to perform the actual build.
//
// Then we call `cargo run/test`, exactly forwarding all user flags, plus some configuration so
// that we control every binary invoked by cargo:
// - We set RUSTC_WRAPPER to ourselves, so for (almost) all rustc invocations, we end up in
// `phase_rustc` with `RustcPhase::Build`. This will in turn either determine that a
// dependency needs to be built (for which it invokes the Miri driver with `MIRI_BE_RUSTC`),
// or determine that this is a binary Miri should run, in which case we generate a JSON file
// with all the information needed to build and run this crate.
// (We don't run it yet since cargo thinks this is a build step, not a run step -- running the
// binary here would lead to a bad user experience.)
// - We set RUSTC to the Miri driver and also set `MIRI_BE_RUSTC`, so that gets called by build
// scripts (and cargo uses it for the version query).
// - We set `target.*.runner` to `cargo-miri runner`, which ends up calling `phase_runner` for
// `RunnerPhase::Cargo`. This parses the JSON file written in `phase_rustc` and then invokes
// the actual Miri driver for interpretation.
// - We set RUSTDOC to ourselves, which ends up in `phase_rustdoc`. There we call regular
// rustdoc with some extra flags, and we set `MIRI_CALLED_FROM_RUSTDOC` to recognize this
// phase in our recursive invocations:
// - We set the `--test-builder` flag of rustdoc to ourselves, which ends up in `phase_rustc`
// with `RustcPhase::Rustdoc`. There we perform a check-build (needed to get the expected
// build failures for `compile_fail` doctests) and then store a JSON file with the
// information needed to run this test.
// - We also set `--runtool` to ourselves, which ends up in `phase_runner` with
// `RunnerPhase::Rustdoc`. There we parse the JSON file written in `phase_rustc` and invoke
// the Miri driver for interpretation.
// Dispatch running as part of sysroot compilation.
if env::var_os("MIRI_CALLED_FROM_XARGO").is_some() {
phase_rustc(args, RustcPhase::Setup);
return;
}
// The way rustdoc invokes rustc is indistuingishable from the way cargo invokes rustdoc by the
// arguments alone. `phase_cargo_rustdoc` sets this environment variable to let us disambiguate.
if env::var_os("MIRI_CALLED_FROM_RUSTDOC").is_some() {
// ...however, we then also see this variable when rustdoc invokes us as the testrunner!
// The runner is invoked as `$runtool ($runtool-arg)* output_file`;
// since we don't specify any runtool-args, and rustdoc supplies multiple arguments to
// the test-builder unconditionally, we can just check the number of remaining arguments:
if args.len() == 1 {
phase_runner(args, RunnerPhase::Rustdoc);
} else {
phase_rustc(args, RustcPhase::Rustdoc);
}
return;
}
let Some(first) = args.next() else {
show_error!(
"`cargo-miri` called without first argument; please only invoke this binary through `cargo miri`"
)
};
match first.as_str() {
"miri" => phase_cargo_miri(args),
"runner" => phase_runner(args, RunnerPhase::Cargo),
arg if arg == env::var("RUSTC").unwrap() => {
// If the first arg is equal to the RUSTC env ariable (which should be set at this
// point), then we need to behave as rustc. This is the somewhat counter-intuitive
// behavior of having both RUSTC and RUSTC_WRAPPER set
// (see https://github.com/rust-lang/cargo/issues/10886).
phase_rustc(args, RustcPhase::Build)
}
_ => {
// Everything else must be rustdoc. But we need to get `first` "back onto the iterator",
// it is some part of the rustdoc invocation.
phase_rustdoc(iter::once(first).chain(args));
}
}
}

View File

@ -0,0 +1,601 @@
//! Implements the various phases of `cargo miri run/test`.
use std::env;
use std::fmt::Write as _;
use std::fs::{self, File};
use std::io::BufReader;
use std::path::PathBuf;
use std::process::Command;
use crate::{setup::*, util::*};
const CARGO_MIRI_HELP: &str = r#"Runs binary crates and tests in Miri
Usage:
cargo miri [subcommand] [<cargo options>...] [--] [<program/test suite options>...]
Subcommands:
run, r Run binaries
test, t Run tests
nextest Run tests with nextest (requires cargo-nextest installed)
setup Only perform automatic setup, but without asking questions (for getting a proper libstd)
The cargo options are exactly the same as for `cargo run` and `cargo test`, respectively.
Examples:
cargo miri run
cargo miri test -- test-suite-filter
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.
"#;
fn show_help() {
println!("{}", CARGO_MIRI_HELP);
}
fn show_version() {
let mut version = format!("miri {}", env!("CARGO_PKG_VERSION"));
// Only use `option_env` on vergen variables to ensure the build succeeds
// when vergen failed to find the git info.
if let Some(sha) = option_env!("VERGEN_GIT_SHA_SHORT") {
// This `unwrap` can never fail because if VERGEN_GIT_SHA_SHORT exists, then so does
// VERGEN_GIT_COMMIT_DATE.
#[allow(clippy::option_env_unwrap)]
write!(&mut version, " ({} {})", sha, option_env!("VERGEN_GIT_COMMIT_DATE").unwrap())
.unwrap();
}
println!("{}", version);
}
fn forward_patched_extern_arg(args: &mut impl Iterator<Item = String>, cmd: &mut Command) {
cmd.arg("--extern"); // always forward flag, but adjust filename:
let path = args.next().expect("`--extern` should be followed by a filename");
if let Some(lib) = path.strip_suffix(".rlib") {
// If this is an rlib, make it an rmeta.
cmd.arg(format!("{}.rmeta", lib));
} else {
// Some other extern file (e.g. a `.so`). Forward unchanged.
cmd.arg(path);
}
}
pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
// Check for version and help flags even when invoked as `cargo-miri`.
if has_arg_flag("--help") || has_arg_flag("-h") {
show_help();
return;
}
if has_arg_flag("--version") || has_arg_flag("-V") {
show_version();
return;
}
// Require a subcommand before any flags.
// We cannot know which of those flags take arguments and which do not,
// so we cannot detect subcommands later.
let Some(subcommand) = args.next() else {
show_error!("`cargo miri` needs to be called with a subcommand (`run`, `test`)");
};
let subcommand = match &*subcommand {
"setup" => MiriCommand::Setup,
"test" | "t" | "run" | "r" | "nextest" => MiriCommand::Forward(subcommand),
_ =>
show_error!(
"`cargo miri` supports the following subcommands: `run`, `test`, `nextest`, and `setup`."
),
};
let verbose = num_arg_flag("-v");
// Determine the involved architectures.
let host = version_info().host;
let target = get_arg_flag_value("--target");
let target = target.as_ref().unwrap_or(&host);
// We always setup.
setup(&subcommand, &host, target);
// 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
// requires some extra work to make the build check-only (see all the `--emit` hacks below).
// <https://github.com/rust-lang/miri/pull/1540#issuecomment-693553191> describes an alternative
// approach that uses `cargo check`, making that part easier but target and binary handling
// harder.
let cargo_miri_path = std::env::current_exe()
.expect("current executable path invalid")
.into_os_string()
.into_string()
.expect("current executable path is not valid UTF-8");
let cargo_cmd = match subcommand {
MiriCommand::Forward(s) => s,
MiriCommand::Setup => return, // `cargo miri setup` stops here.
};
let metadata = get_cargo_metadata();
let mut cmd = cargo();
cmd.arg(cargo_cmd);
// Forward all arguments before `--` other than `--target-dir` and its value to Cargo.
// (We want to *change* the target-dir value, so we must not forward it.)
let mut target_dir = None;
for arg in ArgSplitFlagValue::from_string_iter(&mut args, "--target-dir") {
match arg {
Ok(value) => {
if target_dir.is_some() {
show_error!("`--target-dir` is provided more than once");
}
target_dir = Some(value.into());
}
Err(arg) => {
cmd.arg(arg);
}
}
}
// Detect the target directory if it's not specified via `--target-dir`.
// (`cargo metadata` does not support `--target-dir`, that's why we have to handle this ourselves.)
let target_dir = target_dir.get_or_insert_with(|| metadata.target_directory.clone());
// Set `--target-dir` to `miri` inside the original target directory.
target_dir.push("miri");
cmd.arg("--target-dir").arg(target_dir);
// Make sure the build target is explicitly set.
// This is needed to make the `target.runner` settings do something,
// and it later helps us detect which crates are proc-macro/build-script
// (host crates) and which crates are needed for the program itself.
if get_arg_flag_value("--target").is_none() {
// No target given. Explicitly pick the host.
cmd.arg("--target");
cmd.arg(&host);
}
// Set ourselves as runner for al binaries invoked by cargo.
// We use `all()` since `true` is not a thing in cfg-lang, but the empty conjunction is. :)
let cargo_miri_path_for_toml = escape_for_toml(&cargo_miri_path);
cmd.arg("--config")
.arg(format!("target.'cfg(all())'.runner=[{cargo_miri_path_for_toml}, 'runner']"));
// Forward all further arguments after `--` to cargo.
cmd.arg("--").args(args);
// 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`.)
if env::var_os("RUSTC_WRAPPER").is_some() {
println!(
"WARNING: Ignoring `RUSTC_WRAPPER` environment variable, Miri does not support wrapping."
);
}
cmd.env("RUSTC_WRAPPER", &cargo_miri_path);
// We are going to invoke `MIRI` for everything, not `RUSTC`.
if env::var_os("RUSTC").is_some() && env::var_os("MIRI").is_none() {
println!(
"WARNING: Ignoring `RUSTC` environment variable; set `MIRI` if you want to control the binary used as the driver."
);
}
// Build scripts (and also cargo: https://github.com/rust-lang/cargo/issues/10885) will invoke
// `rustc` even when `RUSTC_WRAPPER` is set. 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.
//
// 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
// there would be a collision with other invocations of cargo-miri (as rustdoc or as runner). We
// explicitly do this even if RUSTC_STAGE is set, since for these builds we do *not* want the
// 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!
// Set rustdoc to us as well, so we can run doctests.
cmd.env("RUSTDOC", &cargo_miri_path);
cmd.env("MIRI_LOCAL_CRATES", local_crates(&metadata));
if verbose > 0 {
cmd.env("MIRI_VERBOSE", verbose.to_string()); // This makes the other phases verbose.
}
// Run cargo.
debug_cmd("[cargo-miri miri]", verbose, &cmd);
exec(cmd)
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum RustcPhase {
/// `rustc` called via `xargo` for sysroot build.
Setup,
/// `rustc` called by `cargo` for regular build.
Build,
/// `rustc` called by `rustdoc` for doctest.
Rustdoc,
}
pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
/// Determines if we are being invoked (as rustc) to build a crate for
/// the "target" architecture, in contrast to the "host" architecture.
/// Host crates are for build scripts and proc macros and still need to
/// be built like normal; target crates need to be built for or interpreted
/// by Miri.
///
/// Currently, we detect this by checking for "--target=", which is
/// never set for host crates. This matches what rustc bootstrap does,
/// which hopefully makes it "reliable enough". This relies on us always
/// invoking cargo itself with `--target`, which `in_cargo_miri` ensures.
fn is_target_crate() -> bool {
get_arg_flag_value("--target").is_some()
}
/// Returns whether or not Cargo invoked the wrapper (this binary) to compile
/// the final, binary crate (either a test for 'cargo test', or a binary for 'cargo run')
/// Cargo does not give us this information directly, so we need to check
/// various command-line flags.
fn is_runnable_crate() -> bool {
let is_bin = get_arg_flag_value("--crate-type").as_deref().unwrap_or("bin") == "bin";
let is_test = has_arg_flag("--test");
is_bin || is_test
}
fn out_filename(prefix: &str, suffix: &str) -> PathBuf {
if let Some(out_dir) = get_arg_flag_value("--out-dir") {
let mut path = PathBuf::from(out_dir);
path.push(format!(
"{}{}{}{}",
prefix,
get_arg_flag_value("--crate-name").unwrap(),
// This is technically a `-C` flag but the prefix seems unique enough...
// (and cargo passes this before the filename so it should be unique)
get_arg_flag_value("extra-filename").unwrap_or_default(),
suffix,
));
path
} else {
let out_file = get_arg_flag_value("-o").unwrap();
PathBuf::from(out_file)
}
}
// phase_cargo_miri set `MIRI_BE_RUSTC` for when build scripts directly invoke the driver;
// however, if we get called back by cargo here, we'll carefully compute the right flags
// ourselves, so we first un-do what the earlier phase did.
env::remove_var("MIRI_BE_RUSTC");
let verbose = std::env::var("MIRI_VERBOSE")
.map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
let target_crate = is_target_crate();
// Determine whether this is cargo/xargo invoking rustc to get some infos.
let info_query = get_arg_flag_value("--print").is_some() || has_arg_flag("-vV");
let store_json = |info: CrateRunInfo| {
// Create a stub .d file to stop Cargo from "rebuilding" the crate:
// https://github.com/rust-lang/miri/issues/1724#issuecomment-787115693
// As we store a JSON file instead of building the crate here, an empty file is fine.
let dep_info_name = out_filename("", ".d");
if verbose > 0 {
eprintln!("[cargo-miri rustc] writing stub dep-info to `{}`", dep_info_name.display());
}
File::create(dep_info_name).expect("failed to create fake .d file");
let filename = out_filename("", "");
if verbose > 0 {
eprintln!("[cargo-miri rustc] writing run info to `{}`", filename.display());
}
info.store(&filename);
// For Windows, do the same thing again with `.exe` appended to the filename.
// (Need to do this here as cargo moves that "binary" to a different place before running it.)
info.store(&out_filename("", ".exe"));
};
let runnable_crate = !info_query && is_runnable_crate();
if runnable_crate && target_crate {
assert!(
phase != RustcPhase::Setup,
"there should be no interpretation during sysroot build"
);
let inside_rustdoc = phase == RustcPhase::Rustdoc;
// This is the binary or test crate that we want to interpret under Miri.
// But we cannot run it here, as cargo invoked us as a compiler -- our stdin and stdout are not
// like we want them.
// Instead of compiling, we write JSON into the output file with all the relevant command-line flags
// and environment variables; this is used when cargo calls us again in the CARGO_TARGET_RUNNER phase.
let env = CrateRunEnv::collect(args, inside_rustdoc);
store_json(CrateRunInfo::RunWith(env.clone()));
// Rustdoc expects us to exit with an error code if the test is marked as `compile_fail`,
// just creating the JSON file is not enough: we need to detect syntax errors,
// so we need to run Miri with `MIRI_BE_RUSTC` for a check-only build.
if inside_rustdoc {
let mut cmd = miri();
// Ensure --emit argument for a check-only build is present.
if let Some(val) =
ArgFlagValueIter::from_str_iter(env.args.iter().map(|s| s as &str), "--emit").next()
{
// For `no_run` tests, rustdoc passes a `--emit` flag; make sure it has the right shape.
assert_eq!(val, "metadata");
} else {
// For all other kinds of tests, we can just add our flag.
cmd.arg("--emit=metadata");
}
// Alter the `-o` parameter so that it does not overwrite the JSON file we stored above.
let mut args = env.args;
for i in 0..args.len() {
if args[i] == "-o" {
args[i + 1].push_str(".miri");
}
}
cmd.args(&args);
cmd.env("MIRI_BE_RUSTC", "target");
if verbose > 0 {
eprintln!(
"[cargo-miri rustc inside rustdoc] captured input:\n{}",
std::str::from_utf8(&env.stdin).unwrap()
);
eprintln!("[cargo-miri rustc inside rustdoc] going to run:\n{:?}", cmd);
}
exec_with_pipe(cmd, &env.stdin, format!("{}.stdin", out_filename("", "").display()));
}
return;
}
if runnable_crate && get_arg_flag_values("--extern").any(|krate| krate == "proc_macro") {
// This is a "runnable" `proc-macro` crate (unit tests). We do not support
// interpreting that under Miri now, so we write a JSON file to (display a
// helpful message and) skip it in the runner phase.
store_json(CrateRunInfo::SkipProcMacroTest);
return;
}
let mut cmd = miri();
let mut emit_link_hack = false;
// 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 !info_query && target_crate {
// Forward arguments, but remove "link" from "--emit" to make this a check-only build.
let emit_flag = "--emit";
while let Some(arg) = args.next() {
if let Some(val) = arg.strip_prefix(emit_flag) {
// Patch this argument. First, extract its value.
let val =
val.strip_prefix('=').expect("`cargo` should pass `--emit=X` as one argument");
let mut val: Vec<_> = val.split(',').collect();
// Now make sure "link" is not in there, but "metadata" is.
if let Some(i) = val.iter().position(|&s| s == "link") {
emit_link_hack = true;
val.remove(i);
if !val.iter().any(|&s| s == "metadata") {
val.push("metadata");
}
}
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
forward_patched_extern_arg(&mut args, &mut cmd);
} else {
cmd.arg(arg);
}
}
// During setup, patch the panic runtime for `libpanic_abort` (mirroring what bootstrap usually does).
if phase == RustcPhase::Setup
&& get_arg_flag_value("--crate-name").as_deref() == Some("panic_abort")
{
cmd.arg("-C").arg("panic=abort");
}
} else {
// For host crates (but not when we are just printing some info),
// we might still have to set the sysroot.
if !info_query {
// When we're running `cargo-miri` from `x.py` we need to pass the sysroot explicitly
// due to bootstrap complications.
if let Some(sysroot) = std::env::var_os("MIRI_HOST_SYSROOT") {
cmd.arg("--sysroot").arg(sysroot);
}
}
// For host crates or when we are printing, just forward everything.
cmd.args(args);
}
// We want to compile, not interpret. We still use Miri to make sure the compiler version etc
// are the exact same as what is used for interpretation.
// MIRI_DEFAULT_ARGS should not be used to build host crates, hence setting "target" or "host"
// as the value here to help Miri differentiate them.
cmd.env("MIRI_BE_RUSTC", if target_crate { "target" } else { "host" });
// Run it.
if verbose > 0 {
eprintln!(
"[cargo-miri rustc] target_crate={target_crate} runnable_crate={runnable_crate} info_query={info_query}"
);
}
// Create a stub .rlib file if "link" was requested by cargo.
// This is necessary to prevent cargo from doing rebuilds all the time.
if emit_link_hack {
// Some platforms prepend "lib", some do not... let's just create both files.
File::create(out_filename("lib", ".rlib")).expect("failed to create fake .rlib file");
File::create(out_filename("", ".rlib")).expect("failed to create fake .rlib file");
// Just in case this is a cdylib or staticlib, also create those fake files.
File::create(out_filename("lib", ".so")).expect("failed to create fake .so file");
File::create(out_filename("lib", ".a")).expect("failed to create fake .a file");
File::create(out_filename("lib", ".dylib")).expect("failed to create fake .dylib file");
File::create(out_filename("", ".dll")).expect("failed to create fake .dll file");
File::create(out_filename("", ".lib")).expect("failed to create fake .lib file");
}
debug_cmd("[cargo-miri rustc]", verbose, &cmd);
exec(cmd);
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum RunnerPhase {
/// `cargo` is running a binary
Cargo,
/// `rustdoc` is running a binary
Rustdoc,
}
pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: RunnerPhase) {
// phase_cargo_miri set `MIRI_BE_RUSTC` for when build scripts directly invoke the driver;
// however, if we get called back by cargo here, we'll carefully compute the right flags
// ourselves, so we first un-do what the earlier phase did.
env::remove_var("MIRI_BE_RUSTC");
let verbose = std::env::var("MIRI_VERBOSE")
.map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
let binary = binary_args.next().unwrap();
let file = File::open(&binary)
.unwrap_or_else(|_| show_error!(
"file {:?} not found or `cargo-miri` invoked incorrectly; please only invoke this binary through `cargo miri`", binary
));
let file = BufReader::new(file);
let info = serde_json::from_reader(file).unwrap_or_else(|_| {
show_error!("file {:?} contains outdated or invalid JSON; try `cargo clean`", binary)
});
let info = match info {
CrateRunInfo::RunWith(info) => info,
CrateRunInfo::SkipProcMacroTest => {
eprintln!(
"Running unit tests of `proc-macro` crates is not currently supported by Miri."
);
return;
}
};
let mut cmd = miri();
// Set missing env vars. We prefer build-time env vars over run-time ones; see
// <https://github.com/rust-lang/miri/issues/1661> for the kind of issue that fixes.
for (name, val) in info.env {
if let Some(old_val) = env::var_os(&name) {
if old_val == val {
// This one did not actually change, no need to re-set it.
// (This keeps the `debug_cmd` below more manageable.)
continue;
} else if verbose > 0 {
eprintln!(
"[cargo-miri runner] Overwriting run-time env var {:?}={:?} with build-time value {:?}",
name, old_val, val
);
}
}
cmd.env(name, val);
}
// 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
// `.rmeta`.
// We also need to remove `--error-format` as cargo specifies that to be JSON,
// but when we run here, cargo does not interpret the JSON any more. `--json`
// then also nees to be dropped.
let mut args = info.args.into_iter();
let error_format_flag = "--error-format";
let json_flag = "--json";
while let Some(arg) = args.next() {
if arg == "--extern" {
forward_patched_extern_arg(&mut args, &mut cmd);
} else if let Some(suffix) = arg.strip_prefix(error_format_flag) {
assert!(suffix.starts_with('='));
// Drop this argument.
} else if let Some(suffix) = arg.strip_prefix(json_flag) {
assert!(suffix.starts_with('='));
// Drop this argument.
} else {
cmd.arg(arg);
}
}
// Respect `MIRIFLAGS`.
if let Ok(a) = env::var("MIRIFLAGS") {
// This code is taken from `RUSTFLAGS` handling in cargo.
let args = a.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string);
cmd.args(args);
}
// Then pass binary arguments.
cmd.arg("--");
cmd.args(binary_args);
// Make sure we use the build-time working directory for interpreting Miri/rustc arguments.
// But then we need to switch to the run-time one, which we instruct Miri do do by setting `MIRI_CWD`.
cmd.current_dir(info.current_dir);
cmd.env("MIRI_CWD", env::current_dir().unwrap());
// Run it.
debug_cmd("[cargo-miri runner]", verbose, &cmd);
match phase {
RunnerPhase::Rustdoc => exec_with_pipe(cmd, &info.stdin, format!("{}.stdin", binary)),
RunnerPhase::Cargo => exec(cmd),
}
}
pub fn phase_rustdoc(mut args: impl Iterator<Item = String>) {
let verbose = std::env::var("MIRI_VERBOSE")
.map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
// phase_cargo_miri sets the RUSTDOC env var to ourselves, so we can't use that here;
// just default to a straight-forward invocation for now:
let mut cmd = Command::new("rustdoc");
let extern_flag = "--extern";
let runtool_flag = "--runtool";
while let Some(arg) = args.next() {
if arg == extern_flag {
// Patch --extern arguments to use *.rmeta files, since phase_cargo_rustc only creates stub *.rlib files.
forward_patched_extern_arg(&mut args, &mut cmd);
} else if arg == runtool_flag {
// An existing --runtool flag indicates cargo is running in cross-target mode, which we don't support.
// Note that this is only passed when cargo is run with the unstable -Zdoctest-xcompile flag;
// otherwise, we won't be called as rustdoc at all.
show_error!("cross-interpreting doctests is not currently supported by Miri.");
} else {
cmd.arg(arg);
}
}
// Doctests of `proc-macro` crates (and their dependencies) are always built for the host,
// so we are not able to run them in Miri.
if get_arg_flag_values("--crate-type").any(|crate_type| crate_type == "proc-macro") {
eprintln!("Running doctests of `proc-macro` crates is not currently supported by Miri.");
return;
}
// For each doctest, rustdoc starts two child processes: first the test is compiled,
// then the produced executable is invoked. We want to reroute both of these to cargo-miri,
// such that the first time we'll enter phase_cargo_rustc, and phase_cargo_runner second.
//
// rustdoc invokes the test-builder by forwarding most of its own arguments, which makes
// it difficult to determine when phase_cargo_rustc should run instead of phase_cargo_rustdoc.
// Furthermore, the test code is passed via stdin, rather than a temporary file, so we need
// to let phase_cargo_rustc know to expect that. We'll use this environment variable as a flag:
cmd.env("MIRI_CALLED_FROM_RUSTDOC", "1");
// The `--test-builder` and `--runtool` arguments are unstable rustdoc features,
// which are disabled by default. We first need to enable them explicitly:
cmd.arg("-Z").arg("unstable-options");
// rustdoc needs to know the right sysroot.
cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap());
// make sure the 'miri' flag is set for rustdoc
cmd.arg("--cfg").arg("miri");
// Make rustdoc call us back.
let cargo_miri_path = std::env::current_exe().expect("current executable path invalid");
cmd.arg("--test-builder").arg(&cargo_miri_path); // invoked by forwarding most arguments
cmd.arg("--runtool").arg(&cargo_miri_path); // invoked with just a single path argument
debug_cmd("[cargo-miri rustdoc]", verbose, &cmd);
exec(cmd)
}

View File

@ -0,0 +1,245 @@
//! Implements `cargo miri setup` via xargo
use std::env;
use std::ffi::OsStr;
use std::fs::{self};
use std::io::BufRead;
use std::ops::Not;
use std::path::{Path, PathBuf};
use std::process::{self, Command};
use crate::{util::*, version::*};
fn xargo_version() -> Option<(u32, u32, u32)> {
let out = xargo_check().arg("--version").output().ok()?;
if !out.status.success() {
return None;
}
// Parse output. The first line looks like "xargo 0.3.12 (b004f1c 2018-12-13)".
let line = out
.stderr
.lines()
.next()
.expect("malformed `xargo --version` output: not at least one line")
.expect("malformed `xargo --version` output: error reading first line");
let (name, version) = {
let mut split = line.split(' ');
(
split.next().expect("malformed `xargo --version` output: empty"),
split.next().expect("malformed `xargo --version` output: not at least two words"),
)
};
if name != "xargo" {
// This is some fork of xargo
return None;
}
let mut version_pieces = version.split('.');
let major = version_pieces
.next()
.expect("malformed `xargo --version` output: not a major version piece")
.parse()
.expect("malformed `xargo --version` output: major version is not an integer");
let minor = version_pieces
.next()
.expect("malformed `xargo --version` output: not a minor version piece")
.parse()
.expect("malformed `xargo --version` output: minor version is not an integer");
let patch = version_pieces
.next()
.expect("malformed `xargo --version` output: not a patch version piece")
.parse()
.expect("malformed `xargo --version` output: patch version is not an integer");
if version_pieces.next().is_some() {
panic!("malformed `xargo --version` output: more than three pieces in version");
}
Some((major, minor, patch))
}
/// Performs the setup required to make `cargo miri` work: Getting a custom-built libstd. Then sets
/// `MIRI_SYSROOT`. Skipped if `MIRI_SYSROOT` is already set, in which case we expect the user has
/// done all this already.
pub fn setup(subcommand: &MiriCommand, host: &str, target: &str) {
let only_setup = matches!(subcommand, MiriCommand::Setup);
let ask_user = !only_setup;
let print_sysroot = only_setup && has_arg_flag("--print-sysroot"); // whether we just print the sysroot path
if std::env::var_os("MIRI_SYSROOT").is_some() {
if only_setup {
println!("WARNING: MIRI_SYSROOT already set, not doing anything.")
}
return;
}
// First, we need xargo.
if xargo_version().map_or(true, |v| v < XARGO_MIN_VERSION) {
if std::env::var_os("XARGO_CHECK").is_some() {
// The user manually gave us a xargo binary; don't do anything automatically.
show_error!("xargo is too old; please upgrade to the latest version")
}
let mut cmd = cargo();
cmd.args(["install", "xargo"]);
ask_to_run(cmd, ask_user, "install a recent enough xargo");
}
// Determine where the rust sources are located. The env vars manually setting the source
// (`MIRI_LIB_SRC`, `XARGO_RUST_SRC`) trump auto-detection.
let rust_src_env_var =
std::env::var_os("MIRI_LIB_SRC").or_else(|| std::env::var_os("XARGO_RUST_SRC"));
let rust_src = match rust_src_env_var {
Some(path) => {
let path = PathBuf::from(path);
// Make path absolute if possible.
path.canonicalize().unwrap_or(path)
}
None => {
// Check for `rust-src` rustup component.
let output = miri_for_host()
.args(["--print", "sysroot"])
.output()
.expect("failed to determine sysroot");
if !output.status.success() {
show_error!(
"Failed to determine sysroot; Miri said:\n{}",
String::from_utf8_lossy(&output.stderr).trim_end()
);
}
let sysroot = std::str::from_utf8(&output.stdout).unwrap();
let sysroot = Path::new(sysroot.trim_end_matches('\n'));
// Check for `$SYSROOT/lib/rustlib/src/rust/library`; test if that contains `std/Cargo.toml`.
let rustup_src =
sysroot.join("lib").join("rustlib").join("src").join("rust").join("library");
if !rustup_src.join("std").join("Cargo.toml").exists() {
// Ask the user to install the `rust-src` component, and use that.
let mut cmd = Command::new("rustup");
cmd.args(["component", "add", "rust-src"]);
ask_to_run(
cmd,
ask_user,
"install the `rust-src` component for the selected toolchain",
);
}
rustup_src
}
};
if !rust_src.exists() {
show_error!("given Rust source directory `{}` does not exist.", rust_src.display());
}
if rust_src.file_name().and_then(OsStr::to_str) != Some("library") {
show_error!(
"given Rust source directory `{}` does not seem to be the `library` subdirectory of \
a Rust source checkout.",
rust_src.display()
);
}
// Next, we need our own libstd. Prepare a xargo project for that purpose.
// We will do this work in whatever is a good cache dir for this platform.
let dirs = directories::ProjectDirs::from("org", "rust-lang", "miri").unwrap();
let dir = dirs.cache_dir();
if !dir.exists() {
fs::create_dir_all(dir).unwrap();
}
// The interesting bit: Xargo.toml (only needs content if we actually need std)
let xargo_toml = if std::env::var_os("MIRI_NO_STD").is_some() {
""
} else {
r#"
[dependencies.std]
default_features = false
# We support unwinding, so enable that panic runtime.
features = ["panic_unwind", "backtrace"]
[dependencies.test]
"#
};
write_to_file(&dir.join("Xargo.toml"), xargo_toml);
// The boring bits: a dummy project for xargo.
// FIXME: With xargo-check, can we avoid doing this?
write_to_file(
&dir.join("Cargo.toml"),
r#"
[package]
name = "miri-xargo"
description = "A dummy project for building libstd with xargo."
version = "0.0.0"
[lib]
path = "lib.rs"
"#,
);
write_to_file(&dir.join("lib.rs"), "#![no_std]");
// Figure out where xargo will build its stuff.
// Unfortunately, it puts things into a different directory when the
// architecture matches the host.
let sysroot = if target == host { dir.join("HOST") } else { PathBuf::from(dir) };
// Make sure all target-level Miri invocations know their sysroot.
std::env::set_var("MIRI_SYSROOT", &sysroot);
// Now invoke xargo.
let mut command = xargo_check();
command.arg("check").arg("-q");
command.current_dir(dir);
command.env("XARGO_HOME", dir);
command.env("XARGO_RUST_SRC", &rust_src);
// We always need to set a target so rustc bootstrap can tell apart host from target crates.
command.arg("--target").arg(target);
// Use Miri as rustc to build a libstd compatible with us (and use the right flags).
// However, when we are running in bootstrap, we cannot just overwrite `RUSTC`,
// because we still need bootstrap to distinguish between host and target crates.
// In that case we overwrite `RUSTC_REAL` instead which determines the rustc used
// for target crates.
// We set ourselves (`cargo-miri`) instead of Miri directly to be able to patch the flags
// for `libpanic_abort` (usually this is done by bootstrap but we have to do it ourselves).
// The `MIRI_CALLED_FROM_XARGO` will mean we dispatch to `phase_setup_rustc`.
let cargo_miri_path = std::env::current_exe().expect("current executable path invalid");
if env::var_os("RUSTC_STAGE").is_some() {
assert!(env::var_os("RUSTC").is_some());
command.env("RUSTC_REAL", &cargo_miri_path);
} else {
command.env("RUSTC", &cargo_miri_path);
}
command.env("MIRI_CALLED_FROM_XARGO", "1");
// Make sure there are no other wrappers getting in our way
// (Cc https://github.com/rust-lang/miri/issues/1421, https://github.com/rust-lang/miri/issues/2429).
// Looks like setting `RUSTC_WRAPPER` to the empty string overwrites `build.rustc-wrapper` set via `config.toml`.
command.env("RUSTC_WRAPPER", "");
// Disable debug assertions in the standard library -- Miri is already slow enough. But keep the
// overflow checks, they are cheap. This completely overwrites flags the user might have set,
// which is consistent with normal `cargo build` that does not apply `RUSTFLAGS` to the sysroot
// either.
command.env("RUSTFLAGS", "-Cdebug-assertions=off -Coverflow-checks=on");
// Manage the output the user sees.
if only_setup {
// We want to be explicit.
eprintln!("Preparing a sysroot for Miri (target: {target})...");
if print_sysroot {
// Be extra sure there is no noise on stdout.
command.stdout(process::Stdio::null());
}
} else {
// We want to be quiet, but still let the user know that something is happening.
eprint!("Preparing a sysroot for Miri (target: {target})... ");
command.stdout(process::Stdio::null());
command.stderr(process::Stdio::null());
}
// Finally run it!
if command.status().expect("failed to run xargo").success().not() {
if only_setup {
show_error!("failed to run xargo, see error details above")
} else {
show_error!("failed to run xargo; run `cargo miri setup` to see the error details")
}
}
// Figure out what to print.
if only_setup {
eprintln!("A sysroot for Miri is now available in `{}`.", sysroot.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.display());
}
}

View File

@ -0,0 +1,314 @@
use std::collections::HashMap;
use std::env;
use std::ffi::OsString;
use std::fmt::Write as _;
use std::fs::{self, File};
use std::io::{self, BufWriter, Read, Write};
use std::ops::Not;
use std::path::{Path, PathBuf};
use std::process::Command;
use cargo_metadata::{Metadata, MetadataCommand};
use rustc_version::VersionMeta;
use serde::{Deserialize, Serialize};
pub use crate::arg::*;
pub fn show_error(msg: &impl std::fmt::Display) -> ! {
eprintln!("fatal error: {msg}");
std::process::exit(1)
}
macro_rules! show_error {
($($tt:tt)*) => { crate::util::show_error(&format_args!($($tt)*)) };
}
/// The information to run a crate with the given environment.
#[derive(Clone, Serialize, Deserialize)]
pub struct CrateRunEnv {
/// The command-line arguments.
pub args: Vec<String>,
/// The environment.
pub env: Vec<(OsString, OsString)>,
/// The current working directory.
pub current_dir: OsString,
/// The contents passed via standard input.
pub stdin: Vec<u8>,
}
impl CrateRunEnv {
/// Gather all the information we need.
pub fn collect(args: impl Iterator<Item = String>, capture_stdin: bool) -> Self {
let args = args.collect();
let env = env::vars_os().collect();
let current_dir = env::current_dir().unwrap().into_os_string();
let mut stdin = Vec::new();
if capture_stdin {
std::io::stdin().lock().read_to_end(&mut stdin).expect("cannot read stdin");
}
CrateRunEnv { args, env, current_dir, stdin }
}
}
/// The information Miri needs to run a crate. Stored as JSON when the crate is "compiled".
#[derive(Serialize, Deserialize)]
pub enum CrateRunInfo {
/// Run it with the given environment.
RunWith(CrateRunEnv),
/// Skip it as Miri does not support interpreting such kind of crates.
SkipProcMacroTest,
}
impl CrateRunInfo {
pub fn store(&self, filename: &Path) {
let file = File::create(filename)
.unwrap_or_else(|_| show_error!("cannot create `{}`", filename.display()));
let file = BufWriter::new(file);
serde_json::ser::to_writer(file, self)
.unwrap_or_else(|_| show_error!("cannot write to `{}`", filename.display()));
}
}
#[derive(Clone, Debug)]
pub enum MiriCommand {
/// Our own special 'setup' command.
Setup,
/// A command to be forwarded to cargo.
Forward(String),
}
/// Escapes `s` in a way that is suitable for using it as a string literal in TOML syntax.
pub fn escape_for_toml(s: &str) -> String {
// We want to surround this string in quotes `"`. So we first escape all quotes,
// and also all backslashes (that are used to escape quotes).
let s = s.replace('\\', r#"\\"#).replace('"', r#"\""#);
format!("\"{}\"", s)
}
/// Returns the path to the `miri` binary
pub fn find_miri() -> PathBuf {
if let Some(path) = env::var_os("MIRI") {
return path.into();
}
let mut path = std::env::current_exe().expect("current executable path invalid");
if cfg!(windows) {
path.set_file_name("miri.exe");
} else {
path.set_file_name("miri");
}
path
}
pub fn miri() -> Command {
Command::new(find_miri())
}
pub fn miri_for_host() -> Command {
let mut cmd = miri();
cmd.env("MIRI_BE_RUSTC", "host");
cmd
}
pub fn version_info() -> VersionMeta {
VersionMeta::for_command(miri_for_host())
.expect("failed to determine underlying rustc version of Miri")
}
pub fn cargo() -> Command {
Command::new(env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")))
}
pub fn xargo_check() -> Command {
Command::new(env::var_os("XARGO_CHECK").unwrap_or_else(|| OsString::from("xargo-check")))
}
/// Execute the `Command`, where possible by replacing the current process with a new process
/// described by the `Command`. Then exit this process with the exit code of the new process.
pub fn exec(mut cmd: Command) -> ! {
// On non-Unix imitate POSIX exec as closely as we can
#[cfg(not(unix))]
{
let exit_status = cmd.status().expect("failed to run command");
std::process::exit(exit_status.code().unwrap_or(-1))
}
// On Unix targets, actually exec.
// If exec returns, process setup has failed. This is the same error condition as the expect in
// the non-Unix case.
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
let error = cmd.exec();
Err(error).expect("failed to run command")
}
}
/// Execute the `Command`, where possible by replacing the current process with a new process
/// described by the `Command`. Then exit this process with the exit code of the new process.
/// `input` is also piped to the new process's stdin, on cfg(unix) platforms by writing its
/// contents to `path` first, then setting stdin to that file.
pub fn exec_with_pipe<P>(mut cmd: Command, input: &[u8], path: P) -> !
where
P: AsRef<Path>,
{
#[cfg(unix)]
{
// Write the bytes we want to send to stdin out to a file
std::fs::write(&path, input).unwrap();
// Open the file for reading, and set our new stdin to it
let stdin = File::open(&path).unwrap();
cmd.stdin(stdin);
// Unlink the file so that it is fully cleaned up as soon as the new process exits
std::fs::remove_file(&path).unwrap();
// Finally, we can hand off control.
exec(cmd)
}
#[cfg(not(unix))]
{
drop(path); // We don't need the path, we can pipe the bytes directly
cmd.stdin(std::process::Stdio::piped());
let mut child = cmd.spawn().expect("failed to spawn process");
{
let stdin = child.stdin.as_mut().expect("failed to open stdin");
stdin.write_all(input).expect("failed to write out test source");
}
let exit_status = child.wait().expect("failed to run command");
std::process::exit(exit_status.code().unwrap_or(-1))
}
}
pub fn ask_to_run(mut cmd: Command, ask: bool, text: &str) {
// Disable interactive prompts in CI (GitHub Actions, Travis, AppVeyor, etc).
// Azure doesn't set `CI` though (nothing to see here, just Microsoft being Microsoft),
// so we also check their `TF_BUILD`.
let is_ci = env::var_os("CI").is_some() || env::var_os("TF_BUILD").is_some();
if ask && !is_ci {
let mut buf = String::new();
print!("I will run `{:?}` to {}. Proceed? [Y/n] ", cmd, text);
io::stdout().flush().unwrap();
io::stdin().read_line(&mut buf).unwrap();
match buf.trim().to_lowercase().as_ref() {
// Proceed.
"" | "y" | "yes" => {}
"n" | "no" => show_error!("aborting as per your request"),
a => show_error!("invalid answer `{}`", a),
};
} else {
eprintln!("Running `{:?}` to {}.", cmd, text);
}
if cmd.status().unwrap_or_else(|_| panic!("failed to execute {:?}", cmd)).success().not() {
show_error!("failed to {}", text);
}
}
/// Writes the given content to the given file *cross-process atomically*, in the sense that another
/// process concurrently reading that file will see either the old content or the new content, but
/// not some intermediate (e.g., empty) state.
///
/// We assume no other parts of this same process are trying to read or write that file.
pub fn write_to_file(filename: &Path, content: &str) {
// Create a temporary file with the desired contents.
let mut temp_filename = filename.as_os_str().to_os_string();
temp_filename.push(&format!(".{}", std::process::id()));
let mut temp_file = File::create(&temp_filename).unwrap();
temp_file.write_all(content.as_bytes()).unwrap();
drop(temp_file);
// Move file to the desired location.
fs::rename(temp_filename, filename).unwrap();
}
// Computes the extra flags that need to be passed to cargo to make it behave like the current
// cargo invocation.
fn cargo_extra_flags() -> Vec<String> {
let mut flags = Vec::new();
// `-Zunstable-options` is required by `--config`.
flags.push("-Zunstable-options".to_string());
// Forward `--config` flags.
let config_flag = "--config";
for arg in get_arg_flag_values(config_flag) {
flags.push(config_flag.to_string());
flags.push(arg);
}
// Forward `--manifest-path`.
let manifest_flag = "--manifest-path";
if let Some(manifest) = get_arg_flag_value(manifest_flag) {
flags.push(manifest_flag.to_string());
flags.push(manifest);
}
// Forwarding `--target-dir` would make sense, but `cargo metadata` does not support that flag.
flags
}
pub fn get_cargo_metadata() -> Metadata {
// This will honor the `CARGO` env var the same way our `cargo()` does.
MetadataCommand::new().no_deps().other_options(cargo_extra_flags()).exec().unwrap()
}
/// Pulls all the crates in this workspace from the cargo metadata.
/// Workspace members are emitted like "miri 0.1.0 (path+file:///path/to/miri)"
/// Additionally, somewhere between cargo metadata and TyCtxt, '-' gets replaced with '_' so we
/// make that same transformation here.
pub fn local_crates(metadata: &Metadata) -> String {
assert!(!metadata.workspace_members.is_empty());
let mut local_crates = String::new();
for member in &metadata.workspace_members {
let name = member.repr.split(' ').next().unwrap();
let name = name.replace('-', "_");
local_crates.push_str(&name);
local_crates.push(',');
}
local_crates.pop(); // Remove the trailing ','
local_crates
}
fn env_vars_from_cmd(cmd: &Command) -> Vec<(String, String)> {
let mut envs = HashMap::new();
for (key, value) in std::env::vars() {
envs.insert(key, value);
}
for (key, value) in cmd.get_envs() {
if let Some(value) = value {
envs.insert(key.to_string_lossy().to_string(), value.to_string_lossy().to_string());
} else {
envs.remove(&key.to_string_lossy().to_string());
}
}
let mut envs: Vec<_> = envs.into_iter().collect();
envs.sort();
envs
}
/// Debug-print a command that is going to be run.
pub fn debug_cmd(prefix: &str, verbose: usize, cmd: &Command) {
if verbose == 0 {
return;
}
// We only do a single `eprintln!` call to minimize concurrency interactions.
let mut out = prefix.to_string();
writeln!(out, " running command: env \\").unwrap();
if verbose > 1 {
// Print the full environment this will be called in.
for (key, value) in env_vars_from_cmd(cmd) {
writeln!(out, "{key}={value:?} \\").unwrap();
}
} else {
// Print only what has been changed for this `cmd`.
for (var, val) in cmd.get_envs() {
if let Some(val) = val {
writeln!(out, "{}={:?} \\", var.to_string_lossy(), val).unwrap();
} else {
writeln!(out, "--unset={}", var.to_string_lossy()).unwrap();
}
}
}
write!(out, "{cmd:?}").unwrap();
eprintln!("{}", out);
}

View File

@ -0,0 +1,2 @@
// We put this in a separate file so that it can be hashed for GHA caching.
pub const XARGO_MIN_VERSION: (u32, u32, u32) = (0, 3, 26);

101
src/tools/miri/ci.sh Executable file
View File

@ -0,0 +1,101 @@
#!/bin/bash
set -euo pipefail
set -x
# Determine configuration for installed build
echo "Installing release version of Miri"
export RUSTFLAGS="-D warnings"
export CARGO_INCREMENTAL=0
./miri install # implicitly locked
# Prepare debug build for direct `./miri` invocations
echo "Building debug version of Miri"
export CARGO_EXTRA_FLAGS="--locked"
./miri check --no-default-features # make sure this can be built
./miri check --all-features # and this, too
./miri build --all-targets # the build that all the `./miri test` below will use
echo
# Test
function run_tests {
if [ -n "${MIRI_TEST_TARGET+exists}" ]; then
echo "Testing foreign architecture $MIRI_TEST_TARGET"
else
echo "Testing host architecture"
fi
## ui test suite
./miri test
if [ -z "${MIRI_TEST_TARGET+exists}" ]; then
# Only for host architecture: tests with optimizations (`-O` is what cargo passes, but crank MIR
# optimizations up all the way).
# Optimizations change diagnostics (mostly backtraces), so we don't check them
#FIXME(#2155): we want to only run the pass and panic tests here, not the fail tests.
MIRIFLAGS="${MIRIFLAGS:-} -O -Zmir-opt-level=4" MIRI_SKIP_UI_CHECKS=1 ./miri test -- tests/{pass,panic}
fi
## test-cargo-miri
# On Windows, there is always "python", not "python3" or "python2".
if command -v python3 > /dev/null; then
PYTHON=python3
else
PYTHON=python
fi
# Some environment setup that attempts to confuse the heck out of cargo-miri.
if [ "$HOST_TARGET" = x86_64-unknown-linux-gnu ]; then
# These act up on Windows (`which miri` produces a filename that does not exist?!?),
# so let's do this only on Linux. Also makes sure things work without these set.
export RUSTC=$(which rustc)
export MIRI=$(which miri)
fi
mkdir -p .cargo
echo 'build.rustc-wrapper = "thisdoesnotexist"' > .cargo/config.toml
# Run the actual test
${PYTHON} test-cargo-miri/run-test.py
echo
# Clean up
unset RUSTC MIRI
rm -rf .cargo
# Ensure that our benchmarks all work, but only on Linux hosts.
if [ -z "${MIRI_TEST_TARGET+exists}" ] && [ "$HOST_TARGET" = x86_64-unknown-linux-gnu ] ; then
for BENCH in $(ls "bench-cargo-miri"); do
cargo miri run --manifest-path bench-cargo-miri/$BENCH/Cargo.toml
done
fi
}
function run_tests_minimal {
if [ -n "${MIRI_TEST_TARGET+exists}" ]; then
echo "Testing MINIMAL foreign architecture $MIRI_TEST_TARGET: only testing $@"
else
echo "Testing MINIMAL host architecture: only testing $@"
fi
./miri test -- "$@"
}
# host
run_tests
case $HOST_TARGET in
x86_64-unknown-linux-gnu)
MIRI_TEST_TARGET=i686-unknown-linux-gnu run_tests
MIRI_TEST_TARGET=aarch64-apple-darwin run_tests
MIRI_TEST_TARGET=i686-pc-windows-msvc run_tests
MIRI_TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal hello integer vec panic/panic concurrency/simple atomic data_race env/var
MIRI_TEST_TARGET=aarch64-linux-android run_tests_minimal hello integer vec panic/panic
MIRI_TEST_TARGET=thumbv7em-none-eabihf MIRI_NO_STD=1 run_tests_minimal no_std # no_std embedded architecture
;;
x86_64-apple-darwin)
MIRI_TEST_TARGET=mips64-unknown-linux-gnuabi64 run_tests # big-endian architecture
MIRI_TEST_TARGET=x86_64-pc-windows-msvc run_tests
;;
i686-pc-windows-msvc)
MIRI_TEST_TARGET=x86_64-unknown-linux-gnu run_tests
;;
*)
echo "FATAL: unknown OS"
exit 1
;;
esac

236
src/tools/miri/miri Executable file
View File

@ -0,0 +1,236 @@
#!/bin/bash
set -e
USAGE=$(cat <<"EOF"
COMMANDS
./miri install <flags>:
Installs the miri driver and cargo-miri. <flags> are passed to `cargo
install`. Sets up the rpath such that the installed binary should work in any
working directory. However, the rustup toolchain when invoking `cargo miri`
needs to be the same one used for `./miri install`.
./miri build <flags>:
Just build miri. <flags> are passed to `cargo build`.
./miri check <flags>:
Just check miri. <flags> are passed to `cargo check`.
./miri test <flags>:
Build miri, set up a sysroot and then run the test suite. <flags> are passed
to the final `cargo test` invocation.
./miri run <flags>:
Build miri, set up a sysroot and then run the driver with the given <flags>.
(Also respects MIRIFLAGS environment variable.)
./miri fmt <flags>:
Format all sources and tests. <flags> are passed to `rustfmt`.
./miri clippy <flags>:
Runs clippy on all sources. <flags> are passed to `cargo clippy`.
./miri cargo <flags>:
Runs just `cargo <flags>` with the Miri-specific environment variables.
Mainly meant to be invoked by rust-analyzer.
./miri many-seeds <command>:
Runs <command> 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 bench <benches>:
Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
<benches> can explicitly list the benchmarks to run; by default, all of them are run.
ENVIRONMENT VARIABLES
MIRI_SYSROOT:
If already set, the "sysroot setup" step is skipped.
CARGO_EXTRA_FLAGS:
Pass extra flags to all cargo invocations. (Ignored by `./miri cargo`.)
EOF
)
## We need to know where we are.
# macOS does not have a useful readlink/realpath so we have to use Python instead...
MIRIDIR=$(python3 -c 'import os, sys; print(os.path.dirname(os.path.realpath(sys.argv[1])))' "$0")
## Run the auto-things.
if [ -z "$MIRI_AUTO_OPS" ]; then
export MIRI_AUTO_OPS=42
# Run this first, so that the toolchain doesn't change after
# other code has run.
if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-toolchain" ] ; then
(cd "$MIRIDIR" && ./rustup-toolchain)
fi
if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-fmt" ] ; then
$0 fmt
fi
if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-clippy" ] ; then
$0 clippy -- -D warnings
fi
fi
## Determine command and toolchain.
COMMAND="$1"
[ $# -gt 0 ] && shift
# Doing this *after* auto-toolchain logic above, since that might change the toolchain.
TOOLCHAIN=$(cd "$MIRIDIR"; rustup show active-toolchain | head -n 1 | cut -d ' ' -f 1)
## Handle some commands early, since they should *not* alter the environment.
case "$COMMAND" in
many-seeds)
for SEED in $({ echo obase=16; seq 0 255; } | bc); do
echo "Trying seed: $SEED"
MIRIFLAGS="$MIRIFLAGS -Zmiri-seed=$SEED" $@ || { echo "Failing seed: $SEED"; break; }
done
exit 0
;;
bench)
# Make sure we have an up-to-date Miri installed
"$0" install
# Run the requested benchmarks
if [ -z "${1+exists}" ]; then
BENCHES=( $(ls "$MIRIDIR/bench-cargo-miri" ) )
else
BENCHES=("$@")
fi
for BENCH in "${BENCHES[@]}"; do
hyperfine -w 1 -m 5 --shell=none "cargo +$TOOLCHAIN miri run --manifest-path $MIRIDIR/bench-cargo-miri/$BENCH/Cargo.toml"
done
exit 0
;;
esac
## Prepare the environment
# Determine some toolchain properties
# export the target so its available in miri
TARGET=$(rustc +$TOOLCHAIN --version --verbose | grep "^host:" | cut -d ' ' -f 2)
SYSROOT=$(rustc +$TOOLCHAIN --print sysroot)
LIBDIR=$SYSROOT/lib/rustlib/$TARGET/lib
if ! test -d "$LIBDIR"; then
echo "Something went wrong determining the library dir."
echo "I got $LIBDIR but that does not exist."
echo "Please report a bug at https://github.com/rust-lang/miri/issues."
exit 2
fi
# Prepare flags for cargo and rustc.
CARGO="cargo +$TOOLCHAIN"
# Share target dir between `miri` and `cargo-miri`.
if [ -z "$CARGO_TARGET_DIR" ]; then
export CARGO_TARGET_DIR="$MIRIDIR/target"
fi
# We configure dev builds to not be unusably slow.
if [ -z "$CARGO_PROFILE_DEV_OPT_LEVEL" ]; then
export CARGO_PROFILE_DEV_OPT_LEVEL=2
fi
# Enable rustc-specific lints (ignored without `-Zunstable-options`).
export RUSTFLAGS="-Zunstable-options -Wrustc::internal $RUSTFLAGS"
# We set the rpath so that Miri finds the private rustc libraries it needs.
export RUSTFLAGS="-C link-args=-Wl,-rpath,$LIBDIR $RUSTFLAGS"
## Helper functions
# Build a sysroot and set MIRI_SYSROOT to use it. Arguments are passed to `cargo miri setup`.
build_sysroot() {
if ! MIRI_SYSROOT="$($CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml -q -- miri setup --print-sysroot "$@")"; then
echo "'cargo miri setup' failed"
exit 1
fi
export MIRI_SYSROOT
}
# Prepare and set MIRI_SYSROOT. Respects `MIRI_TEST_TARGET` and takes into account
# locally built vs. distributed rustc.
find_sysroot() {
if [ -n "$MIRI_SYSROOT" ]; then
# Sysroot already set, use that.
return 0
fi
# We need to build a sysroot.
if [ -n "$MIRI_TEST_TARGET" ]; then
build_sysroot --target "$MIRI_TEST_TARGET"
else
build_sysroot
fi
}
## Main
# Run command.
case "$COMMAND" in
install)
# "--locked" to respect the Cargo.lock file if it exists.
$CARGO install $CARGO_EXTRA_FLAGS --path "$MIRIDIR" --force --locked "$@"
$CARGO install $CARGO_EXTRA_FLAGS --path "$MIRIDIR"/cargo-miri --force --locked "$@"
;;
check)
# Check, and let caller control flags.
$CARGO check $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml --all-targets "$@"
$CARGO check $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
;;
build)
# Build, and let caller control flags.
$CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@"
$CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
;;
test|bless)
# First build and get a sysroot.
$CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml
find_sysroot
if [ "$COMMAND" = "bless" ]; then
export MIRI_BLESS="Gesundheit"
fi
# Then test, and let caller control flags.
# Only in root project as `cargo-miri` has no tests.
$CARGO test $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@"
;;
run)
# Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so
# that we set the MIRI_SYSROOT up the right way.
FOUND_TARGET_OPT=0
for ARG in "$@"; do
if [ "$LAST_ARG" = "--target" ]; then
# Found it!
export MIRI_TEST_TARGET="$ARG"
FOUND_TARGET_OPT=1
break
fi
LAST_ARG="$ARG"
done
if [ "$FOUND_TARGET_OPT" = "0" ] && [ -n "$MIRI_TEST_TARGET" ]; then
# Make sure Miri actually uses this target.
MIRIFLAGS="$MIRIFLAGS --target $MIRI_TEST_TARGET"
fi
# First build and get a sysroot.
$CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml
find_sysroot
# Then run the actual command.
exec $CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml -- $MIRIFLAGS "$@"
;;
fmt)
find "$MIRIDIR" -not \( -name target -prune \) -name '*.rs' \
| xargs rustfmt +$TOOLCHAIN --edition=2021 --config-path "$MIRIDIR/rustfmt.toml" "$@"
;;
clippy)
$CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml --all-targets "$@"
$CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
;;
cargo)
# We carefully kept the working dir intact, so this will run cargo *on the workspace in the
# current working dir*, not on the main Miri workspace. That is exactly what RA needs.
$CARGO "$@"
;;
*)
if [ -n "$COMMAND" ]; then
echo "Unknown command: $COMMAND"
echo
fi
echo "$USAGE"
exit 1
esac

View File

@ -0,0 +1 @@
acb8934fd57b3c2740c4abac0a5728c2c9b1423b

View File

@ -0,0 +1,5 @@
version = "Two"
use_small_heuristics = "Max"
match_arm_blocks = false
match_arm_leading_pipes = "Preserve"
force_multiline_blocks = true

53
src/tools/miri/rustup-toolchain Executable file
View File

@ -0,0 +1,53 @@
#!/bin/bash
set -e
# Manages a rustup toolchain called "miri".
#
# All commands set "miri" as the override toolchain for the current directory,
# and make the `rust-version` file match that toolchain.
#
# USAGE:
#
# ./rustup-toolchain: Update "miri" toolchain to match `rust-version` (the known-good version for this commit).
#
# ./rustup-toolchain HEAD: Update "miri" toolchain and `rust-version` file to latest rustc HEAD.
#
# ./rustup-toolchain $COMMIT: Update "miri" toolchain and `rust-version` file to match that commit.
#
# Any extra parameters are passed to `rustup-toolchain-install-master`.
# Make sure rustup-toolchain-install-master is installed.
if ! which rustup-toolchain-install-master >/dev/null; then
echo "Please install rustup-toolchain-install-master by running 'cargo install rustup-toolchain-install-master'"
exit 1
fi
# Determine new commit.
if [[ "$1" == "" ]]; then
NEW_COMMIT=$(cat rust-version)
elif [[ "$1" == "HEAD" ]]; then
NEW_COMMIT=$(git ls-remote https://github.com/rust-lang/rust/ HEAD | cut -f 1)
else
NEW_COMMIT="$1"
fi
echo "$NEW_COMMIT" > rust-version
shift || true # don't fail if shifting fails
# Check if we already are at that commit.
CUR_COMMIT=$(rustc +miri --version -v 2>/dev/null | grep "^commit-hash: " | cut -d " " -f 2)
if [[ "$CUR_COMMIT" == "$NEW_COMMIT" ]]; then
echo "miri toolchain is already at commit $CUR_COMMIT."
rustup override set miri
exit 0
fi
# Install and setup new toolchain.
rustup toolchain uninstall miri
rustup-toolchain-install-master -n miri -c cargo -c rust-src -c rustc-dev -c llvm-tools -c rustfmt -c clippy "$@" -- "$NEW_COMMIT"
rustup override set miri
# Cleanup.
cargo clean
# Call 'cargo metadata' on the sources in case that changes the lockfile
# (which fails under some setups when it is done from inside vscode).
cargo metadata --format-version 1 --manifest-path "$(rustc --print sysroot)/lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml" >/dev/null

View File

@ -0,0 +1,562 @@
#![feature(rustc_private, stmt_expr_attributes)]
#![allow(
clippy::manual_range_contains,
clippy::useless_format,
clippy::field_reassign_with_default
)]
extern crate rustc_data_structures;
extern crate rustc_driver;
extern crate rustc_hir;
extern crate rustc_interface;
extern crate rustc_metadata;
extern crate rustc_middle;
extern crate rustc_session;
use std::env;
use std::num::NonZeroU64;
use std::path::PathBuf;
use std::str::FromStr;
use log::debug;
use rustc_data_structures::sync::Lrc;
use rustc_driver::Compilation;
use rustc_hir::{self as hir, def_id::LOCAL_CRATE, Node};
use rustc_interface::interface::Config;
use rustc_middle::{
middle::exported_symbols::{
ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel,
},
ty::{query::ExternProviders, TyCtxt},
};
use rustc_session::{config::CrateType, search_paths::PathKind, CtfeBacktrace};
use miri::{BacktraceStyle, ProvenanceMode};
struct MiriCompilerCalls {
miri_config: miri::MiriConfig,
}
impl rustc_driver::Callbacks for MiriCompilerCalls {
fn config(&mut self, config: &mut Config) {
config.override_queries = Some(|_, _, external_providers| {
external_providers.used_crate_source = |tcx, cnum| {
let mut providers = ExternProviders::default();
rustc_metadata::provide_extern(&mut providers);
let mut crate_source = (providers.used_crate_source)(tcx, cnum);
// HACK: rustc will emit "crate ... required to be available in rlib format, but
// was not found in this form" errors once we use `tcx.dependency_formats()` if
// there's no rlib provided, so setting a dummy path here to workaround those errors.
Lrc::make_mut(&mut crate_source).rlib = Some((PathBuf::new(), PathKind::All));
crate_source
};
});
}
fn after_analysis<'tcx>(
&mut self,
compiler: &rustc_interface::interface::Compiler,
queries: &'tcx rustc_interface::Queries<'tcx>,
) -> Compilation {
compiler.session().abort_if_errors();
queries.global_ctxt().unwrap().peek_mut().enter(|tcx| {
init_late_loggers(tcx);
if !tcx.sess.crate_types().contains(&CrateType::Executable) {
tcx.sess.fatal("miri only makes sense on bin crates");
}
let (entry_def_id, entry_type) = if let Some(entry_def) = tcx.entry_fn(()) {
entry_def
} else {
tcx.sess.fatal("miri can only run programs that have a main function");
};
let mut config = self.miri_config.clone();
// Add filename to `miri` arguments.
config.args.insert(0, compiler.input().filestem().to_string());
// Adjust working directory for interpretation.
if let Some(cwd) = env::var_os("MIRI_CWD") {
env::set_current_dir(cwd).unwrap();
}
if let Some(return_code) = miri::eval_entry(tcx, entry_def_id, entry_type, config) {
std::process::exit(
i32::try_from(return_code).expect("Return value was too large!"),
);
}
});
compiler.session().abort_if_errors();
Compilation::Stop
}
}
struct MiriBeRustCompilerCalls {
target_crate: bool,
}
impl rustc_driver::Callbacks for MiriBeRustCompilerCalls {
#[allow(rustc::potential_query_instability)] // rustc_codegen_ssa (where this code is copied from) also allows this lint
fn config(&mut self, config: &mut Config) {
if config.opts.prints.is_empty() && self.target_crate {
// Queries overriden here affect the data stored in `rmeta` files of dependencies,
// which will be used later in non-`MIRI_BE_RUSTC` mode.
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.
local_providers.exported_symbols = |tcx, cnum| {
assert_eq!(cnum, LOCAL_CRATE);
tcx.arena.alloc_from_iter(
// This is based on:
// https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L62-L63
// https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L174
tcx.reachable_set(()).iter().filter_map(|&local_def_id| {
// Do the same filtering that rustc does:
// https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L84-L102
// Otherwise it may cause unexpected behaviours and ICEs
// (https://github.com/rust-lang/rust/issues/86261).
let is_reachable_non_generic = matches!(
tcx.hir().get(tcx.hir().local_def_id_to_hir_id(local_def_id)),
Node::Item(&hir::Item {
kind: hir::ItemKind::Static(..) | hir::ItemKind::Fn(..),
..
}) | Node::ImplItem(&hir::ImplItem {
kind: hir::ImplItemKind::Fn(..),
..
})
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,
},
))
}),
)
}
});
}
}
}
fn show_error(msg: &impl std::fmt::Display) -> ! {
eprintln!("fatal error: {msg}");
std::process::exit(1)
}
macro_rules! show_error {
($($tt:tt)*) => { show_error(&format_args!($($tt)*)) };
}
fn init_early_loggers() {
// Note that our `extern crate log` is *not* the same as rustc's; as a result, we have to
// initialize them both, and we always initialize `miri`'s first.
let env = env_logger::Env::new().filter("MIRI_LOG").write_style("MIRI_LOG_STYLE");
env_logger::init_from_env(env);
// Enable verbose entry/exit logging by default if MIRI_LOG is set.
if env::var_os("MIRI_LOG").is_some() && env::var_os("RUSTC_LOG_ENTRY_EXIT").is_none() {
env::set_var("RUSTC_LOG_ENTRY_EXIT", "1");
}
// We only initialize `rustc` if the env var is set (so the user asked for it).
// If it is not set, we avoid initializing now so that we can initialize
// later with our custom settings, and *not* log anything for what happens before
// `miri` gets started.
if env::var_os("RUSTC_LOG").is_some() {
rustc_driver::init_rustc_env_logger();
}
}
fn init_late_loggers(tcx: TyCtxt<'_>) {
// We initialize loggers right before we start evaluation. We overwrite the `RUSTC_LOG`
// env var if it is not set, control it based on `MIRI_LOG`.
// (FIXME: use `var_os`, but then we need to manually concatenate instead of `format!`.)
if let Ok(var) = env::var("MIRI_LOG") {
if env::var_os("RUSTC_LOG").is_none() {
// We try to be a bit clever here: if `MIRI_LOG` is just a single level
// used for everything, we only apply it to the parts of rustc that are
// CTFE-related. Otherwise, we use it verbatim for `RUSTC_LOG`.
// This way, if you set `MIRI_LOG=trace`, you get only the right parts of
// rustc traced, but you can also do `MIRI_LOG=miri=trace,rustc_const_eval::interpret=debug`.
if log::Level::from_str(&var).is_ok() {
env::set_var(
"RUSTC_LOG",
&format!(
"rustc_middle::mir::interpret={0},rustc_const_eval::interpret={0}",
var
),
);
} else {
env::set_var("RUSTC_LOG", &var);
}
rustc_driver::init_rustc_env_logger();
}
}
// If `MIRI_BACKTRACE` is set and `RUSTC_CTFE_BACKTRACE` is not, set `RUSTC_CTFE_BACKTRACE`.
// Do this late, so we ideally only apply this to Miri's errors.
if let Some(val) = env::var_os("MIRI_BACKTRACE") {
let ctfe_backtrace = match &*val.to_string_lossy() {
"immediate" => CtfeBacktrace::Immediate,
"0" => CtfeBacktrace::Disabled,
_ => CtfeBacktrace::Capture,
};
*tcx.sess.ctfe_backtrace.borrow_mut() = ctfe_backtrace;
}
}
/// Returns the "default sysroot" that Miri will use for host things if no `--sysroot` flag is set.
/// Should be a compile-time constant.
fn host_sysroot() -> Option<String> {
if option_env!("RUSTC_STAGE").is_some() {
// This is being built as part of rustc, and gets shipped with rustup.
// We can rely on the sysroot computation in librustc_session.
return None;
}
// For builds outside rustc, we need to ensure that we got a sysroot
// that gets used as a default. The sysroot computation in librustc_session would
// end up somewhere in the build dir (see `get_or_default_sysroot`).
// Taken from PR <https://github.com/Manishearth/rust-clippy/pull/911>.
let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
Some(match (home, toolchain) {
(Some(home), Some(toolchain)) => {
// Check that at runtime, we are still in this toolchain (if there is any toolchain).
if let Some(toolchain_runtime) =
env::var_os("RUSTUP_TOOLCHAIN").or_else(|| env::var_os("MULTIRUST_TOOLCHAIN"))
{
if toolchain_runtime != toolchain {
show_error!(
"This Miri got built with local toolchain `{toolchain}`, but now is being run under a different toolchain. \n\
Make sure to run Miri in the toolchain it got built with, e.g. via `cargo +{toolchain} miri`."
)
}
}
format!("{}/toolchains/{}", home, toolchain)
}
_ => option_env!("RUST_SYSROOT")
.unwrap_or_else(|| {
show_error!(
"To build Miri without rustup, set the `RUST_SYSROOT` env var at build time",
)
})
.to_owned(),
})
}
/// Execute a compiler with the given CLI arguments and callbacks.
fn run_compiler(
mut args: Vec<String>,
target_crate: bool,
callbacks: &mut (dyn rustc_driver::Callbacks + Send),
) -> ! {
// Make sure we use the right default sysroot. The default sysroot is wrong,
// because `get_or_default_sysroot` in `librustc_session` bases that on `current_exe`.
//
// Make sure we always call `host_sysroot` as that also does some sanity-checks
// of the environment we were built in and whether it matches what we are running in.
let host_default_sysroot = host_sysroot();
// Now see if we even need to set something.
let sysroot_flag = "--sysroot";
if !args.iter().any(|e| e == sysroot_flag) {
// No sysroot was set, let's see if we have a custom default we want to configure.
let default_sysroot = if target_crate {
// Using the built-in default here would be plain wrong, so we *require*
// the env var to make sure things make sense.
Some(env::var("MIRI_SYSROOT").unwrap_or_else(|_| {
show_error!(
"Miri was invoked in 'target' mode without `MIRI_SYSROOT` or `--sysroot` being set"
)
}))
} else {
host_default_sysroot
};
if let Some(sysroot) = default_sysroot {
// We need to overwrite the default that librustc_session would compute.
args.push(sysroot_flag.to_owned());
args.push(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`:
// https://github.com/rust-lang/miri/issues/1760
if target_crate {
// Some options have different defaults in Miri than in plain rustc; apply those by making
// them the first arguments after the binary name (but later arguments can overwrite them).
args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string));
}
// Invoke compiler, and handle return code.
let exit_code = rustc_driver::catch_with_exit_code(move || {
rustc_driver::RunCompiler::new(&args, callbacks).run()
});
std::process::exit(exit_code)
}
/// Parses a comma separated list of `T` from the given string:
///
/// `<value1>,<value2>,<value3>,...`
fn parse_comma_list<T: FromStr>(input: &str) -> Result<Vec<T>, T::Err> {
input.split(',').map(str::parse::<T>).collect()
}
fn main() {
// Snapshot a copy of the environment before `rustc` starts messing with it.
// (`install_ice_hook` might change `RUST_BACKTRACE`.)
let env_snapshot = env::vars_os().collect::<Vec<_>>();
// Earliest rustc setup.
rustc_driver::install_ice_hook();
// If the environment asks us to actually be rustc, then do that.
if let Some(crate_kind) = env::var_os("MIRI_BE_RUSTC") {
rustc_driver::init_rustc_env_logger();
let target_crate = if crate_kind == "target" {
true
} else if crate_kind == "host" {
false
} else {
panic!("invalid `MIRI_BE_RUSTC` value: {:?}", crate_kind)
};
// We cannot use `rustc_driver::main` as we need to adjust the CLI arguments.
run_compiler(
env::args().collect(),
target_crate,
&mut MiriBeRustCompilerCalls { target_crate },
)
}
// Init loggers the Miri way.
init_early_loggers();
// Parse our arguments and split them across `rustc` and `miri`.
let mut miri_config = miri::MiriConfig::default();
miri_config.env = env_snapshot;
let mut rustc_args = vec![];
let mut after_dashdash = false;
// If user has explicitly enabled/disabled isolation
let mut isolation_enabled: Option<bool> = None;
for arg in env::args() {
if rustc_args.is_empty() {
// Very first arg: binary name.
rustc_args.push(arg);
} else if after_dashdash {
// Everything that comes after `--` is forwarded to the interpreted crate.
miri_config.args.push(arg);
} else if arg == "--" {
after_dashdash = true;
} else if arg == "-Zmiri-disable-validation" {
miri_config.validate = false;
} else if arg == "-Zmiri-disable-stacked-borrows" {
miri_config.stacked_borrows = false;
} else if arg == "-Zmiri-disable-data-race-detector" {
miri_config.data_race_detector = false;
miri_config.weak_memory_emulation = false;
} else if arg == "-Zmiri-disable-alignment-check" {
miri_config.check_alignment = miri::AlignmentCheck::None;
} else if arg == "-Zmiri-symbolic-alignment-check" {
miri_config.check_alignment = miri::AlignmentCheck::Symbolic;
} else if arg == "-Zmiri-check-number-validity" {
eprintln!(
"WARNING: the flag `-Zmiri-check-number-validity` no longer has any effect \
since it is now enabled by default"
);
} else if arg == "-Zmiri-disable-abi-check" {
miri_config.check_abi = false;
} else if arg == "-Zmiri-disable-isolation" {
if matches!(isolation_enabled, Some(true)) {
show_error!(
"-Zmiri-disable-isolation cannot be used along with -Zmiri-isolation-error"
);
} else {
isolation_enabled = Some(false);
}
miri_config.isolated_op = miri::IsolatedOp::Allow;
} else if arg == "-Zmiri-disable-weak-memory-emulation" {
miri_config.weak_memory_emulation = false;
} else if arg == "-Zmiri-track-weak-memory-loads" {
miri_config.track_outdated_loads = true;
} else if let Some(param) = arg.strip_prefix("-Zmiri-isolation-error=") {
if matches!(isolation_enabled, Some(false)) {
show_error!(
"-Zmiri-isolation-error cannot be used along with -Zmiri-disable-isolation"
);
} else {
isolation_enabled = Some(true);
}
miri_config.isolated_op = match param {
"abort" => miri::IsolatedOp::Reject(miri::RejectOpWith::Abort),
"hide" => miri::IsolatedOp::Reject(miri::RejectOpWith::NoWarning),
"warn" => miri::IsolatedOp::Reject(miri::RejectOpWith::Warning),
"warn-nobacktrace" =>
miri::IsolatedOp::Reject(miri::RejectOpWith::WarningWithoutBacktrace),
_ =>
show_error!(
"-Zmiri-isolation-error must be `abort`, `hide`, `warn`, or `warn-nobacktrace`"
),
};
} else if arg == "-Zmiri-ignore-leaks" {
miri_config.ignore_leaks = true;
} else if arg == "-Zmiri-panic-on-unsupported" {
miri_config.panic_on_unsupported = true;
} else if arg == "-Zmiri-tag-raw-pointers" {
eprintln!("WARNING: `-Zmiri-tag-raw-pointers` has no effect; it is enabled by default");
} else if arg == "-Zmiri-strict-provenance" {
miri_config.provenance_mode = ProvenanceMode::Strict;
} else if arg == "-Zmiri-permissive-provenance" {
miri_config.provenance_mode = ProvenanceMode::Permissive;
} else if arg == "-Zmiri-mute-stdout-stderr" {
miri_config.mute_stdout_stderr = true;
} else if arg == "-Zmiri-retag-fields" {
miri_config.retag_fields = true;
} else if arg == "-Zmiri-track-raw-pointers" {
eprintln!(
"WARNING: `-Zmiri-track-raw-pointers` has no effect; it is enabled by default"
);
} else if let Some(param) = arg.strip_prefix("-Zmiri-seed=") {
if miri_config.seed.is_some() {
show_error!("Cannot specify -Zmiri-seed multiple times!");
}
let seed = u64::from_str_radix(param, 16)
.unwrap_or_else(|_| show_error!(
"-Zmiri-seed should only contain valid hex digits [0-9a-fA-F] and must fit into a u64 (max 16 characters)"
));
miri_config.seed = Some(seed);
} else if let Some(_param) = arg.strip_prefix("-Zmiri-env-exclude=") {
show_error!(
"`-Zmiri-env-exclude` has been removed; unset env vars before starting Miri instead"
);
} 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<u64> = 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
),
};
for id in ids.into_iter().map(miri::SbTag::new) {
if let Some(id) = id {
miri_config.tracked_pointer_tags.insert(id);
} else {
show_error!("-Zmiri-track-pointer-tag requires nonzero arguments");
}
}
} else if let Some(param) = arg.strip_prefix("-Zmiri-track-call-id=") {
let ids: Vec<u64> = 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
),
};
for id in ids.into_iter().map(miri::CallId::new) {
if let Some(id) = id {
miri_config.tracked_call_ids.insert(id);
} else {
show_error!("-Zmiri-track-call-id requires a nonzero argument");
}
}
} else if let Some(param) = arg.strip_prefix("-Zmiri-track-alloc-id=") {
let ids: Vec<miri::AllocId> = match parse_comma_list::<NonZeroU64>(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);
} else if let Some(param) = arg.strip_prefix("-Zmiri-compare-exchange-weak-failure-rate=") {
let rate = match param.parse::<f64>() {
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;
} else if let Some(param) = arg.strip_prefix("-Zmiri-preemption-rate=") {
let rate = match param.parse::<f64>() {
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;
} 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::<u32>() {
Ok(i) => i,
Err(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-tag-gc=") {
let interval = match param.parse::<u32>() {
Ok(i) => i,
Err(err) => show_error!("-Zmiri-tag-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());
} else if let Some(param) = arg.strip_prefix("-Zmiri-backtrace=") {
miri_config.backtrace_style = match param {
"0" => BacktraceStyle::Off,
"1" => BacktraceStyle::Short,
"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=") {
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()
);
}
miri_config.external_so_file = Some(filename.into());
} else {
show_error!("-Zmiri-extern-so-file `{}` does not exist", filename);
}
} else {
// Forward to rustc.
rustc_args.push(arg);
}
}
debug!("rustc arguments: {:?}", rustc_args);
debug!("crate arguments: {:?}", miri_config.args);
run_compiler(rustc_args, /* target_crate: */ true, &mut MiriCompilerCalls { miri_config })
}

115
src/tools/miri/src/clock.rs Normal file
View File

@ -0,0 +1,115 @@
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant as StdInstant};
/// When using a virtual clock, this defines how many nanoseconds we pretend are passing for each
/// basic block.
const NANOSECONDS_PER_BASIC_BLOCK: u64 = 10;
#[derive(Debug)]
pub struct Instant {
kind: InstantKind,
}
#[derive(Debug)]
enum InstantKind {
Host(StdInstant),
Virtual { nanoseconds: u64 },
}
impl Instant {
pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
match self.kind {
InstantKind::Host(instant) =>
instant.checked_add(duration).map(|i| Instant { kind: InstantKind::Host(i) }),
InstantKind::Virtual { nanoseconds } =>
u128::from(nanoseconds)
.checked_add(duration.as_nanos())
.and_then(|n| u64::try_from(n).ok())
.map(|nanoseconds| Instant { kind: InstantKind::Virtual { nanoseconds } }),
}
}
pub fn duration_since(&self, earlier: Instant) -> Duration {
match (&self.kind, earlier.kind) {
(InstantKind::Host(instant), InstantKind::Host(earlier)) =>
instant.duration_since(earlier),
(
InstantKind::Virtual { nanoseconds },
InstantKind::Virtual { nanoseconds: earlier },
) => Duration::from_nanos(nanoseconds.saturating_sub(earlier)),
_ => panic!("all `Instant` must be of the same kind"),
}
}
}
/// A monotone clock used for `Instant` simulation.
#[derive(Debug)]
pub struct Clock {
kind: ClockKind,
}
#[derive(Debug)]
enum ClockKind {
Host {
/// The "time anchor" for this machine's monotone clock.
time_anchor: StdInstant,
},
Virtual {
/// The "current virtual time".
nanoseconds: AtomicU64,
},
}
impl Clock {
/// Create a new clock based on the availability of communication with the host.
pub fn new(communicate: bool) -> Self {
let kind = if communicate {
ClockKind::Host { time_anchor: StdInstant::now() }
} else {
ClockKind::Virtual { nanoseconds: 0.into() }
};
Self { kind }
}
/// Let the time pass for a small interval.
pub fn tick(&self) {
match &self.kind {
ClockKind::Host { .. } => {
// Time will pass without us doing anything.
}
ClockKind::Virtual { nanoseconds } => {
nanoseconds.fetch_add(NANOSECONDS_PER_BASIC_BLOCK, Ordering::SeqCst);
}
}
}
/// Sleep for the desired duration.
pub fn sleep(&self, duration: Duration) {
match &self.kind {
ClockKind::Host { .. } => std::thread::sleep(duration),
ClockKind::Virtual { nanoseconds } => {
// Just pretend that we have slept for some time.
nanoseconds.fetch_add(duration.as_nanos().try_into().unwrap(), Ordering::SeqCst);
}
}
}
/// Return the `anchor` instant, to convert between monotone instants and durations relative to the anchor.
pub fn anchor(&self) -> Instant {
match &self.kind {
ClockKind::Host { time_anchor } => Instant { kind: InstantKind::Host(*time_anchor) },
ClockKind::Virtual { .. } => Instant { kind: InstantKind::Virtual { nanoseconds: 0 } },
}
}
pub fn now(&self) -> Instant {
match &self.kind {
ClockKind::Host { .. } => Instant { kind: InstantKind::Host(StdInstant::now()) },
ClockKind::Virtual { nanoseconds } =>
Instant {
kind: InstantKind::Virtual { nanoseconds: nanoseconds.load(Ordering::SeqCst) },
},
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
pub mod data_race;
mod range_object_map;
pub mod sync;
pub mod thread;
mod vector_clock;
pub mod weak_memory;

View File

@ -0,0 +1,277 @@
//! Implements a map from allocation ranges to data. This is somewhat similar to RangeMap, but the
//! ranges and data are discrete and non-splittable -- they represent distinct "objects". An
//! allocation in the map will always have the same range until explicitly removed
use rustc_target::abi::Size;
use std::ops::{Index, IndexMut, Range};
use rustc_const_eval::interpret::AllocRange;
#[derive(Clone, Debug)]
struct Elem<T> {
/// The range covered by this element; never empty.
range: AllocRange,
/// The data stored for this element.
data: T,
}
/// Index of an allocation within the map
type Position = usize;
#[derive(Clone, Debug)]
pub struct RangeObjectMap<T> {
v: Vec<Elem<T>>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum AccessType {
/// The access perfectly overlaps (same offset and range) with the exsiting allocation
PerfectlyOverlapping(Position),
/// The access does not touch any exising allocation
Empty(Position),
/// The access overlaps with one or more existing allocations
ImperfectlyOverlapping(Range<Position>),
}
impl<T> RangeObjectMap<T> {
pub fn new() -> Self {
Self { v: Vec::new() }
}
/// Finds the position of the allocation containing the given offset. If the offset is not
/// in an existing allocation, then returns Err containing the position
/// where such allocation should be inserted
fn find_offset(&self, offset: Size) -> Result<Position, Position> {
// We do a binary search.
let mut left = 0usize; // inclusive
let mut right = self.v.len(); // exclusive
loop {
if left == right {
// No element contains the given offset. But the
// position is where such element should be placed at.
return Err(left);
}
let candidate = left.checked_add(right).unwrap() / 2;
let elem = &self.v[candidate];
if offset < elem.range.start {
// We are too far right (offset is further left).
debug_assert!(candidate < right); // we are making progress
right = candidate;
} else if offset >= elem.range.end() {
// We are too far left (offset is further right).
debug_assert!(candidate >= left); // we are making progress
left = candidate + 1;
} else {
// This is it!
return Ok(candidate);
}
}
}
/// Determines whether a given access on `range` overlaps with
/// an existing allocation
pub fn access_type(&self, range: AllocRange) -> AccessType {
match self.find_offset(range.start) {
Ok(pos) => {
// Start of the range belongs to an existing object, now let's check the overlapping situation
let elem = &self.v[pos];
// FIXME: derive Eq for AllocRange in rustc
if elem.range.start == range.start && elem.range.size == range.size {
// Happy case: perfectly overlapping access
AccessType::PerfectlyOverlapping(pos)
} else {
// FIXME: add a last() method to AllocRange that returns the last inclusive offset (end() is exclusive)
let end_pos = match self.find_offset(range.end() - Size::from_bytes(1)) {
// If the end lands in an existing object, add one to get the exclusive position
Ok(inclusive_pos) => inclusive_pos + 1,
Err(exclusive_pos) => exclusive_pos,
};
AccessType::ImperfectlyOverlapping(pos..end_pos)
}
}
Err(pos) => {
// Start of the range doesn't belong to an existing object
match self.find_offset(range.end() - Size::from_bytes(1)) {
// Neither does the end
Err(end_pos) =>
if pos == end_pos {
// There's nothing between the start and the end, so the range thing is empty
AccessType::Empty(pos)
} else {
// Otherwise we have entirely covered an existing object
AccessType::ImperfectlyOverlapping(pos..end_pos)
},
// Otherwise at least part of it overlaps with something else
Ok(end_pos) => AccessType::ImperfectlyOverlapping(pos..end_pos + 1),
}
}
}
}
/// Inserts an object and its occupied range at given position
// The Position can be calculated from AllocRange, but the only user of AllocationMap
// always calls access_type before calling insert/index/index_mut, and we don't
// want to repeat the binary search on each time, so we ask the caller to supply Position
pub fn insert_at_pos(&mut self, pos: Position, range: AllocRange, data: T) {
self.v.insert(pos, Elem { range, data });
// If we aren't the first element, then our start must be greater than the preivous element's end
if pos > 0 {
assert!(self.v[pos - 1].range.end() <= range.start);
}
// If we aren't the last element, then our end must be smaller than next element's start
if pos < self.v.len() - 1 {
assert!(range.end() <= self.v[pos + 1].range.start);
}
}
pub fn remove_pos_range(&mut self, pos_range: Range<Position>) {
self.v.drain(pos_range);
}
pub fn remove_from_pos(&mut self, pos: Position) {
self.v.remove(pos);
}
}
impl<T> Index<Position> for RangeObjectMap<T> {
type Output = T;
fn index(&self, pos: Position) -> &Self::Output {
&self.v[pos].data
}
}
impl<T> IndexMut<Position> for RangeObjectMap<T> {
fn index_mut(&mut self, pos: Position) -> &mut Self::Output {
&mut self.v[pos].data
}
}
#[cfg(test)]
mod tests {
use rustc_const_eval::interpret::alloc_range;
use super::*;
#[test]
fn empty_map() {
// FIXME: make Size::from_bytes const
let four = Size::from_bytes(4);
let map = RangeObjectMap::<()>::new();
// Correctly tells where we should insert the first element (at position 0)
assert_eq!(map.find_offset(Size::from_bytes(3)), Err(0));
// Correctly tells the access type along with the supposed position
assert_eq!(map.access_type(alloc_range(Size::ZERO, four)), AccessType::Empty(0));
}
#[test]
#[should_panic]
fn no_overlapping_inserts() {
let four = Size::from_bytes(4);
let mut map = RangeObjectMap::<&str>::new();
// |_|_|_|_|#|#|#|#|_|_|_|_|...
// 0 1 2 3 4 5 6 7 8 9 a b c d
map.insert_at_pos(0, alloc_range(four, four), "#");
// |_|_|_|_|#|#|#|#|_|_|_|_|...
// 0 ^ ^ ^ ^ 5 6 7 8 9 a b c d
map.insert_at_pos(0, alloc_range(Size::from_bytes(1), four), "@");
}
#[test]
fn boundaries() {
let four = Size::from_bytes(4);
let mut map = RangeObjectMap::<&str>::new();
// |#|#|#|#|_|_|...
// 0 1 2 3 4 5
map.insert_at_pos(0, alloc_range(Size::ZERO, four), "#");
// |#|#|#|#|_|_|...
// 0 1 2 3 ^ 5
assert_eq!(map.find_offset(four), Err(1));
// |#|#|#|#|_|_|_|_|_|...
// 0 1 2 3 ^ ^ ^ ^ 8
assert_eq!(map.access_type(alloc_range(four, four)), AccessType::Empty(1));
let eight = Size::from_bytes(8);
// |#|#|#|#|_|_|_|_|@|@|@|@|_|_|...
// 0 1 2 3 4 5 6 7 8 9 a b c d
map.insert_at_pos(1, alloc_range(eight, four), "@");
// |#|#|#|#|_|_|_|_|@|@|@|@|_|_|...
// 0 1 2 3 4 5 6 ^ 8 9 a b c d
assert_eq!(map.find_offset(Size::from_bytes(7)), Err(1));
// |#|#|#|#|_|_|_|_|@|@|@|@|_|_|...
// 0 1 2 3 ^ ^ ^ ^ 8 9 a b c d
assert_eq!(map.access_type(alloc_range(four, four)), AccessType::Empty(1));
}
#[test]
fn perfectly_overlapping() {
let four = Size::from_bytes(4);
let mut map = RangeObjectMap::<&str>::new();
// |#|#|#|#|_|_|...
// 0 1 2 3 4 5
map.insert_at_pos(0, alloc_range(Size::ZERO, four), "#");
// |#|#|#|#|_|_|...
// ^ ^ ^ ^ 4 5
assert_eq!(map.find_offset(Size::ZERO), Ok(0));
assert_eq!(
map.access_type(alloc_range(Size::ZERO, four)),
AccessType::PerfectlyOverlapping(0)
);
// |#|#|#|#|@|@|@|@|_|...
// 0 1 2 3 4 5 6 7 8
map.insert_at_pos(1, alloc_range(four, four), "@");
// |#|#|#|#|@|@|@|@|_|...
// 0 1 2 3 ^ ^ ^ ^ 8
assert_eq!(map.find_offset(four), Ok(1));
assert_eq!(map.access_type(alloc_range(four, four)), AccessType::PerfectlyOverlapping(1));
}
#[test]
fn straddling() {
let four = Size::from_bytes(4);
let mut map = RangeObjectMap::<&str>::new();
// |_|_|_|_|#|#|#|#|_|_|_|_|...
// 0 1 2 3 4 5 6 7 8 9 a b c d
map.insert_at_pos(0, alloc_range(four, four), "#");
// |_|_|_|_|#|#|#|#|_|_|_|_|...
// 0 1 ^ ^ ^ ^ 6 7 8 9 a b c d
assert_eq!(
map.access_type(alloc_range(Size::from_bytes(2), four)),
AccessType::ImperfectlyOverlapping(0..1)
);
// |_|_|_|_|#|#|#|#|_|_|_|_|...
// 0 1 2 3 4 5 ^ ^ ^ ^ a b c d
assert_eq!(
map.access_type(alloc_range(Size::from_bytes(6), four)),
AccessType::ImperfectlyOverlapping(0..1)
);
// |_|_|_|_|#|#|#|#|_|_|_|_|...
// 0 1 ^ ^ ^ ^ ^ ^ ^ ^ a b c d
assert_eq!(
map.access_type(alloc_range(Size::from_bytes(2), Size::from_bytes(8))),
AccessType::ImperfectlyOverlapping(0..1)
);
// |_|_|_|_|#|#|#|#|_|_|@|@|_|_|...
// 0 1 2 3 4 5 6 7 8 9 a b c d
map.insert_at_pos(1, alloc_range(Size::from_bytes(10), Size::from_bytes(2)), "@");
// |_|_|_|_|#|#|#|#|_|_|@|@|_|_|...
// 0 1 2 3 4 5 ^ ^ ^ ^ ^ ^ ^ ^
assert_eq!(
map.access_type(alloc_range(Size::from_bytes(6), Size::from_bytes(8))),
AccessType::ImperfectlyOverlapping(0..2)
);
}
}

View File

@ -0,0 +1,584 @@
use std::collections::{hash_map::Entry, VecDeque};
use std::num::NonZeroU32;
use std::ops::Not;
use log::trace;
use rustc_data_structures::fx::FxHashMap;
use rustc_index::vec::{Idx, IndexVec};
use super::vector_clock::VClock;
use crate::*;
/// We cannot use the `newtype_index!` macro because we have to use 0 as a
/// sentinel value meaning that the identifier is not assigned. This is because
/// the pthreads static initializers initialize memory with zeros (see the
/// `src/shims/sync.rs` file).
macro_rules! declare_id {
($name: ident) => {
/// 0 is used to indicate that the id was not yet assigned and,
/// therefore, is not a valid identifier.
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct $name(NonZeroU32);
impl $name {
// Panics if `id == 0`.
pub fn from_u32(id: u32) -> Self {
Self(NonZeroU32::new(id).unwrap())
}
}
impl Idx for $name {
fn new(idx: usize) -> Self {
// We use 0 as a sentinel value (see the comment above) and,
// therefore, need to shift by one when converting from an index
// into a vector.
let shifted_idx = u32::try_from(idx).unwrap().checked_add(1).unwrap();
$name(NonZeroU32::new(shifted_idx).unwrap())
}
fn index(self) -> usize {
// See the comment in `Self::new`.
// (This cannot underflow because self is NonZeroU32.)
usize::try_from(self.0.get() - 1).unwrap()
}
}
impl $name {
pub fn to_u32_scalar(&self) -> Scalar<Provenance> {
Scalar::from_u32(self.0.get())
}
}
};
}
declare_id!(MutexId);
/// The mutex state.
#[derive(Default, Debug)]
struct Mutex {
/// The thread that currently owns the lock.
owner: Option<ThreadId>,
/// How many times the mutex was locked by the owner.
lock_count: usize,
/// The queue of threads waiting for this mutex.
queue: VecDeque<ThreadId>,
/// 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,
}
declare_id!(RwLockId);
/// The read-write lock state.
#[derive(Default, Debug)]
struct RwLock {
/// The writer thread that currently owns the lock.
writer: Option<ThreadId>,
/// The readers that currently own the lock and how many times they acquired
/// the lock.
readers: FxHashMap<ThreadId, usize>,
/// The queue of writer threads waiting for this lock.
writer_queue: VecDeque<ThreadId>,
/// The queue of reader threads waiting for this lock.
reader_queue: VecDeque<ThreadId>,
/// Data race handle 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
/// the current writer.
/// 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
/// 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
/// readers are finished.
/// Has to be stored separately since reader lock acquires
/// must load the clock of the last write and must not
/// add happens-before orderings between shared reader
/// locks.
data_race_reader: VClock,
}
declare_id!(CondvarId);
/// A thread waiting on a conditional variable.
#[derive(Debug)]
struct CondvarWaiter {
/// The thread that is waiting on this variable.
thread: ThreadId,
/// The mutex on which the thread is waiting.
mutex: MutexId,
}
/// The conditional variable state.
#[derive(Default, Debug)]
struct Condvar {
waiters: VecDeque<CondvarWaiter>,
/// Tracks the happens-before relationship
/// between a cond-var signal and a cond-var
/// wait during a non-suprious signal event.
/// Contains the clock of the last thread to
/// perform a futex-signal.
data_race: VClock,
}
/// The futex state.
#[derive(Default, Debug)]
struct Futex {
waiters: VecDeque<FutexWaiter>,
/// Tracks the happens-before relationship
/// between a futex-wake and a futex-wait
/// during a non-spurious wake event.
/// Contains the clock of the last thread to
/// perform a futex-wake.
data_race: VClock,
}
/// A thread waiting on a futex.
#[derive(Debug)]
struct FutexWaiter {
/// The thread that is waiting on this futex.
thread: ThreadId,
/// The bitset used by FUTEX_*_BITSET, or u32::MAX for other operations.
bitset: u32,
}
/// The state of all synchronization variables.
#[derive(Default, Debug)]
pub(crate) struct SynchronizationState {
mutexes: IndexVec<MutexId, Mutex>,
rwlocks: IndexVec<RwLockId, RwLock>,
condvars: IndexVec<CondvarId, Condvar>,
futexes: FxHashMap<u64, Futex>,
}
// Private extension trait for local helper methods
impl<'mir, 'tcx: 'mir> EvalContextExtPriv<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Take a reader out of the queue waiting for the lock.
/// Returns `true` if some thread got the rwlock.
#[inline]
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.rwlock_reader_lock(id, reader);
true
} else {
false
}
}
/// Take the writer out of the queue waiting for the lock.
/// Returns `true` if some thread got the rwlock.
#[inline]
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.rwlock_writer_lock(id, writer);
true
} else {
false
}
}
/// Take a thread out of the queue waiting for the mutex, and lock
/// the mutex for it. Returns `true` if some thread has the mutex now.
#[inline]
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.mutex_lock(id, thread);
true
} else {
false
}
}
}
// Public interface to synchronization primitives. Please note that in most
// cases, the function calls are infallible and it is the client's (shim
// implementation's) responsibility to detect and deal with erroneous
// situations.
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
#[inline]
/// Create state for a new mutex.
fn mutex_create(&mut self) -> MutexId {
let this = self.eval_context_mut();
this.machine.threads.sync.mutexes.push(Default::default())
}
#[inline]
/// Provides the closure with the next MutexId. Creates that mutex if the closure returns None,
/// otherwise returns the value from the closure
fn mutex_get_or_create<F>(&mut self, existing: F) -> InterpResult<'tcx, MutexId>
where
F: FnOnce(&mut MiriInterpCx<'mir, 'tcx>, MutexId) -> InterpResult<'tcx, Option<MutexId>>,
{
let this = self.eval_context_mut();
let next_index = this.machine.threads.sync.mutexes.next_index();
if let Some(old) = existing(this, next_index)? {
Ok(old)
} else {
let new_index = this.machine.threads.sync.mutexes.push(Default::default());
assert_eq!(next_index, new_index);
Ok(new_index)
}
}
#[inline]
/// Get the id of the thread that currently owns this lock.
fn mutex_get_owner(&mut self, id: MutexId) -> ThreadId {
let this = self.eval_context_ref();
this.machine.threads.sync.mutexes[id].owner.unwrap()
}
#[inline]
/// Check if locked.
fn mutex_is_locked(&self, id: MutexId) -> bool {
let this = self.eval_context_ref();
this.machine.threads.sync.mutexes[id].owner.is_some()
}
/// Lock by setting the mutex owner and increasing the lock count.
fn mutex_lock(&mut self, id: MutexId, thread: ThreadId) {
let this = self.eval_context_mut();
let mutex = &mut this.machine.threads.sync.mutexes[id];
if let Some(current_owner) = mutex.owner {
assert_eq!(thread, current_owner, "mutex already locked by another thread");
assert!(
mutex.lock_count > 0,
"invariant violation: lock_count == 0 iff the thread is unlocked"
);
} else {
mutex.owner = Some(thread);
}
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);
}
}
/// Try unlocking by decreasing the lock count and returning the old lock
/// count. If the lock count reaches 0, release the lock and potentially
/// give to a new owner. If the lock was not locked by `expected_owner`,
/// return `None`.
fn mutex_unlock(&mut self, id: MutexId, expected_owner: ThreadId) -> Option<usize> {
let this = self.eval_context_mut();
let mutex = &mut this.machine.threads.sync.mutexes[id];
if let Some(current_owner) = mutex.owner {
// Mutex is locked.
if current_owner != expected_owner {
// Only the owner can unlock the mutex.
return None;
}
let old_lock_count = mutex.lock_count;
mutex.lock_count = old_lock_count
.checked_sub(1)
.expect("invariant violation: lock_count == 0 iff the thread is unlocked");
if mutex.lock_count == 0 {
mutex.owner = None;
// The mutex is completely unlocked. Try transfering ownership
// to another thread.
if let Some(data_race) = &this.machine.data_race {
data_race.validate_lock_release(&mut mutex.data_race, current_owner);
}
this.mutex_dequeue_and_lock(id);
}
Some(old_lock_count)
} else {
// Mutex is not locked.
None
}
}
#[inline]
/// Put the thread into the queue waiting for the mutex.
fn mutex_enqueue_and_block(&mut self, id: MutexId, thread: ThreadId) {
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);
}
#[inline]
/// Create state for a new read write lock.
fn rwlock_create(&mut self) -> RwLockId {
let this = self.eval_context_mut();
this.machine.threads.sync.rwlocks.push(Default::default())
}
#[inline]
/// Provides the closure with the next RwLockId. Creates that RwLock if the closure returns None,
/// otherwise returns the value from the closure
fn rwlock_get_or_create<F>(&mut self, existing: F) -> InterpResult<'tcx, RwLockId>
where
F: FnOnce(&mut MiriInterpCx<'mir, 'tcx>, RwLockId) -> InterpResult<'tcx, Option<RwLockId>>,
{
let this = self.eval_context_mut();
let next_index = this.machine.threads.sync.rwlocks.next_index();
if let Some(old) = existing(this, next_index)? {
Ok(old)
} else {
let new_index = this.machine.threads.sync.rwlocks.push(Default::default());
assert_eq!(next_index, new_index);
Ok(new_index)
}
}
#[inline]
/// Check if locked.
fn rwlock_is_locked(&self, id: RwLockId) -> bool {
let this = self.eval_context_ref();
let rwlock = &this.machine.threads.sync.rwlocks[id];
trace!(
"rwlock_is_locked: {:?} writer is {:?} and there are {} reader threads (some of which could hold multiple read locks)",
id,
rwlock.writer,
rwlock.readers.len(),
);
rwlock.writer.is_some() || rwlock.readers.is_empty().not()
}
#[inline]
/// Check if write locked.
fn rwlock_is_write_locked(&self, id: RwLockId) -> bool {
let this = self.eval_context_ref();
let rwlock = &this.machine.threads.sync.rwlocks[id];
trace!("rwlock_is_write_locked: {:?} writer is {:?}", id, rwlock.writer);
rwlock.writer.is_some()
}
/// Read-lock the lock by adding the `reader` the list of threads that own
/// this lock.
fn rwlock_reader_lock(&mut self, id: RwLockId, reader: ThreadId) {
let this = self.eval_context_mut();
assert!(!this.rwlock_is_write_locked(id), "the lock is write locked");
trace!("rwlock_reader_lock: {:?} now also held (one more time) by {:?}", id, reader);
let rwlock = &mut this.machine.threads.sync.rwlocks[id];
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);
}
}
/// Try read-unlock the lock for `reader` and potentially give the lock to a new owner.
/// Returns `true` if succeeded, `false` if this `reader` did not hold the lock.
fn rwlock_reader_unlock(&mut self, id: RwLockId, reader: ThreadId) -> bool {
let this = self.eval_context_mut();
let rwlock = &mut this.machine.threads.sync.rwlocks[id];
match rwlock.readers.entry(reader) {
Entry::Occupied(mut entry) => {
let count = entry.get_mut();
assert!(*count > 0, "rwlock locked with count == 0");
*count -= 1;
if *count == 0 {
trace!("rwlock_reader_unlock: {:?} no longer held by {:?}", id, reader);
entry.remove();
} else {
trace!("rwlock_reader_unlock: {:?} held one less time by {:?}", id, reader);
}
}
Entry::Vacant(_) => return false, // we did not even own this lock
}
if let Some(data_race) = &this.machine.data_race {
data_race.validate_lock_release_shared(&mut rwlock.data_race_reader, reader);
}
// 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
let rwlock = &mut this.machine.threads.sync.rwlocks[id];
rwlock.data_race.clone_from(&rwlock.data_race_reader);
this.rwlock_dequeue_and_lock_writer(id);
}
true
}
#[inline]
/// Put the reader in the queue waiting for the lock and block it.
fn rwlock_enqueue_and_block_reader(&mut self, id: RwLockId, reader: ThreadId) {
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);
}
#[inline]
/// Lock by setting the writer that owns the lock.
fn rwlock_writer_lock(&mut self, id: RwLockId, writer: ThreadId) {
let this = self.eval_context_mut();
assert!(!this.rwlock_is_locked(id), "the rwlock is already locked");
trace!("rwlock_writer_lock: {:?} now held by {:?}", id, writer);
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);
}
}
#[inline]
/// Try to unlock by removing the writer.
fn rwlock_writer_unlock(&mut self, id: RwLockId, expected_writer: ThreadId) -> bool {
let this = self.eval_context_mut();
let rwlock = &mut this.machine.threads.sync.rwlocks[id];
if let Some(current_writer) = rwlock.writer {
if current_writer != expected_writer {
// Only the owner can unlock the rwlock.
return false;
}
rwlock.writer = None;
trace!("rwlock_writer_unlock: {:?} unlocked by {:?}", id, expected_writer);
// Release memory to both reader and writer vector clocks
// since this writer happens-before both the union of readers once they are finished
// and the next writer
if let Some(data_race) = &this.machine.data_race {
data_race.validate_lock_release(&mut rwlock.data_race, current_writer);
data_race.validate_lock_release(&mut rwlock.data_race_reader, current_writer);
}
// The thread was a writer.
//
// We are prioritizing writers here against the readers. As a
// result, not only readers can starve writers, but also writers can
// starve readers.
if this.rwlock_dequeue_and_lock_writer(id) {
// Someone got the write lock, nice.
} else {
// Give the lock to all readers.
while this.rwlock_dequeue_and_lock_reader(id) {
// Rinse and repeat.
}
}
true
} else {
false
}
}
#[inline]
/// Put the writer in the queue waiting for the lock.
fn rwlock_enqueue_and_block_writer(&mut self, id: RwLockId, writer: ThreadId) {
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);
}
#[inline]
/// Create state for a new conditional variable.
fn condvar_create(&mut self) -> CondvarId {
let this = self.eval_context_mut();
this.machine.threads.sync.condvars.push(Default::default())
}
#[inline]
/// Provides the closure with the next CondvarId. Creates that Condvar if the closure returns None,
/// otherwise returns the value from the closure
fn condvar_get_or_create<F>(&mut self, existing: F) -> InterpResult<'tcx, CondvarId>
where
F: FnOnce(
&mut MiriInterpCx<'mir, 'tcx>,
CondvarId,
) -> InterpResult<'tcx, Option<CondvarId>>,
{
let this = self.eval_context_mut();
let next_index = this.machine.threads.sync.condvars.next_index();
if let Some(old) = existing(this, next_index)? {
Ok(old)
} else {
let new_index = this.machine.threads.sync.condvars.push(Default::default());
assert_eq!(next_index, new_index);
Ok(new_index)
}
}
#[inline]
/// Is the conditional variable awaited?
fn condvar_is_awaited(&mut self, id: CondvarId) -> bool {
let this = self.eval_context_mut();
!this.machine.threads.sync.condvars[id].waiters.is_empty()
}
/// Mark that the thread is waiting on the conditional variable.
fn condvar_wait(&mut self, id: CondvarId, thread: ThreadId, mutex: 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");
waiters.push_back(CondvarWaiter { thread, mutex });
}
/// Wake up some thread (if there is any) sleeping on the conditional
/// variable.
fn condvar_signal(&mut self, id: CondvarId) -> Option<(ThreadId, MutexId)> {
let this = self.eval_context_mut();
let current_thread = this.get_active_thread();
let condvar = &mut this.machine.threads.sync.condvars[id];
let data_race = &this.machine.data_race;
// 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);
}
condvar.waiters.pop_front().map(|waiter| {
if let Some(data_race) = data_race {
data_race.validate_lock_acquire(&condvar.data_race, waiter.thread);
}
(waiter.thread, waiter.mutex)
})
}
#[inline]
/// Remove the thread from the queue of threads waiting on this conditional variable.
fn condvar_remove_waiter(&mut self, id: CondvarId, thread: ThreadId) {
let this = self.eval_context_mut();
this.machine.threads.sync.condvars[id].waiters.retain(|waiter| waiter.thread != thread);
}
fn futex_wait(&mut self, addr: u64, thread: ThreadId, bitset: u32) {
let this = self.eval_context_mut();
let futex = &mut this.machine.threads.sync.futexes.entry(addr).or_default();
let waiters = &mut futex.waiters;
assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting");
waiters.push_back(FutexWaiter { thread, bitset });
}
fn futex_wake(&mut self, addr: u64, bitset: u32) -> Option<ThreadId> {
let this = self.eval_context_mut();
let current_thread = this.get_active_thread();
let futex = &mut this.machine.threads.sync.futexes.get_mut(&addr)?;
let data_race = &this.machine.data_race;
// 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);
}
// 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);
}
waiter.thread
})
}
fn futex_remove_waiter(&mut self, addr: u64, thread: ThreadId) {
let this = self.eval_context_mut();
if let Some(futex) = this.machine.threads.sync.futexes.get_mut(&addr) {
futex.waiters.retain(|waiter| waiter.thread != thread);
}
}
}

View File

@ -0,0 +1,933 @@
//! Implements threads.
use std::cell::RefCell;
use std::collections::hash_map::Entry;
use std::num::TryFromIntError;
use std::time::{Duration, SystemTime};
use log::trace;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def_id::DefId;
use rustc_index::vec::{Idx, IndexVec};
use rustc_middle::mir::Mutability;
use rustc_middle::ty::layout::TyAndLayout;
use rustc_target::spec::abi::Abi;
use crate::concurrency::data_race;
use crate::concurrency::sync::SynchronizationState;
use crate::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SchedulingAction {
/// Execute step on the active thread.
ExecuteStep,
/// Execute a timeout callback.
ExecuteTimeoutCallback,
/// Execute destructors of the active thread.
ExecuteDtors,
/// Stop the program.
Stop,
}
/// Timeout callbacks can be created by synchronization primitives to tell the
/// scheduler that they should be called once some period of time passes.
type TimeoutCallback<'mir, 'tcx> = Box<
dyn FnOnce(&mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>) -> InterpResult<'tcx> + 'tcx,
>;
/// A thread identifier.
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct ThreadId(u32);
/// The main thread. When it terminates, the whole application terminates.
const MAIN_THREAD: ThreadId = ThreadId(0);
impl ThreadId {
pub fn to_u32(self) -> u32 {
self.0
}
}
impl Idx for ThreadId {
fn new(idx: usize) -> Self {
ThreadId(u32::try_from(idx).unwrap())
}
fn index(self) -> usize {
usize::try_from(self.0).unwrap()
}
}
impl TryFrom<u64> for ThreadId {
type Error = TryFromIntError;
fn try_from(id: u64) -> Result<Self, Self::Error> {
u32::try_from(id).map(Self)
}
}
impl From<u32> for ThreadId {
fn from(id: u32) -> Self {
Self(id)
}
}
impl From<ThreadId> for u64 {
fn from(t: ThreadId) -> Self {
t.0.into()
}
}
/// 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 has terminated its execution. We do not delete terminated
/// threads (FIXME: why?).
Terminated,
}
/// The join status of a thread.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum ThreadJoinStatus {
/// The thread can be joined.
Joinable,
/// A thread is detached if its join handle was destroyed and no other
/// thread can join it.
Detached,
/// The thread was already joined by some thread and cannot be joined again.
Joined,
}
/// A thread.
pub struct Thread<'mir, 'tcx> {
state: ThreadState,
/// Name of the thread.
thread_name: Option<Vec<u8>>,
/// The virtual call stack.
stack: Vec<Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>>,
/// The join status.
join_status: ThreadJoinStatus,
/// The temporary used for storing the argument of
/// the call to `miri_start_panic` (the panic payload) when unwinding.
/// This is pointer-sized, and matches the `Payload` type in `src/libpanic_unwind/miri.rs`.
pub(crate) panic_payload: Option<Scalar<Provenance>>,
/// Last OS error location in memory. It is a 32-bit integer.
pub(crate) last_error: Option<MPlaceTy<'tcx, Provenance>>,
}
impl<'mir, 'tcx> Thread<'mir, 'tcx> {
/// Check if the thread is done executing (no more stack frames). If yes,
/// change the state to terminated and return `true`.
fn check_terminated(&mut self) -> bool {
if self.state == ThreadState::Enabled {
if self.stack.is_empty() {
self.state = ThreadState::Terminated;
return true;
}
}
false
}
/// Get the name of the current thread, or `<unnamed>` if it was not set.
fn thread_name(&self) -> &[u8] {
if let Some(ref thread_name) = self.thread_name { thread_name } else { b"<unnamed>" }
}
}
impl<'mir, 'tcx> std::fmt::Debug for Thread<'mir, 'tcx> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}({:?}, {:?})",
String::from_utf8_lossy(self.thread_name()),
self.state,
self.join_status
)
}
}
impl<'mir, 'tcx> Default for Thread<'mir, 'tcx> {
fn default() -> Self {
Self {
state: ThreadState::Enabled,
thread_name: None,
stack: Vec::new(),
join_status: ThreadJoinStatus::Joinable,
panic_payload: None,
last_error: None,
}
}
}
impl<'mir, 'tcx> Thread<'mir, 'tcx> {
fn new(name: &str) -> Self {
let mut thread = Thread::default();
thread.thread_name = Some(Vec::from(name.as_bytes()));
thread
}
}
/// A specific moment in time.
#[derive(Debug)]
pub enum Time {
Monotonic(Instant),
RealTime(SystemTime),
}
impl Time {
/// 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) =>
time.duration_since(SystemTime::now()).unwrap_or(Duration::new(0, 0)),
}
}
}
/// Callbacks are used to implement timeouts. For example, waiting on a
/// conditional variable with a timeout creates a callback that is called after
/// the specified time and unblocks the thread. If another thread signals on the
/// 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,
/// The called function.
callback: TimeoutCallback<'mir, 'tcx>,
}
impl<'mir, 'tcx> std::fmt::Debug for TimeoutCallbackInfo<'mir, 'tcx> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TimeoutCallback({:?})", self.call_time)
}
}
/// A set of threads.
#[derive(Debug)]
pub struct ThreadManager<'mir, 'tcx> {
/// Identifier of the currently active thread.
active_thread: ThreadId,
/// Threads used in the program.
///
/// Note that this vector also contains terminated threads.
threads: IndexVec<ThreadId, Thread<'mir, 'tcx>>,
/// This field is pub(crate) because the synchronization primitives
/// (`crate::sync`) need a way to access it.
pub(crate) sync: SynchronizationState,
/// A mapping from a thread-local static to an allocation id of a thread
/// specific allocation.
thread_local_alloc_ids: RefCell<FxHashMap<(DefId, ThreadId), Pointer<Provenance>>>,
/// A flag that indicates that we should change the active thread.
yield_active_thread: bool,
/// Callbacks that are called once the specified time passes.
timeout_callbacks: FxHashMap<ThreadId, TimeoutCallbackInfo<'mir, 'tcx>>,
}
impl<'mir, 'tcx> Default for ThreadManager<'mir, 'tcx> {
fn default() -> Self {
let mut threads = IndexVec::new();
// Create the main thread and add it to the list of threads.
threads.push(Thread::new("main"));
Self {
active_thread: ThreadId::new(0),
threads,
sync: SynchronizationState::default(),
thread_local_alloc_ids: Default::default(),
yield_active_thread: false,
timeout_callbacks: FxHashMap::default(),
}
}
}
impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
pub(crate) fn init(ecx: &mut MiriInterpCx<'mir, 'tcx>) {
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;
}
}
/// Check if we have an allocation for the given thread local static for the
/// active thread.
fn get_thread_local_alloc_id(&self, def_id: DefId) -> Option<Pointer<Provenance>> {
self.thread_local_alloc_ids.borrow().get(&(def_id, self.active_thread)).cloned()
}
/// Set the pointer for the allocation of the given thread local
/// static for the active thread.
///
/// Panics if a thread local is initialized twice for the same thread.
fn set_thread_local_alloc(&self, def_id: DefId, ptr: Pointer<Provenance>) {
self.thread_local_alloc_ids
.borrow_mut()
.try_insert((def_id, self.active_thread), ptr)
.unwrap();
}
/// Borrow the stack of the active thread.
pub fn active_thread_stack(&self) -> &[Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>] {
&self.threads[self.active_thread].stack
}
/// Mutably borrow the stack of the active thread.
fn active_thread_stack_mut(
&mut self,
) -> &mut Vec<Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>> {
&mut self.threads[self.active_thread].stack
}
pub fn iter(&self) -> impl Iterator<Item = &Thread<'mir, 'tcx>> {
self.threads.iter()
}
pub fn all_stacks(
&self,
) -> impl Iterator<Item = &[Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>]> {
self.threads.iter().map(|t| &t.stack[..])
}
/// Create a new thread and returns its id.
fn create_thread(&mut self) -> ThreadId {
let new_thread_id = ThreadId::new(self.threads.len());
self.threads.push(Default::default());
new_thread_id
}
/// Set an active thread and return the id of the thread that was active before.
fn set_active_thread_id(&mut self, id: ThreadId) -> ThreadId {
let active_thread_id = self.active_thread;
self.active_thread = id;
assert!(self.active_thread.index() < self.threads.len());
active_thread_id
}
/// Get the id of the currently active thread.
pub fn get_active_thread_id(&self) -> ThreadId {
self.active_thread
}
/// Get the total number of threads that were ever spawn by this program.
pub fn get_total_thread_count(&self) -> usize {
self.threads.len()
}
/// Get the total of threads that are currently live, i.e., not yet terminated.
/// (They might be blocked.)
pub fn get_live_thread_count(&self) -> usize {
self.threads.iter().filter(|t| !matches!(t.state, ThreadState::Terminated)).count()
}
/// Has the given thread terminated?
fn has_terminated(&self, thread_id: ThreadId) -> bool {
self.threads[thread_id].state == ThreadState::Terminated
}
/// Have all threads terminated?
fn have_all_terminated(&self) -> bool {
self.threads.iter().all(|thread| thread.state == ThreadState::Terminated)
}
/// Enable the thread for execution. The thread must be terminated.
fn enable_thread(&mut self, thread_id: ThreadId) {
assert!(self.has_terminated(thread_id));
self.threads[thread_id].state = ThreadState::Enabled;
}
/// Get a mutable borrow of the currently active thread.
fn active_thread_mut(&mut self) -> &mut Thread<'mir, 'tcx> {
&mut self.threads[self.active_thread]
}
/// Get a shared borrow of the currently active thread.
fn active_thread_ref(&self) -> &Thread<'mir, 'tcx> {
&self.threads[self.active_thread]
}
/// Mark the thread as detached, which means that no other thread will try
/// to join it and the thread is responsible for cleaning up.
///
/// `allow_terminated_joined` allows detaching joined threads that have already terminated.
/// This matches Windows's behavior for `CloseHandle`.
///
/// See <https://docs.microsoft.com/en-us/windows/win32/procthread/thread-handles-and-identifiers>:
/// > The handle is valid until closed, even after the thread it represents has been terminated.
fn detach_thread(&mut self, id: ThreadId, allow_terminated_joined: bool) -> InterpResult<'tcx> {
trace!("detaching {:?}", id);
let is_ub = if allow_terminated_joined && self.threads[id].state == ThreadState::Terminated
{
// "Detached" in particular means "not yet joined". Redundant detaching is still UB.
self.threads[id].join_status == ThreadJoinStatus::Detached
} else {
self.threads[id].join_status != ThreadJoinStatus::Joinable
};
if is_ub {
throw_ub_format!("trying to detach thread that was already detached or joined");
}
self.threads[id].join_status = ThreadJoinStatus::Detached;
Ok(())
}
/// Mark that the active thread tries to join the thread with `joined_thread_id`.
fn join_thread(
&mut self,
joined_thread_id: ThreadId,
data_race: Option<&mut data_race::GlobalState>,
) -> InterpResult<'tcx> {
if self.threads[joined_thread_id].join_status == ThreadJoinStatus::Detached {
throw_ub_format!("trying to join a detached thread");
}
// Mark the joined thread as being joined so that we detect if other
// threads try to join it.
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);
trace!(
"{:?} blocked on {:?} when trying to join",
self.active_thread,
joined_thread_id
);
} else {
// The thread has already terminated - mark join happens-before
if let Some(data_race) = data_race {
data_race.thread_joined(self, self.active_thread, joined_thread_id);
}
}
Ok(())
}
/// Mark that the active thread tries to exclusively join the thread with `joined_thread_id`.
/// If the thread is already joined by another thread, it will throw UB
fn join_thread_exclusive(
&mut self,
joined_thread_id: ThreadId,
data_race: Option<&mut data_race::GlobalState>,
) -> InterpResult<'tcx> {
if self.threads[joined_thread_id].join_status == ThreadJoinStatus::Joined {
throw_ub_format!("trying to join an already joined thread");
}
if joined_thread_id == self.active_thread {
throw_ub_format!("trying to join itself");
}
assert!(
self.threads
.iter()
.all(|thread| thread.state != ThreadState::BlockedOnJoin(joined_thread_id)),
"this thread already has threads waiting for its termination"
);
self.join_thread(joined_thread_id, data_race)
}
/// Set the name of the given thread.
pub fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
self.threads[thread].thread_name = Some(new_thread_name);
}
/// Get the name of the given thread.
pub fn get_thread_name(&self, thread: ThreadId) -> &[u8] {
self.threads[thread].thread_name()
}
/// Put the thread into the blocked state.
fn block_thread(&mut self, thread: ThreadId) {
let state = &mut self.threads[thread].state;
assert_eq!(*state, ThreadState::Enabled);
*state = ThreadState::BlockedOnSync;
}
/// Put the blocked thread into the enabled state.
fn unblock_thread(&mut self, thread: ThreadId) {
let state = &mut self.threads[thread].state;
assert_eq!(*state, ThreadState::BlockedOnSync);
*state = ThreadState::Enabled;
}
/// Change the active thread to some enabled thread.
fn yield_active_thread(&mut self) {
// We do not yield immediately, as swapping out the current stack while executing a MIR statement
// could lead to all sorts of confusion.
// We should only switch stacks between steps.
self.yield_active_thread = true;
}
/// Register the given `callback` to be called once the `call_time` passes.
///
/// The callback will be called with `thread` being the active thread, and
/// the callback may not change the active thread.
fn register_timeout_callback(
&mut self,
thread: ThreadId,
call_time: Time,
callback: TimeoutCallback<'mir, 'tcx>,
) {
self.timeout_callbacks
.try_insert(thread, TimeoutCallbackInfo { call_time, callback })
.unwrap();
}
/// Unregister the callback for the `thread`.
fn unregister_timeout_callback_if_exists(&mut self, thread: ThreadId) {
self.timeout_callbacks.remove(&thread);
}
/// Get a callback that is ready to be called.
fn get_ready_callback(
&mut self,
clock: &Clock,
) -> Option<(ThreadId, TimeoutCallback<'mir, 'tcx>)> {
// We iterate over all threads in the order of their indices because
// this allows us to have a deterministic scheduler.
for thread in self.threads.indices() {
match self.timeout_callbacks.entry(thread) {
Entry::Occupied(entry) =>
if entry.get().call_time.get_wait_time(clock) == Duration::new(0, 0) {
return Some((thread, entry.remove().callback));
},
Entry::Vacant(_) => {}
}
}
None
}
/// Wakes up threads joining on the active one and deallocates thread-local statics.
/// The `AllocId` that can now be freed are returned.
fn thread_terminated(
&mut self,
mut data_race: Option<&mut data_race::GlobalState>,
) -> Vec<Pointer<Provenance>> {
let mut free_tls_statics = Vec::new();
{
let mut thread_local_statics = self.thread_local_alloc_ids.borrow_mut();
thread_local_statics.retain(|&(_def_id, thread), &mut alloc_id| {
if thread != self.active_thread {
// Keep this static around.
return true;
}
// Delete this static from the map and from memory.
// We cannot free directly here as we cannot use `?` in this context.
free_tls_statics.push(alloc_id);
false
});
}
// Set the thread into a terminated state in the data-race detector.
if let Some(ref mut data_race) = data_race {
data_race.thread_terminated(self);
}
// 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) {
// The thread has terminated, mark happens-before edge to joining thread
if data_race.is_some() {
joined_threads.push(i);
}
trace!("unblocking {:?} because {:?} terminated", i, self.active_thread);
thread.state = ThreadState::Enabled;
}
}
for &i in &joined_threads {
data_race.as_mut().unwrap().thread_joined(self, i, self.active_thread);
}
free_tls_statics
}
/// Decide which action to take next and on which thread.
///
/// The currently implemented scheduling policy is the one that is commonly
/// used in stateless model checkers such as Loom: run the active thread as
/// long as we can and switch only when we have to (the active thread was
/// blocked, terminated, or has explicitly asked to be preempted).
fn schedule(&mut self, clock: &Clock) -> InterpResult<'tcx, SchedulingAction> {
// Check whether the thread has **just** terminated (`check_terminated`
// checks whether the thread has popped all its stack and if yes, sets
// the thread state to terminated).
if self.threads[self.active_thread].check_terminated() {
return Ok(SchedulingAction::ExecuteDtors);
}
// If we get here again and the thread is *still* terminated, there are no more dtors to run.
if self.threads[MAIN_THREAD].state == ThreadState::Terminated {
// The main thread terminated; stop the program.
// We do *not* run TLS dtors of remaining threads, which seems to match rustc behavior.
return Ok(SchedulingAction::Stop);
}
// This thread and the program can keep going.
if self.threads[self.active_thread].state == ThreadState::Enabled
&& !self.yield_active_thread
{
// The currently active thread is still enabled, just continue with it.
return Ok(SchedulingAction::ExecuteStep);
}
// The active thread yielded. Let's see if there are any timeouts to take care of. We do
// this *before* running any other thread, to ensure that timeouts "in the past" fire before
// any other thread can take an action. This ensures that for `pthread_cond_timedwait`, "an
// error is returned if [...] the absolute time specified by abstime has already been passed
// at the time of the call".
// <https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_timedwait.html>
let potential_sleep_time =
self.timeout_callbacks.values().map(|info| info.call_time.get_wait_time(clock)).min();
if potential_sleep_time == Some(Duration::new(0, 0)) {
return Ok(SchedulingAction::ExecuteTimeoutCallback);
}
// No callbacks scheduled, pick a regular thread to execute.
// The active thread blocked or yielded. So we go search for another enabled thread.
// Crucially, we start searching at the current active thread ID, rather than at 0, since we
// want to avoid always scheduling threads 0 and 1 without ever making progress in thread 2.
//
// `skip(N)` means we start iterating at thread N, so we skip 1 more to start just *after*
// the active thread. Then after that we look at `take(N)`, i.e., the threads *before* the
// active thread.
let threads = self
.threads
.iter_enumerated()
.skip(self.active_thread.index() + 1)
.chain(self.threads.iter_enumerated().take(self.active_thread.index()));
for (id, thread) in threads {
debug_assert_ne!(self.active_thread, id);
if thread.state == ThreadState::Enabled {
self.active_thread = id;
break;
}
}
self.yield_active_thread = false;
if self.threads[self.active_thread].state == ThreadState::Enabled {
return Ok(SchedulingAction::ExecuteStep);
}
// We have not found a thread to execute.
if self.threads.iter().all(|thread| thread.state == ThreadState::Terminated) {
unreachable!("all threads terminated without the main thread terminating?!");
} else if let Some(sleep_time) = potential_sleep_time {
// All threads are currently blocked, but we have unexecuted
// timeout_callbacks, which may unblock some of the threads. Hence,
// sleep until the first callback.
clock.sleep(sleep_time);
Ok(SchedulingAction::ExecuteTimeoutCallback)
} else {
throw_machine_stop!(TerminationInfo::Deadlock);
}
}
}
// Public interface to thread management.
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Get a thread-specific allocation id for the given thread-local static.
/// If needed, allocate a new one.
fn get_or_create_thread_local_alloc(
&mut self,
def_id: DefId,
) -> InterpResult<'tcx, Pointer<Provenance>> {
let this = self.eval_context_mut();
let tcx = this.tcx;
if let Some(old_alloc) = this.machine.threads.get_thread_local_alloc_id(def_id) {
// We already have a thread-specific allocation id for this
// thread-local static.
Ok(old_alloc)
} else {
// We need to allocate a thread-specific allocation id for this
// thread-local static.
// First, we compute the initial value for this static.
if tcx.is_foreign_item(def_id) {
throw_unsup_format!("foreign thread-local statics are not supported");
}
let allocation = tcx.eval_static_initializer(def_id)?;
let mut allocation = allocation.inner().clone();
// This allocation will be deallocated when the thread dies, so it is not in read-only memory.
allocation.mutability = Mutability::Mut;
// Create a fresh allocation with this content.
let new_alloc = this.allocate_raw_ptr(allocation, MiriMemoryKind::Tls.into())?;
this.machine.threads.set_thread_local_alloc(def_id, new_alloc);
Ok(new_alloc)
}
}
#[inline]
fn create_thread(&mut self) -> ThreadId {
let this = self.eval_context_mut();
let id = this.machine.threads.create_thread();
if let Some(data_race) = &mut this.machine.data_race {
data_race.thread_created(&this.machine.threads, id);
}
id
}
#[inline]
fn start_thread(
&mut self,
thread: Option<MPlaceTy<'tcx, Provenance>>,
start_routine: Pointer<Option<Provenance>>,
start_abi: Abi,
func_arg: ImmTy<'tcx, Provenance>,
ret_layout: TyAndLayout<'tcx>,
) -> InterpResult<'tcx, ThreadId> {
let this = self.eval_context_mut();
// Create the new thread
let new_thread_id = this.create_thread();
// Write the current thread-id, switch to the next thread later
// to treat this write operation as occuring on the current thread.
if let Some(thread_info_place) = thread {
this.write_scalar(
Scalar::from_uint(new_thread_id.to_u32(), thread_info_place.layout.size),
&thread_info_place.into(),
)?;
}
// Finally switch to new thread so that we can push the first stackframe.
// After this all accesses will be treated as occuring in the new thread.
let old_thread_id = this.set_active_thread(new_thread_id);
// Perform the function pointer load in the new thread frame.
let instance = this.get_ptr_fn(start_routine)?.as_instance()?;
// Note: the returned value is currently ignored (see the FIXME in
// pthread_join in shims/unix/thread.rs) because the Rust standard library does not use
// it.
let ret_place = this.allocate(ret_layout, MiriMemoryKind::Machine.into())?;
this.call_function(
instance,
start_abi,
&[*func_arg],
Some(&ret_place.into()),
StackPopCleanup::Root { cleanup: true },
)?;
// Restore the old active thread frame.
this.set_active_thread(old_thread_id);
Ok(new_thread_id)
}
#[inline]
fn detach_thread(
&mut self,
thread_id: ThreadId,
allow_terminated_joined: bool,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
this.machine.threads.detach_thread(thread_id, allow_terminated_joined)
}
#[inline]
fn join_thread(&mut self, joined_thread_id: ThreadId) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
this.machine.threads.join_thread(joined_thread_id, this.machine.data_race.as_mut())?;
Ok(())
}
#[inline]
fn join_thread_exclusive(&mut self, joined_thread_id: ThreadId) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
this.machine
.threads
.join_thread_exclusive(joined_thread_id, this.machine.data_race.as_mut())?;
Ok(())
}
#[inline]
fn set_active_thread(&mut self, thread_id: ThreadId) -> ThreadId {
let this = self.eval_context_mut();
this.machine.threads.set_active_thread_id(thread_id)
}
#[inline]
fn get_active_thread(&self) -> ThreadId {
let this = self.eval_context_ref();
this.machine.threads.get_active_thread_id()
}
#[inline]
fn active_thread_mut(&mut self) -> &mut Thread<'mir, 'tcx> {
let this = self.eval_context_mut();
this.machine.threads.active_thread_mut()
}
#[inline]
fn active_thread_ref(&self) -> &Thread<'mir, 'tcx> {
let this = self.eval_context_ref();
this.machine.threads.active_thread_ref()
}
#[inline]
fn get_total_thread_count(&self) -> usize {
let this = self.eval_context_ref();
this.machine.threads.get_total_thread_count()
}
#[inline]
fn has_terminated(&self, thread_id: ThreadId) -> bool {
let this = self.eval_context_ref();
this.machine.threads.has_terminated(thread_id)
}
#[inline]
fn have_all_terminated(&self) -> bool {
let this = self.eval_context_ref();
this.machine.threads.have_all_terminated()
}
#[inline]
fn enable_thread(&mut self, thread_id: ThreadId) {
let this = self.eval_context_mut();
this.machine.threads.enable_thread(thread_id);
}
#[inline]
fn active_thread_stack(&self) -> &[Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>] {
let this = self.eval_context_ref();
this.machine.threads.active_thread_stack()
}
#[inline]
fn active_thread_stack_mut(
&mut self,
) -> &mut Vec<Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>> {
let this = self.eval_context_mut();
this.machine.threads.active_thread_stack_mut()
}
#[inline]
fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
let this = self.eval_context_mut();
this.machine.threads.set_thread_name(thread, new_thread_name);
}
#[inline]
fn set_thread_name_wide(&mut self, thread: ThreadId, new_thread_name: &[u16]) {
let this = self.eval_context_mut();
// The Windows `GetThreadDescription` shim to get the thread name isn't implemented, so being lossy is okay.
// This is only read by diagnostics, which already use `from_utf8_lossy`.
this.machine
.threads
.set_thread_name(thread, String::from_utf16_lossy(new_thread_name).into_bytes());
}
#[inline]
fn get_thread_name<'c>(&'c self, thread: ThreadId) -> &'c [u8]
where
'mir: 'c,
{
let this = self.eval_context_ref();
this.machine.threads.get_thread_name(thread)
}
#[inline]
fn block_thread(&mut self, thread: ThreadId) {
let this = self.eval_context_mut();
this.machine.threads.block_thread(thread);
}
#[inline]
fn unblock_thread(&mut self, thread: ThreadId) {
let this = self.eval_context_mut();
this.machine.threads.unblock_thread(thread);
}
#[inline]
fn yield_active_thread(&mut self) {
let this = self.eval_context_mut();
this.machine.threads.yield_active_thread();
}
#[inline]
fn maybe_preempt_active_thread(&mut self) {
use rand::Rng as _;
let this = self.eval_context_mut();
if this.machine.rng.get_mut().gen_bool(this.machine.preemption_rate) {
this.yield_active_thread();
}
}
#[inline]
fn register_timeout_callback(
&mut self,
thread: ThreadId,
call_time: Time,
callback: TimeoutCallback<'mir, 'tcx>,
) {
let this = self.eval_context_mut();
if !this.machine.communicate() && matches!(call_time, Time::RealTime(..)) {
panic!("cannot have `RealTime` callback with isolation enabled!")
}
this.machine.threads.register_timeout_callback(thread, call_time, callback);
}
#[inline]
fn unregister_timeout_callback_if_exists(&mut self, thread: ThreadId) {
let this = self.eval_context_mut();
this.machine.threads.unregister_timeout_callback_if_exists(thread);
}
/// Execute a timeout callback on the callback's thread.
#[inline]
fn run_timeout_callback(&mut self) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let (thread, callback) = if let Some((thread, callback)) =
this.machine.threads.get_ready_callback(&this.machine.clock)
{
(thread, callback)
} else {
// get_ready_callback can return None if the computer's clock
// was shifted after calling the scheduler and before the call
// to get_ready_callback (see issue
// https://github.com/rust-lang/miri/issues/1763). In this case,
// just do nothing, which effectively just returns to the
// scheduler.
return Ok(());
};
// This back-and-forth with `set_active_thread` is here because of two
// design decisions:
// 1. Make the caller and not the callback responsible for changing
// thread.
// 2. Make the scheduler the only place that can change the active
// thread.
let old_thread = this.set_active_thread(thread);
callback(this)?;
this.set_active_thread(old_thread);
Ok(())
}
/// Decide which action to take next and on which thread.
#[inline]
fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> {
let this = self.eval_context_mut();
this.machine.threads.schedule(&this.machine.clock)
}
/// Handles thread termination of the active thread: wakes up threads joining on this one,
/// and deallocated thread-local statics.
///
/// This is called from `tls.rs` after handling the TLS dtors.
#[inline]
fn thread_terminated(&mut self) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
for ptr in this.machine.threads.thread_terminated(this.machine.data_race.as_mut()) {
this.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?;
}
Ok(())
}
}

View File

@ -0,0 +1,470 @@
use rustc_index::vec::Idx;
use smallvec::SmallVec;
use std::{cmp::Ordering, fmt::Debug, ops::Index};
/// A vector clock index, this is associated with a thread id
/// but in some cases one vector index may be shared with
/// multiple thread ids if it safe to do so.
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct VectorIdx(u32);
impl VectorIdx {
#[inline(always)]
pub fn to_u32(self) -> u32 {
self.0
}
pub const MAX_INDEX: VectorIdx = VectorIdx(u32::MAX);
}
impl Idx for VectorIdx {
#[inline]
fn new(idx: usize) -> Self {
VectorIdx(u32::try_from(idx).unwrap())
}
#[inline]
fn index(self) -> usize {
usize::try_from(self.0).unwrap()
}
}
impl From<u32> for VectorIdx {
#[inline]
fn from(id: u32) -> Self {
Self(id)
}
}
/// The size of the vector-clock to store inline
/// clock vectors larger than this will be stored on the heap
const SMALL_VECTOR: usize = 4;
/// The type of the time-stamps recorded in the data-race detector
/// set to a type of unsigned integer
pub type VTimestamp = u32;
/// A vector clock for detecting data-races, this is conceptually
/// a map from a vector index (and thus a thread id) to a timestamp.
/// The compare operations require that the invariant that the last
/// element in the internal timestamp slice must not be a 0, hence
/// all zero vector clocks are always represented by the empty slice;
/// and allows for the implementation of compare operations to short
/// circuit the calculation and return the correct result faster,
/// also this means that there is only one unique valid length
/// for each set of vector clock values and hence the PartialEq
/// and Eq derivations are correct.
#[derive(PartialEq, Eq, Default, Debug)]
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 {
let len = index.index() + 1;
let mut vec = smallvec::smallvec![0; len];
vec[index.index()] = timestamp;
VClock(vec)
}
/// Load the internal timestamp slice in the vector clock
#[inline]
pub fn as_slice(&self) -> &[VTimestamp] {
self.0.as_slice()
}
/// Get a mutable slice to the internal vector with minimum `min_len`
/// elements, to preserve invariants this vector 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] {
if self.0.len() < min_len {
self.0.resize(min_len, 0);
}
assert!(self.0.len() >= min_len);
self.0.as_mut_slice()
}
/// 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) {
let idx = idx.index();
let mut_slice = self.get_mut_with_min_len(idx + 1);
let idx_ref = &mut mut_slice[idx];
*idx_ref = idx_ref.checked_add(1).expect("Vector clock overflow")
}
// Join the two vector-clocks together, this
// sets each vector-element to the maximum value
// of that element in either of the two source elements.
pub fn join(&mut self, other: &Self) {
let rhs_slice = other.as_slice();
let lhs_slice = self.get_mut_with_min_len(rhs_slice.len());
for (l, &r) in lhs_slice.iter_mut().zip(rhs_slice.iter()) {
*l = r.max(*l);
}
}
/// Set the element at the current index of the vector
pub fn set_at_index(&mut self, other: &Self, idx: VectorIdx) {
let mut_slice = self.get_mut_with_min_len(idx.index() + 1);
mut_slice[idx.index()] = other[idx];
}
/// Set the vector to the all-zero vector
#[inline]
pub 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 {
fn clone(&self) -> Self {
VClock(self.0.clone())
}
// Optimized clone-from, can be removed
// and replaced with a derive once a similar
// optimization is inserted into SmallVec's
// clone implementation.
fn clone_from(&mut self, source: &Self) {
let source_slice = source.as_slice();
self.0.clear();
self.0.extend_from_slice(source_slice);
}
}
impl PartialOrd for VClock {
fn partial_cmp(&self, other: &VClock) -> Option<Ordering> {
// Load the values as slices
let lhs_slice = self.as_slice();
let rhs_slice = other.as_slice();
// Iterate through the combined vector slice continuously updating
// the value of `order` to the current comparison of the vector from
// index 0 to the currently checked index.
// An Equal ordering can be converted into Less or Greater ordering
// on finding an element that is less than or greater than the other
// but if one Greater and one Less element-wise comparison is found
// then no ordering is possible and so directly return an ordering
// of None.
let mut iter = lhs_slice.iter().zip(rhs_slice.iter());
let mut order = match iter.next() {
Some((lhs, rhs)) => lhs.cmp(rhs),
None => Ordering::Equal,
};
for (l, r) in iter {
match order {
Ordering::Equal => order = l.cmp(r),
Ordering::Less =>
if l > r {
return None;
},
Ordering::Greater =>
if l < r {
return None;
},
}
}
// Now test if either left or right have trailing elements,
// by the invariant the trailing elements have at least 1
// non zero value, so no additional calculation is required
// to determine the result of the PartialOrder.
let l_len = lhs_slice.len();
let r_len = rhs_slice.len();
match l_len.cmp(&r_len) {
// Equal means no additional elements: return current order
Ordering::Equal => Some(order),
// Right has at least 1 element > than the implicit 0,
// so the only valid values are Ordering::Less or None.
Ordering::Less =>
match order {
Ordering::Less | Ordering::Equal => Some(Ordering::Less),
Ordering::Greater => None,
},
// Left has at least 1 element > than the implicit 0,
// so the only valid values are Ordering::Greater or None.
Ordering::Greater =>
match order {
Ordering::Greater | Ordering::Equal => Some(Ordering::Greater),
Ordering::Less => None,
},
}
}
fn lt(&self, other: &VClock) -> bool {
// Load the values as slices
let lhs_slice = self.as_slice();
let rhs_slice = other.as_slice();
// If l_len > r_len then at least one element
// in l_len is > than r_len, therefore the result
// is either Some(Greater) or None, so return false
// early.
let l_len = lhs_slice.len();
let r_len = rhs_slice.len();
if l_len <= r_len {
// If any elements on the left are greater than the right
// then the result is None or Some(Greater), both of which
// return false, the earlier test asserts that no elements in the
// extended tail violate this assumption. Otherwise l <= r, finally
// the case where the values are potentially equal needs to be considered
// and false returned as well
let mut equal = l_len == r_len;
for (&l, &r) in lhs_slice.iter().zip(rhs_slice.iter()) {
if l > r {
return false;
} else if l < r {
equal = false;
}
}
!equal
} else {
false
}
}
fn le(&self, other: &VClock) -> bool {
// Load the values as slices
let lhs_slice = self.as_slice();
let rhs_slice = other.as_slice();
// If l_len > r_len then at least one element
// in l_len is > than r_len, therefore the result
// is either Some(Greater) or None, so return false
// early.
let l_len = lhs_slice.len();
let r_len = rhs_slice.len();
if l_len <= r_len {
// If any elements on the left are greater than the right
// then the result is None or Some(Greater), both of which
// return false, the earlier test asserts that no elements in the
// extended tail violate this assumption. Otherwise l <= r
!lhs_slice.iter().zip(rhs_slice.iter()).any(|(&l, &r)| l > r)
} else {
false
}
}
fn gt(&self, other: &VClock) -> bool {
// Load the values as slices
let lhs_slice = self.as_slice();
let rhs_slice = other.as_slice();
// If r_len > l_len then at least one element
// in r_len is > than l_len, therefore the result
// is either Some(Less) or None, so return false
// early.
let l_len = lhs_slice.len();
let r_len = rhs_slice.len();
if l_len >= r_len {
// If any elements on the left are less than the right
// then the result is None or Some(Less), both of which
// return false, the earlier test asserts that no elements in the
// extended tail violate this assumption. Otherwise l >=, finally
// the case where the values are potentially equal needs to be considered
// and false returned as well
let mut equal = l_len == r_len;
for (&l, &r) in lhs_slice.iter().zip(rhs_slice.iter()) {
if l < r {
return false;
} else if l > r {
equal = false;
}
}
!equal
} else {
false
}
}
fn ge(&self, other: &VClock) -> bool {
// Load the values as slices
let lhs_slice = self.as_slice();
let rhs_slice = other.as_slice();
// If r_len > l_len then at least one element
// in r_len is > than l_len, therefore the result
// is either Some(Less) or None, so return false
// early.
let l_len = lhs_slice.len();
let r_len = rhs_slice.len();
if l_len >= r_len {
// If any elements on the left are less than the right
// then the result is None or Some(Less), both of which
// return false, the earlier test asserts that no elements in the
// extended tail violate this assumption. Otherwise l >= r
!lhs_slice.iter().zip(rhs_slice.iter()).any(|(&l, &r)| l < r)
} else {
false
}
}
}
impl Index<VectorIdx> for VClock {
type Output = VTimestamp;
#[inline]
fn index(&self, index: VectorIdx) -> &VTimestamp {
self.as_slice().get(index.to_u32() as usize).unwrap_or(&0)
}
}
/// Test vector clock ordering operations
/// data-race detection is tested in the external
/// test suite
#[cfg(test)]
mod tests {
use super::{VClock, VTimestamp, VectorIdx};
use std::cmp::Ordering;
#[test]
fn test_equal() {
let mut c1 = VClock::default();
let mut c2 = VClock::default();
assert_eq!(c1, c2);
c1.increment_index(VectorIdx(5));
assert_ne!(c1, c2);
c2.increment_index(VectorIdx(53));
assert_ne!(c1, c2);
c1.increment_index(VectorIdx(53));
assert_ne!(c1, c2);
c2.increment_index(VectorIdx(5));
assert_eq!(c1, c2);
}
#[test]
fn test_partial_order() {
// Small test
assert_order(&[1], &[1], Some(Ordering::Equal));
assert_order(&[1], &[2], Some(Ordering::Less));
assert_order(&[2], &[1], Some(Ordering::Greater));
assert_order(&[1], &[1, 2], Some(Ordering::Less));
assert_order(&[2], &[1, 2], None);
// Misc tests
assert_order(&[400], &[0, 1], None);
// Large test
assert_order(
&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0],
Some(Ordering::Equal),
);
assert_order(
&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 0],
Some(Ordering::Less),
);
assert_order(
&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11],
&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0],
Some(Ordering::Greater),
);
assert_order(
&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11],
&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 0],
None,
);
assert_order(
&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9],
&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0],
Some(Ordering::Less),
);
assert_order(
&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9],
&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 0],
Some(Ordering::Less),
);
}
fn from_slice(mut slice: &[VTimestamp]) -> VClock {
while let Some(0) = slice.last() {
slice = &slice[..slice.len() - 1]
}
VClock(smallvec::SmallVec::from_slice(slice))
}
fn assert_order(l: &[VTimestamp], r: &[VTimestamp], o: Option<Ordering>) {
let l = from_slice(l);
let r = from_slice(r);
//Test partial_cmp
let compare = l.partial_cmp(&r);
assert_eq!(compare, o, "Invalid comparison\n l: {:?}\n r: {:?}", l, r);
let alt_compare = r.partial_cmp(&l);
assert_eq!(
alt_compare,
o.map(Ordering::reverse),
"Invalid alt comparison\n l: {:?}\n r: {:?}",
l,
r
);
//Test operators with faster implementations
assert_eq!(
matches!(compare, Some(Ordering::Less)),
l < r,
"Invalid (<):\n l: {:?}\n r: {:?}",
l,
r
);
assert_eq!(
matches!(compare, Some(Ordering::Less) | Some(Ordering::Equal)),
l <= r,
"Invalid (<=):\n l: {:?}\n r: {:?}",
l,
r
);
assert_eq!(
matches!(compare, Some(Ordering::Greater)),
l > r,
"Invalid (>):\n l: {:?}\n r: {:?}",
l,
r
);
assert_eq!(
matches!(compare, Some(Ordering::Greater) | Some(Ordering::Equal)),
l >= r,
"Invalid (>=):\n l: {:?}\n r: {:?}",
l,
r
);
assert_eq!(
matches!(alt_compare, Some(Ordering::Less)),
r < l,
"Invalid alt (<):\n l: {:?}\n r: {:?}",
l,
r
);
assert_eq!(
matches!(alt_compare, Some(Ordering::Less) | Some(Ordering::Equal)),
r <= l,
"Invalid alt (<=):\n l: {:?}\n r: {:?}",
l,
r
);
assert_eq!(
matches!(alt_compare, Some(Ordering::Greater)),
r > l,
"Invalid alt (>):\n l: {:?}\n r: {:?}",
l,
r
);
assert_eq!(
matches!(alt_compare, Some(Ordering::Greater) | Some(Ordering::Equal)),
r >= l,
"Invalid alt (>=):\n l: {:?}\n r: {:?}",
l,
r
);
}
}

View File

@ -0,0 +1,630 @@
//! Implementation of C++11-consistent weak memory emulation using store buffers
//! based on Dynamic Race Detection for C++ ("the paper"):
//! <https://www.doc.ic.ac.uk/~afd/homepages/papers/pdfs/2017/POPL.pdf>
//!
//! This implementation will never generate weak memory behaviours forbidden by the C++11 model,
//! but it is incapable of producing all possible weak behaviours allowed by the model. There are
//! certain weak behaviours observable on real hardware but not while using this.
//!
//! Note that this implementation does not fully take into account of C++20's memory model revision to SC accesses
//! and fences introduced by P0668 (<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0668r5.html>).
//! This implementation is not fully correct under the revised C++20 model and may generate behaviours C++20
//! disallows (<https://github.com/rust-lang/miri/issues/2301>).
//!
//! A modification is made to the paper's model to partially address C++20 changes.
//! Specifically, if an SC load reads from an atomic store of any ordering, then a later SC load cannot read from
//! an earlier store in the location's modification order. This is to prevent creating a backwards S edge from the second
//! load to the first, as a result of C++20's coherence-ordered before rules.
//!
//! Rust follows the C++20 memory model (except for the Consume ordering and some operations not performable through C++'s
//! std::atomic<T> API). It is therefore possible for this implementation to generate behaviours never observable when the
//! same program is compiled and run natively. Unfortunately, no literature exists at the time of writing which proposes
//! an implementable and C++20-compatible relaxed memory model that supports all atomic operation existing in Rust. The closest one is
//! A Promising Semantics for Relaxed-Memory Concurrency by Jeehoon Kang et al. (<https://www.cs.tau.ac.il/~orilahav/papers/popl17.pdf>)
//! However, this model lacks SC accesses and is therefore unusable by Miri (SC accesses are everywhere in library code).
//!
//! If you find anything that proposes a relaxed memory model that is C++20-consistent, supports all orderings Rust's atomic accesses
//! and fences accept, and is implementable (with operational semanitcs), please open a GitHub issue!
//!
//! One characteristic of this implementation, in contrast to some other notable operational models such as ones proposed in
//! Taming Release-Acquire Consistency by Ori Lahav et al. (<https://plv.mpi-sws.org/sra/paper.pdf>) or Promising Semantics noted above,
//! is that this implementation does not require each thread to hold an isolated view of the entire memory. Here, store buffers are per-location
//! and shared across all threads. This is more memory efficient but does require store elements (representing writes to a location) to record
//! information about reads, whereas in the other two models it is the other way round: reads points to the write it got its value from.
//! Additionally, writes in our implementation do not have globally unique timestamps attached. In the other two models this timestamp is
//! used to make sure a value in a thread's view is not overwritten by a write that occured earlier than the one in the existing view.
//! In our implementation, this is detected using read information attached to store elements, as there is no data strucutre representing reads.
//!
//! The C++ memory model is built around the notion of an 'atomic object', so it would be natural
//! to attach store buffers to atomic objects. However, Rust follows LLVM in that it only has
//! 'atomic accesses'. Therefore Miri cannot know when and where atomic 'objects' are being
//! created or destroyed, to manage its store buffers. Instead, we hence lazily create an
//! atomic object on the first atomic access to a given region, and we destroy that object
//! on the next non-atomic or imperfectly overlapping atomic access to that region.
//! These lazy (de)allocations happen in memory_accessed() on non-atomic accesses, and
//! get_or_create_store_buffer() on atomic accesses. This mostly works well, but it does
//! lead to some issues (<https://github.com/rust-lang/miri/issues/2164>).
//!
//! One consequence of this difference is that safe/sound Rust allows for more operations on atomic locations
//! than the C++20 atomic API was intended to allow, such as non-atomically accessing
//! a previously atomically accessed location, or accessing previously atomically accessed locations with a differently sized operation
//! (such as accessing the top 16 bits of an AtomicU32). These senarios are generally undiscussed in formalisations of C++ memory model.
//! In Rust, these operations can only be done through a `&mut AtomicFoo` reference or one derived from it, therefore these operations
//! can only happen after all previous accesses on the same locations. This implementation is adapted to allow these operations.
//! A mixed atomicity read that races with writes, or a write that races with reads or writes will still cause UBs to be thrown.
//! Mixed size atomic accesses must not race with any other atomic access, whether read or write, or a UB will be thrown.
//! You can refer to test cases in weak_memory/extra_cpp.rs and weak_memory/extra_cpp_unsafe.rs for examples of these operations.
// Our and the author's own implementation (tsan11) of the paper have some deviations from the provided operational semantics in §5.3:
// 1. In the operational semantics, store elements keep a copy of the atomic object's vector clock (AtomicCellClocks::sync_vector in miri),
// but this is not used anywhere so it's omitted here.
//
// 2. In the operational semantics, each store element keeps the timestamp of a thread when it loads from the store.
// If the same thread loads from the same store element multiple times, then the timestamps at all loads are saved in a list of load elements.
// This is not necessary as later loads by the same thread will always have greater timetstamp values, so we only need to record the timestamp of the first
// load by each thread. This optimisation is done in tsan11
// (https://github.com/ChrisLidbury/tsan11/blob/ecbd6b81e9b9454e01cba78eb9d88684168132c7/lib/tsan/rtl/tsan_relaxed.h#L35-L37)
// and here.
//
// 3. §4.5 of the paper wants an SC store to mark all existing stores in the buffer that happens before it
// as SC. This is not done in the operational semantics but implemented correctly in tsan11
// (https://github.com/ChrisLidbury/tsan11/blob/ecbd6b81e9b9454e01cba78eb9d88684168132c7/lib/tsan/rtl/tsan_relaxed.cc#L160-L167)
// and here.
//
// 4. W_SC ; R_SC case requires the SC load to ignore all but last store maked SC (stores not marked SC are not
// affected). But this rule is applied to all loads in ReadsFromSet from the paper (last two lines of code), not just SC load.
// This is implemented correctly in tsan11
// (https://github.com/ChrisLidbury/tsan11/blob/ecbd6b81e9b9454e01cba78eb9d88684168132c7/lib/tsan/rtl/tsan_relaxed.cc#L295)
// and here.
use std::{
cell::{Ref, RefCell},
collections::VecDeque,
};
use rustc_const_eval::interpret::{alloc_range, AllocRange, InterpResult, MPlaceTy, Scalar};
use rustc_data_structures::fx::FxHashMap;
use crate::*;
use super::{
data_race::{GlobalState as DataRaceState, ThreadClockSet},
range_object_map::{AccessType, RangeObjectMap},
vector_clock::{VClock, VTimestamp, VectorIdx},
};
pub type AllocExtra = StoreBufferAlloc;
// Each store buffer must be bounded otherwise it will grow indefinitely.
// However, bounding the store buffer means restricting the amount of weak
// behaviours observable. The author picked 128 as a good tradeoff
// so we follow them here.
const STORE_BUFFER_LIMIT: usize = 128;
#[derive(Debug, Clone)]
pub struct StoreBufferAlloc {
/// Store buffer of each atomic object in this allocation
// Behind a RefCell because we need to allocate/remove on read access
store_buffers: RefCell<RangeObjectMap<StoreBuffer>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct StoreBuffer {
// Stores to this location in modification order
buffer: VecDeque<StoreElement>,
}
/// Whether a load returned the latest value or not.
#[derive(PartialEq, Eq)]
enum LoadRecency {
Latest,
Outdated,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct StoreElement {
/// The identifier of the vector index, corresponding to a thread
/// that performed the store.
store_index: VectorIdx,
/// Whether this store is SC.
is_seqcst: bool,
/// The timestamp of the storing thread when it performed the store
timestamp: VTimestamp,
/// The value of this store
// FIXME: this means the store must be fully initialized;
// we will have to change this if we want to support atomics on
// (partially) uninitialized data.
val: Scalar<Provenance>,
/// Metadata about loads from this store element,
/// behind a RefCell to keep load op take &self
load_info: RefCell<LoadInfo>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
struct LoadInfo {
/// Timestamp of first loads from this store element by each thread
timestamps: FxHashMap<VectorIdx, VTimestamp>,
/// Whether this store element has been read by an SC load
sc_loaded: bool,
}
impl StoreBufferAlloc {
pub fn new_allocation() -> Self {
Self { store_buffers: RefCell::new(RangeObjectMap::new()) }
}
/// Checks if the range imperfectly overlaps with existing buffers
/// Used to determine if mixed-size atomic accesses
fn is_overlapping(&self, range: AllocRange) -> bool {
let buffers = self.store_buffers.borrow();
let access_type = buffers.access_type(range);
matches!(access_type, AccessType::ImperfectlyOverlapping(_))
}
/// When a non-atomic access happens on a location that has been atomically accessed
/// before without data race, we can determine that the non-atomic access fully happens
/// after all the prior atomic accesses so the location no longer needs to exhibit
/// any weak memory behaviours until further atomic accesses.
pub fn memory_accessed(&self, range: AllocRange, global: &DataRaceState) {
if !global.ongoing_action_data_race_free() {
let mut buffers = self.store_buffers.borrow_mut();
let access_type = buffers.access_type(range);
match access_type {
AccessType::PerfectlyOverlapping(pos) => {
buffers.remove_from_pos(pos);
}
AccessType::ImperfectlyOverlapping(pos_range) => {
buffers.remove_pos_range(pos_range);
}
AccessType::Empty(_) => {
// The range had no weak behaivours attached, do nothing
}
}
}
}
/// Gets a store buffer associated with an atomic object in this allocation,
/// or creates one with the specified initial value if no atomic object exists yet.
fn get_or_create_store_buffer<'tcx>(
&self,
range: AllocRange,
init: Scalar<Provenance>,
) -> InterpResult<'tcx, Ref<'_, StoreBuffer>> {
let access_type = self.store_buffers.borrow().access_type(range);
let pos = match access_type {
AccessType::PerfectlyOverlapping(pos) => pos,
AccessType::Empty(pos) => {
let mut buffers = self.store_buffers.borrow_mut();
buffers.insert_at_pos(pos, range, StoreBuffer::new(init));
pos
}
AccessType::ImperfectlyOverlapping(pos_range) => {
// Once we reach here we would've already checked that this access is not racy
let mut buffers = self.store_buffers.borrow_mut();
buffers.remove_pos_range(pos_range.clone());
buffers.insert_at_pos(pos_range.start, range, StoreBuffer::new(init));
pos_range.start
}
};
Ok(Ref::map(self.store_buffers.borrow(), |buffer| &buffer[pos]))
}
/// Gets a mutable store buffer associated with an atomic object in this allocation
fn get_or_create_store_buffer_mut<'tcx>(
&mut self,
range: AllocRange,
init: Scalar<Provenance>,
) -> InterpResult<'tcx, &mut StoreBuffer> {
let buffers = self.store_buffers.get_mut();
let access_type = buffers.access_type(range);
let pos = match access_type {
AccessType::PerfectlyOverlapping(pos) => pos,
AccessType::Empty(pos) => {
buffers.insert_at_pos(pos, range, StoreBuffer::new(init));
pos
}
AccessType::ImperfectlyOverlapping(pos_range) => {
buffers.remove_pos_range(pos_range.clone());
buffers.insert_at_pos(pos_range.start, range, StoreBuffer::new(init));
pos_range.start
}
};
Ok(&mut buffers[pos])
}
}
impl<'mir, 'tcx: 'mir> StoreBuffer {
fn new(init: Scalar<Provenance>) -> Self {
let mut buffer = VecDeque::new();
buffer.reserve(STORE_BUFFER_LIMIT);
let mut ret = Self { buffer };
let store_elem = StoreElement {
// The thread index and timestamp of the initialisation write
// are never meaningfully used, so it's fine to leave them as 0
store_index: VectorIdx::from(0),
timestamp: 0,
val: init,
is_seqcst: false,
load_info: RefCell::new(LoadInfo::default()),
};
ret.buffer.push_back(store_elem);
ret
}
/// Reads from the last store in modification order
fn read_from_last_store(
&self,
global: &DataRaceState,
thread_mgr: &ThreadManager<'_, '_>,
is_seqcst: bool,
) {
let store_elem = self.buffer.back();
if let Some(store_elem) = store_elem {
let (index, clocks) = global.current_thread_state(thread_mgr);
store_elem.load_impl(index, &clocks, is_seqcst);
}
}
fn buffered_read(
&self,
global: &DataRaceState,
thread_mgr: &ThreadManager<'_, '_>,
is_seqcst: bool,
rng: &mut (impl rand::Rng + ?Sized),
validate: impl FnOnce() -> InterpResult<'tcx>,
) -> InterpResult<'tcx, (Scalar<Provenance>, LoadRecency)> {
// Having a live borrow to store_buffer while calling validate_atomic_load is fine
// because the race detector doesn't touch store_buffer
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);
// Load from a valid entry in the store buffer
self.fetch_store(is_seqcst, &clocks, &mut *rng)
};
// Unlike in buffered_atomic_write, thread clock updates have to be done
// after we've picked a store element from the store buffer, as presented
// in ATOMIC LOAD rule of the paper. This is because fetch_store
// requires access to ThreadClockSet.clock, which is updated by the race detector
validate()?;
let (index, clocks) = global.current_thread_state(thread_mgr);
let loaded = store_elem.load_impl(index, &clocks, is_seqcst);
Ok((loaded, recency))
}
fn buffered_write(
&mut self,
val: Scalar<Provenance>,
global: &DataRaceState,
thread_mgr: &ThreadManager<'_, '_>,
is_seqcst: bool,
) -> InterpResult<'tcx> {
let (index, clocks) = global.current_thread_state(thread_mgr);
self.store_impl(val, index, &clocks.clock, is_seqcst);
Ok(())
}
#[allow(clippy::if_same_then_else, clippy::needless_bool)]
/// Selects a valid store element in the buffer.
fn fetch_store<R: rand::Rng + ?Sized>(
&self,
is_seqcst: bool,
clocks: &ThreadClockSet,
rng: &mut R,
) -> (&StoreElement, LoadRecency) {
use rand::seq::IteratorRandom;
let mut found_sc = false;
// FIXME: we want an inclusive take_while (stops after a false predicate, but
// includes the element that gave the false), but such function doesn't yet
// exist in the standard libary https://github.com/rust-lang/rust/issues/62208
// so we have to hack around it with keep_searching
let mut keep_searching = true;
let candidates = self
.buffer
.iter()
.rev()
.take_while(move |&store_elem| {
if !keep_searching {
return false;
}
keep_searching = if store_elem.timestamp <= clocks.clock[store_elem.store_index] {
// CoWR: if a store happens-before the current load,
// then we can't read-from anything earlier in modification order.
// C++20 §6.9.2.2 [intro.races] paragraph 18
false
} else if store_elem.load_info.borrow().timestamps.iter().any(
|(&load_index, &load_timestamp)| load_timestamp <= clocks.clock[load_index],
) {
// CoRR: if there was a load from this store which happened-before the current load,
// then we cannot read-from anything earlier in modification order.
// C++20 §6.9.2.2 [intro.races] paragraph 16
false
} else if store_elem.timestamp <= clocks.fence_seqcst[store_elem.store_index] {
// The current load, which may be sequenced-after an SC fence, cannot read-before
// the last store sequenced-before an SC fence in another thread.
// C++17 §32.4 [atomics.order] paragraph 6
false
} else if store_elem.timestamp <= clocks.write_seqcst[store_elem.store_index]
&& store_elem.is_seqcst
{
// The current non-SC load, which may be sequenced-after an SC fence,
// cannot read-before the last SC store executed before the fence.
// C++17 §32.4 [atomics.order] paragraph 4
false
} else if is_seqcst
&& store_elem.timestamp <= clocks.read_seqcst[store_elem.store_index]
{
// The current SC load cannot read-before the last store sequenced-before
// the last SC fence.
// C++17 §32.4 [atomics.order] paragraph 5
false
} else if is_seqcst && store_elem.load_info.borrow().sc_loaded {
// The current SC load cannot read-before a store that an earlier SC load has observed.
// See https://github.com/rust-lang/miri/issues/2301#issuecomment-1222720427
// Consequences of C++20 §31.4 [atomics.order] paragraph 3.1, 3.3 (coherence-ordered before)
// and 4.1 (coherence-ordered before between SC makes global total order S)
false
} else {
true
};
true
})
.filter(|&store_elem| {
if is_seqcst && store_elem.is_seqcst {
// An SC load needs to ignore all but last store maked SC (stores not marked SC are not
// affected)
let include = !found_sc;
found_sc = true;
include
} else {
true
}
});
let chosen = candidates.choose(rng).expect("store buffer cannot be empty");
if std::ptr::eq(chosen, self.buffer.back().expect("store buffer cannot be empty")) {
(chosen, LoadRecency::Latest)
} else {
(chosen, LoadRecency::Outdated)
}
}
/// ATOMIC STORE IMPL in the paper (except we don't need the location's vector clock)
fn store_impl(
&mut self,
val: Scalar<Provenance>,
index: VectorIdx,
thread_clock: &VClock,
is_seqcst: bool,
) {
let store_elem = StoreElement {
store_index: index,
timestamp: thread_clock[index],
// In the language provided in the paper, an atomic store takes the value from a
// non-atomic memory location.
// But we already have the immediate value here so we don't need to do the memory
// access
val,
is_seqcst,
load_info: RefCell::new(LoadInfo::default()),
};
self.buffer.push_back(store_elem);
if self.buffer.len() > STORE_BUFFER_LIMIT {
self.buffer.pop_front();
}
if is_seqcst {
// Every store that happens before this needs to be marked as SC
// so that in a later SC load, only the last SC store (i.e. this one) or stores that
// aren't ordered by hb with the last SC is picked.
self.buffer.iter_mut().rev().for_each(|elem| {
if elem.timestamp <= thread_clock[elem.store_index] {
elem.is_seqcst = true;
}
})
}
}
}
impl StoreElement {
/// ATOMIC LOAD IMPL in the paper
/// Unlike the operational semantics in the paper, we don't need to keep track
/// of the thread timestamp for every single load. Keeping track of the first (smallest)
/// timestamp of each thread that has loaded from a store is sufficient: if the earliest
/// load of another thread happens before the current one, then we must stop searching the store
/// buffer regardless of subsequent loads by the same thread; if the earliest load of another
/// thread doesn't happen before the current one, then no subsequent load by the other thread
/// can happen before the current one.
fn load_impl(
&self,
index: VectorIdx,
clocks: &ThreadClockSet,
is_seqcst: bool,
) -> Scalar<Provenance> {
let mut load_info = self.load_info.borrow_mut();
load_info.sc_loaded |= is_seqcst;
let _ = load_info.timestamps.try_insert(index, clocks.clock[index]);
self.val
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
crate::MiriInterpCxExt<'mir, 'tcx>
{
// If weak memory emulation is enabled, check if this atomic op imperfectly overlaps with a previous
// atomic read or write. If it does, then we require it to be ordered (non-racy) with all previous atomic
// accesses on all the bytes in range
fn validate_overlapping_atomic(
&self,
place: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_ref();
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?;
if let crate::AllocExtra {
weak_memory: Some(alloc_buffers),
data_race: Some(alloc_clocks),
..
} = this.get_alloc_extra(alloc_id)?
{
let range = alloc_range(base_offset, place.layout.size);
if alloc_buffers.is_overlapping(range)
&& !alloc_clocks.race_free_with_atomic(
range,
this.machine.data_race.as_ref().unwrap(),
&this.machine.threads,
)
{
throw_unsup_format!(
"racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation"
);
}
}
Ok(())
}
fn buffered_atomic_rmw(
&mut self,
new_val: Scalar<Provenance>,
place: &MPlaceTy<'tcx, Provenance>,
atomic: AtomicRwOrd,
init: Scalar<Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?;
if let (
crate::AllocExtra { weak_memory: Some(alloc_buffers), .. },
crate::MiriMachine { data_race: Some(global), threads, .. },
) = this.get_alloc_extra_mut(alloc_id)?
{
if atomic == AtomicRwOrd::SeqCst {
global.sc_read(threads);
global.sc_write(threads);
}
let range = alloc_range(base_offset, place.layout.size);
let buffer = alloc_buffers.get_or_create_store_buffer_mut(range, init)?;
buffer.read_from_last_store(global, threads, atomic == AtomicRwOrd::SeqCst);
buffer.buffered_write(new_val, global, threads, atomic == AtomicRwOrd::SeqCst)?;
}
Ok(())
}
fn buffered_atomic_read(
&self,
place: &MPlaceTy<'tcx, Provenance>,
atomic: AtomicReadOrd,
latest_in_mo: Scalar<Provenance>,
validate: impl FnOnce() -> InterpResult<'tcx>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_ref();
if let Some(global) = &this.machine.data_race {
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?;
if let Some(alloc_buffers) = this.get_alloc_extra(alloc_id)?.weak_memory.as_ref() {
if atomic == AtomicReadOrd::SeqCst {
global.sc_read(&this.machine.threads);
}
let mut rng = this.machine.rng.borrow_mut();
let buffer = alloc_buffers.get_or_create_store_buffer(
alloc_range(base_offset, place.layout.size),
latest_in_mo,
)?;
let (loaded, recency) = buffer.buffered_read(
global,
&this.machine.threads,
atomic == AtomicReadOrd::SeqCst,
&mut *rng,
validate,
)?;
if global.track_outdated_loads && recency == LoadRecency::Outdated {
this.emit_diagnostic(NonHaltingDiagnostic::WeakMemoryOutdatedLoad);
}
return Ok(loaded);
}
}
// Race detector or weak memory disabled, simply read the latest value
validate()?;
Ok(latest_in_mo)
}
fn buffered_atomic_write(
&mut self,
val: Scalar<Provenance>,
dest: &MPlaceTy<'tcx, Provenance>,
atomic: AtomicWriteOrd,
init: Scalar<Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(dest.ptr)?;
if let (
crate::AllocExtra { weak_memory: Some(alloc_buffers), .. },
crate::MiriMachine { data_race: Some(global), threads, .. },
) = this.get_alloc_extra_mut(alloc_id)?
{
if atomic == AtomicWriteOrd::SeqCst {
global.sc_write(threads);
}
// UGLY HACK: in write_scalar_atomic() we don't know the value before our write,
// so init == val always. If the buffer is fresh then we would've duplicated an entry,
// so we need to remove it.
// See https://github.com/rust-lang/miri/issues/2164
let was_empty = matches!(
alloc_buffers
.store_buffers
.borrow()
.access_type(alloc_range(base_offset, dest.layout.size)),
AccessType::Empty(_)
);
let buffer = alloc_buffers
.get_or_create_store_buffer_mut(alloc_range(base_offset, dest.layout.size), init)?;
if was_empty {
buffer.buffer.pop_front();
}
buffer.buffered_write(val, global, threads, atomic == AtomicWriteOrd::SeqCst)?;
}
// Caller should've written to dest with the vanilla scalar write, we do nothing here
Ok(())
}
/// Caller should never need to consult the store buffer for the latest value.
/// This function is used exclusively for failed atomic_compare_exchange_scalar
/// to perform load_impl on the latest store element
fn perform_read_on_buffered_latest(
&self,
place: &MPlaceTy<'tcx, Provenance>,
atomic: AtomicReadOrd,
init: Scalar<Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_ref();
if let Some(global) = &this.machine.data_race {
if atomic == AtomicReadOrd::SeqCst {
global.sc_read(&this.machine.threads);
}
let size = place.layout.size;
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?;
if let Some(alloc_buffers) = this.get_alloc_extra(alloc_id)?.weak_memory.as_ref() {
let buffer = alloc_buffers
.get_or_create_store_buffer(alloc_range(base_offset, size), init)?;
buffer.read_from_last_store(
global,
&this.machine.threads,
atomic == AtomicReadOrd::SeqCst,
);
}
}
Ok(())
}
}

View File

@ -0,0 +1,500 @@
use std::fmt;
use std::num::NonZeroU64;
use log::trace;
use rustc_span::{source_map::DUMMY_SP, SpanData, Symbol};
use rustc_target::abi::{Align, Size};
use crate::stacked_borrows::{diagnostics::TagHistory, AccessKind};
use crate::*;
/// Details of premature program termination.
pub enum TerminationInfo {
Exit(i64),
Abort(String),
UnsupportedInIsolation(String),
StackedBorrowsUb {
msg: String,
help: Option<String>,
history: Option<TagHistory>,
},
Int2PtrWithStrictProvenance,
Deadlock,
MultipleSymbolDefinitions {
link_name: Symbol,
first: SpanData,
first_crate: Symbol,
second: SpanData,
second_crate: Symbol,
},
SymbolShimClashing {
link_name: Symbol,
span: SpanData,
},
}
impl fmt::Display for TerminationInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use TerminationInfo::*;
match self {
Exit(code) => write!(f, "the evaluated program completed with exit code {code}"),
Abort(msg) => write!(f, "{msg}"),
UnsupportedInIsolation(msg) => write!(f, "{msg}"),
Int2PtrWithStrictProvenance =>
write!(
f,
"integer-to-pointer casts and `ptr::from_exposed_addr` are not supported with `-Zmiri-strict-provenance`"
),
StackedBorrowsUb { msg, .. } => write!(f, "{msg}"),
Deadlock => write!(f, "the evaluated program deadlocked"),
MultipleSymbolDefinitions { link_name, .. } =>
write!(f, "multiple definitions of symbol `{link_name}`"),
SymbolShimClashing { link_name, .. } =>
write!(f, "found `{link_name}` symbol definition that clashes with a built-in shim",),
}
}
}
impl MachineStopType for TerminationInfo {}
/// Miri specific diagnostics
pub enum NonHaltingDiagnostic {
CreatedPointerTag(NonZeroU64, Option<(AllocId, AllocRange)>),
/// This `Item` was popped from the borrow stack, either due to an access with the given tag or
/// a deallocation when the second argument is `None`.
PoppedPointerTag(Item, Option<(ProvenanceExtra, AccessKind)>),
CreatedCallId(CallId),
CreatedAlloc(AllocId, Size, Align, MemoryKind<MiriMemoryKind>),
FreedAlloc(AllocId),
RejectedIsolatedOp(String),
ProgressReport {
block_count: u64, // how many basic blocks have been run so far
},
Int2Ptr {
details: bool,
},
WeakMemoryOutdatedLoad,
}
/// Level of Miri specific diagnostics
enum DiagLevel {
Error,
Warning,
Note,
}
/// Attempts to prune a stacktrace to omit the Rust runtime, and returns a bool indicating if any
/// frames were pruned. If the stacktrace does not have any local frames, we conclude that it must
/// be pointing to a problem in the Rust runtime itself, and do not prune it at all.
fn prune_stacktrace<'tcx>(
mut stacktrace: Vec<FrameInfo<'tcx>>,
machine: &MiriMachine<'_, 'tcx>,
) -> (Vec<FrameInfo<'tcx>>, bool) {
match machine.backtrace_style {
BacktraceStyle::Off => {
// Remove all frames marked with `caller_location` -- that attribute indicates we
// usually want to point at the caller, not them.
stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(machine.tcx));
// Retain one frame so that we can print a span for the error itself
stacktrace.truncate(1);
(stacktrace, false)
}
BacktraceStyle::Short => {
let original_len = stacktrace.len();
// Only prune frames if there is at least one local frame. This check ensures that if
// we get a backtrace that never makes it to the user code because it has detected a
// bug in the Rust runtime, we don't prune away every frame.
let has_local_frame = stacktrace.iter().any(|frame| machine.is_local(frame));
if has_local_frame {
// Remove all frames marked with `caller_location` -- that attribute indicates we
// usually want to point at the caller, not them.
stacktrace
.retain(|frame| !frame.instance.def.requires_caller_location(machine.tcx));
// This is part of the logic that `std` uses to select the relevant part of a
// backtrace. But here, we only look for __rust_begin_short_backtrace, not
// __rust_end_short_backtrace because the end symbol comes from a call to the default
// panic handler.
stacktrace = stacktrace
.into_iter()
.take_while(|frame| {
let def_id = frame.instance.def_id();
let path = machine.tcx.def_path_str(def_id);
!path.contains("__rust_begin_short_backtrace")
})
.collect::<Vec<_>>();
// After we prune frames from the bottom, there are a few left that are part of the
// Rust runtime. So we remove frames until we get to a local symbol, which should be
// main or a test.
// This len check ensures that we don't somehow remove every frame, as doing so breaks
// the primary error message.
while stacktrace.len() > 1
&& stacktrace.last().map_or(false, |frame| !machine.is_local(frame))
{
stacktrace.pop();
}
}
let was_pruned = stacktrace.len() != original_len;
(stacktrace, was_pruned)
}
BacktraceStyle::Full => (stacktrace, false),
}
}
/// Emit a custom diagnostic without going through the miri-engine machinery
pub fn report_error<'tcx, 'mir>(
ecx: &InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
e: InterpErrorInfo<'tcx>,
) -> Option<i64> {
use InterpError::*;
let mut msg = vec![];
let (title, helps) = match &e.kind() {
MachineStop(info) => {
let info = info.downcast_ref::<TerminationInfo>().expect("invalid MachineStop payload");
use TerminationInfo::*;
let title = match info {
Exit(code) => return Some(*code),
Abort(_) => Some("abnormal termination"),
UnsupportedInIsolation(_) | Int2PtrWithStrictProvenance =>
Some("unsupported operation"),
StackedBorrowsUb { .. } => Some("Undefined Behavior"),
Deadlock => Some("deadlock"),
MultipleSymbolDefinitions { .. } | SymbolShimClashing { .. } => None,
};
#[rustfmt::skip]
let helps = match info {
UnsupportedInIsolation(_) =>
vec![
(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")),
],
StackedBorrowsUb { help, history, .. } => {
let url = "https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md";
msg.extend(help.clone());
let mut helps = vec![
(None, format!("this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental")),
(None, format!("see {url} for further information")),
];
if let Some(TagHistory {created, invalidated, protected}) = history.clone() {
helps.push((Some(created.1), created.0));
if let Some((msg, span)) = invalidated {
helps.push((Some(span), msg));
}
if let Some((protector_msg, protector_span)) = protected {
helps.push((Some(protector_span), protector_msg));
}
}
helps
}
MultipleSymbolDefinitions { first, first_crate, second, second_crate, .. } =>
vec![
(Some(*first), format!("it's first defined here, in crate `{first_crate}`")),
(Some(*second), format!("then it's defined here again, in crate `{second_crate}`")),
],
SymbolShimClashing { link_name, span } =>
vec![(Some(*span), format!("the `{link_name}` symbol is defined here"))],
Int2PtrWithStrictProvenance =>
vec![(None, format!("use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead"))],
_ => vec![],
};
(title, helps)
}
_ => {
#[rustfmt::skip]
let title = match e.kind() {
Unsupported(_) =>
"unsupported operation",
UndefinedBehavior(_) =>
"Undefined Behavior",
ResourceExhaustion(_) =>
"resource exhaustion",
InvalidProgram(
InvalidProgramInfo::AlreadyReported(_) |
InvalidProgramInfo::Layout(..) |
InvalidProgramInfo::ReferencedConstant
) =>
"post-monomorphization error",
kind =>
bug!("This error should be impossible in Miri: {kind:?}"),
};
#[rustfmt::skip]
let helps = match e.kind() {
Unsupported(
UnsupportedOpInfo::ThreadLocalStatic(_) |
UnsupportedOpInfo::ReadExternStatic(_) |
UnsupportedOpInfo::PartialPointerOverwrite(_) | // we make memory uninit instead
UnsupportedOpInfo::ReadPointerAsBytes
) =>
panic!("Error should never be raised by Miri: {kind:?}", kind = e.kind()),
Unsupported(
UnsupportedOpInfo::Unsupported(_) |
UnsupportedOpInfo::PartialPointerCopy(_)
) =>
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"))],
UndefinedBehavior(UndefinedBehaviorInfo::AlignmentCheckFailed { .. })
if ecx.machine.check_alignment == AlignmentCheck::Symbolic
=>
vec![
(None, format!("this usually indicates that your program performed an invalid operation and caused Undefined Behavior")),
(None, format!("but due to `-Zmiri-symbolic-alignment-check`, alignment errors can also be false positives")),
],
UndefinedBehavior(_) =>
vec![
(None, format!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior")),
(None, format!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information")),
],
InvalidProgram(_) | ResourceExhaustion(_) | MachineStop(_) =>
vec![],
};
(Some(title), helps)
}
};
let stacktrace = ecx.generate_stacktrace();
let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine);
e.print_backtrace();
msg.insert(0, e.to_string());
report_msg(
DiagLevel::Error,
&if let Some(title) = title { format!("{}: {}", title, msg[0]) } else { msg[0].clone() },
msg,
vec![],
helps,
&stacktrace,
&ecx.machine,
);
// Include a note like `std` does when we omit frames from a backtrace
if was_pruned {
ecx.tcx.sess.diagnostic().note_without_error(
"some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace",
);
}
// Debug-dump all locals.
for (i, frame) in ecx.active_thread_stack().iter().enumerate() {
trace!("-------------------");
trace!("Frame {}", i);
trace!(" return: {:?}", *frame.return_place);
for (i, local) in frame.locals.iter().enumerate() {
trace!(" local {}: {:?}", i, local.value);
}
}
// Extra output to help debug specific issues.
match e.kind() {
UndefinedBehavior(UndefinedBehaviorInfo::InvalidUninitBytes(Some((alloc_id, access)))) => {
eprintln!(
"Uninitialized memory occurred at {alloc_id:?}{range:?}, in this allocation:",
range = access.uninit,
);
eprintln!("{:?}", ecx.dump_alloc(*alloc_id));
}
_ => {}
}
None
}
/// Report an error or note (depending on the `error` argument) with the given stacktrace.
/// Also emits a full stacktrace of the interpreter stack.
/// We want to present a multi-line span message for some errors. Diagnostics do not support this
/// directly, so we pass the lines as a `Vec<String>` and display each line after the first with an
/// additional `span_label` or `note` call.
fn report_msg<'tcx>(
diag_level: DiagLevel,
title: &str,
span_msg: Vec<String>,
notes: Vec<(Option<SpanData>, String)>,
helps: Vec<(Option<SpanData>, String)>,
stacktrace: &[FrameInfo<'tcx>],
machine: &MiriMachine<'_, 'tcx>,
) {
let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span);
let sess = machine.tcx.sess;
let mut err = match diag_level {
DiagLevel::Error => sess.struct_span_err(span, title).forget_guarantee(),
DiagLevel::Warning => sess.struct_span_warn(span, title),
DiagLevel::Note => sess.diagnostic().span_note_diag(span, title),
};
// Show main message.
if span != DUMMY_SP {
for line in span_msg {
err.span_label(span, line);
}
} else {
// Make sure we show the message even when it is a dummy span.
for line in span_msg {
err.note(&line);
}
err.note("(no span available)");
}
// Show note and help messages.
for (span_data, note) in &notes {
if let Some(span_data) = span_data {
err.span_note(span_data.span(), note);
} else {
err.note(note);
}
}
for (span_data, help) in &helps {
if let Some(span_data) = span_data {
err.span_help(span_data.span(), help);
} else {
err.help(help);
}
}
if notes.len() + helps.len() > 0 {
// Add visual separator before backtrace.
err.note("BACKTRACE:");
}
// Add backtrace
for (idx, frame_info) in stacktrace.iter().enumerate() {
let is_local = machine.is_local(frame_info);
// No span for non-local frames and the first frame (which is the error site).
if is_local && idx > 0 {
err.span_note(frame_info.span, &frame_info.to_string());
} else {
err.note(&frame_info.to_string());
}
}
err.emit();
}
impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
pub fn emit_diagnostic(&self, e: NonHaltingDiagnostic) {
use NonHaltingDiagnostic::*;
let stacktrace =
MiriInterpCx::generate_stacktrace_from_stack(self.threads.active_thread_stack());
let (stacktrace, _was_pruned) = prune_stacktrace(stacktrace, self);
let (title, diag_level) = match e {
RejectedIsolatedOp(_) => ("operation rejected by isolation", DiagLevel::Warning),
Int2Ptr { .. } => ("integer-to-pointer cast", DiagLevel::Warning),
CreatedPointerTag(..)
| PoppedPointerTag(..)
| CreatedCallId(..)
| CreatedAlloc(..)
| FreedAlloc(..)
| ProgressReport { .. }
| WeakMemoryOutdatedLoad => ("tracking was triggered", DiagLevel::Note),
};
let msg = match e {
CreatedPointerTag(tag, None) => format!("created tag {tag:?}"),
CreatedPointerTag(tag, Some((alloc_id, range))) =>
format!("created tag {tag:?} at {alloc_id:?}{range:?}"),
PoppedPointerTag(item, tag) =>
match tag {
None => format!("popped tracked tag for item {item:?} due to deallocation",),
Some((tag, access)) => {
format!(
"popped tracked tag for item {item:?} due to {access:?} access for {tag:?}",
)
}
},
CreatedCallId(id) => format!("function call with id {id}"),
CreatedAlloc(AllocId(id), size, align, kind) =>
format!(
"created {kind} allocation of {size} bytes (alignment {align} bytes) with id {id}",
size = size.bytes(),
align = align.bytes(),
),
FreedAlloc(AllocId(id)) => format!("freed allocation with id {id}"),
RejectedIsolatedOp(ref op) =>
format!("{op} was made to return an error due to isolation"),
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"),
};
let notes = match e {
ProgressReport { block_count } => {
// It is important that each progress report is slightly different, since
// identical diagnostics are being deduplicated.
vec![(None, format!("so far, {block_count} basic blocks have been executed"))]
}
_ => vec![],
};
let helps = match e {
Int2Ptr { details: true } =>
vec![
(
None,
format!(
"This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`,"
),
),
(
None,
format!("which means that Miri might miss pointer bugs in this program."),
),
(
None,
format!(
"See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation."
),
),
(
None,
format!(
"To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead."
),
),
(
None,
format!(
"You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics."
),
),
(
None,
format!(
"Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning."
),
),
],
_ => vec![],
};
report_msg(diag_level, title, vec![msg], notes, helps, &stacktrace, self);
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn emit_diagnostic(&self, e: NonHaltingDiagnostic) {
let this = self.eval_context_ref();
this.machine.emit_diagnostic(e);
}
/// We had a panic in Miri itself, try to print something useful.
fn handle_ice(&self) {
eprintln!();
eprintln!(
"Miri caused an ICE during evaluation. Here's the interpreter backtrace at the time of the panic:"
);
let this = self.eval_context_ref();
let stacktrace = this.generate_stacktrace();
report_msg(
DiagLevel::Note,
"the place in the program where the ICE was triggered",
vec![],
vec![],
vec![],
&stacktrace,
&this.machine,
);
}
}

514
src/tools/miri/src/eval.rs Normal file
View File

@ -0,0 +1,514 @@
//! Main evaluator loop and setting up the initial stack frame.
use std::ffi::{OsStr, OsString};
use std::iter;
use std::panic::{self, AssertUnwindSafe};
use std::path::PathBuf;
use std::thread;
use log::info;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def_id::DefId;
use rustc_middle::ty::{
self,
layout::{LayoutCx, LayoutOf},
TyCtxt,
};
use rustc_target::spec::abi::Abi;
use rustc_session::config::EntryFnType;
use crate::*;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum AlignmentCheck {
/// Do not check alignment.
None,
/// Check alignment "symbolically", i.e., using only the requested alignment for an allocation and not its real base address.
Symbolic,
/// Check alignment on the actual physical integer address.
Int,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum RejectOpWith {
/// Isolated op is rejected with an abort of the machine.
Abort,
/// If not Abort, miri returns an error for an isolated op.
/// Following options determine if user should be warned about such error.
/// Do not print warning about rejected isolated op.
NoWarning,
/// Print a warning about rejected isolated op, with backtrace.
Warning,
/// Print a warning about rejected isolated op, without backtrace.
WarningWithoutBacktrace,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum IsolatedOp {
/// Reject an op requiring communication with the host. By
/// default, miri rejects the op with an abort. If not, it returns
/// an error code, and prints a warning about it. Warning levels
/// are controlled by `RejectOpWith` enum.
Reject(RejectOpWith),
/// Execute op requiring communication with the host, i.e. disable isolation.
Allow,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum BacktraceStyle {
/// Prints a terser backtrace which ideally only contains relevant information.
Short,
/// Prints a backtrace with all possible information.
Full,
/// Prints only the frame that the error occurs in.
Off,
}
/// Configuration needed to spawn a Miri instance.
#[derive(Clone)]
pub struct MiriConfig {
/// The host environment snapshot to use as basis for what is provided to the interpreted program.
/// (This is still subject to isolation as well as `forwarded_env_vars`.)
pub env: Vec<(OsString, OsString)>,
/// Determine if validity checking is enabled.
pub validate: bool,
/// Determines if Stacked Borrows is enabled.
pub stacked_borrows: bool,
/// Controls alignment checking.
pub check_alignment: AlignmentCheck,
/// Controls function [ABI](Abi) checking.
pub check_abi: bool,
/// Action for an op requiring communication with the host.
pub isolated_op: IsolatedOp,
/// Determines if memory leaks should be ignored.
pub ignore_leaks: bool,
/// Environment variables that should always be forwarded from the host.
pub forwarded_env_vars: Vec<String>,
/// Command-line arguments passed to the interpreted program.
pub args: Vec<String>,
/// The seed to use when non-determinism or randomness are required (e.g. ptr-to-int cast, `getrandom()`).
pub seed: Option<u64>,
/// The stacked borrows pointer ids to report about
pub tracked_pointer_tags: FxHashSet<SbTag>,
/// The stacked borrows call IDs to report about
pub tracked_call_ids: FxHashSet<CallId>,
/// The allocation ids to report about.
pub tracked_alloc_ids: FxHashSet<AllocId>,
/// Determine if data race detection should be enabled
pub data_race_detector: bool,
/// Determine if weak memory emulation should be enabled. Requires data race detection to be enabled
pub weak_memory_emulation: bool,
/// Track when an outdated (weak memory) load happens.
pub track_outdated_loads: bool,
/// Rate of spurious failures for compare_exchange_weak atomic operations,
/// between 0.0 and 1.0, defaulting to 0.8 (80% chance of failure).
pub cmpxchg_weak_failure_rate: f64,
/// If `Some`, enable the `measureme` profiler, writing results to a file
/// with the specified prefix.
pub measureme_out: Option<String>,
/// Panic when unsupported functionality is encountered.
pub panic_on_unsupported: bool,
/// Which style to use for printing backtraces.
pub backtrace_style: BacktraceStyle,
/// Which provenance to use for int2ptr casts
pub provenance_mode: ProvenanceMode,
/// Whether to ignore any output by the program. This is helpful when debugging miri
/// as its messages don't get intermingled with the program messages.
pub mute_stdout_stderr: bool,
/// The probability of the active thread being preempted at the end of each basic block.
pub preemption_rate: f64,
/// Report the current instruction being executed every N basic blocks.
pub report_progress: Option<u32>,
/// Whether Stacked Borrows retagging should recurse into fields of datatypes.
pub retag_fields: bool,
/// 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<PathBuf>,
/// Run a garbage collector for SbTags every N basic blocks.
pub gc_interval: u32,
}
impl Default for MiriConfig {
fn default() -> MiriConfig {
MiriConfig {
env: vec![],
validate: true,
stacked_borrows: true,
check_alignment: AlignmentCheck::Int,
check_abi: true,
isolated_op: IsolatedOp::Reject(RejectOpWith::Abort),
ignore_leaks: false,
forwarded_env_vars: vec![],
args: vec![],
seed: None,
tracked_pointer_tags: FxHashSet::default(),
tracked_call_ids: FxHashSet::default(),
tracked_alloc_ids: FxHashSet::default(),
data_race_detector: true,
weak_memory_emulation: true,
track_outdated_loads: false,
cmpxchg_weak_failure_rate: 0.8, // 80%
measureme_out: None,
panic_on_unsupported: false,
backtrace_style: BacktraceStyle::Short,
provenance_mode: ProvenanceMode::Default,
mute_stdout_stderr: false,
preemption_rate: 0.01, // 1%
report_progress: None,
retag_fields: false,
external_so_file: None,
gc_interval: 10_000,
}
}
}
/// Returns a freshly created `InterpCx`, along with an `MPlaceTy` representing
/// the location where the return value of the `start` function will be
/// written to.
/// Public because this is also used by `priroda`.
pub fn create_ecx<'mir, 'tcx: 'mir>(
tcx: TyCtxt<'tcx>,
entry_id: DefId,
entry_type: EntryFnType,
config: &MiriConfig,
) -> InterpResult<'tcx, (InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, MPlaceTy<'tcx, Provenance>)>
{
let param_env = ty::ParamEnv::reveal_all();
let layout_cx = LayoutCx { tcx, param_env };
let mut ecx = InterpCx::new(
tcx,
rustc_span::source_map::DUMMY_SP,
param_env,
MiriMachine::new(config, layout_cx),
);
// Some parts of initialization require a full `InterpCx`.
MiriMachine::late_init(&mut ecx, config)?;
// Make sure we have MIR. We check MIR for some stable monomorphic function in libcore.
let sentinel = ecx.try_resolve_path(&["core", "ascii", "escape_default"]);
if !matches!(sentinel, Some(s) if tcx.is_mir_available(s.def.def_id())) {
tcx.sess.fatal(
"the current sysroot was built without `-Zalways-encode-mir`, or libcore seems missing. \
Use `cargo miri setup` to prepare a sysroot that is suitable for Miri."
);
}
// Setup first stack frame.
let entry_instance = ty::Instance::mono(tcx, entry_id);
// First argument is constructed later, because it's skipped if the entry function uses #[start].
// Second argument (argc): length of `config.args`.
let argc = Scalar::from_machine_usize(u64::try_from(config.args.len()).unwrap(), &ecx);
// Third argument (`argv`): created from `config.args`.
let argv = {
// Put each argument in memory, collect pointers.
let mut argvs = Vec::<Immediate<Provenance>>::new();
for arg in config.args.iter() {
// Make space for `0` terminator.
let size = u64::try_from(arg.len()).unwrap().checked_add(1).unwrap();
let arg_type = tcx.mk_array(tcx.types.u8, size);
let arg_place =
ecx.allocate(ecx.layout_of(arg_type)?, MiriMemoryKind::Machine.into())?;
ecx.write_os_str_to_c_str(OsStr::new(arg), arg_place.ptr, size)?;
ecx.mark_immutable(&arg_place);
argvs.push(arg_place.to_ref(&ecx));
}
// Make an array with all these pointers, in the Miri memory.
let argvs_layout = ecx.layout_of(
tcx.mk_array(tcx.mk_imm_ptr(tcx.types.u8), u64::try_from(argvs.len()).unwrap()),
)?;
let argvs_place = ecx.allocate(argvs_layout, MiriMemoryKind::Machine.into())?;
for (idx, arg) in argvs.into_iter().enumerate() {
let place = ecx.mplace_field(&argvs_place, idx)?;
ecx.write_immediate(arg, &place.into())?;
}
ecx.mark_immutable(&argvs_place);
// A pointer to that place is the 3rd argument for main.
let argv = argvs_place.to_ref(&ecx);
// Store `argc` and `argv` for macOS `_NSGetArg{c,v}`.
{
let argc_place =
ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
ecx.write_scalar(argc, &argc_place.into())?;
ecx.mark_immutable(&argc_place);
ecx.machine.argc = Some(*argc_place);
let argv_place = ecx.allocate(
ecx.layout_of(tcx.mk_imm_ptr(tcx.types.unit))?,
MiriMemoryKind::Machine.into(),
)?;
ecx.write_immediate(argv, &argv_place.into())?;
ecx.mark_immutable(&argv_place);
ecx.machine.argv = Some(*argv_place);
}
// Store command line as UTF-16 for Windows `GetCommandLineW`.
{
// Construct a command string with all the arguments.
let cmd_utf16: Vec<u16> = args_to_utf16_command_string(config.args.iter());
let cmd_type = tcx.mk_array(tcx.types.u16, u64::try_from(cmd_utf16.len()).unwrap());
let cmd_place =
ecx.allocate(ecx.layout_of(cmd_type)?, MiriMemoryKind::Machine.into())?;
ecx.machine.cmd_line = Some(*cmd_place);
// Store the UTF-16 string. We just allocated so we know the bounds are fine.
for (idx, &c) in cmd_utf16.iter().enumerate() {
let place = ecx.mplace_field(&cmd_place, idx)?;
ecx.write_scalar(Scalar::from_u16(c), &place.into())?;
}
ecx.mark_immutable(&cmd_place);
}
argv
};
// Return place (in static memory so that it does not count as leak).
let ret_place = ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
// Call start function.
match entry_type {
EntryFnType::Main { .. } => {
let start_id = tcx.lang_items().start_fn().unwrap();
let main_ret_ty = tcx.fn_sig(entry_id).output();
let main_ret_ty = main_ret_ty.no_bound_vars().unwrap();
let start_instance = ty::Instance::resolve(
tcx,
ty::ParamEnv::reveal_all(),
start_id,
tcx.mk_substs(::std::iter::once(ty::subst::GenericArg::from(main_ret_ty))),
)
.unwrap()
.unwrap();
let main_ptr = ecx.create_fn_alloc_ptr(FnVal::Instance(entry_instance));
// Inlining of `DEFAULT` from
// https://github.com/rust-lang/rust/blob/master/compiler/rustc_session/src/config/sigpipe.rs.
// Alaways using DEFAULT is okay since we don't support signals in Miri anyway.
let sigpipe = 2;
ecx.call_function(
start_instance,
Abi::Rust,
&[
Scalar::from_pointer(main_ptr, &ecx).into(),
argc.into(),
argv,
Scalar::from_u8(sigpipe).into(),
],
Some(&ret_place.into()),
StackPopCleanup::Root { cleanup: true },
)?;
}
EntryFnType::Start => {
ecx.call_function(
entry_instance,
Abi::Rust,
&[argc.into(), argv],
Some(&ret_place.into()),
StackPopCleanup::Root { cleanup: true },
)?;
}
}
Ok((ecx, ret_place))
}
/// Evaluates the entry function specified by `entry_id`.
/// Returns `Some(return_code)` if program executed completed.
/// Returns `None` if an evaluation error occurred.
#[allow(clippy::needless_lifetimes)]
pub fn eval_entry<'tcx>(
tcx: TyCtxt<'tcx>,
entry_id: DefId,
entry_type: EntryFnType,
config: MiriConfig,
) -> Option<i64> {
// Copy setting before we move `config`.
let ignore_leaks = config.ignore_leaks;
let (mut ecx, ret_place) = match create_ecx(tcx, entry_id, entry_type, &config) {
Ok(v) => v,
Err(err) => {
err.print_backtrace();
panic!("Miri initialization error: {}", err.kind())
}
};
// Perform the main execution.
let res: thread::Result<InterpResult<'_, i64>> = panic::catch_unwind(AssertUnwindSafe(|| {
// Main loop.
loop {
match ecx.schedule()? {
SchedulingAction::ExecuteStep => {
assert!(ecx.step()?, "a terminated thread was scheduled for execution");
}
SchedulingAction::ExecuteTimeoutCallback => {
ecx.run_timeout_callback()?;
}
SchedulingAction::ExecuteDtors => {
// This will either enable the thread again (so we go back
// to `ExecuteStep`), or determine that this thread is done
// for good.
ecx.schedule_next_tls_dtor_for_active_thread()?;
}
SchedulingAction::Stop => {
break;
}
}
}
let return_code = ecx.read_scalar(&ret_place.into())?.to_machine_isize(&ecx)?;
Ok(return_code)
}));
let res = res.unwrap_or_else(|panic_payload| {
ecx.handle_ice();
panic::resume_unwind(panic_payload)
});
// Machine cleanup. Only do this if all threads have terminated; threads that are still running
// might cause Stacked Borrows errors (https://github.com/rust-lang/miri/issues/2396).
if ecx.have_all_terminated() {
// Even if all threads have terminated, we have to beware of data races since some threads
// might not have joined the main thread (https://github.com/rust-lang/miri/issues/2020,
// https://github.com/rust-lang/miri/issues/2508).
ecx.allow_data_races_all_threads_done();
EnvVars::cleanup(&mut ecx).expect("error during env var cleanup");
}
// Process the result.
match res {
Ok(return_code) => {
if !ignore_leaks {
// Check for thread leaks.
if !ecx.have_all_terminated() {
tcx.sess.err(
"the main thread terminated without waiting for all remaining threads",
);
tcx.sess.note_without_error("pass `-Zmiri-ignore-leaks` to disable this check");
return None;
}
// Check for memory leaks.
info!("Additonal static roots: {:?}", ecx.machine.static_roots);
let leaks = ecx.leak_report(&ecx.machine.static_roots);
if leaks != 0 {
tcx.sess.err("the evaluated program leaked memory");
tcx.sess.note_without_error("pass `-Zmiri-ignore-leaks` to disable this check");
// Ignore the provided return code - let the reported error
// determine the return code.
return None;
}
}
Some(return_code)
}
Err(e) => report_error(&ecx, e),
}
}
/// Turns an array of arguments into a Windows command line string.
///
/// The string will be UTF-16 encoded and NUL terminated.
///
/// Panics if the zeroth argument contains the `"` character because doublequotes
/// in `argv[0]` cannot be encoded using the standard command line parsing rules.
///
/// Further reading:
/// * [Parsing C++ command-line arguments](https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-160#parsing-c-command-line-arguments)
/// * [The C/C++ Parameter Parsing Rules](https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES)
fn args_to_utf16_command_string<I, T>(mut args: I) -> Vec<u16>
where
I: Iterator<Item = T>,
T: AsRef<str>,
{
// Parse argv[0]. Slashes aren't escaped. Literal double quotes are not allowed.
let mut cmd = {
let arg0 = if let Some(arg0) = args.next() {
arg0
} else {
return vec![0];
};
let arg0 = arg0.as_ref();
if arg0.contains('"') {
panic!("argv[0] cannot contain a doublequote (\") character");
} else {
// Always surround argv[0] with quotes.
let mut s = String::new();
s.push('"');
s.push_str(arg0);
s.push('"');
s
}
};
// Build the other arguments.
for arg in args {
let arg = arg.as_ref();
cmd.push(' ');
if arg.is_empty() {
cmd.push_str("\"\"");
} else if !arg.bytes().any(|c| matches!(c, b'"' | b'\t' | b' ')) {
// No quote, tab, or space -- no escaping required.
cmd.push_str(arg);
} else {
// Spaces and tabs are escaped by surrounding them in quotes.
// Quotes are themselves escaped by using backslashes when in a
// quoted block.
// Backslashes only need to be escaped when one or more are directly
// followed by a quote. Otherwise they are taken literally.
cmd.push('"');
let mut chars = arg.chars().peekable();
loop {
let mut nslashes = 0;
while let Some(&'\\') = chars.peek() {
chars.next();
nslashes += 1;
}
match chars.next() {
Some('"') => {
cmd.extend(iter::repeat('\\').take(nslashes * 2 + 1));
cmd.push('"');
}
Some(c) => {
cmd.extend(iter::repeat('\\').take(nslashes));
cmd.push(c);
}
None => {
cmd.extend(iter::repeat('\\').take(nslashes * 2));
break;
}
}
}
cmd.push('"');
}
}
if cmd.contains('\0') {
panic!("interior null in command line arguments");
}
cmd.encode_utf16().chain(iter::once(0)).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "argv[0] cannot contain a doublequote (\") character")]
fn windows_argv0_panic_on_quote() {
args_to_utf16_command_string(["\""].iter());
}
#[test]
fn windows_argv0_no_escape() {
// Ensure that a trailing backslash in argv[0] is not escaped.
let cmd = String::from_utf16_lossy(&args_to_utf16_command_string(
[r"C:\Program Files\", "arg1", "arg 2", "arg \" 3"].iter(),
));
assert_eq!(cmd.trim_end_matches('\0'), r#""C:\Program Files\" arg1 "arg 2" "arg \" 3""#);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
use implementations::NarrowerThan;
/// Replacement for `as` casts going from wide integer to narrower integer.
///
/// # Example
///
/// ```ignore
/// let x = 99_u64;
/// let lo = x.truncate::<u16>();
/// // lo is of type u16, equivalent to `x as u16`.
/// ```
pub(crate) trait Truncate: Sized {
fn truncate<To>(self) -> To
where
To: NarrowerThan<Self>,
{
NarrowerThan::truncate_from(self)
}
}
impl Truncate for u16 {}
impl Truncate for u32 {}
impl Truncate for u64 {}
impl Truncate for u128 {}
mod implementations {
pub(crate) trait NarrowerThan<T> {
fn truncate_from(wide: T) -> Self;
}
macro_rules! impl_narrower_than {
($(NarrowerThan<{$($ty:ty),*}> for $self:ty)*) => {
$($(
impl NarrowerThan<$ty> for $self {
fn truncate_from(wide: $ty) -> Self {
wide as Self
}
}
)*)*
};
}
impl_narrower_than! {
NarrowerThan<{u128, u64, u32, u16}> for u8
NarrowerThan<{u128, u64, u32}> for u16
NarrowerThan<{u128, u64}> for u32
NarrowerThan<{u128}> for u64
}
}

View File

@ -0,0 +1,260 @@
use std::cell::RefCell;
use std::cmp::max;
use std::collections::hash_map::Entry;
use log::trace;
use rand::Rng;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_span::Span;
use rustc_target::abi::{HasDataLayout, Size};
use crate::*;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ProvenanceMode {
/// We support `expose_addr`/`from_exposed_addr` via "wildcard" provenance.
/// However, we want on `from_exposed_addr` to alert the user of the precision loss.
Default,
/// Like `Default`, but without the warning.
Permissive,
/// We error on `from_exposed_addr`, ensuring no precision loss.
Strict,
}
pub type GlobalState = RefCell<GlobalStateInner>;
#[derive(Clone, Debug)]
pub struct GlobalStateInner {
/// This is used as a map between the address of each allocation and its `AllocId`.
/// It is always sorted
int_to_ptr_map: Vec<(u64, AllocId)>,
/// The base address for each allocation. We cannot put that into
/// `AllocExtra` because function pointers also have a base address, and
/// they do not have an `AllocExtra`.
/// This is the inverse of `int_to_ptr_map`.
base_addr: FxHashMap<AllocId, u64>,
/// Whether an allocation has been exposed or not. This cannot be put
/// into `AllocExtra` for the same reason as `base_addr`.
exposed: FxHashSet<AllocId>,
/// This is used as a memory address when a new pointer is casted to an integer. It
/// is always larger than any address that was previously made part of a block.
next_base_addr: u64,
/// The provenance to use for int2ptr casts
provenance_mode: ProvenanceMode,
}
impl GlobalStateInner {
pub fn new(config: &MiriConfig) -> Self {
GlobalStateInner {
int_to_ptr_map: Vec::default(),
base_addr: FxHashMap::default(),
exposed: FxHashSet::default(),
next_base_addr: STACK_ADDR,
provenance_mode: config.provenance_mode,
}
}
}
impl<'mir, 'tcx> GlobalStateInner {
// Returns the exposed `AllocId` that corresponds to the specified addr,
// or `None` if the addr is out of bounds
fn alloc_id_from_addr(ecx: &MiriInterpCx<'mir, 'tcx>, addr: u64) -> Option<AllocId> {
let global_state = ecx.machine.intptrcast.borrow();
assert!(global_state.provenance_mode != ProvenanceMode::Strict);
let pos = global_state.int_to_ptr_map.binary_search_by_key(&addr, |(addr, _)| *addr);
// Determine the in-bounds provenance for this pointer.
// (This is only called on an actual access, so in-bounds is the only possible kind of provenance.)
let alloc_id = match pos {
Ok(pos) => Some(global_state.int_to_ptr_map[pos].1),
Err(0) => None,
Err(pos) => {
// This is the largest of the adresses smaller than `int`,
// i.e. the greatest lower bound (glb)
let (glb, alloc_id) = global_state.int_to_ptr_map[pos - 1];
// This never overflows because `addr >= glb`
let offset = addr - glb;
// If the offset exceeds the size of the allocation, don't use this `alloc_id`.
let size = ecx.get_alloc_info(alloc_id).0;
if offset <= size.bytes() { Some(alloc_id) } else { None }
}
}?;
// We only use this provenance if it has been exposed, *and* is still live.
if global_state.exposed.contains(&alloc_id) {
let (_size, _align, kind) = ecx.get_alloc_info(alloc_id);
match kind {
AllocKind::LiveData | AllocKind::Function | AllocKind::VTable => {
return Some(alloc_id);
}
AllocKind::Dead => {}
}
}
None
}
pub fn expose_ptr(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
alloc_id: AllocId,
sb: SbTag,
) -> InterpResult<'tcx> {
let global_state = ecx.machine.intptrcast.get_mut();
// In strict mode, we don't need this, so we can save some cycles by not tracking it.
if global_state.provenance_mode != ProvenanceMode::Strict {
trace!("Exposing allocation id {alloc_id:?}");
global_state.exposed.insert(alloc_id);
if ecx.machine.stacked_borrows.is_some() {
ecx.expose_tag(alloc_id, sb)?;
}
}
Ok(())
}
pub fn ptr_from_addr_transmute(
_ecx: &MiriInterpCx<'mir, 'tcx>,
addr: u64,
) -> Pointer<Option<Provenance>> {
trace!("Transmuting {:#x} to a pointer", addr);
// We consider transmuted pointers to be "invalid" (`None` provenance).
Pointer::new(None, Size::from_bytes(addr))
}
pub fn ptr_from_addr_cast(
ecx: &MiriInterpCx<'mir, 'tcx>,
addr: u64,
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
trace!("Casting {:#x} to a pointer", addr);
let global_state = ecx.machine.intptrcast.borrow();
match global_state.provenance_mode {
ProvenanceMode::Default => {
// The first time this happens at a particular location, print a warning.
thread_local! {
// `Span` is non-`Send`, so we use a thread-local instead.
static PAST_WARNINGS: RefCell<FxHashSet<Span>> = RefCell::default();
}
PAST_WARNINGS.with_borrow_mut(|past_warnings| {
let first = past_warnings.is_empty();
if past_warnings.insert(ecx.cur_span()) {
// Newly inserted, so first time we see this span.
ecx.emit_diagnostic(NonHaltingDiagnostic::Int2Ptr { details: first });
}
});
}
ProvenanceMode::Strict => {
throw_machine_stop!(TerminationInfo::Int2PtrWithStrictProvenance);
}
ProvenanceMode::Permissive => {}
}
// This is how wildcard pointers are born.
Ok(Pointer::new(Some(Provenance::Wildcard), Size::from_bytes(addr)))
}
fn alloc_base_addr(ecx: &MiriInterpCx<'mir, 'tcx>, alloc_id: AllocId) -> u64 {
let mut global_state = ecx.machine.intptrcast.borrow_mut();
let global_state = &mut *global_state;
match global_state.base_addr.entry(alloc_id) {
Entry::Occupied(entry) => *entry.get(),
Entry::Vacant(entry) => {
// There is nothing wrong with a raw pointer being cast to an integer only after
// it became dangling. Hence we allow dead allocations.
let (size, align, _kind) = ecx.get_alloc_info(alloc_id);
// This allocation does not have a base address yet, pick one.
// Leave some space to the previous allocation, to give it some chance to be less aligned.
let slack = {
let mut rng = ecx.machine.rng.borrow_mut();
// This means that `(global_state.next_base_addr + slack) % 16` is uniformly distributed.
rng.gen_range(0..16)
};
// From next_base_addr + slack, round up to adjust for alignment.
let base_addr = global_state.next_base_addr.checked_add(slack).unwrap();
let base_addr = Self::align_addr(base_addr, align.bytes());
entry.insert(base_addr);
trace!(
"Assigning base address {:#x} to allocation {:?} (size: {}, align: {}, slack: {})",
base_addr,
alloc_id,
size.bytes(),
align.bytes(),
slack,
);
// Remember next base address. If this allocation is zero-sized, leave a gap
// of at least 1 to avoid two allocations having the same base address.
// (The logic in `alloc_id_from_addr` assumes unique addresses, and different
// function/vtable pointers need to be distinguishable!)
global_state.next_base_addr = base_addr.checked_add(max(size.bytes(), 1)).unwrap();
// Given that `next_base_addr` increases in each allocation, pushing the
// corresponding tuple keeps `int_to_ptr_map` sorted
global_state.int_to_ptr_map.push((base_addr, alloc_id));
base_addr
}
}
}
/// Convert a relative (tcx) pointer to an absolute address.
pub fn rel_ptr_to_addr(ecx: &MiriInterpCx<'mir, 'tcx>, ptr: Pointer<AllocId>) -> u64 {
let (alloc_id, offset) = ptr.into_parts(); // offset is relative (AllocId provenance)
let base_addr = GlobalStateInner::alloc_base_addr(ecx, alloc_id);
// Add offset with the right kind of pointer-overflowing arithmetic.
let dl = ecx.data_layout();
dl.overflowing_offset(base_addr, offset.bytes()).0
}
/// When a pointer is used for a memory access, this computes where in which allocation the
/// access is going.
pub fn abs_ptr_to_rel(
ecx: &MiriInterpCx<'mir, 'tcx>,
ptr: Pointer<Provenance>,
) -> Option<(AllocId, Size)> {
let (tag, addr) = ptr.into_parts(); // addr is absolute (Tag provenance)
let alloc_id = if let Provenance::Concrete { alloc_id, .. } = tag {
alloc_id
} else {
// A wildcard pointer.
GlobalStateInner::alloc_id_from_addr(ecx, addr.bytes())?
};
let base_addr = GlobalStateInner::alloc_base_addr(ecx, alloc_id);
// Wrapping "addr - base_addr"
let dl = ecx.data_layout();
#[allow(clippy::cast_possible_wrap)] // we want to wrap here
let neg_base_addr = (base_addr as i64).wrapping_neg();
Some((
alloc_id,
Size::from_bytes(dl.overflowing_signed_offset(addr.bytes(), neg_base_addr).0),
))
}
/// Shifts `addr` to make it aligned with `align` by rounding `addr` to the smallest multiple
/// of `align` that is larger or equal to `addr`
fn align_addr(addr: u64, align: u64) -> u64 {
match addr % align {
0 => addr,
rem => addr.checked_add(align).unwrap() - rem,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_align_addr() {
assert_eq!(GlobalStateInner::align_addr(37, 4), 40);
assert_eq!(GlobalStateInner::align_addr(44, 4), 44);
}
}

126
src/tools/miri/src/lib.rs Normal file
View File

@ -0,0 +1,126 @@
#![feature(rustc_private)]
#![feature(map_first_last)]
#![feature(map_try_insert)]
#![feature(never_type)]
#![feature(try_blocks)]
#![feature(io_error_more)]
#![feature(int_log)]
#![feature(variant_count)]
#![feature(yeet_expr)]
#![feature(is_some_with)]
#![feature(nonzero_ops)]
#![feature(local_key_cell_methods)]
// Configure clippy and other lints
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::comparison_chain,
clippy::enum_variant_names,
clippy::field_reassign_with_default,
clippy::manual_map,
clippy::new_without_default,
clippy::single_match,
clippy::useless_format,
clippy::derive_partial_eq_without_eq,
clippy::derive_hash_xor_eq,
clippy::too_many_arguments,
clippy::type_complexity,
clippy::single_element_loop,
clippy::needless_return,
// We are not implementing queries here so it's fine
rustc::potential_query_instability
)]
#![warn(
rust_2018_idioms,
clippy::cast_possible_wrap, // unsigned -> signed
clippy::cast_sign_loss, // signed -> unsigned
clippy::cast_lossless,
clippy::cast_possible_truncation,
)]
extern crate rustc_apfloat;
extern crate rustc_ast;
#[macro_use]
extern crate rustc_middle;
extern crate rustc_const_eval;
extern crate rustc_data_structures;
extern crate rustc_hir;
extern crate rustc_index;
extern crate rustc_session;
extern crate rustc_span;
extern crate rustc_target;
mod clock;
mod concurrency;
mod diagnostics;
mod eval;
mod helpers;
mod intptrcast;
mod machine;
mod mono_hash_map;
mod operator;
mod range_map;
mod shims;
mod stacked_borrows;
mod tag_gc;
// Establish a "crate-wide prelude": we often import `crate::*`.
// Make all those symbols available in the same place as our own.
pub use rustc_const_eval::interpret::*;
// Resolve ambiguity.
pub use rustc_const_eval::interpret::{self, AllocMap, PlaceTy, Provenance as _};
pub use crate::shims::dlsym::{Dlsym, EvalContextExt as _};
pub use crate::shims::env::{EnvVars, EvalContextExt as _};
pub use crate::shims::foreign_items::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::{EvalContextExt as _, TlsData};
pub use crate::shims::EvalContextExt as _;
pub use crate::clock::{Clock, Instant};
pub use crate::concurrency::{
data_race::{
AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd,
EvalContextExt as DataRaceEvalContextExt,
},
sync::{CondvarId, EvalContextExt as SyncEvalContextExt, MutexId, RwLockId},
thread::{
EvalContextExt as ThreadsEvalContextExt, SchedulingAction, ThreadId, ThreadManager,
ThreadState, Time,
},
};
pub use crate::diagnostics::{
report_error, EvalContextExt as DiagnosticsEvalContextExt, NonHaltingDiagnostic,
TerminationInfo,
};
pub use crate::eval::{
create_ecx, eval_entry, AlignmentCheck, BacktraceStyle, IsolatedOp, MiriConfig, RejectOpWith,
};
pub use crate::helpers::{CurrentSpan, EvalContextExt as HelpersEvalContextExt};
pub use crate::intptrcast::ProvenanceMode;
pub use crate::machine::{
AllocExtra, FrameData, MiriInterpCx, MiriInterpCxExt, MiriMachine, MiriMemoryKind, Provenance,
ProvenanceExtra, NUM_CPUS, PAGE_SIZE, STACK_ADDR, STACK_SIZE,
};
pub use crate::mono_hash_map::MonoHashMap;
pub use crate::operator::EvalContextExt as OperatorEvalContextExt;
pub use crate::range_map::RangeMap;
pub use crate::stacked_borrows::{
CallId, EvalContextExt as StackedBorEvalContextExt, Item, Permission, SbTag, Stack, Stacks,
};
pub use crate::tag_gc::EvalContextExt as _;
/// Insert rustc arguments at the beginning of the argument list that Miri wants to be
/// set per default, for maximal validation power.
pub const MIRI_DEFAULT_ARGS: &[&str] = &[
"-Zalways-encode-mir",
"-Zmir-emit-retag",
"-Zmir-opt-level=0",
"--cfg=miri",
"-Cdebug-assertions=on",
"-Zextra-const-ub-checks",
];

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,110 @@
//! This is a "monotonic `FxHashMap`": A `FxHashMap` that, when shared, can be pushed to but not
//! otherwise mutated. We also box items in the map. This means we can safely provide
//! shared references into existing items in the `FxHashMap`, because they will not be dropped
//! (from being removed) or moved (because they are boxed).
//! The API is is completely tailored to what `memory.rs` needs. It is still in
//! a separate file to minimize the amount of code that has to care about the unsafety.
use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::hash_map::Entry;
use std::hash::Hash;
use rustc_data_structures::fx::FxHashMap;
use crate::AllocMap;
#[derive(Debug, Clone)]
pub struct MonoHashMap<K: Hash + Eq, V>(RefCell<FxHashMap<K, Box<V>>>);
impl<K: Hash + Eq, V> MonoHashMap<K, V> {
/// This function exists for priroda to be able to iterate over all evaluator memory.
///
/// The function is somewhat roundabout with the closure argument because internally the
/// `MonoHashMap` uses a `RefCell`. When iterating over the `FxHashMap` inside the `RefCell`,
/// we need to keep a borrow to the `FxHashMap` inside the iterator. The borrow is only alive
/// as long as the `Ref` returned by `RefCell::borrow()` is alive. So we can't return the
/// iterator, as that would drop the `Ref`. We can't return both, as it's not possible in Rust
/// to have a struct/tuple with a field that refers to another field.
pub fn iter<T>(&self, f: impl FnOnce(&mut dyn Iterator<Item = (&K, &V)>) -> T) -> T {
f(&mut self.0.borrow().iter().map(|(k, v)| (k, &**v)))
}
}
impl<K: Hash + Eq, V> Default for MonoHashMap<K, V> {
fn default() -> Self {
MonoHashMap(RefCell::new(Default::default()))
}
}
impl<K: Hash + Eq, V> AllocMap<K, V> for MonoHashMap<K, V> {
#[inline(always)]
fn contains_key<Q: ?Sized + Hash + Eq>(&mut self, k: &Q) -> bool
where
K: Borrow<Q>,
{
self.0.get_mut().contains_key(k)
}
#[inline(always)]
fn insert(&mut self, k: K, v: V) -> Option<V> {
self.0.get_mut().insert(k, Box::new(v)).map(|x| *x)
}
#[inline(always)]
fn remove<Q: ?Sized + Hash + Eq>(&mut self, k: &Q) -> Option<V>
where
K: Borrow<Q>,
{
self.0.get_mut().remove(k).map(|x| *x)
}
#[inline(always)]
fn filter_map_collect<T>(&self, mut f: impl FnMut(&K, &V) -> Option<T>) -> Vec<T> {
self.0.borrow().iter().filter_map(move |(k, v)| f(k, v)).collect()
}
/// The most interesting method: Providing a shared reference without
/// holding the `RefCell` open, and inserting new data if the key
/// is not used yet.
/// `vacant` is called if the key is not found in the map;
/// if it returns a reference, that is used directly, if it
/// returns owned data, that is put into the map and returned.
#[inline(always)]
fn get_or<E>(&self, k: K, vacant: impl FnOnce() -> Result<V, E>) -> Result<&V, E> {
// We cannot hold borrow_mut while calling `vacant`, since that might have to do lookups in this very map.
if let Some(v) = self.0.borrow().get(&k) {
let val: *const V = &**v;
// This is safe because `val` points into a `Box`, that we know will not move and
// will also not be dropped as long as the shared reference `self` is live.
return unsafe { Ok(&*val) };
}
let new_val = Box::new(vacant()?);
let val: *const V = &**self.0.borrow_mut().try_insert(k, new_val).ok().unwrap();
// This is safe because `val` points into a `Box`, that we know will not move and
// will also not be dropped as long as the shared reference `self` is live.
unsafe { Ok(&*val) }
}
/// Read-only lookup (avoid read-acquiring the RefCell).
fn get(&self, k: K) -> Option<&V> {
let val: *const V = match self.0.borrow().get(&k) {
Some(v) => &**v,
None => return None,
};
// This is safe because `val` points into a `Box`, that we know will not move and
// will also not be dropped as long as the shared reference `self` is live.
unsafe { Some(&*val) }
}
#[inline(always)]
fn get_mut_or<E>(&mut self, k: K, vacant: impl FnOnce() -> Result<V, E>) -> Result<&mut V, E> {
match self.0.get_mut().entry(k) {
Entry::Occupied(e) => Ok(e.into_mut()),
Entry::Vacant(e) => {
let v = vacant()?;
Ok(e.insert(Box::new(v)))
}
}
}
}

View File

@ -0,0 +1,90 @@
use log::trace;
use rustc_middle::{mir, ty::Ty};
use rustc_target::abi::Size;
use crate::*;
pub trait EvalContextExt<'tcx> {
fn binary_ptr_op(
&self,
bin_op: mir::BinOp,
left: &ImmTy<'tcx, Provenance>,
right: &ImmTy<'tcx, Provenance>,
) -> InterpResult<'tcx, (Scalar<Provenance>, bool, Ty<'tcx>)>;
}
impl<'mir, 'tcx> EvalContextExt<'tcx> for super::MiriInterpCx<'mir, 'tcx> {
fn binary_ptr_op(
&self,
bin_op: mir::BinOp,
left: &ImmTy<'tcx, Provenance>,
right: &ImmTy<'tcx, Provenance>,
) -> InterpResult<'tcx, (Scalar<Provenance>, bool, Ty<'tcx>)> {
use rustc_middle::mir::BinOp::*;
trace!("ptr_op: {:?} {:?} {:?}", *left, bin_op, *right);
Ok(match bin_op {
Eq | Ne | Lt | Le | Gt | Ge => {
assert_eq!(left.layout.abi, right.layout.abi); // types an differ, e.g. fn ptrs with different `for`
let size = self.pointer_size();
// Just compare the bits. ScalarPairs are compared lexicographically.
// We thus always compare pairs and simply fill scalars up with 0.
let left = match **left {
Immediate::Scalar(l) => (l.to_bits(size)?, 0),
Immediate::ScalarPair(l1, l2) => (l1.to_bits(size)?, l2.to_bits(size)?),
Immediate::Uninit => panic!("we should never see uninit data here"),
};
let right = match **right {
Immediate::Scalar(r) => (r.to_bits(size)?, 0),
Immediate::ScalarPair(r1, r2) => (r1.to_bits(size)?, r2.to_bits(size)?),
Immediate::Uninit => panic!("we should never see uninit data here"),
};
let res = match bin_op {
Eq => left == right,
Ne => left != right,
Lt => left < right,
Le => left <= right,
Gt => left > right,
Ge => left >= right,
_ => bug!(),
};
(Scalar::from_bool(res), false, self.tcx.types.bool)
}
Offset => {
assert!(left.layout.ty.is_unsafe_ptr());
let ptr = left.to_scalar().to_pointer(self)?;
let offset = right.to_scalar().to_machine_isize(self)?;
let pointee_ty =
left.layout.ty.builtin_deref(true).expect("Offset called on non-ptr type").ty;
let ptr = self.ptr_offset_inbounds(ptr, pointee_ty, offset)?;
(Scalar::from_maybe_pointer(ptr, self), false, left.layout.ty)
}
// Some more operations are possible with atomics.
// The return value always has the provenance of the *left* operand.
Add | Sub | BitOr | BitAnd | BitXor => {
assert!(left.layout.ty.is_unsafe_ptr());
assert!(right.layout.ty.is_unsafe_ptr());
let ptr = left.to_scalar().to_pointer(self)?;
// We do the actual operation with usize-typed scalars.
let left = ImmTy::from_uint(ptr.addr().bytes(), self.machine.layouts.usize);
let right = ImmTy::from_uint(
right.to_scalar().to_machine_usize(self)?,
self.machine.layouts.usize,
);
let (result, overflowing, _ty) =
self.overflowing_binary_op(bin_op, &left, &right)?;
// Construct a new pointer with the provenance of `ptr` (the LHS).
let result_ptr =
Pointer::new(ptr.provenance, Size::from_bytes(result.to_machine_usize(self)?));
(Scalar::from_maybe_pointer(result_ptr, self), overflowing, left.layout.ty)
}
_ => span_bug!(self.cur_span(), "Invalid operator on pointers: {:?}", bin_op),
})
}
}

View File

@ -0,0 +1,300 @@
//! Implements a map from integer indices to data.
//! Rather than storing data for every index, internally, this maps entire ranges to the data.
//! To this end, the APIs all work on ranges, not on individual integers. Ranges are split as
//! necessary (e.g., when [0,5) is first associated with X, and then [1,2) is mutated).
//! Users must not depend on whether a range is coalesced or not, even though this is observable
//! via the iteration APIs.
use std::ops;
use rustc_target::abi::Size;
#[derive(Clone, Debug)]
struct Elem<T> {
/// The range covered by this element; never empty.
range: ops::Range<u64>,
/// The data stored for this element.
data: T,
}
#[derive(Clone, Debug)]
pub struct RangeMap<T> {
v: Vec<Elem<T>>,
}
impl<T> RangeMap<T> {
/// Creates a new `RangeMap` for the given size, and with the given initial value used for
/// the entire range.
#[inline(always)]
pub fn new(size: Size, init: T) -> RangeMap<T> {
let size = size.bytes();
let mut map = RangeMap { v: Vec::new() };
if size > 0 {
map.v.push(Elem { range: 0..size, data: init });
}
map
}
/// Finds the index containing the given offset.
fn find_offset(&self, offset: u64) -> usize {
// We do a binary search.
let mut left = 0usize; // inclusive
let mut right = self.v.len(); // exclusive
loop {
debug_assert!(left < right, "find_offset: offset {} is out-of-bounds", offset);
let candidate = left.checked_add(right).unwrap() / 2;
let elem = &self.v[candidate];
if offset < elem.range.start {
// We are too far right (offset is further left).
debug_assert!(candidate < right); // we are making progress
right = candidate;
} else if offset >= elem.range.end {
// We are too far left (offset is further right).
debug_assert!(candidate >= left); // we are making progress
left = candidate + 1;
} else {
// This is it!
return candidate;
}
}
}
/// Provides read-only iteration over everything in the given range. This does
/// *not* split items if they overlap with the edges. Do not use this to mutate
/// through interior mutability.
///
/// The iterator also provides the offset of the given element.
pub fn iter(&self, offset: Size, len: Size) -> impl Iterator<Item = (Size, &T)> {
let offset = offset.bytes();
let len = len.bytes();
// Compute a slice starting with the elements we care about.
let slice: &[Elem<T>] = if len == 0 {
// We just need any empty iterator. We don't even want to
// yield the element that surrounds this position.
&[]
} else {
let first_idx = self.find_offset(offset);
&self.v[first_idx..]
};
// The first offset that is not included any more.
let end = offset + len;
assert!(
end <= self.v.last().unwrap().range.end,
"iterating beyond the bounds of this RangeMap"
);
slice
.iter()
.take_while(move |elem| elem.range.start < end)
.map(|elem| (Size::from_bytes(elem.range.start), &elem.data))
}
pub fn iter_mut_all(&mut self) -> impl Iterator<Item = &mut T> {
self.v.iter_mut().map(|elem| &mut elem.data)
}
// Splits the element situated at the given `index`, such that the 2nd one starts at offset
// `split_offset`. Do nothing if the element already starts there.
// Returns whether a split was necessary.
fn split_index(&mut self, index: usize, split_offset: u64) -> bool
where
T: Clone,
{
let elem = &mut self.v[index];
if split_offset == elem.range.start || split_offset == elem.range.end {
// Nothing to do.
return false;
}
debug_assert!(
elem.range.contains(&split_offset),
"the `split_offset` is not in the element to be split"
);
// Now we really have to split. Reduce length of first element.
let second_range = split_offset..elem.range.end;
elem.range.end = split_offset;
// Copy the data, and insert second element.
let second = Elem { range: second_range, data: elem.data.clone() };
self.v.insert(index + 1, second);
true
}
/// Provides mutable iteration over everything in the given range. As a side-effect,
/// this will split entries in the map that are only partially hit by the given range,
/// to make sure that when they are mutated, the effect is constrained to the given range.
/// Moreover, this will opportunistically merge neighbouring equal blocks.
///
/// The iterator also provides the offset of the given element.
pub fn iter_mut(&mut self, offset: Size, len: Size) -> impl Iterator<Item = (Size, &mut T)>
where
T: Clone + PartialEq,
{
let offset = offset.bytes();
let len = len.bytes();
// Compute a slice containing exactly the elements we care about
let slice: &mut [Elem<T>] = if len == 0 {
// We just need any empty iterator. We don't even want to
// yield the element that surrounds this position, nor do
// any splitting.
&mut []
} else {
// Make sure we got a clear beginning
let mut first_idx = self.find_offset(offset);
if self.split_index(first_idx, offset) {
// The newly created 2nd element is ours
first_idx += 1;
}
// No more mutation.
let first_idx = first_idx;
// Find our end. Linear scan, but that's ok because the iteration
// is doing the same linear scan anyway -- no increase in complexity.
// We combine this scan with a scan for duplicates that we can merge, to reduce
// the number of elements.
// We stop searching after the first "block" of size 1, to avoid spending excessive
// amounts of time on the merging.
let mut equal_since_idx = first_idx;
// Once we see too many non-mergeable blocks, we stop.
// The initial value is chosen via... magic. Benchmarking and magic.
let mut successful_merge_count = 3usize;
// When the loop is done, this is the first excluded element.
let mut end_idx = first_idx;
loop {
// Compute if `end` is the last element we need to look at.
let done = self.v[end_idx].range.end >= offset + len;
// We definitely need to include `end`, so move the index.
end_idx += 1;
debug_assert!(
done || end_idx < self.v.len(),
"iter_mut: end-offset {} is out-of-bounds",
offset + len
);
// see if we want to merge everything in `equal_since..end` (exclusive at the end!)
if successful_merge_count > 0 {
if done || self.v[end_idx].data != self.v[equal_since_idx].data {
// Everything in `equal_since..end` was equal. Make them just one element covering
// the entire range.
let removed_elems = end_idx - equal_since_idx - 1; // number of elements that we would remove
if removed_elems > 0 {
// Adjust the range of the first element to cover all of them.
let equal_until = self.v[end_idx - 1].range.end; // end of range of last of the equal elements
self.v[equal_since_idx].range.end = equal_until;
// Delete the rest of them.
self.v.splice(equal_since_idx + 1..end_idx, std::iter::empty());
// Adjust `end_idx` because we made the list shorter.
end_idx -= removed_elems;
// Adjust the count for the cutoff.
successful_merge_count += removed_elems;
} else {
// Adjust the count for the cutoff.
successful_merge_count -= 1;
}
// Go on scanning for the next block starting here.
equal_since_idx = end_idx;
}
}
// Leave loop if this is the last element.
if done {
break;
}
}
// Move to last included instead of first excluded index.
let end_idx = end_idx - 1;
// We need to split the end as well. Even if this performs a
// split, we don't have to adjust our index as we only care about
// the first part of the split.
self.split_index(end_idx, offset + len);
// Now we yield the slice. `end` is inclusive.
&mut self.v[first_idx..=end_idx]
};
slice.iter_mut().map(|elem| (Size::from_bytes(elem.range.start), &mut elem.data))
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Query the map at every offset in the range and collect the results.
fn to_vec<T: Copy>(map: &RangeMap<T>, offset: u64, len: u64) -> Vec<T> {
(offset..offset + len)
.into_iter()
.map(|i| {
map.iter(Size::from_bytes(i), Size::from_bytes(1)).next().map(|(_, &t)| t).unwrap()
})
.collect()
}
#[test]
fn basic_insert() {
let mut map = RangeMap::<i32>::new(Size::from_bytes(20), -1);
// Insert.
for (_, x) in map.iter_mut(Size::from_bytes(10), Size::from_bytes(1)) {
*x = 42;
}
// Check.
assert_eq!(to_vec(&map, 10, 1), vec![42]);
assert_eq!(map.v.len(), 3);
// Insert with size 0.
for (_, x) in map.iter_mut(Size::from_bytes(10), Size::from_bytes(0)) {
*x = 19;
}
for (_, x) in map.iter_mut(Size::from_bytes(11), Size::from_bytes(0)) {
*x = 19;
}
assert_eq!(to_vec(&map, 10, 2), vec![42, -1]);
assert_eq!(map.v.len(), 3);
}
#[test]
fn gaps() {
let mut map = RangeMap::<i32>::new(Size::from_bytes(20), -1);
for (_, x) in map.iter_mut(Size::from_bytes(11), Size::from_bytes(1)) {
*x = 42;
}
for (_, x) in map.iter_mut(Size::from_bytes(15), Size::from_bytes(1)) {
*x = 43;
}
assert_eq!(map.v.len(), 5);
assert_eq!(to_vec(&map, 10, 10), vec![-1, 42, -1, -1, -1, 43, -1, -1, -1, -1]);
for (_, x) in map.iter_mut(Size::from_bytes(10), Size::from_bytes(10)) {
if *x < 42 {
*x = 23;
}
}
assert_eq!(map.v.len(), 6);
assert_eq!(to_vec(&map, 10, 10), vec![23, 42, 23, 23, 23, 43, 23, 23, 23, 23]);
assert_eq!(to_vec(&map, 13, 5), vec![23, 23, 43, 23, 23]);
for (_, x) in map.iter_mut(Size::from_bytes(15), Size::from_bytes(5)) {
*x = 19;
}
assert_eq!(map.v.len(), 6);
assert_eq!(to_vec(&map, 10, 10), vec![23, 42, 23, 23, 23, 19, 19, 19, 19, 19]);
// Should be seeing two blocks with 19.
assert_eq!(
map.iter(Size::from_bytes(15), Size::from_bytes(2))
.map(|(_, &t)| t)
.collect::<Vec<_>>(),
vec![19, 19]
);
// A NOP `iter_mut` should trigger merging.
for _ in map.iter_mut(Size::from_bytes(15), Size::from_bytes(5)) {}
assert_eq!(map.v.len(), 5);
assert_eq!(to_vec(&map, 10, 10), vec![23, 42, 23, 23, 23, 19, 19, 19, 19, 19]);
}
#[test]
#[should_panic]
fn out_of_range_iter_mut() {
let mut map = RangeMap::<i32>::new(Size::from_bytes(20), -1);
let _ = map.iter_mut(Size::from_bytes(11), Size::from_bytes(11));
}
#[test]
#[should_panic]
fn out_of_range_iter() {
let map = RangeMap::<i32>::new(Size::from_bytes(20), -1);
let _ = map.iter(Size::from_bytes(11), Size::from_bytes(11));
}
}

View File

@ -0,0 +1,254 @@
use crate::*;
use rustc_ast::ast::Mutability;
use rustc_middle::ty::layout::LayoutOf as _;
use rustc_middle::ty::{self, Instance};
use rustc_span::{BytePos, Loc, Symbol};
use rustc_target::{abi::Size, spec::abi::Abi};
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn handle_miri_backtrace_size(
&mut self,
abi: Abi,
link_name: Symbol,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let [flags] = this.check_shim(abi, Abi::Rust, link_name, args)?;
let flags = this.read_scalar(flags)?.to_u64()?;
if flags != 0 {
throw_unsup_format!("unknown `miri_backtrace_size` flags {}", flags);
}
let frame_count = this.active_thread_stack().len();
this.write_scalar(Scalar::from_machine_usize(frame_count.try_into().unwrap(), this), dest)
}
fn handle_miri_get_backtrace(
&mut self,
abi: Abi,
link_name: Symbol,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let tcx = this.tcx;
let flags = if let Some(flags_op) = args.get(0) {
this.read_scalar(flags_op)?.to_u64()?
} else {
throw_ub_format!("expected at least 1 argument")
};
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())
}
data.push((frame.instance, span.lo()));
}
let ptrs: Vec<_> = data
.into_iter()
.map(|(instance, pos)| {
// We represent a frame pointer by using the `span.lo` value
// as an offset into the function's allocation. This gives us an
// opaque pointer that we can return to user code, and allows us
// to reconstruct the needed frame information in `handle_miri_resolve_frame`.
// Note that we never actually read or write anything from/to this pointer -
// all of the data is represented by the pointer value itself.
let fn_ptr = this.create_fn_alloc_ptr(FnVal::Instance(instance));
fn_ptr.wrapping_offset(Size::from_bytes(pos.0), this)
})
.collect();
let len: u64 = ptrs.len().try_into().unwrap();
let ptr_ty = this.machine.layouts.mut_raw_ptr.ty;
let array_layout = this.layout_of(tcx.mk_array(ptr_ty, len)).unwrap();
match flags {
// storage for pointers is allocated by miri
// deallocating the slice is undefined behavior with a custom global allocator
0 => {
let [_flags] = this.check_shim(abi, Abi::Rust, link_name, args)?;
let alloc = this.allocate(array_layout, MiriMemoryKind::Rust.into())?;
// Write pointers into array
for (i, ptr) in ptrs.into_iter().enumerate() {
let place = this.mplace_index(&alloc, i as u64)?;
this.write_pointer(ptr, &place.into())?;
}
this.write_immediate(
Immediate::new_slice(Scalar::from_maybe_pointer(alloc.ptr, this), len, this),
dest,
)?;
}
// storage for pointers is allocated by the caller
1 => {
let [_flags, buf] = this.check_shim(abi, Abi::Rust, link_name, args)?;
let buf_place = this.deref_operand(buf)?;
let ptr_layout = this.layout_of(ptr_ty)?;
for (i, ptr) in ptrs.into_iter().enumerate() {
let offset = ptr_layout.size * i.try_into().unwrap();
let op_place = buf_place.offset(offset, ptr_layout, this)?;
this.write_pointer(ptr, &op_place.into())?;
}
}
_ => throw_unsup_format!("unknown `miri_get_backtrace` flags {}", flags),
};
Ok(())
}
fn resolve_frame_pointer(
&mut self,
ptr: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, (Instance<'tcx>, Loc, String, String)> {
let this = self.eval_context_mut();
let ptr = this.read_pointer(ptr)?;
// Take apart the pointer, we need its pieces. The offset encodes the span.
let (alloc_id, offset, _prov) = this.ptr_get_alloc_id(ptr)?;
// This has to be an actual global fn ptr, not a dlsym function.
let fn_instance = if let Some(GlobalAlloc::Function(instance)) =
this.tcx.try_get_global_alloc(alloc_id)
{
instance
} else {
throw_ub_format!("expected static function pointer, found {:?}", ptr);
};
let lo =
this.tcx.sess.source_map().lookup_char_pos(BytePos(offset.bytes().try_into().unwrap()));
let name = fn_instance.to_string();
let filename = lo.file.name.prefer_remapped().to_string();
Ok((fn_instance, lo, name, filename))
}
fn handle_miri_resolve_frame(
&mut self,
abi: Abi,
link_name: Symbol,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let [ptr, flags] = this.check_shim(abi, Abi::Rust, link_name, args)?;
let flags = this.read_scalar(flags)?.to_u64()?;
let (fn_instance, lo, name, filename) = this.resolve_frame_pointer(ptr)?;
// Reconstruct the original function pointer,
// which we pass to user code.
let fn_ptr = this.create_fn_alloc_ptr(FnVal::Instance(fn_instance));
let num_fields = dest.layout.fields.count();
if !(4..=5).contains(&num_fields) {
// Always mention 5 fields, since the 4-field struct
// is deprecated and slated for removal.
throw_ub_format!(
"bad declaration of miri_resolve_frame - should return a struct with 5 fields"
);
}
// `u32` is not enough to fit line/colno, which can be `usize`. It seems unlikely that a
// file would have more than 2^32 lines or columns, but whatever, just default to 0.
let lineno: u32 = u32::try_from(lo.line).unwrap_or(0);
// `lo.col` is 0-based - add 1 to make it 1-based for the caller.
let colno: u32 = u32::try_from(lo.col.0.saturating_add(1)).unwrap_or(0);
let dest = this.force_allocation(dest)?;
if let ty::Adt(adt, _) = dest.layout.ty.kind() {
if !adt.repr().c() {
throw_ub_format!(
"miri_resolve_frame must be declared with a `#[repr(C)]` return type"
);
}
}
match flags {
0 => {
// These are "mutable" allocations as we consider them to be owned by the callee.
let name_alloc =
this.allocate_str(&name, MiriMemoryKind::Rust.into(), Mutability::Mut);
let filename_alloc =
this.allocate_str(&filename, MiriMemoryKind::Rust.into(), Mutability::Mut);
this.write_immediate(
name_alloc.to_ref(this),
&this.mplace_field(&dest, 0)?.into(),
)?;
this.write_immediate(
filename_alloc.to_ref(this),
&this.mplace_field(&dest, 1)?.into(),
)?;
}
1 => {
this.write_scalar(
Scalar::from_machine_usize(name.len().try_into().unwrap(), this),
&this.mplace_field(&dest, 0)?.into(),
)?;
this.write_scalar(
Scalar::from_machine_usize(filename.len().try_into().unwrap(), this),
&this.mplace_field(&dest, 1)?.into(),
)?;
}
_ => throw_unsup_format!("unknown `miri_resolve_frame` flags {}", flags),
}
this.write_scalar(Scalar::from_u32(lineno), &this.mplace_field(&dest, 2)?.into())?;
this.write_scalar(Scalar::from_u32(colno), &this.mplace_field(&dest, 3)?.into())?;
// Support a 4-field struct for now - this is deprecated
// and slated for removal.
if num_fields == 5 {
this.write_pointer(fn_ptr, &this.mplace_field(&dest, 4)?.into())?;
}
Ok(())
}
fn handle_miri_resolve_frame_names(
&mut self,
abi: Abi,
link_name: Symbol,
args: &[OpTy<'tcx, Provenance>],
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let [ptr, flags, name_ptr, filename_ptr] =
this.check_shim(abi, Abi::Rust, link_name, args)?;
let flags = this.read_scalar(flags)?.to_u64()?;
if flags != 0 {
throw_unsup_format!("unknown `miri_resolve_frame_names` flags {}", flags);
}
let (_, _, name, filename) = this.resolve_frame_pointer(ptr)?;
this.write_bytes_ptr(this.read_pointer(name_ptr)?, name.bytes())?;
this.write_bytes_ptr(this.read_pointer(filename_ptr)?, filename.bytes())?;
Ok(())
}
}

View File

@ -0,0 +1,48 @@
use rustc_middle::mir;
use rustc_target::spec::abi::Abi;
use crate::helpers::target_os_is_unix;
use crate::*;
use shims::unix::dlsym as unix;
use shims::windows::dlsym as windows;
#[derive(Debug, Copy, Clone)]
#[allow(non_camel_case_types)]
pub enum Dlsym {
Posix(unix::Dlsym),
Windows(windows::Dlsym),
}
impl Dlsym {
// Returns an error for unsupported symbols, and None if this symbol
// should become a NULL pointer (pretend it does not exist).
pub fn from_str<'tcx>(name: &[u8], target_os: &str) -> InterpResult<'tcx, Option<Dlsym>> {
let name = &*String::from_utf8_lossy(name);
Ok(match target_os {
target if target_os_is_unix(target) =>
unix::Dlsym::from_str(name, target)?.map(Dlsym::Posix),
"windows" => windows::Dlsym::from_str(name)?.map(Dlsym::Windows),
os => bug!("dlsym not implemented for target_os {}", os),
})
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn call_dlsym(
&mut self,
dlsym: Dlsym,
abi: Abi,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
ret: Option<mir::BasicBlock>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
match dlsym {
Dlsym::Posix(dlsym) =>
unix::EvalContextExt::call_dlsym(this, dlsym, abi, args, dest, ret),
Dlsym::Windows(dlsym) =>
windows::EvalContextExt::call_dlsym(this, dlsym, abi, args, dest, ret),
}
}
}

View File

@ -0,0 +1,469 @@
use std::env;
use std::ffi::{OsStr, OsString};
use std::io::ErrorKind;
use std::mem;
use rustc_const_eval::interpret::Pointer;
use rustc_data_structures::fx::FxHashMap;
use rustc_middle::ty::layout::LayoutOf;
use rustc_target::abi::Size;
use crate::helpers::target_os_is_unix;
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()
}
}
#[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<OsString, Pointer<Option<Provenance>>>,
/// Place where the `environ` static is stored. Lazily initialized, but then never changes.
pub(crate) environ: Option<MPlaceTy<'tcx, Provenance>>,
}
impl<'tcx> EnvVars<'tcx> {
pub(crate) fn init<'mir>(
ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
config: &MiriConfig,
) -> InterpResult<'tcx> {
let target_os = ecx.tcx.sess.target.os.as_ref();
// Skip the loop entirely if we don't want to forward anything.
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 target_os {
target if target_os_is_unix(target) =>
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);
}
}
}
ecx.update_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.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.environ.unwrap();
let old_vars_ptr = ecx.read_pointer(&environ.into())?;
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<Option<Provenance>>> {
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<Option<Provenance>>> {
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<Option<Provenance>>> {
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)?;
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, u32> {
// ^ 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()?);
windows_check_buffer_size(this.write_os_str_to_wide_str(&var, buf_ptr, buf_size)?)
}
None => {
let envvar_not_found = this.eval_windows("c", "ERROR_ENVVAR_NOT_FOUND")?;
this.set_last_error(envvar_not_found)?;
0 // return zero upon failure
}
})
}
#[allow(non_snake_case)]
fn GetEnvironmentStringsW(&mut self) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
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, i32> {
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(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, i32> {
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())?;
this.update_environ()?;
}
Ok(1) // return non-zero on success
} 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())?;
}
this.update_environ()?;
Ok(1) // return non-zero on success
}
}
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)
}
}
fn getcwd(
&mut self,
buf_op: &OpTy<'tcx, Provenance>,
size_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
let this = self.eval_context_mut();
this.assert_target_os_is_unix("getcwd");
let buf = this.read_pointer(buf_op)?;
let size = this.read_scalar(size_op)?.to_machine_usize(&*this.tcx)?;
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());
}
// 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, u32> {
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(0);
}
// If we cannot get the current directory, we return 0
match env::current_dir() {
Ok(cwd) =>
return Ok(windows_check_buffer_size(this.write_path_to_wide_str(&cwd, buf, size)?)),
Err(e) => this.set_last_error_from_io_error(e.kind())?,
}
Ok(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)
}
}
}
#[allow(non_snake_case)]
fn SetCurrentDirectoryW(
&mut self,
path_op: &OpTy<'tcx, Provenance>, // LPCTSTR
) -> InterpResult<'tcx, i32> {
// ^ 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(0);
}
match env::set_current_dir(path) {
Ok(()) => Ok(1),
Err(e) => {
this.set_last_error_from_io_error(e.kind())?;
Ok(0)
}
}
}
/// 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.
if let Some(environ) = this.machine.env_vars.environ {
let old_vars_ptr = this.read_pointer(&environ.into())?;
this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
} else {
// No `environ` allocated yet, let's do that.
// This is memory backing an extern static, hence `ExternStatic`, not `Env`.
let layout = this.machine.layouts.mut_raw_ptr;
let place = this.allocate(layout, MiriMemoryKind::ExternStatic.into())?;
this.machine.env_vars.environ = Some(place);
}
// Collect all the pointers to each variable in a vector.
let mut vars: Vec<Pointer<Option<Provenance>>> =
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(
tcx.mk_array(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.mplace_field(&vars_place, idx)?;
this.write_pointer(var, &place.into())?;
}
this.write_pointer(vars_place.ptr, &this.machine.env_vars.environ.unwrap().into())?;
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())
}
}

View File

@ -0,0 +1,291 @@
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<Provenance>,
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_machine_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_machine_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: &PlaceTy<'tcx, Provenance>,
ptr: CodePtr,
libffi_args: Vec<libffi::high::Arg<'a>>,
) -> 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::<i8>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Int(IntTy::I16) => {
let x = ffi::call::<i16>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Int(IntTy::I32) => {
let x = ffi::call::<i32>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Int(IntTy::I64) => {
let x = ffi::call::<i64>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Int(IntTy::Isize) => {
let x = ffi::call::<isize>(ptr, libffi_args.as_slice());
// `isize` doesn't `impl Into<i128>`, 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::<u8>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Uint(UintTy::U16) => {
let x = ffi::call::<u16>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Uint(UintTy::U32) => {
let x = ffi::call::<u32>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Uint(UintTy::U64) => {
let x = ffi::call::<u64>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Uint(UintTy::Usize) => {
let x = ffi::call::<usize>(ptr, libffi_args.as_slice());
// `usize` doesn't `impl Into<i128>`, 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<CodePtr> {
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.
#[cfg(unix)]
let mut info = std::mem::MaybeUninit::<libc::Dl_info>::uninit();
#[cfg(unix)]
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: &PlaceTy<'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::<CArg>::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::<Vec<libffi::high::Arg<'_>>>();
// 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),
}
}
}

View File

@ -0,0 +1,811 @@
use std::{collections::hash_map::Entry, iter};
use log::trace;
use rustc_apfloat::Float;
use rustc_ast::expand::allocator::AllocatorKind;
use rustc_hir::{
def::DefKind,
def_id::{CrateNum, DefId, LOCAL_CRATE},
};
use rustc_middle::middle::{
codegen_fn_attrs::CodegenFnAttrFlags, dependency_format::Linkage,
exported_symbols::ExportedSymbol,
};
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::backtrace::EvalContextExt as _;
use crate::helpers::{convert::Truncate, target_os_is_unix};
#[cfg(unix)]
use crate::shims::ffi_support::EvalContextExt as _;
use crate::*;
/// Returned by `emulate_foreign_item_by_name`.
pub enum EmulateByNameResult<'mir, 'tcx> {
/// The caller is expected to jump to the return block.
NeedsJumping,
/// Jumping has already been taken care of.
AlreadyJumped,
/// A MIR body has been found for the function.
MirBody(&'mir mir::Body<'tcx>, ty::Instance<'tcx>),
/// 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> {
/// 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/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" | "powerpc" | "powerpc64" | "asmjs" | "wasm32" => 8,
"x86_64" | "aarch64" | "mips64" | "s390x" | "sparc64" => 16,
arch => bug!("Unsupported target architecture: {}", arch),
};
// Windows always aligns, even small allocations.
// Source: <https://support.microsoft.com/en-us/help/286470/how-to-use-pageheap-exe-in-windows-xp-windows-2000-and-windows-server>
// 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()
}
fn malloc(
&mut self,
size: u64,
zero_init: bool,
kind: MiriMemoryKind,
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
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<Option<Provenance>>,
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<Option<Provenance>>,
new_size: u64,
kind: MiriMemoryKind,
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
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())
}
}
}
/// Lookup the body of a function that has `link_name` as the symbol name.
fn lookup_exported_symbol(
&mut self,
link_name: Symbol,
) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
let this = self.eval_context_mut();
let tcx = this.tcx.tcx;
// If the result was cached, just return it.
// (Cannot use `or_insert` since the code below might have to throw an error.)
let entry = this.machine.exported_symbols_cache.entry(link_name);
let instance = *match entry {
Entry::Occupied(e) => e.into_mut(),
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::integer_arithmetic)]
(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)
} 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));
}
}
}
}
e.insert(instance_and_crate.map(|ic| ic.0))
}
};
match instance {
None => Ok(None), // no symbol with this name
Some(instance) => Ok(Some((this.load_mir(instance.def, None)?, instance))),
}
}
/// Emulates calling a foreign item, failing if the item is not supported.
/// This function will handle `goto_block` if needed.
/// Returns Ok(None) if the foreign item was completely handled
/// by this function.
/// Returns Ok(Some(body)) if processing the foreign item
/// is delegated to another function.
fn emulate_foreign_item(
&mut self,
def_id: DefId,
abi: Abi,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
ret: Option<mir::BasicBlock>,
unwind: StackPopUnwind,
) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
let this = self.eval_context_mut();
let link_name = this.item_link_name(def_id);
let tcx = this.tcx.tcx;
// First: functions that diverge.
let ret = match ret {
None =>
match link_name.as_str() {
"miri_start_panic" => {
// `check_shim` happens inside `handle_miri_start_panic`.
this.handle_miri_start_panic(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.into()));
}
"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,
};
// Second: functions that return immediately.
match this.emulate_foreign_item_by_name(link_name, abi, args, dest)? {
EmulateByNameResult::NeedsJumping => {
trace!("{:?}", this.dump_place(**dest));
this.go_to_block(ret);
}
EmulateByNameResult::AlreadyJumped => (),
EmulateByNameResult::MirBody(mir, instance) => return Ok(Some((mir, instance))),
EmulateByNameResult::NotSupported => {
if let Some(body) = this.lookup_exported_symbol(link_name)? {
return Ok(Some(body));
}
this.handle_unsupported(format!("can't call foreign function: {}", link_name))?;
return Ok(None);
}
}
Ok(None)
}
/// Emulates calling the internal __rust_* allocator functions
fn emulate_allocator(
&mut self,
symbol: Symbol,
default: impl FnOnce(&mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx>,
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
let this = self.eval_context_mut();
let allocator_kind = if let Some(allocator_kind) = this.tcx.allocator_kind(()) {
allocator_kind
} else {
// in real code, this symbol does not exist without an allocator
return Ok(EmulateByNameResult::NotSupported);
};
match allocator_kind {
AllocatorKind::Global => {
let (body, instance) = this
.lookup_exported_symbol(symbol)?
.expect("symbol should be present if there is a global allocator");
Ok(EmulateByNameResult::MirBody(body, instance))
}
AllocatorKind::Default => {
default(this)?;
Ok(EmulateByNameResult::NeedsJumping)
}
}
}
/// Emulates calling a foreign item using its name.
fn emulate_foreign_item_by_name(
&mut self,
link_name: Symbol,
abi: Abi,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
let this = self.eval_context_mut();
// First deal with any external C functions in linked .so file.
#[cfg(unix)]
if this.machine.external_so_lib.as_ref().is_some() {
// 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(EmulateByNameResult::NeedsJumping);
}
}
// When adding a new shim, you should follow the following pattern:
// ```
// "shim_name" => {
// let [arg1, arg2, arg3] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// let result = this.shim_name(arg1, arg2, arg3)?;
// this.write_scalar(result, dest)?;
// }
// ```
// and then define `shim_name` as a helper function in an extension trait in a suitable file
// (see e.g. `unix/fs.rs`):
// ```
// fn shim_name(
// &mut self,
// arg1: &OpTy<'tcx, Provenance>,
// arg2: &OpTy<'tcx, Provenance>,
// arg3: &OpTy<'tcx, Provenance>)
// -> InterpResult<'tcx, Scalar<Provenance>> {
// let this = self.eval_context_mut();
//
// // First thing: load all the arguments. Details depend on the shim.
// let arg1 = this.read_scalar(arg1)?.to_u32()?;
// let arg2 = this.read_pointer(arg2)?; // when you need to work with the pointer directly
// let arg3 = this.deref_operand(arg3)?; // when you want to load/store through the pointer at its declared type
//
// // ...
//
// Ok(Scalar::from_u32(42))
// }
// ```
// You might find existing shims not following this pattern, most
// likely because they predate it or because for some reason they cannot be made to fit.
// Here we dispatch all the shims for foreign functions. If you have a platform specific
// shim, add it to the corresponding submodule.
match link_name.as_str() {
// Miri-specific extern functions
"miri_static_root" => {
let [ptr] = this.check_shim(abi, Abi::Rust, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
let (alloc_id, offset, _) = this.ptr_get_alloc_id(ptr)?;
if offset != Size::ZERO {
throw_unsup_format!("pointer passed to miri_static_root must point to beginning of an allocated block");
}
this.machine.static_roots.push(alloc_id);
}
// 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)?;
}
// Standard C allocation
"malloc" => {
let [size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let size = this.read_scalar(size)?.to_machine_usize(this)?;
let res = this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::C)?;
this.write_pointer(res, dest)?;
}
"calloc" => {
let [items, len] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let items = this.read_scalar(items)?.to_machine_usize(this)?;
let len = this.read_scalar(len)?.to_machine_usize(this)?;
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)?;
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)?;
}
"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_scalar(new_size)?.to_machine_usize(this)?;
let res = this.realloc(old_ptr, new_size, MiriMemoryKind::C)?;
this.write_pointer(res, dest)?;
}
// Rust allocation
"__rust_alloc" => {
let [size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?;
let size = this.read_scalar(size)?.to_machine_usize(this)?;
let align = this.read_scalar(align)?.to_machine_usize(this)?;
return this.emulate_allocator(Symbol::intern("__rg_alloc"), |this| {
Self::check_alloc_request(size, align)?;
let ptr = this.allocate_ptr(
Size::from_bytes(size),
Align::from_bytes(align).unwrap(),
MiriMemoryKind::Rust.into(),
)?;
this.write_pointer(ptr, dest)
});
}
"__rust_alloc_zeroed" => {
let [size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?;
let size = this.read_scalar(size)?.to_machine_usize(this)?;
let align = this.read_scalar(align)?.to_machine_usize(this)?;
return this.emulate_allocator(Symbol::intern("__rg_alloc_zeroed"), |this| {
Self::check_alloc_request(size, align)?;
let ptr = this.allocate_ptr(
Size::from_bytes(size),
Align::from_bytes(align).unwrap(),
MiriMemoryKind::Rust.into(),
)?;
// We just allocated this, the access is definitely in-bounds.
this.write_bytes_ptr(ptr.into(), iter::repeat(0u8).take(usize::try_from(size).unwrap())).unwrap();
this.write_pointer(ptr, dest)
});
}
"__rust_dealloc" => {
let [ptr, old_size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
let old_size = this.read_scalar(old_size)?.to_machine_usize(this)?;
let align = this.read_scalar(align)?.to_machine_usize(this)?;
return this.emulate_allocator(Symbol::intern("__rg_dealloc"), |this| {
// No need to check old_size/align; we anyway check that they match the allocation.
this.deallocate_ptr(
ptr,
Some((Size::from_bytes(old_size), Align::from_bytes(align).unwrap())),
MiriMemoryKind::Rust.into(),
)
});
}
"__rust_realloc" => {
let [ptr, old_size, align, new_size] = this.check_shim(abi, Abi::Rust, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
let old_size = this.read_scalar(old_size)?.to_machine_usize(this)?;
let align = this.read_scalar(align)?.to_machine_usize(this)?;
let new_size = this.read_scalar(new_size)?.to_machine_usize(this)?;
// No need to check old_size; we anyway check that they match the allocation.
return this.emulate_allocator(Symbol::intern("__rg_realloc"), |this| {
Self::check_alloc_request(new_size, align)?;
let align = Align::from_bytes(align).unwrap();
let new_ptr = this.reallocate_ptr(
ptr,
Some((Size::from_bytes(old_size), align)),
Size::from_bytes(new_size),
align,
MiriMemoryKind::Rust.into(),
)?;
this.write_pointer(new_ptr, dest)
});
}
// C memory handling functions
"memcmp" => {
let [left, right, n] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let left = this.read_pointer(left)?;
let right = this.read_pointer(right)?;
let n = Size::from_bytes(this.read_scalar(n)?.to_machine_usize(this)?);
let result = {
let left_bytes = this.read_bytes_ptr_strip_provenance(left, n)?;
let right_bytes = this.read_bytes_ptr_strip_provenance(right, n)?;
use std::cmp::Ordering::*;
match left_bytes.cmp(right_bytes) {
Less => -1i32,
Equal => 0,
Greater => 1,
}
};
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"memrchr" => {
let [ptr, val, num] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
let val = this.read_scalar(val)?.to_i32()?;
let num = this.read_scalar(num)?.to_machine_usize(this)?;
// The docs say val is "interpreted as unsigned char".
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let val = val as u8;
if let Some(idx) = this
.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(num))?
.iter()
.rev()
.position(|&c| c == val)
{
let idx = u64::try_from(idx).unwrap();
#[allow(clippy::integer_arithmetic)] // idx < num, so this never wraps
let new_ptr = ptr.offset(Size::from_bytes(num - idx - 1), this)?;
this.write_pointer(new_ptr, dest)?;
} else {
this.write_null(dest)?;
}
}
"memchr" => {
let [ptr, val, num] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
let val = this.read_scalar(val)?.to_i32()?;
let num = this.read_scalar(num)?.to_machine_usize(this)?;
// The docs say val is "interpreted as unsigned char".
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let val = val as u8;
let idx = this
.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(num))?
.iter()
.position(|&c| c == val);
if let Some(idx) = idx {
let new_ptr = ptr.offset(Size::from_bytes(idx as u64), this)?;
this.write_pointer(new_ptr, dest)?;
} else {
this.write_null(dest)?;
}
}
"strlen" => {
let [ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
let n = this.read_c_str(ptr)?.len();
this.write_scalar(Scalar::from_machine_usize(u64::try_from(n).unwrap(), this), dest)?;
}
// math functions (note that there are also intrinsics for some other functions)
#[rustfmt::skip]
| "cbrtf"
| "coshf"
| "sinhf"
| "tanf"
| "tanhf"
| "acosf"
| "asinf"
| "atanf"
| "log1pf"
| "expm1f"
=> {
let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// FIXME: Using host floats.
let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
let res = match link_name.as_str() {
"cbrtf" => f.cbrt(),
"coshf" => f.cosh(),
"sinhf" => f.sinh(),
"tanf" => f.tan(),
"tanhf" => f.tanh(),
"acosf" => f.acos(),
"asinf" => f.asin(),
"atanf" => f.atan(),
"log1pf" => f.ln_1p(),
"expm1f" => f.exp_m1(),
_ => bug!(),
};
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
}
#[rustfmt::skip]
| "_hypotf"
| "hypotf"
| "atan2f"
| "fdimf"
=> {
let [f1, f2] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// 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.
let f1 = f32::from_bits(this.read_scalar(f1)?.to_u32()?);
let f2 = f32::from_bits(this.read_scalar(f2)?.to_u32()?);
let res = match link_name.as_str() {
"_hypotf" | "hypotf" => f1.hypot(f2),
"atan2f" => f1.atan2(f2),
#[allow(deprecated)]
"fdimf" => f1.abs_sub(f2),
_ => bug!(),
};
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
}
#[rustfmt::skip]
| "cbrt"
| "cosh"
| "sinh"
| "tan"
| "tanh"
| "acos"
| "asin"
| "atan"
| "log1p"
| "expm1"
=> {
let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// FIXME: Using host floats.
let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
let res = match link_name.as_str() {
"cbrt" => f.cbrt(),
"cosh" => f.cosh(),
"sinh" => f.sinh(),
"tan" => f.tan(),
"tanh" => f.tanh(),
"acos" => f.acos(),
"asin" => f.asin(),
"atan" => f.atan(),
"log1p" => f.ln_1p(),
"expm1" => f.exp_m1(),
_ => bug!(),
};
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
}
#[rustfmt::skip]
| "_hypot"
| "hypot"
| "atan2"
| "fdim"
=> {
let [f1, f2] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// FIXME: Using host floats.
let f1 = f64::from_bits(this.read_scalar(f1)?.to_u64()?);
let f2 = f64::from_bits(this.read_scalar(f2)?.to_u64()?);
let res = match link_name.as_str() {
"_hypot" | "hypot" => f1.hypot(f2),
"atan2" => f1.atan2(f2),
#[allow(deprecated)]
"fdim" => f1.abs_sub(f2),
_ => bug!(),
};
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
}
#[rustfmt::skip]
| "_ldexp"
| "ldexp"
| "scalbn"
=> {
let [x, exp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// For radix-2 (binary) systems, `ldexp` and `scalbn` are the same.
let x = this.read_scalar(x)?.to_f64()?;
let exp = this.read_scalar(exp)?.to_i32()?;
// Saturating cast to i16. Even those are outside the valid exponent range so
// `scalbn` below will do its over/underflow handling.
let exp = if exp > i32::from(i16::MAX) {
i16::MAX
} else if exp < i32::from(i16::MIN) {
i16::MIN
} else {
exp.try_into().unwrap()
};
let res = x.scalbn(exp);
this.write_scalar(Scalar::from_f64(res), dest)?;
}
// Architecture-specific shims
"llvm.x86.addcarry.64" if this.tcx.sess.target.arch == "x86_64" => {
// Computes u8+u64+u64, returning tuple (u8,u64) comprising the output carry and truncated sum.
let [c_in, a, b] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?;
let c_in = this.read_scalar(c_in)?.to_u8()?;
let a = this.read_scalar(a)?.to_u64()?;
let b = this.read_scalar(b)?.to_u64()?;
#[allow(clippy::integer_arithmetic)] // adding two u64 and a u8 cannot wrap in a u128
let wide_sum = u128::from(c_in) + u128::from(a) + u128::from(b);
#[allow(clippy::integer_arithmetic)] // it's a u128, we can shift by 64
let (c_out, sum) = ((wide_sum >> 64).truncate::<u8>(), wide_sum.truncate::<u64>());
let c_out_field = this.place_field(dest, 0)?;
this.write_scalar(Scalar::from_u8(c_out), &c_out_field)?;
let sum_field = this.place_field(dest, 1)?;
this.write_scalar(Scalar::from_u64(sum), &sum_field)?;
}
"llvm.x86.sse2.pause" if this.tcx.sess.target.arch == "x86" || this.tcx.sess.target.arch == "x86_64" => {
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.yield_active_thread();
}
"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 {
15 => { // SY ("full system scope")
this.yield_active_thread();
}
_ => {
throw_unsup_format!("unsupported llvm.aarch64.isb argument {}", arg);
}
}
}
// Platform-specific shims
_ => match this.tcx.sess.target.os.as_ref() {
target if target_os_is_unix(target) => return shims::unix::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
"windows" => return shims::windows::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
target => throw_unsup_format!("the target `{}` is not supported", target),
}
};
// 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(EmulateByNameResult::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(())
}
}

View File

@ -0,0 +1,288 @@
use rustc_middle::{mir, mir::BinOp, ty};
use crate::*;
use helpers::check_arg_count;
pub enum AtomicOp {
/// The `bool` indicates whether the result of the operation should be negated
/// (must be a boolean-typed operation).
MirOp(mir::BinOp, bool),
Max,
Min,
}
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.
fn emulate_atomic_intrinsic(
&mut self,
intrinsic_name: &str,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
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 {
"seqcst" => AtomicReadOrd::SeqCst,
"acquire" => AtomicReadOrd::Acquire,
"relaxed" => AtomicReadOrd::Relaxed,
_ => throw_unsup_format!("unsupported read ordering `{ord}`"),
})
}
fn write_ord<'tcx>(ord: &str) -> InterpResult<'tcx, AtomicWriteOrd> {
Ok(match ord {
"seqcst" => AtomicWriteOrd::SeqCst,
"release" => AtomicWriteOrd::Release,
"relaxed" => AtomicWriteOrd::Relaxed,
_ => throw_unsup_format!("unsupported write ordering `{ord}`"),
})
}
fn rw_ord<'tcx>(ord: &str) -> InterpResult<'tcx, AtomicRwOrd> {
Ok(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}`"),
})
}
fn fence_ord<'tcx>(ord: &str) -> InterpResult<'tcx, AtomicFenceOrd> {
Ok(match ord {
"seqcst" => AtomicFenceOrd::SeqCst,
"acqrel" => AtomicFenceOrd::AcqRel,
"acquire" => AtomicFenceOrd::Acquire,
"release" => AtomicFenceOrd::Release,
_ => throw_unsup_format!("unsupported 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)?)?,
["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)?)?,
["cxchg", ord1, 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)?)?,
["or", ord] =>
this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitOr, false), rw_ord(ord)?)?,
["xor", ord] =>
this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitXor, false), rw_ord(ord)?)?,
["and", ord] =>
this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, false), rw_ord(ord)?)?,
["nand", ord] =>
this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, true), rw_ord(ord)?)?,
["xadd", ord] =>
this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::Add, false), rw_ord(ord)?)?,
["xsub", ord] =>
this.atomic_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_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_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_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_op(args, dest, AtomicOp::Max, rw_ord(ord)?)?;
}
_ => throw_unsup_format!("unimplemented intrinsic: `atomic_{intrinsic_name}`"),
}
Ok(())
}
}
impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for MiriInterpCx<'mir, 'tcx> {}
trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
fn atomic_load(
&mut self,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
atomic: AtomicReadOrd,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let [place] = check_arg_count(args)?;
let place = this.deref_operand(place)?;
// Perform atomic load.
let val = this.read_scalar_atomic(&place, atomic)?;
// Perform regular store.
this.write_scalar(val, dest)?;
Ok(())
}
fn atomic_store(
&mut self,
args: &[OpTy<'tcx, Provenance>],
atomic: AtomicWriteOrd,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let [place, val] = check_arg_count(args)?;
let place = this.deref_operand(place)?;
// Perform regular load.
let val = this.read_scalar(val)?;
// Perform atomic store
this.write_scalar_atomic(val, &place, atomic)?;
Ok(())
}
fn compiler_fence_intrinsic(
&mut self,
args: &[OpTy<'tcx, Provenance>],
atomic: AtomicFenceOrd,
) -> InterpResult<'tcx> {
let [] = check_arg_count(args)?;
let _ = atomic;
//FIXME: compiler fences are currently ignored
Ok(())
}
fn atomic_fence_intrinsic(
&mut self,
args: &[OpTy<'tcx, Provenance>],
atomic: AtomicFenceOrd,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let [] = check_arg_count(args)?;
this.atomic_fence(atomic)?;
Ok(())
}
fn atomic_op(
&mut self,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
atomic_op: AtomicOp,
atomic: AtomicRwOrd,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let [place, rhs] = check_arg_count(args)?;
let place = this.deref_operand(place)?;
let rhs = this.read_immediate(rhs)?;
if !place.layout.ty.is_integral() && !place.layout.ty.is_unsafe_ptr() {
span_bug!(
this.cur_span(),
"atomic arithmetic operations only work on integer and raw pointer types",
);
}
if rhs.layout.ty != place.layout.ty {
span_bug!(this.cur_span(), "atomic arithmetic operation type mismatch");
}
match atomic_op {
AtomicOp::Min => {
let old = this.atomic_min_max_scalar(&place, rhs, true, atomic)?;
this.write_immediate(*old, dest)?; // old value is returned
Ok(())
}
AtomicOp::Max => {
let old = this.atomic_min_max_scalar(&place, rhs, false, atomic)?;
this.write_immediate(*old, dest)?; // old value is returned
Ok(())
}
AtomicOp::MirOp(op, neg) => {
let old = this.atomic_op_immediate(&place, &rhs, op, neg, atomic)?;
this.write_immediate(*old, dest)?; // old value is returned
Ok(())
}
}
}
fn atomic_exchange(
&mut self,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
atomic: AtomicRwOrd,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let [place, new] = check_arg_count(args)?;
let place = this.deref_operand(place)?;
let new = this.read_scalar(new)?;
let old = this.atomic_exchange_scalar(&place, new, atomic)?;
this.write_scalar(old, dest)?; // old value is returned
Ok(())
}
fn atomic_compare_exchange_impl(
&mut self,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
success: AtomicRwOrd,
fail: AtomicReadOrd,
can_fail_spuriously: bool,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let [place, expect_old, new] = check_arg_count(args)?;
let place = this.deref_operand(place)?;
let expect_old = this.read_immediate(expect_old)?; // read as immediate for the sake of `binary_op()`
let new = this.read_scalar(new)?;
let old = this.atomic_compare_exchange_scalar(
&place,
&expect_old,
new,
success,
fail,
can_fail_spuriously,
)?;
// Return old value.
this.write_immediate(old, dest)?;
Ok(())
}
fn atomic_compare_exchange(
&mut self,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
success: AtomicRwOrd,
fail: AtomicReadOrd,
) -> InterpResult<'tcx> {
self.atomic_compare_exchange_impl(args, dest, success, fail, false)
}
fn atomic_compare_exchange_weak(
&mut self,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
success: AtomicRwOrd,
fail: AtomicReadOrd,
) -> InterpResult<'tcx> {
self.atomic_compare_exchange_impl(args, dest, success, fail, true)
}
}

View File

@ -0,0 +1,429 @@
mod atomic;
mod simd;
use std::iter;
use log::trace;
use rustc_apfloat::{Float, Round};
use rustc_middle::ty::layout::{IntegerExt, LayoutOf};
use rustc_middle::{
mir,
ty::{self, FloatTy, Ty},
};
use rustc_target::abi::Integer;
use crate::*;
use atomic::EvalContextExt as _;
use helpers::check_arg_count;
use simd::EvalContextExt as _;
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn call_intrinsic(
&mut self,
instance: ty::Instance<'tcx>,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
ret: Option<mir::BasicBlock>,
_unwind: StackPopUnwind,
) -> InterpResult<'tcx> {
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(());
}
// All remaining supported intrinsics have a return place.
let intrinsic_name = this.tcx.item_name(instance.def_id());
let intrinsic_name = intrinsic_name.as_str();
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 {
"try" => return this.handle_try(args, dest, ret),
_ => {}
}
// The rest jumps to `ret` immediately.
this.emulate_intrinsic_by_name(intrinsic_name, args, dest)?;
trace!("{:?}", this.dump_place(**dest));
this.go_to_block(ret);
Ok(())
}
/// Emulates a Miri-supported intrinsic (not supported by the core engine).
fn emulate_intrinsic_by_name(
&mut self,
intrinsic_name: &str,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
if let Some(name) = intrinsic_name.strip_prefix("atomic_") {
return this.emulate_atomic_intrinsic(name, args, dest);
}
if let Some(name) = intrinsic_name.strip_prefix("simd_") {
return this.emulate_simd_intrinsic(name, args, dest);
}
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, _overflowed, _ty) =
this.overflowing_binary_op(mir::BinOp::Eq, &left, &right)?;
// We're type punning a bool as an u8 here.
this.write_scalar(val, dest)?;
}
"const_allocate" => {
// For now, for compatibility with the run-time implementation of this, we just return null.
// See <https://github.com/rust-lang/rust/issues/93935>.
this.write_null(dest)?;
}
"const_deallocate" => {
// complete NOP
}
// Raw memory accesses
"volatile_load" => {
let [place] = check_arg_count(args)?;
let place = this.deref_operand(place)?;
this.copy_op(&place.into(), dest, /*allow_transmute*/ false)?;
}
"volatile_store" => {
let [place, dest] = check_arg_count(args)?;
let place = this.deref_operand(place)?;
this.copy_op(dest, &place.into(), /*allow_transmute*/ false)?;
}
"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_layout = this.layout_of(ty)?;
let val_byte = this.read_scalar(val_byte)?.to_u8()?;
let ptr = this.read_pointer(ptr)?;
let count = this.read_scalar(count)?.to_machine_usize(this)?;
// `checked_mul` enforces a too small bound (the correct one would probably be machine_isize_max),
// but no actual allocation can be big enough for the difference to be noticeable.
let byte_count = ty_layout.size.checked_mul(count, this).ok_or_else(|| {
err_ub_format!("overflow computing total size of `{intrinsic_name}`")
})?;
this.write_bytes_ptr(ptr, iter::repeat(val_byte).take(byte_count.bytes_usize()))?;
}
// Floating-point operations
"fabsf32" => {
let [f] = check_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
// Can be implemented in soft-floats.
this.write_scalar(Scalar::from_f32(f.abs()), dest)?;
}
"fabsf64" => {
let [f] = check_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
// Can be implemented in soft-floats.
this.write_scalar(Scalar::from_f64(f.abs()), dest)?;
}
#[rustfmt::skip]
| "sinf32"
| "cosf32"
| "sqrtf32"
| "expf32"
| "exp2f32"
| "logf32"
| "log10f32"
| "log2f32"
| "floorf32"
| "ceilf32"
| "truncf32"
| "roundf32"
=> {
let [f] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
let f = match intrinsic_name {
"sinf32" => f.sin(),
"cosf32" => f.cos(),
"sqrtf32" => f.sqrt(),
"expf32" => f.exp(),
"exp2f32" => f.exp2(),
"logf32" => f.ln(),
"log10f32" => f.log10(),
"log2f32" => f.log2(),
"floorf32" => f.floor(),
"ceilf32" => f.ceil(),
"truncf32" => f.trunc(),
"roundf32" => f.round(),
_ => bug!(),
};
this.write_scalar(Scalar::from_u32(f.to_bits()), dest)?;
}
#[rustfmt::skip]
| "sinf64"
| "cosf64"
| "sqrtf64"
| "expf64"
| "exp2f64"
| "logf64"
| "log10f64"
| "log2f64"
| "floorf64"
| "ceilf64"
| "truncf64"
| "roundf64"
=> {
let [f] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
let f = match intrinsic_name {
"sinf64" => f.sin(),
"cosf64" => f.cos(),
"sqrtf64" => f.sqrt(),
"expf64" => f.exp(),
"exp2f64" => f.exp2(),
"logf64" => f.ln(),
"log10f64" => f.log10(),
"log2f64" => f.log2(),
"floorf64" => f.floor(),
"ceilf64" => f.ceil(),
"truncf64" => f.trunc(),
"roundf64" => f.round(),
_ => bug!(),
};
this.write_scalar(Scalar::from_u64(f.to_bits()), 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> {
Ok(match x.layout.ty.kind() {
ty::Float(FloatTy::F32) => x.to_scalar().to_f32()?.is_finite(),
ty::Float(FloatTy::F64) => x.to_scalar().to_f64()?.is_finite(),
_ => bug!(
"`{intrinsic_name}` called with non-float input type {ty:?}",
ty = x.layout.ty,
),
})
};
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",
),
_ => {}
}
this.binop_ignore_overflow(op, &a, &b, dest)?;
}
#[rustfmt::skip]
| "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()?;
let res = match intrinsic_name {
"minnumf32" => a.min(b),
"maxnumf32" => a.max(b),
"copysignf32" => a.copy_sign(b),
_ => bug!(),
};
this.write_scalar(Scalar::from_f32(res), dest)?;
}
#[rustfmt::skip]
| "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()?;
let res = match intrinsic_name {
"minnumf64" => a.min(b),
"maxnumf64" => a.max(b),
"copysignf64" => a.copy_sign(b),
_ => bug!(),
};
this.write_scalar(Scalar::from_f64(res), dest)?;
}
"powf32" => {
let [f, f2] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
let f2 = f32::from_bits(this.read_scalar(f2)?.to_u32()?);
let res = f.powf(f2);
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
}
"powf64" => {
let [f, f2] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
let f2 = f64::from_bits(this.read_scalar(f2)?.to_u64()?);
let res = f.powf(f2);
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
}
"fmaf32" => {
let [a, b, c] = check_arg_count(args)?;
// FIXME: Using host floats, to work around https://github.com/rust-lang/miri/issues/2468.
let a = f32::from_bits(this.read_scalar(a)?.to_u32()?);
let b = f32::from_bits(this.read_scalar(b)?.to_u32()?);
let c = f32::from_bits(this.read_scalar(c)?.to_u32()?);
let res = a.mul_add(b, c);
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
}
"fmaf64" => {
let [a, b, c] = check_arg_count(args)?;
// FIXME: Using host floats, to work around https://github.com/rust-lang/miri/issues/2468.
let a = f64::from_bits(this.read_scalar(a)?.to_u64()?);
let b = f64::from_bits(this.read_scalar(b)?.to_u64()?);
let c = f64::from_bits(this.read_scalar(c)?.to_u64()?);
let res = a.mul_add(b, c);
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
}
"powif32" => {
let [f, i] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
let i = this.read_scalar(i)?.to_i32()?;
let res = f.powi(i);
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
}
"powif64" => {
let [f, i] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
let i = this.read_scalar(i)?.to_i32()?;
let res = f.powi(i);
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
}
"float_to_int_unchecked" => {
let [val] = check_arg_count(args)?;
let val = this.read_immediate(val)?;
let res = match val.layout.ty.kind() {
ty::Float(FloatTy::F32) =>
this.float_to_int_unchecked(val.to_scalar().to_f32()?, dest.layout.ty)?,
ty::Float(FloatTy::F64) =>
this.float_to_int_unchecked(val.to_scalar().to_f64()?, dest.layout.ty)?,
_ =>
span_bug!(
this.cur_span(),
"`float_to_int_unchecked` called with non-float input type {:?}",
val.layout.ty
),
};
this.write_scalar(res, dest)?;
}
// Other
"exact_div" => {
let [num, denom] = check_arg_count(args)?;
this.exact_div(&this.read_immediate(num)?, &this.read_immediate(denom)?, dest)?;
}
"breakpoint" => {
let [] = check_arg_count(args)?;
// normally this would raise a SIGTRAP, which aborts if no debugger is connected
throw_machine_stop!(TerminationInfo::Abort(format!("Trace/breakpoint trap")))
}
name => throw_unsup_format!("unimplemented intrinsic: `{name}`"),
}
Ok(())
}
fn float_to_int_unchecked<F>(
&self,
f: F,
dest_ty: Ty<'tcx>,
) -> InterpResult<'tcx, Scalar<Provenance>>
where
F: Float + Into<Scalar<Provenance>>,
{
let this = self.eval_context_ref();
// Step 1: cut off the fractional part of `f`. The result of this is
// guaranteed to be precisely representable in IEEE floats.
let f = f.round_to_integral(Round::TowardZero).value;
// Step 2: Cast the truncated float to the target integer type and see if we lose any information in this step.
Ok(match dest_ty.kind() {
// Unsigned
ty::Uint(t) => {
let size = Integer::from_uint_ty(this, *t).size();
let res = f.to_u128(size.bits_usize());
if res.status.is_empty() {
// No status flags means there was no further rounding or other loss of precision.
Scalar::from_uint(res.value, size)
} else {
// `f` was not representable in this integer type.
throw_ub_format!(
"`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`",
);
}
}
// Signed
ty::Int(t) => {
let size = Integer::from_int_ty(this, *t).size();
let res = f.to_i128(size.bits_usize());
if res.status.is_empty() {
// No status flags means there was no further rounding or other loss of precision.
Scalar::from_int(res.value, size)
} else {
// `f` was not representable in this integer type.
throw_ub_format!(
"`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`",
);
}
}
// Nothing else
_ =>
span_bug!(
this.cur_span(),
"`float_to_int_unchecked` called with non-int output type {dest_ty:?}"
),
})
}
}

View File

@ -0,0 +1,627 @@
use rustc_apfloat::Float;
use rustc_middle::ty::layout::{HasParamEnv, LayoutOf};
use rustc_middle::{mir, ty, ty::FloatTy};
use rustc_target::abi::{Endian, HasDataLayout, Size};
use crate::*;
use helpers::check_arg_count;
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.
fn emulate_simd_intrinsic(
&mut self,
intrinsic_name: &str,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
match intrinsic_name {
#[rustfmt::skip]
| "neg"
| "fabs"
| "ceil"
| "floor"
| "round"
| "trunc"
| "fsqrt" => {
let [op] = check_arg_count(args)?;
let (op, op_len) = this.operand_to_simd(op)?;
let (dest, dest_len) = this.place_to_simd(dest)?;
assert_eq!(dest_len, op_len);
#[derive(Copy, Clone)]
enum HostFloatOp {
Ceil,
Floor,
Round,
Trunc,
Sqrt,
}
#[derive(Copy, Clone)]
enum Op {
MirOp(mir::UnOp),
Abs,
HostOp(HostFloatOp),
}
let which = match intrinsic_name {
"neg" => Op::MirOp(mir::UnOp::Neg),
"fabs" => Op::Abs,
"ceil" => Op::HostOp(HostFloatOp::Ceil),
"floor" => Op::HostOp(HostFloatOp::Floor),
"round" => Op::HostOp(HostFloatOp::Round),
"trunc" => Op::HostOp(HostFloatOp::Trunc),
"fsqrt" => Op::HostOp(HostFloatOp::Sqrt),
_ => unreachable!(),
};
for i in 0..dest_len {
let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?;
let dest = this.mplace_index(&dest, i)?;
let val = match which {
Op::MirOp(mir_op) => this.unary_op(mir_op, &op)?.to_scalar(),
Op::Abs => {
// Works for f32 and f64.
let ty::Float(float_ty) = op.layout.ty.kind() else {
span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name)
};
let op = op.to_scalar();
match float_ty {
FloatTy::F32 => Scalar::from_f32(op.to_f32()?.abs()),
FloatTy::F64 => Scalar::from_f64(op.to_f64()?.abs()),
}
}
Op::HostOp(host_op) => {
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
match float_ty {
FloatTy::F32 => {
let f = f32::from_bits(op.to_scalar().to_u32()?);
let res = match host_op {
HostFloatOp::Ceil => f.ceil(),
HostFloatOp::Floor => f.floor(),
HostFloatOp::Round => f.round(),
HostFloatOp::Trunc => f.trunc(),
HostFloatOp::Sqrt => f.sqrt(),
};
Scalar::from_u32(res.to_bits())
}
FloatTy::F64 => {
let f = f64::from_bits(op.to_scalar().to_u64()?);
let res = match host_op {
HostFloatOp::Ceil => f.ceil(),
HostFloatOp::Floor => f.floor(),
HostFloatOp::Round => f.round(),
HostFloatOp::Trunc => f.trunc(),
HostFloatOp::Sqrt => f.sqrt(),
};
Scalar::from_u64(res.to_bits())
}
}
}
};
this.write_scalar(val, &dest.into())?;
}
}
#[rustfmt::skip]
| "add"
| "sub"
| "mul"
| "div"
| "rem"
| "shl"
| "shr"
| "and"
| "or"
| "xor"
| "eq"
| "ne"
| "lt"
| "le"
| "gt"
| "ge"
| "fmax"
| "fmin"
| "saturating_add"
| "saturating_sub"
| "arith_offset" => {
use mir::BinOp;
let [left, right] = check_arg_count(args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.place_to_simd(dest)?;
assert_eq!(dest_len, left_len);
assert_eq!(dest_len, right_len);
enum Op {
MirOp(BinOp),
SaturatingOp(BinOp),
FMax,
FMin,
WrappingOffset,
}
let which = match intrinsic_name {
"add" => Op::MirOp(BinOp::Add),
"sub" => Op::MirOp(BinOp::Sub),
"mul" => Op::MirOp(BinOp::Mul),
"div" => Op::MirOp(BinOp::Div),
"rem" => Op::MirOp(BinOp::Rem),
"shl" => Op::MirOp(BinOp::Shl),
"shr" => Op::MirOp(BinOp::Shr),
"and" => Op::MirOp(BinOp::BitAnd),
"or" => Op::MirOp(BinOp::BitOr),
"xor" => Op::MirOp(BinOp::BitXor),
"eq" => Op::MirOp(BinOp::Eq),
"ne" => Op::MirOp(BinOp::Ne),
"lt" => Op::MirOp(BinOp::Lt),
"le" => Op::MirOp(BinOp::Le),
"gt" => Op::MirOp(BinOp::Gt),
"ge" => Op::MirOp(BinOp::Ge),
"fmax" => Op::FMax,
"fmin" => Op::FMin,
"saturating_add" => Op::SaturatingOp(BinOp::Add),
"saturating_sub" => Op::SaturatingOp(BinOp::Sub),
"arith_offset" => Op::WrappingOffset,
_ => unreachable!(),
};
for i in 0..dest_len {
let left = this.read_immediate(&this.mplace_index(&left, i)?.into())?;
let right = this.read_immediate(&this.mplace_index(&right, i)?.into())?;
let dest = this.mplace_index(&dest, i)?;
let val = match which {
Op::MirOp(mir_op) => {
let (val, overflowed, ty) = this.overflowing_binary_op(mir_op, &left, &right)?;
if matches!(mir_op, BinOp::Shl | BinOp::Shr) {
// Shifts have extra UB as SIMD operations that the MIR binop does not have.
// See <https://github.com/rust-lang/rust/issues/91237>.
if overflowed {
let r_val = right.to_scalar().to_bits(right.layout.size)?;
throw_ub_format!("overflowing shift by {r_val} in `simd_{intrinsic_name}` in SIMD lane {i}");
}
}
if matches!(mir_op, BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge) {
// Special handling for boolean-returning operations
assert_eq!(ty, this.tcx.types.bool);
let val = val.to_bool().unwrap();
bool_to_simd_element(val, dest.layout.size)
} else {
assert_ne!(ty, this.tcx.types.bool);
assert_eq!(ty, dest.layout.ty);
val
}
}
Op::SaturatingOp(mir_op) => {
this.saturating_arith(mir_op, &left, &right)?
}
Op::WrappingOffset => {
let ptr = left.to_scalar().to_pointer(this)?;
let offset_count = right.to_scalar().to_machine_isize(this)?;
let pointee_ty = left.layout.ty.builtin_deref(true).unwrap().ty;
let pointee_size = i64::try_from(this.layout_of(pointee_ty)?.size.bytes()).unwrap();
let offset_bytes = offset_count.wrapping_mul(pointee_size);
let offset_ptr = ptr.wrapping_signed_offset(offset_bytes, this);
Scalar::from_maybe_pointer(offset_ptr, this)
}
Op::FMax => {
fmax_op(&left, &right)?
}
Op::FMin => {
fmin_op(&left, &right)?
}
};
this.write_scalar(val, &dest.into())?;
}
}
"fma" => {
let [a, b, c] = check_arg_count(args)?;
let (a, a_len) = this.operand_to_simd(a)?;
let (b, b_len) = this.operand_to_simd(b)?;
let (c, c_len) = this.operand_to_simd(c)?;
let (dest, dest_len) = this.place_to_simd(dest)?;
assert_eq!(dest_len, a_len);
assert_eq!(dest_len, b_len);
assert_eq!(dest_len, c_len);
for i in 0..dest_len {
let a = this.read_scalar(&this.mplace_index(&a, i)?.into())?;
let b = this.read_scalar(&this.mplace_index(&b, i)?.into())?;
let c = this.read_scalar(&this.mplace_index(&c, i)?.into())?;
let dest = this.mplace_index(&dest, i)?;
// Works for f32 and f64.
// FIXME: using host floats to work around https://github.com/rust-lang/miri/issues/2468.
let ty::Float(float_ty) = dest.layout.ty.kind() else {
span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name)
};
let val = match float_ty {
FloatTy::F32 => {
let a = f32::from_bits(a.to_u32()?);
let b = f32::from_bits(b.to_u32()?);
let c = f32::from_bits(c.to_u32()?);
let res = a.mul_add(b, c);
Scalar::from_u32(res.to_bits())
}
FloatTy::F64 => {
let a = f64::from_bits(a.to_u64()?);
let b = f64::from_bits(b.to_u64()?);
let c = f64::from_bits(c.to_u64()?);
let res = a.mul_add(b, c);
Scalar::from_u64(res.to_bits())
}
};
this.write_scalar(val, &dest.into())?;
}
}
#[rustfmt::skip]
| "reduce_and"
| "reduce_or"
| "reduce_xor"
| "reduce_any"
| "reduce_all"
| "reduce_max"
| "reduce_min" => {
use mir::BinOp;
let [op] = check_arg_count(args)?;
let (op, op_len) = this.operand_to_simd(op)?;
let imm_from_bool =
|b| ImmTy::from_scalar(Scalar::from_bool(b), this.machine.layouts.bool);
enum Op {
MirOp(BinOp),
MirOpBool(BinOp),
Max,
Min,
}
let which = match intrinsic_name {
"reduce_and" => Op::MirOp(BinOp::BitAnd),
"reduce_or" => Op::MirOp(BinOp::BitOr),
"reduce_xor" => Op::MirOp(BinOp::BitXor),
"reduce_any" => Op::MirOpBool(BinOp::BitOr),
"reduce_all" => Op::MirOpBool(BinOp::BitAnd),
"reduce_max" => Op::Max,
"reduce_min" => Op::Min,
_ => unreachable!(),
};
// Initialize with first lane, then proceed with the rest.
let mut res = this.read_immediate(&this.mplace_index(&op, 0)?.into())?;
if matches!(which, Op::MirOpBool(_)) {
// Convert to `bool` scalar.
res = imm_from_bool(simd_element_to_bool(res)?);
}
for i in 1..op_len {
let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?;
res = match which {
Op::MirOp(mir_op) => {
this.binary_op(mir_op, &res, &op)?
}
Op::MirOpBool(mir_op) => {
let op = imm_from_bool(simd_element_to_bool(op)?);
this.binary_op(mir_op, &res, &op)?
}
Op::Max => {
if matches!(res.layout.ty.kind(), ty::Float(_)) {
ImmTy::from_scalar(fmax_op(&res, &op)?, res.layout)
} else {
// Just boring integers, so NaNs to worry about
if this.binary_op(BinOp::Ge, &res, &op)?.to_scalar().to_bool()? {
res
} else {
op
}
}
}
Op::Min => {
if matches!(res.layout.ty.kind(), ty::Float(_)) {
ImmTy::from_scalar(fmin_op(&res, &op)?, res.layout)
} else {
// Just boring integers, so NaNs to worry about
if this.binary_op(BinOp::Le, &res, &op)?.to_scalar().to_bool()? {
res
} else {
op
}
}
}
};
}
this.write_immediate(*res, dest)?;
}
#[rustfmt::skip]
| "reduce_add_ordered"
| "reduce_mul_ordered" => {
use mir::BinOp;
let [op, init] = check_arg_count(args)?;
let (op, op_len) = this.operand_to_simd(op)?;
let init = this.read_immediate(init)?;
let mir_op = match intrinsic_name {
"reduce_add_ordered" => BinOp::Add,
"reduce_mul_ordered" => BinOp::Mul,
_ => unreachable!(),
};
let mut res = init;
for i in 0..op_len {
let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?;
res = this.binary_op(mir_op, &res, &op)?;
}
this.write_immediate(*res, dest)?;
}
"select" => {
let [mask, yes, no] = check_arg_count(args)?;
let (mask, mask_len) = this.operand_to_simd(mask)?;
let (yes, yes_len) = this.operand_to_simd(yes)?;
let (no, no_len) = this.operand_to_simd(no)?;
let (dest, dest_len) = this.place_to_simd(dest)?;
assert_eq!(dest_len, mask_len);
assert_eq!(dest_len, yes_len);
assert_eq!(dest_len, no_len);
for i in 0..dest_len {
let mask = this.read_immediate(&this.mplace_index(&mask, i)?.into())?;
let yes = this.read_immediate(&this.mplace_index(&yes, i)?.into())?;
let no = this.read_immediate(&this.mplace_index(&no, i)?.into())?;
let dest = this.mplace_index(&dest, i)?;
let val = if simd_element_to_bool(mask)? { yes } else { no };
this.write_immediate(*val, &dest.into())?;
}
}
"select_bitmask" => {
let [mask, yes, no] = check_arg_count(args)?;
let (yes, yes_len) = this.operand_to_simd(yes)?;
let (no, no_len) = this.operand_to_simd(no)?;
let (dest, dest_len) = this.place_to_simd(dest)?;
let bitmask_len = dest_len.max(8);
assert!(mask.layout.ty.is_integral());
assert!(bitmask_len <= 64);
assert_eq!(bitmask_len, mask.layout.size.bits());
assert_eq!(dest_len, yes_len);
assert_eq!(dest_len, no_len);
let dest_len = u32::try_from(dest_len).unwrap();
let bitmask_len = u32::try_from(bitmask_len).unwrap();
let mask: u64 =
this.read_scalar(mask)?.to_bits(mask.layout.size)?.try_into().unwrap();
for i in 0..dest_len {
let mask = mask
& 1u64
.checked_shl(simd_bitmask_index(i, dest_len, this.data_layout().endian))
.unwrap();
let yes = this.read_immediate(&this.mplace_index(&yes, i.into())?.into())?;
let no = this.read_immediate(&this.mplace_index(&no, i.into())?.into())?;
let dest = this.mplace_index(&dest, i.into())?;
let val = if mask != 0 { yes } else { no };
this.write_immediate(*val, &dest.into())?;
}
for i in dest_len..bitmask_len {
// If the mask is "padded", ensure that padding is all-zero.
let mask = mask & 1u64.checked_shl(i).unwrap();
if mask != 0 {
throw_ub_format!(
"a SIMD bitmask less than 8 bits long must be filled with 0s for the remaining bits"
);
}
}
}
#[rustfmt::skip]
"cast" | "as" => {
let [op] = check_arg_count(args)?;
let (op, op_len) = this.operand_to_simd(op)?;
let (dest, dest_len) = this.place_to_simd(dest)?;
assert_eq!(dest_len, op_len);
let safe_cast = intrinsic_name == "as";
for i in 0..dest_len {
let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?;
let dest = this.mplace_index(&dest, i)?;
let val = match (op.layout.ty.kind(), dest.layout.ty.kind()) {
// Int-to-(int|float): always safe
(ty::Int(_) | ty::Uint(_), ty::Int(_) | ty::Uint(_) | ty::Float(_)) =>
this.misc_cast(&op, dest.layout.ty)?,
// Float-to-float: always safe
(ty::Float(_), ty::Float(_)) =>
this.misc_cast(&op, dest.layout.ty)?,
// Float-to-int in safe mode
(ty::Float(_), ty::Int(_) | ty::Uint(_)) if safe_cast =>
this.misc_cast(&op, dest.layout.ty)?,
// Float-to-int in unchecked mode
(ty::Float(FloatTy::F32), ty::Int(_) | ty::Uint(_)) if !safe_cast =>
this.float_to_int_unchecked(op.to_scalar().to_f32()?, dest.layout.ty)?.into(),
(ty::Float(FloatTy::F64), ty::Int(_) | ty::Uint(_)) if !safe_cast =>
this.float_to_int_unchecked(op.to_scalar().to_f64()?, dest.layout.ty)?.into(),
_ =>
throw_unsup_format!(
"Unsupported SIMD cast from element type {from_ty} to {to_ty}",
from_ty = op.layout.ty,
to_ty = dest.layout.ty,
),
};
this.write_immediate(val, &dest.into())?;
}
}
"shuffle" => {
let [left, right, index] = check_arg_count(args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.place_to_simd(dest)?;
// `index` is an array, not a SIMD type
let ty::Array(_, index_len) = index.layout.ty.kind() else {
span_bug!(this.cur_span(), "simd_shuffle index argument has non-array type {}", index.layout.ty)
};
let index_len = index_len.eval_usize(*this.tcx, this.param_env());
assert_eq!(left_len, right_len);
assert_eq!(index_len, dest_len);
for i in 0..dest_len {
let src_index: u64 = this
.read_immediate(&this.operand_index(index, i)?)?
.to_scalar()
.to_u32()?
.into();
let dest = this.mplace_index(&dest, i)?;
let val = if src_index < left_len {
this.read_immediate(&this.mplace_index(&left, src_index)?.into())?
} else if src_index < left_len.checked_add(right_len).unwrap() {
let right_idx = src_index.checked_sub(left_len).unwrap();
this.read_immediate(&this.mplace_index(&right, right_idx)?.into())?
} else {
span_bug!(
this.cur_span(),
"simd_shuffle index {src_index} is out of bounds for 2 vectors of size {left_len}",
);
};
this.write_immediate(*val, &dest.into())?;
}
}
"gather" => {
let [passthru, ptrs, mask] = check_arg_count(args)?;
let (passthru, passthru_len) = this.operand_to_simd(passthru)?;
let (ptrs, ptrs_len) = this.operand_to_simd(ptrs)?;
let (mask, mask_len) = this.operand_to_simd(mask)?;
let (dest, dest_len) = this.place_to_simd(dest)?;
assert_eq!(dest_len, passthru_len);
assert_eq!(dest_len, ptrs_len);
assert_eq!(dest_len, mask_len);
for i in 0..dest_len {
let passthru = this.read_immediate(&this.mplace_index(&passthru, i)?.into())?;
let ptr = this.read_immediate(&this.mplace_index(&ptrs, i)?.into())?;
let mask = this.read_immediate(&this.mplace_index(&mask, i)?.into())?;
let dest = this.mplace_index(&dest, i)?;
let val = if simd_element_to_bool(mask)? {
let place = this.deref_operand(&ptr.into())?;
this.read_immediate(&place.into())?
} else {
passthru
};
this.write_immediate(*val, &dest.into())?;
}
}
"scatter" => {
let [value, ptrs, mask] = check_arg_count(args)?;
let (value, value_len) = this.operand_to_simd(value)?;
let (ptrs, ptrs_len) = this.operand_to_simd(ptrs)?;
let (mask, mask_len) = this.operand_to_simd(mask)?;
assert_eq!(ptrs_len, value_len);
assert_eq!(ptrs_len, mask_len);
for i in 0..ptrs_len {
let value = this.read_immediate(&this.mplace_index(&value, i)?.into())?;
let ptr = this.read_immediate(&this.mplace_index(&ptrs, i)?.into())?;
let mask = this.read_immediate(&this.mplace_index(&mask, i)?.into())?;
if simd_element_to_bool(mask)? {
let place = this.deref_operand(&ptr.into())?;
this.write_immediate(*value, &place.into())?;
}
}
}
"bitmask" => {
let [op] = check_arg_count(args)?;
let (op, op_len) = this.operand_to_simd(op)?;
let bitmask_len = op_len.max(8);
assert!(dest.layout.ty.is_integral());
assert!(bitmask_len <= 64);
assert_eq!(bitmask_len, dest.layout.size.bits());
let op_len = u32::try_from(op_len).unwrap();
let mut res = 0u64;
for i in 0..op_len {
let op = this.read_immediate(&this.mplace_index(&op, i.into())?.into())?;
if simd_element_to_bool(op)? {
res |= 1u64
.checked_shl(simd_bitmask_index(i, op_len, this.data_layout().endian))
.unwrap();
}
}
this.write_int(res, dest)?;
}
name => throw_unsup_format!("unimplemented intrinsic: `simd_{name}`"),
}
Ok(())
}
}
fn bool_to_simd_element(b: bool, size: Size) -> Scalar<Provenance> {
// SIMD uses all-1 as pattern for "true"
let val = if b { -1 } else { 0 };
Scalar::from_int(val, size)
}
fn simd_element_to_bool(elem: ImmTy<'_, Provenance>) -> InterpResult<'_, bool> {
let val = elem.to_scalar().to_int(elem.layout.size)?;
Ok(match val {
0 => false,
-1 => true,
_ => throw_ub_format!("each element of a SIMD mask must be all-0-bits or all-1-bits"),
})
}
fn simd_bitmask_index(idx: u32, vec_len: u32, endianess: Endian) -> u32 {
assert!(idx < vec_len);
match endianess {
Endian::Little => idx,
#[allow(clippy::integer_arithmetic)] // idx < vec_len
Endian::Big => vec_len - 1 - idx, // reverse order of bits
}
}
fn fmax_op<'tcx>(
left: &ImmTy<'tcx, Provenance>,
right: &ImmTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
assert_eq!(left.layout.ty, right.layout.ty);
let ty::Float(float_ty) = left.layout.ty.kind() else {
bug!("fmax operand is not a float")
};
let left = left.to_scalar();
let right = right.to_scalar();
Ok(match float_ty {
FloatTy::F32 => Scalar::from_f32(left.to_f32()?.max(right.to_f32()?)),
FloatTy::F64 => Scalar::from_f64(left.to_f64()?.max(right.to_f64()?)),
})
}
fn fmin_op<'tcx>(
left: &ImmTy<'tcx, Provenance>,
right: &ImmTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
assert_eq!(left.layout.ty, right.layout.ty);
let ty::Float(float_ty) = left.layout.ty.kind() else {
bug!("fmin operand is not a float")
};
let left = left.to_scalar();
let right = right.to_scalar();
Ok(match float_ty {
FloatTy::F32 => Scalar::from_f32(left.to_f32()?.min(right.to_f32()?)),
FloatTy::F64 => Scalar::from_f64(left.to_f64()?.min(right.to_f64()?)),
})
}

View File

@ -0,0 +1,107 @@
#![warn(clippy::integer_arithmetic)]
mod backtrace;
#[cfg(unix)]
pub mod ffi_support;
pub mod foreign_items;
pub mod intrinsics;
pub mod unix;
pub mod windows;
pub mod dlsym;
pub mod env;
pub mod os_str;
pub mod panic;
pub mod time;
pub mod tls;
// End module management, begin local code
use log::trace;
use rustc_middle::{mir, ty};
use rustc_target::spec::abi::Abi;
use crate::*;
use helpers::check_arg_count;
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn find_mir_or_eval_fn(
&mut self,
instance: ty::Instance<'tcx>,
abi: Abi,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
ret: Option<mir::BasicBlock>,
unwind: StackPopUnwind,
) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
let this = self.eval_context_mut();
trace!("eval_fn_call: {:#?}, {:?}", instance, dest);
// There are some more lang items we want to hook that CTFE does not hook (yet).
if this.tcx.lang_items().align_offset_fn() == Some(instance.def.def_id()) {
let [ptr, align] = check_arg_count(args)?;
if this.align_offset(ptr, align, dest, ret, unwind)? {
return Ok(None);
}
}
// Try to see if we can do something about foreign items.
if this.tcx.is_foreign_item(instance.def_id()) {
// An external function call that does not have a MIR body. We either find MIR elsewhere
// or emulate its effect.
// This will be Ok(None) if we're emulating the intrinsic entirely within Miri (no need
// to run extra MIR), and Ok(Some(body)) if we found MIR to run for the
// foreign function
// Any needed call to `goto_block` will be performed by `emulate_foreign_item`.
return this.emulate_foreign_item(instance.def_id(), abi, args, dest, ret, unwind);
}
// Otherwise, load the MIR.
Ok(Some((this.load_mir(instance.def, None)?, instance)))
}
/// Returns `true` if the computation was performed, and `false` if we should just evaluate
/// the actual MIR of `align_offset`.
fn align_offset(
&mut self,
ptr_op: &OpTy<'tcx, Provenance>,
align_op: &OpTy<'tcx, Provenance>,
dest: &PlaceTy<'tcx, Provenance>,
ret: Option<mir::BasicBlock>,
unwind: StackPopUnwind,
) -> InterpResult<'tcx, bool> {
let this = self.eval_context_mut();
let ret = ret.unwrap();
if this.machine.check_alignment != AlignmentCheck::Symbolic {
// Just use actual implementation.
return Ok(false);
}
let req_align = this.read_scalar(align_op)?.to_machine_usize(this)?;
// Stop if the alignment is not a power of two.
if !req_align.is_power_of_two() {
this.start_panic("align_offset: align is not a power-of-two", unwind)?;
return Ok(true); // nothing left to do
}
let ptr = this.read_pointer(ptr_op)?;
if let Ok((alloc_id, _offset, _)) = this.ptr_try_get_alloc_id(ptr) {
// Only do anything if we can identify the allocation this goes to.
let (_size, cur_align, _kind) = this.get_alloc_info(alloc_id);
if cur_align.bytes() >= req_align {
// If the allocation alignment is at least the required alignment we use the
// real implementation.
return Ok(false);
}
}
// Return error result (usize::MAX), and jump to caller.
this.write_scalar(Scalar::from_machine_usize(this.machine_usize_max(), this), dest)?;
this.go_to_block(ret);
Ok(true)
}
}

View File

@ -0,0 +1,308 @@
use std::borrow::Cow;
use std::ffi::{OsStr, OsString};
use std::iter;
use std::path::{Path, PathBuf};
#[cfg(unix)]
use std::os::unix::ffi::{OsStrExt, OsStringExt};
#[cfg(windows)]
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use rustc_middle::ty::layout::LayoutOf;
use rustc_target::abi::{Align, Size};
use crate::*;
/// Represent how path separator conversion should be done.
pub enum PathConversion {
HostToTarget,
TargetToHost,
}
#[cfg(unix)]
pub fn os_str_to_bytes<'a, 'tcx>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
Ok(os_str.as_bytes())
}
#[cfg(not(unix))]
pub fn os_str_to_bytes<'a, 'tcx>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
// On non-unix platforms the best we can do to transform bytes from/to OS strings is to do the
// intermediate transformation into strings. Which invalidates non-utf8 paths that are actually
// valid.
os_str
.to_str()
.map(|s| s.as_bytes())
.ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into())
}
#[cfg(unix)]
pub fn bytes_to_os_str<'a, 'tcx>(bytes: &'a [u8]) -> InterpResult<'tcx, &'a OsStr> {
Ok(OsStr::from_bytes(bytes))
}
#[cfg(not(unix))]
pub fn bytes_to_os_str<'a, 'tcx>(bytes: &'a [u8]) -> InterpResult<'tcx, &'a OsStr> {
let s = std::str::from_utf8(bytes)
.map_err(|_| err_unsup_format!("{:?} is not a valid utf-8 string", bytes))?;
Ok(OsStr::new(s))
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Helper function to read an OsString from a null-terminated sequence of bytes, which is what
/// the Unix APIs usually handle.
fn read_os_str_from_c_str<'a>(
&'a self,
ptr: Pointer<Option<Provenance>>,
) -> InterpResult<'tcx, &'a OsStr>
where
'tcx: 'a,
'mir: 'a,
{
let this = self.eval_context_ref();
let bytes = this.read_c_str(ptr)?;
bytes_to_os_str(bytes)
}
/// Helper function to read an OsString from a 0x0000-terminated sequence of u16,
/// which is what the Windows APIs usually handle.
fn read_os_str_from_wide_str<'a>(
&'a self,
ptr: Pointer<Option<Provenance>>,
) -> InterpResult<'tcx, OsString>
where
'tcx: 'a,
'mir: 'a,
{
#[cfg(windows)]
pub fn u16vec_to_osstring<'tcx>(u16_vec: Vec<u16>) -> InterpResult<'tcx, OsString> {
Ok(OsString::from_wide(&u16_vec[..]))
}
#[cfg(not(windows))]
pub fn u16vec_to_osstring<'tcx>(u16_vec: Vec<u16>) -> InterpResult<'tcx, OsString> {
let s = String::from_utf16(&u16_vec[..])
.map_err(|_| err_unsup_format!("{:?} is not a valid utf-16 string", u16_vec))?;
Ok(s.into())
}
let u16_vec = self.eval_context_ref().read_wide_str(ptr)?;
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.
fn write_os_str_to_c_str(
&mut self,
os_str: &OsStr,
ptr: Pointer<Option<Provenance>>,
size: u64,
) -> InterpResult<'tcx, (bool, u64)> {
let bytes = os_str_to_bytes(os_str)?;
// If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required null
// terminator to memory using the `ptr` pointer would cause an out-of-bounds access.
let string_length = u64::try_from(bytes.len()).unwrap();
let string_length = string_length.checked_add(1).unwrap();
if size < string_length {
return Ok((false, string_length));
}
self.eval_context_mut()
.write_bytes_ptr(ptr, bytes.iter().copied().chain(iter::once(0u8)))?;
Ok((true, string_length))
}
/// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, 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
/// 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.`
fn write_os_str_to_wide_str(
&mut self,
os_str: &OsStr,
ptr: Pointer<Option<Provenance>>,
size: u64,
) -> InterpResult<'tcx, (bool, u64)> {
#[cfg(windows)]
fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec<u16>> {
Ok(os_str.encode_wide().collect())
}
#[cfg(not(windows))]
fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec<u16>> {
// On non-Windows platforms the best we can do to transform Vec<u16> from/to OS strings is to do the
// intermediate transformation into strings. Which invalidates non-utf8 paths that are actually
// valid.
os_str
.to_str()
.map(|s| s.encode_utf16().collect())
.ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into())
}
let u16_vec = os_str_to_u16vec(os_str)?;
// If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required
// 0x0000 terminator to memory would cause an out-of-bounds access.
let string_length = u64::try_from(u16_vec.len()).unwrap();
let string_length = string_length.checked_add(1).unwrap();
if size < string_length {
return Ok((false, string_length));
}
// Store the UTF-16 string.
let size2 = Size::from_bytes(2);
let this = self.eval_context_mut();
let mut alloc = this
.get_ptr_alloc_mut(ptr, size2 * string_length, Align::from_bytes(2).unwrap())?
.unwrap(); // not a ZST, so we will get a result
for (offset, wchar) in u16_vec.into_iter().chain(iter::once(0x0000)).enumerate() {
let offset = u64::try_from(offset).unwrap();
alloc.write_scalar(alloc_range(size2 * offset, size2), Scalar::from_u16(wchar))?;
}
Ok((true, string_length))
}
/// 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<MiriMemoryKind>,
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0` terminator.
let this = self.eval_context_mut();
let arg_type = this.tcx.mk_array(this.tcx.types.u8, size);
let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind)?;
assert!(self.write_os_str_to_c_str(os_str, arg_place.ptr, size).unwrap().0);
Ok(arg_place.ptr)
}
/// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of `u16`.
fn alloc_os_str_as_wide_str(
&mut self,
os_str: &OsStr,
memkind: MemoryKind<MiriMemoryKind>,
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
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 = this.tcx.mk_array(this.tcx.types.u16, size);
let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind)?;
assert!(self.write_os_str_to_wide_str(os_str, arg_place.ptr, size).unwrap().0);
Ok(arg_place.ptr)
}
/// Read a null-terminated sequence of bytes, and perform path separator conversion if needed.
fn read_path_from_c_str<'a>(
&'a self,
ptr: Pointer<Option<Provenance>>,
) -> InterpResult<'tcx, Cow<'a, Path>>
where
'tcx: 'a,
'mir: 'a,
{
let this = self.eval_context_ref();
let os_str = this.read_os_str_from_c_str(ptr)?;
Ok(match this.convert_path_separator(Cow::Borrowed(os_str), PathConversion::TargetToHost) {
Cow::Borrowed(x) => Cow::Borrowed(Path::new(x)),
Cow::Owned(y) => Cow::Owned(PathBuf::from(y)),
})
}
/// Read a null-terminated sequence of `u16`s, and perform path separator conversion if needed.
fn read_path_from_wide_str(
&self,
ptr: Pointer<Option<Provenance>>,
) -> InterpResult<'tcx, PathBuf> {
let this = self.eval_context_ref();
let os_str = this.read_os_str_from_wide_str(ptr)?;
Ok(this
.convert_path_separator(Cow::Owned(os_str), PathConversion::TargetToHost)
.into_owned()
.into())
}
/// Write a Path to the machine memory (as a null-terminated sequence of bytes),
/// adjusting path separators if needed.
fn write_path_to_c_str(
&mut self,
path: &Path,
ptr: Pointer<Option<Provenance>>,
size: u64,
) -> InterpResult<'tcx, (bool, u64)> {
let this = self.eval_context_mut();
let os_str = this
.convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
this.write_os_str_to_c_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(
&mut self,
path: &Path,
ptr: Pointer<Option<Provenance>>,
size: u64,
) -> InterpResult<'tcx, (bool, u64)> {
let this = self.eval_context_mut();
let os_str = this
.convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
this.write_os_str_to_wide_str(&os_str, ptr, size)
}
/// Allocate enough memory to store a Path as a null-terminated sequence of bytes,
/// adjusting path separators if needed.
fn alloc_path_as_c_str(
&mut self,
path: &Path,
memkind: MemoryKind<MiriMemoryKind>,
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
let this = self.eval_context_mut();
let os_str = this
.convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
this.alloc_os_str_as_c_str(&os_str, memkind)
}
fn convert_path_separator<'a>(
&self,
os_str: Cow<'a, OsStr>,
direction: PathConversion,
) -> Cow<'a, OsStr> {
let this = self.eval_context_ref();
let target_os = &this.tcx.sess.target.os;
#[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 converted = os_str
.encode_wide()
.map(|wchar| if wchar == from as u16 { to as u16 } else { wchar })
.collect::<Vec<_>>();
Cow::Owned(OsString::from_wide(&converted))
};
#[cfg(unix)]
return if target_os == "windows" {
// Windows target, Unix host.
let (from, to) = match direction {
PathConversion::HostToTarget => ('/', '\\'),
PathConversion::TargetToHost => ('\\', '/'),
};
let converted = os_str
.as_bytes()
.iter()
.map(|&wchar| if wchar == from as u8 { to as u8 } else { wchar })
.collect::<Vec<_>>();
Cow::Owned(OsString::from_vec(converted))
} else {
// Unix-on-Unix, all is fine.
os_str
};
}
}

View File

@ -0,0 +1,227 @@
//! Panic runtime for Miri.
//!
//! The core pieces of the runtime are:
//! - An implementation of `__rust_maybe_catch_panic` that pushes the invoked stack frame with
//! some extra metadata derived from the panic-catching arguments of `__rust_maybe_catch_panic`.
//! - A hack in `libpanic_unwind` that calls the `miri_start_panic` intrinsic instead of the
//! target-native panic runtime. (This lives in the rustc repo.)
//! - An implementation of `miri_start_panic` that stores its argument (the panic payload), and then
//! immediately returns, but on the *unwind* edge (not the normal return edge), thus initiating unwinding.
//! - A hook executed each time a frame is popped, such that if the frame pushed by `__rust_maybe_catch_panic`
//! gets popped *during unwinding*, we take the panic payload and store it according to the extra
//! metadata we remembered when pushing said frame.
use log::trace;
use rustc_ast::Mutability;
use rustc_middle::{mir, ty};
use rustc_span::Symbol;
use rustc_target::spec::abi::Abi;
use rustc_target::spec::PanicStrategy;
use crate::*;
use helpers::check_arg_count;
/// Holds all of the relevant data for when unwinding hits a `try` frame.
#[derive(Debug)]
pub struct CatchUnwindData<'tcx> {
/// The `catch_fn` callback to call in case of a panic.
catch_fn: Pointer<Option<Provenance>>,
/// The `data` argument for that callback.
data: Scalar<Provenance>,
/// The return place from the original call to `try`.
dest: PlaceTy<'tcx, Provenance>,
/// The return block from the original call to `try`.
ret: mir::BasicBlock,
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Handles the special `miri_start_panic` intrinsic, which is called
/// by libpanic_unwind to delegate the actual unwinding process to Miri.
fn handle_miri_start_panic(
&mut self,
abi: Abi,
link_name: Symbol,
args: &[OpTy<'tcx, Provenance>],
unwind: StackPopUnwind,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
trace!("miri_start_panic: {:?}", this.frame().instance);
// Get the raw pointer stored in arg[0] (the panic payload).
let [payload] = this.check_shim(abi, Abi::Rust, link_name, args)?;
let payload = this.read_scalar(payload)?;
let thread = this.active_thread_mut();
assert!(thread.panic_payload.is_none(), "the panic runtime should avoid double-panics");
thread.panic_payload = Some(payload);
// Jump to the unwind block to begin unwinding.
this.unwind_to_block(unwind)?;
Ok(())
}
/// Handles the `try` intrinsic, the underlying implementation of `std::panicking::try`.
fn handle_try(
&mut self,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
ret: mir::BasicBlock,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
// Signature:
// fn r#try(try_fn: fn(*mut u8), data: *mut u8, catch_fn: fn(*mut u8, *mut u8)) -> i32
// Calls `try_fn` with `data` as argument. If that executes normally, returns 0.
// If that unwinds, calls `catch_fn` with the first argument being `data` and
// then second argument being a target-dependent `payload` (i.e. it is up to us to define
// what that is), and returns 1.
// The `payload` is passed (by libstd) to `__rust_panic_cleanup`, which is then expected to
// return a `Box<dyn Any + Send + 'static>`.
// In Miri, `miri_start_panic` is passed exactly that type, so we make the `payload` simply
// a pointer to `Box<dyn Any + Send + 'static>`.
// Get all the arguments.
let [try_fn, data, catch_fn] = check_arg_count(args)?;
let try_fn = this.read_pointer(try_fn)?;
let data = this.read_scalar(data)?;
let catch_fn = this.read_pointer(catch_fn)?;
// Now we make a function call, and pass `data` as first and only argument.
let f_instance = this.get_ptr_fn(try_fn)?.as_instance()?;
trace!("try_fn: {:?}", f_instance);
this.call_function(
f_instance,
Abi::Rust,
&[data.into()],
None,
// Directly return to caller.
StackPopCleanup::Goto { ret: Some(ret), unwind: StackPopUnwind::Skip },
)?;
// We ourselves will return `0`, eventually (will be overwritten if we catch a panic).
this.write_null(dest)?;
// In unwind mode, we tag this frame with the extra data needed to catch unwinding.
// This lets `handle_stack_pop` (below) know that we should stop unwinding
// when we pop this frame.
if this.tcx.sess.panic_strategy() == PanicStrategy::Unwind {
this.frame_mut().extra.catch_unwind =
Some(CatchUnwindData { catch_fn, data, dest: dest.clone(), ret });
}
Ok(())
}
fn handle_stack_pop_unwind(
&mut self,
mut extra: FrameData<'tcx>,
unwinding: bool,
) -> InterpResult<'tcx, StackPopJump> {
let this = self.eval_context_mut();
trace!("handle_stack_pop_unwind(extra = {:?}, unwinding = {})", extra, unwinding);
// We only care about `catch_panic` if we're unwinding - if we're doing a normal
// return, then we don't need to do anything special.
if let (true, Some(catch_unwind)) = (unwinding, extra.catch_unwind.take()) {
// We've just popped a frame that was pushed by `try`,
// and we are unwinding, so we should catch that.
trace!(
"unwinding: found catch_panic frame during unwinding: {:?}",
this.frame().instance
);
// We set the return value of `try` to 1, since there was a panic.
this.write_scalar(Scalar::from_i32(1), &catch_unwind.dest)?;
// The Thread's `panic_payload` holds what was passed to `miri_start_panic`.
// This is exactly the second argument we need to pass to `catch_fn`.
let payload = this.active_thread_mut().panic_payload.take().unwrap();
// Push the `catch_fn` stackframe.
let f_instance = this.get_ptr_fn(catch_unwind.catch_fn)?.as_instance()?;
trace!("catch_fn: {:?}", f_instance);
this.call_function(
f_instance,
Abi::Rust,
&[catch_unwind.data.into(), payload.into()],
None,
// Directly return to caller of `try`.
StackPopCleanup::Goto { ret: Some(catch_unwind.ret), unwind: StackPopUnwind::Skip },
)?;
// We pushed a new stack frame, the engine should not do any jumping now!
Ok(StackPopJump::NoJump)
} else {
Ok(StackPopJump::Normal)
}
}
/// Start a panic in the interpreter with the given message as payload.
fn start_panic(&mut self, msg: &str, unwind: StackPopUnwind) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
// First arg: message.
let msg = this.allocate_str(msg, MiriMemoryKind::Machine.into(), Mutability::Not);
// Call the lang item.
let panic = this.tcx.lang_items().panic_fn().unwrap();
let panic = ty::Instance::mono(this.tcx.tcx, panic);
this.call_function(
panic,
Abi::Rust,
&[msg.to_ref(this)],
None,
StackPopCleanup::Goto { ret: None, unwind },
)
}
fn assert_panic(
&mut self,
msg: &mir::AssertMessage<'tcx>,
unwind: Option<mir::BasicBlock>,
) -> InterpResult<'tcx> {
use rustc_middle::mir::AssertKind::*;
let this = self.eval_context_mut();
match msg {
BoundsCheck { index, len } => {
// Forward to `panic_bounds_check` lang item.
// First arg: index.
let index = this.read_scalar(&this.eval_operand(index, None)?)?;
// Second arg: len.
let len = this.read_scalar(&this.eval_operand(len, None)?)?;
// Call the lang item.
let panic_bounds_check = this.tcx.lang_items().panic_bounds_check_fn().unwrap();
let panic_bounds_check = ty::Instance::mono(this.tcx.tcx, panic_bounds_check);
this.call_function(
panic_bounds_check,
Abi::Rust,
&[index.into(), len.into()],
None,
StackPopCleanup::Goto {
ret: None,
unwind: match unwind {
Some(cleanup) => StackPopUnwind::Cleanup(cleanup),
None => StackPopUnwind::Skip,
},
},
)?;
}
_ => {
// Forward everything else to `panic` lang item.
this.start_panic(
msg.description(),
match unwind {
Some(cleanup) => StackPopUnwind::Cleanup(cleanup),
None => StackPopUnwind::Skip,
},
)?;
}
}
Ok(())
}
}

View File

@ -0,0 +1,255 @@
use std::time::{Duration, SystemTime};
use crate::*;
/// Returns the time elapsed between the provided time and the unix epoch as a `Duration`.
pub fn system_time_to_duration<'tcx>(time: &SystemTime) -> InterpResult<'tcx, Duration> {
time.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|_| err_unsup_format!("times before the Unix epoch are not supported").into())
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn clock_gettime(
&mut self,
clk_id_op: &OpTy<'tcx, Provenance>,
tp_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
// This clock support is deliberately minimal because a lot of clock types have fiddly
// properties (is it possible for Miri to be suspended independently of the host?). If you
// have a use for another clock type, please open an issue.
let this = self.eval_context_mut();
this.assert_target_os("linux", "clock_gettime");
let clk_id = this.read_scalar(clk_id_op)?.to_i32()?;
// Linux has two main kinds of clocks. REALTIME clocks return the actual time since the
// Unix epoch, including effects which may cause time to move backwards such as NTP.
// Linux further distinguishes regular and "coarse" clocks, but the "coarse" version
// is just specified to be "faster and less precise", so we implement both the same way.
let absolute_clocks =
[this.eval_libc_i32("CLOCK_REALTIME")?, this.eval_libc_i32("CLOCK_REALTIME_COARSE")?];
// The second kind is MONOTONIC clocks for which 0 is an arbitrary time point, but they are
// never allowed to go backwards. We don't need to do any additonal monotonicity
// enforcement because std::time::Instant already guarantees that it is monotonic.
let relative_clocks =
[this.eval_libc_i32("CLOCK_MONOTONIC")?, this.eval_libc_i32("CLOCK_MONOTONIC_COARSE")?];
let duration = if absolute_clocks.contains(&clk_id) {
this.check_no_isolation("`clock_gettime` with `REALTIME` clocks")?;
system_time_to_duration(&SystemTime::now())?
} else if relative_clocks.contains(&clk_id) {
this.machine.clock.now().duration_since(this.machine.clock.anchor())
} else {
let einval = this.eval_libc("EINVAL")?;
this.set_last_error(einval)?;
return Ok(Scalar::from_i32(-1));
};
let tv_sec = duration.as_secs();
let tv_nsec = duration.subsec_nanos();
this.write_int_fields(&[tv_sec.into(), tv_nsec.into()], &this.deref_operand(tp_op)?)?;
Ok(Scalar::from_i32(0))
}
fn gettimeofday(
&mut self,
tv_op: &OpTy<'tcx, Provenance>,
tz_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
this.assert_target_os_is_unix("gettimeofday");
this.check_no_isolation("`gettimeofday`")?;
// Using tz is obsolete and should always be null
let tz = this.read_pointer(tz_op)?;
if !this.ptr_is_null(tz)? {
let einval = this.eval_libc("EINVAL")?;
this.set_last_error(einval)?;
return Ok(-1);
}
let duration = system_time_to_duration(&SystemTime::now())?;
let tv_sec = duration.as_secs();
let tv_usec = duration.subsec_micros();
this.write_int_fields(&[tv_sec.into(), tv_usec.into()], &this.deref_operand(tv_op)?)?;
Ok(0)
}
#[allow(non_snake_case, clippy::integer_arithmetic)]
fn GetSystemTimeAsFileTime(
&mut self,
LPFILETIME_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
this.assert_target_os("windows", "GetSystemTimeAsFileTime");
this.check_no_isolation("`GetSystemTimeAsFileTime`")?;
let NANOS_PER_SEC = this.eval_windows_u64("time", "NANOS_PER_SEC")?;
let INTERVALS_PER_SEC = this.eval_windows_u64("time", "INTERVALS_PER_SEC")?;
let INTERVALS_TO_UNIX_EPOCH = this.eval_windows_u64("time", "INTERVALS_TO_UNIX_EPOCH")?;
let NANOS_PER_INTERVAL = NANOS_PER_SEC / INTERVALS_PER_SEC;
let SECONDS_TO_UNIX_EPOCH = INTERVALS_TO_UNIX_EPOCH / INTERVALS_PER_SEC;
let duration = system_time_to_duration(&SystemTime::now())?
+ Duration::from_secs(SECONDS_TO_UNIX_EPOCH);
let duration_ticks = u64::try_from(duration.as_nanos() / u128::from(NANOS_PER_INTERVAL))
.map_err(|_| err_unsup_format!("programs running more than 2^64 Windows ticks after the Windows epoch are not supported"))?;
let dwLowDateTime = u32::try_from(duration_ticks & 0x00000000FFFFFFFF).unwrap();
let dwHighDateTime = u32::try_from((duration_ticks & 0xFFFFFFFF00000000) >> 32).unwrap();
this.write_int_fields(
&[dwLowDateTime.into(), dwHighDateTime.into()],
&this.deref_operand(LPFILETIME_op)?,
)?;
Ok(())
}
#[allow(non_snake_case)]
fn QueryPerformanceCounter(
&mut self,
lpPerformanceCount_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
this.assert_target_os("windows", "QueryPerformanceCounter");
// QueryPerformanceCounter uses a hardware counter as its basis.
// Miri will emulate a counter with a resolution of 1 nanosecond.
let duration = this.machine.clock.now().duration_since(this.machine.clock.anchor());
let qpc = i64::try_from(duration.as_nanos()).map_err(|_| {
err_unsup_format!("programs running longer than 2^63 nanoseconds are not supported")
})?;
this.write_scalar(
Scalar::from_i64(qpc),
&this.deref_operand(lpPerformanceCount_op)?.into(),
)?;
Ok(-1) // return non-zero on success
}
#[allow(non_snake_case)]
fn QueryPerformanceFrequency(
&mut self,
lpFrequency_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
this.assert_target_os("windows", "QueryPerformanceFrequency");
// Retrieves the frequency of the hardware performance counter.
// The frequency of the performance counter is fixed at system boot and
// is consistent across all processors.
// Miri emulates a "hardware" performance counter with a resolution of 1ns,
// and thus 10^9 counts per second.
this.write_scalar(
Scalar::from_i64(1_000_000_000),
&this.deref_operand(lpFrequency_op)?.into(),
)?;
Ok(-1) // Return non-zero on success
}
fn mach_absolute_time(&self) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_ref();
this.assert_target_os("macos", "mach_absolute_time");
// This returns a u64, with time units determined dynamically by `mach_timebase_info`.
// We return plain nanoseconds.
let duration = this.machine.clock.now().duration_since(this.machine.clock.anchor());
let res = u64::try_from(duration.as_nanos()).map_err(|_| {
err_unsup_format!("programs running longer than 2^64 nanoseconds are not supported")
})?;
Ok(Scalar::from_u64(res))
}
fn mach_timebase_info(
&mut self,
info_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
this.assert_target_os("macos", "mach_timebase_info");
let info = this.deref_operand(info_op)?;
// Since our emulated ticks in `mach_absolute_time` *are* nanoseconds,
// no scaling needs to happen.
let (numer, denom) = (1, 1);
this.write_int_fields(&[numer.into(), denom.into()], &info)?;
Ok(Scalar::from_i32(0)) // KERN_SUCCESS
}
fn nanosleep(
&mut self,
req_op: &OpTy<'tcx, Provenance>,
_rem: &OpTy<'tcx, Provenance>, // Signal handlers are not supported, so rem will never be written to.
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
this.assert_target_os_is_unix("nanosleep");
let duration = match this.read_timespec(&this.deref_operand(req_op)?)? {
Some(duration) => duration,
None => {
let einval = this.eval_libc("EINVAL")?;
this.set_last_error(einval)?;
return Ok(-1);
}
};
// If adding the duration overflows, let's just sleep for an hour. Waking up early is always acceptable.
let now = this.machine.clock.now();
let timeout_time = now
.checked_add(duration)
.unwrap_or_else(|| now.checked_add(Duration::from_secs(3600)).unwrap());
let active_thread = this.get_active_thread();
this.block_thread(active_thread);
this.register_timeout_callback(
active_thread,
Time::Monotonic(timeout_time),
Box::new(move |ecx| {
ecx.unblock_thread(active_thread);
Ok(())
}),
);
Ok(0)
}
#[allow(non_snake_case)]
fn Sleep(&mut self, timeout: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
this.assert_target_os("windows", "Sleep");
let timeout_ms = this.read_scalar(timeout)?.to_u32()?;
let duration = Duration::from_millis(timeout_ms.into());
let timeout_time = this.machine.clock.now().checked_add(duration).unwrap();
let active_thread = this.get_active_thread();
this.block_thread(active_thread);
this.register_timeout_callback(
active_thread,
Time::Monotonic(timeout_time),
Box::new(move |ecx| {
ecx.unblock_thread(active_thread);
Ok(())
}),
);
Ok(())
}
}

View File

@ -0,0 +1,400 @@
//! Implement thread-local storage.
use std::collections::btree_map::Entry as BTreeEntry;
use std::collections::hash_map::Entry as HashMapEntry;
use std::collections::BTreeMap;
use log::trace;
use rustc_data_structures::fx::FxHashMap;
use rustc_middle::ty;
use rustc_target::abi::{HasDataLayout, Size};
use rustc_target::spec::abi::Abi;
use crate::*;
pub type TlsKey = u128;
#[derive(Clone, Debug)]
pub struct TlsEntry<'tcx> {
/// The data for this key. None is used to represent NULL.
/// (We normalize this early to avoid having to do a NULL-ptr-test each time we access the data.)
data: BTreeMap<ThreadId, Scalar<Provenance>>,
dtor: Option<ty::Instance<'tcx>>,
}
#[derive(Clone, Debug)]
struct RunningDtorsState {
/// The last TlsKey used to retrieve a TLS destructor. `None` means that we
/// have not tried to retrieve a TLS destructor yet or that we already tried
/// all keys.
last_dtor_key: Option<TlsKey>,
}
#[derive(Debug)]
pub struct TlsData<'tcx> {
/// The Key to use for the next thread-local allocation.
next_key: TlsKey,
/// pthreads-style thread-local storage.
keys: BTreeMap<TlsKey, TlsEntry<'tcx>>,
/// A single per thread destructor of the thread local storage (that's how
/// things work on macOS) with a data argument.
macos_thread_dtors: BTreeMap<ThreadId, (ty::Instance<'tcx>, Scalar<Provenance>)>,
/// State for currently running TLS dtors. If this map contains a key for a
/// specific thread, it means that we are in the "destruct" phase, during
/// which some operations are UB.
dtors_running: FxHashMap<ThreadId, RunningDtorsState>,
}
impl<'tcx> Default for TlsData<'tcx> {
fn default() -> Self {
TlsData {
next_key: 1, // start with 1 as we must not use 0 on Windows
keys: Default::default(),
macos_thread_dtors: Default::default(),
dtors_running: Default::default(),
}
}
}
impl<'tcx> TlsData<'tcx> {
/// Generate a new TLS key with the given destructor.
/// `max_size` determines the integer size the key has to fit in.
#[allow(clippy::integer_arithmetic)]
pub fn create_tls_key(
&mut self,
dtor: Option<ty::Instance<'tcx>>,
max_size: Size,
) -> InterpResult<'tcx, TlsKey> {
let new_key = self.next_key;
self.next_key += 1;
self.keys.try_insert(new_key, TlsEntry { data: Default::default(), dtor }).unwrap();
trace!("New TLS key allocated: {} with dtor {:?}", new_key, dtor);
if max_size.bits() < 128 && new_key >= (1u128 << max_size.bits()) {
throw_unsup_format!("we ran out of TLS key space");
}
Ok(new_key)
}
pub fn delete_tls_key(&mut self, key: TlsKey) -> InterpResult<'tcx> {
match self.keys.remove(&key) {
Some(_) => {
trace!("TLS key {} removed", key);
Ok(())
}
None => throw_ub_format!("removing a non-existig TLS key: {}", key),
}
}
pub fn load_tls(
&self,
key: TlsKey,
thread_id: ThreadId,
cx: &impl HasDataLayout,
) -> InterpResult<'tcx, Scalar<Provenance>> {
match self.keys.get(&key) {
Some(TlsEntry { data, .. }) => {
let value = data.get(&thread_id).copied();
trace!("TLS key {} for thread {:?} loaded: {:?}", key, thread_id, value);
Ok(value.unwrap_or_else(|| Scalar::null_ptr(cx)))
}
None => throw_ub_format!("loading from a non-existing TLS key: {}", key),
}
}
pub fn store_tls(
&mut self,
key: TlsKey,
thread_id: ThreadId,
new_data: Scalar<Provenance>,
cx: &impl HasDataLayout,
) -> InterpResult<'tcx> {
match self.keys.get_mut(&key) {
Some(TlsEntry { data, .. }) => {
if new_data.to_machine_usize(cx)? != 0 {
trace!("TLS key {} for thread {:?} stored: {:?}", key, thread_id, new_data);
data.insert(thread_id, new_data);
} else {
trace!("TLS key {} for thread {:?} removed", key, thread_id);
data.remove(&thread_id);
}
Ok(())
}
None => throw_ub_format!("storing to a non-existing TLS key: {}", key),
}
}
/// Set the thread wide destructor of the thread local storage for the given
/// thread. This function is used to implement `_tlv_atexit` shim on MacOS.
///
/// Thread wide dtors are available only on MacOS. There is one destructor
/// per thread as can be guessed from the following comment in the
/// [`_tlv_atexit`
/// implementation](https://github.com/opensource-apple/dyld/blob/195030646877261f0c8c7ad8b001f52d6a26f514/src/threadLocalVariables.c#L389):
///
/// NOTE: this does not need locks because it only operates on current thread data
pub fn set_macos_thread_dtor(
&mut self,
thread: ThreadId,
dtor: ty::Instance<'tcx>,
data: Scalar<Provenance>,
) -> InterpResult<'tcx> {
if self.dtors_running.contains_key(&thread) {
// UB, according to libstd docs.
throw_ub_format!(
"setting thread's local storage destructor while destructors are already running"
);
}
if self.macos_thread_dtors.insert(thread, (dtor, data)).is_some() {
throw_unsup_format!(
"setting more than one thread local storage destructor for the same thread is not supported"
);
}
Ok(())
}
/// Returns a dtor, its argument and its index, if one is supposed to run.
/// `key` is the last dtors that was run; we return the *next* one after that.
///
/// An optional destructor function may be associated with each key value.
/// At thread exit, if a key value has a non-NULL destructor pointer,
/// and the thread has a non-NULL value associated with that key,
/// the value of the key is set to NULL, and then the function pointed
/// to is called with the previously associated value as its sole argument.
/// **The order of destructor calls is unspecified if more than one destructor
/// exists for a thread when it exits.**
///
/// If, after all the destructors have been called for all non-NULL values
/// with associated destructors, there are still some non-NULL values with
/// associated destructors, then the process is repeated.
/// If, after at least {PTHREAD_DESTRUCTOR_ITERATIONS} iterations of destructor
/// calls for outstanding non-NULL values, there are still some non-NULL values
/// with associated destructors, implementations may stop calling destructors,
/// or they may continue calling destructors until no non-NULL values with
/// associated destructors exist, even though this might result in an infinite loop.
fn fetch_tls_dtor(
&mut self,
key: Option<TlsKey>,
thread_id: ThreadId,
) -> Option<(ty::Instance<'tcx>, Scalar<Provenance>, TlsKey)> {
use std::ops::Bound::*;
let thread_local = &mut self.keys;
let start = match key {
Some(key) => Excluded(key),
None => Unbounded,
};
// We interpret the documentaion above (taken from POSIX) as saying that we need to iterate
// over all keys and run each destructor at least once before running any destructor a 2nd
// time. That's why we have `key` to indicate how far we got in the current iteration. If we
// return `None`, `schedule_next_pthread_tls_dtor` will re-try with `ket` set to `None` to
// start the next round.
// TODO: In the future, we might consider randomizing destructor order, but we still have to
// uphold this requirement.
for (&key, TlsEntry { data, dtor }) in thread_local.range_mut((start, Unbounded)) {
match data.entry(thread_id) {
BTreeEntry::Occupied(entry) => {
if let Some(dtor) = dtor {
// Set TLS data to NULL, and call dtor with old value.
let data_scalar = entry.remove();
let ret = Some((*dtor, data_scalar, key));
return ret;
}
}
BTreeEntry::Vacant(_) => {}
}
}
None
}
/// Set that dtors are running for `thread`. It is guaranteed not to change
/// the existing values stored in `dtors_running` for this thread. Returns
/// `true` if dtors for `thread` are already running.
fn set_dtors_running_for_thread(&mut self, thread: ThreadId) -> bool {
match self.dtors_running.entry(thread) {
HashMapEntry::Occupied(_) => true,
HashMapEntry::Vacant(entry) => {
// We cannot just do `self.dtors_running.insert` because that
// would overwrite `last_dtor_key` with `None`.
entry.insert(RunningDtorsState { last_dtor_key: None });
false
}
}
}
/// Delete all TLS entries for the given thread. This function should be
/// called after all TLS destructors have already finished.
fn delete_all_thread_tls(&mut self, thread_id: ThreadId) {
for TlsEntry { data, .. } in self.keys.values_mut() {
data.remove(&thread_id);
}
}
pub fn iter(&self, mut visitor: impl FnMut(&Scalar<Provenance>)) {
for scalar in self.keys.values().flat_map(|v| v.data.values()) {
visitor(scalar);
}
}
}
impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
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> {
let this = self.eval_context_mut();
let active_thread = this.get_active_thread();
// 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.
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()?;
// FIXME: Technically, the reason should be `DLL_PROCESS_DETACH` when the main thread exits
// but std treats both the same.
let reason = this.eval_windows("c", "DLL_THREAD_DETACH")?;
// 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
this.call_function(
thread_callback,
Abi::System { unwind: false },
&[Scalar::null_ptr(this).into(), reason.into(), Scalar::null_ptr(this).into()],
None,
StackPopCleanup::Root { cleanup: true },
)?;
this.enable_thread(active_thread);
Ok(())
}
/// Schedule the MacOS thread destructor of the thread local storage to be
/// executed. Returns `true` if scheduled.
///
/// Note: It is safe to call this function also on other Unixes.
fn schedule_macos_tls_dtor(&mut self) -> InterpResult<'tcx, bool> {
let this = self.eval_context_mut();
let thread_id = this.get_active_thread();
if let Some((instance, data)) = this.machine.tls.macos_thread_dtors.remove(&thread_id) {
trace!("Running macos dtor {:?} on {:?} at {:?}", instance, data, thread_id);
this.call_function(
instance,
Abi::C { unwind: false },
&[data.into()],
None,
StackPopCleanup::Root { cleanup: true },
)?;
// Enable the thread so that it steps through the destructor which
// we just scheduled. Since we deleted the destructor, it is
// guaranteed that we will schedule it again. The `dtors_running`
// flag will prevent the code from adding the destructor again.
this.enable_thread(thread_id);
Ok(true)
} else {
Ok(false)
}
}
/// Schedule a pthread TLS destructor. Returns `true` if found
/// a destructor to schedule, and `false` otherwise.
fn schedule_next_pthread_tls_dtor(&mut self) -> InterpResult<'tcx, bool> {
let this = self.eval_context_mut();
let active_thread = this.get_active_thread();
assert!(this.has_terminated(active_thread), "running TLS dtors for non-terminated thread");
// Fetch next dtor after `key`.
let last_key = this.machine.tls.dtors_running[&active_thread].last_dtor_key;
let dtor = match this.machine.tls.fetch_tls_dtor(last_key, active_thread) {
dtor @ Some(_) => dtor,
// We ran each dtor once, start over from the beginning.
None => this.machine.tls.fetch_tls_dtor(None, active_thread),
};
if let Some((instance, ptr, key)) = dtor {
this.machine.tls.dtors_running.get_mut(&active_thread).unwrap().last_dtor_key =
Some(key);
trace!("Running TLS dtor {:?} on {:?} at {:?}", instance, ptr, active_thread);
assert!(
!ptr.to_machine_usize(this).unwrap() != 0,
"data can't be NULL when dtor is called!"
);
this.call_function(
instance,
Abi::C { unwind: false },
&[ptr.into()],
None,
StackPopCleanup::Root { cleanup: true },
)?;
this.enable_thread(active_thread);
return Ok(true);
}
this.machine.tls.dtors_running.get_mut(&active_thread).unwrap().last_dtor_key = None;
Ok(false)
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Schedule an active thread's TLS destructor to run on the active thread.
/// Note that this function does not run the destructors itself, it just
/// schedules them one by one each time it is called and reenables the
/// thread so that it can be executed normally by the main execution loop.
///
/// Note: we consistently run TLS destructors for all threads, including the
/// main thread. However, it is not clear that we should run the TLS
/// destructors for the main thread. See issue:
/// <https://github.com/rust-lang/rust/issues/28129>.
fn schedule_next_tls_dtor_for_active_thread(&mut self) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let active_thread = this.get_active_thread();
trace!("schedule_next_tls_dtor_for_active_thread on thread {:?}", active_thread);
if !this.machine.tls.set_dtors_running_for_thread(active_thread) {
// This is the first time we got asked to schedule a destructor. The
// Windows schedule destructor function must be called exactly once,
// this is why it is in this block.
if this.tcx.sess.target.os == "windows" {
// On Windows, we signal that the thread quit by starting the
// relevant function, reenabling the thread, and going back to
// the scheduler.
this.schedule_windows_tls_dtors()?;
return Ok(());
}
}
// The remaining dtors make some progress each time around the scheduler loop,
// until they return `false` to indicate that they are done.
// The macOS thread wide destructor runs "before any TLS slots get
// freed", so do that first.
if this.schedule_macos_tls_dtor()? {
// We have scheduled a MacOS dtor to run on the thread. Execute it
// to completion and come back here. Scheduling a destructor
// destroys it, so we will not enter this branch again.
return Ok(());
}
if this.schedule_next_pthread_tls_dtor()? {
// We have scheduled a pthread destructor and removed it from the
// destructors list. Run it to completion and come back here.
return Ok(());
}
// All dtors done!
this.machine.tls.delete_all_thread_tls(active_thread);
this.thread_terminated()?;
Ok(())
}
}

View File

@ -0,0 +1,54 @@
use rustc_middle::mir;
use crate::helpers::check_arg_count;
use crate::*;
#[derive(Debug, Copy, Clone)]
#[allow(non_camel_case_types)]
pub enum Dlsym {
signal,
}
impl Dlsym {
// Returns an error for unsupported symbols, and None if this symbol
// should become a NULL pointer (pretend it does not exist).
pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option<Dlsym>> {
Ok(match name {
"signal" => Some(Dlsym::signal),
"android_set_abort_message" => None,
_ => throw_unsup_format!("unsupported Android dlsym: {}", name),
})
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn call_dlsym(
&mut self,
dlsym: Dlsym,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
ret: Option<mir::BasicBlock>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let ret = ret.expect("we don't support any diverging dlsym");
assert!(this.tcx.sess.target.os == "android");
match dlsym {
Dlsym::signal => {
if !this.frame_in_std() {
throw_unsup_format!(
"`signal` support is crude and just enough for libstd to work"
);
}
let &[ref _sig, ref _func] = check_arg_count(args)?;
this.write_null(dest)?;
}
}
log::trace!("{:?}", this.dump_place(**dest));
this.go_to_block(ret);
Ok(())
}
}

View File

@ -0,0 +1,26 @@
use rustc_span::Symbol;
use rustc_target::spec::abi::Abi;
use crate::*;
use shims::foreign_items::EmulateByNameResult;
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_by_name(
&mut self,
link_name: Symbol,
_abi: Abi,
_args: &[OpTy<'tcx, Provenance>],
_dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
let _this = self.eval_context_mut();
#[allow(clippy::match_single_binding)]
match link_name.as_str() {
_ => return Ok(EmulateByNameResult::NotSupported),
}
#[allow(unreachable_code)]
Ok(EmulateByNameResult::NeedsJumping)
}
}

View File

@ -0,0 +1,2 @@
pub mod dlsym;
pub mod foreign_items;

View File

@ -0,0 +1,55 @@
use rustc_middle::mir;
use rustc_target::spec::abi::Abi;
use crate::*;
use shims::unix::android::dlsym as android;
use shims::unix::freebsd::dlsym as freebsd;
use shims::unix::linux::dlsym as linux;
use shims::unix::macos::dlsym as macos;
#[derive(Debug, Copy, Clone)]
pub enum Dlsym {
Android(android::Dlsym),
FreeBsd(freebsd::Dlsym),
Linux(linux::Dlsym),
MacOs(macos::Dlsym),
}
impl Dlsym {
// Returns an error for unsupported symbols, and None if this symbol
// should become a NULL pointer (pretend it does not exist).
pub fn from_str<'tcx>(name: &str, target_os: &str) -> InterpResult<'tcx, Option<Dlsym>> {
Ok(match target_os {
"android" => android::Dlsym::from_str(name)?.map(Dlsym::Android),
"freebsd" => freebsd::Dlsym::from_str(name)?.map(Dlsym::FreeBsd),
"linux" => linux::Dlsym::from_str(name)?.map(Dlsym::Linux),
"macos" => macos::Dlsym::from_str(name)?.map(Dlsym::MacOs),
_ => panic!("unsupported Unix OS {target_os}"),
})
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn call_dlsym(
&mut self,
dlsym: Dlsym,
abi: Abi,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
ret: Option<mir::BasicBlock>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
this.check_abi(abi, Abi::C { unwind: false })?;
match dlsym {
Dlsym::Android(dlsym) =>
android::EvalContextExt::call_dlsym(this, dlsym, args, dest, ret),
Dlsym::FreeBsd(dlsym) =>
freebsd::EvalContextExt::call_dlsym(this, dlsym, args, dest, ret),
Dlsym::Linux(dlsym) => linux::EvalContextExt::call_dlsym(this, dlsym, args, dest, ret),
Dlsym::MacOs(dlsym) => macos::EvalContextExt::call_dlsym(this, dlsym, args, dest, ret),
}
}
}

View File

@ -0,0 +1,606 @@
use std::ffi::OsStr;
use log::trace;
use rustc_middle::ty::layout::LayoutOf;
use rustc_span::Symbol;
use rustc_target::abi::{Align, Size};
use rustc_target::spec::abi::Abi;
use crate::*;
use shims::foreign_items::EmulateByNameResult;
use shims::unix::fs::EvalContextExt as _;
use shims::unix::sync::EvalContextExt as _;
use shims::unix::thread::EvalContextExt as _;
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_by_name(
&mut self,
link_name: Symbol,
abi: Abi,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
let this = self.eval_context_mut();
// See `fn emulate_foreign_item_by_name` in `shims/foreign_items.rs` for the general pattern.
#[rustfmt::skip]
match link_name.as_str() {
// Environment related shims
"getenv" => {
let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.getenv(name)?;
this.write_pointer(result, dest)?;
}
"unsetenv" => {
let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.unsetenv(name)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"setenv" => {
let [name, value, overwrite] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.read_scalar(overwrite)?.to_i32()?;
let result = this.setenv(name, value)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"getcwd" => {
let [buf, size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.getcwd(buf, size)?;
this.write_pointer(result, dest)?;
}
"chdir" => {
let [path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.chdir(path)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
// File related shims
"open" | "open64" => {
// `open` is variadic, the third argument is only present when the second argument has O_CREAT (or on linux O_TMPFILE, but miri doesn't support that) set
this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?;
let result = this.open(args)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"close" => {
let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.close(fd)?;
this.write_scalar(result, dest)?;
}
"fcntl" => {
// `fcntl` is variadic. The argument count is checked based on the first argument
// in `this.fcntl()`, so we do not use `check_shim` here.
this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?;
let result = this.fcntl(args)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"read" => {
let [fd, buf, count] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let fd = this.read_scalar(fd)?.to_i32()?;
let buf = this.read_pointer(buf)?;
let count = this.read_scalar(count)?.to_machine_usize(this)?;
let result = this.read(fd, buf, count)?;
this.write_scalar(Scalar::from_machine_isize(result, this), dest)?;
}
"write" => {
let [fd, buf, n] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let fd = this.read_scalar(fd)?.to_i32()?;
let buf = this.read_pointer(buf)?;
let count = this.read_scalar(n)?.to_machine_usize(this)?;
trace!("Called write({:?}, {:?}, {:?})", fd, buf, count);
let result = this.write(fd, buf, count)?;
// Now, `result` is the value we return back to the program.
this.write_scalar(Scalar::from_machine_isize(result, this), dest)?;
}
"unlink" => {
let [path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.unlink(path)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"symlink" => {
let [target, linkpath] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.symlink(target, linkpath)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"rename" => {
let [oldpath, newpath] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.rename(oldpath, newpath)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"mkdir" => {
let [path, mode] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.mkdir(path, mode)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"rmdir" => {
let [path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.rmdir(path)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"opendir" => {
let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.opendir(name)?;
this.write_scalar(result, dest)?;
}
"closedir" => {
let [dirp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.closedir(dirp)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"lseek64" => {
let [fd, offset, whence] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.lseek64(fd, offset, whence)?;
this.write_scalar(result, dest)?;
}
"ftruncate64" => {
let [fd, length] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.ftruncate64(fd, length)?;
this.write_scalar(result, dest)?;
}
"fsync" => {
let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.fsync(fd)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"fdatasync" => {
let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.fdatasync(fd)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"readlink" => {
let [pathname, buf, bufsize] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.readlink(pathname, buf, bufsize)?;
this.write_scalar(Scalar::from_machine_isize(result, this), dest)?;
}
"posix_fadvise" => {
let [fd, offset, len, advice] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.read_scalar(fd)?.to_i32()?;
this.read_scalar(offset)?.to_machine_isize(this)?;
this.read_scalar(len)?.to_machine_isize(this)?;
this.read_scalar(advice)?.to_i32()?;
// fadvise is only informational, we can ignore it.
this.write_null(dest)?;
}
"realpath" => {
let [path, resolved_path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.realpath(path, resolved_path)?;
this.write_scalar(result, dest)?;
}
"mkstemp" => {
let [template] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.mkstemp(template)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
// Time related shims
"gettimeofday" => {
let [tv, tz] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.gettimeofday(tv, tz)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
// Allocation
"posix_memalign" => {
let [ret, align, size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let ret = this.deref_operand(ret)?;
let align = this.read_scalar(align)?.to_machine_usize(this)?;
let size = this.read_scalar(size)?.to_machine_usize(this)?;
// Align must be power of 2, and also at least ptr-sized (POSIX rules).
// But failure to adhere to this is not UB, it's an error condition.
if !align.is_power_of_two() || align < this.pointer_size().bytes() {
let einval = this.eval_libc_i32("EINVAL")?;
this.write_int(einval, dest)?;
} else {
if size == 0 {
this.write_null(&ret.into())?;
} else {
let ptr = this.allocate_ptr(
Size::from_bytes(size),
Align::from_bytes(align).unwrap(),
MiriMemoryKind::C.into(),
)?;
this.write_pointer(ptr, &ret.into())?;
}
this.write_null(dest)?;
}
}
// Dynamic symbol loading
"dlsym" => {
let [handle, symbol] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.read_scalar(handle)?.to_machine_usize(this)?;
let symbol = this.read_pointer(symbol)?;
let symbol_name = this.read_c_str(symbol)?;
if let Some(dlsym) = Dlsym::from_str(symbol_name, &this.tcx.sess.target.os)? {
let ptr = this.create_fn_alloc_ptr(FnVal::Other(dlsym));
this.write_pointer(ptr, dest)?;
} else {
this.write_null(dest)?;
}
}
// Querying system information
"sysconf" => {
let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let name = this.read_scalar(name)?.to_i32()?;
// FIXME: Which of these are POSIX, and which are GNU/Linux?
// At least the names seem to all also exist on macOS.
let sysconfs: &[(&str, fn(&MiriInterpCx<'_, '_>) -> Scalar<Provenance>)] = &[
("_SC_PAGESIZE", |this| Scalar::from_int(PAGE_SIZE, this.pointer_size())),
("_SC_NPROCESSORS_CONF", |this| Scalar::from_int(NUM_CPUS, this.pointer_size())),
("_SC_NPROCESSORS_ONLN", |this| Scalar::from_int(NUM_CPUS, this.pointer_size())),
// 512 seems to be a reasonable default. The value is not critical, in
// the sense that getpwuid_r takes and checks the buffer length.
("_SC_GETPW_R_SIZE_MAX", |this| Scalar::from_int(512, this.pointer_size()))
];
let mut result = None;
for &(sysconf_name, value) in sysconfs {
let sysconf_name = this.eval_libc_i32(sysconf_name)?;
if sysconf_name == name {
result = Some(value(this));
break;
}
}
if let Some(result) = result {
this.write_scalar(result, dest)?;
} else {
throw_unsup_format!("unimplemented sysconf name: {}", name)
}
}
// Thread-local storage
"pthread_key_create" => {
let [key, dtor] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let key_place = this.deref_operand(key)?;
let dtor = this.read_pointer(dtor)?;
// Extract the function type out of the signature (that seems easier than constructing it ourselves).
let dtor = if !this.ptr_is_null(dtor)? {
Some(this.get_ptr_fn(dtor)?.as_instance()?)
} else {
None
};
// Figure out how large a pthread TLS key actually is.
// To this end, deref the argument type. This is `libc::pthread_key_t`.
let key_type = key.layout.ty
.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.
let key = this.machine.tls.create_tls_key(dtor, key_layout.size)?;
this.write_scalar(Scalar::from_uint(key, key_layout.size), &key_place.into())?;
// Return success (`0`).
this.write_null(dest)?;
}
"pthread_key_delete" => {
let [key] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let key = this.read_scalar(key)?.to_bits(key.layout.size)?;
this.machine.tls.delete_tls_key(key)?;
// Return success (0)
this.write_null(dest)?;
}
"pthread_getspecific" => {
let [key] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let key = this.read_scalar(key)?.to_bits(key.layout.size)?;
let active_thread = this.get_active_thread();
let ptr = this.machine.tls.load_tls(key, active_thread, this)?;
this.write_scalar(ptr, dest)?;
}
"pthread_setspecific" => {
let [key, new_ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let key = this.read_scalar(key)?.to_bits(key.layout.size)?;
let active_thread = this.get_active_thread();
let new_data = this.read_scalar(new_ptr)?;
this.machine.tls.store_tls(key, active_thread, new_data, &*this.tcx)?;
// Return success (`0`).
this.write_null(dest)?;
}
// Synchronization primitives
"pthread_mutexattr_init" => {
let [attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_mutexattr_init(attr)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_mutexattr_settype" => {
let [attr, kind] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_mutexattr_settype(attr, kind)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_mutexattr_destroy" => {
let [attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_mutexattr_destroy(attr)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_mutex_init" => {
let [mutex, attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_mutex_init(mutex, attr)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_mutex_lock" => {
let [mutex] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_mutex_lock(mutex)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_mutex_trylock" => {
let [mutex] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_mutex_trylock(mutex)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_mutex_unlock" => {
let [mutex] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_mutex_unlock(mutex)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_mutex_destroy" => {
let [mutex] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_mutex_destroy(mutex)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_rwlock_rdlock" => {
let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_rwlock_rdlock(rwlock)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_rwlock_tryrdlock" => {
let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_rwlock_tryrdlock(rwlock)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_rwlock_wrlock" => {
let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_rwlock_wrlock(rwlock)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_rwlock_trywrlock" => {
let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_rwlock_trywrlock(rwlock)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_rwlock_unlock" => {
let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_rwlock_unlock(rwlock)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_rwlock_destroy" => {
let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_rwlock_destroy(rwlock)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_condattr_init" => {
let [attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_condattr_init(attr)?;
this.write_scalar(Scalar::from_i32(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)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_cond_init" => {
let [cond, attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_cond_init(cond, attr)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_cond_signal" => {
let [cond] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_cond_signal(cond)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_cond_broadcast" => {
let [cond] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_cond_broadcast(cond)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_cond_wait" => {
let [cond, mutex] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_cond_wait(cond, mutex)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_cond_timedwait" => {
let [cond, mutex, abstime] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.pthread_cond_timedwait(cond, mutex, abstime, dest)?;
}
"pthread_cond_destroy" => {
let [cond] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_cond_destroy(cond)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
// Threading
"pthread_create" => {
let [thread, attr, start, arg] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_create(thread, attr, start, arg)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_join" => {
let [thread, retval] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_join(thread, retval)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_detach" => {
let [thread] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pthread_detach(thread)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_self" => {
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let res = this.pthread_self()?;
this.write_scalar(res, dest)?;
}
"sched_yield" => {
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.sched_yield()?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"nanosleep" => {
let [req, rem] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.nanosleep(req, rem)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
// Miscellaneous
"isatty" => {
let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.isatty(fd)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"pthread_atfork" => {
let [prepare, parent, child] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.read_pointer(prepare)?;
this.read_pointer(parent)?;
this.read_pointer(child)?;
// We do not support forking, so there is nothing to do here.
this.write_null(dest)?;
}
"strerror_r" | "__xpg_strerror_r" => {
let [errnum, buf, buflen] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let errnum = this.read_scalar(errnum)?;
let buf = this.read_pointer(buf)?;
let buflen = this.read_scalar(buflen)?.to_machine_usize(this)?;
let error = this.try_errnum_to_io_error(errnum)?;
let formatted = match error {
Some(err) => format!("{err}"),
None => format!("<unknown errnum in strerror_r: {errnum}>"),
};
let (complete, _) = this.write_os_str_to_c_str(OsStr::new(&formatted), buf, buflen)?;
let ret = if complete { 0 } else { this.eval_libc_i32("ERANGE")? };
this.write_int(ret, dest)?;
}
"getpid" => {
let [] = this.check_shim(abi, Abi::C { unwind: false}, link_name, args)?;
let result = this.getpid()?;
this.write_scalar(Scalar::from_i32(result), 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.
"pthread_attr_getguardsize"
if this.frame_in_std() => {
let [_attr, guard_size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let guard_size = this.deref_operand(guard_size)?;
let guard_size_layout = this.libc_ty_layout("size_t")?;
this.write_scalar(Scalar::from_uint(crate::PAGE_SIZE, guard_size_layout.size), &guard_size.into())?;
// Return success (`0`).
this.write_null(dest)?;
}
| "pthread_attr_init"
| "pthread_attr_destroy"
if this.frame_in_std() => {
let [_] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.write_null(dest)?;
}
| "pthread_attr_setstacksize"
if this.frame_in_std() => {
let [_, _] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.write_null(dest)?;
}
"pthread_attr_getstack"
if this.frame_in_std() => {
// We don't support "pthread_attr_setstack", so we just pretend all stacks have the same values here.
// Hence we can mostly ignore the input `attr_place`.
let [attr_place, addr_place, size_place] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let _attr_place = this.deref_operand(attr_place)?;
let addr_place = this.deref_operand(addr_place)?;
let size_place = this.deref_operand(size_place)?;
this.write_scalar(
Scalar::from_uint(STACK_ADDR, this.pointer_size()),
&addr_place.into(),
)?;
this.write_scalar(
Scalar::from_uint(STACK_SIZE, this.pointer_size()),
&size_place.into(),
)?;
// Return success (`0`).
this.write_null(dest)?;
}
| "signal"
| "sigaltstack"
if this.frame_in_std() => {
let [_, _] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.write_null(dest)?;
}
| "sigaction"
| "mprotect"
if this.frame_in_std() => {
let [_, _, _] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.write_null(dest)?;
}
"getuid"
if this.frame_in_std() => {
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// FOr now, just pretend we always have this fixed UID.
this.write_int(super::UID, dest)?;
}
"getpwuid_r" if this.frame_in_std() => {
let [uid, pwd, buf, buflen, result] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.check_no_isolation("`getpwuid_r`")?;
let uid = this.read_scalar(uid)?.to_u32()?;
let pwd = this.deref_operand(pwd)?;
let buf = this.read_pointer(buf)?;
let buflen = this.read_scalar(buflen)?.to_machine_usize(this)?;
let result = this.deref_operand(result)?;
// Must be for "us".
if uid != crate::shims::unix::UID {
throw_unsup_format!("`getpwuid_r` on other users is not supported");
}
// Reset all fields to `uninit` to make sure nobody reads them.
// (This is a std-only shim so we are okay with such hacks.)
this.write_uninit(&pwd.into())?;
// We only set the home_dir field.
#[allow(deprecated)]
let home_dir = std::env::home_dir().unwrap();
let (written, _) = this.write_path_to_c_str(&home_dir, buf, buflen)?;
let pw_dir = this.mplace_field_named(&pwd, "pw_dir")?;
this.write_pointer(buf, &pw_dir.into())?;
if written {
this.write_pointer(pwd.ptr, &result.into())?;
this.write_null(dest)?;
} else {
this.write_null(&result.into())?;
this.write_scalar(this.eval_libc("ERANGE")?, dest)?;
}
}
// Platform-specific shims
_ => {
let target_os = &*this.tcx.sess.target.os;
match target_os {
"android" => return shims::unix::android::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
"freebsd" => return shims::unix::freebsd::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
"linux" => return shims::unix::linux::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
"macos" => return shims::unix::macos::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest),
_ => panic!("unsupported Unix OS {target_os}"),
}
}
};
Ok(EmulateByNameResult::NeedsJumping)
}
}

View File

@ -0,0 +1,36 @@
use rustc_middle::mir;
use crate::*;
#[derive(Debug, Copy, Clone)]
#[allow(non_camel_case_types)]
pub enum Dlsym {}
impl Dlsym {
// Returns an error for unsupported symbols, and None if this symbol
// should become a NULL pointer (pretend it does not exist).
pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option<Dlsym>> {
throw_unsup_format!("unsupported FreeBSD dlsym: {}", name)
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn call_dlsym(
&mut self,
dlsym: Dlsym,
_args: &[OpTy<'tcx, Provenance>],
_dest: &PlaceTy<'tcx, Provenance>,
ret: Option<mir::BasicBlock>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let _ret = ret.expect("we don't support any diverging dlsym");
assert!(this.tcx.sess.target.os == "freebsd");
match dlsym {}
//trace!("{:?}", this.dump_place(**dest));
//this.go_to_block(ret);
//Ok(())
}
}

View File

@ -0,0 +1,45 @@
use rustc_span::Symbol;
use rustc_target::spec::abi::Abi;
use crate::*;
use shims::foreign_items::EmulateByNameResult;
use shims::unix::thread::EvalContextExt as _;
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_by_name(
&mut self,
link_name: Symbol,
abi: Abi,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
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)?;
let res =
this.pthread_setname_np(this.read_scalar(thread)?, this.read_scalar(name)?)?;
this.write_scalar(res, dest)?;
}
// errno
"__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(EmulateByNameResult::NotSupported),
}
Ok(EmulateByNameResult::NeedsJumping)
}
}

View File

@ -0,0 +1,2 @@
pub mod dlsym;
pub mod foreign_items;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
use rustc_middle::mir;
use crate::*;
#[derive(Debug, Copy, Clone)]
pub enum Dlsym {}
impl Dlsym {
// Returns an error for unsupported symbols, and None if this symbol
// should become a NULL pointer (pretend it does not exist).
pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option<Dlsym>> {
Ok(match name {
"__pthread_get_minstack" => None,
"getrandom" => None, // std falls back to syscall(SYS_getrandom, ...) when this is NULL.
"statx" => None, // std falls back to syscall(SYS_statx, ...) when this is NULL.
_ => throw_unsup_format!("unsupported Linux dlsym: {}", name),
})
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn call_dlsym(
&mut self,
dlsym: Dlsym,
_args: &[OpTy<'tcx, Provenance>],
_dest: &PlaceTy<'tcx, Provenance>,
ret: Option<mir::BasicBlock>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let _ret = ret.expect("we don't support any diverging dlsym");
assert!(this.tcx.sess.target.os == "linux");
match dlsym {}
//trace!("{:?}", this.dump_place(**dest));
//this.go_to_block(ret);
//Ok(())
}
}

View File

@ -0,0 +1,187 @@
use rustc_span::Symbol;
use rustc_target::spec::abi::Abi;
use crate::*;
use shims::foreign_items::EmulateByNameResult;
use shims::unix::fs::EvalContextExt as _;
use shims::unix::linux::sync::futex;
use shims::unix::sync::EvalContextExt as _;
use shims::unix::thread::EvalContextExt as _;
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_by_name(
&mut self,
link_name: Symbol,
abi: Abi,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
let this = self.eval_context_mut();
// See `fn emulate_foreign_item_by_name` 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)
"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)?;
}
// 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)?;
}
// Time related shims
"clock_gettime" => {
// This is a POSIX function but it has only been tested on linux.
let [clk_id, tp] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.clock_gettime(clk_id, tp)?;
this.write_scalar(result, 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)?;
let res =
this.pthread_setname_np(this.read_scalar(thread)?, this.read_scalar(name)?)?;
this.write_scalar(res, dest)?;
}
// Dynamically invoked syscalls
"syscall" => {
// We do not use `check_shim` here because `syscall` is variadic. The argument
// count is checked bellow.
this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?;
// The syscall variadic function is legal to call with more arguments than needed,
// extra arguments are simply ignored. The important check is that when we use an
// argument, we have to also check all arguments *before* it to ensure that they
// have the right type.
let sys_getrandom = this.eval_libc("SYS_getrandom")?.to_machine_usize(this)?;
let sys_statx = this.eval_libc("SYS_statx")?.to_machine_usize(this)?;
let sys_futex = this.eval_libc("SYS_futex")?.to_machine_usize(this)?;
if args.is_empty() {
throw_ub_format!(
"incorrect number of arguments for syscall: got 0, expected at least 1"
);
}
match this.read_scalar(&args[0])?.to_machine_usize(this)? {
// `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<K, V>).
id if id == sys_getrandom => {
// The first argument is the syscall id, so skip over it.
if args.len() < 4 {
throw_ub_format!(
"incorrect number of arguments for `getrandom` syscall: got {}, expected at least 4",
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_machine_isize(result.into(), this), dest)?;
}
// `futex` is used by some synchonization 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(EmulateByNameResult::AlreadyJumped);
}
}
}
// Miscelanneous
"getrandom" => {
let [ptr, len, flags] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
getrandom(this, ptr, len, flags, dest)?;
}
"sched_getaffinity" => {
let [pid, cpusetsize, mask] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.read_scalar(pid)?.to_i32()?;
this.read_scalar(cpusetsize)?.to_machine_usize(this)?;
this.deref_operand(mask)?;
// 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)?;
}
// 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_getattr_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(EmulateByNameResult::NotSupported),
};
Ok(EmulateByNameResult::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: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
let ptr = this.read_pointer(ptr)?;
let len = this.read_scalar(len)?.to_machine_usize(this)?;
// The only supported flags are GRND_RANDOM and GRND_NONBLOCK,
// neither of which have any effect on our current PRNG.
// See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
let _flags = this.read_scalar(flags)?.to_i32();
this.gen_random(ptr, len)?;
this.write_scalar(Scalar::from_machine_usize(len, this), dest)?;
Ok(())
}

View File

@ -0,0 +1,3 @@
pub mod dlsym;
pub mod foreign_items;
pub mod sync;

View File

@ -0,0 +1,261 @@
use crate::concurrency::thread::Time;
use crate::*;
use rustc_target::abi::{Align, Size};
use std::time::SystemTime;
/// Implementation of the SYS_futex syscall.
/// `args` is the arguments *after* the syscall number.
pub fn futex<'tcx>(
this: &mut MiriInterpCx<'_, 'tcx>,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
// The amount of arguments used depends on the type of futex operation.
// The full futex syscall takes six arguments (excluding the syscall
// number), which is also the maximum amount of arguments a linux syscall
// can take on most architectures.
// However, not all futex operations use all six arguments. The unused ones
// may or may not be left out from the `syscall()` call.
// Therefore we don't use `check_arg_count` here, but only check for the
// number of arguments to fall within a range.
if args.len() < 3 {
throw_ub_format!(
"incorrect number of arguments for `futex` syscall: got {}, expected at least 3",
args.len()
);
}
// The first three arguments (after the syscall number itself) are the same to all futex operations:
// (int *addr, int op, int val).
// We checked above that these definitely exist.
let addr = this.read_immediate(&args[0])?;
let op = this.read_scalar(&args[1])?.to_i32()?;
let val = this.read_scalar(&args[2])?.to_i32()?;
let thread = this.get_active_thread();
let addr_scalar = addr.to_scalar();
let addr_usize = addr_scalar.to_machine_usize(this)?;
let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG")?;
let futex_wait = this.eval_libc_i32("FUTEX_WAIT")?;
let futex_wait_bitset = this.eval_libc_i32("FUTEX_WAIT_BITSET")?;
let futex_wake = this.eval_libc_i32("FUTEX_WAKE")?;
let futex_wake_bitset = this.eval_libc_i32("FUTEX_WAKE_BITSET")?;
let futex_realtime = this.eval_libc_i32("FUTEX_CLOCK_REALTIME")?;
// FUTEX_PRIVATE enables an optimization that stops it from working across processes.
// Miri doesn't support that anyway, so we ignore that flag.
match op & !futex_private {
// FUTEX_WAIT: (int *addr, int op = FUTEX_WAIT, int val, const timespec *timeout)
// Blocks the thread if *addr still equals val. Wakes up when FUTEX_WAKE is called on the same address,
// or *timeout expires. `timeout == null` for an infinite timeout.
//
// FUTEX_WAIT_BITSET: (int *addr, int op = FUTEX_WAIT_BITSET, int val, const timespec *timeout, int *_ignored, unsigned int bitset)
// This is identical to FUTEX_WAIT, except:
// - The timeout is absolute rather than relative.
// - You can specify the bitset to selecting what WAKE operations to respond to.
op if op & !futex_realtime == futex_wait || op & !futex_realtime == futex_wait_bitset => {
let wait_bitset = op & !futex_realtime == futex_wait_bitset;
let bitset = if wait_bitset {
if args.len() < 6 {
throw_ub_format!(
"incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT_BITSET`: got {}, expected at least 6",
args.len()
);
}
let _timeout = this.read_pointer(&args[3])?;
let _uaddr2 = this.read_pointer(&args[4])?;
this.read_scalar(&args[5])?.to_u32()?
} else {
if args.len() < 4 {
throw_ub_format!(
"incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT`: got {}, expected at least 4",
args.len()
);
}
u32::MAX
};
if bitset == 0 {
let einval = this.eval_libc("EINVAL")?;
this.set_last_error(einval)?;
this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
return Ok(());
}
// `deref_operand` but not actually dereferencing the ptr yet (it might be NULL!).
let timeout = this.ref_to_mplace(&this.read_immediate(&args[3])?)?;
let timeout_time = if this.ptr_is_null(timeout.ptr)? {
None
} else {
this.check_no_isolation(
"`futex` syscall with `op=FUTEX_WAIT` and non-null timeout",
)?;
let duration = match this.read_timespec(&timeout)? {
Some(duration) => duration,
None => {
let einval = this.eval_libc("EINVAL")?;
this.set_last_error(einval)?;
this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
return Ok(());
}
};
Some(if wait_bitset {
// FUTEX_WAIT_BITSET uses an absolute timestamp.
if op & futex_realtime != 0 {
Time::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap())
} else {
Time::Monotonic(this.machine.clock.anchor().checked_add(duration).unwrap())
}
} else {
// FUTEX_WAIT uses a relative timestamp.
if op & futex_realtime != 0 {
Time::RealTime(SystemTime::now().checked_add(duration).unwrap())
} else {
Time::Monotonic(this.machine.clock.now().checked_add(duration).unwrap())
}
})
};
// Check the pointer for alignment and validity.
// The API requires `addr` to be a 4-byte aligned pointer, and will
// use the 4 bytes at the given address as an (atomic) i32.
this.check_ptr_access_align(
addr_scalar.to_pointer(this)?,
Size::from_bytes(4),
Align::from_bytes(4).unwrap(),
CheckInAllocMsg::MemoryAccessTest,
)?;
// There may be a concurrent thread changing the value of addr
// and then invoking the FUTEX_WAKE syscall. It is critical that the
// effects of this and the other thread are correctly observed,
// otherwise we will deadlock.
//
// There are two scenarios to consider:
// 1. If we (FUTEX_WAIT) execute first, we'll push ourselves into
// the waiters queue and go to sleep. They (addr write & FUTEX_WAKE)
// will see us in the queue and wake us up.
// 2. If they (addr write & FUTEX_WAKE) execute first, we must observe
// addr's new value. If we see an outdated value that happens to equal
// the expected val, then we'll put ourselves to sleep with no one to wake us
// up, so we end up with a deadlock. This is prevented by having a SeqCst
// fence inside FUTEX_WAKE syscall, and another SeqCst fence
// below, the atomic read on addr after the SeqCst fence is guaranteed
// not to see any value older than the addr write immediately before
// calling FUTEX_WAKE. We'll see futex_val != val and return without
// sleeping.
//
// Note that the fences do not create any happens-before relationship.
// The read sees the write immediately before the fence not because
// one happens after the other, but is instead due to a guarantee unique
// to SeqCst fences that restricts what an atomic read placed AFTER the
// fence can see. The read still has to be atomic, otherwise it's a data
// race. This guarantee cannot be achieved with acquire-release fences
// since they only talk about reads placed BEFORE a fence - and places
// no restrictions on what the read itself can see, only that there is
// a happens-before between the fences IF the read happens to see the
// right value. This is useless to us, since we need the read itself
// to see an up-to-date value.
//
// The above case distinction is valid since both FUTEX_WAIT and FUTEX_WAKE
// contain a SeqCst fence, therefore inducting a total order between the operations.
// It is also critical that the fence, the atomic load, and the comparison in FUTEX_WAIT
// altogether happen atomically. If the other thread's fence in FUTEX_WAKE
// gets interleaved after our fence, then we lose the guarantee on the
// atomic load being up-to-date; if the other thread's write on addr and FUTEX_WAKE
// call are interleaved after the load but before the comparison, then we get a TOCTOU
// race condition, and go to sleep thinking the other thread will wake us up,
// even though they have already finished.
//
// Thankfully, preemptions cannot happen inside a Miri shim, so we do not need to
// do anything special to guarantee fence-load-comparison atomicity.
this.atomic_fence(AtomicFenceOrd::SeqCst)?;
// Read an `i32` through the pointer, regardless of any wrapper types.
// It's not uncommon for `addr` to be passed as another type than `*mut i32`, such as `*const AtomicI32`.
let futex_val = this
.read_scalar_at_offset_atomic(
&addr.into(),
0,
this.machine.layouts.i32,
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.futex_wait(addr_usize, thread, bitset);
// Succesfully waking up from FUTEX_WAIT always returns zero.
this.write_scalar(Scalar::from_machine_isize(0, this), dest)?;
// Register a timeout callback if a timeout was specified.
// This callback will override the return value when the timeout triggers.
if let Some(timeout_time) = timeout_time {
let dest = dest.clone();
this.register_timeout_callback(
thread,
timeout_time,
Box::new(move |this| {
this.unblock_thread(thread);
this.futex_remove_waiter(addr_usize, thread);
let etimedout = this.eval_libc("ETIMEDOUT")?;
this.set_last_error(etimedout)?;
this.write_scalar(Scalar::from_machine_isize(-1, this), &dest)?;
Ok(())
}),
);
}
} else {
// The futex value doesn't match the expected value, so we return failure
// right away without sleeping: -1 and errno set to EAGAIN.
let eagain = this.eval_libc("EAGAIN")?;
this.set_last_error(eagain)?;
this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
}
}
// FUTEX_WAKE: (int *addr, int op = FUTEX_WAKE, int val)
// Wakes at most `val` threads waiting on the futex at `addr`.
// Returns the amount of threads woken up.
// Does not access the futex value at *addr.
// FUTEX_WAKE_BITSET: (int *addr, int op = FUTEX_WAKE, int val, const timespect *_unused, int *_unused, unsigned int bitset)
// Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up.
op if op == futex_wake || op == futex_wake_bitset => {
let bitset = if op == futex_wake_bitset {
if args.len() < 6 {
throw_ub_format!(
"incorrect number of arguments for `futex` syscall with `op=FUTEX_WAKE_BITSET`: got {}, expected at least 6",
args.len()
);
}
let _timeout = this.read_pointer(&args[3])?;
let _uaddr2 = this.read_pointer(&args[4])?;
this.read_scalar(&args[5])?.to_u32()?
} else {
u32::MAX
};
if bitset == 0 {
let einval = this.eval_libc("EINVAL")?;
this.set_last_error(einval)?;
this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
return Ok(());
}
// Together with the SeqCst fence in futex_wait, this makes sure that futex_wait
// will see the latest value on addr which could be changed by our caller
// before doing the syscall.
this.atomic_fence(AtomicFenceOrd::SeqCst)?;
let mut n = 0;
#[allow(clippy::integer_arithmetic)]
for _ in 0..val {
if let Some(thread) = this.futex_wake(addr_usize, bitset) {
this.unblock_thread(thread);
this.unregister_timeout_callback_if_exists(thread);
n += 1;
} else {
break;
}
}
this.write_scalar(Scalar::from_machine_isize(n, this), dest)?;
}
op => throw_unsup_format!("Miri does not support `futex` syscall with op={}", op),
}
Ok(())
}

View File

@ -0,0 +1,52 @@
use rustc_middle::mir;
use log::trace;
use crate::*;
use helpers::check_arg_count;
#[derive(Debug, Copy, Clone)]
#[allow(non_camel_case_types)]
pub enum Dlsym {
getentropy,
}
impl Dlsym {
// Returns an error for unsupported symbols, and None if this symbol
// should become a NULL pointer (pretend it does not exist).
pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option<Dlsym>> {
Ok(match name {
"getentropy" => Some(Dlsym::getentropy),
_ => throw_unsup_format!("unsupported macOS dlsym: {}", name),
})
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn call_dlsym(
&mut self,
dlsym: Dlsym,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
ret: Option<mir::BasicBlock>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let ret = ret.expect("we don't support any diverging dlsym");
assert!(this.tcx.sess.target.os == "macos");
match dlsym {
Dlsym::getentropy => {
let [ptr, len] = check_arg_count(args)?;
let ptr = this.read_pointer(ptr)?;
let len = this.read_scalar(len)?.to_machine_usize(this)?;
this.gen_random(ptr, len)?;
this.write_null(dest)?;
}
}
trace!("{:?}", this.dump_place(**dest));
this.go_to_block(ret);
Ok(())
}
}

View File

@ -0,0 +1,197 @@
use rustc_span::Symbol;
use rustc_target::spec::abi::Abi;
use crate::*;
use shims::foreign_items::EmulateByNameResult;
use shims::unix::fs::EvalContextExt as _;
use shims::unix::thread::EvalContextExt as _;
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_by_name(
&mut self,
link_name: Symbol,
abi: Abi,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
let this = self.eval_context_mut();
// See `fn emulate_foreign_item_by_name` in `shims/foreign_items.rs` for the general pattern.
match link_name.as_str() {
// errno
"__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)?;
}
// File related shims
"close$NOCANCEL" => {
let [result] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.close(result)?;
this.write_scalar(result, dest)?;
}
"stat" | "stat64" | "stat$INODE64" => {
let [path, buf] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.macos_stat(path, buf)?;
this.write_scalar(result, dest)?;
}
"lstat" | "lstat64" | "lstat$INODE64" => {
let [path, buf] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.macos_lstat(path, buf)?;
this.write_scalar(result, dest)?;
}
"fstat" | "fstat64" | "fstat$INODE64" => {
let [fd, buf] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.macos_fstat(fd, buf)?;
this.write_scalar(result, dest)?;
}
"opendir$INODE64" => {
let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.opendir(name)?;
this.write_scalar(result, dest)?;
}
"readdir_r" | "readdir_r$INODE64" => {
let [dirp, entry, result] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.macos_readdir_r(dirp, entry, result)?;
this.write_scalar(result, dest)?;
}
"lseek" => {
let [fd, offset, whence] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// macOS is 64bit-only, so this is lseek64
let result = this.lseek64(fd, offset, whence)?;
this.write_scalar(result, dest)?;
}
"ftruncate" => {
let [fd, length] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// macOS is 64bit-only, so this is ftruncate64
let result = this.ftruncate64(fd, length)?;
this.write_scalar(result, dest)?;
}
"realpath$DARWIN_EXTSN" => {
let [path, resolved_path] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.realpath(path, resolved_path)?;
this.write_scalar(result, dest)?;
}
// Environment related shims
"_NSGetEnviron" => {
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.write_pointer(
this.machine.env_vars.environ.expect("machine must be initialized").ptr,
dest,
)?;
}
// Time related shims
"mach_absolute_time" => {
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.mach_absolute_time()?;
this.write_scalar(result, dest)?;
}
"mach_timebase_info" => {
let [info] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.mach_timebase_info(info)?;
this.write_scalar(result, dest)?;
}
// Access to command-line arguments
"_NSGetArgc" => {
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.write_pointer(
this.machine.argc.expect("machine must be initialized").ptr,
dest,
)?;
}
"_NSGetArgv" => {
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.write_pointer(
this.machine.argv.expect("machine must be initialized").ptr,
dest,
)?;
}
"_NSGetExecutablePath" => {
let [buf, bufsize] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.check_no_isolation("`_NSGetExecutablePath`")?;
let buf_ptr = this.read_pointer(buf)?;
let bufsize = this.deref_operand(bufsize)?;
// Using the host current_exe is a bit off, but consistent with Linux
// (where stdlib reads /proc/self/exe).
let path = std::env::current_exe().unwrap();
let (written, size_needed) = this.write_path_to_c_str(
&path,
buf_ptr,
this.read_scalar(&bufsize.into())?.to_u32()?.into(),
)?;
if written {
this.write_null(dest)?;
} else {
this.write_scalar(
Scalar::from_u32(size_needed.try_into().unwrap()),
&bufsize.into(),
)?;
this.write_int(-1, dest)?;
}
}
// Thread-local storage
"_tlv_atexit" => {
let [dtor, data] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let dtor = this.read_pointer(dtor)?;
let dtor = this.get_ptr_fn(dtor)?.as_instance()?;
let data = this.read_scalar(data)?;
let active_thread = this.get_active_thread();
this.machine.tls.set_macos_thread_dtor(active_thread, dtor, data)?;
}
// Querying system information
"pthread_get_stackaddr_np" => {
let [thread] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.read_scalar(thread)?.to_machine_usize(this)?;
let stack_addr = Scalar::from_uint(STACK_ADDR, this.pointer_size());
this.write_scalar(stack_addr, dest)?;
}
"pthread_get_stacksize_np" => {
let [thread] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.read_scalar(thread)?.to_machine_usize(this)?;
let stack_size = Scalar::from_uint(STACK_SIZE, this.pointer_size());
this.write_scalar(stack_size, dest)?;
}
// Threading
"pthread_setname_np" => {
let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let thread = this.pthread_self()?;
this.pthread_setname_np(thread, this.read_scalar(name)?)?;
}
// 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.
"mmap" if this.frame_in_std() => {
// This is a horrible hack, but since the guard page mechanism calls mmap and expects a particular return value, we just give it that value.
let [addr, _, _, _, _, _] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let addr = this.read_scalar(addr)?;
this.write_scalar(addr, dest)?;
}
_ => return Ok(EmulateByNameResult::NotSupported),
};
Ok(EmulateByNameResult::NeedsJumping)
}
}

View File

@ -0,0 +1,2 @@
pub mod dlsym;
pub mod foreign_items;

View File

@ -0,0 +1,16 @@
pub mod dlsym;
pub mod foreign_items;
mod fs;
mod sync;
mod thread;
mod android;
mod freebsd;
mod linux;
mod macos;
pub use fs::{DirHandler, FileHandler};
// Make up some constants.
const UID: u32 = 1000;

View File

@ -0,0 +1,907 @@
use std::time::SystemTime;
use rustc_hir::LangItem;
use rustc_middle::ty::{layout::TyAndLayout, query::TyCtxtAt, Ty};
use crate::concurrency::thread::Time;
use crate::*;
// pthread_mutexattr_t is either 4 or 8 bytes, depending on the platform.
// 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: &mut MiriInterpCx<'mir, 'tcx>,
kind: Scalar<Provenance>,
) -> InterpResult<'tcx, bool> {
Ok(kind == ecx.eval_libc("PTHREAD_MUTEX_DEFAULT")?)
}
fn is_mutex_kind_normal<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
kind: Scalar<Provenance>,
) -> InterpResult<'tcx, bool> {
let kind = kind.to_i32()?;
let mutex_normal_kind = ecx.eval_libc("PTHREAD_MUTEX_NORMAL")?.to_i32()?;
Ok(kind == (mutex_normal_kind | PTHREAD_MUTEX_NORMAL_FLAG))
}
fn mutexattr_get_kind<'mir, 'tcx: 'mir>(
ecx: &MiriInterpCx<'mir, 'tcx>,
attr_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
ecx.read_scalar_at_offset(attr_op, 0, ecx.machine.layouts.i32)
}
fn mutexattr_set_kind<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
attr_op: &OpTy<'tcx, Provenance>,
kind: impl Into<Scalar<Provenance>>,
) -> InterpResult<'tcx, ()> {
ecx.write_scalar_at_offset(attr_op, 0, kind, layout_of_maybe_uninit(ecx.tcx, ecx.tcx.types.i32))
}
// pthread_mutex_t is between 24 and 48 bytes, depending on the platform.
// 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)
fn mutex_get_kind<'mir, 'tcx: 'mir>(
ecx: &MiriInterpCx<'mir, 'tcx>,
mutex_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 };
ecx.read_scalar_at_offset_atomic(
mutex_op,
offset,
ecx.machine.layouts.i32,
AtomicReadOrd::Relaxed,
)
}
fn mutex_set_kind<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
mutex_op: &OpTy<'tcx, Provenance>,
kind: impl Into<Scalar<Provenance>>,
) -> InterpResult<'tcx, ()> {
let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 };
ecx.write_scalar_at_offset_atomic(
mutex_op,
offset,
kind,
layout_of_maybe_uninit(ecx.tcx, ecx.tcx.types.i32),
AtomicWriteOrd::Relaxed,
)
}
fn mutex_get_id<'mir, 'tcx: 'mir>(
ecx: &MiriInterpCx<'mir, 'tcx>,
mutex_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
ecx.read_scalar_at_offset_atomic(mutex_op, 4, ecx.machine.layouts.u32, AtomicReadOrd::Relaxed)
}
fn mutex_set_id<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
mutex_op: &OpTy<'tcx, Provenance>,
id: impl Into<Scalar<Provenance>>,
) -> InterpResult<'tcx, ()> {
ecx.write_scalar_at_offset_atomic(
mutex_op,
4,
id,
layout_of_maybe_uninit(ecx.tcx, ecx.tcx.types.u32),
AtomicWriteOrd::Relaxed,
)
}
fn mutex_get_or_create_id<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
mutex_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, MutexId> {
let value_place = ecx.deref_operand_and_offset(mutex_op, 4, ecx.machine.layouts.u32)?;
ecx.mutex_get_or_create(|ecx, next_id| {
let (old, success) = ecx
.atomic_compare_exchange_scalar(
&value_place,
&ImmTy::from_uint(0u32, ecx.machine.layouts.u32),
next_id.to_u32_scalar(),
AtomicRwOrd::Relaxed,
AtomicReadOrd::Relaxed,
false,
)?
.to_scalar_pair();
Ok(if success.to_bool().expect("compare_exchange's second return value is a bool") {
// Caller of the closure needs to allocate next_id
None
} else {
Some(MutexId::from_u32(old.to_u32().expect("layout is u32")))
})
})
}
// pthread_rwlock_t is between 32 and 56 bytes, depending on the platform.
// 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.
fn rwlock_get_id<'mir, 'tcx: 'mir>(
ecx: &MiriInterpCx<'mir, 'tcx>,
rwlock_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
ecx.read_scalar_at_offset_atomic(rwlock_op, 4, ecx.machine.layouts.u32, AtomicReadOrd::Relaxed)
}
fn rwlock_get_or_create_id<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
rwlock_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, RwLockId> {
let value_place = ecx.deref_operand_and_offset(rwlock_op, 4, ecx.machine.layouts.u32)?;
ecx.rwlock_get_or_create(|ecx, next_id| {
let (old, success) = ecx
.atomic_compare_exchange_scalar(
&value_place,
&ImmTy::from_uint(0u32, ecx.machine.layouts.u32),
next_id.to_u32_scalar(),
AtomicRwOrd::Relaxed,
AtomicReadOrd::Relaxed,
false,
)?
.to_scalar_pair();
Ok(if success.to_bool().expect("compare_exchange's second return value is a bool") {
// Caller of the closure needs to allocate next_id
None
} else {
Some(RwLockId::from_u32(old.to_u32().expect("layout is u32")))
})
})
}
// pthread_condattr_t
// 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).
fn condattr_get_clock_id<'mir, 'tcx: 'mir>(
ecx: &MiriInterpCx<'mir, 'tcx>,
attr_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
ecx.read_scalar_at_offset(attr_op, 0, ecx.machine.layouts.i32)
}
fn condattr_set_clock_id<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
attr_op: &OpTy<'tcx, Provenance>,
clock_id: impl Into<Scalar<Provenance>>,
) -> InterpResult<'tcx, ()> {
ecx.write_scalar_at_offset(
attr_op,
0,
clock_id,
layout_of_maybe_uninit(ecx.tcx, ecx.machine.layouts.i32.ty),
)
}
// pthread_cond_t
// Our chosen memory layout for the emulated conditional variable (does not have
// to match the platform layout!):
// 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
fn cond_get_id<'mir, 'tcx: 'mir>(
ecx: &MiriInterpCx<'mir, 'tcx>,
cond_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
ecx.read_scalar_at_offset_atomic(cond_op, 4, ecx.machine.layouts.u32, AtomicReadOrd::Relaxed)
}
fn cond_set_id<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
cond_op: &OpTy<'tcx, Provenance>,
id: impl Into<Scalar<Provenance>>,
) -> InterpResult<'tcx, ()> {
ecx.write_scalar_at_offset_atomic(
cond_op,
4,
id,
layout_of_maybe_uninit(ecx.tcx, ecx.tcx.types.u32),
AtomicWriteOrd::Relaxed,
)
}
fn cond_get_or_create_id<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
cond_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, CondvarId> {
let value_place = ecx.deref_operand_and_offset(cond_op, 4, ecx.machine.layouts.u32)?;
ecx.condvar_get_or_create(|ecx, next_id| {
let (old, success) = ecx
.atomic_compare_exchange_scalar(
&value_place,
&ImmTy::from_uint(0u32, ecx.machine.layouts.u32),
next_id.to_u32_scalar(),
AtomicRwOrd::Relaxed,
AtomicReadOrd::Relaxed,
false,
)?
.to_scalar_pair();
Ok(if success.to_bool().expect("compare_exchange's second return value is a bool") {
// Caller of the closure needs to allocate next_id
None
} else {
Some(CondvarId::from_u32(old.to_u32().expect("layout is u32")))
})
})
}
fn cond_get_clock_id<'mir, 'tcx: 'mir>(
ecx: &MiriInterpCx<'mir, 'tcx>,
cond_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
ecx.read_scalar_at_offset(cond_op, 8, ecx.machine.layouts.i32)
}
fn cond_set_clock_id<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
cond_op: &OpTy<'tcx, Provenance>,
clock_id: impl Into<Scalar<Provenance>>,
) -> InterpResult<'tcx, ()> {
ecx.write_scalar_at_offset(
cond_op,
8,
clock_id,
layout_of_maybe_uninit(ecx.tcx, ecx.tcx.types.i32),
)
}
/// Try to reacquire the mutex associated with the condition variable after we
/// were signaled.
fn reacquire_cond_mutex<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
thread: ThreadId,
mutex: MutexId,
) -> InterpResult<'tcx> {
ecx.unblock_thread(thread);
if ecx.mutex_is_locked(mutex) {
ecx.mutex_enqueue_and_block(mutex, thread);
} else {
ecx.mutex_lock(mutex, thread);
}
Ok(())
}
/// After a thread waiting on a condvar was signalled:
/// Reacquire the conditional variable and remove the timeout callback if any
/// was registered.
fn post_cond_signal<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
thread: ThreadId,
mutex: MutexId,
) -> InterpResult<'tcx> {
reacquire_cond_mutex(ecx, thread, 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);
Ok(())
}
/// Release the mutex associated with the condition variable because we are
/// entering the waiting state.
fn release_cond_mutex_and_block<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
active_thread: ThreadId,
mutex: MutexId,
) -> InterpResult<'tcx> {
if let Some(old_locked_count) = ecx.mutex_unlock(mutex, active_thread) {
if old_locked_count != 1 {
throw_unsup_format!("awaiting on a lock acquired multiple times is not supported");
}
} else {
throw_ub_format!("awaiting on unlocked or owned by a different thread mutex");
}
ecx.block_thread(active_thread);
Ok(())
}
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn pthread_mutexattr_init(
&mut self,
attr_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let default_kind = this.eval_libc("PTHREAD_MUTEX_DEFAULT")?;
mutexattr_set_kind(this, attr_op, default_kind)?;
Ok(0)
}
fn pthread_mutexattr_settype(
&mut self,
attr_op: &OpTy<'tcx, Provenance>,
kind_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let kind = this.read_scalar(kind_op)?;
if kind == this.eval_libc("PTHREAD_MUTEX_NORMAL")? {
// In `glibc` implementation, the numeric values of
// `PTHREAD_MUTEX_NORMAL` and `PTHREAD_MUTEX_DEFAULT` are equal.
// However, a mutex created by explicitly passing
// `PTHREAD_MUTEX_NORMAL` type has in some cases different behaviour
// from the default mutex for which the type was not explicitly
// specified. For a more detailed discussion, please see
// https://github.com/rust-lang/miri/issues/1419.
//
// To distinguish these two cases in already constructed mutexes, we
// use the same trick as glibc: for the case when
// `pthread_mutexattr_settype` is caled explicitly, we set the
// `PTHREAD_MUTEX_NORMAL_FLAG` flag.
let normal_kind = kind.to_i32()? | PTHREAD_MUTEX_NORMAL_FLAG;
// Check that after setting the flag, the kind is distinguishable
// from all other kinds.
assert_ne!(normal_kind, this.eval_libc("PTHREAD_MUTEX_DEFAULT")?.to_i32()?);
assert_ne!(normal_kind, this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")?.to_i32()?);
assert_ne!(normal_kind, this.eval_libc("PTHREAD_MUTEX_RECURSIVE")?.to_i32()?);
mutexattr_set_kind(this, attr_op, Scalar::from_i32(normal_kind))?;
} else if kind == this.eval_libc("PTHREAD_MUTEX_DEFAULT")?
|| kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")?
|| kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")?
{
mutexattr_set_kind(this, attr_op, kind)?;
} else {
let einval = this.eval_libc_i32("EINVAL")?;
return Ok(einval);
}
Ok(0)
}
fn pthread_mutexattr_destroy(
&mut self,
attr_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
// Destroying an uninit pthread_mutexattr is UB, so check to make sure it's not uninit.
mutexattr_get_kind(this, attr_op)?;
// To catch double-destroys, we de-initialize the mutexattr.
// This is technically not right and might lead to false positives. For example, the below
// code is *likely* sound, even assuming uninit numbers are UB, but Miri complains.
//
// let mut x: MaybeUninit<libc::pthread_mutexattr_t> = MaybeUninit::zeroed();
// libc::pthread_mutexattr_init(x.as_mut_ptr());
// libc::pthread_mutexattr_destroy(x.as_mut_ptr());
// x.assume_init();
//
// However, the way libstd uses the pthread APIs works in our favor here, so we can get away with this.
// This can always be revisited to have some external state to catch double-destroys
// but not complain about the above code. See https://github.com/rust-lang/miri/pull/1933
this.write_uninit(&this.deref_operand(attr_op)?.into())?;
Ok(0)
}
fn pthread_mutex_init(
&mut self,
mutex_op: &OpTy<'tcx, Provenance>,
attr_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let attr = this.read_pointer(attr_op)?;
let kind = if this.ptr_is_null(attr)? {
this.eval_libc("PTHREAD_MUTEX_DEFAULT")?
} else {
mutexattr_get_kind(this, attr_op)?
};
// Write 0 to use the same code path as the static initializers.
mutex_set_id(this, mutex_op, Scalar::from_i32(0))?;
mutex_set_kind(this, mutex_op, kind)?;
Ok(0)
}
fn pthread_mutex_lock(&mut self, mutex_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let kind = mutex_get_kind(this, mutex_op)?;
let id = mutex_get_or_create_id(this, mutex_op)?;
let active_thread = this.get_active_thread();
if this.mutex_is_locked(id) {
let owner_thread = this.mutex_get_owner(id);
if owner_thread != active_thread {
// Enqueue the active thread.
this.mutex_enqueue_and_block(id, active_thread);
Ok(0)
} else {
// Trying to acquire the same mutex again.
if is_mutex_kind_default(this, kind)? {
throw_ub_format!("trying to acquire already locked default mutex");
} else if is_mutex_kind_normal(this, kind)? {
throw_machine_stop!(TerminationInfo::Deadlock);
} else if kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")? {
this.eval_libc_i32("EDEADLK")
} else if kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? {
this.mutex_lock(id, active_thread);
Ok(0)
} else {
throw_unsup_format!(
"called pthread_mutex_lock on an unsupported type of mutex"
);
}
}
} else {
// The mutex is unlocked. Let's lock it.
this.mutex_lock(id, active_thread);
Ok(0)
}
}
fn pthread_mutex_trylock(
&mut self,
mutex_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let kind = mutex_get_kind(this, mutex_op)?;
let id = mutex_get_or_create_id(this, mutex_op)?;
let active_thread = this.get_active_thread();
if this.mutex_is_locked(id) {
let owner_thread = this.mutex_get_owner(id);
if owner_thread != active_thread {
this.eval_libc_i32("EBUSY")
} else {
if is_mutex_kind_default(this, kind)?
|| is_mutex_kind_normal(this, kind)?
|| kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")?
{
this.eval_libc_i32("EBUSY")
} else if kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? {
this.mutex_lock(id, active_thread);
Ok(0)
} else {
throw_unsup_format!(
"called pthread_mutex_trylock on an unsupported type of mutex"
);
}
}
} else {
// The mutex is unlocked. Let's lock it.
this.mutex_lock(id, active_thread);
Ok(0)
}
}
fn pthread_mutex_unlock(
&mut self,
mutex_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let kind = mutex_get_kind(this, mutex_op)?;
let id = mutex_get_or_create_id(this, mutex_op)?;
let active_thread = this.get_active_thread();
if let Some(_old_locked_count) = this.mutex_unlock(id, active_thread) {
// The mutex was locked by the current thread.
Ok(0)
} else {
// The mutex was locked by another thread or not locked at all. See
// the “Unlock When Not Owner” column in
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_unlock.html.
if is_mutex_kind_default(this, kind)? {
throw_ub_format!(
"unlocked a default mutex that was not locked by the current thread"
);
} else if is_mutex_kind_normal(this, kind)? {
throw_ub_format!(
"unlocked a PTHREAD_MUTEX_NORMAL mutex that was not locked by the current thread"
);
} else if kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")?
|| kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")?
{
this.eval_libc_i32("EPERM")
} else {
throw_unsup_format!("called pthread_mutex_unlock on an unsupported type of mutex");
}
}
}
fn pthread_mutex_destroy(
&mut self,
mutex_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let id = mutex_get_or_create_id(this, mutex_op)?;
if this.mutex_is_locked(id) {
throw_ub_format!("destroyed a locked mutex");
}
// Destroying an uninit pthread_mutex is UB, so check to make sure it's not uninit.
mutex_get_kind(this, mutex_op)?;
mutex_get_id(this, mutex_op)?;
// This might lead to false positives, see comment in pthread_mutexattr_destroy
this.write_uninit(&this.deref_operand(mutex_op)?.into())?;
// FIXME: delete interpreter state associated with this mutex.
Ok(0)
}
fn pthread_rwlock_rdlock(
&mut self,
rwlock_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let id = rwlock_get_or_create_id(this, rwlock_op)?;
let active_thread = this.get_active_thread();
if this.rwlock_is_write_locked(id) {
this.rwlock_enqueue_and_block_reader(id, active_thread);
Ok(0)
} else {
this.rwlock_reader_lock(id, active_thread);
Ok(0)
}
}
fn pthread_rwlock_tryrdlock(
&mut self,
rwlock_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let id = rwlock_get_or_create_id(this, rwlock_op)?;
let active_thread = this.get_active_thread();
if this.rwlock_is_write_locked(id) {
this.eval_libc_i32("EBUSY")
} else {
this.rwlock_reader_lock(id, active_thread);
Ok(0)
}
}
fn pthread_rwlock_wrlock(
&mut self,
rwlock_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let id = rwlock_get_or_create_id(this, rwlock_op)?;
let active_thread = this.get_active_thread();
if this.rwlock_is_locked(id) {
// Note: this will deadlock if the lock is already locked by this
// thread in any way.
//
// Relevant documentation:
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_rwlock_wrlock.html
// An in-depth discussion on this topic:
// https://github.com/rust-lang/rust/issues/53127
//
// FIXME: Detect and report the deadlock proactively. (We currently
// report the deadlock only when no thread can continue execution,
// but we could detect that this lock is already locked and report
// an error.)
this.rwlock_enqueue_and_block_writer(id, active_thread);
} else {
this.rwlock_writer_lock(id, active_thread);
}
Ok(0)
}
fn pthread_rwlock_trywrlock(
&mut self,
rwlock_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let id = rwlock_get_or_create_id(this, rwlock_op)?;
let active_thread = this.get_active_thread();
if this.rwlock_is_locked(id) {
this.eval_libc_i32("EBUSY")
} else {
this.rwlock_writer_lock(id, active_thread);
Ok(0)
}
}
fn pthread_rwlock_unlock(
&mut self,
rwlock_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let id = rwlock_get_or_create_id(this, rwlock_op)?;
let active_thread = this.get_active_thread();
#[allow(clippy::if_same_then_else)]
if this.rwlock_reader_unlock(id, active_thread) {
Ok(0)
} else if this.rwlock_writer_unlock(id, active_thread) {
Ok(0)
} else {
throw_ub_format!("unlocked an rwlock that was not locked by the active thread");
}
}
fn pthread_rwlock_destroy(
&mut self,
rwlock_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let id = rwlock_get_or_create_id(this, rwlock_op)?;
if this.rwlock_is_locked(id) {
throw_ub_format!("destroyed a locked rwlock");
}
// Destroying an uninit pthread_rwlock is UB, so check to make sure it's not uninit.
rwlock_get_id(this, rwlock_op)?;
// This might lead to false positives, see comment in pthread_mutexattr_destroy
this.write_uninit(&this.deref_operand(rwlock_op)?.into())?;
// FIXME: delete interpreter state associated with this rwlock.
Ok(0)
}
fn pthread_condattr_init(
&mut self,
attr_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
// 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("CLOCK_REALTIME")?;
condattr_set_clock_id(this, attr_op, default_clock_id)?;
Ok(0)
}
fn pthread_condattr_setclock(
&mut self,
attr_op: &OpTy<'tcx, Provenance>,
clock_id_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let clock_id = this.read_scalar(clock_id_op)?;
if clock_id == this.eval_libc("CLOCK_REALTIME")?
|| clock_id == this.eval_libc("CLOCK_MONOTONIC")?
{
condattr_set_clock_id(this, attr_op, clock_id)?;
} else {
let einval = this.eval_libc_i32("EINVAL")?;
return Ok(Scalar::from_i32(einval));
}
Ok(Scalar::from_i32(0))
}
fn pthread_condattr_getclock(
&mut self,
attr_op: &OpTy<'tcx, Provenance>,
clk_id_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let clock_id = condattr_get_clock_id(this, attr_op)?;
this.write_scalar(clock_id, &this.deref_operand(clk_id_op)?.into())?;
Ok(Scalar::from_i32(0))
}
fn pthread_condattr_destroy(
&mut self,
attr_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
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)?;
// This might lead to false positives, see comment in pthread_mutexattr_destroy
this.write_uninit(&this.deref_operand(attr_op)?.into())?;
Ok(0)
}
fn pthread_cond_init(
&mut self,
cond_op: &OpTy<'tcx, Provenance>,
attr_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let attr = this.read_pointer(attr_op)?;
let clock_id = if this.ptr_is_null(attr)? {
this.eval_libc("CLOCK_REALTIME")?
} else {
condattr_get_clock_id(this, attr_op)?
};
// Write 0 to use the same code path as the static initializers.
cond_set_id(this, cond_op, Scalar::from_i32(0))?;
cond_set_clock_id(this, cond_op, clock_id)?;
Ok(0)
}
fn pthread_cond_signal(&mut self, cond_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let id = cond_get_or_create_id(this, cond_op)?;
if let Some((thread, mutex)) = this.condvar_signal(id) {
post_cond_signal(this, thread, mutex)?;
}
Ok(0)
}
fn pthread_cond_broadcast(
&mut self,
cond_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let id = cond_get_or_create_id(this, cond_op)?;
while let Some((thread, mutex)) = this.condvar_signal(id) {
post_cond_signal(this, thread, mutex)?;
}
Ok(0)
}
fn pthread_cond_wait(
&mut self,
cond_op: &OpTy<'tcx, Provenance>,
mutex_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let id = cond_get_or_create_id(this, cond_op)?;
let mutex_id = mutex_get_or_create_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, mutex_id);
Ok(0)
}
fn pthread_cond_timedwait(
&mut self,
cond_op: &OpTy<'tcx, Provenance>,
mutex_op: &OpTy<'tcx, Provenance>,
abstime_op: &OpTy<'tcx, Provenance>,
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
this.check_no_isolation("`pthread_cond_timedwait`")?;
let id = cond_get_or_create_id(this, cond_op)?;
let mutex_id = mutex_get_or_create_id(this, mutex_op)?;
let active_thread = this.get_active_thread();
// Extract the timeout.
let clock_id = cond_get_clock_id(this, cond_op)?.to_i32()?;
let duration = match this.read_timespec(&this.deref_operand(abstime_op)?)? {
Some(duration) => duration,
None => {
let einval = this.eval_libc("EINVAL")?;
this.write_scalar(einval, dest)?;
return Ok(());
}
};
let timeout_time = if clock_id == this.eval_libc_i32("CLOCK_REALTIME")? {
Time::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())
} 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, mutex_id);
// We return success for now and override it in the timeout callback.
this.write_scalar(Scalar::from_i32(0), dest)?;
// Register the timeout callback.
let dest = dest.clone();
this.register_timeout_callback(
active_thread,
timeout_time,
Box::new(move |ecx| {
// We are not waiting for the condvar any more, wait for the
// mutex instead.
reacquire_cond_mutex(ecx, active_thread, mutex_id)?;
// Remove the thread from the conditional variable.
ecx.condvar_remove_waiter(id, active_thread);
// Set the return value: we timed out.
let etimedout = ecx.eval_libc("ETIMEDOUT")?;
ecx.write_scalar(etimedout, &dest)?;
Ok(())
}),
);
Ok(())
}
fn pthread_cond_destroy(
&mut self,
cond_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let id = cond_get_or_create_id(this, cond_op)?;
if this.condvar_is_awaited(id) {
throw_ub_format!("destroying an awaited conditional variable");
}
// Destroying an uninit pthread_cond is UB, so check to make sure it's not uninit.
cond_get_id(this, cond_op)?;
cond_get_clock_id(this, cond_op)?;
// This might lead to false positives, see comment in pthread_mutexattr_destroy
this.write_uninit(&this.deref_operand(cond_op)?.into())?;
// FIXME: delete interpreter state associated with this condvar.
Ok(0)
}
}
fn layout_of_maybe_uninit<'tcx>(tcx: TyCtxtAt<'tcx>, param: Ty<'tcx>) -> TyAndLayout<'tcx> {
let def_id = tcx.require_lang_item(LangItem::MaybeUninit, None);
let ty = tcx.bound_type_of(def_id).subst(*tcx, &[param.into()]);
let param_env = tcx.param_env(def_id);
tcx.layout_of(param_env.and(ty)).unwrap()
}

View File

@ -0,0 +1,93 @@
use crate::*;
use rustc_middle::ty::layout::LayoutOf;
use rustc_target::spec::abi::Abi;
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn pthread_create(
&mut self,
thread: &OpTy<'tcx, Provenance>,
_attr: &OpTy<'tcx, Provenance>,
start_routine: &OpTy<'tcx, Provenance>,
arg: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let thread_info_place = this.deref_operand(thread)?;
let start_routine = this.read_pointer(start_routine)?;
let func_arg = this.read_immediate(arg)?;
this.start_thread(
Some(thread_info_place),
start_routine,
Abi::C { unwind: false },
func_arg,
this.layout_of(this.tcx.types.usize)?,
)?;
Ok(0)
}
fn pthread_join(
&mut self,
thread: &OpTy<'tcx, Provenance>,
retval: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
if !this.ptr_is_null(this.read_pointer(retval)?)? {
// FIXME: implement reading the thread function's return place.
throw_unsup_format!("Miri supports pthread_join only with retval==NULL");
}
let thread_id = this.read_scalar(thread)?.to_machine_usize(this)?;
this.join_thread_exclusive(thread_id.try_into().expect("thread ID should fit in u32"))?;
Ok(0)
}
fn pthread_detach(&mut self, thread: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let thread_id = this.read_scalar(thread)?.to_machine_usize(this)?;
this.detach_thread(
thread_id.try_into().expect("thread ID should fit in u32"),
/*allow_terminated_joined*/ false,
)?;
Ok(0)
}
fn pthread_self(&mut self) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let thread_id = this.get_active_thread();
Ok(Scalar::from_machine_usize(thread_id.into(), this))
}
fn pthread_setname_np(
&mut self,
thread: Scalar<Provenance>,
name: Scalar<Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let thread = ThreadId::try_from(thread.to_machine_usize(this)?).unwrap();
let name = name.to_pointer(this)?;
let name = this.read_c_str(name)?.to_owned();
this.set_thread_name(thread, name);
Ok(Scalar::from_u32(0))
}
fn sched_yield(&mut self) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
this.yield_active_thread();
Ok(0)
}
}

View File

@ -0,0 +1,136 @@
use rustc_middle::mir;
use rustc_target::abi::Size;
use rustc_target::spec::abi::Abi;
use log::trace;
use crate::helpers::check_arg_count;
use crate::shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle};
use crate::*;
#[derive(Debug, Copy, Clone)]
pub enum Dlsym {
NtWriteFile,
SetThreadDescription,
}
impl Dlsym {
// Returns an error for unsupported symbols, and None if this symbol
// should become a NULL pointer (pretend it does not exist).
pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option<Dlsym>> {
Ok(match name {
"GetSystemTimePreciseAsFileTime" => None,
"NtWriteFile" => Some(Dlsym::NtWriteFile),
"SetThreadDescription" => Some(Dlsym::SetThreadDescription),
_ => throw_unsup_format!("unsupported Windows dlsym: {}", name),
})
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn call_dlsym(
&mut self,
dlsym: Dlsym,
abi: Abi,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
ret: Option<mir::BasicBlock>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let ret = ret.expect("we don't support any diverging dlsym");
assert!(this.tcx.sess.target.os == "windows");
this.check_abi(abi, Abi::System { unwind: false })?;
match dlsym {
Dlsym::NtWriteFile => {
if !this.frame_in_std() {
throw_unsup_format!(
"`NtWriteFile` support is crude and just enough for stdout to work"
);
}
let [
handle,
_event,
_apc_routine,
_apc_context,
io_status_block,
buf,
n,
byte_offset,
_key,
] = check_arg_count(args)?;
let handle = this.read_scalar(handle)?.to_machine_isize(this)?;
let buf = this.read_pointer(buf)?;
let n = this.read_scalar(n)?.to_u32()?;
let byte_offset = this.read_scalar(byte_offset)?.to_machine_usize(this)?; // is actually a pointer
let io_status_block = this.deref_operand(io_status_block)?;
if byte_offset != 0 {
throw_unsup_format!(
"`NtWriteFile` `ByteOffset` paremeter is non-null, which is unsupported"
);
}
let written = if handle == -11 || handle == -12 {
// stdout/stderr
use std::io::{self, Write};
let buf_cont =
this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(u64::from(n)))?;
let res = if this.machine.mute_stdout_stderr {
Ok(buf_cont.len())
} else if handle == -11 {
io::stdout().write(buf_cont)
} else {
io::stderr().write(buf_cont)
};
// We write at most `n` bytes, which is a `u32`, so we cannot have written more than that.
res.ok().map(|n| u32::try_from(n).unwrap())
} else {
throw_unsup_format!(
"on Windows, writing to anything except stdout/stderr is not supported"
)
};
// We have to put the result into io_status_block.
if let Some(n) = written {
let io_status_information =
this.mplace_field_named(&io_status_block, "Information")?;
this.write_scalar(
Scalar::from_machine_usize(n.into(), this),
&io_status_information.into(),
)?;
}
// Return whether this was a success. >= 0 is success.
// For the error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR.
this.write_scalar(
Scalar::from_u32(if written.is_some() { 0 } else { 0xC0000185u32 }),
dest,
)?;
}
Dlsym::SetThreadDescription => {
let [handle, name] = check_arg_count(args)?;
let handle = this.read_scalar(handle)?;
let name = this.read_wide_str(this.read_pointer(name)?)?;
let thread = match Handle::from_scalar(handle, this)? {
Some(Handle::Thread(thread)) => thread,
Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.get_active_thread(),
_ => this.invalid_handle("SetThreadDescription")?,
};
this.set_thread_name_wide(thread, &name);
this.write_null(dest)?;
}
}
trace!("{:?}", this.dump_place(**dest));
this.go_to_block(ret);
Ok(())
}
}

View File

@ -0,0 +1,442 @@
use std::iter;
use rustc_span::Symbol;
use rustc_target::abi::Size;
use rustc_target::spec::abi::Abi;
use crate::*;
use shims::foreign_items::EmulateByNameResult;
use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle};
use shims::windows::sync::EvalContextExt as _;
use shims::windows::thread::EvalContextExt as _;
use smallvec::SmallVec;
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_by_name(
&mut self,
link_name: Symbol,
abi: Abi,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
let this = self.eval_context_mut();
// See `fn emulate_foreign_item_by_name` in `shims/foreign_items.rs` for the general pattern.
// Windows API stubs.
// HANDLE = isize
// NTSTATUS = LONH = i32
// DWORD = ULONG = u32
// BOOL = i32
// BOOLEAN = u8
match link_name.as_str() {
// Environment related shims
"GetEnvironmentVariableW" => {
let [name, buf, size] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let result = this.GetEnvironmentVariableW(name, buf, size)?;
this.write_scalar(Scalar::from_u32(result), dest)?;
}
"SetEnvironmentVariableW" => {
let [name, value] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let result = this.SetEnvironmentVariableW(name, value)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"GetEnvironmentStringsW" => {
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let result = this.GetEnvironmentStringsW()?;
this.write_pointer(result, dest)?;
}
"FreeEnvironmentStringsW" => {
let [env_block] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let result = this.FreeEnvironmentStringsW(env_block)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"GetCurrentDirectoryW" => {
let [size, buf] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let result = this.GetCurrentDirectoryW(size, buf)?;
this.write_scalar(Scalar::from_u32(result), dest)?;
}
"SetCurrentDirectoryW" => {
let [path] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let result = this.SetCurrentDirectoryW(path)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
// Allocation
"HeapAlloc" => {
let [handle, flags, size] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.read_scalar(handle)?.to_machine_isize(this)?;
let flags = this.read_scalar(flags)?.to_u32()?;
let size = this.read_scalar(size)?.to_machine_usize(this)?;
let zero_init = (flags & 0x00000008) != 0; // HEAP_ZERO_MEMORY
let res = this.malloc(size, zero_init, MiriMemoryKind::WinHeap)?;
this.write_pointer(res, dest)?;
}
"HeapFree" => {
let [handle, flags, ptr] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.read_scalar(handle)?.to_machine_isize(this)?;
this.read_scalar(flags)?.to_u32()?;
let ptr = this.read_pointer(ptr)?;
this.free(ptr, MiriMemoryKind::WinHeap)?;
this.write_scalar(Scalar::from_i32(1), dest)?;
}
"HeapReAlloc" => {
let [handle, flags, ptr, size] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.read_scalar(handle)?.to_machine_isize(this)?;
this.read_scalar(flags)?.to_u32()?;
let ptr = this.read_pointer(ptr)?;
let size = this.read_scalar(size)?.to_machine_usize(this)?;
let res = this.realloc(ptr, size, MiriMemoryKind::WinHeap)?;
this.write_pointer(res, dest)?;
}
// errno
"SetLastError" => {
let [error] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let error = this.read_scalar(error)?;
this.set_last_error(error)?;
}
"GetLastError" => {
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let last_error = this.get_last_error()?;
this.write_scalar(last_error, dest)?;
}
// Querying system information
"GetSystemInfo" => {
// Also called from `page_size` crate.
let [system_info] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let system_info = this.deref_operand(system_info)?;
// Initialize with `0`.
this.write_bytes_ptr(
system_info.ptr,
iter::repeat(0u8).take(system_info.layout.size.bytes_usize()),
)?;
// Set selected fields.
let word_layout = this.machine.layouts.u16;
let dword_layout = this.machine.layouts.u32;
let usize_layout = this.machine.layouts.usize;
// Using `mplace_field` is error-prone, see: https://github.com/rust-lang/miri/issues/2136.
// Pointer fields have different sizes on different targets.
// To avoid all these issue we calculate the offsets ourselves.
let field_sizes = [
word_layout.size, // 0, wProcessorArchitecture : WORD
word_layout.size, // 1, wReserved : WORD
dword_layout.size, // 2, dwPageSize : DWORD
usize_layout.size, // 3, lpMinimumApplicationAddress : LPVOID
usize_layout.size, // 4, lpMaximumApplicationAddress : LPVOID
usize_layout.size, // 5, dwActiveProcessorMask : DWORD_PTR
dword_layout.size, // 6, dwNumberOfProcessors : DWORD
dword_layout.size, // 7, dwProcessorType : DWORD
dword_layout.size, // 8, dwAllocationGranularity : DWORD
word_layout.size, // 9, wProcessorLevel : WORD
word_layout.size, // 10, wProcessorRevision : WORD
];
let field_offsets: SmallVec<[Size; 11]> = field_sizes
.iter()
.copied()
.scan(Size::ZERO, |a, x| {
let res = Some(*a);
*a += x;
res
})
.collect();
// Set page size.
let page_size = system_info.offset(field_offsets[2], dword_layout, &this.tcx)?;
this.write_scalar(
Scalar::from_int(PAGE_SIZE, dword_layout.size),
&page_size.into(),
)?;
// Set number of processors.
let num_cpus = system_info.offset(field_offsets[6], dword_layout, &this.tcx)?;
this.write_scalar(Scalar::from_int(NUM_CPUS, dword_layout.size), &num_cpus.into())?;
}
// Thread-local storage
"TlsAlloc" => {
// This just creates a key; Windows does not natively support TLS destructors.
// Create key and return it.
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let key = this.machine.tls.create_tls_key(None, dest.layout.size)?;
this.write_scalar(Scalar::from_uint(key, dest.layout.size), dest)?;
}
"TlsGetValue" => {
let [key] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let key = u128::from(this.read_scalar(key)?.to_u32()?);
let active_thread = this.get_active_thread();
let ptr = this.machine.tls.load_tls(key, active_thread, this)?;
this.write_scalar(ptr, dest)?;
}
"TlsSetValue" => {
let [key, new_ptr] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let key = u128::from(this.read_scalar(key)?.to_u32()?);
let active_thread = this.get_active_thread();
let new_data = this.read_scalar(new_ptr)?;
this.machine.tls.store_tls(key, active_thread, new_data, &*this.tcx)?;
// Return success (`1`).
this.write_scalar(Scalar::from_i32(1), dest)?;
}
// Access to command-line arguments
"GetCommandLineW" => {
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.write_pointer(
this.machine.cmd_line.expect("machine must be initialized").ptr,
dest,
)?;
}
// Time related shims
"GetSystemTimeAsFileTime" => {
#[allow(non_snake_case)]
let [LPFILETIME] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.GetSystemTimeAsFileTime(LPFILETIME)?;
}
"QueryPerformanceCounter" => {
#[allow(non_snake_case)]
let [lpPerformanceCount] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let result = this.QueryPerformanceCounter(lpPerformanceCount)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"QueryPerformanceFrequency" => {
#[allow(non_snake_case)]
let [lpFrequency] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let result = this.QueryPerformanceFrequency(lpFrequency)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"Sleep" => {
let [timeout] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.Sleep(timeout)?;
}
// Synchronization primitives
"AcquireSRWLockExclusive" => {
let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.AcquireSRWLockExclusive(ptr)?;
}
"ReleaseSRWLockExclusive" => {
let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.ReleaseSRWLockExclusive(ptr)?;
}
"TryAcquireSRWLockExclusive" => {
let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let ret = this.TryAcquireSRWLockExclusive(ptr)?;
this.write_scalar(Scalar::from_u8(ret), dest)?;
}
"AcquireSRWLockShared" => {
let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.AcquireSRWLockShared(ptr)?;
}
"ReleaseSRWLockShared" => {
let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.ReleaseSRWLockShared(ptr)?;
}
"TryAcquireSRWLockShared" => {
let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let ret = this.TryAcquireSRWLockShared(ptr)?;
this.write_scalar(Scalar::from_u8(ret), dest)?;
}
// Dynamic symbol loading
"GetProcAddress" => {
#[allow(non_snake_case)]
let [hModule, lpProcName] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.read_scalar(hModule)?.to_machine_isize(this)?;
let name = this.read_c_str(this.read_pointer(lpProcName)?)?;
if let Some(dlsym) = Dlsym::from_str(name, &this.tcx.sess.target.os)? {
let ptr = this.create_fn_alloc_ptr(FnVal::Other(dlsym));
this.write_pointer(ptr, dest)?;
} else {
this.write_null(dest)?;
}
}
// Miscellaneous
"SystemFunction036" => {
// This is really 'RtlGenRandom'.
let [ptr, len] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
let len = this.read_scalar(len)?.to_u32()?;
this.gen_random(ptr, len.into())?;
this.write_scalar(Scalar::from_bool(true), dest)?;
}
"BCryptGenRandom" => {
let [algorithm, ptr, len, flags] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let algorithm = this.read_scalar(algorithm)?;
let algorithm = algorithm.to_machine_usize(this)?;
let ptr = this.read_pointer(ptr)?;
let len = this.read_scalar(len)?.to_u32()?;
let flags = this.read_scalar(flags)?.to_u32()?;
match flags {
0 => {
if algorithm != 0x81 {
// BCRYPT_RNG_ALG_HANDLE
throw_unsup_format!(
"BCryptGenRandom algorithm must be BCRYPT_RNG_ALG_HANDLE when the flag is 0"
);
}
}
2 => {
// BCRYPT_USE_SYSTEM_PREFERRED_RNG
if algorithm != 0 {
throw_unsup_format!(
"BCryptGenRandom algorithm must be NULL when the flag is BCRYPT_USE_SYSTEM_PREFERRED_RNG"
);
}
}
_ => {
throw_unsup_format!(
"BCryptGenRandom is only supported with BCRYPT_USE_SYSTEM_PREFERRED_RNG or BCRYPT_RNG_ALG_HANDLE"
);
}
}
this.gen_random(ptr, len.into())?;
this.write_null(dest)?; // STATUS_SUCCESS
}
"GetConsoleScreenBufferInfo" => {
// `term` needs this, so we fake it.
let [console, buffer_info] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.read_scalar(console)?.to_machine_isize(this)?;
this.deref_operand(buffer_info)?;
// Indicate an error.
// FIXME: we should set last_error, but to what?
this.write_null(dest)?;
}
"GetConsoleMode" => {
// Windows "isatty" (in libtest) needs this, so we fake it.
let [console, mode] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.read_scalar(console)?.to_machine_isize(this)?;
this.deref_operand(mode)?;
// Indicate an error.
// FIXME: we should set last_error, but to what?
this.write_null(dest)?;
}
"GetStdHandle" => {
let [which] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let which = this.read_scalar(which)?.to_i32()?;
// We just make this the identity function, so we know later in `NtWriteFile` which
// one it is. This is very fake, but libtest needs it so we cannot make it a
// std-only shim.
// FIXME: this should return real HANDLEs when io support is added
this.write_scalar(Scalar::from_machine_isize(which.into(), this), dest)?;
}
"CloseHandle" => {
let [handle] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.CloseHandle(handle)?;
this.write_scalar(Scalar::from_u32(1), dest)?;
}
// Threading
"CreateThread" => {
let [security, stacksize, start, arg, flags, thread] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let thread_id =
this.CreateThread(security, stacksize, start, arg, flags, thread)?;
this.write_scalar(Handle::Thread(thread_id).to_scalar(this), dest)?;
}
"WaitForSingleObject" => {
let [handle, timeout] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let ret = this.WaitForSingleObject(handle, timeout)?;
this.write_scalar(Scalar::from_u32(ret), dest)?;
}
"GetCurrentThread" => {
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.write_scalar(
Handle::Pseudo(PseudoHandle::CurrentThread).to_scalar(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.
"GetProcessHeap" if this.frame_in_std() => {
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
// Just fake a HANDLE
// It's fine to not use the Handle type here because its a stub
this.write_scalar(Scalar::from_machine_isize(1, this), dest)?;
}
"GetModuleHandleA" if this.frame_in_std() => {
#[allow(non_snake_case)]
let [_lpModuleName] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
// We need to return something non-null here to make `compat_fn!` work.
this.write_scalar(Scalar::from_machine_isize(1, this), dest)?;
}
"SetConsoleTextAttribute" if this.frame_in_std() => {
#[allow(non_snake_case)]
let [_hConsoleOutput, _wAttribute] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
// Pretend these does not exist / nothing happened, by returning zero.
this.write_null(dest)?;
}
"AddVectoredExceptionHandler" if this.frame_in_std() => {
#[allow(non_snake_case)]
let [_First, _Handler] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
// Any non zero value works for the stdlib. This is just used for stack overflows anyway.
this.write_scalar(Scalar::from_machine_usize(1, this), dest)?;
}
"SetThreadStackGuarantee" if this.frame_in_std() => {
#[allow(non_snake_case)]
let [_StackSizeInBytes] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
// Any non zero value works for the stdlib. This is just used for stack overflows anyway.
this.write_scalar(Scalar::from_u32(1), dest)?;
}
"GetCurrentProcessId" if this.frame_in_std() => {
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let result = this.GetCurrentProcessId()?;
this.write_scalar(Scalar::from_u32(result), dest)?;
}
// this is only callable from std because we know that std ignores the return value
"SwitchToThread" if this.frame_in_std() => {
let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.yield_active_thread();
// FIXME: this should return a nonzero value if this call does result in switching to another thread.
this.write_null(dest)?;
}
_ => return Ok(EmulateByNameResult::NotSupported),
}
Ok(EmulateByNameResult::NeedsJumping)
}
}

View File

@ -0,0 +1,171 @@
use rustc_target::abi::HasDataLayout;
use std::mem::variant_count;
use crate::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PseudoHandle {
CurrentThread,
}
/// Miri representation of a Windows `HANDLE`
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Handle {
Null,
Pseudo(PseudoHandle),
Thread(ThreadId),
}
impl PseudoHandle {
const CURRENT_THREAD_VALUE: u32 = 0;
fn value(self) -> u32 {
match self {
Self::CurrentThread => Self::CURRENT_THREAD_VALUE,
}
}
fn from_value(value: u32) -> Option<Self> {
match value {
Self::CURRENT_THREAD_VALUE => Some(Self::CurrentThread),
_ => None,
}
}
}
impl Handle {
const NULL_DISCRIMINANT: u32 = 0;
const PSEUDO_DISCRIMINANT: u32 = 1;
const THREAD_DISCRIMINANT: u32 = 2;
fn discriminant(self) -> u32 {
match self {
Self::Null => Self::NULL_DISCRIMINANT,
Self::Pseudo(_) => Self::PSEUDO_DISCRIMINANT,
Self::Thread(_) => Self::THREAD_DISCRIMINANT,
}
}
fn data(self) -> u32 {
match self {
Self::Null => 0,
Self::Pseudo(pseudo_handle) => pseudo_handle.value(),
Self::Thread(thread) => thread.to_u32(),
}
}
fn packed_disc_size() -> u32 {
// ceil(log2(x)) is how many bits it takes to store x numbers
let variant_count = variant_count::<Self>();
// however, std's ilog2 is floor(log2(x))
let floor_log2 = variant_count.ilog2();
// we need to add one for non powers of two to compensate for the difference
#[allow(clippy::integer_arithmetic)] // cannot overflow
if variant_count.is_power_of_two() { floor_log2 } else { floor_log2 + 1 }
}
/// Converts a handle into its machine representation.
///
/// The upper [`Self::packed_disc_size()`] bits are used to store a discriminant corresponding to the handle variant.
/// The remaining bits are used for the variant's field.
///
/// None of this layout is guaranteed to applications by Windows or Miri.
fn to_packed(self) -> u32 {
let disc_size = Self::packed_disc_size();
let data_size = u32::BITS.checked_sub(disc_size).unwrap();
let discriminant = self.discriminant();
let data = self.data();
// make sure the discriminant fits into `disc_size` bits
assert!(discriminant < 2u32.pow(disc_size));
// make sure the data fits into `data_size` bits
assert!(data < 2u32.pow(data_size));
// packs the data into the lower `data_size` bits
// and packs the discriminant right above the data
#[allow(clippy::integer_arithmetic)] // cannot overflow
return discriminant << data_size | data;
}
fn new(discriminant: u32, data: u32) -> Option<Self> {
match discriminant {
Self::NULL_DISCRIMINANT if data == 0 => Some(Self::Null),
Self::PSEUDO_DISCRIMINANT => Some(Self::Pseudo(PseudoHandle::from_value(data)?)),
Self::THREAD_DISCRIMINANT => Some(Self::Thread(data.into())),
_ => None,
}
}
/// see docs for `to_packed`
fn from_packed(handle: u32) -> Option<Self> {
let disc_size = Self::packed_disc_size();
let data_size = u32::BITS.checked_sub(disc_size).unwrap();
// the lower `data_size` bits of this mask are 1
#[allow(clippy::integer_arithmetic)] // cannot overflow
let data_mask = 2u32.pow(data_size) - 1;
// the discriminant is stored right above the lower `data_size` bits
#[allow(clippy::integer_arithmetic)] // cannot overflow
let discriminant = handle >> data_size;
// the data is stored in the lower `data_size` bits
let data = handle & data_mask;
Self::new(discriminant, data)
}
pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar<Provenance> {
// 64-bit handles are sign extended 32-bit handles
// see https://docs.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication
#[allow(clippy::cast_possible_wrap)] // we want it to wrap
let signed_handle = self.to_packed() as i32;
Scalar::from_machine_isize(signed_handle.into(), cx)
}
pub fn from_scalar<'tcx>(
handle: Scalar<Provenance>,
cx: &impl HasDataLayout,
) -> InterpResult<'tcx, Option<Self>> {
let sign_extended_handle = handle.to_machine_isize(cx)?;
#[allow(clippy::cast_sign_loss)] // we want to lose the sign
let handle = if let Ok(signed_handle) = i32::try_from(sign_extended_handle) {
signed_handle as u32
} else {
// if a handle doesn't fit in an i32, it isn't valid.
return Ok(None);
};
Ok(Self::from_packed(handle))
}
}
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
#[allow(non_snake_case)]
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn invalid_handle(&mut self, function_name: &str) -> InterpResult<'tcx, !> {
throw_machine_stop!(TerminationInfo::Abort(format!(
"invalid handle passed to `{function_name}`"
)))
}
fn CloseHandle(&mut self, handle_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let handle = this.read_scalar(handle_op)?;
match Handle::from_scalar(handle, this)? {
Some(Handle::Thread(thread)) =>
this.detach_thread(thread, /*allow_terminated_joined*/ true)?,
_ => this.invalid_handle("CloseHandle")?,
}
Ok(())
}
}

View File

@ -0,0 +1,6 @@
pub mod dlsym;
pub mod foreign_items;
mod handle;
mod sync;
mod thread;

Some files were not shown because too many files have changed in this diff Show More