Auto merge of #123557 - GuillaumeGomez:rollup-3af7urf, r=GuillaumeGomez

Rollup of 4 pull requests

Successful merges:

 - #123541 (remove miri-test-libstd hacks that are no longer needed)
 - #123552 (Add missing -Zquery-dep-graph to the spike-neg incr comp tests)
 - #123553 (Miri subtree update)
 - #123554 (Simplify/cleanup `search-result-color.goml`)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2024-04-06 17:12:12 +00:00
commit aa1c45908d
39 changed files with 1153 additions and 1266 deletions

View File

@ -56,11 +56,6 @@
//! [`Rc`]: rc //! [`Rc`]: rc
//! [`RefCell`]: core::cell //! [`RefCell`]: core::cell
// To run alloc tests without x.py without ending up with two copies of alloc, Miri needs to be
// able to "empty" this crate. See <https://github.com/rust-lang/miri-test-libstd/issues/4>.
// rustc itself never sets the feature, so this line has no effect there.
#![cfg(any(not(feature = "miri-test-libstd"), test, doctest))]
//
#![allow(unused_attributes)] #![allow(unused_attributes)]
#![stable(feature = "alloc", since = "1.36.0")] #![stable(feature = "alloc", since = "1.36.0")]
#![doc( #![doc(
@ -71,7 +66,6 @@
#![doc(cfg_hide( #![doc(cfg_hide(
not(test), not(test),
not(any(test, bootstrap)), not(any(test, bootstrap)),
any(not(feature = "miri-test-libstd"), test, doctest),
no_global_oom_handling, no_global_oom_handling,
not(no_global_oom_handling), not(no_global_oom_handling),
not(no_rc), not(no_rc),

View File

@ -57,10 +57,7 @@
// //
// This cfg won't affect doc tests. // This cfg won't affect doc tests.
#![cfg(not(test))] #![cfg(not(test))]
// To run core tests without x.py without ending up with two copies of core, Miri needs to be //
// able to "empty" this crate. See <https://github.com/rust-lang/miri-test-libstd/issues/4>.
// rustc itself never sets the feature, so this line has no effect there.
#![cfg(any(not(feature = "miri-test-libstd"), test, doctest))]
#![stable(feature = "core", since = "1.6.0")] #![stable(feature = "core", since = "1.6.0")]
#![doc( #![doc(
html_playground_url = "https://play.rust-lang.org/", html_playground_url = "https://play.rust-lang.org/",
@ -71,7 +68,6 @@
#![doc(rust_logo)] #![doc(rust_logo)]
#![doc(cfg_hide( #![doc(cfg_hide(
not(test), not(test),
any(not(feature = "miri-test-libstd"), test, doctest),
no_fp_fmt_parse, no_fp_fmt_parse,
target_pointer_width = "16", target_pointer_width = "16",
target_pointer_width = "32", target_pointer_width = "32",

View File

@ -212,13 +212,7 @@
//! [rust-discord]: https://discord.gg/rust-lang //! [rust-discord]: https://discord.gg/rust-lang
//! [array]: prim@array //! [array]: prim@array
//! [slice]: prim@slice //! [slice]: prim@slice
// To run std tests without x.py without ending up with two copies of std, Miri needs to be
// able to "empty" this crate. See <https://github.com/rust-lang/miri-test-libstd/issues/4>.
// rustc itself never sets the feature, so this line has no effect there.
#![cfg(any(not(feature = "miri-test-libstd"), test, doctest))]
// miri-test-libstd also prefers to make std use the sysroot versions of the dependencies.
#![cfg_attr(feature = "miri-test-libstd", feature(rustc_private))]
//
#![cfg_attr(not(feature = "restricted-std"), stable(feature = "rust1", since = "1.0.0"))] #![cfg_attr(not(feature = "restricted-std"), stable(feature = "rust1", since = "1.0.0"))]
#![cfg_attr(feature = "restricted-std", unstable(feature = "restricted_std", issue = "none"))] #![cfg_attr(feature = "restricted-std", unstable(feature = "restricted_std", issue = "none"))]
#![cfg_attr(not(bootstrap), rustc_preserve_ub_checks)] #![cfg_attr(not(bootstrap), rustc_preserve_ub_checks)]

View File

@ -24,8 +24,8 @@ jobs:
include: include:
- os: ubuntu-latest - os: ubuntu-latest
host_target: x86_64-unknown-linux-gnu host_target: x86_64-unknown-linux-gnu
- os: macos-latest - os: macos-14
host_target: x86_64-apple-darwin host_target: aarch64-apple-darwin
- os: windows-latest - os: windows-latest
host_target: i686-pc-windows-msvc host_target: i686-pc-windows-msvc
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -49,15 +49,16 @@ jobs:
with: with:
path: | path: |
# Taken from <https://doc.rust-lang.org/nightly/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci>. # Taken from <https://doc.rust-lang.org/nightly/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci>.
~/.cargo/bin # Cache package/registry information
~/.cargo/registry/index ~/.cargo/registry/index
~/.cargo/registry/cache ~/.cargo/registry/cache
~/.cargo/git/db ~/.cargo/git/db
# contains package information of crates installed via `cargo install`. # Cache installed binaries
~/.cargo/bin
~/.cargo/.crates.toml ~/.cargo/.crates.toml
~/.cargo/.crates2.json ~/.cargo/.crates2.json
key: ${{ runner.os }}-cargo-reset20230315-${{ hashFiles('**/Cargo.lock') }} key: cargo-${{ runner.os }}-reset20240331-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-reset20230315 restore-keys: cargo-${{ runner.os }}-reset20240331
- name: Install rustup-toolchain-install-master - name: Install rustup-toolchain-install-master
if: ${{ steps.cache.outputs.cache-hit != 'true' }} if: ${{ steps.cache.outputs.cache-hit != 'true' }}
@ -98,15 +99,16 @@ jobs:
with: with:
path: | path: |
# Taken from <https://doc.rust-lang.org/nightly/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci>. # Taken from <https://doc.rust-lang.org/nightly/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci>.
~/.cargo/bin # Cache package/registry information
~/.cargo/registry/index ~/.cargo/registry/index
~/.cargo/registry/cache ~/.cargo/registry/cache
~/.cargo/git/db ~/.cargo/git/db
# contains package information of crates installed via `cargo install`. # Cache installed binaries
~/.cargo/bin
~/.cargo/.crates.toml ~/.cargo/.crates.toml
~/.cargo/.crates2.json ~/.cargo/.crates2.json
key: ${{ runner.os }}-cargo-reset20230315-${{ hashFiles('**/Cargo.lock') }} key: cargo-${{ runner.os }}-reset20240331-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-reset20230315 restore-keys: cargo-${{ runner.os }}-reset20240331
- name: Install rustup-toolchain-install-master - name: Install rustup-toolchain-install-master
if: ${{ steps.cache.outputs.cache-hit != 'true' }} if: ${{ steps.cache.outputs.cache-hit != 'true' }}
@ -130,6 +132,10 @@ jobs:
run: ./miri fmt --check run: ./miri fmt --check
- name: clippy - name: clippy
run: ./miri clippy -- -D warnings run: ./miri clippy -- -D warnings
- name: clippy (no features)
run: ./miri clippy --no-default-features -- -D warnings
- name: clippy (all features)
run: ./miri clippy --all-features -- -D warnings
- name: rustdoc - name: rustdoc
run: RUSTDOCFLAGS="-Dwarnings" ./miri cargo doc --document-private-items run: RUSTDOCFLAGS="-Dwarnings" ./miri cargo doc --document-private-items
@ -189,7 +195,7 @@ jobs:
with: with:
fetch-depth: 256 # get a bit more of the history fetch-depth: 256 # get a bit more of the history
- name: install josh-proxy - name: install josh-proxy
run: RUSTFLAGS="--cap-lints warn" cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r22.12.06 run: RUSTFLAGS="--cap-lints warn" cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r23.12.04
- name: setup bot git name and email - name: setup bot git name and email
run: | run: |
git config --global user.name 'The Miri Cronjob Bot' git config --global user.name 'The Miri Cronjob Bot'

View File

