mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-26 08:44:35 +00:00
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:
commit
f45b570e08
25
src/tools/miri/.editorconfig
Normal file
25
src/tools/miri/.editorconfig
Normal 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
6
src/tools/miri/.gitattributes
vendored
Normal 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
156
src/tools/miri/.github/workflows/ci.yml
vendored
Normal 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
13
src/tools/miri/.gitignore
vendored
Normal 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-*
|
9
src/tools/miri/.gitpod.yml
Normal file
9
src/tools/miri/.gitpod.yml
Normal 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"
|
277
src/tools/miri/CONTRIBUTING.md
Normal file
277
src/tools/miri/CONTRIBUTING.md
Normal 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
813
src/tools/miri/Cargo.lock
Normal 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
61
src/tools/miri/Cargo.toml
Normal 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.
|
176
src/tools/miri/LICENSE-APACHE
Normal file
176
src/tools/miri/LICENSE-APACHE
Normal 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
|
23
src/tools/miri/LICENSE-MIT
Normal file
23
src/tools/miri/LICENSE-MIT
Normal 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
639
src/tools/miri/README.md
Normal 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.
|
94
src/tools/miri/bench-cargo-miri/backtraces/Cargo.lock
Normal file
94
src/tools/miri/bench-cargo-miri/backtraces/Cargo.lock
Normal 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"
|
9
src/tools/miri/bench-cargo-miri/backtraces/Cargo.toml
Normal file
9
src/tools/miri/bench-cargo-miri/backtraces/Cargo.toml
Normal 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"
|
29
src/tools/miri/bench-cargo-miri/backtraces/src/main.rs
Normal file
29
src/tools/miri/bench-cargo-miri/backtraces/src/main.rs
Normal 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);
|
||||
}
|
7
src/tools/miri/bench-cargo-miri/mse/Cargo.lock
Normal file
7
src/tools/miri/bench-cargo-miri/mse/Cargo.lock
Normal 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"
|
7
src/tools/miri/bench-cargo-miri/mse/Cargo.toml
Normal file
7
src/tools/miri/bench-cargo-miri/mse/Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "mse"
|
||||
version = "0.1.0"
|
||||
authors = ["Ralf Jung <post@ralfj.de>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
32
src/tools/miri/bench-cargo-miri/mse/src/main.rs
Normal file
32
src/tools/miri/bench-cargo-miri/mse/src/main.rs
Normal file
File diff suppressed because one or more lines are too long
89
src/tools/miri/bench-cargo-miri/serde1/Cargo.lock
Normal file
89
src/tools/miri/bench-cargo-miri/serde1/Cargo.lock
Normal 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"
|
9
src/tools/miri/bench-cargo-miri/serde1/Cargo.toml
Normal file
9
src/tools/miri/bench-cargo-miri/serde1/Cargo.toml
Normal 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"
|
13
src/tools/miri/bench-cargo-miri/serde1/src/main.rs
Normal file
13
src/tools/miri/bench-cargo-miri/serde1/src/main.rs
Normal file
File diff suppressed because one or more lines are too long
89
src/tools/miri/bench-cargo-miri/serde2/Cargo.lock
Normal file
89
src/tools/miri/bench-cargo-miri/serde2/Cargo.lock
Normal 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"
|
9
src/tools/miri/bench-cargo-miri/serde2/Cargo.toml
Normal file
9
src/tools/miri/bench-cargo-miri/serde2/Cargo.toml
Normal 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"
|
18
src/tools/miri/bench-cargo-miri/serde2/src/main.rs
Normal file
18
src/tools/miri/bench-cargo-miri/serde2/src/main.rs
Normal file
File diff suppressed because one or more lines are too long
@ -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"
|
@ -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]
|
@ -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;
|
||||
}
|
||||
}
|
16
src/tools/miri/bench-cargo-miri/unicode/Cargo.lock
Normal file
16
src/tools/miri/bench-cargo-miri/unicode/Cargo.lock
Normal 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"
|
9
src/tools/miri/bench-cargo-miri/unicode/Cargo.toml
Normal file
9
src/tools/miri/bench-cargo-miri/unicode/Cargo.toml
Normal 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"
|
20
src/tools/miri/bench-cargo-miri/unicode/src/main.rs
Normal file
20
src/tools/miri/bench-cargo-miri/unicode/src/main.rs
Normal 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
8
src/tools/miri/build.rs
Normal 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);
|
||||
}
|
554
src/tools/miri/cargo-miri/Cargo.lock
Normal file
554
src/tools/miri/cargo-miri/Cargo.lock
Normal 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"
|
32
src/tools/miri/cargo-miri/Cargo.toml
Normal file
32
src/tools/miri/cargo-miri/Cargo.toml
Normal 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"] }
|
11
src/tools/miri/cargo-miri/build.rs
Normal file
11
src/tools/miri/cargo-miri/build.rs
Normal 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
4
src/tools/miri/cargo-miri/miri
Executable 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 "$@"
|
134
src/tools/miri/cargo-miri/src/arg.rs
Normal file
134
src/tools/miri/cargo-miri/src/arg.rs
Normal 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()
|
||||
}
|
97
src/tools/miri/cargo-miri/src/main.rs
Normal file
97
src/tools/miri/cargo-miri/src/main.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
601
src/tools/miri/cargo-miri/src/phases.rs
Normal file
601
src/tools/miri/cargo-miri/src/phases.rs
Normal 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)
|
||||
}
|
245
src/tools/miri/cargo-miri/src/setup.rs
Normal file
245
src/tools/miri/cargo-miri/src/setup.rs
Normal 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());
|
||||
}
|
||||
}
|
314
src/tools/miri/cargo-miri/src/util.rs
Normal file
314
src/tools/miri/cargo-miri/src/util.rs
Normal 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);
|
||||
}
|
2
src/tools/miri/cargo-miri/src/version.rs
Normal file
2
src/tools/miri/cargo-miri/src/version.rs
Normal 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
101
src/tools/miri/ci.sh
Executable 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
236
src/tools/miri/miri
Executable 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
|
1
src/tools/miri/rust-version
Normal file
1
src/tools/miri/rust-version
Normal file
@ -0,0 +1 @@
|
||||
acb8934fd57b3c2740c4abac0a5728c2c9b1423b
|
5
src/tools/miri/rustfmt.toml
Normal file
5
src/tools/miri/rustfmt.toml
Normal 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
53
src/tools/miri/rustup-toolchain
Executable 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
|
562
src/tools/miri/src/bin/miri.rs
Normal file
562
src/tools/miri/src/bin/miri.rs
Normal 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
115
src/tools/miri/src/clock.rs
Normal 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) },
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
1596
src/tools/miri/src/concurrency/data_race.rs
Normal file
1596
src/tools/miri/src/concurrency/data_race.rs
Normal file
File diff suppressed because it is too large
Load Diff
6
src/tools/miri/src/concurrency/mod.rs
Normal file
6
src/tools/miri/src/concurrency/mod.rs
Normal 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;
|
277
src/tools/miri/src/concurrency/range_object_map.rs
Normal file
277
src/tools/miri/src/concurrency/range_object_map.rs
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
584
src/tools/miri/src/concurrency/sync.rs
Normal file
584
src/tools/miri/src/concurrency/sync.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
933
src/tools/miri/src/concurrency/thread.rs
Normal file
933
src/tools/miri/src/concurrency/thread.rs
Normal 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(())
|
||||
}
|
||||
}
|
470
src/tools/miri/src/concurrency/vector_clock.rs
Normal file
470
src/tools/miri/src/concurrency/vector_clock.rs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
630
src/tools/miri/src/concurrency/weak_memory.rs
Normal file
630
src/tools/miri/src/concurrency/weak_memory.rs
Normal 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(())
|
||||
}
|
||||
}
|
500
src/tools/miri/src/diagnostics.rs
Normal file
500
src/tools/miri/src/diagnostics.rs
Normal 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 ¬es {
|
||||
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
514
src/tools/miri/src/eval.rs
Normal 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""#);
|
||||
}
|
||||
}
|
1004
src/tools/miri/src/helpers.rs
Normal file
1004
src/tools/miri/src/helpers.rs
Normal file
File diff suppressed because it is too large
Load Diff
49
src/tools/miri/src/helpers/convert.rs
Normal file
49
src/tools/miri/src/helpers/convert.rs
Normal 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
|
||||
}
|
||||
}
|
260
src/tools/miri/src/intptrcast.rs
Normal file
260
src/tools/miri/src/intptrcast.rs
Normal 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
126
src/tools/miri/src/lib.rs
Normal 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",
|
||||
];
|
1077
src/tools/miri/src/machine.rs
Normal file
1077
src/tools/miri/src/machine.rs
Normal file
File diff suppressed because it is too large
Load Diff
110
src/tools/miri/src/mono_hash_map.rs
Normal file
110
src/tools/miri/src/mono_hash_map.rs
Normal 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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
90
src/tools/miri/src/operator.rs
Normal file
90
src/tools/miri/src/operator.rs
Normal 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),
|
||||
})
|
||||
}
|
||||
}
|
300
src/tools/miri/src/range_map.rs
Normal file
300
src/tools/miri/src/range_map.rs
Normal 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));
|
||||
}
|
||||
}
|
254
src/tools/miri/src/shims/backtrace.rs
Normal file
254
src/tools/miri/src/shims/backtrace.rs
Normal 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(())
|
||||
}
|
||||
}
|
48
src/tools/miri/src/shims/dlsym.rs
Normal file
48
src/tools/miri/src/shims/dlsym.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
469
src/tools/miri/src/shims/env.rs
Normal file
469
src/tools/miri/src/shims/env.rs
Normal 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())
|
||||
}
|
||||
}
|
291
src/tools/miri/src/shims/ffi_support.rs
Normal file
291
src/tools/miri/src/shims/ffi_support.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
811
src/tools/miri/src/shims/foreign_items.rs
Normal file
811
src/tools/miri/src/shims/foreign_items.rs
Normal 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(())
|
||||
}
|
||||
}
|
288
src/tools/miri/src/shims/intrinsics/atomic.rs
Normal file
288
src/tools/miri/src/shims/intrinsics/atomic.rs
Normal 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)
|
||||
}
|
||||
}
|
429
src/tools/miri/src/shims/intrinsics/mod.rs
Normal file
429
src/tools/miri/src/shims/intrinsics/mod.rs
Normal 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:?}"
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
627
src/tools/miri/src/shims/intrinsics/simd.rs
Normal file
627
src/tools/miri/src/shims/intrinsics/simd.rs
Normal 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()?)),
|
||||
})
|
||||
}
|
107
src/tools/miri/src/shims/mod.rs
Normal file
107
src/tools/miri/src/shims/mod.rs
Normal 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)
|
||||
}
|
||||
}
|
308
src/tools/miri/src/shims/os_str.rs
Normal file
308
src/tools/miri/src/shims/os_str.rs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
227
src/tools/miri/src/shims/panic.rs
Normal file
227
src/tools/miri/src/shims/panic.rs
Normal 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(())
|
||||
}
|
||||
}
|
255
src/tools/miri/src/shims/time.rs
Normal file
255
src/tools/miri/src/shims/time.rs
Normal 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(())
|
||||
}
|
||||
}
|
400
src/tools/miri/src/shims/tls.rs
Normal file
400
src/tools/miri/src/shims/tls.rs
Normal 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(())
|
||||
}
|
||||
}
|
54
src/tools/miri/src/shims/unix/android/dlsym.rs
Normal file
54
src/tools/miri/src/shims/unix/android/dlsym.rs
Normal 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(())
|
||||
}
|
||||
}
|
26
src/tools/miri/src/shims/unix/android/foreign_items.rs
Normal file
26
src/tools/miri/src/shims/unix/android/foreign_items.rs
Normal 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)
|
||||
}
|
||||
}
|
2
src/tools/miri/src/shims/unix/android/mod.rs
Normal file
2
src/tools/miri/src/shims/unix/android/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod dlsym;
|
||||
pub mod foreign_items;
|
55
src/tools/miri/src/shims/unix/dlsym.rs
Normal file
55
src/tools/miri/src/shims/unix/dlsym.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
606
src/tools/miri/src/shims/unix/foreign_items.rs
Normal file
606
src/tools/miri/src/shims/unix/foreign_items.rs
Normal 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)
|
||||
}
|
||||
}
|
36
src/tools/miri/src/shims/unix/freebsd/dlsym.rs
Normal file
36
src/tools/miri/src/shims/unix/freebsd/dlsym.rs
Normal 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(())
|
||||
}
|
||||
}
|
45
src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
Normal file
45
src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
Normal 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)
|
||||
}
|
||||
}
|
2
src/tools/miri/src/shims/unix/freebsd/mod.rs
Normal file
2
src/tools/miri/src/shims/unix/freebsd/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod dlsym;
|
||||
pub mod foreign_items;
|
1952
src/tools/miri/src/shims/unix/fs.rs
Normal file
1952
src/tools/miri/src/shims/unix/fs.rs
Normal file
File diff suppressed because it is too large
Load Diff
40
src/tools/miri/src/shims/unix/linux/dlsym.rs
Normal file
40
src/tools/miri/src/shims/unix/linux/dlsym.rs
Normal 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(())
|
||||
}
|
||||
}
|
187
src/tools/miri/src/shims/unix/linux/foreign_items.rs
Normal file
187
src/tools/miri/src/shims/unix/linux/foreign_items.rs
Normal 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(())
|
||||
}
|
3
src/tools/miri/src/shims/unix/linux/mod.rs
Normal file
3
src/tools/miri/src/shims/unix/linux/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod dlsym;
|
||||
pub mod foreign_items;
|
||||
pub mod sync;
|
261
src/tools/miri/src/shims/unix/linux/sync.rs
Normal file
261
src/tools/miri/src/shims/unix/linux/sync.rs
Normal 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(())
|
||||
}
|
52
src/tools/miri/src/shims/unix/macos/dlsym.rs
Normal file
52
src/tools/miri/src/shims/unix/macos/dlsym.rs
Normal 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(())
|
||||
}
|
||||
}
|
197
src/tools/miri/src/shims/unix/macos/foreign_items.rs
Normal file
197
src/tools/miri/src/shims/unix/macos/foreign_items.rs
Normal 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)
|
||||
}
|
||||
}
|
2
src/tools/miri/src/shims/unix/macos/mod.rs
Normal file
2
src/tools/miri/src/shims/unix/macos/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod dlsym;
|
||||
pub mod foreign_items;
|
16
src/tools/miri/src/shims/unix/mod.rs
Normal file
16
src/tools/miri/src/shims/unix/mod.rs
Normal 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;
|
907
src/tools/miri/src/shims/unix/sync.rs
Normal file
907
src/tools/miri/src/shims/unix/sync.rs
Normal 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()
|
||||
}
|
93
src/tools/miri/src/shims/unix/thread.rs
Normal file
93
src/tools/miri/src/shims/unix/thread.rs
Normal 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)
|
||||
}
|
||||
}
|
136
src/tools/miri/src/shims/windows/dlsym.rs
Normal file
136
src/tools/miri/src/shims/windows/dlsym.rs
Normal 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(())
|
||||
}
|
||||
}
|
442
src/tools/miri/src/shims/windows/foreign_items.rs
Normal file
442
src/tools/miri/src/shims/windows/foreign_items.rs
Normal 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)
|
||||
}
|
||||
}
|
171
src/tools/miri/src/shims/windows/handle.rs
Normal file
171
src/tools/miri/src/shims/windows/handle.rs
Normal 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(())
|
||||
}
|
||||
}
|
6
src/tools/miri/src/shims/windows/mod.rs
Normal file
6
src/tools/miri/src/shims/windows/mod.rs
Normal 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
Loading…
Reference in New Issue
Block a user