diff --git a/Cargo.lock b/Cargo.lock index 75c6ab37bd14f..7765353092553 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,7 +43,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -567,7 +567,7 @@ dependencies = [ "termize", "tokio", "toml 0.7.8", - "ui_test", + "ui_test 0.26.5", "walkdir", ] @@ -1442,7 +1442,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", ] [[package]] @@ -2342,18 +2354,18 @@ dependencies = [ "chrono-tz", "colored", "directories", - "getrandom", + "getrandom 0.3.1", "libc", "libffi", "libloading", "measureme", - "rand", + "rand 0.9.0", "regex", "rustc_version", "smallvec", "tempfile", "tikv-jemalloc-sys", - "ui_test", + "ui_test 0.28.0", "windows-sys 0.52.0", ] @@ -2782,7 +2794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" dependencies = [ "phf_shared 0.10.0", - "rand", + "rand 0.8.5", ] [[package]] @@ -2792,7 +2804,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared 0.11.3", - "rand", + "rand 0.8.5", ] [[package]] @@ -2860,7 +2872,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -2978,8 +2990,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.0", + "zerocopy 0.8.14", ] [[package]] @@ -2989,7 +3012,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.0", ] [[package]] @@ -2998,7 +3031,17 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom 0.3.1", + "zerocopy 0.8.14", ] [[package]] @@ -3007,7 +3050,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -3045,7 +3088,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", + "getrandom 0.2.15", "libredox", "thiserror 1.0.69", ] @@ -3283,7 +3326,7 @@ name = "rustc_abi" version = "0.0.0" dependencies = [ "bitflags", - "rand", + "rand 0.8.5", "rand_xoshiro", "rustc_data_structures", "rustc_feature", @@ -3897,7 +3940,7 @@ dependencies = [ name = "rustc_incremental" version = "0.0.0" dependencies = [ - "rand", + "rand 0.8.5", "rustc_ast", "rustc_data_structures", "rustc_errors", @@ -5218,7 +5261,7 @@ checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", - "getrandom", + "getrandom 0.2.15", "once_cell", "rustix", "windows-sys 0.59.0", @@ -5281,8 +5324,8 @@ version = "0.1.0" dependencies = [ "indicatif", "num", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "rayon", ] @@ -5602,7 +5645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", - "rand", + "rand 0.8.5", "static_assertions", ] @@ -5662,6 +5705,32 @@ dependencies = [ "spanned", ] +[[package]] +name = "ui_test" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7484683d60d50ca1d1b6433c3dbf6c5ad71d20387acdcfb16fe79573f3fba576" +dependencies = [ + "annotate-snippets 0.11.5", + "anyhow", + "bstr", + "cargo-platform", + "cargo_metadata 0.18.1", + "color-eyre", + "colored", + "comma", + "crossbeam-channel", + "indicatif", + "levenshtein", + "prettydiff", + "regex", + "rustc_version", + "rustfix", + "serde", + "serde_json", + "spanned", +] + [[package]] name = "unic-langid" version = "0.9.5" @@ -5843,7 +5912,7 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -5880,6 +5949,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasi-preview1-component-adapter-provider" version = "29.0.1" @@ -6475,6 +6553,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ab703352da6a72f35c39a533526393725640575bb211f61987a2748323ad956" +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + [[package]] name = "wit-component" version = "0.223.0" @@ -6584,7 +6671,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" +dependencies = [ + "zerocopy-derive 0.8.14", ] [[package]] @@ -6598,6 +6694,17 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock index 363b96fdff1f6..57a757f9085ca 100644 --- a/src/tools/miri/Cargo.lock +++ b/src/tools/miri/Cargo.lock @@ -351,7 +351,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", ] [[package]] @@ -529,12 +541,12 @@ dependencies = [ "chrono-tz", "colored", "directories", - "getrandom", + "getrandom 0.3.1", "libc", "libffi", "libloading", "measureme", - "rand", + "rand 0.9.0", "regex", "rustc_version", "smallvec", @@ -662,7 +674,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ "phf_shared", - "rand", + "rand 0.8.5", ] [[package]] @@ -692,7 +704,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -729,19 +741,28 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "libc", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.9.0", + "zerocopy 0.8.14", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.0", ] [[package]] @@ -749,8 +770,15 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" dependencies = [ - "getrandom", + "getrandom 0.3.1", + "zerocopy 0.8.14", ] [[package]] @@ -768,7 +796,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ - "getrandom", + "getrandom 0.2.15", "libredox", "thiserror", ] @@ -1051,9 +1079,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ui_test" -version = "0.26.5" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ee4c40e5a5f9fa6864ff976473e5d6a6e9884b6ce68b40690d9f87e1994c83" +checksum = "7484683d60d50ca1d1b6433c3dbf6c5ad71d20387acdcfb16fe79573f3fba576" dependencies = [ "annotate-snippets", "anyhow", @@ -1105,6 +1133,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -1244,6 +1281,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -1251,7 +1297,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" +dependencies = [ + "zerocopy-derive 0.8.14", ] [[package]] @@ -1264,3 +1319,14 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zerocopy-derive" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml index 6e8e270985e07..de80722fc3df1 100644 --- a/src/tools/miri/Cargo.toml +++ b/src/tools/miri/Cargo.toml @@ -18,8 +18,8 @@ test = false # we have no unit tests doctest = false # and no doc tests [dependencies] -getrandom = { version = "0.2", features = ["std"] } -rand = "0.8" +getrandom = { version = "0.3", features = ["std"] } +rand = "0.9" smallvec = { version = "1.7", features = ["drain_filter"] } aes = { version = "0.8.3", features = ["hazmat"] } measureme = "11" @@ -47,8 +47,8 @@ windows-sys = { version = "0.52", features = [ ] } [dev-dependencies] +ui_test = "0.28.0" colored = "2" -ui_test = "0.26.5" rustc_version = "0.4" regex = "1.5.5" tempfile = "3" diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh index fb3fc621565e3..5583030b490ae 100755 --- a/src/tools/miri/ci/ci.sh +++ b/src/tools/miri/ci/ci.sh @@ -14,9 +14,7 @@ function endgroup { begingroup "Building Miri" # Global configuration -# We are getting some odd linker warnings on macOS, make sure they do not fail the build. -# (See .) -export RUSTFLAGS="-D warnings -A linker-messages" +export RUSTFLAGS="-D warnings" export CARGO_INCREMENTAL=0 export CARGO_EXTRA_FLAGS="--locked" diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 0d405f532fcd8..6e84524c8aa86 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -2f0ad2a71e4a4528bb80bcb24bf8fa4e50cb87c2 +6dd75f0d6802f56564f5f9c947a85ded286d3986 diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs index 3d0fc5590eb81..a4f2a117b181c 100644 --- a/src/tools/miri/src/alloc_addresses/mod.rs +++ b/src/tools/miri/src/alloc_addresses/mod.rs @@ -217,7 +217,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // We have to pick a fresh address. // Leave some space to the previous allocation, to give it some chance to be less aligned. // We ensure that `(global_state.next_base_addr + slack) % 16` is uniformly distributed. - let slack = rng.gen_range(0..16); + let slack = rng.random_range(0..16); // From next_base_addr + slack, round up to adjust for alignment. let base_addr = global_state .next_base_addr diff --git a/src/tools/miri/src/alloc_addresses/reuse_pool.rs b/src/tools/miri/src/alloc_addresses/reuse_pool.rs index b0c7ee7dff562..c0d24a9fbbcf9 100644 --- a/src/tools/miri/src/alloc_addresses/reuse_pool.rs +++ b/src/tools/miri/src/alloc_addresses/reuse_pool.rs @@ -58,7 +58,7 @@ impl ReusePool { // We don't remember stack addresses: there's a lot of them (so the perf impact is big), // and we only want to reuse stack slots within the same thread or else we'll add a lot of // undesired synchronization. - if kind == MemoryKind::Stack || !rng.gen_bool(self.address_reuse_rate) { + if kind == MemoryKind::Stack || !rng.random_bool(self.address_reuse_rate) { return; } let clock = clock(); @@ -88,10 +88,10 @@ impl ReusePool { thread: ThreadId, ) -> Option<(u64, Option)> { // Determine whether we'll even attempt a reuse. As above, we don't do reuse for stack addresses. - if kind == MemoryKind::Stack || !rng.gen_bool(self.address_reuse_rate) { + if kind == MemoryKind::Stack || !rng.random_bool(self.address_reuse_rate) { return None; } - let cross_thread_reuse = rng.gen_bool(self.address_reuse_cross_thread_rate); + let cross_thread_reuse = rng.random_bool(self.address_reuse_cross_thread_rate); // Determine the pool to take this from. let subpool = self.subpool(align); // Let's see if we can find something of the right size. We want to find the full range of @@ -118,7 +118,7 @@ impl ReusePool { return None; } // Pick a random element with the desired size. - let idx = rng.gen_range(begin..end); + let idx = rng.random_range(begin..end); // Remove it from the pool and return. let (chosen_addr, chosen_size, chosen_thread, clock) = subpool.remove(idx); debug_assert!(chosen_size >= size && chosen_addr % align.bytes() == 0); diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 988a0be632774..77692ed8655ea 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -723,8 +723,8 @@ fn main() { // Ensure we have parallelism for many-seeds mode. if many_seeds.is_some() && !rustc_args.iter().any(|arg| arg.starts_with("-Zthreads=")) { - // Clamp to 8 threads; things get a lot less efficient beyond that due to lock contention. - let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(8); + // Clamp to 10 threads; things get a lot less efficient beyond that due to lock contention. + let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(10); rustc_args.push(format!("-Zthreads={threads}")); } let many_seeds = diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs index bcc8668dbc122..18a5a0612bb06 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs @@ -865,7 +865,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); let new_perm = NewPermission::from_ref_ty(val.layout.ty, kind, this); let cause = match kind { - RetagKind::TwoPhase { .. } => RetagCause::TwoPhase, + RetagKind::TwoPhase => RetagCause::TwoPhase, RetagKind::FnEntry => unreachable!(), RetagKind::Raw | RetagKind::Default => RetagCause::Normal, }; @@ -880,7 +880,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); let retag_fields = this.machine.borrow_tracker.as_mut().unwrap().get_mut().retag_fields; let retag_cause = match kind { - RetagKind::TwoPhase { .. } => unreachable!(), // can only happen in `retag_ptr_value` + RetagKind::TwoPhase => unreachable!(), // can only happen in `retag_ptr_value` RetagKind::FnEntry => RetagCause::FnEntry, RetagKind::Default | RetagKind::Raw => RetagCause::Normal, }; @@ -904,10 +904,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { new_perm: NewPermission, ) -> InterpResult<'tcx> { let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?; - let val = self.ecx.sb_retag_reference(&val, new_perm, RetagInfo { - cause: self.retag_cause, - in_field: self.in_field, - })?; + let val = self.ecx.sb_retag_reference( + &val, + new_perm, + RetagInfo { cause: self.retag_cause, in_field: self.in_field }, + )?; self.ecx.write_immediate(*val, place)?; interp_ok(()) } @@ -996,10 +997,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { access: Some(AccessKind::Write), protector: Some(ProtectorKind::StrongProtector), }; - this.sb_retag_place(place, new_perm, RetagInfo { - cause: RetagCause::InPlaceFnPassing, - in_field: false, - }) + this.sb_retag_place( + place, + new_perm, + RetagInfo { cause: RetagCause::InPlaceFnPassing, in_field: false }, + ) } /// Mark the given tag as exposed. It was found on a pointer with the given AllocId. diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs index 5d7c3d8c219f9..5c12ce39d10da 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs @@ -379,14 +379,18 @@ pub mod diagnostics { use super::*; impl fmt::Display for PermissionPriv { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", match self { - ReservedFrz { conflicted: false } => "Reserved", - ReservedFrz { conflicted: true } => "Reserved (conflicted)", - ReservedIM => "Reserved (interior mutable)", - Active => "Active", - Frozen => "Frozen", - Disabled => "Disabled", - }) + write!( + f, + "{}", + match self { + ReservedFrz { conflicted: false } => "Reserved", + ReservedFrz { conflicted: true } => "Reserved (conflicted)", + ReservedIM => "Reserved (interior mutable)", + Active => "Active", + Frozen => "Frozen", + Disabled => "Disabled", + } + ) } } diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs index fd69278f20a7f..3389b1c602c33 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs @@ -581,15 +581,18 @@ impl Tree { let mut debug_info = NodeDebugInfo::new(root_tag, root_default_perm, span); // name the root so that all allocations contain one named pointer debug_info.add_name("root of the allocation"); - nodes.insert(root_idx, Node { - tag: root_tag, - parent: None, - children: SmallVec::default(), - default_initial_perm: root_default_perm, - // The root may never be skipped, all accesses will be local. - default_initial_idempotent_foreign_access: IdempotentForeignAccess::None, - debug_info, - }); + nodes.insert( + root_idx, + Node { + tag: root_tag, + parent: None, + children: SmallVec::default(), + default_initial_perm: root_default_perm, + // The root may never be skipped, all accesses will be local. + default_initial_idempotent_foreign_access: IdempotentForeignAccess::None, + debug_info, + }, + ); nodes }; let rperms = { @@ -624,14 +627,17 @@ impl<'tcx> Tree { let parent_idx = self.tag_mapping.get(&parent_tag).unwrap(); let strongest_idempotent = default_initial_perm.strongest_idempotent_foreign_access(prot); // Create the node - self.nodes.insert(idx, Node { - tag: new_tag, - parent: Some(parent_idx), - children: SmallVec::default(), - default_initial_perm, - default_initial_idempotent_foreign_access: strongest_idempotent, - debug_info: NodeDebugInfo::new(new_tag, default_initial_perm, span), - }); + self.nodes.insert( + idx, + Node { + tag: new_tag, + parent: Some(parent_idx), + children: SmallVec::default(), + default_initial_perm, + default_initial_idempotent_foreign_access: strongest_idempotent, + debug_info: NodeDebugInfo::new(new_tag, default_initial_perm, span), + }, + ); // Register new_tag as a child of parent_tag self.nodes.get_mut(parent_idx).unwrap().children.push(idx); // Initialize perms diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs index 4cdc9348dc9f8..b1ca434361b4a 100644 --- a/src/tools/miri/src/concurrency/data_race.rs +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -830,7 +830,7 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> { let success_rate = 1.0 - this.machine.cmpxchg_weak_failure_rate; let cmpxchg_success = eq.to_scalar().to_bool()? && if can_fail_spuriously { - this.machine.rng.get_mut().gen_bool(success_rate) + this.machine.rng.get_mut().random_bool(success_rate) } else { true }; diff --git a/src/tools/miri/src/concurrency/sync.rs b/src/tools/miri/src/concurrency/sync.rs index 14c72e9398add..268268848ed2f 100644 --- a/src/tools/miri/src/concurrency/sync.rs +++ b/src/tools/miri/src/concurrency/sync.rs @@ -128,7 +128,7 @@ struct Condvar { /// The futex state. #[derive(Default, Debug)] struct Futex { - waiters: VecDeque, + waiters: Vec, /// Tracks the happens-before relationship /// between a futex-wake and a futex-wait /// during a non-spurious wake event. @@ -140,6 +140,12 @@ struct Futex { #[derive(Default, Clone)] pub struct FutexRef(Rc>); +impl FutexRef { + pub fn waiters(&self) -> usize { + self.0.borrow().waiters.len() + } +} + impl VisitProvenance for FutexRef { fn visit_provenance(&self, _visit: &mut VisitWith<'_>) { // No provenance in `Futex`. @@ -728,25 +734,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(true) } - /// Wait for the futex to be signaled, or a timeout. - /// On a signal, `retval_succ` is written to `dest`. - /// On a timeout, `retval_timeout` is written to `dest` and `errno_timeout` is set as the last error. + /// Wait for the futex to be signaled, or a timeout. Once the thread is + /// unblocked, `callback` is called with the unblock reason. fn futex_wait( &mut self, futex_ref: FutexRef, bitset: u32, timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>, - retval_succ: Scalar, - retval_timeout: Scalar, - dest: MPlaceTy<'tcx>, - errno_timeout: IoError, + callback: DynUnblockCallback<'tcx>, ) { let this = self.eval_context_mut(); let thread = this.active_thread(); let mut futex = futex_ref.0.borrow_mut(); let waiters = &mut futex.waiters; assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting"); - waiters.push_back(FutexWaiter { thread, bitset }); + waiters.push(FutexWaiter { thread, bitset }); drop(futex); this.block_thread( @@ -755,10 +757,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { callback!( @capture<'tcx> { futex_ref: FutexRef, - retval_succ: Scalar, - retval_timeout: Scalar, - dest: MPlaceTy<'tcx>, - errno_timeout: IoError, + callback: DynUnblockCallback<'tcx>, } |this, unblock: UnblockKind| { match unblock { @@ -768,29 +767,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if let Some(data_race) = &this.machine.data_race { data_race.acquire_clock(&futex.clock, &this.machine.threads); } - // Write the return value. - this.write_scalar(retval_succ, &dest)?; - interp_ok(()) }, UnblockKind::TimedOut => { // Remove the waiter from the futex. let thread = this.active_thread(); let mut futex = futex_ref.0.borrow_mut(); futex.waiters.retain(|waiter| waiter.thread != thread); - // Set errno and write return value. - this.set_last_error(errno_timeout)?; - this.write_scalar(retval_timeout, &dest)?; - interp_ok(()) }, } + + callback.call(this, unblock) } ), ); } - /// Wake up the first thread in the queue that matches any of the bits in the bitset. - /// Returns whether anything was woken. - fn futex_wake(&mut self, futex_ref: &FutexRef, bitset: u32) -> InterpResult<'tcx, bool> { + /// Wake up `count` of the threads in the queue that match any of the bits + /// in the bitset. Returns how many threads were woken. + fn futex_wake( + &mut self, + futex_ref: &FutexRef, + bitset: u32, + count: usize, + ) -> InterpResult<'tcx, usize> { let this = self.eval_context_mut(); let mut futex = futex_ref.0.borrow_mut(); let data_race = &this.machine.data_race; @@ -800,13 +799,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { data_race.release_clock(&this.machine.threads, |clock| futex.clock.clone_from(clock)); } - // Wake up the first thread in the queue that matches any of the bits in the bitset. - let Some(i) = futex.waiters.iter().position(|w| w.bitset & bitset != 0) else { - return interp_ok(false); - }; - let waiter = futex.waiters.remove(i).unwrap(); + // Remove `count` of the threads in the queue that match any of the bits in the bitset. + // We collect all of them before unblocking because the unblock callback may access the + // futex state to retrieve the remaining number of waiters on macOS. + let waiters: Vec<_> = + futex.waiters.extract_if(.., |w| w.bitset & bitset != 0).take(count).collect(); drop(futex); - this.unblock_thread(waiter.thread, BlockReason::Futex)?; - interp_ok(true) + + let woken = waiters.len(); + for waiter in waiters { + this.unblock_thread(waiter.thread, BlockReason::Futex)?; + } + + interp_ok(woken) } } diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 6d22dd8d68d93..a8a2491304dd1 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -1138,7 +1138,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { use rand::Rng as _; let this = self.eval_context_mut(); - if this.machine.rng.get_mut().gen_bool(this.machine.preemption_rate) { + if this.machine.rng.get_mut().random_bool(this.machine.preemption_rate) { this.yield_active_thread(); } } diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index c8f04e252072e..36b15dbf623da 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -558,15 +558,15 @@ where match chars.next() { Some('"') => { - cmd.extend(iter::repeat('\\').take(nslashes * 2 + 1)); + cmd.extend(iter::repeat_n('\\', nslashes * 2 + 1)); cmd.push('"'); } Some(c) => { - cmd.extend(iter::repeat('\\').take(nslashes)); + cmd.extend(iter::repeat_n('\\', nslashes)); cmd.push(c); } None => { - cmd.extend(iter::repeat('\\').take(nslashes * 2)); + cmd.extend(iter::repeat_n('\\', nslashes * 2)); break; } } diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index c5538351d7dd1..a26f12cdfb1e2 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -421,7 +421,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if this.machine.communicate() { // Fill the buffer using the host's rng. - getrandom::getrandom(&mut data) + getrandom::fill(&mut data) .map_err(|err| err_unsup_format!("host getrandom failed: {}", err))?; } else { let rng = this.machine.rng.get_mut(); @@ -678,6 +678,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) } + /// Helper function used inside shims of foreign functions to check that the target OS + /// is one of `target_oses`. It returns an error containing the `name` of the foreign function + /// in a message if this is not the case. + fn check_target_os(&self, target_oses: &[&str], name: Symbol) -> InterpResult<'tcx> { + let target_os = self.eval_context_ref().tcx.sess.target.os.as_ref(); + if !target_oses.contains(&target_os) { + throw_unsup_format!("`{name}` is not supported on {target_os}"); + } + interp_ok(()) + } + /// Helper function used inside the shims of foreign functions to assert that the target OS /// is part of the UNIX family. It panics showing a message with the `name` of the foreign function /// if this is not the case. @@ -991,6 +1002,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { check_arg_count(args) } + /// Check shim for variadic function. + /// Returns a tuple that consisting of an array of fixed args, and a slice of varargs. + fn check_shim_variadic<'a, const N: usize>( + &mut self, + abi: &FnAbi<'tcx, Ty<'tcx>>, + exp_abi: Conv, + link_name: Symbol, + args: &'a [OpTy<'tcx>], + ) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], &'a [OpTy<'tcx>])> + where + &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>, + { + self.check_abi_and_shim_symbol_clash(abi, exp_abi, link_name)?; + check_vargarg_fixed_arg_count(link_name, abi, args) + } + /// Mark a machine allocation that was just created as immutable. fn mark_immutable(&mut self, mplace: &MPlaceTy<'tcx>) { let this = self.eval_context_mut(); @@ -1184,8 +1211,10 @@ where throw_ub_format!("incorrect number of arguments: got {}, expected {}", args.len(), N) } -/// Check that the number of args is at least the minumim what we expect. -pub fn check_min_arg_count<'a, 'tcx, const N: usize>( +/// Check that the number of varargs is at least the minimum what we expect. +/// Fixed args should not be included. +/// Use `check_vararg_fixed_arg_count` to extract the varargs slice from full function arguments. +pub fn check_min_vararg_count<'a, 'tcx, const N: usize>( name: &'a str, args: &'a [OpTy<'tcx>], ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> { @@ -1193,7 +1222,35 @@ pub fn check_min_arg_count<'a, 'tcx, const N: usize>( return interp_ok(ops); } throw_ub_format!( - "incorrect number of arguments for `{name}`: got {}, expected at least {}", + "not enough variadic arguments for `{name}`: got {}, expected at least {}", + args.len(), + N + ) +} + +/// Check the number of fixed args of a vararg function. +/// Returns a tuple that consisting of an array of fixed args, and a slice of varargs. +fn check_vargarg_fixed_arg_count<'a, 'tcx, const N: usize>( + link_name: Symbol, + abi: &FnAbi<'tcx, Ty<'tcx>>, + args: &'a [OpTy<'tcx>], +) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], &'a [OpTy<'tcx>])> { + if !abi.c_variadic { + throw_ub_format!("calling a variadic function with a non-variadic caller-side signature"); + } + if abi.fixed_count != u32::try_from(N).unwrap() { + throw_ub_format!( + "incorrect number of fixed arguments for variadic function `{}`: got {}, expected {N}", + link_name.as_str(), + abi.fixed_count + ) + } + if let Some(args) = args.split_first_chunk() { + return interp_ok(args); + } + throw_ub_format!( + "incorrect number of arguments for `{}`: got {}, expected at least {}", + link_name.as_str(), args.len(), N ) diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs index 9eebbc5d3631e..bce78adcaea45 100644 --- a/src/tools/miri/src/intrinsics/mod.rs +++ b/src/tools/miri/src/intrinsics/mod.rs @@ -141,7 +141,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // FIXME: should we check for validity here? It's tricky because we do not have a // place. Codegen does not seem to set any attributes like `noundef` for intrinsic // calls, so we don't *have* to do anything. - let branch: bool = this.machine.rng.get_mut().gen(); + let branch: bool = this.machine.rng.get_mut().random(); this.write_scalar(Scalar::from_bool(branch), dest)?; } @@ -289,7 +289,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let a = this.read_scalar(a)?.to_f32()?; let b = this.read_scalar(b)?.to_f32()?; let c = this.read_scalar(c)?.to_f32()?; - let fuse: bool = this.machine.rng.get_mut().gen(); + let fuse: bool = this.machine.rng.get_mut().random(); let res = if fuse { // FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11 a.to_host().mul_add(b.to_host(), c.to_host()).to_soft() @@ -304,7 +304,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let a = this.read_scalar(a)?.to_f64()?; let b = this.read_scalar(b)?.to_f64()?; let c = this.read_scalar(c)?.to_f64()?; - let fuse: bool = this.machine.rng.get_mut().gen(); + let fuse: bool = this.machine.rng.get_mut().random(); let res = if fuse { // FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11 a.to_host().mul_add(b.to_host(), c.to_host()).to_soft() diff --git a/src/tools/miri/src/intrinsics/simd.rs b/src/tools/miri/src/intrinsics/simd.rs index 63a61dcd14878..45e316b190a68 100644 --- a/src/tools/miri/src/intrinsics/simd.rs +++ b/src/tools/miri/src/intrinsics/simd.rs @@ -304,7 +304,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let c = this.read_scalar(&this.project_index(&c, i)?)?; let dest = this.project_index(&dest, i)?; - let fuse: bool = intrinsic_name == "fma" || this.machine.rng.get_mut().gen(); + let fuse: bool = intrinsic_name == "fma" || this.machine.rng.get_mut().random(); // Works for f32 and f64. // FIXME: using host floats to work around https://github.com/rust-lang/miri/issues/2468. @@ -639,8 +639,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let (right, right_len) = this.project_to_simd(right)?; let (dest, dest_len) = this.project_to_simd(dest)?; - let index = - generic_args[2].expect_const().to_value().valtree.unwrap_branch(); + let index = generic_args[2].expect_const().to_value().valtree.unwrap_branch(); let index_len = index.len(); assert_eq!(left_len, right_len); diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 3ec35763a7d05..45054c37c40e9 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -15,6 +15,8 @@ #![feature(unqualified_local_imports)] #![feature(derive_coerce_pointee)] #![feature(arbitrary_self_types)] +#![feature(unsigned_is_multiple_of)] +#![feature(extract_if)] // Configure clippy and other lints #![allow( clippy::collapsible_else_if, @@ -36,6 +38,7 @@ clippy::needless_question_mark, clippy::needless_lifetimes, clippy::too_long_first_doc_paragraph, + // We don't use translatable diagnostics rustc::diagnostic_outside_of_impl, // We are not implementing queries here so it's fine rustc::potential_query_instability, diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 3727b5f4cae4a..4735db48e81f8 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -1112,10 +1112,13 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { // Call the lang item. let panic = ecx.tcx.lang_items().get(reason.lang_item()).unwrap(); let panic = ty::Instance::mono(ecx.tcx.tcx, panic); - ecx.call_function(panic, ExternAbi::Rust, &[], None, StackPopCleanup::Goto { - ret: None, - unwind: mir::UnwindAction::Unreachable, - })?; + ecx.call_function( + panic, + ExternAbi::Rust, + &[], + None, + StackPopCleanup::Goto { ret: None, unwind: mir::UnwindAction::Unreachable }, + )?; interp_ok(()) } @@ -1501,7 +1504,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { catch_unwind: None, timing, is_user_relevant: ecx.machine.is_user_relevant(&frame), - salt: ecx.machine.rng.borrow_mut().gen::() % ADDRS_PER_ANON_GLOBAL, + salt: ecx.machine.rng.borrow_mut().random_range(0..ADDRS_PER_ANON_GLOBAL), data_race: ecx.machine.data_race.as_ref().map(|_| data_race::FrameState::default()), }; @@ -1716,7 +1719,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { if unique { CTFE_ALLOC_SALT } else { - ecx.machine.rng.borrow_mut().gen::() % ADDRS_PER_ANON_GLOBAL + ecx.machine.rng.borrow_mut().random_range(0..ADDRS_PER_ANON_GLOBAL) } } diff --git a/src/tools/miri/src/math.rs b/src/tools/miri/src/math.rs index ed3d2d55678e2..7117f722fee89 100644 --- a/src/tools/miri/src/math.rs +++ b/src/tools/miri/src/math.rs @@ -1,9 +1,12 @@ use rand::Rng as _; -use rand::distributions::Distribution as _; use rustc_apfloat::Float as _; use rustc_apfloat::ieee::IeeeFloat; -/// Disturbes a floating-point result by a relative error on the order of (-2^scale, 2^scale). +/// Disturbes a floating-point result by a relative error in the range (-2^scale, 2^scale). +/// +/// For a 2^N ULP error, you can use an `err_scale` of `-(F::PRECISION - 1 - N)`. +/// In other words, a 1 ULP (absolute) error is the same as a `2^-(F::PRECISION-1)` relative error. +/// (Subtracting 1 compensates for the integer bit.) pub(crate) fn apply_random_float_error( ecx: &mut crate::MiriInterpCx<'_>, val: F, @@ -11,12 +14,15 @@ pub(crate) fn apply_random_float_error( ) -> F { let rng = ecx.machine.rng.get_mut(); // Generate a random integer in the range [0, 2^PREC). - let dist = rand::distributions::Uniform::new(0, 1 << F::PRECISION); - let err = F::from_u128(dist.sample(rng)) - .value - .scalbn(err_scale.strict_sub(F::PRECISION.try_into().unwrap())); + // (When read as binary, the position of the first `1` determines the exponent, + // and the remaining bits fill the mantissa. `PREC` is one plus the size of the mantissa, + // so this all works out.) + let r = F::from_u128(rng.random_range(0..(1 << F::PRECISION))).value; + // Multiply this with 2^(scale - PREC). The result is between 0 and + // 2^PREC * 2^(scale - PREC) = 2^scale. + let err = r.scalbn(err_scale.strict_sub(F::PRECISION.try_into().unwrap())); // give it a random sign - let err = if rng.gen::() { -err } else { err }; + let err = if rng.random() { -err } else { err }; // multiple the value with (1+err) (val * (F::from_u128(1).value + err).value).value } diff --git a/src/tools/miri/src/operator.rs b/src/tools/miri/src/operator.rs index 43c628d66d590..c588b6fc7f159 100644 --- a/src/tools/miri/src/operator.rs +++ b/src/tools/miri/src/operator.rs @@ -108,7 +108,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Pick one of the NaNs. let nan = nans.choose(&mut *rand).unwrap(); // Non-deterministically flip the sign. - if rand.gen() { + if rand.random() { // This will properly flip even for NaN. -nan } else { @@ -120,6 +120,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_ref(); // Return one side non-deterministically. let mut rand = this.machine.rng.borrow_mut(); - if rand.gen() { a } else { b } + if rand.random() { a } else { b } } } diff --git a/src/tools/miri/src/shims/alloc.rs b/src/tools/miri/src/shims/alloc.rs index 0fda13e061600..323b95d5f5f23 100644 --- a/src/tools/miri/src/shims/alloc.rs +++ b/src/tools/miri/src/shims/alloc.rs @@ -81,7 +81,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn malloc(&mut self, size: u64, init: AllocInit) -> InterpResult<'tcx, Pointer> { let this = self.eval_context_mut(); let align = this.malloc_align(size); - let ptr = this.allocate_ptr(Size::from_bytes(size), align, MiriMemoryKind::C.into(), init)?; + let ptr = + this.allocate_ptr(Size::from_bytes(size), align, MiriMemoryKind::C.into(), init)?; interp_ok(ptr.into()) } @@ -92,7 +93,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { size: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - let memptr = this.deref_pointer(memptr)?; + let memptr = this.deref_pointer_as(memptr, this.machine.layouts.mut_raw_ptr)?; let align = this.read_target_usize(align)?; let size = this.read_target_usize(size)?; @@ -105,7 +106,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Size::from_bytes(size), Align::from_bytes(align).unwrap(), MiriMemoryKind::C.into(), - AllocInit::Uninit + AllocInit::Uninit, )?; this.write_pointer(ptr, &memptr)?; interp_ok(Scalar::from_i32(0)) @@ -138,7 +139,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Size::from_bytes(new_size), new_align, MiriMemoryKind::C.into(), - AllocInit::Uninit + AllocInit::Uninit, )?; interp_ok(new_ptr.into()) } @@ -179,7 +180,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Size::from_bytes(size), Align::from_bytes(align).unwrap(), MiriMemoryKind::C.into(), - AllocInit::Uninit + AllocInit::Uninit, )?; interp_ok(ptr.into()) } diff --git a/src/tools/miri/src/shims/backtrace.rs b/src/tools/miri/src/shims/backtrace.rs index 1622ef280d25c..7e667e70a1721 100644 --- a/src/tools/miri/src/shims/backtrace.rs +++ b/src/tools/miri/src/shims/backtrace.rs @@ -4,7 +4,6 @@ use rustc_middle::ty::{self, Instance, Ty}; use rustc_span::{BytePos, Loc, Symbol, hygiene}; use rustc_target::callconv::{Conv, FnAbi}; -use crate::helpers::check_min_arg_count; use crate::*; impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} @@ -34,13 +33,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { abi: &FnAbi<'tcx, Ty<'tcx>>, link_name: Symbol, args: &[OpTy<'tcx>], - dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let tcx = this.tcx; + let ptr_ty = this.machine.layouts.mut_raw_ptr.ty; + let ptr_layout = this.layout_of(ptr_ty)?; + + let [flags, buf] = this.check_shim(abi, Conv::Rust, link_name, args)?; - let [flags] = check_min_arg_count("miri_get_backtrace", args)?; let flags = this.read_scalar(flags)?.to_u64()?; + let buf_place = this.deref_pointer_as(buf, ptr_layout)?; let mut data = Vec::new(); for frame in this.active_thread_stack().iter().rev() { @@ -63,44 +64,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }) .collect(); - let len: u64 = ptrs.len().try_into().unwrap(); - - let ptr_ty = this.machine.layouts.mut_raw_ptr.ty; - let array_layout = this.layout_of(Ty::new_array(tcx.tcx, ptr_ty, len)).unwrap(); - match flags { - // storage for pointers is allocated by miri - // deallocating the slice is undefined behavior with a custom global allocator 0 => { - let [_flags] = this.check_shim(abi, Conv::Rust, link_name, args)?; - - let alloc = this.allocate(array_layout, MiriMemoryKind::Rust.into())?; - - // Write pointers into array - for (i, ptr) in ptrs.into_iter().enumerate() { - let place = this.project_index(&alloc, i as u64)?; - - this.write_pointer(ptr, &place)?; - } - - this.write_immediate(Immediate::new_slice(alloc.ptr(), len, this), dest)?; + throw_unsup_format!("miri_get_backtrace: v0 is not supported any more"); } - // storage for pointers is allocated by the caller - 1 => { - let [_flags, buf] = this.check_shim(abi, Conv::Rust, link_name, args)?; - - let buf_place = this.deref_pointer(buf)?; - - let ptr_layout = this.layout_of(ptr_ty)?; - + 1 => for (i, ptr) in ptrs.into_iter().enumerate() { let offset = ptr_layout.size.checked_mul(i.try_into().unwrap(), this).unwrap(); let op_place = buf_place.offset(offset, ptr_layout, this)?; this.write_pointer(ptr, &op_place)?; - } - } + }, _ => throw_unsup_format!("unknown `miri_get_backtrace` flags {}", flags), }; diff --git a/src/tools/miri/src/shims/extern_static.rs b/src/tools/miri/src/shims/extern_static.rs index f0aebfe169378..20dd3c8db2a3e 100644 --- a/src/tools/miri/src/shims/extern_static.rs +++ b/src/tools/miri/src/shims/extern_static.rs @@ -60,10 +60,10 @@ impl<'tcx> MiriMachine<'tcx> { match ecx.tcx.sess.target.os.as_ref() { "linux" => { - Self::null_ptr_extern_statics(ecx, &[ - "__cxa_thread_atexit_impl", - "__clock_gettime64", - ])?; + Self::null_ptr_extern_statics( + ecx, + &["__cxa_thread_atexit_impl", "__clock_gettime64"], + )?; Self::weak_symbol_extern_statics(ecx, &["getrandom", "statx"])?; } "freebsd" => { diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index 73425eee51569..6b4f4cdc922a0 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -1,6 +1,6 @@ use std::any::Any; use std::collections::BTreeMap; -use std::io::{IsTerminal, Read, SeekFrom, Write}; +use std::io::{IsTerminal, SeekFrom, Write}; use std::marker::CoercePointee; use std::ops::Deref; use std::rc::{Rc, Weak}; @@ -140,8 +140,8 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt { _communicate_allowed: bool, _ptr: Pointer, _len: usize, - _dest: &MPlaceTy<'tcx>, _ecx: &mut MiriInterpCx<'tcx>, + _finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { throw_unsup_format!("cannot read from {}", self.name()); } @@ -154,8 +154,8 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt { _communicate_allowed: bool, _ptr: Pointer, _len: usize, - _dest: &MPlaceTy<'tcx>, _ecx: &mut MiriInterpCx<'tcx>, + _finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { throw_unsup_format!("cannot write to {}", self.name()); } @@ -207,19 +207,16 @@ impl FileDescription for io::Stdin { communicate_allowed: bool, ptr: Pointer, len: usize, - dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { - let mut bytes = vec![0; len]; 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")?; } - let result = Read::read(&mut &*self, &mut bytes); - match result { - Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest), - Err(e) => ecx.set_last_error_and_return(e, dest), - } + + let result = ecx.read_from_host(&*self, len, ptr)?; + finish.call(ecx, result) } fn is_tty(&self, communicate_allowed: bool) -> bool { @@ -237,22 +234,19 @@ impl FileDescription for io::Stdout { _communicate_allowed: bool, ptr: Pointer, len: usize, - dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { - let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?; - // We allow writing to stderr even with isolation enabled. - let result = Write::write(&mut &*self, bytes); + // We allow writing to stdout even with isolation enabled. + let result = ecx.write_to_host(&*self, len, ptr)?; // 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(); - match result { - Ok(write_size) => ecx.return_write_success(write_size, dest), - Err(e) => ecx.set_last_error_and_return(e, dest), - } + + finish.call(ecx, result) } fn is_tty(&self, communicate_allowed: bool) -> bool { @@ -270,17 +264,13 @@ impl FileDescription for io::Stderr { _communicate_allowed: bool, ptr: Pointer, len: usize, - dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { - let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?; // We allow writing to stderr even with isolation enabled. + let result = ecx.write_to_host(&*self, len, ptr)?; // No need to flush, stderr is not buffered. - let result = Write::write(&mut &*self, bytes); - match result { - Ok(write_size) => ecx.return_write_success(write_size, dest), - Err(e) => ecx.set_last_error_and_return(e, dest), - } + finish.call(ecx, result) } fn is_tty(&self, communicate_allowed: bool) -> bool { @@ -302,11 +292,11 @@ impl FileDescription for NullOutput { _communicate_allowed: bool, _ptr: Pointer, len: usize, - dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { // We just don't write anything, but report to the user that we did. - ecx.return_write_success(len, dest) + finish.call(ecx, Ok(len)) } } @@ -405,40 +395,41 @@ impl FdTable { impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { - /// Helper to implement `FileDescription::read`: - /// This is only used when `read` is successful. - /// `actual_read_size` should be the return value of some underlying `read` call that used - /// `bytes` as its output buffer. - /// The length of `bytes` must not exceed either the host's or the target's `isize`. - /// `bytes` is written to `buf` and the size is written to `dest`. - fn return_read_success( + /// Read data from a host `Read` type, store the result into machine memory, + /// and return whether that worked. + fn read_from_host( &mut self, - buf: Pointer, - bytes: &[u8], - actual_read_size: usize, - dest: &MPlaceTy<'tcx>, - ) -> InterpResult<'tcx> { + mut file: impl io::Read, + len: usize, + ptr: Pointer, + ) -> InterpResult<'tcx, Result> { let this = self.eval_context_mut(); - // If reading to `bytes` did not fail, we write those bytes to the buffer. - // Crucially, if fewer than `bytes.len()` bytes were read, only write - // that much into the output buffer! - this.write_bytes_ptr(buf, bytes[..actual_read_size].iter().copied())?; - // The actual read size is always less than what got originally requested so this cannot fail. - this.write_int(u64::try_from(actual_read_size).unwrap(), dest)?; - interp_ok(()) + let mut bytes = vec![0; len]; + let result = file.read(&mut bytes); + match result { + Ok(read_size) => { + // If reading to `bytes` did not fail, we write those bytes to the buffer. + // Crucially, if fewer than `bytes.len()` bytes were read, only write + // that much into the output buffer! + this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?; + interp_ok(Ok(read_size)) + } + Err(e) => interp_ok(Err(IoError::HostError(e))), + } } - /// Helper to implement `FileDescription::write`: - /// This function is only used when `write` is successful, and writes `actual_write_size` to `dest` - fn return_write_success( + /// Write data to a host `Write` type, withthe bytes taken from machine memory. + fn write_to_host( &mut self, - actual_write_size: usize, - dest: &MPlaceTy<'tcx>, - ) -> InterpResult<'tcx> { + mut file: impl io::Write, + len: usize, + ptr: Pointer, + ) -> InterpResult<'tcx, Result> { let this = self.eval_context_mut(); - // The actual write size is always less than what got originally requested so this cannot fail. - this.write_int(u64::try_from(actual_write_size).unwrap(), dest)?; - interp_ok(()) + + let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?; + let result = file.write(bytes); + interp_ok(result.map_err(IoError::HostError)) } } diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 1ce0c209de9e2..97bfb04f1f471 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -357,7 +357,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Obtains a Miri backtrace. See the README for details. "miri_get_backtrace" => { // `check_shim` happens inside `handle_miri_get_backtrace`. - this.handle_miri_get_backtrace(abi, link_name, args, dest)?; + this.handle_miri_get_backtrace(abi, link_name, args)?; } // Resolves a Miri backtrace frame. See the README for details. "miri_resolve_frame" => { @@ -509,7 +509,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { Size::from_bytes(size), Align::from_bytes(align).unwrap(), memory_kind.into(), - AllocInit::Uninit + AllocInit::Uninit, )?; ecx.write_pointer(ptr, dest) @@ -538,7 +538,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { Size::from_bytes(size), Align::from_bytes(align).unwrap(), MiriMemoryKind::Rust.into(), - AllocInit::Zero + AllocInit::Zero, )?; this.write_pointer(ptr, dest) }); @@ -599,7 +599,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { Size::from_bytes(new_size), align, MiriMemoryKind::Rust.into(), - AllocInit::Uninit + AllocInit::Uninit, )?; this.write_pointer(new_ptr, dest) }); @@ -861,7 +861,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { "lgammaf_r" => { let [x, signp] = this.check_shim(abi, Conv::C, link_name, args)?; let x = this.read_scalar(x)?.to_f32()?; - let signp = this.deref_pointer(signp)?; + let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?; // Using host floats (but it's fine, these operations do not have guaranteed precision). let (res, sign) = x.to_host().ln_gamma(); @@ -872,7 +872,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { "lgamma_r" => { let [x, signp] = this.check_shim(abi, Conv::C, link_name, args)?; let x = this.read_scalar(x)?.to_f64()?; - let signp = this.deref_pointer(signp)?; + let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?; // Using host floats (but it's fine, these operations do not have guaranteed precision). let (res, sign) = x.to_host().ln_gamma(); diff --git a/src/tools/miri/src/shims/panic.rs b/src/tools/miri/src/shims/panic.rs index 93479540009ea..83f331bb173db 100644 --- a/src/tools/miri/src/shims/panic.rs +++ b/src/tools/miri/src/shims/panic.rs @@ -247,10 +247,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Call the lang item associated with this message. let fn_item = this.tcx.require_lang_item(msg.panic_function(), None); let instance = ty::Instance::mono(this.tcx.tcx, fn_item); - this.call_function(instance, ExternAbi::Rust, &[], None, StackPopCleanup::Goto { - ret: None, - unwind, - })?; + this.call_function( + instance, + ExternAbi::Rust, + &[], + None, + StackPopCleanup::Goto { ret: None, unwind }, + )?; } } interp_ok(()) diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index d6c77d9c4d9a0..64b3ce6b4e494 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -132,16 +132,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.assert_target_os_is_unix("localtime_r"); this.check_no_isolation("`localtime_r`")?; - let timep = this.deref_pointer(timep)?; + let time_layout = this.libc_ty_layout("time_t"); + let timep = this.deref_pointer_as(timep, time_layout)?; let result = this.deref_pointer_as(result_op, this.libc_ty_layout("tm"))?; // The input "represents the number of seconds elapsed since the Epoch, // 1970-01-01 00:00:00 +0000 (UTC)". - let sec_since_epoch: i64 = this - .read_scalar(&timep)? - .to_int(this.libc_ty_layout("time_t").size)? - .try_into() - .unwrap(); + let sec_since_epoch: i64 = + this.read_scalar(&timep)?.to_int(time_layout.size)?.try_into().unwrap(); let dt_utc: DateTime = DateTime::from_timestamp(sec_since_epoch, 0).expect("Invalid timestamp"); @@ -254,7 +252,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let qpc = i64::try_from(duration.as_nanos()).map_err(|_| { err_unsup_format!("programs running longer than 2^63 nanoseconds are not supported") })?; - this.write_scalar(Scalar::from_i64(qpc), &this.deref_pointer(lpPerformanceCount_op)?)?; + this.write_scalar( + Scalar::from_i64(qpc), + &this.deref_pointer_as(lpPerformanceCount_op, this.machine.layouts.i64)?, + )?; interp_ok(Scalar::from_i32(-1)) // return non-zero on success } diff --git a/src/tools/miri/src/shims/unix/android/thread.rs b/src/tools/miri/src/shims/unix/android/thread.rs index 8d5d4a52b6efe..c7e2c4d507b22 100644 --- a/src/tools/miri/src/shims/unix/android/thread.rs +++ b/src/tools/miri/src/shims/unix/android/thread.rs @@ -3,7 +3,7 @@ use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::{Conv, FnAbi}; -use crate::helpers::check_min_arg_count; +use crate::helpers::check_min_vararg_count; use crate::shims::unix::thread::{EvalContextExt as _, ThreadNameResult}; use crate::*; @@ -16,18 +16,15 @@ pub fn prctl<'tcx>( args: &[OpTy<'tcx>], dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { - // We do not use `check_shim` here because `prctl` is variadic. The argument - // count is checked bellow. - ecx.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?; + let ([op], varargs) = ecx.check_shim_variadic(abi, Conv::C, link_name, args)?; // FIXME: Use constants once https://github.com/rust-lang/libc/pull/3941 backported to the 0.2 branch. let pr_set_name = 15; let pr_get_name = 16; - let [op] = check_min_arg_count("prctl", args)?; let res = match ecx.read_scalar(op)?.to_i32()? { op if op == pr_set_name => { - let [_, name] = check_min_arg_count("prctl(PR_SET_NAME, ...)", args)?; + let [name] = check_min_vararg_count("prctl(PR_SET_NAME, ...)", varargs)?; let name = ecx.read_scalar(name)?; let thread = ecx.pthread_self()?; // The Linux kernel silently truncates long names. @@ -38,7 +35,7 @@ pub fn prctl<'tcx>( Scalar::from_u32(0) } op if op == pr_get_name => { - let [_, name] = check_min_arg_count("prctl(PR_GET_NAME, ...)", args)?; + let [name] = check_min_vararg_count("prctl(PR_GET_NAME, ...)", varargs)?; let name = ecx.read_scalar(name)?; let thread = ecx.pthread_self()?; let len = Scalar::from_target_usize(TASK_COMM_LEN as u64, ecx); diff --git a/src/tools/miri/src/shims/unix/fd.rs b/src/tools/miri/src/shims/unix/fd.rs index 0b59490308b44..3f85b9ae9bd94 100644 --- a/src/tools/miri/src/shims/unix/fd.rs +++ b/src/tools/miri/src/shims/unix/fd.rs @@ -6,7 +6,7 @@ use std::io::ErrorKind; use rustc_abi::Size; -use crate::helpers::check_min_arg_count; +use crate::helpers::check_min_vararg_count; use crate::shims::files::FileDescription; use crate::shims::unix::linux_like::epoll::EpollReadyEvents; use crate::shims::unix::*; @@ -30,8 +30,8 @@ pub trait UnixFileDescription: FileDescription { _offset: u64, _ptr: Pointer, _len: usize, - _dest: &MPlaceTy<'tcx>, _ecx: &mut MiriInterpCx<'tcx>, + _finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { throw_unsup_format!("cannot pread from {}", self.name()); } @@ -46,8 +46,8 @@ pub trait UnixFileDescription: FileDescription { _ptr: Pointer, _len: usize, _offset: u64, - _dest: &MPlaceTy<'tcx>, _ecx: &mut MiriInterpCx<'tcx>, + _finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { throw_unsup_format!("cannot pwrite to {}", self.name()); } @@ -127,11 +127,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) } - fn fcntl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> { + fn fcntl( + &mut self, + fd_num: &OpTy<'tcx>, + cmd: &OpTy<'tcx>, + varargs: &[OpTy<'tcx>], + ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - let [fd_num, cmd] = check_min_arg_count("fcntl", args)?; - let fd_num = this.read_scalar(fd_num)?.to_i32()?; let cmd = this.read_scalar(cmd)?.to_i32()?; @@ -163,7 +166,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "fcntl(fd, F_DUPFD_CLOEXEC, ...)" }; - let [_, _, start] = check_min_arg_count(cmd_name, args)?; + let [start] = check_min_vararg_count(cmd_name, varargs)?; let start = this.read_scalar(start)?.to_i32()?; if let Some(fd) = this.machine.fds.get(fd_num) { @@ -233,7 +236,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let count = usize::try_from(count).unwrap(); // now it fits in a `usize` let communicate = this.machine.communicate(); - // We temporarily dup the FD to be able to retain mutable access to `this`. + // Get the FD. let Some(fd) = this.machine.fds.get(fd_num) else { trace!("read: FD not found"); return this.set_last_error_and_return(LibcError("EBADF"), dest); @@ -244,13 +247,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // 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 finish = { + let dest = dest.clone(); + callback!( + @capture<'tcx> { + count: usize, + dest: MPlaceTy<'tcx>, + } + |this, result: Result| { + match result { + Ok(read_size) => { + assert!(read_size <= count); + // This must fit since `count` fits. + this.write_int(u64::try_from(read_size).unwrap(), &dest) + } + Err(e) => { + this.set_last_error_and_return(e, &dest) + } + }} + ) + }; match offset { - None => fd.read(communicate, buf, count, dest, this)?, + None => fd.read(communicate, buf, count, this, finish)?, Some(offset) => { let Ok(offset) = u64::try_from(offset) else { return this.set_last_error_and_return(LibcError("EINVAL"), dest); }; - fd.as_unix().pread(communicate, offset, buf, count, dest, this)? + fd.as_unix().pread(communicate, offset, buf, count, this, finish)? } }; interp_ok(()) @@ -284,13 +307,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_last_error_and_return(LibcError("EBADF"), dest); }; + let finish = { + let dest = dest.clone(); + callback!( + @capture<'tcx> { + count: usize, + dest: MPlaceTy<'tcx>, + } + |this, result: Result| { + match result { + Ok(write_size) => { + assert!(write_size <= count); + // This must fit since `count` fits. + this.write_int(u64::try_from(write_size).unwrap(), &dest) + } + Err(e) => { + this.set_last_error_and_return(e, &dest) + } + }} + ) + }; match offset { - None => fd.write(communicate, buf, count, dest, this)?, + None => fd.write(communicate, buf, count, this, finish)?, Some(offset) => { let Ok(offset) = u64::try_from(offset) else { return this.set_last_error_and_return(LibcError("EINVAL"), dest); }; - fd.as_unix().pwrite(communicate, buf, count, offset, dest, this)? + fd.as_unix().pwrite(communicate, buf, count, offset, this, finish)? } }; interp_ok(()) diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 3353cf2cc59d2..d459ec7cb774c 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -205,10 +205,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { 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, Conv::C, link_name)?; - let result = this.fcntl(args)?; + let ([fd_num, cmd], varargs) = + this.check_shim_variadic(abi, Conv::C, link_name, args)?; + let result = this.fcntl(fd_num, cmd, varargs)?; this.write_scalar(result, dest)?; } "dup" => { @@ -236,8 +235,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "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, Conv::C, link_name)?; - let result = this.open(args)?; + let ([path_raw, flag], varargs) = + this.check_shim_variadic(abi, Conv::C, link_name, args)?; + let result = this.open(path_raw, flag, varargs)?; this.write_scalar(result, dest)?; } "unlink" => { @@ -354,10 +354,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "pipe2" => { // Currently this function does not exist on all Unixes, e.g. on macOS. - if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "solaris" | "illumos") - { - throw_unsup_format!("`pipe2` is not supported on {}", this.tcx.sess.target.os); - } + this.check_target_os(&["linux", "freebsd", "solaris", "illumos"], link_name)?; let [pipefd, flags] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.pipe2(pipefd, Some(flags))?; this.write_scalar(result, dest)?; @@ -402,12 +399,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "reallocarray" => { // Currently this function does not exist on all Unixes, e.g. on macOS. - if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "android") { - throw_unsup_format!( - "`reallocarray` is not supported on {}", - this.tcx.sess.target.os - ); - } + this.check_target_os(&["linux", "freebsd", "android"], link_name)?; let [ptr, nmemb, size] = this.check_shim(abi, Conv::C, link_name, args)?; let ptr = this.read_pointer(ptr)?; let nmemb = this.read_target_usize(nmemb)?; @@ -656,13 +648,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "sched_getaffinity" => { // Currently this function does not exist on all Unixes, e.g. on macOS. - if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "android") { - throw_unsup_format!( - "`sched_getaffinity` is not supported on {}", - this.tcx.sess.target.os - ); - } - + this.check_target_os(&["linux", "freebsd", "android"], link_name)?; let [pid, cpusetsize, mask] = this.check_shim(abi, Conv::C, link_name, args)?; let pid = this.read_scalar(pid)?.to_u32()?; let cpusetsize = this.read_target_usize(cpusetsize)?; @@ -699,13 +685,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "sched_setaffinity" => { // Currently this function does not exist on all Unixes, e.g. on macOS. - if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "android") { - throw_unsup_format!( - "`sched_setaffinity` is not supported on {}", - this.tcx.sess.target.os - ); - } - + this.check_target_os(&["linux", "freebsd", "android"], link_name)?; let [pid, cpusetsize, mask] = this.check_shim(abi, Conv::C, link_name, args)?; let pid = this.read_scalar(pid)?.to_u32()?; let cpusetsize = this.read_target_usize(cpusetsize)?; @@ -761,16 +741,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "getentropy" => { // This function is non-standard but exists with the same signature and behavior on // Linux, macOS, FreeBSD and Solaris/Illumos. - if !matches!( - &*this.tcx.sess.target.os, - "linux" | "macos" | "freebsd" | "illumos" | "solaris" | "android" - ) { - throw_unsup_format!( - "`getentropy` is not supported on {}", - this.tcx.sess.target.os - ); - } - + this.check_target_os( + &["linux", "macos", "freebsd", "illumos", "solaris", "android"], + link_name, + )?; let [buf, bufsize] = this.check_shim(abi, Conv::C, link_name, args)?; let buf = this.read_pointer(buf)?; let bufsize = this.read_target_usize(bufsize)?; @@ -797,15 +771,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "getrandom" => { // This function is non-standard but exists with the same signature and behavior on // Linux, FreeBSD and Solaris/Illumos. - if !matches!( - &*this.tcx.sess.target.os, - "linux" | "freebsd" | "illumos" | "solaris" | "android" - ) { - throw_unsup_format!( - "`getrandom` is not supported on {}", - this.tcx.sess.target.os - ); - } + this.check_target_os( + &["linux", "freebsd", "illumos", "solaris", "android"], + link_name, + )?; let [ptr, len, flags] = this.check_shim(abi, Conv::C, link_name, args)?; let ptr = this.read_pointer(ptr)?; let len = this.read_target_usize(len)?; @@ -817,12 +786,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "arc4random_buf" => { // This function is non-standard but exists with the same signature and // same behavior (eg never fails) on FreeBSD and Solaris/Illumos. - if !matches!(&*this.tcx.sess.target.os, "freebsd" | "illumos" | "solaris") { - throw_unsup_format!( - "`arc4random_buf` is not supported on {}", - this.tcx.sess.target.os - ); - } + this.check_target_os(&["freebsd", "illumos", "solaris"], link_name)?; let [ptr, len] = this.check_shim(abi, Conv::C, link_name, args)?; let ptr = this.read_pointer(ptr)?; let len = this.read_target_usize(len)?; @@ -842,15 +806,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // For arm32 they did something custom, but similar enough that the same // `_Unwind_RaiseException` impl in miri should work: // https://github.com/ARM-software/abi-aa/blob/main/ehabi32/ehabi32.rst - if !matches!( - &*this.tcx.sess.target.os, - "linux" | "freebsd" | "illumos" | "solaris" | "android" | "macos" - ) { - throw_unsup_format!( - "`_Unwind_RaiseException` is not supported on {}", - this.tcx.sess.target.os - ); - } + this.check_target_os( + &["linux", "freebsd", "illumos", "solaris", "android", "macos"], + link_name, + )?; // This function looks and behaves excatly like miri_start_unwind. let [payload] = this.check_shim(abi, Conv::C, link_name, args)?; this.handle_miri_start_unwind(payload)?; @@ -866,8 +825,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // These shims are enabled only when the caller is in the standard library. "pthread_attr_getguardsize" if this.frame_in_std() => { let [_attr, guard_size] = this.check_shim(abi, Conv::C, link_name, args)?; - let guard_size = this.deref_pointer(guard_size)?; let guard_size_layout = this.libc_ty_layout("size_t"); + let guard_size = this.deref_pointer_as(guard_size, guard_size_layout)?; this.write_scalar( Scalar::from_uint(this.machine.page_size, guard_size_layout.size), &guard_size, @@ -893,8 +852,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.check_shim(abi, Conv::C, link_name, args)?; let _attr_place = this.deref_pointer_as(attr_place, this.libc_ty_layout("pthread_attr_t"))?; - let addr_place = this.deref_pointer(addr_place)?; - let size_place = this.deref_pointer(size_place)?; + let addr_place = this.deref_pointer_as(addr_place, this.machine.layouts.usize)?; + let size_place = this.deref_pointer_as(size_place, this.machine.layouts.usize)?; this.write_scalar( Scalar::from_uint(this.machine.stack_addr, this.pointer_size()), @@ -928,7 +887,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let pwd = this.deref_pointer_as(pwd, this.libc_ty_layout("passwd"))?; let buf = this.read_pointer(buf)?; let buflen = this.read_target_usize(buflen)?; - let result = this.deref_pointer(result)?; + let result = this.deref_pointer_as(result, this.machine.layouts.mut_raw_ptr)?; // Must be for "us". if uid != UID { diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs index 03dbd931329c7..08d06fe5d4c61 100644 --- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -60,17 +60,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // since freebsd 12 the former form can be expected. "stat" | "stat@FBSD_1.0" => { let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?; - let result = this.macos_fbsd_solaris_stat(path, buf)?; + let result = this.macos_fbsd_solarish_stat(path, buf)?; this.write_scalar(result, dest)?; } "lstat" | "lstat@FBSD_1.0" => { let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?; - let result = this.macos_fbsd_solaris_lstat(path, buf)?; + let result = this.macos_fbsd_solarish_lstat(path, buf)?; this.write_scalar(result, dest)?; } "fstat" | "fstat@FBSD_1.0" => { let [fd, buf] = this.check_shim(abi, Conv::C, link_name, args)?; - let result = this.macos_fbsd_solaris_fstat(fd, buf)?; + let result = this.macos_fbsd_solarish_fstat(fd, buf)?; this.write_scalar(result, dest)?; } "readdir_r" | "readdir_r@FBSD_1.0" => { diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index cafce62cfedbe..c7399b00d3fe2 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -13,7 +13,7 @@ use rustc_abi::Size; use rustc_data_structures::fx::FxHashMap; use self::shims::time::system_time_to_duration; -use crate::helpers::check_min_arg_count; +use crate::helpers::check_min_vararg_count; use crate::shims::files::{EvalContextExt as _, FileDescription, FileDescriptionRef}; use crate::shims::os_str::bytes_to_os_str; use crate::shims::unix::fd::{FlockOp, UnixFileDescription}; @@ -35,16 +35,13 @@ impl FileDescription for FileHandle { communicate_allowed: bool, ptr: Pointer, len: usize, - dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { assert!(communicate_allowed, "isolation should have prevented even opening a file"); - let mut bytes = vec![0; len]; - let result = (&mut &self.file).read(&mut bytes); - match result { - Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest), - Err(e) => ecx.set_last_error_and_return(e, dest), - } + + let result = ecx.read_from_host(&self.file, len, ptr)?; + finish.call(ecx, result) } fn write<'tcx>( @@ -52,16 +49,13 @@ impl FileDescription for FileHandle { communicate_allowed: bool, ptr: Pointer, len: usize, - dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { assert!(communicate_allowed, "isolation should have prevented even opening a file"); - let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?; - let result = (&mut &self.file).write(bytes); - match result { - Ok(write_size) => ecx.return_write_success(write_size, dest), - Err(e) => ecx.set_last_error_and_return(e, dest), - } + + let result = ecx.write_to_host(&self.file, len, ptr)?; + finish.call(ecx, result) } fn seek<'tcx>( @@ -119,8 +113,8 @@ impl UnixFileDescription for FileHandle { offset: u64, ptr: Pointer, len: usize, - dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { assert!(communicate_allowed, "isolation should have prevented even opening a file"); let mut bytes = vec![0; len]; @@ -137,11 +131,17 @@ impl UnixFileDescription for FileHandle { .expect("failed to restore file position, this shouldn't be possible"); res }; - let result = f(); - match result { - Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest), - Err(e) => ecx.set_last_error_and_return(e, dest), - } + let result = match f() { + Ok(read_size) => { + // If reading to `bytes` did not fail, we write those bytes to the buffer. + // Crucially, if fewer than `bytes.len()` bytes were read, only write + // that much into the output buffer! + ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?; + Ok(read_size) + } + Err(e) => Err(IoError::HostError(e)), + }; + finish.call(ecx, result) } fn pwrite<'tcx>( @@ -150,8 +150,8 @@ impl UnixFileDescription for FileHandle { ptr: Pointer, len: usize, offset: u64, - dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { assert!(communicate_allowed, "isolation should have prevented even opening a file"); // Emulates pwrite using seek + write + seek to restore cursor position. @@ -169,10 +169,7 @@ impl UnixFileDescription for FileHandle { res }; let result = f(); - match result { - Ok(write_size) => ecx.return_write_success(write_size, dest), - Err(e) => ecx.set_last_error_and_return(e, dest), - } + finish.call(ecx, result.map_err(IoError::HostError)) } fn flock<'tcx>( @@ -273,7 +270,7 @@ impl UnixFileDescription for FileHandle { impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {} trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { - fn macos_fbsd_solaris_write_buf( + fn macos_fbsd_solarish_write_stat_buf( &mut self, metadata: FileMetadata, buf_op: &OpTy<'tcx>, @@ -321,9 +318,9 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { } if matches!(&*this.tcx.sess.target.os, "solaris" | "illumos") { - // FIXME: write st_fstype field once libc is updated. - // https://github.com/rust-lang/libc/pull/4145 - //this.write_int_fields_named(&[("st_fstype", 0)], &buf)?; + let st_fstype = this.project_field_named(&buf, "st_fstype")?; + // This is an array; write 0 into first element so that it encodes the empty string. + this.write_int(0, &this.project_index(&st_fstype, 0)?)?; } interp_ok(0) @@ -452,9 +449,12 @@ fn maybe_sync_file( impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { - fn open(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> { - let [path_raw, flag] = check_min_arg_count("open", args)?; - + fn open( + &mut self, + path_raw: &OpTy<'tcx>, + flag: &OpTy<'tcx>, + varargs: &[OpTy<'tcx>], + ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); let path_raw = this.read_pointer(path_raw)?; @@ -507,7 +507,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Get the mode. On macOS, the argument type `mode_t` is actually `u16`, but // C integer promotion rules mean that on the ABI level, it gets passed as `u32` // (see https://github.com/rust-lang/rust/issues/71915). - let [_, _, mode] = check_min_arg_count("open(pathname, O_CREAT, ...)", args)?; + let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?; let mode = this.read_scalar(mode)?.to_u32()?; #[cfg(unix)] @@ -668,7 +668,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) } - fn macos_fbsd_solaris_stat( + fn macos_fbsd_solarish_stat( &mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>, @@ -694,11 +694,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Err(err) => return this.set_last_error_and_return_i32(err), }; - interp_ok(Scalar::from_i32(this.macos_fbsd_solaris_write_buf(metadata, buf_op)?)) + interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?)) } // `lstat` is used to get symlink metadata. - fn macos_fbsd_solaris_lstat( + fn macos_fbsd_solarish_lstat( &mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>, @@ -726,10 +726,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Err(err) => return this.set_last_error_and_return_i32(err), }; - interp_ok(Scalar::from_i32(this.macos_fbsd_solaris_write_buf(metadata, buf_op)?)) + interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?)) } - fn macos_fbsd_solaris_fstat( + fn macos_fbsd_solarish_fstat( &mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>, @@ -756,7 +756,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Ok(metadata) => metadata, Err(err) => return this.set_last_error_and_return_i32(err), }; - interp_ok(Scalar::from_i32(this.macos_fbsd_solaris_write_buf(metadata, buf_op)?)) + interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?)) } fn linux_statx( @@ -1109,7 +1109,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Size::from_bytes(size), dirent_layout.align.abi, MiriMemoryKind::Runtime.into(), - AllocInit::Uninit + AllocInit::Uninit, )?; let entry: Pointer = entry.into(); @@ -1169,6 +1169,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } let dirp = this.read_target_usize(dirp_op)?; + let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?; // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { @@ -1254,15 +1255,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } _ => unreachable!(), } - - let result_place = this.deref_pointer(result_op)?; this.write_scalar(this.read_scalar(entry_op)?, &result_place)?; Scalar::from_i32(0) } None => { // end of stream: return 0, assign *result=NULL - this.write_null(&this.deref_pointer(result_op)?)?; + this.write_null(&result_place)?; Scalar::from_i32(0) } Some(Err(e)) => { @@ -1548,7 +1547,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { - use rand::seq::SliceRandom; + use rand::seq::IndexedRandom; // POSIX defines the template string. const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX"; diff --git a/src/tools/miri/src/shims/unix/linux/mem.rs b/src/tools/miri/src/shims/unix/linux/mem.rs index 6418d749d3d9d..8e5a3021b1c03 100644 --- a/src/tools/miri/src/shims/unix/linux/mem.rs +++ b/src/tools/miri/src/shims/unix/linux/mem.rs @@ -49,7 +49,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Size::from_bytes(new_size), align, MiriMemoryKind::Mmap.into(), - AllocInit::Zero + AllocInit::Zero, )?; interp_ok(Scalar::from_pointer(ptr, this)) diff --git a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs index 4b76bbb2b4de4..936d436bd82d6 100644 --- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs +++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs @@ -51,20 +51,20 @@ impl FileDescription for EventFd { _communicate_allowed: bool, ptr: Pointer, len: usize, - dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { // We're treating the buffer as a `u64`. let ty = ecx.machine.layouts.u64; // Check the size of slice, and return error only if the size of the slice < 8. if len < ty.size.bytes_usize() { - return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest); + return finish.call(ecx, Err(ErrorKind::InvalidInput.into())); } // Turn the pointer into a place at the right type. let buf_place = ecx.ptr_to_mplace_unaligned(ptr, ty); - eventfd_read(buf_place, dest, self, ecx) + eventfd_read(buf_place, self, ecx, finish) } /// A write call adds the 8-byte integer value supplied in @@ -84,20 +84,20 @@ impl FileDescription for EventFd { _communicate_allowed: bool, ptr: Pointer, len: usize, - dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { // We're treating the buffer as a `u64`. let ty = ecx.machine.layouts.u64; // Check the size of slice, and return error only if the size of the slice < 8. if len < ty.layout.size.bytes_usize() { - return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest); + return finish.call(ecx, Err(ErrorKind::InvalidInput.into())); } // Turn the pointer into a place at the right type. let buf_place = ecx.ptr_to_mplace_unaligned(ptr, ty); - eventfd_write(buf_place, dest, self, ecx) + eventfd_write(buf_place, self, ecx, finish) } fn as_unix(&self) -> &dyn UnixFileDescription { @@ -183,15 +183,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// else just add the user-supplied value to current counter. fn eventfd_write<'tcx>( buf_place: MPlaceTy<'tcx>, - dest: &MPlaceTy<'tcx>, eventfd: FileDescriptionRef, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { // Figure out which value we should add. let num = ecx.read_scalar(&buf_place)?.to_u64()?; // u64::MAX as input is invalid because the maximum value of counter is u64::MAX - 1. if num == u64::MAX { - return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest); + return finish.call(ecx, Err(ErrorKind::InvalidInput.into())); } match eventfd.counter.get().checked_add(num) { @@ -219,16 +219,14 @@ fn eventfd_write<'tcx>( ecx.check_and_update_readiness(eventfd)?; // Return how many bytes we consumed from the user-provided buffer. - return ecx.write_int(buf_place.layout.size.bytes(), dest); + return finish.call(ecx, Ok(buf_place.layout.size.bytes_usize())); } None | Some(u64::MAX) => { // We can't update the state, so we have to block. if eventfd.is_nonblock { - return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest); + return finish.call(ecx, Err(ErrorKind::WouldBlock.into())); } - let dest = dest.clone(); - eventfd.blocked_write_tid.borrow_mut().push(ecx.active_thread()); let weak_eventfd = FileDescriptionRef::downgrade(&eventfd); @@ -239,7 +237,7 @@ fn eventfd_write<'tcx>( @capture<'tcx> { num: u64, buf_place: MPlaceTy<'tcx>, - dest: MPlaceTy<'tcx>, + finish: DynMachineCallback<'tcx, Result>, weak_eventfd: WeakFileDescriptionRef, } |this, unblock: UnblockKind| { @@ -247,7 +245,7 @@ fn eventfd_write<'tcx>( // When we get unblocked, try again. We know the ref is still valid, // otherwise there couldn't be a `write` that unblocks us. let eventfd_ref = weak_eventfd.upgrade().unwrap(); - eventfd_write(buf_place, &dest, eventfd_ref, this) + eventfd_write(buf_place, eventfd_ref, this, finish) } ), ); @@ -260,9 +258,9 @@ fn eventfd_write<'tcx>( /// else just return the current counter value to the caller and set the counter to 0. fn eventfd_read<'tcx>( buf_place: MPlaceTy<'tcx>, - dest: &MPlaceTy<'tcx>, eventfd: FileDescriptionRef, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { // Set counter to 0, get old value. let counter = eventfd.counter.replace(0); @@ -270,9 +268,8 @@ fn eventfd_read<'tcx>( // Block when counter == 0. if counter == 0 { if eventfd.is_nonblock { - return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest); + return finish.call(ecx, Err(ErrorKind::WouldBlock.into())); } - let dest = dest.clone(); eventfd.blocked_read_tid.borrow_mut().push(ecx.active_thread()); @@ -283,7 +280,7 @@ fn eventfd_read<'tcx>( callback!( @capture<'tcx> { buf_place: MPlaceTy<'tcx>, - dest: MPlaceTy<'tcx>, + finish: DynMachineCallback<'tcx, Result>, weak_eventfd: WeakFileDescriptionRef, } |this, unblock: UnblockKind| { @@ -291,7 +288,7 @@ fn eventfd_read<'tcx>( // When we get unblocked, try again. We know the ref is still valid, // otherwise there couldn't be a `write` that unblocks us. let eventfd_ref = weak_eventfd.upgrade().unwrap(); - eventfd_read(buf_place, &dest, eventfd_ref, this) + eventfd_read(buf_place, eventfd_ref, this, finish) } ), ); @@ -317,7 +314,7 @@ fn eventfd_read<'tcx>( ecx.check_and_update_readiness(eventfd)?; // Tell userspace how many bytes we put into the buffer. - return ecx.write_int(buf_place.layout.size.bytes(), dest); + return finish.call(ecx, Ok(buf_place.layout.size.bytes_usize())); } interp_ok(()) } diff --git a/src/tools/miri/src/shims/unix/linux_like/sync.rs b/src/tools/miri/src/shims/unix/linux_like/sync.rs index 51124fb2a003b..280bee4800fe5 100644 --- a/src/tools/miri/src/shims/unix/linux_like/sync.rs +++ b/src/tools/miri/src/shims/unix/linux_like/sync.rs @@ -1,5 +1,5 @@ use crate::concurrency::sync::FutexRef; -use crate::helpers::check_min_arg_count; +use crate::helpers::check_min_vararg_count; use crate::*; struct LinuxFutex { @@ -10,7 +10,7 @@ struct LinuxFutex { /// `args` is the arguments *including* the syscall number. pub fn futex<'tcx>( ecx: &mut MiriInterpCx<'tcx>, - args: &[OpTy<'tcx>], + varargs: &[OpTy<'tcx>], dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { // The amount of arguments used depends on the type of futex operation. @@ -21,7 +21,7 @@ pub fn futex<'tcx>( // may or may not be left out from the `syscall()` call. // Therefore we don't use `check_arg_count` here, but only check for the // number of arguments to fall within a range. - let [_, addr, op, val] = check_min_arg_count("`syscall(SYS_futex, ...)`", args)?; + let [addr, op, val] = check_min_vararg_count("`syscall(SYS_futex, ...)`", varargs)?; // The first three arguments (after the syscall number itself) are the same to all futex operations: // (int *addr, int op, int val). @@ -55,14 +55,16 @@ pub fn futex<'tcx>( let wait_bitset = op & !futex_realtime == futex_wait_bitset; let (timeout, bitset) = if wait_bitset { - let [_, _, _, _, timeout, uaddr2, bitset] = - check_min_arg_count("`syscall(SYS_futex, FUTEX_WAIT_BITSET, ...)`", args)?; + let [_, _, _, timeout, uaddr2, bitset] = check_min_vararg_count( + "`syscall(SYS_futex, FUTEX_WAIT_BITSET, ...)`", + varargs, + )?; let _timeout = ecx.read_pointer(timeout)?; let _uaddr2 = ecx.read_pointer(uaddr2)?; (timeout, ecx.read_scalar(bitset)?.to_u32()?) } else { - let [_, _, _, _, timeout] = - check_min_arg_count("`syscall(SYS_futex, FUTEX_WAIT, ...)`", args)?; + let [_, _, _, timeout] = + check_min_vararg_count("`syscall(SYS_futex, FUTEX_WAIT, ...)`", varargs)?; (timeout, u32::MAX) }; @@ -156,14 +158,24 @@ pub fn futex<'tcx>( .futex .clone(); + let dest = dest.clone(); ecx.futex_wait( futex_ref, bitset, timeout, - Scalar::from_target_isize(0, ecx), // retval_succ - Scalar::from_target_isize(-1, ecx), // retval_timeout - dest.clone(), - LibcError("ETIMEDOUT"), // errno_timeout + callback!( + @capture<'tcx> { + dest: MPlaceTy<'tcx>, + } + |ecx, unblock: UnblockKind| match unblock { + UnblockKind::Ready => { + ecx.write_int(0, &dest) + } + UnblockKind::TimedOut => { + ecx.set_last_error_and_return(LibcError("ETIMEDOUT"), &dest) + } + } + ), ); } else { // The futex value doesn't match the expected value, so we return failure @@ -190,8 +202,10 @@ pub fn futex<'tcx>( let futex_ref = futex_ref.futex.clone(); let bitset = if op == futex_wake_bitset { - let [_, _, _, _, timeout, uaddr2, bitset] = - check_min_arg_count("`syscall(SYS_futex, FUTEX_WAKE_BITSET, ...)`", args)?; + let [_, _, _, timeout, uaddr2, bitset] = check_min_vararg_count( + "`syscall(SYS_futex, FUTEX_WAKE_BITSET, ...)`", + varargs, + )?; let _timeout = ecx.read_pointer(timeout)?; let _uaddr2 = ecx.read_pointer(uaddr2)?; ecx.read_scalar(bitset)?.to_u32()? @@ -205,16 +219,8 @@ pub fn futex<'tcx>( // will see the latest value on addr which could be changed by our caller // before doing the syscall. ecx.atomic_fence(AtomicFenceOrd::SeqCst)?; - let mut n = 0; - #[expect(clippy::arithmetic_side_effects)] - for _ in 0..val { - if ecx.futex_wake(&futex_ref, bitset)? { - n += 1; - } else { - break; - } - } - ecx.write_scalar(Scalar::from_target_isize(n, ecx), dest)?; + let woken = ecx.futex_wake(&futex_ref, bitset, val.try_into().unwrap())?; + ecx.write_scalar(Scalar::from_target_isize(woken.try_into().unwrap(), ecx), dest)?; } op => throw_unsup_format!("Miri does not support `futex` syscall with op={}", op), } diff --git a/src/tools/miri/src/shims/unix/linux_like/syscall.rs b/src/tools/miri/src/shims/unix/linux_like/syscall.rs index 5fb262e176f0b..22c6dc975070b 100644 --- a/src/tools/miri/src/shims/unix/linux_like/syscall.rs +++ b/src/tools/miri/src/shims/unix/linux_like/syscall.rs @@ -2,7 +2,7 @@ use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::{Conv, FnAbi}; -use crate::helpers::check_min_arg_count; +use crate::helpers::check_min_vararg_count; use crate::shims::unix::linux_like::eventfd::EvalContextExt as _; use crate::shims::unix::linux_like::sync::futex; use crate::*; @@ -14,9 +14,7 @@ pub fn syscall<'tcx>( args: &[OpTy<'tcx>], dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { - // We do not use `check_shim` here because `syscall` is variadic. The argument - // count is checked bellow. - ecx.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?; + let ([op], varargs) = ecx.check_shim_variadic(abi, Conv::C, link_name, args)?; // The syscall variadic function is legal to call with more arguments than needed, // extra arguments are simply ignored. The important check is that when we use an // argument, we have to also check all arguments *before* it to ensure that they @@ -26,14 +24,13 @@ pub fn syscall<'tcx>( let sys_futex = ecx.eval_libc("SYS_futex").to_target_usize(ecx)?; let sys_eventfd2 = ecx.eval_libc("SYS_eventfd2").to_target_usize(ecx)?; - let [op] = check_min_arg_count("syscall", args)?; match ecx.read_target_usize(op)? { // `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)` // is called if a `HashMap` is created the regular way (e.g. HashMap). num if num == sys_getrandom => { // Used by getrandom 0.1 // The first argument is the syscall id, so skip over it. - let [_, ptr, len, flags] = check_min_arg_count("syscall(SYS_getrandom, ...)", args)?; + let [ptr, len, flags] = check_min_vararg_count("syscall(SYS_getrandom, ...)", varargs)?; let ptr = ecx.read_pointer(ptr)?; let len = ecx.read_target_usize(len)?; @@ -47,10 +44,10 @@ pub fn syscall<'tcx>( } // `futex` is used by some synchronization primitives. num if num == sys_futex => { - futex(ecx, args, dest)?; + futex(ecx, varargs, dest)?; } num if num == sys_eventfd2 => { - let [_, initval, flags] = check_min_arg_count("syscall(SYS_evetfd2, ...)", args)?; + let [initval, flags] = check_min_vararg_count("syscall(SYS_evetfd2, ...)", varargs)?; let result = ecx.eventfd(initval, flags)?; ecx.write_int(result.to_i32()?, dest)?; diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index 85c963774a159..918fd8dd52dfd 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -2,13 +2,20 @@ use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::{Conv, FnAbi}; -use super::sync::EvalContextExt as _; -use crate::helpers::check_min_arg_count; +use super::sync::{EvalContextExt as _, MacOsFutexTimeout}; use crate::shims::unix::*; use crate::*; -pub fn is_dyn_sym(_name: &str) -> bool { - false +pub fn is_dyn_sym(name: &str) -> bool { + match name { + // These only became available with macOS 11.0, so std looks them up dynamically. + "os_sync_wait_on_address" + | "os_sync_wait_on_address_with_deadline" + | "os_sync_wait_on_address_with_timeout" + | "os_sync_wake_by_address_any" + | "os_sync_wake_by_address_all" => true, + _ => false, + } } impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} @@ -40,17 +47,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "stat" | "stat64" | "stat$INODE64" => { let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?; - let result = this.macos_fbsd_solaris_stat(path, buf)?; + let result = this.macos_fbsd_solarish_stat(path, buf)?; this.write_scalar(result, dest)?; } "lstat" | "lstat64" | "lstat$INODE64" => { let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?; - let result = this.macos_fbsd_solaris_lstat(path, buf)?; + let result = this.macos_fbsd_solarish_lstat(path, buf)?; this.write_scalar(result, dest)?; } "fstat" | "fstat64" | "fstat$INODE64" => { let [fd, buf] = this.check_shim(abi, Conv::C, link_name, args)?; - let result = this.macos_fbsd_solaris_fstat(fd, buf)?; + let result = this.macos_fbsd_solarish_fstat(fd, buf)?; this.write_scalar(result, dest)?; } "opendir$INODE64" => { @@ -69,10 +76,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "ioctl" => { - // `ioctl` is variadic. The argument count is checked based on the first argument - // in `this.ioctl()`, so we do not use `check_shim` here. - this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?; - let result = this.ioctl(args)?; + let ([fd_num, cmd], varargs) = + this.check_shim_variadic(abi, Conv::C, link_name, args)?; + let result = this.ioctl(fd_num, cmd, varargs)?; this.write_scalar(result, dest)?; } @@ -216,6 +222,58 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } + // Futex primitives + "os_sync_wait_on_address" => { + let [addr_op, value_op, size_op, flags_op] = + this.check_shim(abi, Conv::C, link_name, args)?; + this.os_sync_wait_on_address( + addr_op, + value_op, + size_op, + flags_op, + MacOsFutexTimeout::None, + dest, + )?; + } + "os_sync_wait_on_address_with_deadline" => { + let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] = + this.check_shim(abi, Conv::C, link_name, args)?; + this.os_sync_wait_on_address( + addr_op, + value_op, + size_op, + flags_op, + MacOsFutexTimeout::Absolute { clock_op, timeout_op }, + dest, + )?; + } + "os_sync_wait_on_address_with_timeout" => { + let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] = + this.check_shim(abi, Conv::C, link_name, args)?; + this.os_sync_wait_on_address( + addr_op, + value_op, + size_op, + flags_op, + MacOsFutexTimeout::Relative { clock_op, timeout_op }, + dest, + )?; + } + "os_sync_wake_by_address_any" => { + let [addr_op, size_op, flags_op] = + this.check_shim(abi, Conv::C, link_name, args)?; + this.os_sync_wake_by_address( + addr_op, size_op, flags_op, /* all */ false, dest, + )?; + } + "os_sync_wake_by_address_all" => { + let [addr_op, size_op, flags_op] = + this.check_shim(abi, Conv::C, link_name, args)?; + this.os_sync_wake_by_address( + addr_op, size_op, flags_op, /* all */ true, dest, + )?; + } + "os_unfair_lock_lock" => { let [lock_op] = this.check_shim(abi, Conv::C, link_name, args)?; this.os_unfair_lock_lock(lock_op)?; @@ -243,12 +301,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(EmulateItemResult::NeedsReturn) } - fn ioctl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> { + fn ioctl( + &mut self, + fd_num: &OpTy<'tcx>, + cmd: &OpTy<'tcx>, + _varargs: &[OpTy<'tcx>], + ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); let fioclex = this.eval_libc_u64("FIOCLEX"); - let [fd_num, cmd] = check_min_arg_count("ioctl", args)?; let fd_num = this.read_scalar(fd_num)?.to_i32()?; let cmd = this.read_scalar(cmd)?.to_u64()?; diff --git a/src/tools/miri/src/shims/unix/macos/sync.rs b/src/tools/miri/src/shims/unix/macos/sync.rs index 330c64f06a3e6..6ba52f2f57e40 100644 --- a/src/tools/miri/src/shims/unix/macos/sync.rs +++ b/src/tools/miri/src/shims/unix/macos/sync.rs @@ -10,8 +10,12 @@ //! and we do not detect copying of the lock, but macOS doesn't guarantee anything //! in that case either. +use std::cell::Cell; +use std::time::Duration; + use rustc_abi::Size; +use crate::concurrency::sync::FutexRef; use crate::*; #[derive(Clone)] @@ -20,6 +24,26 @@ enum MacOsUnfairLock { Active { mutex_ref: MutexRef }, } +pub enum MacOsFutexTimeout<'a, 'tcx> { + None, + Relative { clock_op: &'a OpTy<'tcx>, timeout_op: &'a OpTy<'tcx> }, + Absolute { clock_op: &'a OpTy<'tcx>, timeout_op: &'a OpTy<'tcx> }, +} + +/// Metadata for a macOS futex. +/// +/// Since macOS 11.0, Apple has exposed the previously private futex API consisting +/// of `os_sync_wait_on_address` (and friends) and `os_sync_wake_by_address_{any, all}`. +/// These work with different value sizes and flags, which are validated to be consistent. +/// This structure keeps track of both the futex queue and these values. +struct MacOsFutex { + futex: FutexRef, + /// The size in bytes of the atomic primitive underlying this futex. + size: Cell, + /// Whether the futex is shared across process boundaries. + shared: Cell, +} + impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {} trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { fn os_unfair_lock_get_data<'a>( @@ -30,7 +54,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { 'tcx: 'a, { let this = self.eval_context_mut(); - let lock = this.deref_pointer(lock_ptr)?; + let lock = this.deref_pointer_as(lock_ptr, this.libc_ty_layout("os_unfair_lock_s"))?; this.lazy_sync_get_data( &lock, Size::ZERO, // offset for init tracking @@ -54,6 +78,198 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + /// Implements [`os_sync_wait_on_address`], [`os_sync_wait_on_address_with_deadline`] + /// and [`os_sync_wait_on_address_with_timeout`]. + /// + /// [`os_sync_wait_on_address`]: https://developer.apple.com/documentation/os/os_sync_wait_on_address?language=objc + /// [`os_sync_wait_on_address_with_deadline`]: https://developer.apple.com/documentation/os/os_sync_wait_on_address_with_deadline?language=objc + /// [`os_sync_wait_on_address_with_timeout`]: https://developer.apple.com/documentation/os/os_sync_wait_on_address_with_timeout?language=objc + fn os_sync_wait_on_address( + &mut self, + addr_op: &OpTy<'tcx>, + value_op: &OpTy<'tcx>, + size_op: &OpTy<'tcx>, + flags_op: &OpTy<'tcx>, + timeout: MacOsFutexTimeout<'_, 'tcx>, + dest: &MPlaceTy<'tcx>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let none = this.eval_libc_u32("OS_SYNC_WAIT_ON_ADDRESS_NONE"); + let shared = this.eval_libc_u32("OS_SYNC_WAIT_ON_ADDRESS_SHARED"); + let absolute_clock = this.eval_libc_u32("OS_CLOCK_MACH_ABSOLUTE_TIME"); + + let ptr = this.read_pointer(addr_op)?; + let value = this.read_scalar(value_op)?.to_u64()?; + let size = this.read_target_usize(size_op)?; + let flags = this.read_scalar(flags_op)?.to_u32()?; + + let clock_timeout = match timeout { + MacOsFutexTimeout::None => None, + MacOsFutexTimeout::Relative { clock_op, timeout_op } => { + let clock = this.read_scalar(clock_op)?.to_u32()?; + let timeout = this.read_scalar(timeout_op)?.to_u64()?; + Some((clock, TimeoutAnchor::Relative, timeout)) + } + MacOsFutexTimeout::Absolute { clock_op, timeout_op } => { + let clock = this.read_scalar(clock_op)?.to_u32()?; + let timeout = this.read_scalar(timeout_op)?.to_u64()?; + Some((clock, TimeoutAnchor::Absolute, timeout)) + } + }; + + // Perform validation of the arguments. + let addr = ptr.addr().bytes(); + if addr == 0 + || !matches!(size, 4 | 8) + || !addr.is_multiple_of(size) + || (flags != none && flags != shared) + || clock_timeout + .is_some_and(|(clock, _, timeout)| clock != absolute_clock || timeout == 0) + { + this.set_last_error_and_return(LibcError("EINVAL"), dest)?; + return interp_ok(()); + } + + let is_shared = flags == shared; + let timeout = clock_timeout.map(|(_, anchor, timeout)| { + // The only clock that is currenlty supported is the monotonic clock. + // While the deadline argument of `os_sync_wait_on_address_with_deadline` + // is actually not in nanoseconds but in the units of `mach_current_time`, + // the two are equivalent in miri. + (TimeoutClock::Monotonic, anchor, Duration::from_nanos(timeout)) + }); + + // See the Linux futex implementation for why this fence exists. + this.atomic_fence(AtomicFenceOrd::SeqCst)?; + + let layout = this.machine.layouts.uint(Size::from_bytes(size)).unwrap(); + let futex_val = this + .read_scalar_atomic(&this.ptr_to_mplace(ptr, layout), AtomicReadOrd::Acquire)? + .to_bits(Size::from_bytes(size))?; + + let futex = this + .get_sync_or_init(ptr, |_| { + MacOsFutex { + futex: Default::default(), + size: Cell::new(size), + shared: Cell::new(is_shared), + } + }) + .unwrap(); + + // Detect mismatches between the flags and sizes used on this address + // by comparing it with the parameters used by the other waiters in + // the current list. If the list is currently empty, update those + // parameters. + if futex.futex.waiters() == 0 { + futex.size.set(size); + futex.shared.set(is_shared); + } else if futex.size.get() != size || futex.shared.get() != is_shared { + this.set_last_error_and_return(LibcError("EINVAL"), dest)?; + return interp_ok(()); + } + + if futex_val == value.into() { + // If the values are the same, we have to block. + let futex_ref = futex.futex.clone(); + let dest = dest.clone(); + this.futex_wait( + futex_ref.clone(), + u32::MAX, // bitset + timeout, + callback!( + @capture<'tcx> { + dest: MPlaceTy<'tcx>, + futex_ref: FutexRef, + } + |this, unblock: UnblockKind| { + match unblock { + UnblockKind::Ready => { + let remaining = futex_ref.waiters().try_into().unwrap(); + this.write_scalar(Scalar::from_i32(remaining), &dest) + } + UnblockKind::TimedOut => { + this.set_last_error_and_return(LibcError("ETIMEDOUT"), &dest) + } + } + } + ), + ); + } else { + // else retrieve the current number of waiters. + let waiters = futex.futex.waiters().try_into().unwrap(); + this.write_scalar(Scalar::from_i32(waiters), dest)?; + } + + interp_ok(()) + } + + /// Implements [`os_sync_wake_by_address_all`] and [`os_sync_wake_by_address_any`]. + /// + /// [`os_sync_wake_by_address_all`]: https://developer.apple.com/documentation/os/os_sync_wake_by_address_all?language=objc + /// [`os_sync_wake_by_address_any`]: https://developer.apple.com/documentation/os/os_sync_wake_by_address_any?language=objc + fn os_sync_wake_by_address( + &mut self, + addr_op: &OpTy<'tcx>, + size_op: &OpTy<'tcx>, + flags_op: &OpTy<'tcx>, + all: bool, + dest: &MPlaceTy<'tcx>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let none = this.eval_libc_u32("OS_SYNC_WAKE_BY_ADDRESS_NONE"); + let shared = this.eval_libc_u32("OS_SYNC_WAKE_BY_ADDRESS_SHARED"); + + let ptr = this.read_pointer(addr_op)?; + let size = this.read_target_usize(size_op)?; + let flags = this.read_scalar(flags_op)?.to_u32()?; + + // Perform validation of the arguments. + let addr = ptr.addr().bytes(); + if addr == 0 || !matches!(size, 4 | 8) || (flags != none && flags != shared) { + this.set_last_error_and_return(LibcError("EINVAL"), dest)?; + return interp_ok(()); + } + + let is_shared = flags == shared; + + let Some(futex) = this.get_sync_or_init(ptr, |_| { + MacOsFutex { + futex: Default::default(), + size: Cell::new(size), + shared: Cell::new(is_shared), + } + }) else { + // No AllocId, or no live allocation at that AllocId. Return an + // error code. (That seems nicer than silently doing something + // non-intuitive.) This means that if an address gets reused by a + // new allocation, we'll use an independent futex queue for this... + // that seems acceptable. + this.set_last_error_and_return(LibcError("ENOENT"), dest)?; + return interp_ok(()); + }; + + if futex.futex.waiters() == 0 { + this.set_last_error_and_return(LibcError("ENOENT"), dest)?; + return interp_ok(()); + // If there are waiters in the queue, they have all used the parameters + // stored in `futex` (we check this in `os_sync_wait_on_address` above). + // Detect mismatches between "our" parameters and the parameters used by + // the waiters and return an error in that case. + } else if futex.size.get() != size || futex.shared.get() != is_shared { + this.set_last_error_and_return(LibcError("EINVAL"), dest)?; + return interp_ok(()); + } + + let futex_ref = futex.futex.clone(); + + // See the Linux futex implementation for why this fence exists. + this.atomic_fence(AtomicFenceOrd::SeqCst)?; + this.futex_wake(&futex_ref, u32::MAX, if all { usize::MAX } else { 1 })?; + this.write_scalar(Scalar::from_i32(0), dest)?; + interp_ok(()) + } + fn os_unfair_lock_lock(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); diff --git a/src/tools/miri/src/shims/unix/mem.rs b/src/tools/miri/src/shims/unix/mem.rs index 2d5d3a6471ab9..aefeee6f7a3a3 100644 --- a/src/tools/miri/src/shims/unix/mem.rs +++ b/src/tools/miri/src/shims/unix/mem.rs @@ -116,7 +116,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { align, MiriMemoryKind::Mmap.into(), // mmap guarantees new mappings are zero-init. - AllocInit::Zero + AllocInit::Zero, )?; interp_ok(Scalar::from_pointer(ptr, this)) diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs index f94783a390722..21d4f41f48529 100644 --- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs @@ -87,17 +87,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // File related shims "stat" | "stat64" => { let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?; - let result = this.macos_fbsd_solaris_stat(path, buf)?; + let result = this.macos_fbsd_solarish_stat(path, buf)?; this.write_scalar(result, dest)?; } "lstat" | "lstat64" => { let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?; - let result = this.macos_fbsd_solaris_lstat(path, buf)?; + let result = this.macos_fbsd_solarish_lstat(path, buf)?; this.write_scalar(result, dest)?; } "fstat" | "fstat64" => { let [fd, buf] = this.check_shim(abi, Conv::C, link_name, args)?; - let result = this.macos_fbsd_solaris_fstat(fd, buf)?; + let result = this.macos_fbsd_solarish_fstat(fd, buf)?; this.write_scalar(result, dest)?; } "readdir" => { @@ -163,7 +163,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { throw_unsup_format!("pset_info is only supported with list==NULL"); } - let cpus = this.deref_pointer(cpus)?; + let cpus = this.deref_pointer_as(cpus, this.machine.layouts.u32)?; this.write_scalar(Scalar::from_u32(this.machine.num_cpus), &cpus)?; this.write_null(dest)?; } diff --git a/src/tools/miri/src/shims/unix/sync.rs b/src/tools/miri/src/shims/unix/sync.rs index 5b0a9398b4b6a..9f1fabfbf6494 100644 --- a/src/tools/miri/src/shims/unix/sync.rs +++ b/src/tools/miri/src/shims/unix/sync.rs @@ -170,7 +170,7 @@ fn mutex_create<'tcx>( mutex_ptr: &OpTy<'tcx>, kind: MutexKind, ) -> InterpResult<'tcx, PthreadMutex> { - let mutex = ecx.deref_pointer(mutex_ptr)?; + let mutex = ecx.deref_pointer_as(mutex_ptr, ecx.libc_ty_layout("pthread_mutex_t"))?; let id = ecx.machine.sync.mutex_create(); let data = PthreadMutex { mutex_ref: id, kind }; ecx.lazy_sync_init(&mutex, mutex_init_offset(ecx)?, data.clone())?; @@ -186,7 +186,7 @@ fn mutex_get_data<'tcx, 'a>( where 'tcx: 'a, { - let mutex = ecx.deref_pointer(mutex_ptr)?; + let mutex = ecx.deref_pointer_as(mutex_ptr, ecx.libc_ty_layout("pthread_mutex_t"))?; ecx.lazy_sync_get_data( &mutex, mutex_init_offset(ecx)?, @@ -265,7 +265,7 @@ fn rwlock_get_data<'tcx, 'a>( where 'tcx: 'a, { - let rwlock = ecx.deref_pointer(rwlock_ptr)?; + let rwlock = ecx.deref_pointer_as(rwlock_ptr, ecx.libc_ty_layout("pthread_rwlock_t"))?; ecx.lazy_sync_get_data( &rwlock, rwlock_init_offset(ecx)?, @@ -383,7 +383,7 @@ fn cond_create<'tcx>( cond_ptr: &OpTy<'tcx>, clock: ClockId, ) -> InterpResult<'tcx, PthreadCondvar> { - let cond = ecx.deref_pointer(cond_ptr)?; + let cond = ecx.deref_pointer_as(cond_ptr, ecx.libc_ty_layout("pthread_cond_t"))?; let id = ecx.machine.sync.condvar_create(); let data = PthreadCondvar { id, clock }; ecx.lazy_sync_init(&cond, cond_init_offset(ecx)?, data)?; @@ -397,7 +397,7 @@ fn cond_get_data<'tcx, 'a>( where 'tcx: 'a, { - let cond = ecx.deref_pointer(cond_ptr)?; + let cond = ecx.deref_pointer_as(cond_ptr, ecx.libc_ty_layout("pthread_cond_t"))?; ecx.lazy_sync_get_data( &cond, cond_init_offset(ecx)?, @@ -760,7 +760,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); let clock_id = condattr_get_clock_id(this, attr_op)?; - this.write_scalar(Scalar::from_i32(clock_id), &this.deref_pointer(clk_id_op)?)?; + this.write_scalar( + Scalar::from_i32(clock_id), + &this.deref_pointer_as(clk_id_op, this.libc_ty_layout("clockid_t"))?, + )?; interp_ok(()) } diff --git a/src/tools/miri/src/shims/unix/unnamed_socket.rs b/src/tools/miri/src/shims/unix/unnamed_socket.rs index 08515b815a900..e183bfdf0e137 100644 --- a/src/tools/miri/src/shims/unix/unnamed_socket.rs +++ b/src/tools/miri/src/shims/unix/unnamed_socket.rs @@ -5,9 +5,7 @@ use std::cell::{Cell, OnceCell, RefCell}; use std::collections::VecDeque; use std::io; -use std::io::{ErrorKind, Read}; - -use rustc_abi::Size; +use std::io::ErrorKind; use crate::concurrency::VClock; use crate::shims::files::{ @@ -92,10 +90,10 @@ impl FileDescription for AnonSocket { _communicate_allowed: bool, ptr: Pointer, len: usize, - dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { - anonsocket_read(self, len, ptr, dest, ecx) + anonsocket_read(self, ptr, len, ecx, finish) } fn write<'tcx>( @@ -103,10 +101,10 @@ impl FileDescription for AnonSocket { _communicate_allowed: bool, ptr: Pointer, len: usize, - dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { - anonsocket_write(self, ptr, len, dest, ecx) + anonsocket_write(self, ptr, len, ecx, finish) } fn as_unix(&self) -> &dyn UnixFileDescription { @@ -119,25 +117,25 @@ fn anonsocket_write<'tcx>( self_ref: FileDescriptionRef, ptr: Pointer, len: usize, - dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { // Always succeed on write size 0. // ("If count is zero and fd refers to a file other than a regular file, the results are not specified.") if len == 0 { - return ecx.return_write_success(0, dest); + return finish.call(ecx, Ok(0)); } // We are writing to our peer's readbuf. let Some(peer_fd) = self_ref.peer_fd().upgrade() else { // If the upgrade from Weak to Rc fails, it indicates that all read ends have been // closed. It is an error to write even if there would be space. - return ecx.set_last_error_and_return(ErrorKind::BrokenPipe, dest); + return finish.call(ecx, Err(ErrorKind::BrokenPipe.into())); }; let Some(writebuf) = &peer_fd.readbuf else { // Writing to the read end of a pipe. - return ecx.set_last_error_and_return(IoError::LibcError("EBADF"), dest); + return finish.call(ecx, Err(IoError::LibcError("EBADF"))); }; // Let's see if we can write. @@ -145,13 +143,12 @@ fn anonsocket_write<'tcx>( if available_space == 0 { if self_ref.is_nonblock { // Non-blocking socketpair with a full buffer. - return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest); + return finish.call(ecx, Err(ErrorKind::WouldBlock.into())); } else { self_ref.blocked_write_tid.borrow_mut().push(ecx.active_thread()); // Blocking socketpair with a full buffer. // Block the current thread; only keep a weak ref for this. let weak_self_ref = FileDescriptionRef::downgrade(&self_ref); - let dest = dest.clone(); ecx.block_thread( BlockReason::UnnamedSocket, None, @@ -160,14 +157,14 @@ fn anonsocket_write<'tcx>( weak_self_ref: WeakFileDescriptionRef, ptr: Pointer, len: usize, - dest: MPlaceTy<'tcx>, + finish: DynMachineCallback<'tcx, Result>, } |this, unblock: UnblockKind| { assert_eq!(unblock, UnblockKind::Ready); // If we got unblocked, then our peer successfully upgraded its weak // ref to us. That means we can also upgrade our weak ref. let self_ref = weak_self_ref.upgrade().unwrap(); - anonsocket_write(self_ref, ptr, len, &dest, this) + anonsocket_write(self_ref, ptr, len, this, finish) } ), ); @@ -180,9 +177,9 @@ fn anonsocket_write<'tcx>( writebuf.clock.join(clock); }); // Do full write / partial write based on the space available. - let actual_write_size = len.min(available_space); - let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?; - writebuf.buf.extend(&bytes[..actual_write_size]); + let write_size = len.min(available_space); + let actual_write_size = ecx.write_to_host(&mut writebuf.buf, write_size, ptr)?.unwrap(); + assert_eq!(actual_write_size, write_size); // Need to stop accessing peer_fd so that it can be notified. drop(writebuf); @@ -197,7 +194,7 @@ fn anonsocket_write<'tcx>( // The kernel does this even if the fd was already readable before, so we follow suit. ecx.check_and_update_readiness(peer_fd)?; - return ecx.return_write_success(actual_write_size, dest); + return finish.call(ecx, Ok(write_size)); } interp_ok(()) } @@ -205,14 +202,14 @@ fn anonsocket_write<'tcx>( /// Read from AnonSocket and return the number of bytes read. fn anonsocket_read<'tcx>( self_ref: FileDescriptionRef, - len: usize, ptr: Pointer, - dest: &MPlaceTy<'tcx>, + len: usize, ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { // Always succeed on read size 0. if len == 0 { - return ecx.return_read_success(ptr, &[], 0, dest); + return finish.call(ecx, Ok(0)); } let Some(readbuf) = &self_ref.readbuf else { @@ -225,43 +222,41 @@ fn anonsocket_read<'tcx>( if self_ref.peer_fd().upgrade().is_none() { // Socketpair with no peer and empty buffer. // 0 bytes successfully read indicates end-of-file. - return ecx.return_read_success(ptr, &[], 0, dest); + return finish.call(ecx, Ok(0)); } else if self_ref.is_nonblock { // Non-blocking socketpair with writer and empty buffer. // https://linux.die.net/man/2/read // EAGAIN or EWOULDBLOCK can be returned for socket, // POSIX.1-2001 allows either error to be returned for this case. // Since there is no ErrorKind for EAGAIN, WouldBlock is used. - return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest); + return finish.call(ecx, Err(ErrorKind::WouldBlock.into())); } else { self_ref.blocked_read_tid.borrow_mut().push(ecx.active_thread()); // Blocking socketpair with writer and empty buffer. // Block the current thread; only keep a weak ref for this. let weak_self_ref = FileDescriptionRef::downgrade(&self_ref); - let dest = dest.clone(); ecx.block_thread( BlockReason::UnnamedSocket, None, callback!( @capture<'tcx> { weak_self_ref: WeakFileDescriptionRef, - len: usize, ptr: Pointer, - dest: MPlaceTy<'tcx>, + len: usize, + finish: DynMachineCallback<'tcx, Result>, } |this, unblock: UnblockKind| { assert_eq!(unblock, UnblockKind::Ready); // If we got unblocked, then our peer successfully upgraded its weak // ref to us. That means we can also upgrade our weak ref. let self_ref = weak_self_ref.upgrade().unwrap(); - anonsocket_read(self_ref, len, ptr, &dest, this) + anonsocket_read(self_ref, ptr, len, this, finish) } ), ); } } else { // There's data to be read! - let mut bytes = vec![0; len]; let mut readbuf = readbuf.borrow_mut(); // Synchronize with all previous writes to this buffer. // FIXME: this over-synchronizes; a more precise approach would be to @@ -270,7 +265,7 @@ fn anonsocket_read<'tcx>( // Do full read / partial read based on the space available. // Conveniently, `read` exists on `VecDeque` and has exactly the desired behavior. - let actual_read_size = readbuf.buf.read(&mut bytes[..]).unwrap(); + let read_size = ecx.read_from_host(&mut readbuf.buf, len, ptr)?.unwrap(); // Need to drop before others can access the readbuf again. drop(readbuf); @@ -293,7 +288,7 @@ fn anonsocket_read<'tcx>( ecx.check_and_update_readiness(peer_fd)?; }; - return ecx.return_read_success(ptr, &bytes, actual_read_size, dest); + return finish.call(ecx, Ok(read_size)); } interp_ok(()) } @@ -362,7 +357,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let domain = this.read_scalar(domain)?.to_i32()?; let mut flags = this.read_scalar(type_)?.to_i32()?; let protocol = this.read_scalar(protocol)?.to_i32()?; - let sv = this.deref_pointer(sv)?; + // This is really a pointer to `[i32; 2]` but we use a ptr-to-first-element representation. + let sv = this.deref_pointer_as(sv, this.machine.layouts.i32)?; let mut is_sock_nonblock = false; diff --git a/src/tools/miri/src/shims/windows/env.rs b/src/tools/miri/src/shims/windows/env.rs index 72c1fb58023a8..1b2ccd99ef9f4 100644 --- a/src/tools/miri/src/shims/windows/env.rs +++ b/src/tools/miri/src/shims/windows/env.rs @@ -218,7 +218,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let token = this.read_target_isize(token)?; let buf = this.read_pointer(buf)?; - let size = this.deref_pointer(size)?; + let size = this.deref_pointer_as(size, this.machine.layouts.u32)?; if token != -4 { throw_unsup_format!( diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index 4462d025beada..fae6170a9e72c 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -266,7 +266,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Size::from_bytes(size), Align::from_bytes(align).unwrap(), MiriMemoryKind::WinHeap.into(), - init + init, )?; this.write_pointer(ptr, dest)?; } @@ -299,7 +299,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Size::from_bytes(size), Align::from_bytes(align).unwrap(), MiriMemoryKind::WinHeap.into(), - AllocInit::Uninit + AllocInit::Uninit, )?; this.write_pointer(new_ptr, dest)?; } @@ -335,7 +335,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Initialize with `0`. this.write_bytes_ptr( system_info.ptr(), - iter::repeat(0u8).take(system_info.layout.size.bytes_usize()), + iter::repeat_n(0u8, system_info.layout.size.bytes_usize()), )?; // Set selected fields. this.write_int_fields_named( @@ -523,7 +523,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let [handle, name_ptr] = this.check_shim(abi, sys_conv, link_name, args)?; let handle = this.read_scalar(handle)?; - let name_ptr = this.deref_pointer(name_ptr)?; // the pointer where we should store the ptr to the name + let name_ptr = this.deref_pointer_as(name_ptr, this.machine.layouts.mut_raw_ptr)?; // the pointer where we should store the ptr to the name let thread = match Handle::try_from_scalar(handle, this)? { Ok(Handle::Thread(thread)) => Ok(thread), @@ -725,7 +725,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "GetConsoleMode" if this.frame_in_std() => { let [console, mode] = this.check_shim(abi, sys_conv, link_name, args)?; this.read_target_isize(console)?; - this.deref_pointer(mode)?; + this.deref_pointer_as(mode, this.machine.layouts.u32)?; // Indicate an error. this.write_null(dest)?; } diff --git a/src/tools/miri/src/shims/windows/sync.rs b/src/tools/miri/src/shims/windows/sync.rs index 4001201bf678a..8d5ea7db9e496 100644 --- a/src/tools/miri/src/shims/windows/sync.rs +++ b/src/tools/miri/src/shims/windows/sync.rs @@ -29,7 +29,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { { let this = self.eval_context_mut(); - let init_once = this.deref_pointer(init_once_ptr)?; + let init_once = + this.deref_pointer_as(init_once_ptr, this.windows_ty_layout("INIT_ONCE"))?; let init_offset = Size::ZERO; this.lazy_sync_get_data( @@ -85,7 +86,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let id = this.init_once_get_data(init_once_op)?.id; let flags = this.read_scalar(flags_op)?.to_u32()?; - let pending_place = this.deref_pointer(pending_op)?; + // PBOOL is int* + let pending_place = this.deref_pointer_as(pending_op, this.machine.layouts.i32)?; let context = this.read_pointer(context_op)?; if flags != 0 { @@ -210,14 +212,27 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { .futex .clone(); + let dest = dest.clone(); this.futex_wait( futex_ref, u32::MAX, // bitset timeout, - Scalar::from_i32(1), // retval_succ - Scalar::from_i32(0), // retval_timeout - dest.clone(), - IoError::WindowsError("ERROR_TIMEOUT"), // errno_timeout + callback!( + @capture<'tcx> { + dest: MPlaceTy<'tcx> + } + |this, unblock: UnblockKind| { + match unblock { + UnblockKind::Ready => { + this.write_int(1, &dest) + } + UnblockKind::TimedOut => { + this.set_last_error(IoError::WindowsError("ERROR_TIMEOUT"))?; + this.write_int(0, &dest) + } + } + } + ), ); } @@ -242,7 +257,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; let futex_ref = futex_ref.futex.clone(); - this.futex_wake(&futex_ref, u32::MAX)?; + this.futex_wake(&futex_ref, u32::MAX, 1)?; interp_ok(()) } @@ -262,7 +277,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; let futex_ref = futex_ref.futex.clone(); - while this.futex_wake(&futex_ref, u32::MAX)? {} + this.futex_wake(&futex_ref, u32::MAX, usize::MAX)?; interp_ok(()) } diff --git a/src/tools/miri/src/shims/windows/thread.rs b/src/tools/miri/src/shims/windows/thread.rs index efc1c2286bcbc..5db554044227c 100644 --- a/src/tools/miri/src/shims/windows/thread.rs +++ b/src/tools/miri/src/shims/windows/thread.rs @@ -29,7 +29,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let thread = if this.ptr_is_null(this.read_pointer(thread_op)?)? { None } else { - let thread_info_place = this.deref_pointer(thread_op)?; + let thread_info_place = this.deref_pointer_as(thread_op, this.machine.layouts.u32)?; Some(thread_info_place) }; diff --git a/src/tools/miri/test_dependencies/Cargo.lock b/src/tools/miri/test_dependencies/Cargo.lock index 0a5e9f62dd9fb..af92f9d0dec49 100644 --- a/src/tools/miri/test_dependencies/Cargo.lock +++ b/src/tools/miri/test_dependencies/Cargo.lock @@ -105,6 +105,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", +] + [[package]] name = "gimli" version = "0.29.0" @@ -178,6 +190,7 @@ dependencies = [ "cfg-if", "getrandom 0.1.16", "getrandom 0.2.15", + "getrandom 0.3.1", "libc", "num_cpus", "page_size", @@ -359,6 +372,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -507,3 +529,12 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] diff --git a/src/tools/miri/test_dependencies/Cargo.toml b/src/tools/miri/test_dependencies/Cargo.toml index e7eff46afca52..78dddaf11dff5 100644 --- a/src/tools/miri/test_dependencies/Cargo.toml +++ b/src/tools/miri/test_dependencies/Cargo.toml @@ -8,13 +8,14 @@ version = "0.1.0" edition = "2021" [dependencies] -# all dependencies (and their transitive ones) listed here can be used in `tests/`. +# all dependencies (and their transitive ones) listed here can be used in `tests/*-dep`. libc = "0.2" num_cpus = "1.10.1" cfg-if = "1" getrandom_01 = { package = "getrandom", version = "0.1" } getrandom_02 = { package = "getrandom", version = "0.2", features = ["js"] } +getrandom_03 = { package = "getrandom", version = "0.3" } [target.'cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))'.dependencies] tempfile = "3" diff --git a/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs b/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs index 4b6f344a78e28..457f32e55446e 100644 --- a/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs +++ b/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs @@ -8,5 +8,5 @@ fn main() { fn test_file_open_missing_needed_mode() { let name = b"missing_arg.txt\0"; let name_ptr = name.as_ptr().cast::(); - let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) }; //~ ERROR: Undefined Behavior: incorrect number of arguments for `open(pathname, O_CREAT, ...)`: got 2, expected at least 3 + let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) }; //~ ERROR: Undefined Behavior: not enough variadic arguments } diff --git a/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.stderr b/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.stderr index ca9e3c6c4be40..f48b75460d4f9 100644 --- a/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.stderr +++ b/src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: incorrect number of arguments for `open(pathname, O_CREAT, ...)`: got 2, expected at least 3 +error: Undefined Behavior: not enough variadic arguments for `open(pathname, O_CREAT, ...)`: got 0, expected at least 1 --> tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs:LL:CC | -LL | ... { libc::open(name_ptr, libc::O_CREAT) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect number of arguments for `open(pathname, O_CREAT, ...)`: got 2, expected at least 3 +LL | let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not enough variadic arguments for `open(pathname, O_CREAT, ...)`: got 0, expected at least 1 | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs index a20539ee7c708..c557c35c9dea8 100644 --- a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs @@ -1,10 +1,13 @@ extern "Rust" { - fn miri_get_backtrace(flags: u64) -> Box<[*mut ()]>; + fn miri_backtrace_size(flags: u64) -> usize; + fn miri_get_backtrace(flags: u64, buf: *mut *mut ()); fn miri_resolve_frame(ptr: *mut (), flags: u64); } fn main() { - let frames = unsafe { miri_get_backtrace(0) }; + let size = unsafe { miri_backtrace_size(0) }; + let mut frames = vec![std::ptr::null_mut(); size]; + unsafe { miri_get_backtrace(1, frames.as_mut_ptr()) }; for frame in frames.iter() { unsafe { miri_resolve_frame(*frame, 0); //~ ERROR: Undefined Behavior: bad declaration of miri_resolve_frame - should return a struct with 5 fields diff --git a/src/tools/miri/tests/fail/shims/non_vararg_signature_mismatch.rs b/src/tools/miri/tests/fail/shims/non_vararg_signature_mismatch.rs new file mode 100644 index 0000000000000..b920e6795f90d --- /dev/null +++ b/src/tools/miri/tests/fail/shims/non_vararg_signature_mismatch.rs @@ -0,0 +1,17 @@ +//@ignore-target: windows # File handling is not implemented yet +//@compile-flags: -Zmiri-disable-isolation +use std::ffi::{CString, OsStr, c_char, c_int}; +use std::os::unix::ffi::OsStrExt; + +// Declare a variadic function as non-variadic. +extern "C" { + fn open(path: *const c_char, oflag: c_int) -> c_int; +} + +fn main() { + let c_path = CString::new(OsStr::new("./text").as_bytes()).expect("CString::new failed"); + let _fd = unsafe { + open(c_path.as_ptr(), /* value does not matter */ 0) + //~^ ERROR: calling a variadic function with a non-variadic caller-side signature + }; +} diff --git a/src/tools/miri/tests/fail/shims/non_vararg_signature_mismatch.stderr b/src/tools/miri/tests/fail/shims/non_vararg_signature_mismatch.stderr new file mode 100644 index 0000000000000..43813af9a3c61 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/non_vararg_signature_mismatch.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calling a variadic function with a non-variadic caller-side signature + --> tests/fail/shims/non_vararg_signature_mismatch.rs:LL:CC + | +LL | open(c_path.as_ptr(), /* value does not matter */ 0) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ calling a variadic function with a non-variadic caller-side signature + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at tests/fail/shims/non_vararg_signature_mismatch.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/shims/wrong_fixed_arg_count.rs b/src/tools/miri/tests/fail/shims/wrong_fixed_arg_count.rs new file mode 100644 index 0000000000000..e9cb69418d22f --- /dev/null +++ b/src/tools/miri/tests/fail/shims/wrong_fixed_arg_count.rs @@ -0,0 +1,16 @@ +//@ignore-target: windows # File handling is not implemented yet +//@compile-flags: -Zmiri-disable-isolation +use std::ffi::{CString, OsStr, c_char, c_int}; +use std::os::unix::ffi::OsStrExt; + +extern "C" { + fn open(path: *const c_char, ...) -> c_int; +} + +fn main() { + let c_path = CString::new(OsStr::new("./text").as_bytes()).expect("CString::new failed"); + let _fd = unsafe { + open(c_path.as_ptr(), /* value does not matter */ 0) + //~^ ERROR: incorrect number of fixed arguments for variadic function + }; +} diff --git a/src/tools/miri/tests/fail/shims/wrong_fixed_arg_count.stderr b/src/tools/miri/tests/fail/shims/wrong_fixed_arg_count.stderr new file mode 100644 index 0000000000000..12e464813cfff --- /dev/null +++ b/src/tools/miri/tests/fail/shims/wrong_fixed_arg_count.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: incorrect number of fixed arguments for variadic function `open`: got 1, expected 2 + --> tests/fail/shims/wrong_fixed_arg_count.rs:LL:CC + | +LL | open(c_path.as_ptr(), /* value does not matter */ 0) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect number of fixed arguments for variadic function `open`: got 1, expected 2 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at tests/fail/shims/wrong_fixed_arg_count.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/pass-dep/concurrency/apple-futex.rs b/src/tools/miri/tests/pass-dep/concurrency/apple-futex.rs new file mode 100644 index 0000000000000..becb90eb92307 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/concurrency/apple-futex.rs @@ -0,0 +1,276 @@ +//@only-target: darwin +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::time::{Duration, Instant}; +use std::{io, ptr, thread}; + +fn wake_nobody() { + let futex = 0; + + // Wake 1 waiter. Expect ENOENT as nobody is waiting. + unsafe { + assert_eq!( + libc::os_sync_wake_by_address_any( + ptr::from_ref(&futex).cast_mut().cast(), + size_of::(), + libc::OS_SYNC_WAKE_BY_ADDRESS_NONE + ), + -1 + ); + assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ENOENT); + } +} + +fn wake_dangling() { + let futex = Box::new(0); + let ptr = ptr::from_ref(&futex).cast_mut().cast(); + drop(futex); + + // Expect error since this is now "unmapped" memory. + unsafe { + assert_eq!( + libc::os_sync_wake_by_address_any( + ptr, + size_of::(), + libc::OS_SYNC_WAKE_BY_ADDRESS_NONE + ), + -1 + ); + assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ENOENT); + } +} + +fn wait_wrong_val() { + let futex: i32 = 123; + + // Only wait if the futex value is 456. + unsafe { + assert_eq!( + libc::os_sync_wait_on_address( + ptr::from_ref(&futex).cast_mut().cast(), + 456, + size_of::(), + libc::OS_SYNC_WAIT_ON_ADDRESS_NONE + ), + 0, + ); + } +} + +fn wait_timeout() { + let start = Instant::now(); + + let futex: i32 = 123; + + // Wait for 200ms, with nobody waking us up early. + unsafe { + assert_eq!( + libc::os_sync_wait_on_address_with_timeout( + ptr::from_ref(&futex).cast_mut().cast(), + 123, + size_of::(), + libc::OS_SYNC_WAIT_ON_ADDRESS_NONE, + libc::OS_CLOCK_MACH_ABSOLUTE_TIME, + 200_000_000, + ), + -1, + ); + assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT); + } + + assert!((200..1000).contains(&start.elapsed().as_millis())); +} + +fn wait_absolute_timeout() { + let start = Instant::now(); + + // Get the current monotonic timestamp. + #[allow(deprecated)] + let mut deadline = unsafe { libc::mach_absolute_time() }; + + // Add 200ms. + // What we should be doing here is call `mach_timebase_info` to determine the + // unit used for `deadline`, but we know what Miri returns for that function: + // the unit is nanoseconds. + deadline += 200_000_000; + + let futex: i32 = 123; + + // Wait for 200ms from now, with nobody waking us up early. + unsafe { + assert_eq!( + libc::os_sync_wait_on_address_with_deadline( + ptr::from_ref(&futex).cast_mut().cast(), + 123, + size_of::(), + libc::OS_SYNC_WAIT_ON_ADDRESS_NONE, + libc::OS_CLOCK_MACH_ABSOLUTE_TIME, + deadline, + ), + -1, + ); + assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT); + } + + assert!((200..1000).contains(&start.elapsed().as_millis())); +} + +fn wait_wake() { + let start = Instant::now(); + + static mut FUTEX: i32 = 0; + + let t = thread::spawn(move || { + thread::sleep(Duration::from_millis(200)); + unsafe { + assert_eq!( + libc::os_sync_wake_by_address_any( + (&raw const FUTEX).cast_mut().cast(), + size_of::(), + libc::OS_SYNC_WAKE_BY_ADDRESS_NONE, + ), + 0, + ); + } + }); + + unsafe { + assert_eq!( + libc::os_sync_wait_on_address( + (&raw const FUTEX).cast_mut().cast(), + 0, + size_of::(), + libc::OS_SYNC_WAIT_ON_ADDRESS_NONE, + ), + 0, + ); + } + + // When running this in stress-gc mode, things can take quite long. + // So the timeout is 3000 ms. + assert!((200..3000).contains(&start.elapsed().as_millis())); + t.join().unwrap(); +} + +fn wait_wake_multiple() { + let val = 0i32; + let futex = &val; + + thread::scope(|s| { + // Spawn some threads and make them wait on the futex. + for i in 0..4 { + s.spawn(move || unsafe { + assert_eq!( + libc::os_sync_wait_on_address( + ptr::from_ref(futex).cast_mut().cast(), + 0, + size_of::(), + libc::OS_SYNC_WAIT_ON_ADDRESS_NONE, + ), + // The last two threads will be woken at the same time, + // but for the first two threads the remaining number + // of waiters should be strictly decreasing. + if i < 2 { 3 - i } else { 0 }, + ); + }); + + thread::yield_now(); + } + + // Wake the threads up again. + unsafe { + assert_eq!( + libc::os_sync_wake_by_address_any( + ptr::from_ref(futex).cast_mut().cast(), + size_of::(), + libc::OS_SYNC_WAKE_BY_ADDRESS_NONE, + ), + 0 + ); + + assert_eq!( + libc::os_sync_wake_by_address_any( + ptr::from_ref(futex).cast_mut().cast(), + size_of::(), + libc::OS_SYNC_WAKE_BY_ADDRESS_NONE, + ), + 0 + ); + + // Wake both remaining threads at the same time. + assert_eq!( + libc::os_sync_wake_by_address_all( + ptr::from_ref(futex).cast_mut().cast(), + size_of::(), + libc::OS_SYNC_WAKE_BY_ADDRESS_NONE, + ), + 0 + ); + } + }) +} + +fn param_mismatch() { + let futex = 0; + thread::scope(|s| { + s.spawn(|| { + unsafe { + assert_eq!( + libc::os_sync_wait_on_address_with_timeout( + ptr::from_ref(&futex).cast_mut().cast(), + 0, + size_of::(), + libc::OS_SYNC_WAIT_ON_ADDRESS_NONE, + libc::OS_CLOCK_MACH_ABSOLUTE_TIME, + 400_000_000, + ), + -1, + ); + assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT); + } + }); + + s.spawn(|| { + thread::yield_now(); + unsafe { + assert_eq!( + libc::os_sync_wait_on_address( + ptr::from_ref(&futex).cast_mut().cast(), + 0, + size_of::(), + libc::OS_SYNC_WAIT_ON_ADDRESS_SHARED, + ), + -1, + ); + // This call fails because it uses the shared flag whereas the first waiter didn't. + assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::EINVAL); + } + }); + + thread::yield_now(); + + unsafe { + assert_eq!( + libc::os_sync_wake_by_address_any( + ptr::from_ref(&futex).cast_mut().cast(), + size_of::(), + libc::OS_SYNC_WAIT_ON_ADDRESS_SHARED, + ), + -1, + ); + // This call fails because it uses the shared flag whereas the waiter didn't. + assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::EINVAL); + } + }); +} + +fn main() { + wake_nobody(); + wake_dangling(); + wait_wrong_val(); + wait_timeout(); + wait_absolute_timeout(); + wait_wake(); + wait_wake_multiple(); + param_mismatch(); +} diff --git a/src/tools/miri/tests/pass-dep/getrandom.rs b/src/tools/miri/tests/pass-dep/getrandom.rs index a5bc5ec7079be..d359730e7f971 100644 --- a/src/tools/miri/tests/pass-dep/getrandom.rs +++ b/src/tools/miri/tests/pass-dep/getrandom.rs @@ -3,7 +3,7 @@ //@revisions: isolation no_isolation //@[no_isolation]compile-flags: -Zmiri-disable-isolation -/// Test direct calls of getrandom 0.1 and 0.2. +/// Test direct calls of getrandom 0.1, 0.2 and 0.3. fn main() { let mut data = vec![0; 16]; @@ -13,4 +13,6 @@ fn main() { getrandom_01::getrandom(&mut data).unwrap(); getrandom_02::getrandom(&mut data).unwrap(); + + getrandom_03::fill(&mut data).unwrap(); } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs index 23e2122ee50fa..dc3ab2828faa2 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs @@ -230,10 +230,10 @@ fn test_two_same_fd_in_same_epoll_instance() { //Two notification should be received. let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap(); let expected_value = 5 as u64; - check_epoll_wait::<8>(epfd, &[ - (expected_event, expected_value), - (expected_event, expected_value), - ]); + check_epoll_wait::<8>( + epfd, + &[(expected_event, expected_value), (expected_event, expected_value)], + ); } fn test_epoll_eventfd() { @@ -290,10 +290,10 @@ fn test_epoll_socketpair_both_sides() { let expected_value0 = fds[0] as u64; let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap(); let expected_value1 = fds[1] as u64; - check_epoll_wait::<8>(epfd, &[ - (expected_event0, expected_value0), - (expected_event1, expected_value1), - ]); + check_epoll_wait::<8>( + epfd, + &[(expected_event0, expected_value0), (expected_event1, expected_value1)], + ); // Read from fds[0]. let mut buf: [u8; 5] = [0; 5]; @@ -453,10 +453,10 @@ fn test_socketpair_read() { let expected_value0 = fds[0] as u64; let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap(); let expected_value1 = fds[1] as u64; - check_epoll_wait::<8>(epfd, &[ - (expected_event0, expected_value0), - (expected_event1, expected_value1), - ]); + check_epoll_wait::<8>( + epfd, + &[(expected_event0, expected_value0), (expected_event1, expected_value1)], + ); // Read 3 bytes from fds[0]. let mut buf: [u8; 3] = [0; 3]; diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs index 9b9542b88a962..3bc953c3a5fbf 100644 --- a/src/tools/miri/tests/ui.rs +++ b/src/tools/miri/tests/ui.rs @@ -13,7 +13,7 @@ use ui_test::custom_flags::edition::Edition; use ui_test::dependencies::DependencyBuilder; use ui_test::per_test_config::TestConfig; use ui_test::spanned::Spanned; -use ui_test::{CommandBuilder, Config, Format, Match, OutputConflictHandling, status_emitter}; +use ui_test::{CommandBuilder, Config, Format, Match, ignore_output_conflict, status_emitter}; #[derive(Copy, Clone, Debug)] enum Mode { @@ -82,9 +82,18 @@ fn build_native_lib() -> PathBuf { native_lib_path } +struct WithDependencies { + bless: bool, +} + /// Does *not* set any args or env vars, since it is shared between the test runner and /// run_dep_mode. -fn miri_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) -> Config { +fn miri_config( + target: &str, + path: &str, + mode: Mode, + with_dependencies: Option, +) -> Config { // Miri is rustc-like, so we create a default builder for rustc and modify it let mut program = CommandBuilder::rustc(); program.program = miri_path(); @@ -119,22 +128,26 @@ fn miri_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) -> // keep in sync with `./miri run` config.comment_defaults.base().add_custom("edition", Edition("2021".into())); - if with_dependencies { - config.comment_defaults.base().set_custom("dependencies", DependencyBuilder { - program: CommandBuilder { - // Set the `cargo-miri` binary, which we expect to be in the same folder as the `miri` binary. - // (It's a separate crate, so we don't get an env var from cargo.) - program: miri_path() - .with_file_name(format!("cargo-miri{}", env::consts::EXE_SUFFIX)), - // There is no `cargo miri build` so we just use `cargo miri run`. - args: ["miri", "run"].into_iter().map(Into::into).collect(), - // Reset `RUSTFLAGS` to work around . - envs: vec![("RUSTFLAGS".into(), None)], - ..CommandBuilder::cargo() + if let Some(WithDependencies { bless }) = with_dependencies { + config.comment_defaults.base().set_custom( + "dependencies", + DependencyBuilder { + program: CommandBuilder { + // Set the `cargo-miri` binary, which we expect to be in the same folder as the `miri` binary. + // (It's a separate crate, so we don't get an env var from cargo.) + program: miri_path() + .with_file_name(format!("cargo-miri{}", env::consts::EXE_SUFFIX)), + // There is no `cargo miri build` so we just use `cargo miri run`. + args: ["miri", "run"].into_iter().map(Into::into).collect(), + // Reset `RUSTFLAGS` to work around . + envs: vec![("RUSTFLAGS".into(), None)], + ..CommandBuilder::cargo() + }, + crate_manifest_path: Path::new("test_dependencies").join("Cargo.toml"), + build_std: None, + bless_lockfile: bless, }, - crate_manifest_path: Path::new("test_dependencies").join("Cargo.toml"), - build_std: None, - }); + ); } config } @@ -146,7 +159,20 @@ fn run_tests( with_dependencies: bool, tmpdir: &Path, ) -> Result<()> { + // Handle command-line arguments. + let mut args = ui_test::Args::test()?; + args.bless |= env::var_os("RUSTC_BLESS").is_some_and(|v| v != "0"); + + let with_dependencies = with_dependencies.then_some(WithDependencies { bless: args.bless }); + let mut config = miri_config(target, path, mode, with_dependencies); + config.with_args(&args); + config.bless_command = Some("./miri test --bless".into()); + + if env::var_os("MIRI_SKIP_UI_CHECKS").is_some() { + assert!(!args.bless, "cannot use RUSTC_BLESS and MIRI_SKIP_UI_CHECKS at the same time"); + config.output_conflict_handling = ignore_output_conflict; + } // Add a test env var to do environment communication tests. config.program.envs.push(("MIRI_ENV_VAR_TEST".into(), Some("0".into()))); @@ -182,16 +208,6 @@ fn run_tests( config.program.args.push(flag); } - // Handle command-line arguments. - let mut args = ui_test::Args::test()?; - args.bless |= env::var_os("RUSTC_BLESS").is_some_and(|v| v != "0"); - config.with_args(&args); - config.bless_command = Some("./miri test --bless".into()); - - if env::var_os("MIRI_SKIP_UI_CHECKS").is_some() { - assert!(!args.bless, "cannot use RUSTC_BLESS and MIRI_SKIP_UI_CHECKS at the same time"); - config.output_conflict_handling = OutputConflictHandling::Ignore; - } eprintln!(" Compiler: {}", config.program.display()); ui_test::run_tests_generic( // Only run one test suite. In the future we can add all test suites to one `Vec` and run @@ -327,7 +343,8 @@ fn main() -> Result<()> { } fn run_dep_mode(target: String, args: impl Iterator) -> Result<()> { - let mut config = miri_config(&target, "", Mode::RunDep, /* with dependencies */ true); + let mut config = + miri_config(&target, "", Mode::RunDep, Some(WithDependencies { bless: false })); config.comment_defaults.base().custom.remove("edition"); // `./miri` adds an `--edition` in `args`, so don't set it twice config.fill_host_and_target()?; config.program.args = args.collect();