@ -241,18 +241,20 @@ We use the [`josh` proxy](https://github.com/josh-project/josh) to transmit chan
rustc and Miri repositories. You can install it as follows: rustc and Miri repositories. You can install it as follows:
```sh ```sh
cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r22.12.06 RUSTFLAGS="--cap-lints=warn" cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r23.12.04
``` ```
Josh will automatically be started and stopped by `./miri`. Josh will automatically be started and stopped by `./miri`.
### Importing changes from the rustc repo ### Importing changes from the rustc repo
*Note: this usually happens automatically, so these steps rarely have to be done by hand.*
We assume we start on an up-to-date master branch in the Miri repo. We assume we start on an up-to-date master branch in the Miri repo.
```sh ```sh
# Fetch and merge rustc side of the history. Takes ca 5 min the first time. # Fetch and merge rustc side of the history. Takes ca 5 min the first time.
# This will also update the 'rustc-version' file. # This will also update the `rustc-version` file.
./miri rustc-pull ./miri rustc-pull
# Update local toolchain and apply formatting. # Update local toolchain and apply formatting.
./miri toolchain && ./miri fmt ./miri toolchain && ./miri fmt
@ -266,12 +268,6 @@ needed.
### Exporting changes to the rustc repo ### Exporting changes to the rustc repo
Keep in mind that pushing is the most complicated job that josh has to do -- pulling just filters
the rustc history, but pushing needs to construct a new rustc history that would filter to the given
Miri history! To avoid problems, it is a good idea to always pull immediately before you push. If
you are getting strange errors, chances are you are running into [this josh
bug](https://github.com/josh-project/josh/issues/998). In that case, please get in touch on Zulip.
We will use the josh proxy to push to your fork of rustc. Run the following in the Miri repo, We will use the josh proxy to push to your fork of rustc. Run the following in the Miri repo,
assuming we are on an up-to-date master branch: assuming we are on an up-to-date master branch:
@ -280,9 +276,9 @@ assuming we are on an up-to-date master branch:
./miri rustc-push YOUR_NAME miri ./miri rustc-push YOUR_NAME miri
``` ```
This will create a new branch called 'miri' in your fork, and the output should This will create a new branch called `miri` in your fork, and the output should include a link that
include a link to create a rustc PR that will integrate those changes into the creates a rustc PR to integrate those changes into the main repository. If that PR has conflicts,
main repository. you need to pull rustc changes into Miri first, and then re-do the rustc push.
If this fails due to authentication problems, it can help to make josh push via ssh instead of If this fails due to authentication problems, it can help to make josh push via ssh instead of
https. Add the following to your `.gitconfig`: https. Add the following to your `.gitconfig`:

View File

@ -27,60 +27,59 @@ export RUSTFLAGS="-D warnings"
export CARGO_INCREMENTAL=0 export CARGO_INCREMENTAL=0
export CARGO_EXTRA_FLAGS="--locked" export CARGO_EXTRA_FLAGS="--locked"
# Determine configuration for installed build # Determine configuration for installed build (used by test-cargo-miri).
echo "Installing release version of Miri" echo "Installing release version of Miri"
./miri install time ./miri install
echo "Checking various feature flag configurations"
./miri check --no-default-features # make sure this can be built
./miri check # and this, too
# `--all-features` is used for the build below, so no extra check needed.
# Prepare debug build for direct `./miri` invocations. # Prepare debug build for direct `./miri` invocations.
# We enable all features to make sure the Stacked Borrows consistency check runs. # We enable all features to make sure the Stacked Borrows consistency check runs.
echo "Building debug version of Miri" echo "Building debug version of Miri"
export CARGO_EXTRA_FLAGS="$CARGO_EXTRA_FLAGS --all-features" export CARGO_EXTRA_FLAGS="$CARGO_EXTRA_FLAGS --all-features"
./miri build --all-targets # the build that all the `./miri test` below will use time ./miri build --all-targets # the build that all the `./miri test` below will use
endgroup endgroup
# Test # Run tests. Recognizes these variables:
# - MIRI_TEST_TARGET: the target to test. Empty for host target.
# - GC_STRESS: if non-empty, run the GC stress test for the main test suite.
# - MIR_OPT: if non-empty, re-run test `pass` tests with mir-opt-level=4
# - MANY_SEEDS: if set to N, run the "many-seeds" tests N times
# - TEST_BENCH: if non-empty, check that the benchmarks all build
# - CARGO_MIRI_ENV: if non-empty, set some env vars and config to potentially confuse cargo-miri
function run_tests { function run_tests {
if [ -n "${MIRI_TEST_TARGET:-}" ]; then if [ -n "${MIRI_TEST_TARGET-}" ]; then
begingroup "Testing foreign architecture $MIRI_TEST_TARGET" begingroup "Testing foreign architecture $MIRI_TEST_TARGET"
else else
begingroup "Testing host architecture" begingroup "Testing host architecture"
fi fi
## ui test suite ## ui test suite
# On the host, also stress-test the GC. if [ -n "${GC_STRESS-}" ]; then
if [ -z "${MIRI_TEST_TARGET:-}" ]; then time MIRIFLAGS="${MIRIFLAGS-} -Zmiri-provenance-gc=1" ./miri test
MIRIFLAGS="${MIRIFLAGS:-} -Zmiri-provenance-gc=1" ./miri test
else else
./miri test time ./miri test
fi fi
# Host-only tests ## advanced tests
if [ -z "${MIRI_TEST_TARGET:-}" ]; then if [ -n "${MIR_OPT-}" ]; then
# Running these on all targets is unlikely to catch more problems and would
# cost a lot of CI time.
# Tests with optimizations (`-O` is what cargo passes, but crank MIR optimizations up all the # Tests with optimizations (`-O` is what cargo passes, but crank MIR optimizations up all the
# way, too). # way, too).
# Optimizations change diagnostics (mostly backtraces), so we don't check # Optimizations change diagnostics (mostly backtraces), so we don't check
# them. Also error locations change so we don't run the failing tests. # them. Also error locations change so we don't run the failing tests.
# We explicitly enable debug-assertions here, they are disabled by -O but we have tests # We explicitly enable debug-assertions here, they are disabled by -O but we have tests
# which exist to check that we panic on debug assertion failures. # which exist to check that we panic on debug assertion failures.
MIRIFLAGS="${MIRIFLAGS:-} -O -Zmir-opt-level=4 -Cdebug-assertions=yes" MIRI_SKIP_UI_CHECKS=1 ./miri test -- tests/{pass,panic} time MIRIFLAGS="${MIRIFLAGS-} -O -Zmir-opt-level=4 -Cdebug-assertions=yes" MIRI_SKIP_UI_CHECKS=1 ./miri test -- tests/{pass,panic}
fi
if [ -n "${MANY_SEEDS-}" ]; then
# Also run some many-seeds tests. 64 seeds means this takes around a minute per test. # Also run some many-seeds tests. 64 seeds means this takes around a minute per test.
# (Need to invoke via explicit `bash -c` for Windows.) # (Need to invoke via explicit `bash -c` for Windows.)
for FILE in tests/many-seeds/*.rs; do time for FILE in tests/many-seeds/*.rs; do
MIRI_SEEDS=64 ./miri many-seeds "$BASH" -c "./miri run '$FILE'" MIRI_SEEDS=$MANY_SEEDS ./miri many-seeds "$BASH" -c "./miri run '$FILE'"
done done
fi
if [ -n "${TEST_BENCH-}" ]; then
# Check that the benchmarks build and run, but without actually benchmarking. # Check that the benchmarks build and run, but without actually benchmarking.
HYPERFINE="'$BASH' -c" ./miri bench time HYPERFINE="'$BASH' -c" ./miri bench
fi fi
## test-cargo-miri ## test-cargo-miri
@ -91,16 +90,18 @@ function run_tests {
PYTHON=python PYTHON=python
fi fi
# Some environment setup that attempts to confuse the heck out of cargo-miri. # Some environment setup that attempts to confuse the heck out of cargo-miri.
if [ "$HOST_TARGET" = x86_64-unknown-linux-gnu ]; then if [ -n "${CARGO_MIRI_ENV-}" ]; then
# These act up on Windows (`which miri` produces a filename that does not exist?!?), # 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. # RUSTC is the main thing to set (it changes the first argument our wrapper will see).
export RUSTC=$(which rustc) # Produces a warning unless we also set MIRI # Unless MIRI is also set, that produces a warning.
export RUSTC=$(which rustc)
export MIRI=$(rustc +miri --print sysroot)/bin/miri export MIRI=$(rustc +miri --print sysroot)/bin/miri
# We entirely ignore other wrappers.
mkdir -p .cargo
echo 'build.rustc-wrapper = "thisdoesnotexist"' > .cargo/config.toml
fi fi
mkdir -p .cargo
echo 'build.rustc-wrapper = "thisdoesnotexist"' > .cargo/config.toml
# Run the actual test # Run the actual test
${PYTHON} test-cargo-miri/run-test.py time ${PYTHON} test-cargo-miri/run-test.py
# Clean up # Clean up
unset RUSTC MIRI unset RUSTC MIRI
rm -rf .cargo rm -rf .cargo
@ -109,7 +110,7 @@ function run_tests {
} }
function run_tests_minimal { function run_tests_minimal {
if [ -n "${MIRI_TEST_TARGET:-}" ]; then if [ -n "${MIRI_TEST_TARGET-}" ]; then
begingroup "Testing MINIMAL foreign architecture $MIRI_TEST_TARGET: only testing $@" begingroup "Testing MINIMAL foreign architecture $MIRI_TEST_TARGET: only testing $@"
else else
echo "run_tests_minimal requires MIRI_TEST_TARGET to be set" echo "run_tests_minimal requires MIRI_TEST_TARGET to be set"
@ -126,38 +127,49 @@ function run_tests_minimal {
## Main Testing Logic ## ## Main Testing Logic ##
# Host target.
run_tests
# Extra targets.
# In particular, fully cover all tier 1 targets. # In particular, fully cover all tier 1 targets.
case $HOST_TARGET in case $HOST_TARGET in
x86_64-unknown-linux-gnu) x86_64-unknown-linux-gnu)
# Host
GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests
# Extra tier 1
MIRI_TEST_TARGET=i686-unknown-linux-gnu run_tests MIRI_TEST_TARGET=i686-unknown-linux-gnu run_tests
MIRI_TEST_TARGET=aarch64-unknown-linux-gnu run_tests MIRI_TEST_TARGET=aarch64-unknown-linux-gnu run_tests
MIRI_TEST_TARGET=aarch64-apple-darwin run_tests MIRI_TEST_TARGET=x86_64-apple-darwin run_tests
MIRI_TEST_TARGET=i686-pc-windows-gnu run_tests MIRI_TEST_TARGET=i686-pc-windows-gnu run_tests
MIRI_TEST_TARGET=x86_64-pc-windows-gnu run_tests MIRI_TEST_TARGET=x86_64-pc-windows-gnu run_tests
# Extra tier 2
MIRI_TEST_TARGET=aarch64-apple-darwin run_tests
MIRI_TEST_TARGET=arm-unknown-linux-gnueabi run_tests MIRI_TEST_TARGET=arm-unknown-linux-gnueabi run_tests
# Some targets are only partially supported. # Partially supported targets (tier 2)
MIRI_TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal hello integer vec panic/panic concurrency/simple pthread-threadname libc-getentropy libc-getrandom libc-misc libc-fs atomic env align num_cpus MIRI_TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal hello integer vec panic/panic concurrency/simple pthread-threadname libc-getentropy libc-getrandom libc-misc libc-fs atomic env align num_cpus
MIRI_TEST_TARGET=i686-unknown-freebsd run_tests_minimal hello integer vec panic/panic concurrency/simple pthread-threadname libc-getentropy libc-getrandom libc-misc libc-fs atomic env align num_cpus MIRI_TEST_TARGET=i686-unknown-freebsd run_tests_minimal hello integer vec panic/panic concurrency/simple pthread-threadname libc-getentropy libc-getrandom libc-misc libc-fs atomic env align num_cpus
MIRI_TEST_TARGET=aarch64-linux-android run_tests_minimal hello integer vec panic/panic MIRI_TEST_TARGET=aarch64-linux-android run_tests_minimal hello integer vec panic/panic
MIRI_TEST_TARGET=wasm32-wasi run_tests_minimal no_std integer strings wasm MIRI_TEST_TARGET=wasm32-wasi run_tests_minimal no_std integer strings wasm
MIRI_TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std integer strings wasm MIRI_TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std integer strings wasm
MIRI_TEST_TARGET=thumbv7em-none-eabihf run_tests_minimal no_std # no_std embedded architecture MIRI_TEST_TARGET=thumbv7em-none-eabihf run_tests_minimal no_std
MIRI_TEST_TARGET=tests/avr.json MIRI_NO_STD=1 run_tests_minimal no_std # JSON target file # Custom target JSON file
MIRI_TEST_TARGET=tests/avr.json MIRI_NO_STD=1 run_tests_minimal no_std
;; ;;
x86_64-apple-darwin) aarch64-apple-darwin)
# Host (tier 2)
GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests
# Extra tier 1
MIRI_TEST_TARGET=x86_64-pc-windows-msvc CARGO_MIRI_ENV=1 run_tests
# Extra tier 2
MIRI_TEST_TARGET=s390x-unknown-linux-gnu run_tests # big-endian architecture MIRI_TEST_TARGET=s390x-unknown-linux-gnu run_tests # big-endian architecture
MIRI_TEST_TARGET=x86_64-pc-windows-msvc run_tests
;; ;;
i686-pc-windows-msvc) i686-pc-windows-msvc)
# Host
# Only smoke-test `many-seeds`; 64 runs take 15min here!
GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=1 TEST_BENCH=1 run_tests
# Extra tier 1
# We really want to ensure a Linux target works on a Windows host,
# and a 64bit target works on a 32bit host.
MIRI_TEST_TARGET=x86_64-unknown-linux-gnu run_tests MIRI_TEST_TARGET=x86_64-unknown-linux-gnu run_tests
;; ;;
*) *)
echo "FATAL: unknown OS" echo "FATAL: unknown host target: $HOST_TARGET"
exit 1 exit 1
;; ;;
esac esac

View File

@ -297,7 +297,7 @@ impl Command {
}; };
// Prepare the branch. Pushing works much better if we use as base exactly // Prepare the branch. Pushing works much better if we use as base exactly
// the commit that we pulled from last time, so we use the `rust-version` // the commit that we pulled from last time, so we use the `rust-version`
// file as a good approximation of that. // file to find out which commit that would be.
println!("Preparing {github_user}/rust (base: {base})..."); println!("Preparing {github_user}/rust (base: {base})...");
if cmd!(sh, "git fetch https://github.com/{github_user}/rust {branch}") if cmd!(sh, "git fetch https://github.com/{github_user}/rust {branch}")
.ignore_stderr() .ignore_stderr()

View File

@ -1 +1 @@
5baf1e13f568b61e121953bf6a3d09faee7dd446 23d47dba319331d4418827cfbb8c1af283497d3c

View File

@ -48,7 +48,7 @@ impl AccessCause {
/// Complete data for an event: /// Complete data for an event:
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Event { pub struct Event {
/// Transformation of permissions that occured because of this event. /// Transformation of permissions that occurred because of this event.
pub transition: PermTransition, pub transition: PermTransition,
/// Kind of the access that triggered this event. /// Kind of the access that triggered this event.
pub access_cause: AccessCause, pub access_cause: AccessCause,
@ -58,7 +58,7 @@ pub struct Event {
/// `None` means that this is an implicit access to the entire allocation /// `None` means that this is an implicit access to the entire allocation
/// (used for the implicit read on protector release). /// (used for the implicit read on protector release).
pub access_range: Option<AllocRange>, pub access_range: Option<AllocRange>,
/// The transition recorded by this event only occured on a subrange of /// The transition recorded by this event only occurred on a subrange of
/// `access_range`: a single access on `access_range` triggers several events, /// `access_range`: a single access on `access_range` triggers several events,
/// each with their own mutually disjoint `transition_range`. No-op transitions /// each with their own mutually disjoint `transition_range`. No-op transitions
/// should not be recorded as events, so the union of all `transition_range` is not /// should not be recorded as events, so the union of all `transition_range` is not

View File

@ -473,7 +473,7 @@ impl Tree {
let rperms = { let rperms = {
let mut perms = UniValMap::default(); let mut perms = UniValMap::default();
// We manually set it to `Active` on all in-bounds positions. // We manually set it to `Active` on all in-bounds positions.
// We also ensure that it is initalized, so that no `Active` but // We also ensure that it is initialized, so that no `Active` but
// not yet initialized nodes exist. Essentially, we pretend there // not yet initialized nodes exist. Essentially, we pretend there
// was a write that initialized these to `Active`. // was a write that initialized these to `Active`.
perms.insert(root_idx, LocationState::new_init(Permission::new_active())); perms.insert(root_idx, LocationState::new_init(Permission::new_active()));

View File

@ -75,7 +75,7 @@ fn protected_enforces_noalias() {
} }
} }
/// We are going to exhaustively test the possibily of inserting /// We are going to exhaustively test the possibility of inserting
/// a spurious read in some code. /// a spurious read in some code.
/// ///
/// We choose some pointer `x` through which we want a spurious read to be inserted. /// We choose some pointer `x` through which we want a spurious read to be inserted.
@ -270,7 +270,7 @@ mod spurious_read {
match self { match self {
TestEvent::Access(acc) => write!(f, "{acc}"), TestEvent::Access(acc) => write!(f, "{acc}"),
// The fields of the `Ret` variants just serve to make them // The fields of the `Ret` variants just serve to make them
// impossible to instanciate via the `RetX = NoRet` type; we can // impossible to instantiate via the `RetX = NoRet` type; we can
// always ignore their value. // always ignore their value.
TestEvent::RetX(_) => write!(f, "ret x"), TestEvent::RetX(_) => write!(f, "ret x"),
TestEvent::RetY(_) => write!(f, "ret y"), TestEvent::RetY(_) => write!(f, "ret y"),
@ -395,7 +395,7 @@ mod spurious_read {
match evt { match evt {
TestEvent::Access(acc) => self.perform_test_access(acc), TestEvent::Access(acc) => self.perform_test_access(acc),
// The fields of the `Ret` variants just serve to make them // The fields of the `Ret` variants just serve to make them
// impossible to instanciate via the `RetX = NoRet` type; we can // impossible to instantiate via the `RetX = NoRet` type; we can
// always ignore their value. // always ignore their value.
TestEvent::RetX(_) => self.end_protector_x(), TestEvent::RetX(_) => self.end_protector_x(),
TestEvent::RetY(_) => self.end_protector_y(), TestEvent::RetY(_) => self.end_protector_y(),

View File

@ -31,7 +31,7 @@ use rustc_target::spec::abi::Abi;
use crate::{ use crate::{
concurrency::{data_race, weak_memory}, concurrency::{data_race, weak_memory},
shims::unix::FileHandler, shims::unix::FdTable,
*, *,
}; };
@ -463,9 +463,9 @@ pub struct MiriMachine<'mir, 'tcx> {
pub(crate) validate: bool, pub(crate) validate: bool,
/// The table of file descriptors. /// The table of file descriptors.
pub(crate) file_handler: shims::unix::FileHandler, pub(crate) fds: shims::unix::FdTable,
/// The table of directory descriptors. /// The table of directory descriptors.
pub(crate) dir_handler: shims::unix::DirHandler, pub(crate) dirs: shims::unix::DirTable,
/// This machine's monotone clock. /// This machine's monotone clock.
pub(crate) clock: Clock, pub(crate) clock: Clock,
@ -640,8 +640,8 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
tls: TlsData::default(), tls: TlsData::default(),
isolated_op: config.isolated_op, isolated_op: config.isolated_op,
validate: config.validate, validate: config.validate,
file_handler: FileHandler::new(config.mute_stdout_stderr), fds: FdTable::new(config.mute_stdout_stderr),
dir_handler: Default::default(), dirs: Default::default(),
layouts, layouts,
threads: ThreadManager::default(), threads: ThreadManager::default(),
static_roots: Vec::new(), static_roots: Vec::new(),
@ -774,11 +774,11 @@ impl VisitProvenance for MiriMachine<'_, '_> {
argv, argv,
cmd_line, cmd_line,
extern_statics, extern_statics,
dir_handler, dirs,
borrow_tracker, borrow_tracker,
data_race, data_race,
alloc_addresses, alloc_addresses,
file_handler, fds,
tcx: _, tcx: _,
isolated_op: _, isolated_op: _,
validate: _, validate: _,
@ -817,8 +817,8 @@ impl VisitProvenance for MiriMachine<'_, '_> {
threads.visit_provenance(visit); threads.visit_provenance(visit);
tls.visit_provenance(visit); tls.visit_provenance(visit);
env_vars.visit_provenance(visit); env_vars.visit_provenance(visit);
dir_handler.visit_provenance(visit); dirs.visit_provenance(visit);
file_handler.visit_provenance(visit); fds.visit_provenance(visit);
data_race.visit_provenance(visit); data_race.visit_provenance(visit);
borrow_tracker.visit_provenance(visit); borrow_tracker.visit_provenance(visit);
alloc_addresses.visit_provenance(visit); alloc_addresses.visit_provenance(visit);
@ -1066,7 +1066,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
let extern_decl_layout = ecx.tcx.layout_of(ty::ParamEnv::empty().and(def_ty)).unwrap(); let extern_decl_layout = ecx.tcx.layout_of(ty::ParamEnv::empty().and(def_ty)).unwrap();
if extern_decl_layout.size != shim_size || extern_decl_layout.align.abi != shim_align { if extern_decl_layout.size != shim_size || extern_decl_layout.align.abi != shim_align {
throw_unsup_format!( throw_unsup_format!(
"`extern` static `{name}` from crate `{krate}` has been declared \ "extern static `{link_name}` has been declared as `{krate}::{name}` \
with a size of {decl_size} bytes and alignment of {decl_align} bytes, \ with a size of {decl_size} bytes and alignment of {decl_align} bytes, \
but Miri emulates it via an extern static shim \ but Miri emulates it via an extern static shim \
with a size of {shim_size} bytes and alignment of {shim_align} bytes", with a size of {shim_size} bytes and alignment of {shim_align} bytes",
@ -1080,11 +1080,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
} }
Ok(ptr) Ok(ptr)
} else { } else {
throw_unsup_format!( throw_unsup_format!("extern static `{link_name}` is not supported by Miri",)
"`extern` static `{name}` from crate `{krate}` is not supported by Miri",
name = ecx.tcx.def_path_str(def_id),
krate = ecx.tcx.crate_name(def_id.krate),
)
} }
} }

View File

@ -0,0 +1,431 @@
//! General management of file descriptors, and support for
//! standard file descriptors (stdin/stdout/stderr).
use std::any::Any;
use std::collections::BTreeMap;
use std::io::{self, ErrorKind, IsTerminal, Read, SeekFrom, Write};
use rustc_middle::ty::TyCtxt;
use rustc_target::abi::Size;
use crate::shims::unix::*;
use crate::*;
/// Represents an open file descriptor.
pub trait FileDescriptor: std::fmt::Debug + Any {
fn name(&self) -> &'static str;
fn read<'tcx>(
&mut self,
_communicate_allowed: bool,
_bytes: &mut [u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
throw_unsup_format!("cannot read from {}", self.name());
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
_bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
throw_unsup_format!("cannot write to {}", self.name());
}
fn seek<'tcx>(
&mut self,
_communicate_allowed: bool,
_offset: SeekFrom,
) -> InterpResult<'tcx, io::Result<u64>> {
throw_unsup_format!("cannot seek on {}", self.name());
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
) -> InterpResult<'tcx, io::Result<i32>> {
throw_unsup_format!("cannot close {}", self.name());
}
/// Return a new file descriptor *that refers to the same underlying object*.
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>>;
fn is_tty(&self, _communicate_allowed: bool) -> bool {
// Most FDs are not tty's and the consequence of a wrong `false` are minor,
// so we use a default impl here.
false
}
}
impl dyn FileDescriptor {
#[inline(always)]
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
(self as &dyn Any).downcast_ref()
}
#[inline(always)]
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
(self as &mut dyn Any).downcast_mut()
}
}
impl FileDescriptor for io::Stdin {
fn name(&self) -> &'static str {
"stdin"
}
fn read<'tcx>(
&mut self,
communicate_allowed: bool,
bytes: &mut [u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
if !communicate_allowed {
// We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
helpers::isolation_abort_error("`read` from stdin")?;
}
Ok(Read::read(self, bytes))
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(io::stdin()))
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.is_terminal()
}
}
impl FileDescriptor for io::Stdout {
fn name(&self) -> &'static str {
"stdout"
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
// We allow writing to stderr even with isolation enabled.
let result = Write::write(&mut { self }, bytes);
// Stdout is buffered, flush to make sure it appears on the
// screen. This is the write() syscall of the interpreted
// program, we want it to correspond to a write() syscall on
// the host -- there is no good in adding extra buffering
// here.
io::stdout().flush().unwrap();
Ok(result)
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(io::stdout()))
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.is_terminal()
}
}
impl FileDescriptor for io::Stderr {
fn name(&self) -> &'static str {
"stderr"
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
// We allow writing to stderr even with isolation enabled.
// No need to flush, stderr is not buffered.
Ok(Write::write(&mut { self }, bytes))
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(io::stderr()))
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.is_terminal()
}
}
/// Like /dev/null
#[derive(Debug)]
pub struct NullOutput;
impl FileDescriptor for NullOutput {
fn name(&self) -> &'static str {
"stderr and stdout"
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
// We just don't write anything, but report to the user that we did.
Ok(Ok(bytes.len()))
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(NullOutput))
}
}
/// The file descriptor table
#[derive(Debug)]
pub struct FdTable {
pub fds: BTreeMap<i32, Box<dyn FileDescriptor>>,
}
impl VisitProvenance for FdTable {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
// All our FileDescriptor do not have any tags.
}
}
impl FdTable {
pub(crate) fn new(mute_stdout_stderr: bool) -> FdTable {
let mut fds: BTreeMap<_, Box<dyn FileDescriptor>> = BTreeMap::new();
fds.insert(0i32, Box::new(io::stdin()));
if mute_stdout_stderr {
fds.insert(1i32, Box::new(NullOutput));
fds.insert(2i32, Box::new(NullOutput));
} else {
fds.insert(1i32, Box::new(io::stdout()));
fds.insert(2i32, Box::new(io::stderr()));
}
FdTable { fds }
}
pub fn insert_fd(&mut self, file_handle: Box<dyn FileDescriptor>) -> i32 {
self.insert_fd_with_min_fd(file_handle, 0)
}
/// Insert a new FD that is at least `min_fd`.
pub fn insert_fd_with_min_fd(
&mut self,
file_handle: Box<dyn FileDescriptor>,
min_fd: i32,
) -> i32 {
// Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
// between used FDs, the find_map combinator will return it. If the first such unused FD
// is after all other used FDs, the find_map combinator will return None, and we will use
// the FD following the greatest FD thus far.
let candidate_new_fd =
self.fds.range(min_fd..).zip(min_fd..).find_map(|((fd, _fh), counter)| {
if *fd != counter {
// There was a gap in the fds stored, return the first unused one
// (note that this relies on BTreeMap iterating in key order)
Some(counter)
} else {
// This fd is used, keep going
None
}
});
let new_fd = candidate_new_fd.unwrap_or_else(|| {
// find_map ran out of BTreeMap entries before finding a free fd, use one plus the
// maximum fd in the map
self.fds.last_key_value().map(|(fd, _)| fd.checked_add(1).unwrap()).unwrap_or(min_fd)
});
self.fds.try_insert(new_fd, file_handle).unwrap();
new_fd
}
pub fn get(&self, fd: i32) -> Option<&dyn FileDescriptor> {
Some(&**self.fds.get(&fd)?)
}
pub fn get_mut(&mut self, fd: i32) -> Option<&mut dyn FileDescriptor> {
Some(&mut **self.fds.get_mut(&fd)?)
}
pub fn remove(&mut self, fd: i32) -> Option<Box<dyn FileDescriptor>> {
self.fds.remove(&fd)
}
pub fn is_fd(&self, fd: i32) -> bool {
self.fds.contains_key(&fd)
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn fcntl(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
if args.len() < 2 {
throw_ub_format!(
"incorrect number of arguments for fcntl: got {}, expected at least 2",
args.len()
);
}
let fd = this.read_scalar(&args[0])?.to_i32()?;
let cmd = this.read_scalar(&args[1])?.to_i32()?;
// We only support getting the flags for a descriptor.
if cmd == this.eval_libc_i32("F_GETFD") {
// Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
// `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
// always sets this flag when opening a file. However we still need to check that the
// file itself is open.
if this.machine.fds.is_fd(fd) {
Ok(this.eval_libc_i32("FD_CLOEXEC"))
} else {
this.fd_not_found()
}
} else if cmd == this.eval_libc_i32("F_DUPFD")
|| cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")
{
// Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
// because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
// differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
// thus they can share the same implementation here.
if args.len() < 3 {
throw_ub_format!(
"incorrect number of arguments for fcntl with cmd=`F_DUPFD`/`F_DUPFD_CLOEXEC`: got {}, expected at least 3",
args.len()
);
}
let start = this.read_scalar(&args[2])?.to_i32()?;
match this.machine.fds.get_mut(fd) {
Some(file_descriptor) => {
let dup_result = file_descriptor.dup();
match dup_result {
Ok(dup_fd) => Ok(this.machine.fds.insert_fd_with_min_fd(dup_fd, start)),
Err(e) => {
this.set_last_error_from_io_error(e.kind())?;
Ok(-1)
}
}
}
None => this.fd_not_found(),
}
} else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC") {
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fcntl`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
return Ok(-1);
}
this.ffullsync_fd(fd)
} else {
throw_unsup_format!("the {:#x} command is not supported for `fcntl`)", cmd);
}
}
fn close(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let fd = this.read_scalar(fd_op)?.to_i32()?;
Ok(Scalar::from_i32(if let Some(file_descriptor) = this.machine.fds.remove(fd) {
let result = file_descriptor.close(this.machine.communicate())?;
this.try_unwrap_io_result(result)?
} else {
this.fd_not_found()?
}))
}
/// Function used when a file descriptor does not exist. It returns `Ok(-1)`and sets
/// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
/// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
/// types (like `read`, that returns an `i64`).
fn fd_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
let this = self.eval_context_mut();
let ebadf = this.eval_libc("EBADF");
this.set_last_error(ebadf)?;
Ok((-1).into())
}
fn read(
&mut self,
fd: i32,
buf: Pointer<Option<Provenance>>,
count: u64,
) -> InterpResult<'tcx, i64> {
let this = self.eval_context_mut();
// Isolation check is done via `FileDescriptor` trait.
trace!("Reading from FD {}, size {}", fd, count);
// Check that the *entire* buffer is actually valid memory.
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccessTest)?;
// We cap the number of read bytes to the largest value that we are able to fit in both the
// host's and target's `isize`. This saves us from having to handle overflows later.
let count = count
.min(u64::try_from(this.target_isize_max()).unwrap())
.min(u64::try_from(isize::MAX).unwrap());
let communicate = this.machine.communicate();
if let Some(file_descriptor) = this.machine.fds.get_mut(fd) {
trace!("read: FD mapped to {:?}", file_descriptor);
// We want to read at most `count` bytes. We are sure that `count` is not negative
// because it was a target's `usize`. Also we are sure that its smaller than
// `usize::MAX` because it is bounded by the host's `isize`.
let mut bytes = vec![0; usize::try_from(count).unwrap()];
// `File::read` never returns a value larger than `count`,
// so this cannot fail.
let result = file_descriptor
.read(communicate, &mut bytes, *this.tcx)?
.map(|c| i64::try_from(c).unwrap());
match result {
Ok(read_bytes) => {
// If reading to `bytes` did not fail, we write those bytes to the buffer.
this.write_bytes_ptr(buf, bytes)?;
Ok(read_bytes)
}
Err(e) => {
this.set_last_error_from_io_error(e.kind())?;
Ok(-1)
}
}
} else {
trace!("read: FD not found");
this.fd_not_found()
}
}
fn write(
&mut self,
fd: i32,
buf: Pointer<Option<Provenance>>,
count: u64,
) -> InterpResult<'tcx, i64> {
let this = self.eval_context_mut();
// Isolation check is done via `FileDescriptor` trait.
// Check that the *entire* buffer is actually valid memory.
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccessTest)?;
// We cap the number of written bytes to the largest value that we are able to fit in both the
// host's and target's `isize`. This saves us from having to handle overflows later.
let count = count
.min(u64::try_from(this.target_isize_max()).unwrap())
.min(u64::try_from(isize::MAX).unwrap());
let communicate = this.machine.communicate();
if let Some(file_descriptor) = this.machine.fds.get(fd) {
let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?;
let result = file_descriptor
.write(communicate, bytes, *this.tcx)?
.map(|c| i64::try_from(c).unwrap());
this.try_unwrap_io_result(result)
} else {
this.fd_not_found()
}
}
}

View File

@ -6,12 +6,9 @@ use rustc_span::Symbol;
use rustc_target::abi::{Align, Size}; use rustc_target::abi::{Align, Size};
use rustc_target::spec::abi::Abi; use rustc_target::spec::abi::Abi;
use crate::shims::unix::*;
use crate::*; use crate::*;
use shims::foreign_items::EmulateForeignItemResult; use shims::foreign_items::EmulateForeignItemResult;
use shims::unix::fs::EvalContextExt as _;
use shims::unix::mem::EvalContextExt as _;
use shims::unix::sync::EvalContextExt as _;
use shims::unix::thread::EvalContextExt as _;
use shims::unix::freebsd::foreign_items as freebsd; use shims::unix::freebsd::foreign_items as freebsd;
use shims::unix::linux::foreign_items as linux; use shims::unix::linux::foreign_items as linux;
@ -51,7 +48,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern. // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern.
#[rustfmt::skip] #[rustfmt::skip]
match link_name.as_str() { match link_name.as_str() {
// Environment related shims // Environment variables
"getenv" => { "getenv" => {
let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.getenv(name)?; let result = this.getenv(name)?;
@ -79,25 +76,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.write_scalar(Scalar::from_i32(result), dest)?; this.write_scalar(Scalar::from_i32(result), dest)?;
} }
// File related shims // File descriptors
"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" => { "read" => {
let [fd, buf, count] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let [fd, buf, count] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let fd = this.read_scalar(fd)?.to_i32()?; let fd = this.read_scalar(fd)?.to_i32()?;
@ -116,6 +95,26 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// Now, `result` is the value we return back to the program. // Now, `result` is the value we return back to the program.
this.write_scalar(Scalar::from_target_isize(result, this), dest)?; this.write_scalar(Scalar::from_target_isize(result, this), 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)?;
}
// File and file system access
"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)?;
}
"unlink" => { "unlink" => {
let [path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let [path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.unlink(path)?; let result = this.unlink(path)?;
@ -219,7 +218,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.write_scalar(Scalar::from_i32(result), dest)?; this.write_scalar(Scalar::from_i32(result), dest)?;
} }
// Time related shims // Sockets
"socketpair" => {
let [domain, type_, protocol, sv] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.socketpair(domain, type_, protocol, sv)?;
this.write_scalar(result, dest)?;
}
// Time
"gettimeofday" => { "gettimeofday" => {
let [tv, tz] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let [tv, tz] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.gettimeofday(tv, tz)?; let result = this.gettimeofday(tv, tz)?;
@ -598,7 +606,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if bufsize > 256 { if bufsize > 256 {
let err = this.eval_libc("EIO"); let err = this.eval_libc("EIO");
this.set_last_error(err)?; this.set_last_error(err)?;
this.write_scalar(Scalar::from_i32(-1), dest)? this.write_scalar(Scalar::from_i32(-1), dest)?;
} else { } else {
this.gen_random(buf, bufsize)?; this.gen_random(buf, bufsize)?;
this.write_scalar(Scalar::from_i32(0), dest)?; this.write_scalar(Scalar::from_i32(0), dest)?;

View File

@ -1,10 +1,9 @@
use rustc_span::Symbol; use rustc_span::Symbol;
use rustc_target::spec::abi::Abi; use rustc_target::spec::abi::Abi;
use crate::shims::unix::*;
use crate::*; use crate::*;
use shims::foreign_items::EmulateForeignItemResult; use shims::foreign_items::EmulateForeignItemResult;
use shims::unix::fs::EvalContextExt as _;
use shims::unix::thread::EvalContextExt as _;
pub fn is_dyn_sym(_name: &str) -> bool { pub fn is_dyn_sym(_name: &str) -> bool {
false false

View File

@ -1,6 +1,6 @@
use std::any::Any; //! File and file system access
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fs::{ use std::fs::{
read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir, read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir,
}; };
@ -13,70 +13,16 @@ use rustc_middle::ty::TyCtxt;
use rustc_target::abi::Size; use rustc_target::abi::Size;
use crate::shims::os_str::bytes_to_os_str; use crate::shims::os_str::bytes_to_os_str;
use crate::shims::unix::*;
use crate::*; use crate::*;
use shims::time::system_time_to_duration; use shims::time::system_time_to_duration;
#[derive(Debug)] #[derive(Debug)]
pub struct FileHandle { struct FileHandle {
file: File, file: File,
writable: bool, writable: bool,
} }
pub trait FileDescriptor: std::fmt::Debug + Any {
fn name(&self) -> &'static str;
fn read<'tcx>(
&mut self,
_communicate_allowed: bool,
_bytes: &mut [u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
throw_unsup_format!("cannot read from {}", self.name());
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
_bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
throw_unsup_format!("cannot write to {}", self.name());
}
fn seek<'tcx>(
&mut self,
_communicate_allowed: bool,
_offset: SeekFrom,
) -> InterpResult<'tcx, io::Result<u64>> {
throw_unsup_format!("cannot seek on {}", self.name());
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
) -> InterpResult<'tcx, io::Result<i32>> {
throw_unsup_format!("cannot close {}", self.name());
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>>;
fn is_tty(&self, _communicate_allowed: bool) -> bool {
false
}
}
impl dyn FileDescriptor {
#[inline(always)]
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
(self as &dyn Any).downcast_ref()
}
#[inline(always)]
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
(self as &mut dyn Any).downcast_mut()
}
}
impl FileDescriptor for FileHandle { impl FileDescriptor for FileHandle {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"FILE" "FILE"
@ -147,172 +93,6 @@ impl FileDescriptor for FileHandle {
} }
} }
impl FileDescriptor for io::Stdin {
fn name(&self) -> &'static str {
"stdin"
}
fn read<'tcx>(
&mut self,
communicate_allowed: bool,
bytes: &mut [u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
if !communicate_allowed {
// We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
helpers::isolation_abort_error("`read` from stdin")?;
}
Ok(Read::read(self, bytes))
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(io::stdin()))
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.is_terminal()
}
}
impl FileDescriptor for io::Stdout {
fn name(&self) -> &'static str {
"stdout"
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
// We allow writing to stderr even with isolation enabled.
let result = Write::write(&mut { self }, bytes);
// Stdout is buffered, flush to make sure it appears on the
// screen. This is the write() syscall of the interpreted
// program, we want it to correspond to a write() syscall on
// the host -- there is no good in adding extra buffering
// here.
io::stdout().flush().unwrap();
Ok(result)
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(io::stdout()))
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.is_terminal()
}
}
impl FileDescriptor for io::Stderr {
fn name(&self) -> &'static str {
"stderr"
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
// We allow writing to stderr even with isolation enabled.
// No need to flush, stderr is not buffered.
Ok(Write::write(&mut { self }, bytes))
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(io::stderr()))
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.is_terminal()
}
}
#[derive(Debug)]
struct NullOutput;
impl FileDescriptor for NullOutput {
fn name(&self) -> &'static str {
"stderr and stdout"
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
// We just don't write anything, but report to the user that we did.
Ok(Ok(bytes.len()))
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(NullOutput))
}
}
#[derive(Debug)]
pub struct FileHandler {
pub handles: BTreeMap<i32, Box<dyn FileDescriptor>>,
}
impl VisitProvenance for FileHandler {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
// All our FileDescriptor do not have any tags.
}
}
impl FileHandler {
pub(crate) fn new(mute_stdout_stderr: bool) -> FileHandler {
let mut handles: BTreeMap<_, Box<dyn FileDescriptor>> = BTreeMap::new();
handles.insert(0i32, Box::new(io::stdin()));
if mute_stdout_stderr {
handles.insert(1i32, Box::new(NullOutput));
handles.insert(2i32, Box::new(NullOutput));
} else {
handles.insert(1i32, Box::new(io::stdout()));
handles.insert(2i32, Box::new(io::stderr()));
}
FileHandler { handles }
}
pub fn insert_fd(&mut self, file_handle: Box<dyn FileDescriptor>) -> i32 {
self.insert_fd_with_min_fd(file_handle, 0)
}
fn insert_fd_with_min_fd(&mut self, file_handle: Box<dyn FileDescriptor>, min_fd: i32) -> i32 {
// Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
// between used FDs, the find_map combinator will return it. If the first such unused FD
// is after all other used FDs, the find_map combinator will return None, and we will use
// the FD following the greatest FD thus far.
let candidate_new_fd =
self.handles.range(min_fd..).zip(min_fd..).find_map(|((fd, _fh), counter)| {
if *fd != counter {
// There was a gap in the fds stored, return the first unused one
// (note that this relies on BTreeMap iterating in key order)
Some(counter)
} else {
// This fd is used, keep going
None
}
});
let new_fd = candidate_new_fd.unwrap_or_else(|| {
// find_map ran out of BTreeMap entries before finding a free fd, use one plus the
// maximum fd in the map
self.handles
.last_key_value()
.map(|(fd, _)| fd.checked_add(1).unwrap())
.unwrap_or(min_fd)
});
self.handles.try_insert(new_fd, file_handle).unwrap();
new_fd
}
}
impl<'mir, 'tcx: 'mir> EvalContextExtPrivate<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} impl<'mir, 'tcx: 'mir> EvalContextExtPrivate<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn macos_stat_write_buf( fn macos_stat_write_buf(
@ -411,10 +191,11 @@ trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx
/// An open directory, tracked by DirHandler. /// An open directory, tracked by DirHandler.
#[derive(Debug)] #[derive(Debug)]
pub struct OpenDir { struct OpenDir {
/// The directory reader on the host. /// The directory reader on the host.
read_dir: ReadDir, read_dir: ReadDir,
/// The most recent entry returned by readdir() /// The most recent entry returned by readdir().
/// Will be freed by the next call.
entry: Pointer<Option<Provenance>>, entry: Pointer<Option<Provenance>>,
} }
@ -425,8 +206,11 @@ impl OpenDir {
} }
} }
/// The table of open directories.
/// Curiously, Unix/POSIX does not unify this into the "file descriptor" concept... everything
/// is a file, except a directory is not?
#[derive(Debug)] #[derive(Debug)]
pub struct DirHandler { pub struct DirTable {
/// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir, /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir,
/// and closedir. /// and closedir.
/// ///
@ -441,7 +225,7 @@ pub struct DirHandler {
next_id: u64, next_id: u64,
} }
impl DirHandler { impl DirTable {
#[allow(clippy::arithmetic_side_effects)] #[allow(clippy::arithmetic_side_effects)]
fn insert_new(&mut self, read_dir: ReadDir) -> u64 { fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
let id = self.next_id; let id = self.next_id;
@ -451,9 +235,9 @@ impl DirHandler {
} }
} }
impl Default for DirHandler { impl Default for DirTable {
fn default() -> DirHandler { fn default() -> DirTable {
DirHandler { DirTable {
streams: FxHashMap::default(), streams: FxHashMap::default(),
// Skip 0 as an ID, because it looks like a null pointer to libc // Skip 0 as an ID, because it looks like a null pointer to libc
next_id: 1, next_id: 1,
@ -461,9 +245,9 @@ impl Default for DirHandler {
} }
} }
impl VisitProvenance for DirHandler { impl VisitProvenance for DirTable {
fn visit_provenance(&self, visit: &mut VisitWith<'_>) { fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
let DirHandler { streams, next_id: _ } = self; let DirTable { streams, next_id: _ } = self;
for dir in streams.values() { for dir in streams.values() {
dir.entry.visit_provenance(visit); dir.entry.visit_provenance(visit);
@ -615,200 +399,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
} }
let fd = options.open(path).map(|file| { let fd = options.open(path).map(|file| {
let fh = &mut this.machine.file_handler; let fh = &mut this.machine.fds;
fh.insert_fd(Box::new(FileHandle { file, writable })) fh.insert_fd(Box::new(FileHandle { file, writable }))
}); });
this.try_unwrap_io_result(fd) this.try_unwrap_io_result(fd)
} }
fn fcntl(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
if args.len() < 2 {
throw_ub_format!(
"incorrect number of arguments for fcntl: got {}, expected at least 2",
args.len()
);
}
let fd = this.read_scalar(&args[0])?.to_i32()?;
let cmd = this.read_scalar(&args[1])?.to_i32()?;
// We only support getting the flags for a descriptor.
if cmd == this.eval_libc_i32("F_GETFD") {
// Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
// `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
// always sets this flag when opening a file. However we still need to check that the
// file itself is open.
if this.machine.file_handler.handles.contains_key(&fd) {
Ok(this.eval_libc_i32("FD_CLOEXEC"))
} else {
this.handle_not_found()
}
} else if cmd == this.eval_libc_i32("F_DUPFD")
|| cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")
{
// Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
// because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
// differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
// thus they can share the same implementation here.
if args.len() < 3 {
throw_ub_format!(
"incorrect number of arguments for fcntl with cmd=`F_DUPFD`/`F_DUPFD_CLOEXEC`: got {}, expected at least 3",
args.len()
);
}
let start = this.read_scalar(&args[2])?.to_i32()?;
let fh = &mut this.machine.file_handler;
match fh.handles.get_mut(&fd) {
Some(file_descriptor) => {
let dup_result = file_descriptor.dup();
match dup_result {
Ok(dup_fd) => Ok(fh.insert_fd_with_min_fd(dup_fd, start)),
Err(e) => {
this.set_last_error_from_io_error(e.kind())?;
Ok(-1)
}
}
}
None => this.handle_not_found(),
}
} else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC") {
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fcntl`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
return Ok(-1);
}
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
// FIXME: Support fullfsync for all FDs
let FileHandle { file, writable } =
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
err_unsup_format!(
"`F_FULLFSYNC` is only supported on file-backed file descriptors"
)
})?;
let io_result = maybe_sync_file(file, *writable, File::sync_all);
this.try_unwrap_io_result(io_result)
} else {
this.handle_not_found()
}
} else {
throw_unsup_format!("the {:#x} command is not supported for `fcntl`)", cmd);
}
}
fn close(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let fd = this.read_scalar(fd_op)?.to_i32()?;
Ok(Scalar::from_i32(
if let Some(file_descriptor) = this.machine.file_handler.handles.remove(&fd) {
let result = file_descriptor.close(this.machine.communicate())?;
this.try_unwrap_io_result(result)?
} else {
this.handle_not_found()?
},
))
}
/// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
/// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
/// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
/// types (like `read`, that returns an `i64`).
fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
let this = self.eval_context_mut();
let ebadf = this.eval_libc("EBADF");
this.set_last_error(ebadf)?;
Ok((-1).into())
}
fn read(
&mut self,
fd: i32,
buf: Pointer<Option<Provenance>>,
count: u64,
) -> InterpResult<'tcx, i64> {
let this = self.eval_context_mut();
// Isolation check is done via `FileDescriptor` trait.
trace!("Reading from FD {}, size {}", fd, count);
// Check that the *entire* buffer is actually valid memory.
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccessTest)?;
// We cap the number of read bytes to the largest value that we are able to fit in both the
// host's and target's `isize`. This saves us from having to handle overflows later.
let count = count
.min(u64::try_from(this.target_isize_max()).unwrap())
.min(u64::try_from(isize::MAX).unwrap());
let communicate = this.machine.communicate();
if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
trace!("read: FD mapped to {:?}", file_descriptor);
// We want to read at most `count` bytes. We are sure that `count` is not negative
// because it was a target's `usize`. Also we are sure that its smaller than
// `usize::MAX` because it is bounded by the host's `isize`.
let mut bytes = vec![0; usize::try_from(count).unwrap()];
// `File::read` never returns a value larger than `count`,
// so this cannot fail.
let result = file_descriptor
.read(communicate, &mut bytes, *this.tcx)?
.map(|c| i64::try_from(c).unwrap());
match result {
Ok(read_bytes) => {
// If reading to `bytes` did not fail, we write those bytes to the buffer.
this.write_bytes_ptr(buf, bytes)?;
Ok(read_bytes)
}
Err(e) => {
this.set_last_error_from_io_error(e.kind())?;
Ok(-1)
}
}
} else {
trace!("read: FD not found");
this.handle_not_found()
}
}
fn write(
&mut self,
fd: i32,
buf: Pointer<Option<Provenance>>,
count: u64,
) -> InterpResult<'tcx, i64> {
let this = self.eval_context_mut();
// Isolation check is done via `FileDescriptor` trait.
// Check that the *entire* buffer is actually valid memory.
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccessTest)?;
// We cap the number of written bytes to the largest value that we are able to fit in both the
// host's and target's `isize`. This saves us from having to handle overflows later.
let count = count
.min(u64::try_from(this.target_isize_max()).unwrap())
.min(u64::try_from(isize::MAX).unwrap());
let communicate = this.machine.communicate();
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?;
let result = file_descriptor
.write(communicate, bytes, *this.tcx)?
.map(|c| i64::try_from(c).unwrap());
this.try_unwrap_io_result(result)
} else {
this.handle_not_found()
}
}
fn lseek64( fn lseek64(
&mut self, &mut self,
fd: i32, fd: i32,
@ -832,16 +429,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}; };
let communicate = this.machine.communicate(); let communicate = this.machine.communicate();
Ok(Scalar::from_i64( Ok(Scalar::from_i64(if let Some(file_descriptor) = this.machine.fds.get_mut(fd) {
if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) { let result = file_descriptor
let result = file_descriptor .seek(communicate, seek_from)?
.seek(communicate, seek_from)? .map(|offset| i64::try_from(offset).unwrap());
.map(|offset| i64::try_from(offset).unwrap()); this.try_unwrap_io_result(result)?
this.try_unwrap_io_result(result)? } else {
} else { this.fd_not_found()?
this.handle_not_found()? }))
},
))
} }
fn unlink(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { fn unlink(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
@ -970,7 +565,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fstat`", reject_with)?; this.reject_in_isolation("`fstat`", reject_with)?;
// Set error code as "EBADF" (bad fd) // Set error code as "EBADF" (bad fd)
return Ok(Scalar::from_i32(this.handle_not_found()?)); return Ok(Scalar::from_i32(this.fd_not_found()?));
} }
let metadata = match FileMetadata::from_fd(this, fd)? { let metadata = match FileMetadata::from_fd(this, fd)? {
@ -1269,7 +864,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
match result { match result {
Ok(dir_iter) => { Ok(dir_iter) => {
let id = this.machine.dir_handler.insert_new(dir_iter); let id = this.machine.dirs.insert_new(dir_iter);
// The libc API for opendir says that this method returns a pointer to an opaque // The libc API for opendir says that this method returns a pointer to an opaque
// structure, but we are returning an ID number. Thus, pass it as a scalar of // structure, but we are returning an ID number. Thus, pass it as a scalar of
@ -1301,7 +896,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
return Ok(Scalar::null_ptr(this)); return Ok(Scalar::null_ptr(this));
} }
let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| { let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir") err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
})?; })?;
@ -1366,7 +961,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
} }
}; };
let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).unwrap(); let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
let old_entry = std::mem::replace(&mut open_dir.entry, entry); let old_entry = std::mem::replace(&mut open_dir.entry, entry);
this.free(old_entry, MiriMemoryKind::Runtime)?; this.free(old_entry, MiriMemoryKind::Runtime)?;
@ -1391,10 +986,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`readdir_r`", reject_with)?; this.reject_in_isolation("`readdir_r`", reject_with)?;
// Set error code as "EBADF" (bad fd) // Set error code as "EBADF" (bad fd)
return Ok(Scalar::from_i32(this.handle_not_found()?)); return Ok(Scalar::from_i32(this.fd_not_found()?));
} }
let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| { let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir") err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
})?; })?;
Ok(Scalar::from_i32(match open_dir.read_dir.next() { Ok(Scalar::from_i32(match open_dir.read_dir.next() {
@ -1507,15 +1102,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`closedir`", reject_with)?; this.reject_in_isolation("`closedir`", reject_with)?;
// Set error code as "EBADF" (bad fd) // Set error code as "EBADF" (bad fd)
return this.handle_not_found(); return this.fd_not_found();
} }
if let Some(open_dir) = this.machine.dir_handler.streams.remove(&dirp) { if let Some(open_dir) = this.machine.dirs.streams.remove(&dirp) {
this.free(open_dir.entry, MiriMemoryKind::Runtime)?; this.free(open_dir.entry, MiriMemoryKind::Runtime)?;
drop(open_dir); drop(open_dir);
Ok(0) Ok(0)
} else { } else {
this.handle_not_found() this.fd_not_found()
} }
} }
@ -1526,37 +1121,35 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`ftruncate64`", reject_with)?; this.reject_in_isolation("`ftruncate64`", reject_with)?;
// Set error code as "EBADF" (bad fd) // Set error code as "EBADF" (bad fd)
return Ok(Scalar::from_i32(this.handle_not_found()?)); return Ok(Scalar::from_i32(this.fd_not_found()?));
} }
Ok(Scalar::from_i32( Ok(Scalar::from_i32(if let Some(file_descriptor) = this.machine.fds.get_mut(fd) {
if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) { // FIXME: Support ftruncate64 for all FDs
// FIXME: Support ftruncate64 for all FDs let FileHandle { file, writable } =
let FileHandle { file, writable } = file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| { err_unsup_format!(
err_unsup_format!( "`ftruncate64` is only supported on file-backed file descriptors"
"`ftruncate64` is only supported on file-backed file descriptors" )
) })?;
})?; if *writable {
if *writable { if let Ok(length) = length.try_into() {
if let Ok(length) = length.try_into() { let result = file.set_len(length);
let result = file.set_len(length); this.try_unwrap_io_result(result.map(|_| 0i32))?
this.try_unwrap_io_result(result.map(|_| 0i32))?
} else {
let einval = this.eval_libc("EINVAL");
this.set_last_error(einval)?;
-1
}
} else { } else {
// The file is not writable
let einval = this.eval_libc("EINVAL"); let einval = this.eval_libc("EINVAL");
this.set_last_error(einval)?; this.set_last_error(einval)?;
-1 -1
} }
} else { } else {
this.handle_not_found()? // The file is not writable
}, let einval = this.eval_libc("EINVAL");
)) this.set_last_error(einval)?;
-1
}
} else {
this.fd_not_found()?
}))
} }
fn fsync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { fn fsync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
@ -1573,20 +1166,24 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fsync`", reject_with)?; this.reject_in_isolation("`fsync`", reject_with)?;
// Set error code as "EBADF" (bad fd) // Set error code as "EBADF" (bad fd)
return this.handle_not_found(); return this.fd_not_found();
} }
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) { return self.ffullsync_fd(fd);
// FIXME: Support fsync for all FDs }
let FileHandle { file, writable } =
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| { fn ffullsync_fd(&mut self, fd: i32) -> InterpResult<'tcx, i32> {
err_unsup_format!("`fsync` is only supported on file-backed file descriptors") let this = self.eval_context_mut();
})?; let Some(file_descriptor) = this.machine.fds.get(fd) else {
let io_result = maybe_sync_file(file, *writable, File::sync_all); return Ok(this.fd_not_found()?);
this.try_unwrap_io_result(io_result) };
} else { // Only regular files support synchronization.
this.handle_not_found() let FileHandle { file, writable } =
} file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
})?;
let io_result = maybe_sync_file(file, *writable, File::sync_all);
this.try_unwrap_io_result(io_result)
} }
fn fdatasync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { fn fdatasync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
@ -1598,22 +1195,19 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fdatasync`", reject_with)?; this.reject_in_isolation("`fdatasync`", reject_with)?;
// Set error code as "EBADF" (bad fd) // Set error code as "EBADF" (bad fd)
return this.handle_not_found(); return this.fd_not_found();
} }
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) { let Some(file_descriptor) = this.machine.fds.get(fd) else {
// FIXME: Support fdatasync for all FDs return Ok(this.fd_not_found()?);
let FileHandle { file, writable } = };
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| { // Only regular files support synchronization.
err_unsup_format!( let FileHandle { file, writable } =
"`fdatasync` is only supported on file-backed file descriptors" file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
) err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
})?; })?;
let io_result = maybe_sync_file(file, *writable, File::sync_data); let io_result = maybe_sync_file(file, *writable, File::sync_data);
this.try_unwrap_io_result(io_result) this.try_unwrap_io_result(io_result)
} else {
this.handle_not_found()
}
} }
fn sync_file_range( fn sync_file_range(
@ -1648,22 +1242,21 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`sync_file_range`", reject_with)?; this.reject_in_isolation("`sync_file_range`", reject_with)?;
// Set error code as "EBADF" (bad fd) // Set error code as "EBADF" (bad fd)
return Ok(Scalar::from_i32(this.handle_not_found()?)); return Ok(Scalar::from_i32(this.fd_not_found()?));
} }
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) { let Some(file_descriptor) = this.machine.fds.get(fd) else {
// FIXME: Support sync_data_range for all FDs return Ok(Scalar::from_i32(this.fd_not_found()?));
let FileHandle { file, writable } = };
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| { // Only regular files support synchronization.
err_unsup_format!( let FileHandle { file, writable } =
"`sync_data_range` is only supported on file-backed file descriptors" file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
) err_unsup_format!(
})?; "`sync_data_range` is only supported on file-backed file descriptors"
let io_result = maybe_sync_file(file, *writable, File::sync_data); )
Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?)) })?;
} else { let io_result = maybe_sync_file(file, *writable, File::sync_data);
Ok(Scalar::from_i32(this.handle_not_found()?)) Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
}
} }
fn readlink( fn readlink(
@ -1720,7 +1313,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// "returns 1 if fd is an open file descriptor referring to a terminal; // "returns 1 if fd is an open file descriptor referring to a terminal;
// otherwise 0 is returned, and errno is set to indicate the error" // otherwise 0 is returned, and errno is set to indicate the error"
let fd = this.read_scalar(miri_fd)?.to_i32()?; let fd = this.read_scalar(miri_fd)?.to_i32()?;
let error = if let Some(fd) = this.machine.file_handler.handles.get(&fd) { let error = if let Some(fd) = this.machine.fds.get(fd) {
if fd.is_tty(this.machine.communicate()) { if fd.is_tty(this.machine.communicate()) {
return Ok(Scalar::from_i32(1)); return Ok(Scalar::from_i32(1));
} else { } else {
@ -1897,7 +1490,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
match file { match file {
Ok(f) => { Ok(f) => {
let fh = &mut this.machine.file_handler; let fh = &mut this.machine.fds;
let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true })); let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true }));
return Ok(fd); return Ok(fd);
} }
@ -1963,7 +1556,7 @@ impl FileMetadata {
ecx: &mut MiriInterpCx<'_, 'tcx>, ecx: &mut MiriInterpCx<'_, 'tcx>,
fd: i32, fd: i32,
) -> InterpResult<'tcx, Option<FileMetadata>> { ) -> InterpResult<'tcx, Option<FileMetadata>> {
let option = ecx.machine.file_handler.handles.get(&fd); let option = ecx.machine.fds.get(fd);
let file = match option { let file = match option {
Some(file_descriptor) => Some(file_descriptor) =>
&file_descriptor &file_descriptor
@ -1974,7 +1567,7 @@ impl FileMetadata {
) )
})? })?
.file, .file,
None => return ecx.handle_not_found().map(|_: i32| None), None => return ecx.fd_not_found().map(|_: i32| None),
}; };
let metadata = file.metadata(); let metadata = file.metadata();

View File

@ -1,17 +1,52 @@
use std::cell::Cell; use std::io;
use rustc_middle::ty::ScalarInt; use rustc_data_structures::fx::FxHashMap;
use crate::shims::unix::*;
use crate::*; use crate::*;
use epoll::{Epoll, EpollEvent};
use event::Event;
use socketpair::SocketPair;
use shims::unix::fs::EvalContextExt as _; /// An `Epoll` file descriptor connects file handles and epoll events
#[derive(Clone, Debug, Default)]
struct Epoll {
/// The file descriptors we are watching, and what we are watching for.
file_descriptors: FxHashMap<i32, EpollEvent>,
}
pub mod epoll; /// Epoll Events associate events with data.
pub mod event; /// These fields are currently unused by miri.
pub mod socketpair; /// This matches the `epoll_event` struct defined
/// by the epoll_ctl man page. For more information
/// see the man page:
///
/// <https://man7.org/linux/man-pages/man2/epoll_ctl.2.html>
#[derive(Clone, Debug)]
struct EpollEvent {
#[allow(dead_code)]
events: u32,
/// `Scalar<Provenance>` is used to represent the
/// `epoll_data` type union.
#[allow(dead_code)]
data: Scalar<Provenance>,
}
impl FileDescriptor for Epoll {
fn name(&self) -> &'static str {
"epoll"
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
// FIXME: this is probably wrong -- check if the `dup`ed descriptor truly uses an
// independent event set.
Ok(Box::new(self.clone()))
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
) -> InterpResult<'tcx, io::Result<i32>> {
Ok(Ok(0))
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
@ -35,7 +70,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
throw_unsup_format!("epoll_create1 flags {flags} are not implemented"); throw_unsup_format!("epoll_create1 flags {flags} are not implemented");
} }
let fd = this.machine.file_handler.insert_fd(Box::new(Epoll::default())); let fd = this.machine.fds.insert_fd(Box::new(Epoll::default()));
Ok(Scalar::from_i32(fd)) Ok(Scalar::from_i32(fd))
} }
@ -79,7 +114,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let data = this.read_scalar(&data)?; let data = this.read_scalar(&data)?;
let event = EpollEvent { events, data }; let event = EpollEvent { events, data };
if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) { if let Some(epfd) = this.machine.fds.get_mut(epfd) {
let epfd = epfd let epfd = epfd
.downcast_mut::<Epoll>() .downcast_mut::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?; .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
@ -87,10 +122,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
epfd.file_descriptors.insert(fd, event); epfd.file_descriptors.insert(fd, event);
Ok(Scalar::from_i32(0)) Ok(Scalar::from_i32(0))
} else { } else {
Ok(Scalar::from_i32(this.handle_not_found()?)) Ok(Scalar::from_i32(this.fd_not_found()?))
} }
} else if op == epoll_ctl_del { } else if op == epoll_ctl_del {
if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) { if let Some(epfd) = this.machine.fds.get_mut(epfd) {
let epfd = epfd let epfd = epfd
.downcast_mut::<Epoll>() .downcast_mut::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?; .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
@ -98,7 +133,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
epfd.file_descriptors.remove(&fd); epfd.file_descriptors.remove(&fd);
Ok(Scalar::from_i32(0)) Ok(Scalar::from_i32(0))
} else { } else {
Ok(Scalar::from_i32(this.handle_not_found()?)) Ok(Scalar::from_i32(this.fd_not_found()?))
} }
} else { } else {
let einval = this.eval_libc("EINVAL"); let einval = this.eval_libc("EINVAL");
@ -150,7 +185,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let _maxevents = this.read_scalar(maxevents)?.to_i32()?; let _maxevents = this.read_scalar(maxevents)?.to_i32()?;
let _timeout = this.read_scalar(timeout)?.to_i32()?; let _timeout = this.read_scalar(timeout)?.to_i32()?;
if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) { if let Some(epfd) = this.machine.fds.get_mut(epfd) {
let _epfd = epfd let _epfd = epfd
.downcast_mut::<Epoll>() .downcast_mut::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?; .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?;
@ -158,96 +193,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// FIXME return number of events ready when scheme for marking events ready exists // FIXME return number of events ready when scheme for marking events ready exists
throw_unsup_format!("returning ready events from epoll_wait is not yet implemented"); throw_unsup_format!("returning ready events from epoll_wait is not yet implemented");
} else { } else {
Ok(Scalar::from_i32(this.handle_not_found()?)) Ok(Scalar::from_i32(this.fd_not_found()?))
} }
} }
/// This function creates an `Event` that is used as an event wait/notify mechanism by
/// user-space applications, and by the kernel to notify user-space applications of events.
/// The `Event` contains an `u64` counter maintained by the kernel. The counter is initialized
/// with the value specified in the `initval` argument.
///
/// A new file descriptor referring to the `Event` is returned. The `read`, `write`, `poll`,
/// `select`, and `close` operations can be performed on the file descriptor. For more
/// information on these operations, see the man page linked below.
///
/// The `flags` are not currently implemented for eventfd.
/// The `flags` may be bitwise ORed to change the behavior of `eventfd`:
/// `EFD_CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor.
/// `EFD_NONBLOCK` - Set the `O_NONBLOCK` file status flag on the new open file description.
/// `EFD_SEMAPHORE` - miri does not support semaphore-like semantics.
///
/// <https://linux.die.net/man/2/eventfd>
#[expect(clippy::needless_if)]
fn eventfd(
&mut self,
val: &OpTy<'tcx, Provenance>,
flags: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let val = this.read_scalar(val)?.to_u32()?;
let flags = this.read_scalar(flags)?.to_i32()?;
let efd_cloexec = this.eval_libc_i32("EFD_CLOEXEC");
let efd_nonblock = this.eval_libc_i32("EFD_NONBLOCK");
let efd_semaphore = this.eval_libc_i32("EFD_SEMAPHORE");
if flags & (efd_cloexec | efd_nonblock | efd_semaphore) == 0 {
throw_unsup_format!("{flags} is unsupported");
}
// FIXME handle the cloexec and nonblock flags
if flags & efd_cloexec == efd_cloexec {}
if flags & efd_nonblock == efd_nonblock {}
if flags & efd_semaphore == efd_semaphore {
throw_unsup_format!("EFD_SEMAPHORE is unsupported");
}
let fh = &mut this.machine.file_handler;
let fd = fh.insert_fd(Box::new(Event { val: Cell::new(val.into()) }));
Ok(Scalar::from_i32(fd))
}
/// Currently this function creates new `SocketPair`s without specifying the domain, type, or
/// protocol of the new socket and these are stored in the socket values `sv` argument.
///
/// This function creates an unnamed pair of connected sockets in the specified domain, of the
/// specified type, and using the optionally specified protocol.
///
/// The `domain` argument specified a communication domain; this selects the protocol family
/// used for communication. The socket `type` specifies the communication semantics.
/// The `protocol` specifies a particular protocol to use with the socket. Normally there's
/// only a single protocol supported for a particular socket type within a given protocol
/// family, in which case `protocol` can be specified as 0. It is possible that many protocols
/// exist and in that case, a particular protocol must be specified.
///
/// For more information on the arguments see the socket manpage:
/// <https://linux.die.net/man/2/socket>
///
/// <https://linux.die.net/man/2/socketpair>
fn socketpair(
&mut self,
domain: &OpTy<'tcx, Provenance>,
type_: &OpTy<'tcx, Provenance>,
protocol: &OpTy<'tcx, Provenance>,
sv: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let _domain = this.read_scalar(domain)?.to_i32()?;
let _type_ = this.read_scalar(type_)?.to_i32()?;
let _protocol = this.read_scalar(protocol)?.to_i32()?;
let sv = this.deref_pointer(sv)?;
let fh = &mut this.machine.file_handler;
let sv0 = fh.insert_fd(Box::new(SocketPair));
let sv0 = ScalarInt::try_from_int(sv0, sv.layout.size).unwrap();
let sv1 = fh.insert_fd(Box::new(SocketPair));
let sv1 = ScalarInt::try_from_int(sv1, sv.layout.size).unwrap();
this.write_scalar(sv0, &sv)?;
this.write_scalar(sv1, &sv.offset(sv.layout.size, sv.layout, this)?)?;
Ok(Scalar::from_i32(0))
}
} }

View File

@ -0,0 +1,125 @@
//! Linux `eventfd` implementation.
//! Currently just a stub.
use std::cell::Cell;
use std::io;
use rustc_middle::ty::TyCtxt;
use rustc_target::abi::Endian;
use crate::shims::unix::*;
use crate::*;
/// A kind of file descriptor created by `eventfd`.
/// The `Event` type isn't currently written to by `eventfd`.
/// The interface is meant to keep track of objects associated
/// with a file descriptor. For more information see the man
/// page below:
///
/// <https://man.netbsd.org/eventfd.2>
#[derive(Debug)]
struct Event {
/// The object contains an unsigned 64-bit integer (uint64_t) counter that is maintained by the
/// kernel. This counter is initialized with the value specified in the argument initval.
val: Cell<u64>,
}
impl FileDescriptor for Event {
fn name(&self) -> &'static str {
"event"
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
// FIXME: this is wrong, the new and old FD should refer to the same event object!
Ok(Box::new(Event { val: self.val.clone() }))
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
) -> InterpResult<'tcx, io::Result<i32>> {
Ok(Ok(0))
}
/// A write call adds the 8-byte integer value supplied in
/// its buffer (in native endianness) to the counter. The maximum value that may be
/// stored in the counter is the largest unsigned 64-bit value
/// minus 1 (i.e., 0xfffffffffffffffe). If the addition would
/// cause the counter's value to exceed the maximum, then the
/// write either blocks until a read is performed on the
/// file descriptor, or fails with the error EAGAIN if the
/// file descriptor has been made nonblocking.
/// A write fails with the error EINVAL if the size of the
/// supplied buffer is less than 8 bytes, or if an attempt is
/// made to write the value 0xffffffffffffffff.
fn write<'tcx>(
&self,
_communicate_allowed: bool,
bytes: &[u8],
tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
let v1 = self.val.get();
let bytes: [u8; 8] = bytes.try_into().unwrap(); // FIXME fail gracefully when this has the wrong size
// Convert from target endianness to host endianness.
let num = match tcx.sess.target.endian {
Endian::Little => u64::from_le_bytes(bytes),
Endian::Big => u64::from_be_bytes(bytes),
};
// FIXME handle blocking when addition results in exceeding the max u64 value
// or fail with EAGAIN if the file descriptor is nonblocking.
let v2 = v1.checked_add(num).unwrap();
self.val.set(v2);
assert_eq!(8, bytes.len());
Ok(Ok(8))
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// This function creates an `Event` that is used as an event wait/notify mechanism by
/// user-space applications, and by the kernel to notify user-space applications of events.
/// The `Event` contains an `u64` counter maintained by the kernel. The counter is initialized
/// with the value specified in the `initval` argument.
///
/// A new file descriptor referring to the `Event` is returned. The `read`, `write`, `poll`,
/// `select`, and `close` operations can be performed on the file descriptor. For more
/// information on these operations, see the man page linked below.
///
/// The `flags` are not currently implemented for eventfd.
/// The `flags` may be bitwise ORed to change the behavior of `eventfd`:
/// `EFD_CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor.
/// `EFD_NONBLOCK` - Set the `O_NONBLOCK` file status flag on the new open file description.
/// `EFD_SEMAPHORE` - miri does not support semaphore-like semantics.
///
/// <https://linux.die.net/man/2/eventfd>
fn eventfd(
&mut self,
val: &OpTy<'tcx, Provenance>,
flags: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let val = this.read_scalar(val)?.to_u32()?;
let flags = this.read_scalar(flags)?.to_i32()?;
let efd_cloexec = this.eval_libc_i32("EFD_CLOEXEC");
let efd_nonblock = this.eval_libc_i32("EFD_NONBLOCK");
let efd_semaphore = this.eval_libc_i32("EFD_SEMAPHORE");
if flags & (efd_cloexec | efd_nonblock | efd_semaphore) != flags {
throw_unsup_format!("eventfd: flag {flags:#x} is unsupported");
}
if flags & efd_cloexec == efd_cloexec {
// cloexec does nothing as we don't support `exec`
}
if flags & efd_nonblock == efd_nonblock {
// FIXME remember the nonblock flag
}
if flags & efd_semaphore == efd_semaphore {
throw_unsup_format!("eventfd: EFD_SEMAPHORE is unsupported");
}
let fd = this.machine.fds.insert_fd(Box::new(Event { val: Cell::new(val.into()) }));
Ok(Scalar::from_i32(fd))
}
}

View File

@ -1,47 +0,0 @@
use crate::*;
use crate::shims::unix::fs::FileDescriptor;
use rustc_data_structures::fx::FxHashMap;
use std::io;
/// An `Epoll` file descriptor connects file handles and epoll events
#[derive(Clone, Debug, Default)]
pub struct Epoll {
/// The file descriptors we are watching, and what we are watching for.
pub file_descriptors: FxHashMap<i32, EpollEvent>,
}
/// Epoll Events associate events with data.
/// These fields are currently unused by miri.
/// This matches the `epoll_event` struct defined
/// by the epoll_ctl man page. For more information
/// see the man page:
///
/// <https://man7.org/linux/man-pages/man2/epoll_ctl.2.html>
#[derive(Clone, Debug)]
pub struct EpollEvent {
#[allow(dead_code)]
pub events: u32,
/// `Scalar<Provenance>` is used to represent the
/// `epoll_data` type union.
#[allow(dead_code)]
pub data: Scalar<Provenance>,
}
impl FileDescriptor for Epoll {
fn name(&self) -> &'static str {
"epoll"
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(self.clone()))
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
) -> InterpResult<'tcx, io::Result<i32>> {
Ok(Ok(0))
}
}

View File

@ -1,72 +0,0 @@
use crate::shims::unix::fs::FileDescriptor;
use rustc_const_eval::interpret::InterpResult;
use rustc_middle::ty::TyCtxt;
use rustc_target::abi::Endian;
use std::cell::Cell;
use std::io;
/// A kind of file descriptor created by `eventfd`.
/// The `Event` type isn't currently written to by `eventfd`.
/// The interface is meant to keep track of objects associated
/// with a file descriptor. For more information see the man
/// page below:
///
/// <https://man.netbsd.org/eventfd.2>
#[derive(Debug)]
pub struct Event {
/// The object contains an unsigned 64-bit integer (uint64_t) counter that is maintained by the
/// kernel. This counter is initialized with the value specified in the argument initval.
pub val: Cell<u64>,
}
impl FileDescriptor for Event {
fn name(&self) -> &'static str {
"event"
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(Event { val: self.val.clone() }))
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
) -> InterpResult<'tcx, io::Result<i32>> {
Ok(Ok(0))
}
/// A write call adds the 8-byte integer value supplied in
/// its buffer (in native endianness) to the counter. The maximum value that may be
/// stored in the counter is the largest unsigned 64-bit value
/// minus 1 (i.e., 0xfffffffffffffffe). If the addition would
/// cause the counter's value to exceed the maximum, then the
/// write either blocks until a read is performed on the
/// file descriptor, or fails with the error EAGAIN if the
/// file descriptor has been made nonblocking.
/// A write fails with the error EINVAL if the size of the
/// supplied buffer is less than 8 bytes, or if an attempt is
/// made to write the value 0xffffffffffffffff.
fn write<'tcx>(
&self,
_communicate_allowed: bool,
bytes: &[u8],
tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
let v1 = self.val.get();
let bytes: [u8; 8] = bytes.try_into().unwrap(); // FIXME fail gracefully when this has the wrong size
// Convert from target endianness to host endianness.
let num = match tcx.sess.target.endian {
Endian::Little => u64::from_le_bytes(bytes),
Endian::Big => u64::from_be_bytes(bytes),
};
// FIXME handle blocking when addition results in exceeding the max u64 value
// or fail with EAGAIN if the file descriptor is nonblocking.
let v2 = v1.checked_add(num).unwrap();
self.val.set(v2);
assert_eq!(8, bytes.len());
Ok(Ok(8))
}
}

View File

@ -1,28 +0,0 @@
use crate::*;
use crate::shims::unix::fs::FileDescriptor;
use std::io;
/// Pair of connected sockets.
///
/// We currently don't allow sending any data through this pair, so this can be just a dummy.
#[derive(Debug)]
pub struct SocketPair;
impl FileDescriptor for SocketPair {
fn name(&self) -> &'static str {
"socketpair"
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(SocketPair))
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
) -> InterpResult<'tcx, io::Result<i32>> {
Ok(Ok(0))
}
}

View File

@ -3,15 +3,13 @@ use rustc_target::spec::abi::Abi;
use crate::machine::SIGRTMAX; use crate::machine::SIGRTMAX;
use crate::machine::SIGRTMIN; use crate::machine::SIGRTMIN;
use crate::shims::unix::*;
use crate::*; use crate::*;
use shims::foreign_items::EmulateForeignItemResult; use shims::foreign_items::EmulateForeignItemResult;
use shims::unix::fs::EvalContextExt as _; use shims::unix::linux::epoll::EvalContextExt as _;
use shims::unix::linux::fd::EvalContextExt as _; use shims::unix::linux::eventfd::EvalContextExt as _;
use shims::unix::linux::mem::EvalContextExt as _; use shims::unix::linux::mem::EvalContextExt as _;
use shims::unix::linux::sync::futex; use shims::unix::linux::sync::futex;
use shims::unix::mem::EvalContextExt as _;
use shims::unix::sync::EvalContextExt as _;
use shims::unix::thread::EvalContextExt as _;
pub fn is_dyn_sym(name: &str) -> bool { pub fn is_dyn_sym(name: &str) -> bool {
matches!(name, "getrandom") matches!(name, "getrandom")
@ -31,34 +29,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern. // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern.
match link_name.as_str() { match link_name.as_str() {
// errno
"__errno_location" => {
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let errno_place = this.last_error_place()?;
this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
}
// File related shims (but also see "syscall" below for statx) // File related shims (but also see "syscall" below for statx)
"readdir64" => { "readdir64" => {
let [dirp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let [dirp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.linux_readdir64(dirp)?; let result = this.linux_readdir64(dirp)?;
this.write_scalar(result, dest)?; this.write_scalar(result, dest)?;
} }
"mmap64" => {
let [addr, length, prot, flags, fd, offset] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let offset = this.read_scalar(offset)?.to_i64()?;
let ptr = this.mmap(addr, length, prot, flags, fd, offset.into())?;
this.write_scalar(ptr, dest)?;
}
// Linux-only
"sync_file_range" => { "sync_file_range" => {
let [fd, offset, nbytes, flags] = let [fd, offset, nbytes, flags] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.sync_file_range(fd, offset, nbytes, flags)?; let result = this.sync_file_range(fd, offset, nbytes, flags)?;
this.write_scalar(result, dest)?; this.write_scalar(result, dest)?;
} }
// epoll, eventfd
"epoll_create1" => { "epoll_create1" => {
let [flag] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let [flag] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.epoll_create1(flag)?; let result = this.epoll_create1(flag)?;
@ -82,29 +66,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let result = this.eventfd(val, flag)?; let result = this.eventfd(val, flag)?;
this.write_scalar(result, dest)?; this.write_scalar(result, dest)?;
} }
"mremap" => {
let [old_address, old_size, new_size, flags] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let ptr = this.mremap(old_address, old_size, new_size, flags)?;
this.write_scalar(ptr, dest)?;
}
"socketpair" => {
let [domain, type_, protocol, sv] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.socketpair(domain, type_, protocol, sv)?;
this.write_scalar(result, dest)?;
}
"__libc_current_sigrtmin" => {
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.write_scalar(Scalar::from_i32(SIGRTMIN), dest)?;
}
"__libc_current_sigrtmax" => {
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.write_scalar(Scalar::from_i32(SIGRTMAX), dest)?;
}
// Threading // Threading
"pthread_condattr_setclock" => { "pthread_condattr_setclock" => {
@ -206,6 +167,34 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
getrandom(this, ptr, len, flags, dest)?; getrandom(this, ptr, len, flags, dest)?;
} }
"mmap64" => {
let [addr, length, prot, flags, fd, offset] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let offset = this.read_scalar(offset)?.to_i64()?;
let ptr = this.mmap(addr, length, prot, flags, fd, offset.into())?;
this.write_scalar(ptr, dest)?;
}
"mremap" => {
let [old_address, old_size, new_size, flags] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let ptr = this.mremap(old_address, old_size, new_size, flags)?;
this.write_scalar(ptr, dest)?;
}
"__errno_location" => {
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let errno_place = this.last_error_place()?;
this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
}
"__libc_current_sigrtmin" => {
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.write_scalar(Scalar::from_i32(SIGRTMIN), dest)?;
}
"__libc_current_sigrtmax" => {
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.write_scalar(Scalar::from_i32(SIGRTMAX), dest)?;
}
// Incomplete shims that we "stub out" just to get pre-main initialization code to work. // 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. // These shims are enabled only when the caller is in the standard library.

View File

@ -1,4 +1,5 @@
pub mod fd; pub mod epoll;
pub mod eventfd;
pub mod foreign_items; pub mod foreign_items;
pub mod mem; pub mod mem;
pub mod sync; pub mod sync;

View File

@ -1,10 +1,9 @@
use rustc_span::Symbol; use rustc_span::Symbol;
use rustc_target::spec::abi::Abi; use rustc_target::spec::abi::Abi;
use crate::shims::unix::*;
use crate::*; use crate::*;
use shims::foreign_items::EmulateForeignItemResult; use shims::foreign_items::EmulateForeignItemResult;
use shims::unix::fs::EvalContextExt as _;
use shims::unix::thread::EvalContextExt as _;
pub fn is_dyn_sym(_name: &str) -> bool { pub fn is_dyn_sym(_name: &str) -> bool {
false false

View File

@ -1,7 +1,9 @@
pub mod foreign_items; pub mod foreign_items;
mod fd;
mod fs; mod fs;
mod mem; mod mem;
mod socket;
mod sync; mod sync;
mod thread; mod thread;
@ -9,7 +11,15 @@ mod freebsd;
mod linux; mod linux;
mod macos; mod macos;
pub use fs::{DirHandler, FileHandler}; pub use fd::{FdTable, FileDescriptor};
pub use fs::DirTable;
// All the unix-specific extension traits
pub use fd::EvalContextExt as _;
pub use fs::EvalContextExt as _;
pub use mem::EvalContextExt as _;
pub use socket::EvalContextExt as _;
pub use sync::EvalContextExt as _;
pub use thread::EvalContextExt as _;
// Make up some constants. // Make up some constants.
const UID: u32 = 1000; const UID: u32 = 1000;

View File

@ -0,0 +1,65 @@
use std::io;
use crate::shims::unix::*;
use crate::*;
/// Pair of connected sockets.
///
/// We currently don't allow sending any data through this pair, so this can be just a dummy.
#[derive(Debug)]
struct SocketPair;
impl FileDescriptor for SocketPair {
fn name(&self) -> &'static str {
"socketpair"
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(SocketPair))
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
) -> InterpResult<'tcx, io::Result<i32>> {
Ok(Ok(0))
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Currently this function this function is a stub. Eventually we need to
/// properly implement an FD type for sockets and have this function create
/// two sockets and associated FDs such that writing to one will produce
/// data that can be read from the other.
///
/// For more information on the arguments see the socketpair manpage:
/// <https://linux.die.net/man/2/socketpair>
fn socketpair(
&mut self,
domain: &OpTy<'tcx, Provenance>,
type_: &OpTy<'tcx, Provenance>,
protocol: &OpTy<'tcx, Provenance>,
sv: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let _domain = this.read_scalar(domain)?.to_i32()?;
let _type_ = this.read_scalar(type_)?.to_i32()?;
let _protocol = this.read_scalar(protocol)?.to_i32()?;
let sv = this.deref_pointer(sv)?;
// FIXME: fail on unsupported inputs
let fds = &mut this.machine.fds;
let sv0 = fds.insert_fd(Box::new(SocketPair));
let sv0 = Scalar::try_from_int(sv0, sv.layout.size).unwrap();
let sv1 = fds.insert_fd(Box::new(SocketPair));
let sv1 = Scalar::try_from_int(sv1, sv.layout.size).unwrap();
this.write_scalar(sv0, &sv)?;
this.write_scalar(sv1, &sv.offset(sv.layout.size, sv.layout, this)?)?;
Ok(Scalar::from_i32(0))
}
}

View File

@ -31,11 +31,11 @@ def cargo_miri(cmd, quiet = True):
def normalize_stdout(str): def normalize_stdout(str):
str = str.replace("src\\", "src/") # normalize paths across platforms str = str.replace("src\\", "src/") # normalize paths across platforms
str = re.sub("finished in \d+\.\d\ds", "finished in $TIME", str) # the time keeps changing, obviously str = re.sub("finished in \\d+\\.\\d\\ds", "finished in $TIME", str) # the time keeps changing, obviously
return str return str
def normalize_stderr(str): def normalize_stderr(str):
str = re.sub("Preparing a sysroot for Miri \(target: [a-z0-9_-]+\)\.\.\. done\n", "", str) # remove leading cargo-miri setup output str = re.sub("Preparing a sysroot for Miri \\(target: [a-z0-9_-]+\\)\\.\\.\\. done\n", "", str) # remove leading cargo-miri setup output
return str return str
def check_output(actual, path, name): def check_output(actual, path, name):

View File

@ -1,9 +0,0 @@
CODEABI_1.0 {
global: *add_one_int*;
*printer*;
*test_stack_spill*;
*get_unsigned_int*;
*add_int16*;
*add_short_to_long*;
local: *;
};

View File

@ -0,0 +1,12 @@
CODEABI_1.0 {
# Define which symbols to export.
global:
add_one_int;
printer;
test_stack_spill;
get_unsigned_int;
add_int16;
add_short_to_long;
# The rest remains private.
local: *;
};

View File

@ -1,8 +1,8 @@
error: unsupported operation: `extern` static `FOO` from crate `extern_static` is not supported by Miri error: unsupported operation: extern static `FOO` is not supported by Miri
--> $DIR/extern_static.rs:LL:CC --> $DIR/extern_static.rs:LL:CC
| |
LL | let _val = unsafe { std::ptr::addr_of!(FOO) }; LL | let _val = unsafe { std::ptr::addr_of!(FOO) };
| ^^^ `extern` static `FOO` from crate `extern_static` is not supported by Miri | ^^^ extern static `FOO` is not supported by Miri
| |
= help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
= note: BACKTRACE: = note: BACKTRACE:

View File

@ -1,8 +1,8 @@
error: unsupported operation: `extern` static `E` from crate `extern_static_in_const` is not supported by Miri error: unsupported operation: extern static `E` is not supported by Miri
--> $DIR/extern_static_in_const.rs:LL:CC --> $DIR/extern_static_in_const.rs:LL:CC
| |
LL | let _val = X; LL | let _val = X;
| ^ `extern` static `E` from crate `extern_static_in_const` is not supported by Miri | ^ extern static `E` is not supported by Miri
| |
= help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
= note: BACKTRACE: = note: BACKTRACE:

View File

@ -6,5 +6,5 @@ extern "C" {
} }
fn main() { fn main() {
let _val = unsafe { environ }; //~ ERROR: /has been declared with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of [48] bytes and alignment of [48] bytes/ let _val = unsafe { environ }; //~ ERROR: /with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of [48] bytes and alignment of [48] bytes/
} }

View File

@ -1,8 +1,8 @@
error: unsupported operation: `extern` static `environ` from crate `extern_static_wrong_size` has been declared with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes error: unsupported operation: extern static `environ` has been declared as `extern_static_wrong_size::environ` with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes
--> $DIR/extern_static_wrong_size.rs:LL:CC --> $DIR/extern_static_wrong_size.rs:LL:CC
| |
LL | let _val = unsafe { environ }; LL | let _val = unsafe { environ };
| ^^^^^^^ `extern` static `environ` from crate `extern_static_wrong_size` has been declared with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes | ^^^^^^^ extern static `environ` has been declared as `extern_static_wrong_size::environ` with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes
| |
= help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
= note: BACKTRACE: = note: BACKTRACE:

View File

@ -1,8 +1,8 @@
error: unsupported operation: `extern` static `_dispatch_queue_attr_concurrent` from crate `issue_miri_3288_ice_symbolic_alignment_extern_static` is not supported by Miri error: unsupported operation: extern static `_dispatch_queue_attr_concurrent` is not supported by Miri
--> $DIR/issue-miri-3288-ice-symbolic-alignment-extern-static.rs:LL:CC --> $DIR/issue-miri-3288-ice-symbolic-alignment-extern-static.rs:LL:CC
| |
LL | let _val = *DISPATCH_QUEUE_CONCURRENT; LL | let _val = *DISPATCH_QUEUE_CONCURRENT;
| ^^^^^^^^^^^^^^^^^^^^^^^^^ `extern` static `_dispatch_queue_attr_concurrent` from crate `issue_miri_3288_ice_symbolic_alignment_extern_static` is not supported by Miri | ^^^^^^^^^^^^^^^^^^^^^^^^^ extern static `_dispatch_queue_attr_concurrent` is not supported by Miri
| |
= help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
= note: BACKTRACE: = note: BACKTRACE:

View File

@ -89,7 +89,7 @@ fn retagx_retagy_retx_writey_rety() {
// - retag `y` protected // - retag `y` protected
// - (wait for the other thread to return so that there is no foreign protector when we write) // - (wait for the other thread to return so that there is no foreign protector when we write)
// - attempt a write through `y`. // - attempt a write through `y`.
// - (UB should have occured by now, but the next step would be to // - (UB should have occurred by now, but the next step would be to
// remove `y`'s protector) // remove `y`'s protector)
let thread_y = thread::spawn(move || { let thread_y = thread::spawn(move || {
let b = (2, by); let b = (2, by);

View File

@ -1,9 +1,10 @@
use colored::*;
use regex::bytes::Regex;
use std::ffi::OsString; use std::ffi::OsString;
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{env, process::Command}; use std::{env, process::Command};
use colored::*;
use regex::bytes::Regex;
use ui_test::color_eyre::eyre::{Context, Result}; use ui_test::color_eyre::eyre::{Context, Result};
use ui_test::{ use ui_test::{
status_emitter, CommandBuilder, Config, Format, Match, Mode, OutputConflictHandling, status_emitter, CommandBuilder, Config, Format, Match, Mode, OutputConflictHandling,
@ -44,12 +45,15 @@ fn build_so_for_c_ffi_tests() -> PathBuf {
// This is to avoid automatically adding `malloc`, etc. // This is to avoid automatically adding `malloc`, etc.
// Source: https://anadoxin.org/blog/control-over-symbol-exports-in-gcc.html/ // Source: https://anadoxin.org/blog/control-over-symbol-exports-in-gcc.html/
"-fPIC", "-fPIC",
"-Wl,--version-script=tests/extern-so/libcode.version", "-Wl,--version-script=tests/extern-so/libtest.map",
]) ])
.output() .output()
.expect("failed to generate shared object file for testing external C function calls"); .expect("failed to generate shared object file for testing external C function calls");
if !cc_output.status.success() { if !cc_output.status.success() {
panic!("error in generating shared object file for testing external C function calls"); panic!(
"error in generating shared object file for testing external C function calls:\n{}",
String::from_utf8_lossy(&cc_output.stderr),
);
} }
so_file_path so_file_path
} }
@ -120,10 +124,10 @@ fn run_tests(
config.program.args.push("--target".into()); config.program.args.push("--target".into());
config.program.args.push(target.into()); config.program.args.push(target.into());
// If we're on linux, and we're testing the extern-so functionality, // If we're testing the extern-so functionality, then build the shared object file for testing
// then build the shared object file for testing external C function calls // external C function calls and push the relevant compiler flag.
// and push the relevant compiler flag. if path.starts_with("tests/extern-so/") {
if cfg!(target_os = "linux") && path.starts_with("tests/extern-so/") { assert!(cfg!(target_os = "linux"));
let so_file_path = build_so_for_c_ffi_tests(); let so_file_path = build_so_for_c_ffi_tests();
let mut flag = std::ffi::OsString::from("-Zmiri-extern-so-file="); let mut flag = std::ffi::OsString::from("-Zmiri-extern-so-file=");
flag.push(so_file_path.into_os_string()); flag.push(so_file_path.into_os_string());

View File

@ -7,6 +7,7 @@
//@ revisions:rpass1 rpass2 //@ revisions:rpass1 rpass2
//@ should-fail //@ should-fail
//@ compile-flags: -Z query-dep-graph
#![feature(rustc_attrs)] #![feature(rustc_attrs)]

View File

@ -7,6 +7,7 @@
//@ revisions:rpass1 rpass2 //@ revisions:rpass1 rpass2
//@ should-fail //@ should-fail
//@ compile-flags: -Z query-dep-graph
#![feature(rustc_attrs)] #![feature(rustc_attrs)]

View File

@ -1,6 +1,108 @@
// The goal of this test is to ensure the color of the text is the one expected. // The goal of this test is to ensure the color of the text is the one expected.
include: "utils.goml" include: "utils.goml"
define-function: (
"check-search-color",
[
theme, count_color, desc_color, path_color, bottom_border_color, keyword_color,
struct_color, associatedtype_color, tymethod_color, method_color, structfield_color,
structfield_hover_color, macro_color, fn_color, hover_path_color, hover_background, grey
],
block {
call-function: ("switch-theme", {"theme": |theme|})
// Waiting for the search results to appear...
wait-for: "#search-tabs"
assert-css: (
"#search-tabs > button > .count",
{"color": |count_color|},
ALL,
)
assert-css: (
"//*[@class='desc'][text()='Just a normal struct.']",
{"color": |desc_color|},
)
assert-css: (
"//*[@class='result-name']//*[text()='test_docs::']",
{"color": |path_color|},
)
// Checking the color of the bottom border.
assert-css: (
".search-results > a",
{"border-bottom-color": |bottom_border_color|}
)
store-value: (entry_color, |path_color|) // color of the search entry
store-value: (hover_entry_color, |hover_path_color|) // color of the hovered/focused search entry
store-value: (background_color, "transparent")
store-value: (hover_background_color, |hover_background|)
store-value: (grey, |grey|)
call-function: ("check-result-color", {
"result_kind": "keyword",
"color": |keyword_color|,
"hover_color": |keyword_color|,
})
call-function: ("check-result-color", {
"result_kind": "struct",
"color": |struct_color|,
"hover_color": |struct_color|,
})
call-function: ("check-result-color", {
"result_kind": "associatedtype",
"color": |associatedtype_color|,
"hover_color": |associatedtype_color|,
})
call-function: ("check-result-color", {
"result_kind": "tymethod",
"color": |tymethod_color|,
"hover_color": |tymethod_color|,
})
call-function: ("check-result-color", {
"result_kind": "method",
"color": |method_color|,
"hover_color": |method_color|,
})
call-function: ("check-result-color", {
"result_kind": "structfield",
"color": |structfield_color|,
"hover_color": |structfield_hover_color|,
})
call-function: ("check-result-color", {
"result_kind": "macro",
"color": |macro_color|,
"hover_color": |macro_color|,
})
call-function: ("check-result-color", {
"result_kind": "fn",
"color": |fn_color|,
"hover_color": |fn_color|,
})
// Checking the `<a>` container.
move-cursor-to: ".search-input"
focus: ".search-input" // To ensure the `<a>` container isn't focused or hovered.
assert-css: (
"//*[@class='result-name']//*[text()='test_docs::']/ancestor::a",
{"color": |path_color|, "background-color": "transparent"},
ALL,
)
// Checking color and background on hover.
move-cursor-to: "//*[@class='desc'][text()='Just a normal struct.']"
assert-css: (
"//*[@class='result-name']//*[text()='test_docs::']",
{"color": |hover_path_color|},
)
assert-css: (
"//*[@class='result-name']//*[text()='test_docs::']/ancestor::a",
{"color": |hover_path_color|, "background-color": |hover_background|},
)
}
)
define-function: ( define-function: (
"check-result-color", "check-result-color",
[result_kind, color, hover_color], [result_kind, color, hover_color],
@ -44,304 +146,67 @@ go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=coo"
show-text: true show-text: true
// Ayu theme // Ayu theme
call-function: ("switch-theme", {"theme": "ayu"}) call-function: ("check-search-color", {
"theme": "ayu",
// Waiting for the search results to appear... "count_color": "#888",
wait-for: "#search-tabs" "desc_color": "#c5c5c5",
assert-css: ( "path_color": "#0096cf",
"#search-tabs > button > .count", "bottom_border_color": "#aaa3",
{"color": "#888"}, "keyword_color": "#39afd7",
ALL, "struct_color": "#ffa0a5",
) "associatedtype_color": "#39afd7",
assert-css: ( "tymethod_color": "#fdd687",
"//*[@class='desc'][text()='Just a normal struct.']", "method_color": "#fdd687",
{"color": "#c5c5c5"}, "structfield_color": "#0096cf",
) "structfield_hover_color": "#fff",
assert-css: ( "macro_color": "#a37acc",
"//*[@class='result-name']//*[text()='test_docs::']", "fn_color": "#fdd687",
{"color": "#0096cf"}, "hover_path_color": "#fff",
) "hover_background": "#3c3c3c",
"grey": "#999",
// Checking the color of the bottom border. })
assert-css: (
".search-results > a",
{"border-bottom-color": "#aaa3"}
)
store-value: (entry_color, "#0096cf") // color of the search entry
store-value: (hover_entry_color, "#fff") // color of the hovered/focused search entry
store-value: (background_color, "transparent") // background color
store-value: (hover_background_color, "#3c3c3c") // hover background color
store-value: (grey, "#999")
call-function: (
"check-result-color", {
"result_kind": "keyword",
"color": "#39afd7",
"hover_color": "#39afd7",
},
)
call-function: (
"check-result-color", {
"result_kind": "struct",
"color": "#ffa0a5",
"hover_color": "#ffa0a5",
},
)
call-function: (
"check-result-color", {
"result_kind": "associatedtype",
"color": "#39afd7",
"hover_color": "#39afd7",
},
)
call-function: (
"check-result-color", {
"result_kind": "tymethod",
"color": "#fdd687",
"hover_color": "#fdd687",
},
)
call-function: (
"check-result-color", {
"result_kind": "method",
"color": "#fdd687",
"hover_color": "#fdd687",
},
)
call-function: (
"check-result-color", {
"result_kind": "structfield",
"color": "#0096cf",
"hover_color": "#fff",
},
)
call-function: (
"check-result-color", {
"result_kind": "macro",
"color": "#a37acc",
"hover_color": "#a37acc",
},
)
call-function: (
"check-result-color", {
"result_kind": "fn",
"color": "#fdd687",
"hover_color": "#fdd687",
},
)
// Checking the `<a>` container.
move-cursor-to: ".search-input"
focus: ".search-input" // To ensure the `<a>` container isnt focus or hover.
assert-css: (
"//*[@class='result-name']//*[text()='test_docs::']/ancestor::a",
{"color": "#0096cf", "background-color": "transparent"},
ALL,
)
// Checking color and background on hover.
move-cursor-to: "//*[@class='desc'][text()='Just a normal struct.']"
assert-css: (
"//*[@class='result-name']//*[text()='test_docs::']",
{"color": "#fff"},
)
assert-css: (
"//*[@class='result-name']//*[text()='test_docs::']/ancestor::a",
{"color": "#fff", "background-color": "#3c3c3c"},
)
// Dark theme // Dark theme
call-function: ("switch-theme", {"theme": "dark"}) call-function: ("check-search-color", {
"theme": "dark",
// Waiting for the search results to appear... "count_color": "#888",
wait-for: "#search-tabs" "desc_color": "#ddd",
assert-css: ( "path_color": "#ddd",
"#search-tabs > button > .count", "bottom_border_color": "#aaa3",
{"color": "#888"}, "keyword_color": "#d2991d",
ALL, "struct_color": "#2dbfb8",
) "associatedtype_color": "#d2991d",
assert-css: ( "tymethod_color": "#2bab63",
"//*[@class='desc'][text()='Just a normal struct.']", "method_color": "#2bab63",
{"color": "#ddd"}, "structfield_color": "#ddd",
) "structfield_hover_color": "#ddd",
assert-css: ( "macro_color": "#09bd00",
"//*[@class='result-name']//*[text()='test_docs::']", "fn_color": "#2bab63",
{"color": "#ddd"}, "hover_path_color": "#ddd",
) "hover_background": "#616161",
"grey": "#ccc",
// Checking the color of the bottom border. })
assert-css: (
".search-results > a",
{"border-bottom-color": "#aaa3"}
)
store-value: (entry_color, "#ddd") // color of the search entry
store-value: (hover_entry_color, "#ddd") // color of the hovered/focused search entry
store-value: (background_color, "transparent") // background color
store-value: (hover_background_color, "#616161") // hover background color
store-value: (grey, "#ccc")
call-function: (
"check-result-color", {
"result_kind": "keyword",
"color": "#d2991d",
"hover_color": "#d2991d",
},
)
call-function: (
"check-result-color", {
"result_kind": "struct",
"color": "#2dbfb8",
"hover_color": "#2dbfb8",
},
)
call-function: (
"check-result-color", {
"result_kind": "associatedtype",
"color": "#d2991d",
"hover_color": "#d2991d",
},
)
call-function: (
"check-result-color", {
"result_kind": "tymethod",
"color": "#2bab63",
"hover_color": "#2bab63",
},
)
call-function: (
"check-result-color", {
"result_kind": "method",
"color": "#2bab63",
"hover_color": "#2bab63",
},
)
call-function: (
"check-result-color", {
"result_kind": "structfield",
"color": "#ddd",
"hover_color": "#ddd",
},
)
call-function: (
"check-result-color", {
"result_kind": "macro",
"color": "#09bd00",
"hover_color": "#09bd00",
},
)
call-function: (
"check-result-color", {
"result_kind": "fn",
"color": "#2bab63",
"hover_color": "#2bab63",
},
)
// Checking the `<a>` container.
move-cursor-to: ".search-input"
focus: ".search-input" // To ensure the `<a>` container isnt focus or hover.
assert-css: (
"//*[@class='result-name']//*[text()='test_docs::']/ancestor::a",
{"color": "#ddd", "background-color": "transparent"},
)
// Light theme // Light theme
call-function: ("switch-theme", {"theme": "light"}) call-function: ("check-search-color", {
"theme": "light",
// Waiting for the search results to appear... "count_color": "#888",
wait-for: "#search-tabs" "desc_color": "#000",
assert-css: ( "path_color": "#000",
"#search-tabs > button > .count", "bottom_border_color": "#aaa3",
{"color": "#888"}, "keyword_color": "#3873ad",
ALL, "struct_color": "#ad378a",
) "associatedtype_color": "#3873ad",
assert-css: ( "tymethod_color": "#ad7c37",
"//*[@class='desc'][text()='Just a normal struct.']", "method_color": "#ad7c37",
{"color": "#000"}, "structfield_color": "#000",
) "structfield_hover_color": "#000",
assert-css: ( "macro_color": "#068000",
"//*[@class='result-name']//*[text()='test_docs::']", "fn_color": "#ad7c37",
{"color": "#000"}, "hover_path_color": "#000",
) "hover_background": "#ccc",
"grey": "#999",
// Checking the color of the bottom border. })
assert-css: (
".search-results > a",
{"border-bottom-color": "#aaa3"}
)
store-value: (entry_color, "#000") // color of the search entry
store-value: (hover_entry_color, "#000") // color of the hovered/focused search entry
store-value: (background_color, "transparent") // background color
store-value: (hover_background_color, "#ccc") // hover background color
store-value: (grey, "#999")
call-function: (
"check-result-color", {
"result_kind": "keyword",
"color": "#3873ad",
"hover_color": "#3873ad",
},
)
call-function: (
"check-result-color", {
"result_kind": "struct",
"color": "#ad378a",
"hover_color": "#ad378a",
},
)
call-function: (
"check-result-color", {
"result_kind": "associatedtype",
"color": "#3873ad",
"hover_color": "#3873ad",
},
)
call-function: (
"check-result-color", {
"result_kind": "tymethod",
"color": "#ad7c37",
"hover_color": "#ad7c37",
},
)
call-function: (
"check-result-color", {
"result_kind": "method",
"color": "#ad7c37",
"hover_color": "#ad7c37",
},
)
call-function: (
"check-result-color", {
"result_kind": "structfield",
"color": "#000",
"hover_color": "#000",
},
)
call-function: (
"check-result-color", {
"result_kind": "macro",
"color": "#068000",
"hover_color": "#068000",
},
)
call-function: (
"check-result-color", {
"result_kind": "fn",
"color": "#ad7c37",
"hover_color": "#ad7c37",
},
)
// Checking the `<a>` container.
move-cursor-to: ".search-input"
focus: ".search-input" // To ensure the `<a>` container isnt focus or hover.
assert-css: (
"//*[@class='result-name']//*[text()='test_docs::']/ancestor::a",
{"color": "#000", "background-color": "transparent"},
)
// Check the alias. // Check the alias.
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"