diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index 740118bb4a02b..3078cd35b1084 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -231,6 +231,9 @@ jobs: exit ${exitcode} fi + # Store merge commit message + git log -1 --pretty=%B > message.txt + # Format changes ./miri toolchain ./miri fmt --check || (./miri fmt && git commit -am "fmt") @@ -239,7 +242,7 @@ jobs: BRANCH="rustup-$(date -u +%Y-%m-%d)" git switch -c $BRANCH git push -u origin $BRANCH - gh pr create -B master --title 'Automatic Rustup' --body "Update \`rustc\` to https://github.com/rust-lang/rust/commit/$(cat rust-version)." + gh pr create -B master --title 'Automatic Rustup' --body-file message.txt env: GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index a5214e213b3cf..d47967c0a4d34 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -246,6 +246,21 @@ such races. Note: `cargo-nextest` does not support doctests, see https://github.com/nextest-rs/nextest/issues/16 +### Directly invoking the `miri` driver + +The recommended way to invoke Miri is via `cargo miri`. Directly invoking the underlying `miri` +driver is not supported, which is why that binary is not even installed into the PATH. However, if +you need to run Miri on many small tests and want to invoke it directly like you would invoke +`rustc`, that is still possible with a bit of extra effort: + +```sh +# one-time setup +cargo +nightly miri setup +SYSROOT=$(cargo +nightly miri setup --print-sysroot) +# per file +~/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/miri --sysroot "$SYSROOT" file.rs +``` + ### Common Problems When using the above instructions, you may encounter a number of confusing compiler diff --git a/src/tools/miri/doc/genmc.md b/src/tools/miri/doc/genmc.md index 44e11dcbec44c..7da7a3d189487 100644 --- a/src/tools/miri/doc/genmc.md +++ b/src/tools/miri/doc/genmc.md @@ -1,15 +1,26 @@ # **(WIP)** Documentation for Miri-GenMC +**NOTE: GenMC mode is not yet fully implemented, and has [several correctness issues](https://github.com/rust-lang/miri/issues/4572). Using GenMC mode currently requires manually compiling Miri, see [Usage](#usage).** + + [GenMC](https://github.com/MPI-SWS/genmc) is a stateless model checker for exploring concurrent executions of a program. Miri-GenMC integrates that model checker into Miri. -**NOTE: Currently, no actual GenMC functionality is part of Miri, this is still WIP.** +Miri in GenMC mode takes a program as input like regular Miri, but instead of running it once, the program is executed repeatedly, until all possible executions allowed by the Rust memory model are explored. +This includes all possible thread interleavings and all allowed return values for atomic operations, including cases that are very rare to encounter on actual hardware. +(However, this does not include other sources of non-determinism, such as the absolute addresses of allocations. +It is hence still possible to have latent bugs in a test case even if they passed GenMC.) - +GenMC requires the input program to be bounded, i.e., have finitely many possible executions, otherwise it will not terminate. +Any loops that may run infinitely must be replaced or bounded (see below). + +GenMC makes use of Dynamic Partial Order Reduction (DPOR) to reduce the number of executions that must be explored, but the runtime can still be super-exponential in the size of the input program (number of threads and amount of interaction between threads). +Large programs may not be verifiable in a reasonable amount of time. ## Usage For testing/developing Miri-GenMC: +- install all [dependencies required by GenMC](https://github.com/MPI-SWS/genmc?tab=readme-ov-file#dependencies) - clone the Miri repo. - build Miri-GenMC with `./miri build --features=genmc`. - OR: install Miri-GenMC in the current system with `./miri install --features=genmc` @@ -50,6 +61,66 @@ Note that `cargo miri test` in GenMC mode is currently not supported. +### Eliminating unbounded loops + +As mentioned above, GenMC requires all loops to be bounded. +Otherwise, it is not possible to exhaustively explore all executions. +Currently, Miri-GenMC has no support for automatically bounding loops, so this needs to be done manually. + +#### Bounding loops without side effects + +The easiest case is that of a loop that simply spins until it observes a certain condition, without any side effects. +Such loops can be limited to one iteration, as demonstrated by the following example: + +```rust +#[cfg(miri)] +unsafe extern "Rust" { + // This is a special function that Miri provides. + // It blocks the thread calling this function if the condition is false. + pub unsafe fn miri_genmc_assume(condition: bool); +} + +// This functions loads an atomic boolean in a loop until it is true. +// GenMC will explore all executions where this does 1, 2, ..., ∞ loads, which means the verification will never terminate. +fn spin_until_true(flag: &AtomicBool) { + while !flag.load(Relaxed) { + std::hint::spin_loop(); + } +} + +// By replacing this loop with an assume statement, the only executions that will be explored are those with exactly 1 load that observes the expected value. +// Incorrect use of assume statements can lead GenMC to miss important executions, so it is marked `unsafe`. +fn spin_until_true_genmc(flag: &AtomicBool) { + unsafe { miri_genmc_assume(flag.load(Relaxed)) }; +} +``` + +#### Bounding loops with side effects + +Some loops do contain side effects, meaning the number of explored iterations affects the rest of the program. +Replacing the loop with one iteration like we did above would mean we miss all those possible executions. + +In such a case, the loop can be limited to a fixed number of iterations instead. +The choice of iteration limit trades off verification time for possibly missing bugs requiring more iterations. + +```rust +/// The loop in this function has a side effect, which is to increment the counter for the number of iterations. +/// Instead of replacing the loop entirely (which would miss all executions with `count > 0`), we limit the loop to at most 3 iterations. +fn count_until_true_genmc(flag: &AtomicBool) -> u64 { + let mut count = 0; + while !flag.load(Relaxed) { + count += 1; + std::hint::spin_loop(); + // Any execution that takes more than 3 iterations will not be explored. + unsafe { miri_genmc_assume(count <= 3) }; + } + count +} +``` + + + + ## Limitations Some or all of these limitations might get removed in the future: diff --git a/src/tools/miri/genmc-sys/build.rs b/src/tools/miri/genmc-sys/build.rs index 8d437c20a0926..f10e151a3d0bc 100644 --- a/src/tools/miri/genmc-sys/build.rs +++ b/src/tools/miri/genmc-sys/build.rs @@ -28,7 +28,7 @@ mod downloading { /// The GenMC repository the we get our commit from. pub(crate) const GENMC_GITHUB_URL: &str = "https://gitlab.inf.ethz.ch/public-plf/genmc.git"; /// The GenMC commit we depend on. It must be available on the specified GenMC repository. - pub(crate) const GENMC_COMMIT: &str = "af9cc9ccd5d412b16defc35dbf36571c63a19c76"; + pub(crate) const GENMC_COMMIT: &str = "ce775ccd7866db820fa12ffca66463087a11dd96"; /// Ensure that a local GenMC repo is present and set to the correct commit. /// Return the path of the GenMC repo and whether the checked out commit was changed. @@ -227,12 +227,17 @@ fn compile_cpp_dependencies(genmc_path: &Path, always_configure: bool) { // These definitions are parsed into a cmake list and then printed to the config.h file, so they are ';' separated. let definitions = llvm_definitions.split(";"); + // These are all the C++ files we need to compile, which needs to be updated if more C++ files are added to Miri. + // We use absolute paths since relative paths can confuse IDEs when attempting to go-to-source on a path in a compiler error. + let cpp_files_base_path = Path::new("cpp/src/"); let cpp_files = [ - "./cpp/src/MiriInterface/EventHandling.cpp", - "./cpp/src/MiriInterface/Exploration.cpp", - "./cpp/src/MiriInterface/Setup.cpp", - "./cpp/src/MiriInterface/ThreadManagement.cpp", - ]; + "MiriInterface/EventHandling.cpp", + "MiriInterface/Exploration.cpp", + "MiriInterface/Mutex.cpp", + "MiriInterface/Setup.cpp", + "MiriInterface/ThreadManagement.cpp", + ] + .map(|file| std::path::absolute(cpp_files_base_path.join(file)).unwrap()); let mut bridge = cxx_build::bridge("src/lib.rs"); // FIXME(genmc,cmake): Remove once the GenMC debug setting is available in the config.h file. diff --git a/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp b/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp index 444c9375319af..b0bd397ab34b1 100644 --- a/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp +++ b/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp @@ -12,7 +12,6 @@ // GenMC headers: #include "ExecutionGraph/EventLabel.hpp" -#include "Static/ModuleID.hpp" #include "Support/MemOrdering.hpp" #include "Support/RMWOps.hpp" #include "Verification/Config.hpp" @@ -36,6 +35,7 @@ struct LoadResult; struct StoreResult; struct ReadModifyWriteResult; struct CompareExchangeResult; +struct MutexLockResult; // GenMC uses `int` for its thread IDs. using ThreadId = int; @@ -126,7 +126,7 @@ struct MiriGenmcShim : private GenMCDriver { /**** Memory (de)allocation ****/ auto handle_malloc(ThreadId thread_id, uint64_t size, uint64_t alignment) -> uint64_t; - void handle_free(ThreadId thread_id, uint64_t address); + auto handle_free(ThreadId thread_id, uint64_t address) -> bool; /**** Thread management ****/ void handle_thread_create(ThreadId thread_id, ThreadId parent_id); @@ -134,6 +134,16 @@ struct MiriGenmcShim : private GenMCDriver { void handle_thread_finish(ThreadId thread_id, uint64_t ret_val); void handle_thread_kill(ThreadId thread_id); + /**** Blocking instructions ****/ + /// Inform GenMC that the thread should be blocked. + void handle_assume_block(ThreadId thread_id, AssumeType assume_type); + + /**** Mutex handling ****/ + auto handle_mutex_lock(ThreadId thread_id, uint64_t address, uint64_t size) -> MutexLockResult; + auto handle_mutex_try_lock(ThreadId thread_id, uint64_t address, uint64_t size) + -> MutexLockResult; + auto handle_mutex_unlock(ThreadId thread_id, uint64_t address, uint64_t size) -> StoreResult; + /***** Exploration related functionality *****/ /** Ask the GenMC scheduler for a new thread to schedule and return whether the execution is @@ -207,9 +217,10 @@ struct MiriGenmcShim : private GenMCDriver { * Automatically calls `inc_pos` and `dec_pos` where needed for the given thread. */ template - auto handle_load_reset_if_none(ThreadId tid, Ts&&... params) -> HandleResult { + auto handle_load_reset_if_none(ThreadId tid, std::optional old_val, Ts&&... params) + -> HandleResult { const auto pos = inc_pos(tid); - const auto ret = GenMCDriver::handleLoad(pos, std::forward(params)...); + const auto ret = GenMCDriver::handleLoad(pos, old_val, std::forward(params)...); // If we didn't get a value, we have to reset the index of the current thread. if (!std::holds_alternative(ret)) { dec_pos(tid); @@ -250,6 +261,7 @@ namespace GenmcScalarExt { inline GenmcScalar uninit() { return GenmcScalar { .value = 0, + .extra = 0, .is_init = false, }; } @@ -257,13 +269,20 @@ inline GenmcScalar uninit() { inline GenmcScalar from_sval(SVal sval) { return GenmcScalar { .value = sval.get(), + .extra = sval.getExtra(), .is_init = true, }; } inline SVal to_sval(GenmcScalar scalar) { ERROR_ON(!scalar.is_init, "Cannot convert an uninitialized `GenmcScalar` into an `SVal`\n"); - return SVal(scalar.value); + return SVal(scalar.value, scalar.extra); +} + +inline std::optional try_to_sval(GenmcScalar scalar) { + if (scalar.is_init) + return { SVal(scalar.value, scalar.extra) }; + return std::nullopt; } } // namespace GenmcScalarExt @@ -342,4 +361,22 @@ inline CompareExchangeResult from_error(std::unique_ptr error) { } } // namespace CompareExchangeResultExt +namespace MutexLockResultExt { +inline MutexLockResult ok(bool is_lock_acquired) { + return MutexLockResult { /* error: */ nullptr, /* is_reset: */ false, is_lock_acquired }; +} + +inline MutexLockResult reset() { + return MutexLockResult { /* error: */ nullptr, + /* is_reset: */ true, + /* is_lock_acquired: */ false }; +} + +inline MutexLockResult from_error(std::unique_ptr error) { + return MutexLockResult { /* error: */ std::move(error), + /* is_reset: */ false, + /* is_lock_acquired: */ false }; +} +} // namespace MutexLockResultExt + #endif /* GENMC_MIRI_INTERFACE_HPP */ diff --git a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp index 05c82641df948..2b6e5749d41a5 100644 --- a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp +++ b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp @@ -30,6 +30,13 @@ #include #include +/**** Blocking instructions ****/ + +void MiriGenmcShim::handle_assume_block(ThreadId thread_id, AssumeType assume_type) { + BUG_ON(getExec().getGraph().isThreadBlocked(thread_id)); + GenMCDriver::handleAssume(inc_pos(thread_id), assume_type); +} + /**** Memory access handling ****/ [[nodiscard]] auto MiriGenmcShim::handle_load( @@ -43,6 +50,7 @@ const auto type = AType::Unsigned; const auto ret = handle_load_reset_if_none( thread_id, + GenmcScalarExt::try_to_sval(old_val), ord, SAddr(address), ASize(size), @@ -52,6 +60,7 @@ if (const auto* err = std::get_if(&ret)) return LoadResultExt::from_error(format_error(*err)); const auto* ret_val = std::get_if(&ret); + // FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values. if (ret_val == nullptr) ERROR("Unimplemented: load returned unexpected result."); return LoadResultExt::from_value(*ret_val); @@ -68,6 +77,7 @@ const auto pos = inc_pos(thread_id); const auto ret = GenMCDriver::handleStore( pos, + GenmcScalarExt::try_to_sval(old_val), ord, SAddr(address), ASize(size), @@ -78,15 +88,14 @@ if (const auto* err = std::get_if(&ret)) return StoreResultExt::from_error(format_error(*err)); - if (!std::holds_alternative(ret)) - ERROR("store returned unexpected result"); - - // FIXME(genmc,mixed-accesses): Use the value that GenMC returns from handleStore (once - // available). - const auto& g = getExec().getGraph(); - return StoreResultExt::ok( - /* is_coherence_order_maximal_write */ g.co_max(SAddr(address))->getPos() == pos + + const bool* is_coherence_order_maximal_write = std::get_if(&ret); + // FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values. + ERROR_ON( + nullptr == is_coherence_order_maximal_write, + "Unimplemented: Store returned unexpected result." ); + return StoreResultExt::ok(*is_coherence_order_maximal_write); } void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { @@ -111,6 +120,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { // `FaiRead` and `FaiWrite`. const auto load_ret = handle_load_reset_if_none( thread_id, + GenmcScalarExt::try_to_sval(old_val), ordering, SAddr(address), ASize(size), @@ -123,6 +133,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { return ReadModifyWriteResultExt::from_error(format_error(*err)); const auto* ret_val = std::get_if(&load_ret); + // FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values. if (nullptr == ret_val) { ERROR("Unimplemented: read-modify-write returned unexpected result."); } @@ -133,6 +144,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { const auto storePos = inc_pos(thread_id); const auto store_ret = GenMCDriver::handleStore( storePos, + GenmcScalarExt::try_to_sval(old_val), ordering, SAddr(address), ASize(size), @@ -142,16 +154,16 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { if (const auto* err = std::get_if(&store_ret)) return ReadModifyWriteResultExt::from_error(format_error(*err)); - const auto* store_ret_val = std::get_if(&store_ret); - ERROR_ON(nullptr == store_ret_val, "Unimplemented: RMW store returned unexpected result."); - - // FIXME(genmc,mixed-accesses): Use the value that GenMC returns from handleStore (once - // available). - const auto& g = getExec().getGraph(); + const bool* is_coherence_order_maximal_write = std::get_if(&store_ret); + // FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values. + ERROR_ON( + nullptr == is_coherence_order_maximal_write, + "Unimplemented: RMW store returned unexpected result." + ); return ReadModifyWriteResultExt::ok( /* old_value: */ read_old_val, new_value, - /* is_coherence_order_maximal_write */ g.co_max(SAddr(address))->getPos() == storePos + *is_coherence_order_maximal_write ); } @@ -177,6 +189,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { const auto load_ret = handle_load_reset_if_none( thread_id, + GenmcScalarExt::try_to_sval(old_val), success_ordering, SAddr(address), ASize(size), @@ -187,6 +200,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { if (const auto* err = std::get_if(&load_ret)) return CompareExchangeResultExt::from_error(format_error(*err)); const auto* ret_val = std::get_if(&load_ret); + // FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values. ERROR_ON(nullptr == ret_val, "Unimplemented: load returned unexpected result."); const auto read_old_val = *ret_val; if (read_old_val != expectedVal) @@ -197,6 +211,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { const auto storePos = inc_pos(thread_id); const auto store_ret = GenMCDriver::handleStore( storePos, + GenmcScalarExt::try_to_sval(old_val), success_ordering, SAddr(address), ASize(size), @@ -205,19 +220,13 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { ); if (const auto* err = std::get_if(&store_ret)) return CompareExchangeResultExt::from_error(format_error(*err)); - const auto* store_ret_val = std::get_if(&store_ret); + const bool* is_coherence_order_maximal_write = std::get_if(&store_ret); + // FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values. ERROR_ON( - nullptr == store_ret_val, + nullptr == is_coherence_order_maximal_write, "Unimplemented: compare-exchange store returned unexpected result." ); - - // FIXME(genmc,mixed-accesses): Use the value that GenMC returns from handleStore (once - // available). - const auto& g = getExec().getGraph(); - return CompareExchangeResultExt::success( - read_old_val, - /* is_coherence_order_maximal_write */ g.co_max(SAddr(address))->getPos() == storePos - ); + return CompareExchangeResultExt::success(read_old_val, *is_coherence_order_maximal_write); } /**** Memory (de)allocation ****/ @@ -244,8 +253,9 @@ auto MiriGenmcShim::handle_malloc(ThreadId thread_id, uint64_t size, uint64_t al return ret_val.get(); } -void MiriGenmcShim::handle_free(ThreadId thread_id, uint64_t address) { +auto MiriGenmcShim::handle_free(ThreadId thread_id, uint64_t address) -> bool { const auto pos = inc_pos(thread_id); GenMCDriver::handleFree(pos, SAddr(address), EventDeps()); - // FIXME(genmc): add error handling once GenMC returns errors from `handleFree` + // FIXME(genmc): use returned error from `handleFree` once implemented in GenMC. + return getResult().status.has_value(); } diff --git a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Exploration.cpp b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Exploration.cpp index 5e7188f17e0d2..0f64083ddda68 100644 --- a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Exploration.cpp +++ b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Exploration.cpp @@ -24,8 +24,10 @@ auto MiriGenmcShim::schedule_next( if (const auto result = GenMCDriver::scheduleNext(threads_action_)) return SchedulingResult { ExecutionState::Ok, static_cast(result.value()) }; - if (GenMCDriver::isExecutionBlocked()) + if (getExec().getGraph().isBlocked()) return SchedulingResult { ExecutionState::Blocked, 0 }; + if (getResult().status.has_value()) // the "value" here is a `VerificationError` + return SchedulingResult { ExecutionState::Error, 0 }; return SchedulingResult { ExecutionState::Finished, 0 }; } diff --git a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Mutex.cpp b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Mutex.cpp new file mode 100644 index 0000000000000..fc3f5e6e09a67 --- /dev/null +++ b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Mutex.cpp @@ -0,0 +1,159 @@ +/** This file contains functionality related to handling mutexes. */ + +#include "MiriInterface.hpp" + +// GenMC headers: +#include "Static/ModuleID.hpp" + +// CXX.rs generated headers: +#include "genmc-sys/src/lib.rs.h" + +#define MUTEX_UNLOCKED SVal(0) +#define MUTEX_LOCKED SVal(1) + +auto MiriGenmcShim::handle_mutex_lock(ThreadId thread_id, uint64_t address, uint64_t size) + -> MutexLockResult { + // This annotation informs GenMC about the condition required to make this lock call succeed. + // It stands for `value_read_by_load != MUTEX_LOCKED`. + const auto size_bits = size * 8; + const auto annot = std::move(Annotation( + AssumeType::Spinloop, + Annotation::ExprVP( + NeExpr::create( + // `RegisterExpr` marks the value of the current expression, i.e., the loaded value. + // The `id` is ignored by GenMC; it is only used by the LLI frontend to substitute + // other variables from previous expressions that may be used here. + RegisterExpr::create(size_bits, /* id */ 0), + ConcreteExpr::create(size_bits, MUTEX_LOCKED) + ) + .release() + ) + )); + + // As usual, we need to tell GenMC which value was stored at this location before this atomic + // access, if there previously was a non-atomic initializing access. We set the initial state of + // a mutex to be "unlocked". + const auto old_val = MUTEX_UNLOCKED; + const auto load_ret = handle_load_reset_if_none( + thread_id, + old_val, + address, + size, + annot, + EventDeps() + ); + if (const auto* err = std::get_if(&load_ret)) + return MutexLockResultExt::from_error(format_error(*err)); + // If we get a `Reset`, GenMC decided that this lock operation should not yet run, since it + // would not acquire the mutex. Like the handling of the case further down where we read a `1` + // ("Mutex already locked"), Miri should call the handle function again once the current thread + // is scheduled by GenMC the next time. + if (std::holds_alternative(load_ret)) + return MutexLockResultExt::reset(); + + const auto* ret_val = std::get_if(&load_ret); + ERROR_ON(!ret_val, "Unimplemented: mutex lock returned unexpected result."); + ERROR_ON( + *ret_val != MUTEX_UNLOCKED && *ret_val != MUTEX_LOCKED, + "Mutex read value was neither 0 nor 1" + ); + const bool is_lock_acquired = *ret_val == MUTEX_UNLOCKED; + if (is_lock_acquired) { + const auto store_ret = GenMCDriver::handleStore( + inc_pos(thread_id), + old_val, + address, + size, + EventDeps() + ); + if (const auto* err = std::get_if(&store_ret)) + return MutexLockResultExt::from_error(format_error(*err)); + // We don't update Miri's memory for this operation so we don't need to know if the store + // was the co-maximal store, but we still check that we at least get a boolean as the result + // of the store. + const bool* is_coherence_order_maximal_write = std::get_if(&store_ret); + ERROR_ON( + nullptr == is_coherence_order_maximal_write, + "Unimplemented: store part of mutex try_lock returned unexpected result." + ); + } else { + // We did not acquire the mutex, so we tell GenMC to block the thread until we can acquire + // it. GenMC determines this based on the annotation we pass with the load further up in + // this function, namely when that load will read a value other than `MUTEX_LOCKED`. + this->handle_assume_block(thread_id, AssumeType::Spinloop); + } + return MutexLockResultExt::ok(is_lock_acquired); +} + +auto MiriGenmcShim::handle_mutex_try_lock(ThreadId thread_id, uint64_t address, uint64_t size) + -> MutexLockResult { + auto& currPos = threads_action_[thread_id].event; + // As usual, we need to tell GenMC which value was stored at this location before this atomic + // access, if there previously was a non-atomic initializing access. We set the initial state of + // a mutex to be "unlocked". + const auto old_val = MUTEX_UNLOCKED; + const auto load_ret = GenMCDriver::handleLoad( + ++currPos, + old_val, + SAddr(address), + ASize(size) + ); + if (const auto* err = std::get_if(&load_ret)) + return MutexLockResultExt::from_error(format_error(*err)); + const auto* ret_val = std::get_if(&load_ret); + if (nullptr == ret_val) { + ERROR("Unimplemented: mutex trylock load returned unexpected result."); + } + + ERROR_ON( + *ret_val != MUTEX_UNLOCKED && *ret_val != MUTEX_LOCKED, + "Mutex read value was neither 0 nor 1" + ); + const bool is_lock_acquired = *ret_val == MUTEX_UNLOCKED; + if (!is_lock_acquired) { + return MutexLockResultExt::ok(false); /* Lock already held. */ + } + + const auto store_ret = GenMCDriver::handleStore( + ++currPos, + old_val, + SAddr(address), + ASize(size) + ); + if (const auto* err = std::get_if(&store_ret)) + return MutexLockResultExt::from_error(format_error(*err)); + // We don't update Miri's memory for this operation so we don't need to know if the store was + // co-maximal, but we still check that we get a boolean result. + const bool* is_coherence_order_maximal_write = std::get_if(&store_ret); + ERROR_ON( + nullptr == is_coherence_order_maximal_write, + "Unimplemented: store part of mutex try_lock returned unexpected result." + ); + return MutexLockResultExt::ok(true); +} + +auto MiriGenmcShim::handle_mutex_unlock(ThreadId thread_id, uint64_t address, uint64_t size) + -> StoreResult { + const auto pos = inc_pos(thread_id); + const auto ret = GenMCDriver::handleStore( + pos, + // As usual, we need to tell GenMC which value was stored at this location before this + // atomic access, if there previously was a non-atomic initializing access. We set the + // initial state of a mutex to be "unlocked". + /* old_val */ MUTEX_UNLOCKED, + MemOrdering::Release, + SAddr(address), + ASize(size), + AType::Signed, + /* store_value */ MUTEX_UNLOCKED, + EventDeps() + ); + if (const auto* err = std::get_if(&ret)) + return StoreResultExt::from_error(format_error(*err)); + const bool* is_coherence_order_maximal_write = std::get_if(&ret); + ERROR_ON( + nullptr == is_coherence_order_maximal_write, + "Unimplemented: store part of mutex unlock returned unexpected result." + ); + return StoreResultExt::ok(*is_coherence_order_maximal_write); +} diff --git a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Setup.cpp b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Setup.cpp index af13f0d07746e..5455b1a8de7f8 100644 --- a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Setup.cpp +++ b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Setup.cpp @@ -170,9 +170,8 @@ static auto to_genmc_verbosity_level(const LogLevel log_level) -> VerbosityLevel // From a Miri perspective, this API doesn't work very well: most memory starts out // "uninitialized"; // only statics have an initial value. And their initial value is just a sequence of bytes, - // but GenMC - // expect this to be already split into separate atomic variables. So we return a dummy - // value. + // but GenMC expect this to be already split into separate atomic variables. So we return a + // dummy value. // This value should never be visible to the interpreted program. // GenMC does not understand uninitialized memory the same way Miri does, which may cause // this function to be called. The returned value can be visible to Miri or the user: @@ -183,13 +182,14 @@ static auto to_genmc_verbosity_level(const LogLevel log_level) -> VerbosityLevel // Currently, atomic loads can see this value, unless initialized by an *atomic* store. // FIXME(genmc): update this comment once mixed atomic-non-atomic support is added. // - // FIXME(genmc): implement proper support for uninitialized memory in GenMC. Ideally, the - // initial value getter would return an `optional`, since the memory location may be - // uninitialized. + // FIXME(genmc): implement proper support for uninitialized memory in GenMC. + // Ideally, the initial value getter would return an `optional`, since the memory + // location may be uninitialized. .initValGetter = [](const AAccess& a) { return SVal(0xDEAD); }, - // Miri serves non-atomic loads from its own memory and these GenMC checks are wrong in - // that case. This should no longer be required with proper mixed-size access support. - .skipUninitLoadChecks = [](MemOrdering ord) { return ord == MemOrdering::NotAtomic; }, + // Miri serves non-atomic loads from its own memory and these GenMC checks are wrong in that + // case. This should no longer be required with proper mixed-size access support. + .skipUninitLoadChecks = [](const MemAccessLabel* access_label + ) { return access_label->getOrdering() == MemOrdering::NotAtomic; }, }; driver->setInterpCallbacks(std::move(interpreter_callbacks)); diff --git a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/ThreadManagement.cpp b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/ThreadManagement.cpp index 352d27adc3e8b..d2061fcb406c9 100644 --- a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/ThreadManagement.cpp +++ b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/ThreadManagement.cpp @@ -38,6 +38,7 @@ void MiriGenmcShim::handle_thread_join(ThreadId thread_id, ThreadId child_id) { if (!std::holds_alternative(ret)) { dec_pos(thread_id); } + // FIXME(genmc): handle `HandleResult::{Invalid, Reset, VerificationError}` return values. // NOTE: Thread return value is ignored, since Miri doesn't need it. } diff --git a/src/tools/miri/genmc-sys/src/lib.rs b/src/tools/miri/genmc-sys/src/lib.rs index 733b3d780b187..a34c7f2b3a667 100644 --- a/src/tools/miri/genmc-sys/src/lib.rs +++ b/src/tools/miri/genmc-sys/src/lib.rs @@ -45,10 +45,14 @@ pub fn create_genmc_driver_handle( } impl GenmcScalar { - pub const UNINIT: Self = Self { value: 0, is_init: false }; + pub const UNINIT: Self = Self { value: 0, extra: 0, is_init: false }; pub const fn from_u64(value: u64) -> Self { - Self { value, is_init: true } + Self { value, extra: 0, is_init: true } + } + + pub const fn has_provenance(&self) -> bool { + self.extra != 0 } } @@ -162,10 +166,16 @@ mod ffi { } /// This type corresponds to `Option` (or `std::optional`), where `SVal` is the type that GenMC uses for storing values. - /// CXX doesn't support `std::optional` currently, so we need to use an extra `bool` to define whether this value is initialized or not. #[derive(Debug, Clone, Copy)] struct GenmcScalar { + /// The raw byte-level value (discarding provenance, if any) of this scalar. value: u64, + /// This is zero for integer values. For pointers, this encodes the provenance by + /// storing the base address of the allocation that this pointer belongs to. + /// Operations on `SVal` in GenMC (e.g., `fetch_add`) preserve the `extra` of the left argument (`left.fetch_add(right, ...)`). + extra: u64, + /// Indicates whether this value is initialized. If this is `false`, the other fields do not matter. + /// (Ideally we'd use `std::optional` but CXX does not support that.) is_init: bool, } @@ -173,6 +183,7 @@ mod ffi { #[derive(Debug, Clone, Copy)] enum ExecutionState { Ok, + Error, Blocked, Finished, } @@ -243,6 +254,17 @@ mod ffi { is_coherence_order_maximal_write: bool, } + #[must_use] + #[derive(Debug)] + struct MutexLockResult { + /// If there was an error, it will be stored in `error`, otherwise it is `None`. + error: UniquePtr, + /// If true, GenMC determined that we should retry the mutex lock operation once the thread attempting to lock is scheduled again. + is_reset: bool, + /// Indicate whether the lock was acquired by this thread. + is_lock_acquired: bool, + } + /**** These are GenMC types that we have to copy-paste here since cxx does not support "importing" externally defined C++ types. ****/ @@ -258,9 +280,11 @@ mod ffi { /// Corresponds to GenMC's type with the same name. /// Should only be modified if changed by GenMC. enum ActionKind { - /// Any Mir terminator that's atomic and has load semantics. + /// Any MIR terminator that's atomic and that may have load semantics. + /// This includes functions with atomic properties, such as `pthread_create`. + /// If the exact type of the terminator cannot be determined, load is a safe default `Load`. Load, - /// Anything that's not a `Load`. + /// Anything that's definitely not a `Load`. NonLoad, } @@ -292,6 +316,13 @@ mod ffi { UMin = 10, } + #[derive(Debug)] + enum AssumeType { + User = 0, + Barrier = 1, + Spinloop = 2, + } + // # Safety // // This block is unsafe to allow defining safe methods inside. @@ -310,6 +341,7 @@ mod ffi { (This tells cxx that the enums defined above are already defined on the C++ side; it will emit assertions to ensure that the two definitions agree.) ****/ type ActionKind; + type AssumeType; type MemOrdering; type RMWBinOp; type SchedulePolicy; @@ -404,7 +436,8 @@ mod ffi { size: u64, alignment: u64, ) -> u64; - fn handle_free(self: Pin<&mut MiriGenmcShim>, thread_id: i32, address: u64); + /// Returns true if an error was found. + fn handle_free(self: Pin<&mut MiriGenmcShim>, thread_id: i32, address: u64) -> bool; /**** Thread management ****/ fn handle_thread_create(self: Pin<&mut MiriGenmcShim>, thread_id: i32, parent_id: i32); @@ -412,6 +445,36 @@ mod ffi { fn handle_thread_finish(self: Pin<&mut MiriGenmcShim>, thread_id: i32, ret_val: u64); fn handle_thread_kill(self: Pin<&mut MiriGenmcShim>, thread_id: i32); + /**** Blocking instructions ****/ + /// Inform GenMC that the thread should be blocked. + /// Note: this function is currently hardcoded for `AssumeType::User`, corresponding to user supplied assume statements. + /// This can become a parameter once more types of assumes are added. + fn handle_assume_block( + self: Pin<&mut MiriGenmcShim>, + thread_id: i32, + assume_type: AssumeType, + ); + + /**** Mutex handling ****/ + fn handle_mutex_lock( + self: Pin<&mut MiriGenmcShim>, + thread_id: i32, + address: u64, + size: u64, + ) -> MutexLockResult; + fn handle_mutex_try_lock( + self: Pin<&mut MiriGenmcShim>, + thread_id: i32, + address: u64, + size: u64, + ) -> MutexLockResult; + fn handle_mutex_unlock( + self: Pin<&mut MiriGenmcShim>, + thread_id: i32, + address: u64, + size: u64, + ) -> StoreResult; + /***** Exploration related functionality *****/ /// Ask the GenMC scheduler for a new thread to schedule and diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 388e88fe43eb7..f877706520ea1 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -f6092f224d2b1774b31033f12d0bee626943b02f +36e4f5d1fe1d63953a5bf1758ce2b64172623e2e diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs index f011ee717850d..05d3444a4eb1a 100644 --- a/src/tools/miri/src/alloc_addresses/mod.rs +++ b/src/tools/miri/src/alloc_addresses/mod.rs @@ -13,6 +13,7 @@ use rustc_middle::ty::TyCtxt; pub use self::address_generator::AddressGenerator; use self::reuse_pool::ReusePool; use crate::concurrency::VClock; +use crate::diagnostics::SpanDedupDiagnostic; use crate::*; #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -42,20 +43,19 @@ pub struct GlobalStateInner { /// they do not have an `AllocExtra`. /// This is the inverse of `int_to_ptr_map`. base_addr: FxHashMap, - /// Temporarily store prepared memory space for global allocations the first time their memory - /// address is required. This is used to ensure that the memory is allocated before Miri assigns - /// it an internal address, which is important for matching the internal address to the machine - /// address so FFI can read from pointers. - prepared_alloc_bytes: FxHashMap, - /// A pool of addresses we can reuse for future allocations. - reuse: ReusePool, - /// Whether an allocation has been exposed or not. This cannot be put + /// The set of exposed allocations. This cannot be put /// into `AllocExtra` for the same reason as `base_addr`. exposed: FxHashSet, - /// The generator for new addresses in a given range. - address_generator: AddressGenerator, /// The provenance to use for int2ptr casts provenance_mode: ProvenanceMode, + /// The generator for new addresses in a given range, and a pool for address reuse. This is + /// `None` if addresses are generated elsewhere (in native-lib mode or with GenMC). + address_generation: Option<(AddressGenerator, ReusePool)>, + /// Native-lib mode only: Temporarily store prepared memory space for global allocations the + /// first time their memory address is required. This is used to ensure that the memory is + /// allocated before Miri assigns it an internal address, which is important for matching the + /// internal address to the machine address so FFI can read from pointers. + prepared_alloc_bytes: Option>, } impl VisitProvenance for GlobalStateInner { @@ -64,9 +64,8 @@ impl VisitProvenance for GlobalStateInner { int_to_ptr_map: _, base_addr: _, prepared_alloc_bytes: _, - reuse: _, exposed: _, - address_generator: _, + address_generation: _, provenance_mode: _, } = self; // Though base_addr, int_to_ptr_map, and exposed contain AllocIds, we do not want to visit them. @@ -83,11 +82,16 @@ impl GlobalStateInner { GlobalStateInner { int_to_ptr_map: Vec::default(), base_addr: FxHashMap::default(), - prepared_alloc_bytes: FxHashMap::default(), - reuse: ReusePool::new(config), exposed: FxHashSet::default(), - address_generator: AddressGenerator::new(stack_addr..tcx.target_usize_max()), provenance_mode: config.provenance_mode, + address_generation: (config.native_lib.is_empty() && config.genmc_config.is_none()) + .then(|| { + ( + AddressGenerator::new(stack_addr..tcx.target_usize_max()), + ReusePool::new(config), + ) + }), + prepared_alloc_bytes: (!config.native_lib.is_empty()).then(FxHashMap::default), } } @@ -147,6 +151,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Store prepared allocation to be picked up for use later. global_state .prepared_alloc_bytes + .as_mut() + .unwrap() .try_insert(alloc_id, prepared_bytes) .unwrap(); ptr @@ -173,29 +179,25 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // We don't have to expose this pointer yet, we do that in `prepare_for_native_call`. return interp_ok(base_ptr.addr().to_u64()); } - // We are not in native lib mode, so we control the addresses ourselves. + // We are not in native lib or genmc mode, so we control the addresses ourselves. + let (addr_gen, reuse) = global_state.address_generation.as_mut().unwrap(); let mut rng = this.machine.rng.borrow_mut(); - if let Some((reuse_addr, clock)) = global_state.reuse.take_addr( - &mut *rng, - info.size, - info.align, - memory_kind, - this.active_thread(), - ) { + if let Some((reuse_addr, clock)) = + reuse.take_addr(&mut *rng, info.size, info.align, memory_kind, this.active_thread()) + { if let Some(clock) = clock { this.acquire_clock(&clock)?; } interp_ok(reuse_addr) } else { // We have to pick a fresh address. - let new_addr = - global_state.address_generator.generate(info.size, info.align, &mut rng)?; + let new_addr = addr_gen.generate(info.size, info.align, &mut rng)?; // If we filled up more than half the address space, start aggressively reusing // addresses to avoid running out. - let remaining_range = global_state.address_generator.get_remaining(); + let remaining_range = addr_gen.get_remaining(); if remaining_range.start > remaining_range.end / 2 { - global_state.reuse.address_space_shortage(); + reuse.address_space_shortage(); } interp_ok(new_addr) @@ -207,13 +209,7 @@ impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Returns the `AllocId` that corresponds to the specified addr, // or `None` if the addr is out of bounds. - // Setting `only_exposed_allocations` selects whether only exposed allocations are considered. - fn alloc_id_from_addr( - &self, - addr: u64, - size: i64, - only_exposed_allocations: bool, - ) -> Option { + fn alloc_id_from_addr(&self, addr: u64, size: i64) -> Option { let this = self.eval_context_ref(); let global_state = this.machine.alloc_addresses.borrow(); assert!(global_state.provenance_mode != ProvenanceMode::Strict); @@ -242,13 +238,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } }?; - // We only use this provenance if it has been exposed, or if the caller requested also non-exposed allocations - if !only_exposed_allocations || global_state.exposed.contains(&alloc_id) { + // We only use this provenance if it has been exposed. + if global_state.exposed.contains(&alloc_id) { // This must still be live, since we remove allocations from `int_to_ptr_map` when they get freed. - // In GenMC mode, we keep all allocations, so this check doesn't apply there. - if this.machine.data_race.as_genmc_ref().is_none() { - debug_assert!(this.is_alloc_live(alloc_id)); - } + debug_assert!(this.is_alloc_live(alloc_id)); Some(alloc_id) } else { None @@ -349,12 +342,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match global_state.provenance_mode { ProvenanceMode::Default => { // The first time this happens at a particular location, print a warning. - let mut int2ptr_warned = this.machine.int2ptr_warned.borrow_mut(); - let first = int2ptr_warned.is_empty(); - if int2ptr_warned.insert(this.cur_span()) { - // Newly inserted, so first time we see this span. - this.emit_diagnostic(NonHaltingDiagnostic::Int2Ptr { details: first }); - } + static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new(); + this.dedup_diagnostic(&DEDUP, |first| { + NonHaltingDiagnostic::Int2Ptr { details: first } + }); } ProvenanceMode::Strict => { throw_machine_stop!(TerminationInfo::Int2PtrWithStrictProvenance); @@ -414,6 +405,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let mut global_state = this.machine.alloc_addresses.borrow_mut(); let mut prepared_alloc_bytes = global_state .prepared_alloc_bytes + .as_mut() + .unwrap() .remove(&id) .unwrap_or_else(|| panic!("alloc bytes for {id:?} have not been prepared")); // Sanity-check that the prepared allocation has the right size and alignment. @@ -443,8 +436,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { alloc_id } else { // A wildcard pointer. - let only_exposed_allocations = true; - this.alloc_id_from_addr(addr.bytes(), size, only_exposed_allocations)? + this.alloc_id_from_addr(addr.bytes(), size)? }; // This cannot fail: since we already have a pointer with that provenance, adjust_alloc_root_pointer @@ -465,13 +457,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { impl<'tcx> MiriMachine<'tcx> { pub fn free_alloc_id(&mut self, dead_id: AllocId, size: Size, align: Align, kind: MemoryKind) { - // In GenMC mode, we can't remove dead allocation info since such pointers can - // still be stored in atomics and we need this info to convert GenMC pointers to Miri pointers. - // `global_state.reuse` is also unused so we can just skip this entire function. - if self.data_race.as_genmc_ref().is_some() { - return; - } - let global_state = self.alloc_addresses.get_mut(); let rng = self.rng.get_mut(); @@ -496,15 +481,17 @@ impl<'tcx> MiriMachine<'tcx> { // `alloc_id_from_addr` any more. global_state.exposed.remove(&dead_id); // Also remember this address for future reuse. - let thread = self.threads.active_thread(); - global_state.reuse.add_addr(rng, addr, size, align, kind, thread, || { - // We already excluded GenMC above. We cannot use `self.release_clock` as - // `self.alloc_addresses` is borrowed. - if let Some(data_race) = self.data_race.as_vclocks_ref() { - data_race.release_clock(&self.threads, |clock| clock.clone()) - } else { - VClock::default() - } - }) + if let Some((_addr_gen, reuse)) = global_state.address_generation.as_mut() { + let thread = self.threads.active_thread(); + reuse.add_addr(rng, addr, size, align, kind, thread, || { + // We cannot be in GenMC mode as then `address_generation` is `None`. We cannot use + // `self.release_clock` as `self.alloc_addresses` is borrowed. + if let Some(data_race) = self.data_race.as_vclocks_ref() { + data_race.release_clock(&self.threads, |clock| clock.clone()) + } else { + VClock::default() + } + }) + } } } diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 8b15a7863476e..3c759521c6ca0 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -188,6 +188,11 @@ impl rustc_driver::Callbacks for MiriCompilerCalls { // Run in GenMC mode if enabled. if config.genmc_config.is_some() { + // Validate GenMC settings. + if let Err(err) = GenmcConfig::validate(&mut config, tcx) { + fatal_error!("Invalid settings: {err}"); + } + // This is the entry point used in GenMC mode. // This closure will be called multiple times to explore the concurrent execution space of the program. let eval_entry_once = |genmc_ctx: Rc| { @@ -352,6 +357,7 @@ fn fatal_error_(msg: &impl std::fmt::Display) -> ! { macro_rules! fatal_error { ($($tt:tt)*) => { $crate::fatal_error_(&format_args!($($tt)*)) }; } +#[allow(unused)] // use depends on cfg use fatal_error; /// Execute a compiler with the given CLI arguments and callbacks. @@ -744,11 +750,6 @@ fn main() { let many_seeds = many_seeds.map(|seeds| ManySeedsConfig { seeds, keep_going: many_seeds_keep_going }); - // Validate settings for data race detection and GenMC mode. - if let Err(err) = GenmcConfig::validate_genmc_mode_settings(&mut miri_config) { - fatal_error!("Invalid settings: {err}"); - } - if miri_config.weak_memory_emulation && !miri_config.data_race_detector { fatal_error!( "Weak memory emulation cannot be enabled when the data race detector is disabled" diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs index 63b18028a5b9d..997d7799a5f1c 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs @@ -221,7 +221,7 @@ impl AllocHistory { pub fn new(id: AllocId, item: Item, machine: &MiriMachine<'_>) -> Self { Self { id, - root: (item, machine.current_span()), + root: (item, machine.current_user_relevant_span()), creations: SmallVec::new(), invalidations: SmallVec::new(), protectors: SmallVec::new(), @@ -269,11 +269,11 @@ impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> { }; self.history .creations - .push(Creation { retag: op.clone(), span: self.machine.current_span() }); + .push(Creation { retag: op.clone(), span: self.machine.current_user_relevant_span() }); } pub fn log_invalidation(&mut self, tag: BorTag) { - let mut span = self.machine.current_span(); + let mut span = self.machine.current_user_relevant_span(); let (range, cause) = match &self.operation { Operation::Retag(RetagOp { info, range, permission, .. }) => { if info.cause == RetagCause::FnEntry { @@ -298,7 +298,7 @@ impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> { }; self.history .protectors - .push(Protection { tag: op.new_tag, span: self.machine.current_span() }); + .push(Protection { tag: op.new_tag, span: self.machine.current_user_relevant_span() }); } pub fn get_logs_relevant_to( 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 5fe00ab02c4b9..127d832f73e79 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs @@ -6,6 +6,7 @@ mod item; mod stack; use std::fmt::Write; +use std::sync::atomic::AtomicBool; use std::{cmp, mem}; use rustc_abi::{BackendRepr, Size}; @@ -822,7 +823,8 @@ trait EvalContextPrivExt<'tcx, 'ecx>: crate::MiriInterpCxExt<'tcx> { let size = match size { Some(size) => size, None => { - if !this.machine.sb_extern_type_warned.replace(true) { + static DEDUP: AtomicBool = AtomicBool::new(false); + if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) { this.emit_diagnostic(NonHaltingDiagnostic::ExternTypeReborrow); } return interp_ok(place.clone()); diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs index 6e5b5c807aa22..865097af10181 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs @@ -33,7 +33,7 @@ impl<'tcx> Tree { machine: &MiriMachine<'tcx>, ) -> Self { let tag = state.root_ptr_tag(id, machine); // Fresh tag for the root - let span = machine.current_span(); + let span = machine.current_user_relevant_span(); Tree::new(tag, size, span) } @@ -61,7 +61,7 @@ impl<'tcx> Tree { ProvenanceExtra::Wildcard => return interp_ok(()), }; let global = machine.borrow_tracker.as_ref().unwrap(); - let span = machine.current_span(); + let span = machine.current_user_relevant_span(); self.perform_access( tag, Some((range, access_kind, diagnostics::AccessCause::Explicit(access_kind))), @@ -86,7 +86,7 @@ impl<'tcx> Tree { ProvenanceExtra::Wildcard => return interp_ok(()), }; let global = machine.borrow_tracker.as_ref().unwrap(); - let span = machine.current_span(); + let span = machine.current_user_relevant_span(); self.dealloc(tag, alloc_range(Size::ZERO, size), global, alloc_id, span) } @@ -107,7 +107,7 @@ impl<'tcx> Tree { tag: BorTag, alloc_id: AllocId, // diagnostics ) -> InterpResult<'tcx> { - let span = machine.current_span(); + let span = machine.current_user_relevant_span(); // `None` makes it the magic on-protector-end operation self.perform_access(tag, None, global, alloc_id, span) } @@ -360,7 +360,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Some((range_in_alloc, AccessKind::Read, diagnostics::AccessCause::Reborrow)), this.machine.borrow_tracker.as_ref().unwrap(), alloc_id, - this.machine.current_span(), + this.machine.current_user_relevant_span(), )?; // Also inform the data race model (but only if any bytes are actually affected). @@ -386,7 +386,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { inside_perms, new_perm.outside_perm, protected, - this.machine.current_span(), + this.machine.current_user_relevant_span(), )?; drop(tree_borrows); diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs index 1ad9ace1b5d1a..1f30f9d7288a2 100644 --- a/src/tools/miri/src/concurrency/data_race.rs +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -1208,7 +1208,7 @@ impl VClockAlloc { ty: Option>, machine: &MiriMachine<'_>, ) -> InterpResult<'tcx> { - let current_span = machine.current_span(); + let current_span = machine.current_user_relevant_span(); let global = machine.data_race.as_vclocks_ref().unwrap(); if !global.race_detecting() { return interp_ok(()); @@ -1250,7 +1250,7 @@ impl VClockAlloc { ty: Option>, machine: &mut MiriMachine<'_>, ) -> InterpResult<'tcx> { - let current_span = machine.current_span(); + let current_span = machine.current_user_relevant_span(); let global = machine.data_race.as_vclocks_mut().unwrap(); if !global.race_detecting() { return interp_ok(()); @@ -1304,7 +1304,7 @@ impl Default for LocalClocks { impl FrameState { pub fn local_write(&self, local: mir::Local, storage_live: bool, machine: &MiriMachine<'_>) { - let current_span = machine.current_span(); + let current_span = machine.current_user_relevant_span(); let global = machine.data_race.as_vclocks_ref().unwrap(); if !global.race_detecting() { return; @@ -1334,7 +1334,7 @@ impl FrameState { } pub fn local_read(&self, local: mir::Local, machine: &MiriMachine<'_>) { - let current_span = machine.current_span(); + let current_span = machine.current_user_relevant_span(); let global = machine.data_race.as_vclocks_ref().unwrap(); if !global.race_detecting() { return; @@ -1573,7 +1573,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { size.bytes() ); - let current_span = this.machine.current_span(); + let current_span = this.machine.current_user_relevant_span(); // Perform the atomic operation. data_race.maybe_perform_sync_operation( &this.machine.threads, @@ -1827,7 +1827,7 @@ impl GlobalState { machine: &MiriMachine<'tcx>, atomic: AtomicFenceOrd, ) -> InterpResult<'tcx> { - let current_span = machine.current_span(); + let current_span = machine.current_user_relevant_span(); self.maybe_perform_sync_operation(&machine.threads, current_span, |index, mut clocks| { trace!("Atomic fence on {:?} with ordering {:?}", index, atomic); @@ -1915,7 +1915,7 @@ impl GlobalState { callback: impl FnOnce(&VClock) -> R, ) -> R { let thread = threads.active_thread(); - let span = threads.active_thread_ref().current_span(); + let span = threads.active_thread_ref().current_user_relevant_span(); let (index, mut clocks) = self.thread_state_mut(thread); let r = callback(&clocks.clock); // Increment the clock, so that all following events cannot be confused with anything that diff --git a/src/tools/miri/src/concurrency/genmc/config.rs b/src/tools/miri/src/concurrency/genmc/config.rs index c7cfa6012b8dc..a05cda46f3e74 100644 --- a/src/tools/miri/src/concurrency/genmc/config.rs +++ b/src/tools/miri/src/concurrency/genmc/config.rs @@ -1,4 +1,6 @@ use genmc_sys::LogLevel; +use rustc_abi::Endian; +use rustc_middle::ty::TyCtxt; use super::GenmcParams; use crate::{IsolatedOp, MiriConfig, RejectOpWith}; @@ -32,8 +34,6 @@ impl GenmcConfig { genmc_config: &mut Option, trimmed_arg: &str, ) -> Result<(), String> { - // FIXME(genmc): Ensure host == target somewhere. - if genmc_config.is_none() { *genmc_config = Some(Default::default()); } @@ -86,11 +86,16 @@ impl GenmcConfig { /// /// Unsupported configurations return an error. /// Adjusts Miri settings where required, printing a warnings if the change might be unexpected for the user. - pub fn validate_genmc_mode_settings(miri_config: &mut MiriConfig) -> Result<(), &'static str> { + pub fn validate(miri_config: &mut MiriConfig, tcx: TyCtxt<'_>) -> Result<(), &'static str> { let Some(genmc_config) = miri_config.genmc_config.as_mut() else { return Ok(()); }; + // Check for supported target. + if tcx.data_layout.endian != Endian::Little || tcx.data_layout.pointer_size().bits() != 64 { + return Err("GenMC only supports 64bit little-endian targets"); + } + // Check for disallowed configurations. if !miri_config.data_race_detector { return Err("Cannot disable data race detection in GenMC mode"); diff --git a/src/tools/miri/src/concurrency/genmc/dummy.rs b/src/tools/miri/src/concurrency/genmc/dummy.rs index c28984cef35ad..b9e09e34dc360 100644 --- a/src/tools/miri/src/concurrency/genmc/dummy.rs +++ b/src/tools/miri/src/concurrency/genmc/dummy.rs @@ -1,11 +1,13 @@ use rustc_abi::{Align, Size}; use rustc_const_eval::interpret::{AllocId, InterpCx, InterpResult}; +use rustc_middle::ty::TyCtxt; +pub use self::intercept::EvalContextExt as GenmcEvalContextExt; pub use self::run::run_genmc_mode; use crate::intrinsics::AtomicRmwOp; use crate::{ - AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, MemoryKind, MiriMachine, Scalar, - ThreadId, ThreadManager, VisitProvenance, VisitWith, + AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, MemoryKind, MiriMachine, OpTy, + Scalar, ThreadId, ThreadManager, VisitProvenance, VisitWith, }; #[derive(Clone, Copy, Debug)] @@ -36,9 +38,36 @@ mod run { } } +mod intercept { + use super::*; + + impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} + pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + fn genmc_intercept_function( + &mut self, + _instance: rustc_middle::ty::Instance<'tcx>, + _args: &[rustc_const_eval::interpret::FnArg<'tcx, crate::Provenance>], + _dest: &crate::PlaceTy<'tcx>, + ) -> InterpResult<'tcx, bool> { + unreachable!() + } + + fn handle_genmc_verifier_assume(&mut self, _condition: &OpTy<'tcx>) -> InterpResult<'tcx> { + unreachable!(); + } + } +} + impl GenmcCtx { // We don't provide the `new` function in the dummy module. + pub(crate) fn schedule_thread<'tcx>( + &self, + _ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, + ) -> InterpResult<'tcx, Option> { + unreachable!() + } + /**** Memory access handling ****/ pub(super) fn set_ongoing_action_data_race_free(&self, _enable: bool) { @@ -191,26 +220,6 @@ impl GenmcCtx { ) -> InterpResult<'tcx> { unreachable!() } - - /**** Scheduling functionality ****/ - - pub fn schedule_thread<'tcx>( - &self, - _ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, - ) -> InterpResult<'tcx, ThreadId> { - unreachable!() - } - - /**** Blocking instructions ****/ - - #[allow(unused)] - pub(crate) fn handle_verifier_assume<'tcx>( - &self, - _machine: &MiriMachine<'tcx>, - _condition: bool, - ) -> InterpResult<'tcx, ()> { - unreachable!() - } } impl VisitProvenance for GenmcCtx { @@ -231,8 +240,9 @@ impl GenmcConfig { } } - pub fn validate_genmc_mode_settings( + pub fn validate( _miri_config: &mut crate::MiriConfig, + _tcx: TyCtxt<'_>, ) -> Result<(), &'static str> { Ok(()) } diff --git a/src/tools/miri/src/concurrency/genmc/helper.rs b/src/tools/miri/src/concurrency/genmc/helper.rs index 48a5ec8bb2608..b2e4b5aea5346 100644 --- a/src/tools/miri/src/concurrency/genmc/helper.rs +++ b/src/tools/miri/src/concurrency/genmc/helper.rs @@ -1,52 +1,19 @@ -use std::sync::RwLock; - use genmc_sys::{MemOrdering, RMWBinOp}; use rustc_abi::Size; use rustc_const_eval::interpret::{InterpResult, interp_ok}; -use rustc_data_structures::fx::FxHashSet; use rustc_middle::mir; +use rustc_middle::mir::interpret; use rustc_middle::ty::ScalarInt; -use rustc_span::Span; use tracing::debug; use super::GenmcScalar; -use crate::diagnostics::EvalContextExt; +use crate::alloc_addresses::EvalContextExt as _; use crate::intrinsics::AtomicRmwOp; -use crate::{ - AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, InterpCx, MiriInterpCx, - MiriMachine, NonHaltingDiagnostic, Scalar, throw_unsup_format, -}; +use crate::*; /// Maximum size memory access in bytes that GenMC supports. pub(super) const MAX_ACCESS_SIZE: u64 = 8; -/// Type for storing spans for already emitted warnings. -pub(super) type WarningCache = RwLock>; - -#[derive(Default)] -pub(super) struct Warnings { - pub(super) compare_exchange_failure_ordering: WarningCache, - pub(super) compare_exchange_weak: WarningCache, -} - -/// Emit a warning if it hasn't already been reported for current span. -pub(super) fn emit_warning<'tcx>( - ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, - cache: &WarningCache, - diagnostic: impl FnOnce() -> NonHaltingDiagnostic, -) { - let span = ecx.machine.current_span(); - if cache.read().unwrap().contains(&span) { - return; - } - // This span has not yet been reported, so we insert it into the cache and report it. - let mut cache = cache.write().unwrap(); - if cache.insert(span) { - // Some other thread may have added this span while we didn't hold the lock, so we only emit it if the insertions succeeded. - ecx.emit_diagnostic(diagnostic()); - } -} - /// This function is used to split up a large memory access into aligned, non-overlapping chunks of a limited size. /// Returns an iterator over the chunks, yielding `(base address, size)` of each chunk, ordered by address. pub fn split_access(address: Size, size: Size) -> impl Iterator { @@ -80,19 +47,30 @@ pub fn split_access(address: Size, size: Size) -> impl Iterator( - _ecx: &MiriInterpCx<'tcx>, + ecx: &MiriInterpCx<'tcx>, + genmc_ctx: &GenmcCtx, scalar: Scalar, ) -> InterpResult<'tcx, GenmcScalar> { interp_ok(match scalar { rustc_const_eval::interpret::Scalar::Int(scalar_int) => { // FIXME(genmc): Add u128 support once GenMC supports it. let value: u64 = scalar_int.to_uint(scalar_int.size()).try_into().unwrap(); - GenmcScalar { value, is_init: true } + GenmcScalar { value, extra: 0, is_init: true } + } + rustc_const_eval::interpret::Scalar::Ptr(pointer, size) => { + // FIXME(genmc,borrow tracking): Borrow tracking information is lost. + let addr = crate::Pointer::from(pointer).addr(); + if let crate::Provenance::Wildcard = pointer.provenance { + throw_unsup_format!("Pointers with wildcard provenance not allowed in GenMC mode"); + } + let (alloc_id, _size, _prov_extra) = + rustc_const_eval::interpret::Machine::ptr_get_alloc(ecx, pointer, size.into()) + .unwrap(); + let base_addr = ecx.addr_from_alloc_id(alloc_id, None)?; + // Add the base_addr alloc_id pair to the map. + genmc_ctx.exec_state.genmc_shared_allocs_map.borrow_mut().insert(base_addr, alloc_id); + GenmcScalar { value: addr.bytes(), extra: base_addr, is_init: true } } - rustc_const_eval::interpret::Scalar::Ptr(_pointer, _size) => - throw_unsup_format!( - "FIXME(genmc): Implement sending pointers (with provenance) to GenMC." - ), }) } @@ -101,16 +79,25 @@ pub fn scalar_to_genmc_scalar<'tcx>( /// Convert a `GenmcScalar` back into a Miri `Scalar`. /// For pointers, attempt to convert the stored base address of their allocation back into an `AllocId`. pub fn genmc_scalar_to_scalar<'tcx>( - _ecx: &MiriInterpCx<'tcx>, + ecx: &MiriInterpCx<'tcx>, + genmc_ctx: &GenmcCtx, scalar: GenmcScalar, size: Size, ) -> InterpResult<'tcx, Scalar> { - // FIXME(genmc): Add GenmcScalar to Miri Pointer conversion. - - // NOTE: GenMC always returns 64 bit values, and the upper bits are not yet truncated. - // FIXME(genmc): GenMC should be doing the truncation, not Miri. - let (value_scalar_int, _got_truncated) = ScalarInt::truncate_from_uint(scalar.value, size); - interp_ok(Scalar::Int(value_scalar_int)) + // If `extra` is zero, we have a regular integer. + if scalar.extra == 0 { + // NOTE: GenMC always returns 64 bit values, and the upper bits are not yet truncated. + // FIXME(genmc): GenMC should be doing the truncation, not Miri. + let (value_scalar_int, _got_truncated) = ScalarInt::truncate_from_uint(scalar.value, size); + return interp_ok(Scalar::from(value_scalar_int)); + } + // `extra` is non-zero, we have a pointer. + // When we get a pointer from GenMC, then we must have sent it to GenMC before in the same execution (since the reads-from relation is always respected). + let alloc_id = genmc_ctx.exec_state.genmc_shared_allocs_map.borrow()[&scalar.extra]; + // FIXME(genmc,borrow tracking): Borrow tracking not yet supported. + let provenance = machine::Provenance::Concrete { alloc_id, tag: BorTag::default() }; + let ptr = interpret::Pointer::new(provenance, Size::from_bytes(scalar.value)); + interp_ok(Scalar::from_pointer(ptr, &ecx.tcx)) } impl AtomicReadOrd { diff --git a/src/tools/miri/src/concurrency/genmc/mod.rs b/src/tools/miri/src/concurrency/genmc/mod.rs index 0086d3f2bf0bc..bc475c680b5b1 100644 --- a/src/tools/miri/src/concurrency/genmc/mod.rs +++ b/src/tools/miri/src/concurrency/genmc/mod.rs @@ -7,36 +7,36 @@ use genmc_sys::{ }; use rustc_abi::{Align, Size}; use rustc_const_eval::interpret::{AllocId, InterpCx, InterpResult, interp_ok}; +use rustc_data_structures::fx::FxHashMap; use rustc_middle::{throw_machine_stop, throw_ub_format, throw_unsup_format}; // FIXME(genmc,tracing): Implement some work-around for enabling debug/trace level logging (currently disabled statically in rustc). use tracing::{debug, info}; use self::global_allocations::{EvalContextExt as _, GlobalAllocationHandler}; use self::helper::{ - MAX_ACCESS_SIZE, Warnings, emit_warning, genmc_scalar_to_scalar, - maybe_upgrade_compare_exchange_success_orderings, scalar_to_genmc_scalar, to_genmc_rmw_op, + MAX_ACCESS_SIZE, genmc_scalar_to_scalar, maybe_upgrade_compare_exchange_success_orderings, + scalar_to_genmc_scalar, to_genmc_rmw_op, }; use self::run::GenmcMode; use self::thread_id_map::ThreadIdMap; use crate::concurrency::genmc::helper::split_access; +use crate::diagnostics::SpanDedupDiagnostic; use crate::intrinsics::AtomicRmwOp; -use crate::{ - AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, MemoryKind, MiriConfig, - MiriMachine, MiriMemoryKind, NonHaltingDiagnostic, Scalar, TerminationInfo, ThreadId, - ThreadManager, VisitProvenance, VisitWith, -}; +use crate::*; mod config; mod global_allocations; mod helper; mod run; pub(crate) mod scheduling; +mod shims; mod thread_id_map; pub use genmc_sys::GenmcParams; pub use self::config::GenmcConfig; pub use self::run::run_genmc_mode; +pub use self::shims::EvalContextExt as GenmcEvalContextExt; #[derive(Debug)] pub enum ExecutionEndResult { @@ -83,6 +83,9 @@ struct PerExecutionState { /// we cover all possible executions. /// `None` if no thread has called `exit` and the main thread isn't finished yet. exit_status: Cell>, + + /// Allocations in this map have been sent to GenMC, and should thus be kept around, since future loads from GenMC may return this allocation again. + genmc_shared_allocs_map: RefCell>, } impl PerExecutionState { @@ -90,6 +93,7 @@ impl PerExecutionState { self.allow_data_races.replace(false); self.thread_id_manager.borrow_mut().reset(); self.exit_status.set(None); + self.genmc_shared_allocs_map.borrow_mut().clear(); } } @@ -97,18 +101,11 @@ struct GlobalState { /// Keep track of global allocations, to ensure they keep the same address across different executions, even if the order of allocations changes. /// The `AllocId` for globals is stable across executions, so we can use it as an identifier. global_allocations: GlobalAllocationHandler, - - /// Cache for which warnings have already been shown to the user. - /// `None` if warnings are disabled. - warning_cache: Option, } impl GlobalState { - fn new(target_usize_max: u64, print_warnings: bool) -> Self { - Self { - global_allocations: GlobalAllocationHandler::new(target_usize_max), - warning_cache: print_warnings.then(Default::default), - } + fn new(target_usize_max: u64) -> Self { + Self { global_allocations: GlobalAllocationHandler::new(target_usize_max) } } } @@ -203,6 +200,14 @@ impl GenmcCtx { fn get_alloc_data_races(&self) -> bool { self.exec_state.allow_data_races.get() } + + /// Get the GenMC id of the currently active thread. + #[must_use] + fn active_thread_genmc_tid<'tcx>(&self, machine: &MiriMachine<'tcx>) -> i32 { + let thread_infos = self.exec_state.thread_id_manager.borrow(); + let curr_thread = machine.threads.active_thread(); + thread_infos.get_genmc_tid(curr_thread) + } } /// GenMC event handling. These methods are used to inform GenMC about events happening in the program, and to handle scheduling decisions. @@ -266,13 +271,13 @@ impl GenmcCtx { ) -> InterpResult<'tcx, Scalar> { assert!(!self.get_alloc_data_races(), "atomic load with data race checking disabled."); let genmc_old_value = if let Some(scalar) = old_val { - scalar_to_genmc_scalar(ecx, scalar)? + scalar_to_genmc_scalar(ecx, self, scalar)? } else { GenmcScalar::UNINIT }; let read_value = self.handle_load(&ecx.machine, address, size, ordering.to_genmc(), genmc_old_value)?; - genmc_scalar_to_scalar(ecx, read_value, size) + genmc_scalar_to_scalar(ecx, self, read_value, size) } /// Inform GenMC about an atomic store. @@ -289,9 +294,9 @@ impl GenmcCtx { ordering: AtomicWriteOrd, ) -> InterpResult<'tcx, bool> { assert!(!self.get_alloc_data_races(), "atomic store with data race checking disabled."); - let genmc_value = scalar_to_genmc_scalar(ecx, value)?; + let genmc_value = scalar_to_genmc_scalar(ecx, self, value)?; let genmc_old_value = if let Some(scalar) = old_value { - scalar_to_genmc_scalar(ecx, scalar)? + scalar_to_genmc_scalar(ecx, self, scalar)? } else { GenmcScalar::UNINIT }; @@ -312,12 +317,10 @@ impl GenmcCtx { ordering: AtomicFenceOrd, ) -> InterpResult<'tcx> { assert!(!self.get_alloc_data_races(), "atomic fence with data race checking disabled."); - - let thread_infos = self.exec_state.thread_id_manager.borrow(); - let curr_thread = machine.threads.active_thread(); - let genmc_tid = thread_infos.get_genmc_tid(curr_thread); - - self.handle.borrow_mut().pin_mut().handle_fence(genmc_tid, ordering.to_genmc()); + self.handle + .borrow_mut() + .pin_mut() + .handle_fence(self.active_thread_genmc_tid(machine), ordering.to_genmc()); interp_ok(()) } @@ -343,8 +346,8 @@ impl GenmcCtx { size, ordering, to_genmc_rmw_op(atomic_op, is_signed), - scalar_to_genmc_scalar(ecx, rhs_scalar)?, - scalar_to_genmc_scalar(ecx, old_value)?, + scalar_to_genmc_scalar(ecx, self, rhs_scalar)?, + scalar_to_genmc_scalar(ecx, self, old_value)?, ) } @@ -366,8 +369,8 @@ impl GenmcCtx { size, ordering, /* genmc_rmw_op */ RMWBinOp::Xchg, - scalar_to_genmc_scalar(ecx, rhs_scalar)?, - scalar_to_genmc_scalar(ecx, old_value)?, + scalar_to_genmc_scalar(ecx, self, rhs_scalar)?, + scalar_to_genmc_scalar(ecx, self, old_value)?, ) } @@ -405,43 +408,36 @@ impl GenmcCtx { let upgraded_success_ordering = maybe_upgrade_compare_exchange_success_orderings(success, fail); - if let Some(warning_cache) = &self.global_state.warning_cache { - // FIXME(genmc): remove once GenMC supports failure memory ordering in `compare_exchange`. - let (effective_failure_ordering, _) = - upgraded_success_ordering.split_memory_orderings(); - // Return a warning if the actual orderings don't match the upgraded ones. - if success != upgraded_success_ordering || effective_failure_ordering != fail { - emit_warning(ecx, &warning_cache.compare_exchange_failure_ordering, || { - NonHaltingDiagnostic::GenmcCompareExchangeOrderingMismatch { - success_ordering: success, - upgraded_success_ordering, - failure_ordering: fail, - effective_failure_ordering, - } - }); - } - // FIXME(genmc): remove once GenMC implements spurious failures for `compare_exchange_weak`. - if can_fail_spuriously { - emit_warning(ecx, &warning_cache.compare_exchange_weak, || { - NonHaltingDiagnostic::GenmcCompareExchangeWeak - }); - } + // FIXME(genmc): remove once GenMC supports failure memory ordering in `compare_exchange`. + let (effective_failure_ordering, _) = upgraded_success_ordering.split_memory_orderings(); + // Return a warning if the actual orderings don't match the upgraded ones. + if success != upgraded_success_ordering || effective_failure_ordering != fail { + static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new(); + ecx.dedup_diagnostic(&DEDUP, |_first| { + NonHaltingDiagnostic::GenmcCompareExchangeOrderingMismatch { + success_ordering: success, + upgraded_success_ordering, + failure_ordering: fail, + effective_failure_ordering, + } + }); + } + // FIXME(genmc): remove once GenMC implements spurious failures for `compare_exchange_weak`. + if can_fail_spuriously { + static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new(); + ecx.dedup_diagnostic(&DEDUP, |_first| NonHaltingDiagnostic::GenmcCompareExchangeWeak); } debug!( "GenMC: atomic_compare_exchange, address: {address:?}, size: {size:?} (expect: {expected_old_value:?}, new: {new_value:?}, old_value: {old_value:?}, {success:?}, orderings: {fail:?}), can fail spuriously: {can_fail_spuriously}" ); - - let thread_infos = self.exec_state.thread_id_manager.borrow(); - let genmc_tid = thread_infos.get_genmc_tid(ecx.machine.threads.active_thread()); - let cas_result = self.handle.borrow_mut().pin_mut().handle_compare_exchange( - genmc_tid, + self.active_thread_genmc_tid(&ecx.machine), address.bytes(), size.bytes(), - scalar_to_genmc_scalar(ecx, expected_old_value)?, - scalar_to_genmc_scalar(ecx, new_value)?, - scalar_to_genmc_scalar(ecx, old_value)?, + scalar_to_genmc_scalar(ecx, self, expected_old_value)?, + scalar_to_genmc_scalar(ecx, self, new_value)?, + scalar_to_genmc_scalar(ecx, self, old_value)?, upgraded_success_ordering.to_genmc(), fail.to_genmc(), can_fail_spuriously, @@ -452,7 +448,7 @@ impl GenmcCtx { throw_ub_format!("{}", error.to_string_lossy()); } - let return_scalar = genmc_scalar_to_scalar(ecx, cas_result.old_value, size)?; + let return_scalar = genmc_scalar_to_scalar(ecx, self, cas_result.old_value, size)?; debug!( "GenMC: atomic_compare_exchange: result: {cas_result:?}, returning scalar: {return_scalar:?}" ); @@ -597,14 +593,10 @@ impl GenmcCtx { return ecx .get_global_allocation_address(&self.global_state.global_allocations, alloc_id); } - let thread_infos = self.exec_state.thread_id_manager.borrow(); - let curr_thread = machine.threads.active_thread(); - let genmc_tid = thread_infos.get_genmc_tid(curr_thread); // GenMC doesn't support ZSTs, so we set the minimum size to 1 byte let genmc_size = size.bytes().max(1); - let chosen_address = self.handle.borrow_mut().pin_mut().handle_malloc( - genmc_tid, + self.active_thread_genmc_tid(machine), genmc_size, alignment.bytes(), ); @@ -638,11 +630,16 @@ impl GenmcCtx { !self.get_alloc_data_races(), "memory deallocation with data race checking disabled." ); - let thread_infos = self.exec_state.thread_id_manager.borrow(); - let curr_thread = machine.threads.active_thread(); - let genmc_tid = thread_infos.get_genmc_tid(curr_thread); - - self.handle.borrow_mut().pin_mut().handle_free(genmc_tid, address.bytes()); + if self + .handle + .borrow_mut() + .pin_mut() + .handle_free(self.active_thread_genmc_tid(machine), address.bytes()) + { + // FIXME(genmc): improve error handling. + // An error was detected, so we get the error string from GenMC. + throw_ub_format!("{}", self.try_get_error().unwrap()); + } interp_ok(()) } @@ -692,7 +689,7 @@ impl GenmcCtx { let genmc_tid = thread_infos.get_genmc_tid(curr_thread_id); debug!("GenMC: thread {curr_thread_id:?} ({genmc_tid:?}) finished."); - // NOTE: Miri doesn't support return values for threads, but GenMC expects one, so we return 0 + // NOTE: Miri doesn't support return values for threads, but GenMC expects one, so we return 0. self.handle.borrow_mut().pin_mut().handle_thread_finish(genmc_tid, /* ret_val */ 0); } @@ -732,17 +729,6 @@ impl GenmcCtx { self.exec_state.exit_status.set(Some(exit_status)); interp_ok(()) } - - /**** Blocking instructions ****/ - - #[allow(unused)] - pub(crate) fn handle_verifier_assume<'tcx>( - &self, - machine: &MiriMachine<'tcx>, - condition: bool, - ) -> InterpResult<'tcx, ()> { - if condition { interp_ok(()) } else { self.handle_user_block(machine) } - } } impl GenmcCtx { @@ -765,17 +751,12 @@ impl GenmcCtx { "GenMC mode currently does not support atomics larger than {MAX_ACCESS_SIZE} bytes.", ); } - let thread_infos = self.exec_state.thread_id_manager.borrow(); - let curr_thread_id = machine.threads.active_thread(); - let genmc_tid = thread_infos.get_genmc_tid(curr_thread_id); - debug!( - "GenMC: load, thread: {curr_thread_id:?} ({genmc_tid:?}), address: {addr} == {addr:#x}, size: {size:?}, ordering: {memory_ordering:?}, old_value: {genmc_old_value:x?}", + "GenMC: load, address: {addr} == {addr:#x}, size: {size:?}, ordering: {memory_ordering:?}, old_value: {genmc_old_value:x?}", addr = address.bytes() ); - let load_result = self.handle.borrow_mut().pin_mut().handle_load( - genmc_tid, + self.active_thread_genmc_tid(machine), address.bytes(), size.bytes(), memory_ordering, @@ -816,17 +797,12 @@ impl GenmcCtx { "GenMC mode currently does not support atomics larger than {MAX_ACCESS_SIZE} bytes." ); } - let thread_infos = self.exec_state.thread_id_manager.borrow(); - let curr_thread_id = machine.threads.active_thread(); - let genmc_tid = thread_infos.get_genmc_tid(curr_thread_id); - debug!( - "GenMC: store, thread: {curr_thread_id:?} ({genmc_tid:?}), address: {addr} = {addr:#x}, size: {size:?}, ordering {memory_ordering:?}, value: {genmc_value:?}", + "GenMC: store, address: {addr} = {addr:#x}, size: {size:?}, ordering {memory_ordering:?}, value: {genmc_value:?}", addr = address.bytes() ); - let store_result = self.handle.borrow_mut().pin_mut().handle_store( - genmc_tid, + self.active_thread_genmc_tid(machine), address.bytes(), size.bytes(), genmc_value, @@ -867,14 +843,11 @@ impl GenmcCtx { MAX_ACCESS_SIZE, size.bytes() ); - - let curr_thread_id = ecx.machine.threads.active_thread(); - let genmc_tid = self.exec_state.thread_id_manager.borrow().get_genmc_tid(curr_thread_id); debug!( - "GenMC: atomic_rmw_op, thread: {curr_thread_id:?} ({genmc_tid:?}) (op: {genmc_rmw_op:?}, rhs value: {genmc_rhs_scalar:?}), address: {address:?}, size: {size:?}, ordering: {ordering:?}", + "GenMC: atomic_rmw_op (op: {genmc_rmw_op:?}, rhs value: {genmc_rhs_scalar:?}), address: {address:?}, size: {size:?}, ordering: {ordering:?}", ); let rmw_result = self.handle.borrow_mut().pin_mut().handle_read_modify_write( - genmc_tid, + self.active_thread_genmc_tid(&ecx.machine), address.bytes(), size.bytes(), genmc_rmw_op, @@ -888,28 +861,22 @@ impl GenmcCtx { throw_ub_format!("{}", error.to_string_lossy()); } - let old_value_scalar = genmc_scalar_to_scalar(ecx, rmw_result.old_value, size)?; + let old_value_scalar = genmc_scalar_to_scalar(ecx, self, rmw_result.old_value, size)?; let new_value_scalar = if rmw_result.is_coherence_order_maximal_write { - Some(genmc_scalar_to_scalar(ecx, rmw_result.new_value, size)?) + Some(genmc_scalar_to_scalar(ecx, self, rmw_result.new_value, size)?) } else { None }; interp_ok((old_value_scalar, new_value_scalar)) } - - /**** Blocking functionality ****/ - - /// Handle a user thread getting blocked. - /// This may happen due to an manual `assume` statement added by a user - /// or added by some automated program transformation, e.g., for spinloops. - fn handle_user_block<'tcx>(&self, _machine: &MiriMachine<'tcx>) -> InterpResult<'tcx> { - todo!() - } } impl VisitProvenance for GenmcCtx { - fn visit_provenance(&self, _visit: &mut VisitWith<'_>) { - // We don't have any tags. + fn visit_provenance(&self, visit: &mut VisitWith<'_>) { + let genmc_shared_allocs_map = self.exec_state.genmc_shared_allocs_map.borrow(); + for alloc_id in genmc_shared_allocs_map.values().copied() { + visit(Some(alloc_id), None); + } } } diff --git a/src/tools/miri/src/concurrency/genmc/run.rs b/src/tools/miri/src/concurrency/genmc/run.rs index 33c5b6b0a005f..6721a38c683fb 100644 --- a/src/tools/miri/src/concurrency/genmc/run.rs +++ b/src/tools/miri/src/concurrency/genmc/run.rs @@ -16,13 +16,6 @@ pub(super) enum GenmcMode { Verification, } -impl GenmcMode { - /// Return whether warnings on unsupported features should be printed in this mode. - fn print_unsupported_warnings(self) -> bool { - self == GenmcMode::Verification - } -} - /// Do a complete run of the program in GenMC mode. /// This will call `eval_entry` multiple times, until either: /// - An error is detected (indicated by a `None` return value) @@ -57,8 +50,7 @@ fn run_genmc_mode_impl<'tcx>( // There exists only one `global_state` per full run in GenMC mode. // It is shared by all `GenmcCtx` in this run. // FIXME(genmc): implement multithreading once GenMC supports it. - let global_state = - Arc::new(GlobalState::new(tcx.target_usize_max(), mode.print_unsupported_warnings())); + let global_state = Arc::new(GlobalState::new(tcx.target_usize_max())); let genmc_ctx = Rc::new(GenmcCtx::new(config, global_state, mode)); // `rep` is used to report the progress, Miri will panic on wrap-around. diff --git a/src/tools/miri/src/concurrency/genmc/scheduling.rs b/src/tools/miri/src/concurrency/genmc/scheduling.rs index b5c23f2d0845f..6ccbaf4f2482b 100644 --- a/src/tools/miri/src/concurrency/genmc/scheduling.rs +++ b/src/tools/miri/src/concurrency/genmc/scheduling.rs @@ -1,58 +1,122 @@ use genmc_sys::{ActionKind, ExecutionState}; +use rustc_middle::mir::TerminatorKind; +use rustc_middle::ty::{self, Ty}; use super::GenmcCtx; use crate::{ InterpCx, InterpResult, MiriMachine, TerminationInfo, ThreadId, interp_ok, throw_machine_stop, }; +enum NextInstrKind { + MaybeAtomic(ActionKind), + NonAtomic, +} + +/// Check if a call or tail-call could have atomic load semantics. +fn get_next_instruction_kind<'tcx>( + ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, +) -> InterpResult<'tcx, NextInstrKind> { + use NextInstrKind::*; + + let thread_manager = &ecx.machine.threads; + + // Determine whether the next instruction in the current thread might be a load. + // This is used for the "writes-first" scheduling in GenMC. + // Scheduling writes before reads can be beneficial for verification performance. + // `Load` is a safe default for the next instruction type if we cannot guarantee that it isn't a load. + if !thread_manager.active_thread_ref().is_enabled() { + // The current thread can get blocked (e.g., due to a thread join, `Mutex::lock`, assume statement, ...), then we need to ask GenMC for another thread to schedule. + // Most to all blocking operations have load semantics, since they wait on something to change in another thread, + // e.g., a thread join waiting on another thread to finish (join loads the return value(s) of the other thread), + // or a thread waiting for another thread to unlock a `Mutex`, which loads the mutex state (Locked, Unlocked). + // `Load` is a safe default for the next instruction type, since we may not know what the next instruction is. + return interp_ok(MaybeAtomic(ActionKind::Load)); + } + // This thread is still enabled. If it executes a terminator next, we consider yielding, + // but in all other cases we just keep running this thread since it never makes sense + // to yield before a non-atomic operation. + let Some(frame) = thread_manager.active_thread_stack().last() else { + return interp_ok(NonAtomic); + }; + let either::Either::Left(loc) = frame.current_loc() else { + // We are unwinding, so the next step is definitely not atomic. + return interp_ok(NonAtomic); + }; + let basic_block = &frame.body().basic_blocks[loc.block]; + if let Some(_statement) = basic_block.statements.get(loc.statement_index) { + // Statements can't be atomic. + return interp_ok(NonAtomic); + } + match &basic_block.terminator().kind { + // All atomics are modeled as function calls to intrinsic functions. + // The one exception is thread joining, but those are also calls. + TerminatorKind::Call { func, .. } | TerminatorKind::TailCall { func, .. } => + get_function_kind(ecx, func.ty(&frame.body().local_decls, *ecx.tcx)), + // Non-call terminators are not atomic. + _ => interp_ok(NonAtomic), + } +} + +fn get_function_kind<'tcx>( + ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, + func_ty: Ty<'tcx>, +) -> InterpResult<'tcx, NextInstrKind> { + use NextInstrKind::*; + let callee_def_id = match func_ty.kind() { + ty::FnDef(def_id, _args) => *def_id, + _ => return interp_ok(MaybeAtomic(ActionKind::Load)), // we don't know the callee, might be pthread_join + }; + let Some(intrinsic_def) = ecx.tcx.intrinsic(callee_def_id) else { + if ecx.tcx.is_foreign_item(callee_def_id) { + // Some shims, like pthread_join, must be considered loads. So just consider them all loads, + // these calls are not *that* common. + return interp_ok(MaybeAtomic(ActionKind::Load)); + } + // NOTE: Functions intercepted by Miri in `concurrency/genmc/intercep.rs` must also be added here. + // Such intercepted functions, like `sys::Mutex::lock`, should be treated as atomics to ensure we call the scheduler when we encounter one of them. + // These functions must also be classified whether they may have load semantics. + if ecx.tcx.is_diagnostic_item(rustc_span::sym::sys_mutex_lock, callee_def_id) + || ecx.tcx.is_diagnostic_item(rustc_span::sym::sys_mutex_try_lock, callee_def_id) + { + return interp_ok(MaybeAtomic(ActionKind::Load)); + } else if ecx.tcx.is_diagnostic_item(rustc_span::sym::sys_mutex_unlock, callee_def_id) { + return interp_ok(MaybeAtomic(ActionKind::NonLoad)); + } + // The next step is a call to a regular Rust function. + return interp_ok(NonAtomic); + }; + let intrinsic_name = intrinsic_def.name.as_str(); + let Some(suffix) = intrinsic_name.strip_prefix("atomic_") else { + return interp_ok(NonAtomic); // Non-atomic intrinsic, so guaranteed not an atomic load + }; + // `atomic_store`, `atomic_fence` and `atomic_singlethreadfence` are not considered loads. + // Any future `atomic_*` intrinsics may have load semantics, so we err on the side of caution and classify them as "maybe loads". + interp_ok(MaybeAtomic(if matches!(suffix, "store" | "fence" | "singlethreadfence") { + ActionKind::NonLoad + } else { + ActionKind::Load + })) +} + impl GenmcCtx { + /// Returns the thread ID of the next thread to schedule, or `None` to continue with the current thread. pub(crate) fn schedule_thread<'tcx>( &self, ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, - ) -> InterpResult<'tcx, ThreadId> { - let thread_manager = &ecx.machine.threads; - let active_thread_id = thread_manager.active_thread(); - - // Determine whether the next instruction in the current thread might be a load. - // This is used for the "writes-first" scheduling in GenMC. - // Scheduling writes before reads can be beneficial for verification performance. - // `Load` is a safe default for the next instruction type if we cannot guarantee that it isn't a load. - let curr_thread_next_instr_kind = if !thread_manager.active_thread_ref().is_enabled() { - // The current thread can get blocked (e.g., due to a thread join, `Mutex::lock`, assume statement, ...), then we need to ask GenMC for another thread to schedule. - // Most to all blocking operations have load semantics, since they wait on something to change in another thread, - // e.g., a thread join waiting on another thread to finish (join loads the return value(s) of the other thread), - // or a thread waiting for another thread to unlock a `Mutex`, which loads the mutex state (Locked, Unlocked). - ActionKind::Load - } else { - // This thread is still enabled. If it executes a terminator next, we consider yielding, - // but in all other cases we just keep running this thread since it never makes sense - // to yield before a non-atomic operation. - let Some(frame) = thread_manager.active_thread_stack().last() else { - return interp_ok(active_thread_id); - }; - let either::Either::Left(loc) = frame.current_loc() else { - // We are unwinding, so the next step is definitely not atomic. - return interp_ok(active_thread_id); - }; - let basic_block = &frame.body().basic_blocks[loc.block]; - if let Some(_statement) = basic_block.statements.get(loc.statement_index) { - // Statements can't be atomic. - return interp_ok(active_thread_id); - } - - // FIXME(genmc): determine terminator kind. - ActionKind::Load + ) -> InterpResult<'tcx, Option> { + let atomic_kind = match get_next_instruction_kind(ecx)? { + NextInstrKind::MaybeAtomic(atomic_kind) => atomic_kind, + NextInstrKind::NonAtomic => return interp_ok(None), // No need to reschedule on a non-atomic. }; + let active_thread_id = ecx.machine.threads.active_thread(); let thread_infos = self.exec_state.thread_id_manager.borrow(); let genmc_tid = thread_infos.get_genmc_tid(active_thread_id); - let mut mc = self.handle.borrow_mut(); - let pinned_mc = mc.as_mut().unwrap(); - let result = pinned_mc.schedule_next(genmc_tid, curr_thread_next_instr_kind); + let result = self.handle.borrow_mut().pin_mut().schedule_next(genmc_tid, atomic_kind); // Depending on the exec_state, we either schedule the given thread, or we are finished with this execution. match result.exec_state { - ExecutionState::Ok => interp_ok(thread_infos.get_miri_tid(result.next_thread)), + ExecutionState::Ok => interp_ok(Some(thread_infos.get_miri_tid(result.next_thread))), ExecutionState::Blocked => throw_machine_stop!(TerminationInfo::GenmcBlockedExecution), ExecutionState::Finished => { let exit_status = self.exec_state.exit_status.get().expect( @@ -63,6 +127,14 @@ impl GenmcCtx { leak_check: exit_status.do_leak_check() }); } + ExecutionState::Error => { + // GenMC found an error in one of the `handle_*` functions, but didn't return the detected error from the function immediately. + // This is still an bug in the user program, so we print the error string. + panic!( + "GenMC found an error ({:?}), but didn't report it immediately, so we cannot provide an appropriate source code location for where it happened.", + self.try_get_error().unwrap() + ); + } _ => unreachable!(), } } diff --git a/src/tools/miri/src/concurrency/genmc/shims.rs b/src/tools/miri/src/concurrency/genmc/shims.rs new file mode 100644 index 0000000000000..4685dfd1b8dd7 --- /dev/null +++ b/src/tools/miri/src/concurrency/genmc/shims.rs @@ -0,0 +1,234 @@ +use genmc_sys::AssumeType; +use rustc_middle::ty; +use tracing::debug; + +use crate::concurrency::genmc::MAX_ACCESS_SIZE; +use crate::concurrency::thread::EvalContextExt as _; +use crate::*; + +impl GenmcCtx { + /// Handle a user thread getting blocked. + /// This may happen due to an manual `assume` statement added by a user + /// or added by some automated program transformation, e.g., for spinloops. + fn handle_assume_block<'tcx>( + &self, + machine: &MiriMachine<'tcx>, + assume_type: AssumeType, + ) -> InterpResult<'tcx> { + debug!("GenMC: assume statement, blocking active thread."); + self.handle + .borrow_mut() + .pin_mut() + .handle_assume_block(self.active_thread_genmc_tid(machine), assume_type); + interp_ok(()) + } +} + +// Handling of code intercepted by Miri in GenMC mode, such as assume statement or `std::sync::Mutex`. + +impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {} +trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { + /// Small helper to get the arguments of an intercepted function call. + fn get_fn_args( + &self, + instance: ty::Instance<'tcx>, + args: &[FnArg<'tcx>], + ) -> InterpResult<'tcx, [OpTy<'tcx>; N]> { + let this = self.eval_context_ref(); + let args = this.copy_fn_args(args); // FIXME: Should `InPlace` arguments be reset to uninit? + if let Ok(ops) = args.try_into() { + return interp_ok(ops); + } + panic!("{} is a diagnostic item expected to have {} arguments", instance, N); + } + + /**** Blocking functionality ****/ + + /// Handle a thread getting blocked by a user assume (not an automatically generated assume). + /// Unblocking this thread in the current execution will cause a panic. + /// Miri does not provide GenMC with the annotations to determine when to unblock the thread, so it should never be unblocked. + fn handle_user_assume_block(&mut self) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + debug!( + "GenMC: block thread {:?} due to failing assume statement.", + this.machine.threads.active_thread() + ); + assert!(this.machine.threads.active_thread_ref().is_enabled()); + // Block the thread on the GenMC side. + let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap(); + genmc_ctx.handle_assume_block(&this.machine, AssumeType::User)?; + // Block the thread on the Miri side. + this.block_thread( + BlockReason::Genmc, + None, + callback!( + @capture<'tcx> {} + |_this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::Ready); + unreachable!("GenMC should never unblock a thread blocked by an `assume`."); + } + ), + ); + interp_ok(()) + } + + fn intercept_mutex_lock(&mut self, mutex: MPlaceTy<'tcx>) -> InterpResult<'tcx> { + debug!("GenMC: handling Mutex::lock()"); + let this = self.eval_context_mut(); + let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap(); + + let size = mutex.layout.size.bytes(); + assert!( + size <= MAX_ACCESS_SIZE, + "Mutex is larger than maximal size of a memory access supported by GenMC ({size} > {MAX_ACCESS_SIZE})" + ); + let result = genmc_ctx.handle.borrow_mut().pin_mut().handle_mutex_lock( + genmc_ctx.active_thread_genmc_tid(&this.machine), + mutex.ptr().addr().bytes(), + size, + ); + if let Some(error) = result.error.as_ref() { + // FIXME(genmc): improve error handling. + throw_ub_format!("{}", error.to_string_lossy()); + } + if result.is_reset { + debug!("GenMC: Mutex::lock: Reset"); + // GenMC informed us to reset and try the lock again later. + // We block the current thread until GenMC schedules it again. + this.block_thread( + crate::BlockReason::Genmc, + None, + crate::callback!( + @capture<'tcx> { + mutex: MPlaceTy<'tcx>, + } + |this, unblock: crate::UnblockKind| { + debug!("GenMC: Mutex::lock: unblocking callback called, attempting to lock the Mutex again."); + assert_eq!(unblock, crate::UnblockKind::Ready); + this.intercept_mutex_lock(mutex)?; + interp_ok(()) + } + ), + ); + } else if result.is_lock_acquired { + debug!("GenMC: Mutex::lock successfully acquired the Mutex."); + } else { + debug!("GenMC: Mutex::lock failed to acquire the Mutex, permanently blocking thread."); + // NOTE: `handle_mutex_lock` already blocked the current thread on the GenMC side. + this.block_thread( + crate::BlockReason::Genmc, + None, + crate::callback!( + @capture<'tcx> { + mutex: MPlaceTy<'tcx>, + } + |_this, _unblock: crate::UnblockKind| { + unreachable!("A thread blocked on `Mutex::lock` should not be unblocked again."); + } + ), + ); + } + // NOTE: We don't write anything back to Miri's memory where the Mutex is located, that state is handled only by GenMC. + interp_ok(()) + } + + fn intercept_mutex_try_lock( + &mut self, + mutex: MPlaceTy<'tcx>, + dest: &crate::PlaceTy<'tcx>, + ) -> InterpResult<'tcx> { + debug!("GenMC: handling Mutex::try_lock()"); + let this = self.eval_context_mut(); + let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap(); + let size = mutex.layout.size.bytes(); + assert!( + size <= MAX_ACCESS_SIZE, + "Mutex is larger than maximal size of a memory access supported by GenMC ({size} > {MAX_ACCESS_SIZE})" + ); + let result = genmc_ctx.handle.borrow_mut().pin_mut().handle_mutex_try_lock( + genmc_ctx.active_thread_genmc_tid(&this.machine), + mutex.ptr().addr().bytes(), + size, + ); + if let Some(error) = result.error.as_ref() { + // FIXME(genmc): improve error handling. + throw_ub_format!("{}", error.to_string_lossy()); + } + debug!( + "GenMC: Mutex::try_lock(): is_reset: {}, is_lock_acquired: {}", + result.is_reset, result.is_lock_acquired + ); + assert!(!result.is_reset, "GenMC returned 'reset' for a mutex try_lock."); + // Write the return value of try_lock, i.e., whether we acquired the mutex. + this.write_scalar(Scalar::from_bool(result.is_lock_acquired), dest)?; + // NOTE: We don't write anything back to Miri's memory where the Mutex is located, that state is handled only by GenMC. + interp_ok(()) + } + + fn intercept_mutex_unlock(&self, mutex: MPlaceTy<'tcx>) -> InterpResult<'tcx> { + debug!("GenMC: handling Mutex::unlock()"); + let this = self.eval_context_ref(); + let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap(); + let result = genmc_ctx.handle.borrow_mut().pin_mut().handle_mutex_unlock( + genmc_ctx.active_thread_genmc_tid(&this.machine), + mutex.ptr().addr().bytes(), + mutex.layout.size.bytes(), + ); + if let Some(error) = result.error.as_ref() { + // FIXME(genmc): improve error handling. + throw_ub_format!("{}", error.to_string_lossy()); + } + // NOTE: We don't write anything back to Miri's memory where the Mutex is located, that state is handled only by GenMC.} + interp_ok(()) + } +} + +impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} +pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + /// Given a `ty::Instance<'tcx>`, do any required special handling. + /// Returns true if this `instance` should be skipped (i.e., no MIR should be executed for it). + fn genmc_intercept_function( + &mut self, + instance: rustc_middle::ty::Instance<'tcx>, + args: &[rustc_const_eval::interpret::FnArg<'tcx, crate::Provenance>], + dest: &crate::PlaceTy<'tcx>, + ) -> InterpResult<'tcx, bool> { + let this = self.eval_context_mut(); + assert!( + this.machine.data_race.as_genmc_ref().is_some(), + "This function should only be called in GenMC mode." + ); + + // NOTE: When adding new intercepted functions here, they must also be added to `fn get_function_kind` in `concurrency/genmc/scheduling.rs`. + use rustc_span::sym; + if this.tcx.is_diagnostic_item(sym::sys_mutex_lock, instance.def_id()) { + let [mutex] = this.get_fn_args(instance, args)?; + let mutex = this.deref_pointer(&mutex)?; + this.intercept_mutex_lock(mutex)?; + } else if this.tcx.is_diagnostic_item(sym::sys_mutex_try_lock, instance.def_id()) { + let [mutex] = this.get_fn_args(instance, args)?; + let mutex = this.deref_pointer(&mutex)?; + this.intercept_mutex_try_lock(mutex, dest)?; + } else if this.tcx.is_diagnostic_item(sym::sys_mutex_unlock, instance.def_id()) { + let [mutex] = this.get_fn_args(instance, args)?; + let mutex = this.deref_pointer(&mutex)?; + this.intercept_mutex_unlock(mutex)?; + } else { + // Nothing to intercept. + return interp_ok(false); + } + interp_ok(true) + } + + /// Handle an `assume` statement. This will tell GenMC to block the current thread if the `condition` is false. + /// Returns `true` if the current thread should be blocked in Miri too. + fn handle_genmc_verifier_assume(&mut self, condition: &OpTy<'tcx>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let condition_bool = this.read_scalar(condition)?.to_bool()?; + debug!("GenMC: handle_genmc_verifier_assume, condition: {condition:?} = {condition_bool}"); + if condition_bool { + return interp_ok(()); + } + this.handle_user_assume_block() + } +} diff --git a/src/tools/miri/src/concurrency/mod.rs b/src/tools/miri/src/concurrency/mod.rs index 9dae858592f1f..b20a17dd6989e 100644 --- a/src/tools/miri/src/concurrency/mod.rs +++ b/src/tools/miri/src/concurrency/mod.rs @@ -22,5 +22,5 @@ pub mod weak_memory; mod genmc; pub use self::data_race_handler::{AllocDataRaceHandler, GlobalDataRaceHandler}; -pub use self::genmc::{ExitType, GenmcConfig, GenmcCtx, run_genmc_mode}; +pub use self::genmc::{ExitType, GenmcConfig, GenmcCtx, GenmcEvalContextExt, run_genmc_mode}; pub use self::vector_clock::VClock; diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 00c5e337b1e9e..13492c99294ea 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -110,6 +110,9 @@ pub enum BlockReason { Eventfd, /// Blocked on unnamed_socket. UnnamedSocket, + /// Blocked for any reason related to GenMC, such as `assume` statements (GenMC mode only). + /// Will be implicitly unblocked when GenMC schedules this thread again. + Genmc, } /// The state of a thread. @@ -260,7 +263,7 @@ impl<'tcx> Thread<'tcx> { self.top_user_relevant_frame.or_else(|| self.stack.len().checked_sub(1)) } - pub fn current_span(&self) -> Span { + pub fn current_user_relevant_span(&self) -> Span { self.top_user_relevant_frame() .map(|frame_idx| self.stack[frame_idx].current_span()) .unwrap_or(rustc_span::DUMMY_SP) @@ -572,6 +575,7 @@ impl<'tcx> ThreadManager<'tcx> { /// See : /// > The handle is valid until closed, even after the thread it represents has been terminated. fn detach_thread(&mut self, id: ThreadId, allow_terminated_joined: bool) -> InterpResult<'tcx> { + // NOTE: In GenMC mode, we treat detached threads like regular threads that are never joined, so there is no special handling required here. trace!("detaching {:?}", id); let is_ub = if allow_terminated_joined && self.threads[id].state.is_terminated() { @@ -704,14 +708,31 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); // In GenMC mode, we let GenMC do the scheduling. - if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() { - let next_thread_id = genmc_ctx.schedule_thread(this)?; - - let thread_manager = &mut this.machine.threads; - thread_manager.active_thread = next_thread_id; - - assert!(thread_manager.threads[thread_manager.active_thread].state.is_enabled()); - return interp_ok(SchedulingAction::ExecuteStep); + if this.machine.data_race.as_genmc_ref().is_some() { + loop { + let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap(); + let Some(next_thread_id) = genmc_ctx.schedule_thread(this)? else { + return interp_ok(SchedulingAction::ExecuteStep); + }; + // If a thread is blocked on GenMC, we have to implicitly unblock it when it gets scheduled again. + if this.machine.threads.threads[next_thread_id] + .state + .is_blocked_on(BlockReason::Genmc) + { + info!( + "GenMC: scheduling blocked thread {next_thread_id:?}, so we unblock it now." + ); + this.unblock_thread(next_thread_id, BlockReason::Genmc)?; + } + // The thread we just unblocked may have been blocked again during the unblocking callback. + // In that case, we need to ask for a different thread to run next. + let thread_manager = &mut this.machine.threads; + if thread_manager.threads[next_thread_id].state.is_enabled() { + // Set the new active thread. + thread_manager.active_thread = next_thread_id; + return interp_ok(SchedulingAction::ExecuteStep); + } + } } // We are not in GenMC mode, so we control the scheduling. @@ -856,7 +877,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let mut state = tls::TlsDtorsState::default(); Box::new(move |m| state.on_stack_empty(m)) }); - let current_span = this.machine.current_span(); + let current_span = this.machine.current_user_relevant_span(); match &mut this.machine.data_race { GlobalDataRaceHandler::None => {} GlobalDataRaceHandler::Vclocks(data_race) => diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 15f7ccbabca6a..d3486dcbb19c9 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -1,9 +1,11 @@ use std::fmt::{self, Write}; use std::num::NonZero; +use std::sync::Mutex; use rustc_abi::{Align, Size}; use rustc_errors::{Diag, DiagMessage, Level}; -use rustc_span::{DUMMY_SP, SpanData, Symbol}; +use rustc_hash::FxHashSet; +use rustc_span::{DUMMY_SP, Span, SpanData, Symbol}; use crate::borrow_tracker::stacked_borrows::diagnostics::TagHistory; use crate::borrow_tracker::tree_borrows::diagnostics as tree_diagnostics; @@ -835,4 +837,45 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &this.machine, ); } + + /// Call `f` only if this is the first time we are seeing this span. + /// The `first` parameter indicates whether this is the first time *ever* that this diagnostic + /// is emitted. + fn dedup_diagnostic( + &self, + dedup: &SpanDedupDiagnostic, + f: impl FnOnce(/*first*/ bool) -> NonHaltingDiagnostic, + ) { + let this = self.eval_context_ref(); + // We want to deduplicate both based on where the error seems to be located "from the user + // perspective", and the location of the actual operation (to avoid warning about the same + // operation called from different places in the local code). + let span1 = this.machine.current_user_relevant_span(); + // For the "location of the operation", we still skip `track_caller` frames, to match the + // span that the diagnostic will point at. + let span2 = this + .active_thread_stack() + .iter() + .rev() + .find(|frame| !frame.instance().def.requires_caller_location(*this.tcx)) + .map(|frame| frame.current_span()) + .unwrap_or(span1); + + let mut lock = dedup.0.lock().unwrap(); + let first = lock.is_empty(); + // Avoid mutating the hashset unless both spans are new. + if !lock.contains(&span2) && lock.insert(span1) && (span1 == span2 || lock.insert(span2)) { + // Both of the two spans were newly inserted. + this.emit_diagnostic(f(first)); + } + } +} + +/// Helps deduplicate a diagnostic to ensure it is only shown once per span. +pub struct SpanDedupDiagnostic(Mutex>); + +impl SpanDedupDiagnostic { + pub const fn new() -> Self { + Self(Mutex::new(FxHashSet::with_hasher(rustc_hash::FxBuildHasher))) + } } diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index d6646f9586aa6..ab2804fae0d7e 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -1,10 +1,12 @@ use std::num::NonZero; +use std::sync::Mutex; use std::time::Duration; use std::{cmp, iter}; use rand::RngCore; use rustc_abi::{Align, ExternAbi, FieldIdx, FieldsShape, Size, Variants}; use rustc_apfloat::Float; +use rustc_hash::FxHashSet; use rustc_hir::Safety; use rustc_hir::def::{DefKind, Namespace}; use rustc_hir::def_id::{CRATE_DEF_INDEX, CrateNum, DefId, LOCAL_CRATE}; @@ -649,7 +651,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match reject_with { RejectOpWith::Abort => isolation_abort_error(op_name), RejectOpWith::WarningWithoutBacktrace => { - let mut emitted_warnings = this.machine.reject_in_isolation_warned.borrow_mut(); + // Deduplicate these warnings *by shim* (not by span) + static DEDUP: Mutex> = + Mutex::new(FxHashSet::with_hasher(rustc_hash::FxBuildHasher)); + let mut emitted_warnings = DEDUP.lock().unwrap(); if !emitted_warnings.contains(op_name) { // First time we are seeing this. emitted_warnings.insert(op_name.to_owned()); @@ -1058,8 +1063,8 @@ impl<'tcx> MiriMachine<'tcx> { /// `#[track_caller]`. /// This function is backed by a cache, and can be assumed to be very fast. /// It will work even when the stack is empty. - pub fn current_span(&self) -> Span { - self.threads.active_thread_ref().current_span() + pub fn current_user_relevant_span(&self) -> Span { + self.threads.active_thread_ref().current_user_relevant_span() } /// Returns the span of the *caller* of the current operation, again diff --git a/src/tools/miri/src/intrinsics/simd.rs b/src/tools/miri/src/intrinsics/simd.rs index 5f75657e0a220..1e7366b5a8269 100644 --- a/src/tools/miri/src/intrinsics/simd.rs +++ b/src/tools/miri/src/intrinsics/simd.rs @@ -1,7 +1,7 @@ use rand::Rng; use rustc_apfloat::Float; -use rustc_middle::ty::FloatTy; use rustc_middle::ty; +use rustc_middle::ty::FloatTy; use super::check_intrinsic_arg_count; use crate::helpers::{ToHost, ToSoft}; @@ -79,7 +79,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } FloatTy::F128 => unimplemented!("f16_f128"), }; - + this.write_scalar(val, &dest)?; } } diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 1f82f154b0b3f..07af4dcaad115 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -60,6 +60,7 @@ extern crate rustc_ast; extern crate rustc_const_eval; extern crate rustc_data_structures; extern crate rustc_errors; +extern crate rustc_hash; extern crate rustc_hir; extern crate rustc_index; extern crate rustc_middle; @@ -109,6 +110,7 @@ pub type StrictPointer = interpret::Pointer; pub type Scalar = interpret::Scalar; pub type ImmTy<'tcx> = interpret::ImmTy<'tcx, machine::Provenance>; pub type OpTy<'tcx> = interpret::OpTy<'tcx, machine::Provenance>; +pub type FnArg<'tcx> = interpret::FnArg<'tcx, machine::Provenance>; pub type PlaceTy<'tcx> = interpret::PlaceTy<'tcx, machine::Provenance>; pub type MPlaceTy<'tcx> = interpret::MPlaceTy<'tcx, machine::Provenance>; diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 412640a112c09..49c2351665367 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -31,7 +31,9 @@ use rustc_target::callconv::FnAbi; use crate::alloc_addresses::EvalContextExt; use crate::concurrency::cpu_affinity::{self, CpuAffinityMask}; use crate::concurrency::data_race::{self, NaReadType, NaWriteType}; -use crate::concurrency::{AllocDataRaceHandler, GenmcCtx, GlobalDataRaceHandler, weak_memory}; +use crate::concurrency::{ + AllocDataRaceHandler, GenmcCtx, GenmcEvalContextExt as _, GlobalDataRaceHandler, weak_memory, +}; use crate::*; /// First real-time signal. @@ -649,16 +651,6 @@ pub struct MiriMachine<'tcx> { pub(crate) pthread_rwlock_sanity: Cell, pub(crate) pthread_condvar_sanity: Cell, - /// Remembers whether we already warned about an extern type with Stacked Borrows. - pub(crate) sb_extern_type_warned: Cell, - /// Remember whether we already warned about sharing memory with a native call. - #[allow(unused)] - pub(crate) native_call_mem_warned: Cell, - /// Remembers which shims have already shown the warning about erroring in isolation. - pub(crate) reject_in_isolation_warned: RefCell>, - /// Remembers which int2ptr casts we have already warned about. - pub(crate) int2ptr_warned: RefCell>, - /// Cache for `mangle_internal_symbol`. pub(crate) mangle_internal_symbol_cache: FxHashMap<&'static str, String>, @@ -777,9 +769,8 @@ impl<'tcx> MiriMachine<'tcx> { local_crates, extern_statics: FxHashMap::default(), rng: RefCell::new(rng), - allocator: if !config.native_lib.is_empty() { - Some(Rc::new(RefCell::new(crate::alloc::isolated_alloc::IsolatedAlloc::new()))) - } else { None }, + allocator: (!config.native_lib.is_empty()) + .then(|| Rc::new(RefCell::new(crate::alloc::isolated_alloc::IsolatedAlloc::new()))), tracked_alloc_ids: config.tracked_alloc_ids.clone(), track_alloc_accesses: config.track_alloc_accesses, check_alignment: config.check_alignment, @@ -827,10 +818,6 @@ impl<'tcx> MiriMachine<'tcx> { pthread_mutex_sanity: Cell::new(false), pthread_rwlock_sanity: Cell::new(false), pthread_condvar_sanity: Cell::new(false), - sb_extern_type_warned: Cell::new(false), - native_call_mem_warned: Cell::new(false), - reject_in_isolation_warned: Default::default(), - int2ptr_warned: Default::default(), mangle_internal_symbol_cache: Default::default(), force_intrinsic_fallback: config.force_intrinsic_fallback, float_nondet: config.float_nondet, @@ -920,7 +907,7 @@ impl<'tcx> MiriMachine<'tcx> { &ecx.machine.threads, size, kind, - ecx.machine.current_span(), + ecx.machine.current_user_relevant_span(), ), data_race.weak_memory.then(weak_memory::AllocState::new_allocation), ), @@ -944,7 +931,7 @@ impl<'tcx> MiriMachine<'tcx> { ecx.machine .allocation_spans .borrow_mut() - .insert(id, (ecx.machine.current_span(), None)); + .insert(id, (ecx.machine.current_user_relevant_span(), None)); } interp_ok(AllocExtra { borrow_tracker, data_race, backtrace, sync: FxHashMap::default() }) @@ -1004,10 +991,6 @@ impl VisitProvenance for MiriMachine<'_> { pthread_mutex_sanity: _, pthread_rwlock_sanity: _, pthread_condvar_sanity: _, - sb_extern_type_warned: _, - native_call_mem_warned: _, - reject_in_isolation_warned: _, - int2ptr_warned: _, mangle_internal_symbol_cache: _, force_intrinsic_fallback: _, float_nondet: _, @@ -1182,7 +1165,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { ecx: &mut MiriInterpCx<'tcx>, instance: ty::Instance<'tcx>, abi: &FnAbi<'tcx, Ty<'tcx>>, - args: &[FnArg<'tcx, Provenance>], + args: &[FnArg<'tcx>], dest: &PlaceTy<'tcx>, ret: Option, unwind: mir::UnwindAction, @@ -1201,6 +1184,13 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { return ecx.emulate_foreign_item(link_name, abi, &args, dest, ret, unwind); } + if ecx.machine.data_race.as_genmc_ref().is_some() + && ecx.genmc_intercept_function(instance, args, dest)? + { + ecx.return_to_block(ret)?; + return interp_ok(None); + } + // Otherwise, load the MIR. let _trace = enter_trace_span!("load_mir"); interp_ok(Some((ecx.load_mir(instance.def, None)?, instance))) @@ -1211,7 +1201,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { ecx: &mut MiriInterpCx<'tcx>, fn_val: DynSym, abi: &FnAbi<'tcx, Ty<'tcx>>, - args: &[FnArg<'tcx, Provenance>], + args: &[FnArg<'tcx>], dest: &PlaceTy<'tcx>, ret: Option, unwind: mir::UnwindAction, @@ -1567,7 +1557,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { } if let Some((_, deallocated_at)) = machine.allocation_spans.borrow_mut().get_mut(&alloc_id) { - *deallocated_at = Some(machine.current_span()); + *deallocated_at = Some(machine.current_user_relevant_span()); } machine.free_alloc_id(alloc_id, size, align, kind); interp_ok(()) diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index eca8cccf5efc4..08964ba7b329f 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -16,6 +16,7 @@ use rustc_target::callconv::FnAbi; use super::alloc::EvalContextExt as _; use super::backtrace::EvalContextExt as _; +use crate::concurrency::GenmcEvalContextExt as _; use crate::helpers::EvalContextExt as _; use crate::*; @@ -485,6 +486,17 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } } + // GenMC mode: Assume statements block the current thread when their condition is false. + "miri_genmc_assume" => { + let [condition] = + this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?; + if this.machine.data_race.as_genmc_ref().is_some() { + this.handle_genmc_verifier_assume(condition)?; + } else { + throw_unsup_format!("miri_genmc_assume is only supported in GenMC mode") + } + } + // Aborting the process. "exit" => { let [code] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; @@ -815,6 +827,23 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { this.mem_copy(ptr_src, ptr_dest, Size::from_bytes(n), true)?; this.write_pointer(ptr_dest, dest)?; } + "memset" => { + let [ptr_dest, val, n] = + this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let ptr_dest = this.read_pointer(ptr_dest)?; + let val = this.read_scalar(val)?.to_i32()?; + let n = this.read_target_usize(n)?; + // The docs say val is "interpreted as unsigned char". + #[expect(clippy::as_conversions)] + let val = val as u8; + + // C requires that this must always be a valid pointer, even if `n` is zero, so we better check that. + this.ptr_get_alloc_id(ptr_dest, 0)?; + + let bytes = std::iter::repeat_n(val, n.try_into().unwrap()); + this.write_bytes_ptr(ptr_dest, bytes)?; + this.write_pointer(ptr_dest, dest)?; + } // LLVM intrinsics "llvm.prefetch" => { diff --git a/src/tools/miri/src/shims/native_lib/mod.rs b/src/tools/miri/src/shims/native_lib/mod.rs index da8f785e37345..47102c30bc3b1 100644 --- a/src/tools/miri/src/shims/native_lib/mod.rs +++ b/src/tools/miri/src/shims/native_lib/mod.rs @@ -1,11 +1,13 @@ //! Implements calling functions from a native library. use std::ops::Deref; +use std::sync::atomic::AtomicBool; use libffi::low::CodePtr; use libffi::middle::Type as FfiType; use rustc_abi::{HasDataLayout, Size}; -use rustc_middle::ty::{self as ty, IntTy, Ty, UintTy}; +use rustc_middle::ty::layout::HasTypingEnv; +use rustc_middle::ty::{self, IntTy, Ty, UintTy}; use rustc_span::Symbol; use serde::{Deserialize, Serialize}; @@ -219,11 +221,9 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // so we cannot assume 1 access = 1 allocation. :( let mut rg = evt_rg.addr..evt_rg.end(); while let Some(curr) = rg.next() { - let Some(alloc_id) = this.alloc_id_from_addr( - curr.to_u64(), - rg.len().try_into().unwrap(), - /* only_exposed_allocations */ true, - ) else { + let Some(alloc_id) = + this.alloc_id_from_addr(curr.to_u64(), rg.len().try_into().unwrap()) + else { throw_ub_format!("Foreign code did an out-of-bounds access!") }; let alloc = this.get_alloc_raw(alloc_id)?; @@ -281,8 +281,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Helper to print a warning when a pointer is shared with the native code. let expose = |prov: Provenance| -> InterpResult<'tcx> { - // The first time this happens, print a warning. - if !this.machine.native_call_mem_warned.replace(true) { + static DEDUP: AtomicBool = AtomicBool::new(false); + if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) { // Newly set, so first time we get here. this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem { tracing }); } @@ -374,15 +374,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { adt_def: ty::AdtDef<'tcx>, args: &'tcx ty::List>, ) -> InterpResult<'tcx, FfiType> { - // TODO: Certain non-C reprs should be okay also. - if !adt_def.repr().c() { - throw_unsup_format!("passing a non-#[repr(C)] struct over FFI: {orig_ty}") - } // TODO: unions, etc. if !adt_def.is_struct() { - throw_unsup_format!( - "unsupported argument type for native call: {orig_ty} is an enum or union" - ); + throw_unsup_format!("passing an enum or union over FFI: {orig_ty}"); + } + // TODO: Certain non-C reprs should be okay also. + if !adt_def.repr().c() { + throw_unsup_format!("passing a non-#[repr(C)] {} over FFI: {orig_ty}", adt_def.descr()) } let this = self.eval_context_ref(); @@ -396,19 +394,24 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Gets the matching libffi type for a given Ty. fn ty_to_ffitype(&self, ty: Ty<'tcx>) -> InterpResult<'tcx, FfiType> { + let this = self.eval_context_ref(); interp_ok(match ty.kind() { ty::Int(IntTy::I8) => FfiType::i8(), ty::Int(IntTy::I16) => FfiType::i16(), ty::Int(IntTy::I32) => FfiType::i32(), ty::Int(IntTy::I64) => FfiType::i64(), ty::Int(IntTy::Isize) => FfiType::isize(), - // the uints ty::Uint(UintTy::U8) => FfiType::u8(), ty::Uint(UintTy::U16) => FfiType::u16(), ty::Uint(UintTy::U32) => FfiType::u32(), ty::Uint(UintTy::U64) => FfiType::u64(), ty::Uint(UintTy::Usize) => FfiType::usize(), - ty::RawPtr(..) => FfiType::pointer(), + ty::RawPtr(pointee_ty, _mut) => { + if !pointee_ty.is_sized(*this.tcx, this.typing_env()) { + throw_unsup_format!("passing a pointer to an unsized type over FFI: {}", ty); + } + FfiType::pointer() + } ty::Adt(adt_def, args) => self.adt_to_ffitype(ty, *adt_def, args)?, _ => throw_unsup_format!("unsupported argument type for native call: {}", ty), }) diff --git a/src/tools/miri/tests/fail-dep/libc/memset_null.rs b/src/tools/miri/tests/fail-dep/libc/memset_null.rs new file mode 100644 index 0000000000000..c3fa9973e762a --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/memset_null.rs @@ -0,0 +1,8 @@ +use std::ptr; + +// null is explicitly called out as UB in the C docs for `memset`. +fn main() { + unsafe { + libc::memset(ptr::null_mut(), 0, 0); //~ERROR: null pointer + } +} diff --git a/src/tools/miri/tests/fail-dep/libc/memset_null.stderr b/src/tools/miri/tests/fail-dep/libc/memset_null.stderr new file mode 100644 index 0000000000000..fdc8f3a29f940 --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/memset_null.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: pointer not dereferenceable: pointer must point to some allocation, but got null pointer + --> tests/fail-dep/libc/memset_null.rs:LL:CC + | +LL | libc::memset(ptr::null_mut(), 0, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = 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-dep/libc/memset_null.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/both_borrows/aliasing_mut1.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.rs index a9efe17fddfab..a8494eaf0aa49 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.rs +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.rs @@ -2,7 +2,7 @@ //@[tree]compile-flags: -Zmiri-tree-borrows use std::mem; -pub fn safe(x: &mut i32, y: &mut i32) { +fn safe(x: &mut i32, y: &mut i32) { //~[stack]^ ERROR: protect *x = 1; //~[tree] ERROR: /write access through .* is forbidden/ *y = 2; diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.stack.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.stack.stderr index b7fdc7bc414e4..196eaeb3fb6c2 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.stack.stderr @@ -1,8 +1,8 @@ error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is strongly protected --> tests/fail/both_borrows/aliasing_mut1.rs:LL:CC | -LL | pub fn safe(x: &mut i32, y: &mut i32) { - | ^ Undefined Behavior occurred here +LL | fn safe(x: &mut i32, y: &mut i32) { + | ^ Undefined Behavior occurred here | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information @@ -14,8 +14,8 @@ LL | let xraw: *mut i32 = unsafe { mem::transmute(&mut x) }; help: is this argument --> tests/fail/both_borrows/aliasing_mut1.rs:LL:CC | -LL | pub fn safe(x: &mut i32, y: &mut i32) { - | ^ +LL | fn safe(x: &mut i32, y: &mut i32) { + | ^ = note: BACKTRACE (of the first span): = note: inside `safe` at tests/fail/both_borrows/aliasing_mut1.rs:LL:CC note: inside `main` diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr index 207ed3131af37..b9e6e25478062 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr @@ -10,13 +10,13 @@ LL | *x = 1; help: the accessed tag was created here, in the initial state Reserved --> tests/fail/both_borrows/aliasing_mut1.rs:LL:CC | -LL | pub fn safe(x: &mut i32, y: &mut i32) { - | ^ +LL | fn safe(x: &mut i32, y: &mut i32) { + | ^ help: the accessed tag later transitioned to Reserved (conflicted) due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4] --> tests/fail/both_borrows/aliasing_mut1.rs:LL:CC | -LL | pub fn safe(x: &mut i32, y: &mut i32) { - | ^ +LL | fn safe(x: &mut i32, y: &mut i32) { + | ^ = help: this transition corresponds to a temporary loss of write permissions until function exit = note: BACKTRACE (of the first span): = note: inside `safe` at tests/fail/both_borrows/aliasing_mut1.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.rs index 74ea2b28627c1..c1320a25cafa4 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.rs +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.rs @@ -2,7 +2,7 @@ //@[tree]compile-flags: -Zmiri-tree-borrows use std::mem; -pub fn safe(x: &i32, y: &mut i32) { +fn safe(x: &i32, y: &mut i32) { //~[stack]^ ERROR: protect let _v = *x; *y = 2; //~[tree] ERROR: /write access through .* is forbidden/ diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.stack.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.stack.stderr index a13cbec66552a..e70e5b10793c1 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.stack.stderr @@ -1,8 +1,8 @@ error: Undefined Behavior: not granting access to tag because that would remove [SharedReadOnly for ] which is strongly protected --> tests/fail/both_borrows/aliasing_mut2.rs:LL:CC | -LL | pub fn safe(x: &i32, y: &mut i32) { - | ^ Undefined Behavior occurred here +LL | fn safe(x: &i32, y: &mut i32) { + | ^ Undefined Behavior occurred here | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information @@ -14,8 +14,8 @@ LL | let xref = &mut x; help: is this argument --> tests/fail/both_borrows/aliasing_mut2.rs:LL:CC | -LL | pub fn safe(x: &i32, y: &mut i32) { - | ^ +LL | fn safe(x: &i32, y: &mut i32) { + | ^ = note: BACKTRACE (of the first span): = note: inside `safe` at tests/fail/both_borrows/aliasing_mut2.rs:LL:CC note: inside `main` diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr index 90b1b1294c7f3..aed59b21f1379 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr @@ -10,8 +10,8 @@ LL | *y = 2; help: the accessed tag was created here, in the initial state Reserved --> tests/fail/both_borrows/aliasing_mut2.rs:LL:CC | -LL | pub fn safe(x: &i32, y: &mut i32) { - | ^ +LL | fn safe(x: &i32, y: &mut i32) { + | ^ help: the accessed tag later transitioned to Reserved (conflicted) due to a foreign read access at offsets [0x0..0x4] --> tests/fail/both_borrows/aliasing_mut2.rs:LL:CC | diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.rs index 8cb60faf2d078..555e4478224c8 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.rs +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.rs @@ -2,7 +2,7 @@ //@[tree]compile-flags: -Zmiri-tree-borrows use std::mem; -pub fn safe(x: &mut i32, y: &i32) { +fn safe(x: &mut i32, y: &i32) { //~[stack]^ ERROR: borrow stack *x = 1; //~[tree] ERROR: /write access through .* is forbidden/ let _v = *y; diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.stack.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.stack.stderr index 0e9382be2e8f8..9980d14e10549 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.stack.stderr @@ -1,8 +1,8 @@ error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location --> tests/fail/both_borrows/aliasing_mut3.rs:LL:CC | -LL | pub fn safe(x: &mut i32, y: &i32) { - | ^ this error occurs as part of function-entry retag at ALLOC[0x0..0x4] +LL | fn safe(x: &mut i32, y: &i32) { + | ^ this error occurs as part of function-entry retag at ALLOC[0x0..0x4] | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr index 73a5027646388..357d7d220192a 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr @@ -10,13 +10,13 @@ LL | *x = 1; help: the accessed tag was created here, in the initial state Reserved --> tests/fail/both_borrows/aliasing_mut3.rs:LL:CC | -LL | pub fn safe(x: &mut i32, y: &i32) { - | ^ +LL | fn safe(x: &mut i32, y: &i32) { + | ^ help: the accessed tag later transitioned to Reserved (conflicted) due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4] --> tests/fail/both_borrows/aliasing_mut3.rs:LL:CC | -LL | pub fn safe(x: &mut i32, y: &i32) { - | ^ +LL | fn safe(x: &mut i32, y: &i32) { + | ^ = help: this transition corresponds to a temporary loss of write permissions until function exit = note: BACKTRACE (of the first span): = note: inside `safe` at tests/fail/both_borrows/aliasing_mut3.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.rs index c656a5096445e..22484972f4d1c 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.rs +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.rs @@ -5,7 +5,7 @@ use std::cell::Cell; use std::mem; // Make sure &mut UnsafeCell also is exclusive -pub fn safe(x: &i32, y: &mut Cell) { +fn safe(x: &i32, y: &mut Cell) { //~[stack]^ ERROR: protect y.set(1); let _load = *x; diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.stack.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.stack.stderr index c5ad269b39acd..eb2514df588a6 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.stack.stderr @@ -1,8 +1,8 @@ error: Undefined Behavior: not granting access to tag because that would remove [SharedReadOnly for ] which is strongly protected --> tests/fail/both_borrows/aliasing_mut4.rs:LL:CC | -LL | pub fn safe(x: &i32, y: &mut Cell) { - | ^ Undefined Behavior occurred here +LL | fn safe(x: &i32, y: &mut Cell) { + | ^ Undefined Behavior occurred here | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information @@ -14,8 +14,8 @@ LL | let xref = &mut x; help: is this argument --> tests/fail/both_borrows/aliasing_mut4.rs:LL:CC | -LL | pub fn safe(x: &i32, y: &mut Cell) { - | ^ +LL | fn safe(x: &i32, y: &mut Cell) { + | ^ = note: BACKTRACE (of the first span): = note: inside `safe` at tests/fail/both_borrows/aliasing_mut4.rs:LL:CC note: inside `main` diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr index a6a6da3fa2aec..c06ae0e92138e 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr @@ -17,8 +17,8 @@ LL | y.set(1); help: the protected tag was created here, in the initial state Frozen --> tests/fail/both_borrows/aliasing_mut4.rs:LL:CC | -LL | pub fn safe(x: &i32, y: &mut Cell) { - | ^ +LL | fn safe(x: &i32, y: &mut Cell) { + | ^ = note: BACKTRACE (of the first span): = note: inside `std::mem::replace::` at RUSTLIB/core/src/mem/mod.rs:LL:CC = note: inside `std::cell::Cell::::replace` at RUSTLIB/core/src/cell.rs:LL:CC diff --git a/src/tools/miri/tests/fail/data_race/alloc_read_race.rs b/src/tools/miri/tests/fail/data_race/alloc_read_race.rs index 7c5116989943e..65622b7f77830 100644 --- a/src/tools/miri/tests/fail/data_race/alloc_read_race.rs +++ b/src/tools/miri/tests/fail/data_race/alloc_read_race.rs @@ -12,7 +12,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { // Shared atomic pointer let pointer = AtomicPtr::new(null_mut::>()); let ptr = EvilSend(&pointer as *const AtomicPtr>); diff --git a/src/tools/miri/tests/fail/data_race/alloc_write_race.rs b/src/tools/miri/tests/fail/data_race/alloc_write_race.rs index ba8a888de9ea0..a07d284c6c8c7 100644 --- a/src/tools/miri/tests/fail/data_race/alloc_write_race.rs +++ b/src/tools/miri/tests/fail/data_race/alloc_write_race.rs @@ -11,7 +11,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { // Shared atomic pointer let pointer = AtomicPtr::new(null_mut::()); let ptr = EvilSend(&pointer as *const AtomicPtr); diff --git a/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.rs b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.rs index 8cce54603ce57..90fd535f1cb6a 100644 --- a/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.rs +++ b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.rs @@ -10,7 +10,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = AtomicUsize::new(0); let b = &mut a as *mut AtomicUsize; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.rs b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.rs index b6c0ef37cb920..8c59713010af6 100644 --- a/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.rs +++ b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.rs @@ -10,7 +10,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = AtomicUsize::new(0); let b = &mut a as *mut AtomicUsize; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.rs index 03ae6895c5745..95b216a6fe649 100644 --- a/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.rs +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.rs @@ -10,7 +10,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = AtomicUsize::new(0); let b = &mut a as *mut AtomicUsize; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.rs index 4a5edf5cc14dd..c4714e632c3ba 100644 --- a/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.rs +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.rs @@ -10,7 +10,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = AtomicUsize::new(0); let b = &mut a as *mut AtomicUsize; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.rs index e8d930a51dee3..3ef60074fd432 100644 --- a/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.rs +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.rs @@ -10,7 +10,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = AtomicUsize::new(0); let b = &mut a as *mut AtomicUsize; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.rs index 4c67d2d765415..b704468165a45 100644 --- a/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.rs +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.rs @@ -10,7 +10,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = AtomicUsize::new(0); let b = &mut a as *mut AtomicUsize; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race1.rs b/src/tools/miri/tests/fail/data_race/dealloc_read_race1.rs index 999cc2392f5af..64bababe0c853 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_read_race1.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race1.rs @@ -16,7 +16,7 @@ extern "Rust" { fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize); } -pub fn main() { +fn main() { // Shared atomic pointer let pointer: *mut usize = Box::into_raw(Box::new(0usize)); let ptr = EvilSend(pointer); diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs index bd3b037e58381..6e85bcf03aa50 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs @@ -16,7 +16,7 @@ extern "Rust" { fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize); } -pub fn main() { +fn main() { // Shared atomic pointer let pointer: *mut usize = Box::into_raw(Box::new(0usize)); let ptr = EvilSend(pointer); diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs b/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs index e3d06660aab34..76c26da057820 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs @@ -12,7 +12,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { // Shared atomic pointer let pointer = AtomicPtr::new(null_mut::()); let ptr = EvilSend(&pointer as *const AtomicPtr); diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race1.rs b/src/tools/miri/tests/fail/data_race/dealloc_write_race1.rs index 90e87f8c49564..fd71ef09b1253 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_write_race1.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race1.rs @@ -15,7 +15,7 @@ extern "Rust" { #[rustc_std_internal_symbol] fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize); } -pub fn main() { +fn main() { // Shared atomic pointer let pointer: *mut usize = Box::into_raw(Box::new(0usize)); let ptr = EvilSend(pointer); diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs index d9b1af80af493..5c8bbc14a4927 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs @@ -15,7 +15,7 @@ extern "Rust" { #[rustc_std_internal_symbol] fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize); } -pub fn main() { +fn main() { // Shared atomic pointer let pointer: *mut usize = Box::into_raw(Box::new(0usize)); let ptr = EvilSend(pointer); diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs index c1ab1942c6884..bdc25100abaf1 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs @@ -12,7 +12,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { // Shared atomic pointer let pointer = AtomicPtr::new(null_mut::()); let ptr = EvilSend(&pointer as *const AtomicPtr); diff --git a/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.rs b/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.rs index 67af6862737dc..4d716f7db6fda 100644 --- a/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.rs +++ b/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.rs @@ -9,7 +9,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { // Enable and then join with multiple threads. let t1 = spawn(|| ()); let t2 = spawn(|| ()); diff --git a/src/tools/miri/tests/fail/data_race/read_write_race.rs b/src/tools/miri/tests/fail/data_race/read_write_race.rs index 2aadef36c5b95..e7961a4d849cc 100644 --- a/src/tools/miri/tests/fail/data_race/read_write_race.rs +++ b/src/tools/miri/tests/fail/data_race/read_write_race.rs @@ -9,7 +9,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = 0u32; let b = &mut a as *mut u32; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs index cca39bb002c17..4555a82df6c97 100644 --- a/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs +++ b/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs @@ -12,7 +12,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { // Shared atomic pointer let pointer = AtomicPtr::new(null_mut::()); let ptr = EvilSend(&pointer as *const AtomicPtr); diff --git a/src/tools/miri/tests/fail/data_race/relax_acquire_race.rs b/src/tools/miri/tests/fail/data_race/relax_acquire_race.rs index 262c039e4ae17..67e1d65126fa3 100644 --- a/src/tools/miri/tests/fail/data_race/relax_acquire_race.rs +++ b/src/tools/miri/tests/fail/data_race/relax_acquire_race.rs @@ -12,7 +12,7 @@ unsafe impl Sync for EvilSend {} static SYNC: AtomicUsize = AtomicUsize::new(0); -pub fn main() { +fn main() { let mut a = 0u32; let b = &mut a as *mut u32; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/release_seq_race.rs b/src/tools/miri/tests/fail/data_race/release_seq_race.rs index 8aeb6ee6ef1d1..5016617e5d7fa 100644 --- a/src/tools/miri/tests/fail/data_race/release_seq_race.rs +++ b/src/tools/miri/tests/fail/data_race/release_seq_race.rs @@ -13,7 +13,7 @@ unsafe impl Sync for EvilSend {} static SYNC: AtomicUsize = AtomicUsize::new(0); -pub fn main() { +fn main() { let mut a = 0u32; let b = &mut a as *mut u32; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.rs b/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.rs index f465160718f45..ae6a6154e3c64 100644 --- a/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.rs +++ b/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.rs @@ -12,7 +12,7 @@ unsafe impl Sync for EvilSend {} static SYNC: AtomicUsize = AtomicUsize::new(0); -pub fn main() { +fn main() { let mut a = 0u32; let b = &mut a as *mut u32; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/rmw_race.rs b/src/tools/miri/tests/fail/data_race/rmw_race.rs index 39588c15ec7ea..51fddbd684724 100644 --- a/src/tools/miri/tests/fail/data_race/rmw_race.rs +++ b/src/tools/miri/tests/fail/data_race/rmw_race.rs @@ -12,7 +12,7 @@ unsafe impl Sync for EvilSend {} static SYNC: AtomicUsize = AtomicUsize::new(0); -pub fn main() { +fn main() { let mut a = 0u32; let b = &mut a as *mut u32; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/write_write_race.rs b/src/tools/miri/tests/fail/data_race/write_write_race.rs index b1a6b08b4c886..2070c43b7cff6 100644 --- a/src/tools/miri/tests/fail/data_race/write_write_race.rs +++ b/src/tools/miri/tests/fail/data_race/write_write_race.rs @@ -9,7 +9,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = 0u32; let b = &mut a as *mut u32; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs index cd21b0a8fa6c1..b92d17bf8bcd2 100644 --- a/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs +++ b/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs @@ -12,7 +12,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { // Shared atomic pointer let pointer = AtomicPtr::new(null_mut::()); let ptr = EvilSend(&pointer as *const AtomicPtr); diff --git a/src/tools/miri/tests/fail/enum-set-discriminant-niche-variant-wrong.rs b/src/tools/miri/tests/fail/enum-set-discriminant-niche-variant-wrong.rs index eca6d908b448b..221ea106538ed 100644 --- a/src/tools/miri/tests/fail/enum-set-discriminant-niche-variant-wrong.rs +++ b/src/tools/miri/tests/fail/enum-set-discriminant-niche-variant-wrong.rs @@ -25,7 +25,7 @@ fn set_discriminant(ptr: &mut Option>) { } } -pub fn main() { +fn main() { let mut v = None; set_discriminant(&mut v); } diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs index 03cca04702fee..b6cda6007536f 100644 --- a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs @@ -6,7 +6,6 @@ //@compile-flags: -Zmiri-disable-validation #![feature(custom_mir, core_intrinsics)] -#![allow(unused)] use std::intrinsics::mir::*; @@ -31,7 +30,8 @@ fn main() { } } -pub fn callee(x: S, mut y: S) { +#[expect(unused_variables, unused_assignments)] +fn callee(x: S, mut y: S) { // With the setup above, if `x` and `y` are both moved, // then writing to `y` will change the value stored in `x`! y.0 = 0; diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.rs index dff724f8d9657..3cb8ee2b407c4 100644 --- a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.rs +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.rs @@ -29,6 +29,6 @@ fn main() { } } -pub fn callee(x: S) -> S { +fn callee(x: S) -> S { x } diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.rs index 4f7a12ebd2e8b..c61083c17a6b2 100644 --- a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.rs +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.rs @@ -22,7 +22,7 @@ fn main() { } } -pub fn callee(x: S, ptr: *mut S) { +fn callee(x: S, ptr: *mut S) { // With the setup above, if `x` is indeed moved in // (i.e. we actually just get a pointer to the underlying storage), // then writing to `ptr` will change the value stored in `x`! diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.rs index cea6c141c4de9..159863c18fc6e 100644 --- a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.rs +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.rs @@ -23,6 +23,6 @@ fn main() { } #[expect(unused_variables, unused_assignments)] -pub fn change_arg(mut x: S) { +fn change_arg(mut x: S) { x.0 = 0; } diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.rs index 581b71499575c..48e2e6255f339 100644 --- a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.rs +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.rs @@ -24,7 +24,7 @@ fn main() { } #[expect(unused_variables, unused_assignments)] -pub fn change_arg(mut x: S, ptr: *mut S) { +fn change_arg(mut x: S, ptr: *mut S) { x.0 = 0; // If `x` got passed in-place, we'd see the write through `ptr`! // Make sure we are not allowed to do that read. diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.rs index dc22e129e18a2..d981286a141aa 100644 --- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.rs +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.rs @@ -7,7 +7,7 @@ use std::intrinsics::mir::*; #[custom_mir(dialect = "runtime", phase = "optimized")] -pub fn main() { +fn main() { mir! { { let _x = 0; diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs index 2fddaf37235b2..a4e48b0aac6fe 100644 --- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs @@ -7,7 +7,7 @@ use std::intrinsics::mir::*; #[custom_mir(dialect = "runtime", phase = "optimized")] -pub fn main() { +fn main() { mir! { { let _x = 0; diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs index 5f3ecb6502273..3390ddad9b857 100644 --- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs @@ -9,7 +9,7 @@ use std::intrinsics::mir::*; #[custom_mir(dialect = "runtime", phase = "optimized")] -pub fn main() { +fn main() { mir! { { let _x = 0; diff --git a/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs b/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs index 200f1062a3e80..0dd5d09abdeab 100644 --- a/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs +++ b/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs @@ -9,7 +9,7 @@ pub unsafe extern "C" fn foo(_y: f32, x: __m256) -> __m256 { x } -pub fn bar(x: __m256) -> __m256 { +fn bar(x: __m256) -> __m256 { // The first and second argument get mixed up here since caller // and callee do not have the same feature flags. // In Miri, we don't have a concept of "dynamically available feature flags", diff --git a/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.rs b/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.rs index 3da54b9188262..8ea029190276a 100644 --- a/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.rs +++ b/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.rs @@ -1,6 +1,6 @@ #![feature(core_intrinsics)] -pub fn main() { +fn main() { unsafe { use std::intrinsics::*; diff --git a/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.rs b/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.rs index 2b68f6713d806..471347a752d28 100644 --- a/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.rs +++ b/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.rs @@ -1,6 +1,6 @@ #![feature(core_intrinsics)] -pub fn main() { +fn main() { unsafe { use std::intrinsics::*; diff --git a/src/tools/miri/tests/fail/issue-miri-1112.rs b/src/tools/miri/tests/fail/issue-miri-1112.rs index 387253a3f9872..fc21fff9a9f92 100644 --- a/src/tools/miri/tests/fail/issue-miri-1112.rs +++ b/src/tools/miri/tests/fail/issue-miri-1112.rs @@ -11,7 +11,7 @@ pub struct Meta { } impl Meta { - pub fn new() -> Self { + fn new() -> Self { Meta { drop_fn: |_| {}, size: 0, align: 1 } } } diff --git a/src/tools/miri/tests/fail/overlapping_assignment.rs b/src/tools/miri/tests/fail/overlapping_assignment.rs index 84994c179f9ea..237d674513f0e 100644 --- a/src/tools/miri/tests/fail/overlapping_assignment.rs +++ b/src/tools/miri/tests/fail/overlapping_assignment.rs @@ -7,7 +7,7 @@ use std::intrinsics::mir::*; // which wants to prevent overlapping assignments... // So we use two separate pointer arguments, and then arrange for them to alias. #[custom_mir(dialect = "runtime", phase = "optimized")] -pub fn self_copy(ptr1: *mut [i32; 4], ptr2: *mut [i32; 4]) { +fn self_copy(ptr1: *mut [i32; 4], ptr2: *mut [i32; 4]) { mir! { { *ptr1 = *ptr2; //~ERROR: overlapping ranges @@ -16,7 +16,7 @@ pub fn self_copy(ptr1: *mut [i32; 4], ptr2: *mut [i32; 4]) { } } -pub fn main() { +fn main() { let mut x = [0; 4]; let ptr = std::ptr::addr_of_mut!(x); self_copy(ptr, ptr); diff --git a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.tree.stderr b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.tree.stderr index 675bb01b5e751..e6c1745d321c1 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.tree.stderr +++ b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.tree.stderr @@ -16,7 +16,7 @@ LL | panic::catch_unwind(move || unsafe { init(argc, argv, sigpipe) }).map_e help: the protected tag was created here, in the initial state Active --> RUSTLIB/std/src/panic.rs:LL:CC | -LL | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { +LL | fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { | ^ = note: BACKTRACE (of the first span): = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tls_macro_leak.rs b/src/tools/miri/tests/fail/tls_macro_leak.rs index b8a4b81911aca..1aa161061c025 100644 --- a/src/tools/miri/tests/fail/tls_macro_leak.rs +++ b/src/tools/miri/tests/fail/tls_macro_leak.rs @@ -2,7 +2,7 @@ use std::cell::Cell; -pub fn main() { +fn main() { thread_local! { static TLS: Cell> = Cell::new(None); } diff --git a/src/tools/miri/tests/fail/tls_static_leak.rs b/src/tools/miri/tests/fail/tls_static_leak.rs index 4d52803363778..4e29b3afe3b19 100644 --- a/src/tools/miri/tests/fail/tls_static_leak.rs +++ b/src/tools/miri/tests/fail/tls_static_leak.rs @@ -6,7 +6,7 @@ use std::cell::Cell; /// Ensure that leaks through `thread_local` statics *not in the main thread* /// are detected. -pub fn main() { +fn main() { #[thread_local] static TLS: Cell> = Cell::new(None); diff --git a/src/tools/miri/tests/fail/tree_borrows/alternate-read-write.rs b/src/tools/miri/tests/fail/tree_borrows/alternate-read-write.rs index fee88cf3486af..8d49a3cdbf5f3 100644 --- a/src/tools/miri/tests/fail/tree_borrows/alternate-read-write.rs +++ b/src/tools/miri/tests/fail/tree_borrows/alternate-read-write.rs @@ -2,7 +2,7 @@ // Check that TB properly rejects alternating Reads and Writes, but tolerates // alternating only Reads to Reserved mutable references. -pub fn main() { +fn main() { let x = &mut 0u8; let y = unsafe { &mut *(x as *mut u8) }; // Foreign Read, but this is a no-op from the point of view of y (still Reserved) diff --git a/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.rs b/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.rs index ff7978776822b..1aefa217e2d47 100644 --- a/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.rs +++ b/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.rs @@ -11,7 +11,7 @@ struct Foo { field2: Cell, } -pub fn main() { +fn main() { let root = Foo { field1: 42, field2: Cell::new(88) }; unsafe { let a = &root; diff --git a/src/tools/miri/tests/fail/tree_borrows/repeated_foreign_read_lazy_conflicted.rs b/src/tools/miri/tests/fail/tree_borrows/repeated_foreign_read_lazy_conflicted.rs index 36b47a33b181e..4f0d97b4a1097 100644 --- a/src/tools/miri/tests/fail/tree_borrows/repeated_foreign_read_lazy_conflicted.rs +++ b/src/tools/miri/tests/fail/tree_borrows/repeated_foreign_read_lazy_conflicted.rs @@ -11,7 +11,7 @@ unsafe fn access_after_sub_1(x: &mut u8, orig_ptr: *mut u8) { *(x as *mut u8).byte_sub(1) = 42; //~ ERROR: /write access through .* is forbidden/ } -pub fn main() { +fn main() { unsafe { let mut alloc = [0u8, 0u8]; let orig_ptr = addr_of_mut!(alloc) as *mut u8; diff --git a/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.rs b/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.rs index a2e8a533c43ad..ac9b7d7e528ee 100644 --- a/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.rs +++ b/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.rs @@ -14,7 +14,7 @@ impl Foo { } } -pub fn main() { +fn main() { let mut f = Foo(0); let alias = &mut f.0 as *mut u64; let res = f.add(unsafe { diff --git a/src/tools/miri/tests/fail/uninit/padding-struct-in-union.rs b/src/tools/miri/tests/fail/uninit/padding-struct-in-union.rs index 132b85828362d..8343952a2046a 100644 --- a/src/tools/miri/tests/fail/uninit/padding-struct-in-union.rs +++ b/src/tools/miri/tests/fail/uninit/padding-struct-in-union.rs @@ -18,7 +18,7 @@ union FooBar { bar: Bar, } -pub fn main() { +fn main() { // Initialize as u8 to ensure padding bytes are zeroed. let mut foobar = FooBar { bar: Bar { bytes: [0u8; 8] } }; // Reading either field is ok. diff --git a/src/tools/miri/tests/fail/validity/invalid_char_cast.rs b/src/tools/miri/tests/fail/validity/invalid_char_cast.rs index 6a590dc7ba10b..94e1a87fba75e 100644 --- a/src/tools/miri/tests/fail/validity/invalid_char_cast.rs +++ b/src/tools/miri/tests/fail/validity/invalid_char_cast.rs @@ -15,7 +15,7 @@ fn cast(ptr: *const char) -> u32 { } } -pub fn main() { +fn main() { let v = u32::MAX; cast(&v as *const u32 as *const char); } diff --git a/src/tools/miri/tests/fail/validity/invalid_char_match.rs b/src/tools/miri/tests/fail/validity/invalid_char_match.rs index 6c2e65b2bb744..6ec5768162be9 100644 --- a/src/tools/miri/tests/fail/validity/invalid_char_match.rs +++ b/src/tools/miri/tests/fail/validity/invalid_char_match.rs @@ -20,7 +20,7 @@ fn switch_int(ptr: *const char) { } } -pub fn main() { +fn main() { let v = u32::MAX; switch_int(&v as *const u32 as *const char); } diff --git a/src/tools/miri/tests/fail/validity/invalid_enum_cast.rs b/src/tools/miri/tests/fail/validity/invalid_enum_cast.rs index ed451a435b958..ba110ca96d693 100644 --- a/src/tools/miri/tests/fail/validity/invalid_enum_cast.rs +++ b/src/tools/miri/tests/fail/validity/invalid_enum_cast.rs @@ -15,7 +15,7 @@ fn cast(ptr: *const E) { } } -pub fn main() { +fn main() { let v = u32::MAX; cast(&v as *const u32 as *const E); } diff --git a/src/tools/miri/tests/fail/weak_memory/weak_uninit.rs b/src/tools/miri/tests/fail/weak_memory/weak_uninit.rs index b4b4b08498773..7a4e038fabf79 100644 --- a/src/tools/miri/tests/fail/weak_memory/weak_uninit.rs +++ b/src/tools/miri/tests/fail/weak_memory/weak_uninit.rs @@ -34,7 +34,7 @@ fn relaxed() { j2.join().unwrap(); } -pub fn main() { +fn main() { // If we try often enough, we should hit UB. for _ in 0..100 { relaxed(); diff --git a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.rs b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.rs new file mode 100644 index 0000000000000..c18675931719f --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.rs @@ -0,0 +1,44 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test that we can detect a double-free bug across two threads, which only shows up if the second thread reads an atomic pointer at a very specific moment. +// GenMC can detect this error consistently, without having to run the buggy code with multiple RNG seeds or in a loop. + +#![no_main] + +#[path = "../../../utils/genmc.rs"] +mod genmc; + +use std::alloc::{Layout, alloc, dealloc}; +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use crate::genmc::*; + +static X: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); + +unsafe fn free(ptr: *mut u64) { + dealloc(ptr as *mut u8, Layout::new::()) //~ ERROR: Undefined Behavior +} + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + let ids = [ + spawn_pthread_closure(|| { + let a: *mut u64 = alloc(Layout::new::()) as *mut u64; + X.store(a, SeqCst); + // We have to yield to the other thread exactly here to reproduce the double-free. + let b = X.swap(std::ptr::null_mut(), SeqCst); + free(b); + }), + spawn_pthread_closure(|| { + let b = X.load(SeqCst); + if !b.is_null() { + free(b); + } + }), + ]; + join_pthreads(ids); + 0 + } +} diff --git a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.stderr b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.stderr new file mode 100644 index 0000000000000..7d03bd9a8eb84 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.stderr @@ -0,0 +1,39 @@ +Running GenMC Verification... +error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling + --> tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC + | +LL | dealloc(ptr as *mut u8, Layout::new::()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC + | +LL | let a: *mut u64 = alloc(Layout::new::()) as *mut u64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC + | +LL | dealloc(ptr as *mut u8, Layout::new::()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span) on thread `unnamed-ID`: + = note: inside `free` at tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC +note: inside closure + --> tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC + | +LL | free(b); + | ^^^^^^^ + = note: inside ` as std::ops::FnOnce<()>>::call_once` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC}>` + --> tests/genmc/fail/atomics/../../../utils/genmc.rs:LL:CC + | +LL | f(); + | ^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.make.stderr b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.make.stderr new file mode 100644 index 0000000000000..d74fa12256bbf --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.make.stderr @@ -0,0 +1,21 @@ +Running GenMC Verification... +error: Undefined Behavior: memory access failed: attempting to access 8 bytes, but got ALLOC-$HEX which points to before the beginning of the allocation + --> tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs:LL:CC + | +LL | *ptr = 44; + | ^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs:LL:CC + | +LL | let mut b = Box::new(0u64); + | ^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `miri_start` at tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs:LL:CC + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs new file mode 100644 index 0000000000000..87223e990bde9 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs @@ -0,0 +1,61 @@ +//@revisions: send make +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test that we can distinguish two pointers with the same address, but different provenance, after they are sent to GenMC and back. +// We have two variants, one where we send such a pointer to GenMC, and one where we make it on the GenMC side. + +#![no_main] +#![feature(box_as_ptr)] + +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + let atomic_ptr = AtomicPtr::new(std::ptr::null_mut()); + let mut a = Box::new(0u64); + let mut b = Box::new(0u64); + let a_ptr: *mut u64 = Box::as_mut_ptr(&mut a); + let b_ptr: *mut u64 = Box::as_mut_ptr(&mut b); + + // Store valid pointer to `a`: + atomic_ptr.store(a_ptr, Relaxed); + let ptr = atomic_ptr.load(Relaxed); + *ptr = 42; + if *a != 42 { + std::process::abort(); + } + // Store valid pointer to `b`: + atomic_ptr.store(b_ptr, Relaxed); + let ptr = atomic_ptr.load(Relaxed); + *ptr = 43; + if *b != 43 { + std::process::abort(); + } + + // Make `atomic_ptr` contain a pointer with the provenance of `b`, but the address of `a`. + if cfg!(send) { + // Variant 1: create the invalid pointer non-atomically, then send it to GenMC. + let fake_a_ptr = b_ptr.with_addr(a_ptr.addr()); + if a_ptr.addr() != fake_a_ptr.addr() { + std::process::abort(); + } + atomic_ptr.store(fake_a_ptr, Relaxed); + } else { + // Variant 2: send `b_ptr` to GenMC, then create the invalid pointer to `a` using atomic operations. + atomic_ptr.store(b_ptr, Relaxed); + atomic_ptr.fetch_byte_add(a_ptr.addr(), Relaxed); + atomic_ptr.fetch_byte_sub(b_ptr.addr(), Relaxed); + } + let ptr = atomic_ptr.load(Relaxed); + if a_ptr.addr() != ptr.addr() { + std::process::abort(); + } + // This pointer has the same address as `a_ptr`, but not the same + // provenance, so writing to it fails. + *ptr = 44; //~ ERROR: points to before the beginning of the allocation + + 0 + } +} diff --git a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.send.stderr b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.send.stderr new file mode 100644 index 0000000000000..d74fa12256bbf --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.send.stderr @@ -0,0 +1,21 @@ +Running GenMC Verification... +error: Undefined Behavior: memory access failed: attempting to access 8 bytes, but got ALLOC-$HEX which points to before the beginning of the allocation + --> tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs:LL:CC + | +LL | *ptr = 44; + | ^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs:LL:CC + | +LL | let mut b = Box::new(0u64); + | ^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `miri_start` at tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs:LL:CC + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.dealloc.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.dealloc.stderr new file mode 100644 index 0000000000000..bde793014bbf2 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.dealloc.stderr @@ -0,0 +1,24 @@ +Running GenMC Verification... +error: Undefined Behavior: Attempt to access freed memory + --> tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC + | +LL | dealloc(b as *mut u8, Layout::new::()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = 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 on thread `unnamed-ID`: + = note: inside closure at tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC + = note: inside ` as std::ops::FnOnce<()>>::call_once` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC}>` + --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC + | +LL | f(); + | ^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs new file mode 100644 index 0000000000000..e453c16b157d2 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs @@ -0,0 +1,48 @@ +//@revisions: write dealloc +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows -Zmiri-ignore-leaks + +// Test that we can detect data races between an allocation and an unsynchronized action in another thread. +// We have two variants, an alloc-dealloc race and an alloc-write race. +// +// We never deallocate the memory, so leak-checks must be disabled. +// +// FIXME(genmc): The error message is currently suboptimal, since it mentions non-allocated/freed memory, instead of pointing towards the missing synchronization with the allocation. + +#![no_main] + +#[path = "../../../utils/genmc.rs"] +mod genmc; + +use std::alloc::{Layout, alloc, dealloc}; +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use crate::genmc::*; + +static X: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + let ids = [ + spawn_pthread_closure(|| { + let a: *mut u64 = alloc(Layout::new::()) as *mut u64; + X.store(a, Relaxed); // Relaxed ordering does not synchronize the alloc with the other thread. + }), + spawn_pthread_closure(|| { + let b = X.load(Relaxed); + if !b.is_null() { + if cfg!(dealloc) { + // Variant: alloc-dealloc race + dealloc(b as *mut u8, Layout::new::()); //~[dealloc] ERROR: Undefined Behavior + } else { + // Variant: alloc-write race + *b = 42; //~[write] ERROR: Undefined Behavior + } + } + }), + ]; + join_pthreads(ids); + 0 + } +} diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.write.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.write.stderr new file mode 100644 index 0000000000000..7bfafe0ca086c --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.write.stderr @@ -0,0 +1,22 @@ +Running GenMC Verification... +error: Undefined Behavior: Attempt to access non-allocated memory + --> tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC + | +LL | *b = 42; + | ^^^^^^^ Undefined Behavior occurred here + | + = 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 on thread `unnamed-ID`: + = note: inside closure at tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC + = note: inside ` as std::ops::FnOnce<()>>::call_once` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC}>` + --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC + | +LL | f(); + | ^^^ + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs new file mode 100644 index 0000000000000..10e0d8d854c25 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs @@ -0,0 +1,39 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test that use-after-free bugs involving atomic pointers are detected in GenMC mode. + +#![no_main] + +#[path = "../../../utils/genmc.rs"] +mod genmc; +#[path = "../../../utils/mod.rs"] +mod utils; + +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use crate::genmc::*; +use crate::utils::*; + +static X: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); +static mut Y: u64 = 0; + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + let ids = [ + spawn_pthread_closure(|| { + let mut z: u64 = 1234; + X.store(&raw mut z, SeqCst); // The other thread can read this value and then access `z` after it is deallocated. + X.store(&raw mut Y, SeqCst); + }), + spawn_pthread_closure(|| { + let ptr = X.load(SeqCst); + miri_genmc_assume(!ptr.is_null()); + *ptr = 42; //~ ERROR: Undefined Behavior + }), + ]; + join_pthreads(ids); + 0 + } +} diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.stderr new file mode 100644 index 0000000000000..0facf6a5d177c --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.stderr @@ -0,0 +1,32 @@ +Running GenMC Verification... +error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling + --> tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC + | +LL | *ptr = 42; + | ^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC + | +LL | let mut z: u64 = 1234; + | ^^^^^ +help: ALLOC was deallocated here: + --> tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC + | +LL | }), + | ^ + = note: BACKTRACE (of the first span) on thread `unnamed-ID`: + = note: inside closure at tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC + = note: inside ` as std::ops::FnOnce<()>>::call_once` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC}>` + --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC + | +LL | f(); + | ^^^ + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs new file mode 100644 index 0000000000000..e2d3057a5b0df --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs @@ -0,0 +1,47 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test that use-after-free bugs involving atomic pointers are detected in GenMC mode. +// Compared to `atomic_ptr_dealloc_write_race.rs`, this variant checks that the data race is still detected, even if the write happens before the free. +// +// FIXME(genmc): We currently cannot show the two spans related to this error (only the second one), +// since there is no mapping between GenMC events (shown with `-Zmiri-genmc-print-genmc-output`) and Miri spans. + +#![no_main] + +#[path = "../../../utils/genmc.rs"] +mod genmc; +#[path = "../../../utils/mod.rs"] +mod utils; + +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use crate::genmc::*; +use crate::utils::*; + +static X: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); +static Y: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + let ids = [ + spawn_pthread_closure(|| { + let ptr = X.load(SeqCst); + miri_genmc_assume(!ptr.is_null()); + *ptr = 42; + }), + spawn_pthread_closure(|| { + let mut z: u64 = 1234; + X.store(&raw mut z, SeqCst); // The other thread can read this value and then access `z` after it is deallocated. + for _ in 0..10 { + // We do some extra loads here so GenMC schedules the pointer write on the other thread first. + Y.load(Relaxed); + } + // `z` gets dropped on the next line, at which point GenMC will detect the data race with the write in the other thread. + }), //~ ERROR: Undefined Behavior + ]; + join_pthreads(ids); + 0 + } +} diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.stderr new file mode 100644 index 0000000000000..8e4ed1aba0430 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.stderr @@ -0,0 +1,22 @@ +Running GenMC Verification... +error: Undefined Behavior: Attempt to access freed memory + --> tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs:LL:CC + | +LL | }), + | ^ Undefined Behavior occurred here + | + = 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 on thread `unnamed-ID`: + = note: inside closure at tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs:LL:CC + = note: inside ` as std::ops::FnOnce<()>>::call_once` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs:LL:CC}>` + --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC + | +LL | f(); + | ^^^ + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/fail/loom/buggy_inc.rs b/src/tools/miri/tests/genmc/fail/loom/buggy_inc.rs index 508eae756f320..2e614e6a360ba 100644 --- a/src/tools/miri/tests/genmc/fail/loom/buggy_inc.rs +++ b/src/tools/miri/tests/genmc/fail/loom/buggy_inc.rs @@ -42,9 +42,6 @@ impl BuggyInc { fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { unsafe { static BUGGY_INC: BuggyInc = BuggyInc::new(); - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - BUGGY_INC.num.store(0, Relaxed); - let ids = [ spawn_pthread_closure(|| { BUGGY_INC.inc(); diff --git a/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.rs b/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.rs new file mode 100644 index 0000000000000..d2da722f1c02f --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.rs @@ -0,0 +1,28 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows +//@error-in-other-file: Undefined Behavior + +// Test that GenMC throws an error if a `std::sync::Mutex` is unlocked from a different thread than the one that locked it. +// +// This test will cause an error on all targets, even mutexes on that targets allow for unlocking on a different thread. +// GenMC always assumes a `pthread`-like API. + +#![no_main] + +use std::sync::Mutex; + +static MUTEX: Mutex = Mutex::new(0); + +#[derive(Copy, Clone)] +struct EvilSend(pub T); +unsafe impl Send for EvilSend {} + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + let guard = EvilSend(MUTEX.lock().unwrap()); + let handle = std::thread::spawn(move || { + let guard = guard; // avoid field capturing + drop(guard); + }); + handle.join().unwrap(); + 0 +} diff --git a/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr b/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr new file mode 100644 index 0000000000000..e74b76ea415e6 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr @@ -0,0 +1,86 @@ +Running GenMC Verification... +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `miri_start` + --> tests/genmc/fail/shims/mutex_diff_thread_unlock.rs:LL:CC + | +LL | let handle = std::thread::spawn(move || { + | __________________^ +LL | | let guard = guard; // avoid field capturing +LL | | drop(guard); +LL | | }); + | |______^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `miri_start` + --> tests/genmc/fail/shims/mutex_diff_thread_unlock.rs:LL:CC + | +LL | let handle = std::thread::spawn(move || { + | __________________^ +LL | | let guard = guard; // avoid field capturing +LL | | drop(guard); +LL | | }); + | |______^ + +error: Undefined Behavior: Invalid unlock() operation + --> RUSTLIB/std/src/sync/poison/mutex.rs:LL:CC + | +LL | self.lock.inner.unlock(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = 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 on thread `unnamed-ID`: + = note: inside ` as std::ops::Drop>::drop` at RUSTLIB/std/src/sync/poison/mutex.rs:LL:CC + = note: inside `std::ptr::drop_in_place::> - shim(Some(std::sync::MutexGuard<'_, u64>))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC + = note: inside `std::ptr::drop_in_place::>> - shim(Some(EvilSend>))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC + = note: inside `std::mem::drop::>>` at RUSTLIB/core/src/mem/mod.rs:LL:CC +note: inside closure + --> tests/genmc/fail/shims/mutex_diff_thread_unlock.rs:LL:CC + | +LL | drop(guard); + | ^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error; 2 warnings emitted + diff --git a/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.rs b/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.rs new file mode 100644 index 0000000000000..3daff38efbfdd --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.rs @@ -0,0 +1,22 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows +//@error-in-other-file: Undefined Behavior + +// Test that GenMC can detect a double unlock of a mutex. +// This test will cause an error even if the program actually would work entirely fine despite the double-unlock +// because GenMC always assumes a `pthread`-like API. + +#![no_main] + +use std::sync::Mutex; + +static MUTEX: Mutex = Mutex::new(0); + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + let mut guard = MUTEX.lock().unwrap(); + unsafe { + std::ptr::drop_in_place(&raw mut guard); + } + drop(guard); + 0 +} diff --git a/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr b/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr new file mode 100644 index 0000000000000..3ba863668f1e8 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr @@ -0,0 +1,23 @@ +Running GenMC Verification... +error: Undefined Behavior: Invalid unlock() operation + --> RUSTLIB/std/src/sync/poison/mutex.rs:LL:CC + | +LL | self.lock.inner.unlock(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = 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 ` as std::ops::Drop>::drop` at RUSTLIB/std/src/sync/poison/mutex.rs:LL:CC + = note: inside `std::ptr::drop_in_place::> - shim(Some(std::sync::MutexGuard<'_, u64>))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC + = note: inside `std::mem::drop::>` at RUSTLIB/core/src/mem/mod.rs:LL:CC +note: inside `miri_start` + --> tests/genmc/fail/shims/mutex_double_unlock.rs:LL:CC + | +LL | drop(guard); + | ^^^^^^^^^^^ + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.rs b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.rs new file mode 100644 index 0000000000000..aa40e193dbfd0 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.rs @@ -0,0 +1,178 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test several operations on atomic pointers. + +#![no_main] + +#[path = "../../../utils/mod.rs"] +mod utils; + +use std::fmt::{Debug, Write}; +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use crate::utils::*; + +static mut X: u64 = 0; +static mut Y: u64 = 0; + +fn assert_equals(a: T, b: T) { + if a != b { + writeln!(MiriStderr, "{:?}, {:?}", a, b).ok(); + std::process::abort(); + } +} + +/// Check that two pointers are equal and stores to one update the value read from the other. +unsafe fn pointers_equal(a: *mut u64, b: *mut u64) { + assert_equals(a, b); + assert_equals(*a, *b); + *a = 42; + assert_equals(*a, 42); + assert_equals(*b, 42); + *b = 0xAA; + assert_equals(*a, 0xAA); + assert_equals(*b, 0xAA); +} + +unsafe fn test_load_store_exchange() { + let atomic_ptr: AtomicPtr = AtomicPtr::new(&raw mut X); + + // Atomic load can read the initial value. + pointers_equal(atomic_ptr.load(SeqCst), &raw mut X); + // Atomic store works as expected. + atomic_ptr.store(&raw mut Y, SeqCst); + pointers_equal(atomic_ptr.load(SeqCst), &raw mut Y); + // We can read the value of the atomic store non-atomically. + pointers_equal(*atomic_ptr.as_ptr(), &raw mut Y); + // We can read the value of a non-atomic store atomically. + *atomic_ptr.as_ptr() = &raw mut X; + pointers_equal(atomic_ptr.load(SeqCst), &raw mut X); + + // Atomic swap must return the old value and store the new one. + *atomic_ptr.as_ptr() = &raw mut Y; // Test that we can read this non-atomic store using `swap`. + pointers_equal(atomic_ptr.swap(&raw mut X, SeqCst), &raw mut Y); + pointers_equal(*atomic_ptr.as_ptr(), &raw mut X); + pointers_equal(atomic_ptr.load(SeqCst), &raw mut X); + + // Failing compare_exchange (wrong expected pointer). + match atomic_ptr.compare_exchange(&raw mut Y, std::ptr::null_mut(), SeqCst, SeqCst) { + Ok(_ptr) => std::process::abort(), + Err(ptr) => pointers_equal(ptr, &raw mut X), + } + // Non-atomic read value should also be unchanged by a failing compare_exchange. + pointers_equal(*atomic_ptr.as_ptr(), &raw mut X); + + // Failing compare_exchange (null). + match atomic_ptr.compare_exchange(std::ptr::null_mut(), std::ptr::null_mut(), SeqCst, SeqCst) { + Ok(_ptr) => std::process::abort(), + Err(ptr) => pointers_equal(ptr, &raw mut X), + } + // Non-atomic read value should also be unchanged by a failing compare_exchange. + pointers_equal(*atomic_ptr.as_ptr(), &raw mut X); + + // Successful compare_exchange. + match atomic_ptr.compare_exchange(&raw mut X, &raw mut Y, SeqCst, SeqCst) { + Ok(ptr) => pointers_equal(ptr, &raw mut X), + Err(_ptr) => std::process::abort(), + } + // compare_exchange should update the pointer. + pointers_equal(atomic_ptr.load(SeqCst), &raw mut Y); + pointers_equal(*atomic_ptr.as_ptr(), &raw mut Y); +} + +unsafe fn test_add_sub() { + const LEN: usize = 16; + let mut array: [u64; LEN] = std::array::from_fn(|i| i as u64); + let atomic_ptr: AtomicPtr = AtomicPtr::new(&raw mut array[0]); + + // Each element of the array should be reachable using `fetch_ptr_add`. + // All pointers must stay valid. + for i in 0..LEN { + let ptr = atomic_ptr.fetch_ptr_add(1, SeqCst); + pointers_equal(ptr, &raw mut array[i]); + } + // This should return the pointer back to the start of the array. + let ptr = atomic_ptr.fetch_ptr_sub(LEN, SeqCst); + pointers_equal(ptr.offset(-(LEN as isize)), &raw mut array[0]); + pointers_equal(atomic_ptr.load(SeqCst), &raw mut array[0]); + + let array_mid_ptr = &raw mut array[LEN / 2]; + for i in 0..size_of::() { + atomic_ptr.store(array_mid_ptr, SeqCst); // Reset to test `byte_add` and `byte_sub`. + pointers_equal(array_mid_ptr, atomic_ptr.fetch_byte_add(i, SeqCst)); + if array_mid_ptr.byte_add(i) != atomic_ptr.load(SeqCst) { + std::process::abort(); + } + if array_mid_ptr.byte_add(i) != atomic_ptr.fetch_byte_sub(i, SeqCst) { + std::process::abort(); + } + pointers_equal(array_mid_ptr, atomic_ptr.load(SeqCst)); + } +} + +unsafe fn test_and_or_xor() { + const LEN: usize = 16; + #[repr(align(1024))] // Aligned to size 16 * 8 bytes. + struct AlignedArray([u64; LEN]); + + let mut array = AlignedArray(std::array::from_fn(|i| i as u64 * 10)); + let array_ptr = &raw mut array.0[0]; + let atomic_ptr: AtomicPtr = AtomicPtr::new(array_ptr); + + // Test no-op arguments. + assert_equals(array_ptr, atomic_ptr.fetch_or(0, SeqCst)); + assert_equals(array_ptr, atomic_ptr.fetch_xor(0, SeqCst)); + assert_equals(array_ptr, atomic_ptr.fetch_and(!0, SeqCst)); + assert_equals(array_ptr, atomic_ptr.load(SeqCst)); + + // Test identity arguments. + let array_addr = array_ptr as usize; + assert_equals(array_ptr, atomic_ptr.fetch_or(array_addr, SeqCst)); + assert_equals(array_ptr, atomic_ptr.load(SeqCst)); + assert_equals(array_ptr, atomic_ptr.fetch_and(array_addr, SeqCst)); + assert_equals(array_ptr, atomic_ptr.load(SeqCst)); + assert_equals(array_ptr, atomic_ptr.fetch_xor(array_addr, SeqCst)); + assert_equals(std::ptr::null_mut(), atomic_ptr.load(SeqCst)); // `null_mut` is guaranteed to have address 0. + + // Test moving within an allocation. + // The array is aligned to 64 bytes, so we can change which element we point by or/and/xor-ing the address. + let index = LEN / 2; // Choose an index in the middle of the array. + let offset = index * size_of::(); + let array_mid_ptr = &raw mut array.0[index]; + + atomic_ptr.store(array_ptr, SeqCst); // Reset to test `or`. + assert_equals(array_ptr, atomic_ptr.fetch_or(offset, SeqCst)); + assert_equals(array_mid_ptr, atomic_ptr.load(SeqCst)); + + atomic_ptr.store(array_ptr, SeqCst); // Reset to test `xor`. + assert_equals(array_ptr, atomic_ptr.fetch_xor(offset, SeqCst)); + assert_equals(array_mid_ptr, atomic_ptr.load(SeqCst)); + assert_equals(array_mid_ptr, atomic_ptr.fetch_xor(offset, SeqCst)); // two xor should yield original value. + assert_equals(array_ptr, atomic_ptr.load(SeqCst)); + + let mask = !(u64::BITS as usize - 1); + for i in 0..size_of::() { + // We offset the pointer by `i` bytes, making it unaligned. + let offset_ptr = array_ptr.byte_add(i); + atomic_ptr.store(array_ptr, SeqCst); + // `fetch_byte_add` should return the old value. + assert_equals(array_ptr, atomic_ptr.fetch_byte_add(i, SeqCst)); + // `ptr::byte_add` and `AtomicPtr::fetch_byte_add` should give the same result. + if offset_ptr != atomic_ptr.fetch_and(mask, SeqCst) { + std::process::abort(); + } + // Masking off the last bits should restore the pointer. + assert_equals(array_ptr, atomic_ptr.load(SeqCst)); + } +} + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + test_load_store_exchange(); + test_add_sub(); + test_and_or_xor(); + 0 + } +} diff --git a/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.stderr b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.stderr new file mode 100644 index 0000000000000..7867be2dbe8ed --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.stderr @@ -0,0 +1,2 @@ +Running GenMC Verification... +Verification complete with 1 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.rs b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.rs new file mode 100644 index 0000000000000..d846a55cbc31a --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.rs @@ -0,0 +1,57 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test that we can send pointers with any alignment to GenMC and back, even across threads. +// After a round-trip, the pointers should still work properly (no missing provenance). + +#![no_main] +#![allow(static_mut_refs)] + +#[path = "../../../utils/genmc.rs"] +mod genmc; +#[path = "../../../utils/mod.rs"] +mod utils; + +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use genmc::*; +use utils::*; + +static PTR: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); + +static mut X: [u8; 16] = [0; 16]; + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + let ids = [ + spawn_pthread_closure(|| { + for i in 0..X.len() { + X[i] = i.try_into().unwrap(); + PTR.store(&raw mut X[i], SeqCst); + // Wait for the other thread to reset the AtomicPtr. + miri_genmc_assume(PTR.load(SeqCst).is_null()); + // Check that we see the update the other thread did through the pointer. + if X[i] != (i + 1) as u8 { + std::process::abort(); + } + } + }), + spawn_pthread_closure(|| { + for i in 0..X.len() { + let x = PTR.load(SeqCst); + // Wait for the other thread to store the next pointer. + miri_genmc_assume(!x.is_null()); + // Check that we see the update when reading from the pointer. + if usize::from(*x) != i { + std::process::abort(); + } + *x = (i + 1) as u8; + PTR.store(std::ptr::null_mut(), SeqCst); + } + }), + ]; + join_pthreads(ids); + 0 + } +} diff --git a/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.stderr b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.stderr new file mode 100644 index 0000000000000..67d88dfaed711 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.stderr @@ -0,0 +1,2 @@ +Running GenMC Verification... +Verification complete with 33 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs index 8a77d54a64f8b..e17a988cb3736 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs @@ -26,9 +26,6 @@ static mut VALUES: [usize; 2] = [0, 0]; #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - KEY.store(KEY_SENTVAL, Relaxed); - unsafe { let mut a = 0; let mut b = 0; diff --git a/src/tools/miri/tests/genmc/pass/atomics/mixed_atomic_non_atomic.rs b/src/tools/miri/tests/genmc/pass/atomics/mixed_atomic_non_atomic.rs new file mode 100644 index 0000000000000..7601b354b1c00 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/mixed_atomic_non_atomic.rs @@ -0,0 +1,41 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test that we can read the value of a non-atomic store atomically and an of an atomic value non-atomically. + +#![no_main] + +use std::sync::atomic::Ordering::*; +use std::sync::atomic::{AtomicI8, AtomicU64}; + +static X: AtomicU64 = AtomicU64::new(1234); +static Y: AtomicI8 = AtomicI8::new(0xB); + +fn assert_equals(a: T, b: T) { + if a != b { + std::process::abort(); + } +} + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // 8 byte unsigned integer: + // Read initial value. + assert_equals(1234, X.load(Relaxed)); + // Atomic store, non-atomic load. + X.store(0xFFFF, Relaxed); + assert_equals(0xFFFF, unsafe { *X.as_ptr() }); + // Non-atomic store, atomic load. + unsafe { *X.as_ptr() = 0xAAAA }; + assert_equals(0xAAAA, X.load(Relaxed)); + + // 1 byte signed integer: + // Read initial value. + assert_equals(0xB, Y.load(Relaxed)); + // Atomic store, non-atomic load. + Y.store(42, Relaxed); + assert_equals(42, unsafe { *Y.as_ptr() }); + // Non-atomic store, atomic load. + unsafe { *Y.as_ptr() = -42 }; + assert_equals(-42, Y.load(Relaxed)); + 0 +} diff --git a/src/tools/miri/tests/genmc/pass/atomics/mixed_atomic_non_atomic.stderr b/src/tools/miri/tests/genmc/pass/atomics/mixed_atomic_non_atomic.stderr new file mode 100644 index 0000000000000..7867be2dbe8ed --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/mixed_atomic_non_atomic.stderr @@ -0,0 +1,2 @@ +Running GenMC Verification... +Verification complete with 1 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/atomics/read_initial_value.rs b/src/tools/miri/tests/genmc/pass/atomics/read_initial_value.rs new file mode 100644 index 0000000000000..18e039fdd0dfe --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/read_initial_value.rs @@ -0,0 +1,41 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test that we can read the initial value of global, heap and stack allocations in GenMC mode. + +#![no_main] + +use std::sync::atomic::AtomicU64; +use std::sync::atomic::Ordering::*; + +static X: AtomicU64 = AtomicU64::new(1234); + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // Read initial value of global allocation. + if 1234 != unsafe { *X.as_ptr() } { + std::process::abort(); + } + if 1234 != X.load(SeqCst) { + std::process::abort(); + } + + // Read initial value of stack allocation. + let a = AtomicU64::new(0xBB); + if 0xBB != unsafe { *a.as_ptr() } { + std::process::abort(); + } + if 0xBB != a.load(SeqCst) { + std::process::abort(); + } + + // Read initial value of heap allocation. + let b = Box::new(AtomicU64::new(0xCCC)); + if 0xCCC != unsafe { *b.as_ptr() } { + std::process::abort(); + } + if 0xCCC != b.load(SeqCst) { + std::process::abort(); + } + + 0 +} diff --git a/src/tools/miri/tests/genmc/pass/atomics/read_initial_value.stderr b/src/tools/miri/tests/genmc/pass/atomics/read_initial_value.stderr new file mode 100644 index 0000000000000..7867be2dbe8ed --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/read_initial_value.stderr @@ -0,0 +1,2 @@ +Running GenMC Verification... +Verification complete with 1 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/atomics/rmw_ops.rs b/src/tools/miri/tests/genmc/pass/atomics/rmw_ops.rs index f48466e8ce10c..7e6e33c8a7b1c 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/rmw_ops.rs +++ b/src/tools/miri/tests/genmc/pass/atomics/rmw_ops.rs @@ -21,11 +21,8 @@ fn assert_eq(x: T, y: T) { macro_rules! test_rmw_edge_cases { ($int:ty, $atomic:ty) => {{ - let x = <$atomic>::new(123); - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - x.store(123, ORD); - // MAX, ADD + let x = <$atomic>::new(123); assert_eq(123, x.fetch_max(0, ORD)); // `max` keeps existing value assert_eq(123, x.fetch_max(<$int>::MAX, ORD)); // `max` stores the new value assert_eq(<$int>::MAX, x.fetch_add(10, ORD)); // `fetch_add` should be wrapping diff --git a/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.default_R1W1.stderr b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.default_R1W1.stderr new file mode 100644 index 0000000000000..5b60672a09d71 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.default_R1W1.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 4 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.default_R1W2.stderr b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.default_R1W2.stderr new file mode 100644 index 0000000000000..1a025216a22d6 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.default_R1W2.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 188 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.rs b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.rs new file mode 100644 index 0000000000000..934fc977366dc --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.rs @@ -0,0 +1,192 @@ +//@ revisions: default_R1W1 default_R1W2 spinloop_assume_R1W1 spinloop_assume_R1W2 +//@compile-flags: -Zmiri-ignore-leaks -Zmiri-genmc -Zmiri-disable-stacked-borrows -Zmiri-genmc-verbose +//@normalize-stderr-test: "Verification took .*s" -> "Verification took [TIME]s" + +// This test is a translations of the GenMC test `ms-queue-dynamic`, but with all code related to GenMC's hazard pointer API removed. +// The test leaks memory, so leak checks are disabled. +// +// Test variant naming convention: "[VARIANT_NAME]_R[#reader_threads]_W[#writer_threads]". +// We test different numbers of writer threads to see the scaling. +// Implementing optimizations such as automatic spinloop-assume transformation or symmetry reduction should reduce the number of explored executions. +// We also test variants using manual spinloop replacement, which should yield fewer executions in total compared to the unmodified code. +// +// FIXME(genmc): Add revisions `default_R1W3` and `spinloop_assume_R1W3` once Miri-GenMC performance is improved. These currently slow down the test suite too much. +// +// The test uses verbose output to see the difference between blocked and explored executions. + +#![no_main] +#![allow(static_mut_refs)] + +#[path = "../../../utils/genmc.rs"] +mod genmc; +#[allow(unused)] +#[path = "../../../utils/mod.rs"] +mod utils; + +use std::alloc::{Layout, alloc, dealloc}; +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use genmc::*; +use libc::pthread_t; + +const MAX_THREADS: usize = 32; + +static mut QUEUE: MyStack = MyStack::new(); +static mut INPUT: [u64; MAX_THREADS] = [0; MAX_THREADS]; +static mut OUTPUT: [Option; MAX_THREADS] = [None; MAX_THREADS]; + +#[repr(C)] +struct Node { + value: u64, + next: AtomicPtr, +} + +struct MyStack { + head: AtomicPtr, + tail: AtomicPtr, +} + +impl Node { + pub unsafe fn alloc() -> *mut Self { + alloc(Layout::new::()) as *mut Self + } + + pub unsafe fn free(node: *mut Self) { + dealloc(node as *mut u8, Layout::new::()) + } +} + +impl MyStack { + pub const fn new() -> Self { + let head = AtomicPtr::new(std::ptr::null_mut()); + let tail = AtomicPtr::new(std::ptr::null_mut()); + Self { head, tail } + } + + pub unsafe fn init_queue(&mut self, _num_threads: usize) { + let dummy = Node::alloc(); + + (*dummy).next = AtomicPtr::new(std::ptr::null_mut()); + self.head = AtomicPtr::new(dummy); + self.tail = AtomicPtr::new(dummy); + } + + pub unsafe fn clear_queue(&mut self, _num_threads: usize) { + let mut next; + let mut head = *self.head.get_mut(); + while !head.is_null() { + next = *(*head).next.get_mut(); + Node::free(head); + head = next; + } + } + + pub unsafe fn enqueue(&self, value: u64) { + let mut tail; + let node = Node::alloc(); + (*node).value = value; + (*node).next = AtomicPtr::new(std::ptr::null_mut()); + + loop { + tail = self.tail.load(Acquire); + let next = (*tail).next.load(Acquire); + if tail != self.tail.load(Acquire) { + // Looping here has no side effects, so we prevent exploring any executions where this branch happens. + #[cfg(any(spinloop_assume_R1W1, spinloop_assume_R1W2, spinloop_assume_R1W3))] + utils::miri_genmc_assume(false); // GenMC will stop any execution that reaches this. + continue; + } + + if next.is_null() { + if (*tail).next.compare_exchange(next, node, Release, Relaxed).is_ok() { + break; + } + } else { + let _ = self.tail.compare_exchange(tail, next, Release, Relaxed); + } + } + + let _ = self.tail.compare_exchange(tail, node, Release, Relaxed); + } + + pub unsafe fn dequeue(&self) -> Option { + loop { + let head = self.head.load(Acquire); + let tail = self.tail.load(Acquire); + + let next = (*head).next.load(Acquire); + if self.head.load(Acquire) != head { + // Looping here has no side effects, so we prevent exploring any executions where this branch happens. + #[cfg(any(spinloop_assume_R1W1, spinloop_assume_R1W2, spinloop_assume_R1W3))] + utils::miri_genmc_assume(false); // GenMC will stop any execution that reaches this. + continue; + } + if head == tail { + if next.is_null() { + return None; + } + let _ = self.tail.compare_exchange(tail, next, Release, Relaxed); + } else { + let ret_val = (*next).value; + if self.head.compare_exchange(head, next, Release, Relaxed).is_ok() { + // NOTE: The popped `Node` is leaked. + return Some(ret_val); + } + // Looping here has no side effects, so we prevent exploring any executions where this branch happens. + // All operations in the loop leading to here are either loads, or failed compare-exchange operations. + #[cfg(any(spinloop_assume_R1W1, spinloop_assume_R1W2, spinloop_assume_R1W3))] + utils::miri_genmc_assume(false); // GenMC will stop any execution that reaches this. + } + } + } +} + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // We try multiple different parameters for the number and types of threads: + let (readers, writers) = if cfg!(any(default_R1W3, spinloop_assume_R1W3)) { + (1, 3) + } else if cfg!(any(default_R1W2, spinloop_assume_R1W2)) { + (1, 2) + } else { + // default_R1W1, spinloop_assume_R1W1 + (1, 1) + }; + + let num_threads = readers + writers; + if num_threads > MAX_THREADS { + std::process::abort(); + } + + let mut i = 0; + unsafe { + MyStack::init_queue(&mut QUEUE, num_threads); + + /* Spawn threads */ + let mut thread_ids: [pthread_t; MAX_THREADS] = [0; MAX_THREADS]; + for _ in 0..readers { + let pid = i as u64; + thread_ids[i] = spawn_pthread_closure(move || { + OUTPUT[pid as usize] = QUEUE.dequeue(); + }); + i += 1; + } + for _ in 0..writers { + let pid = i as u64; + thread_ids[i] = spawn_pthread_closure(move || { + INPUT[pid as usize] = pid * 10; + QUEUE.enqueue(INPUT[pid as usize]); + }); + i += 1; + } + + for i in 0..num_threads { + join_pthread(thread_ids[i]); + } + + MyStack::clear_queue(&mut QUEUE, num_threads); + } + + 0 +} diff --git a/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.spinloop_assume_R1W1.stderr b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.spinloop_assume_R1W1.stderr new file mode 100644 index 0000000000000..5b60672a09d71 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.spinloop_assume_R1W1.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 4 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.spinloop_assume_R1W2.stderr b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.spinloop_assume_R1W2.stderr new file mode 100644 index 0000000000000..44332a2f027e4 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.spinloop_assume_R1W2.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 152 executions. No errors found. +Number of complete executions explored: 128 +Number of blocked executions seen: 24 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W1.stderr b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W1.stderr new file mode 100644 index 0000000000000..5452d10bb2608 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W1.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 2 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W2.stderr b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W2.stderr new file mode 100644 index 0000000000000..98b0ced1c89aa --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W2.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 22 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W3.stderr b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W3.stderr new file mode 100644 index 0000000000000..ba085e45e57fc --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W3.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 1002 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.rs b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.rs new file mode 100644 index 0000000000000..8bdd2a371f51c --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.rs @@ -0,0 +1,147 @@ +//@ revisions: default_R1W1 default_R1W2 default_R1W3 spinloop_assume_R1W1 spinloop_assume_R1W2 spinloop_assume_R1W3 +//@compile-flags: -Zmiri-ignore-leaks -Zmiri-genmc -Zmiri-disable-stacked-borrows -Zmiri-genmc-verbose +//@normalize-stderr-test: "Verification took .*s" -> "Verification took [TIME]s" + +// This test is a translations of the GenMC test `treiber-stack-dynamic`, but with all code related to GenMC's hazard pointer API removed. +// The test leaks memory, so leak checks are disabled. +// +// Test variant naming convention: "[VARIANT_NAME]_R[#reader_threads]_W[#writer_threads]". +// We test different numbers of writer threads to see the scaling. +// Implementing optimizations such as automatic spinloop-assume transformation or symmetry reduction should reduce the number of explored executions. +// We also test variants using manual spinloop replacement, which should yield fewer executions in total compared to the unmodified code. +// +// The test uses verbose output to see the difference between blocked and explored executions. + +#![no_main] +#![allow(static_mut_refs)] + +#[path = "../../../utils/genmc.rs"] +mod genmc; +#[allow(unused)] +#[path = "../../../utils/mod.rs"] +mod utils; + +use std::alloc::{Layout, alloc, dealloc}; +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use genmc::*; +use libc::pthread_t; + +const MAX_THREADS: usize = 32; + +static mut STACK: MyStack = MyStack::new(); + +#[repr(C)] +struct Node { + value: u64, + next: AtomicPtr, +} + +struct MyStack { + top: AtomicPtr, +} + +impl Node { + pub unsafe fn alloc() -> *mut Self { + alloc(Layout::new::()) as *mut Self + } + + pub unsafe fn free(node: *mut Self) { + dealloc(node as *mut u8, Layout::new::()) + } +} + +impl MyStack { + pub const fn new() -> Self { + Self { top: AtomicPtr::new(std::ptr::null_mut()) } + } + + pub unsafe fn clear_stack(&mut self, _num_threads: usize) { + let mut next; + let mut top = *self.top.get_mut(); + while !top.is_null() { + next = *(*top).next.get_mut(); + Node::free(top); + top = next; + } + } + + pub unsafe fn push(&self, value: u64) { + let node = Node::alloc(); + (*node).value = value; + + loop { + let top = self.top.load(Acquire); + (*node).next.store(top, Relaxed); + if self.top.compare_exchange(top, node, Release, Relaxed).is_ok() { + break; + } + // We manually limit the number of iterations of this spinloop to 1. + #[cfg(any(spinloop_assume_R1W1, spinloop_assume_R1W2, spinloop_assume_R1W3))] + utils::miri_genmc_assume(false); // GenMC will stop any execution that reaches this. + } + } + + pub unsafe fn pop(&self) -> u64 { + loop { + let top = self.top.load(Acquire); + if top.is_null() { + return 0; + } + + let next = (*top).next.load(Relaxed); + if self.top.compare_exchange(top, next, Release, Relaxed).is_ok() { + // NOTE: The popped `Node` is leaked. + return (*top).value; + } + // We manually limit the number of iterations of this spinloop to 1. + #[cfg(any(spinloop_assume_R1W1, spinloop_assume_R1W2, spinloop_assume_R1W3))] + utils::miri_genmc_assume(false); // GenMC will stop any execution that reaches this. + } + } +} + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // We try multiple different parameters for the number and types of threads: + let (readers, writers) = if cfg!(any(default_R1W3, spinloop_assume_R1W3)) { + (1, 3) + } else if cfg!(any(default_R1W2, spinloop_assume_R1W2)) { + (1, 2) + } else { + // default_R1W1, spinloop_assume_R1W1 + (1, 1) + }; + + let num_threads = readers + writers; + if num_threads > MAX_THREADS { + std::process::abort(); + } + + let mut i = 0; + unsafe { + let mut thread_ids: [pthread_t; MAX_THREADS] = [0; MAX_THREADS]; + for _ in 0..readers { + thread_ids[i] = spawn_pthread_closure(move || { + let _idx = STACK.pop(); + }); + i += 1; + } + for _ in 0..writers { + let pid = i as u64; + thread_ids[i] = spawn_pthread_closure(move || { + STACK.push(pid); + }); + i += 1; + } + + for i in 0..num_threads { + join_pthread(thread_ids[i]); + } + + MyStack::clear_stack(&mut STACK, num_threads); + } + + 0 +} diff --git a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W1.stderr b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W1.stderr new file mode 100644 index 0000000000000..5452d10bb2608 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W1.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 2 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W2.stderr b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W2.stderr new file mode 100644 index 0000000000000..9082acd027d4c --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W2.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 16 executions. No errors found. +Number of complete executions explored: 8 +Number of blocked executions seen: 8 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W3.stderr b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W3.stderr new file mode 100644 index 0000000000000..02ac5a982f3f0 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W3.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 195 executions. No errors found. +Number of complete executions explored: 36 +Number of blocked executions seen: 159 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/litmus/2cowr.rs b/src/tools/miri/tests/genmc/pass/litmus/2cowr.rs index d3fdb470ed367..d9b582bb4362d 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/2cowr.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/2cowr.rs @@ -17,10 +17,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/IRIW-acq-sc.rs b/src/tools/miri/tests/genmc/pass/litmus/IRIW-acq-sc.rs index 1ec62ff21342d..6d2dfd4f273e2 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/IRIW-acq-sc.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/IRIW-acq-sc.rs @@ -17,10 +17,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/IRIWish.rs b/src/tools/miri/tests/genmc/pass/litmus/IRIWish.rs index c81573d59d1da..6f1d37962d10a 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/IRIWish.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/IRIWish.rs @@ -28,10 +28,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut results = [1234; 5]; let ids = [ @@ -57,7 +53,7 @@ fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { join_pthreads(ids); // Print the values to check that we get all of them: - writeln!(MiriStderr, "{results:?}").unwrap_or_else(|_| std::process::abort()); + writeln!(MiriStderr, "{results:?}").ok(); 0 } diff --git a/src/tools/miri/tests/genmc/pass/litmus/LB.rs b/src/tools/miri/tests/genmc/pass/litmus/LB.rs index 1cee3230b127c..107121ef4e3c7 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/LB.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/LB.rs @@ -17,10 +17,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/MP.rs b/src/tools/miri/tests/genmc/pass/litmus/MP.rs index e245cdd15eef2..5f9d1b01c37b0 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/MP.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/MP.rs @@ -17,10 +17,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/MPU2_rels_acqf.rs b/src/tools/miri/tests/genmc/pass/litmus/MPU2_rels_acqf.rs index 9bb156a99970f..6f812bf8a8ac9 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/MPU2_rels_acqf.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/MPU2_rels_acqf.rs @@ -22,10 +22,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut a = Ok(1234); let mut b = Ok(1234); @@ -60,7 +56,7 @@ fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { X.load(Relaxed), Y.load(Relaxed) ) - .unwrap_or_else(|_| std::process::abort()); + .ok(); 0 } diff --git a/src/tools/miri/tests/genmc/pass/litmus/MPU_rels_acq.rs b/src/tools/miri/tests/genmc/pass/litmus/MPU_rels_acq.rs index b36c8a288f426..4f20b2cf9def2 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/MPU_rels_acq.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/MPU_rels_acq.rs @@ -18,10 +18,6 @@ use crate::genmc::*; #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { spawn_pthread_closure(|| { X.store(1, Relaxed); diff --git a/src/tools/miri/tests/genmc/pass/litmus/MP_rels_acqf.rs b/src/tools/miri/tests/genmc/pass/litmus/MP_rels_acqf.rs index cf9f5f2dbfa34..19065d3308f8c 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/MP_rels_acqf.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/MP_rels_acqf.rs @@ -18,10 +18,6 @@ use crate::genmc::*; #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { spawn_pthread_closure(|| { X.store(1, Relaxed); diff --git a/src/tools/miri/tests/genmc/pass/litmus/SB.rs b/src/tools/miri/tests/genmc/pass/litmus/SB.rs index e592fe05c4e49..74d45c22a2953 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/SB.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/SB.rs @@ -17,10 +17,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/Z6_U.rs b/src/tools/miri/tests/genmc/pass/litmus/Z6_U.rs index f96679b23a5cd..cbbaa82d6fb5a 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/Z6_U.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/Z6_U.rs @@ -31,10 +31,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; @@ -55,8 +51,7 @@ fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { join_pthreads(ids); // Print the values to check that we get all of them: - writeln!(MiriStderr, "a={a}, b={b}, X={}, Y={}", X.load(Relaxed), Y.load(Relaxed)) - .unwrap_or_else(|_| std::process::abort()); + writeln!(MiriStderr, "a={a}, b={b}, X={}, Y={}", X.load(Relaxed), Y.load(Relaxed)).ok(); 0 } } diff --git a/src/tools/miri/tests/genmc/pass/litmus/casdep.rs b/src/tools/miri/tests/genmc/pass/litmus/casdep.rs index c376d96b111cc..8b8f6e793c1f7 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/casdep.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/casdep.rs @@ -18,11 +18,6 @@ static Z: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - Z.store(0, Relaxed); - unsafe { spawn_pthread_closure(|| { let a = X.load(Relaxed); diff --git a/src/tools/miri/tests/genmc/pass/litmus/ccr.rs b/src/tools/miri/tests/genmc/pass/litmus/ccr.rs index 865895b4dcd96..4537f3d6830ce 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/ccr.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/ccr.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { spawn_pthread_closure(|| { let expected = 0; diff --git a/src/tools/miri/tests/genmc/pass/litmus/cii.rs b/src/tools/miri/tests/genmc/pass/litmus/cii.rs index 663c806e667c4..18f56860f9604 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/cii.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/cii.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { spawn_pthread_closure(|| { let expected = 1; diff --git a/src/tools/miri/tests/genmc/pass/litmus/corr.rs b/src/tools/miri/tests/genmc/pass/litmus/corr.rs index d6c95100fc241..b586e2e0fa8a8 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/corr.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/corr.rs @@ -6,7 +6,6 @@ #[path = "../../../utils/genmc.rs"] mod genmc; - use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering::*; @@ -16,9 +15,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/corr0.rs b/src/tools/miri/tests/genmc/pass/litmus/corr0.rs index f722131fda846..856d566ca8bd3 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/corr0.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/corr0.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/corr1.rs b/src/tools/miri/tests/genmc/pass/litmus/corr1.rs index a4e8249bac309..ccd849802911e 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/corr1.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/corr1.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/corr2.rs b/src/tools/miri/tests/genmc/pass/litmus/corr2.rs index 2f490d3637797..36616bf36371f 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/corr2.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/corr2.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/corw.rs b/src/tools/miri/tests/genmc/pass/litmus/corw.rs index 7acc20822a4f4..9216a4f8368f6 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/corw.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/corw.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let mut a = 1234; let ids = [ diff --git a/src/tools/miri/tests/genmc/pass/litmus/cumul-release.rs b/src/tools/miri/tests/genmc/pass/litmus/cumul-release.rs index 2387976a8ca61..4034f7634e870 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/cumul-release.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/cumul-release.rs @@ -18,11 +18,6 @@ static Z: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - Z.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/default.rs b/src/tools/miri/tests/genmc/pass/litmus/default.rs index 55fb1ac34acb4..0ab26dce419ad 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/default.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/default.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/detour.rs b/src/tools/miri/tests/genmc/pass/litmus/detour.rs index 7136c029bbb54..85c456d5c54e5 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/detour.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/detour.rs @@ -22,11 +22,6 @@ static Z: AtomicI64 = AtomicI64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - Z.store(0, Relaxed); - unsafe { // Make these static so we can exit the main thread while the other threads still run. // If these are `let mut` like the other tests, this will cause a use-after-free bug. diff --git a/src/tools/miri/tests/genmc/pass/litmus/fr_w_w_w_reads.rs b/src/tools/miri/tests/genmc/pass/litmus/fr_w_w_w_reads.rs index 796ffbf97f96b..c8d3d409cf04d 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/fr_w_w_w_reads.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/fr_w_w_w_reads.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let mut result = [1234; 4]; let ids = [ diff --git a/src/tools/miri/tests/genmc/pass/litmus/inc2w.rs b/src/tools/miri/tests/genmc/pass/litmus/inc2w.rs index fd8574c4f7c78..eb84304a1986e 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/inc2w.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/inc2w.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let ids = [ spawn_pthread_closure(|| { diff --git a/src/tools/miri/tests/genmc/pass/shims/mutex_deadlock.rs b/src/tools/miri/tests/genmc/pass/shims/mutex_deadlock.rs new file mode 100644 index 0000000000000..df47fbfbc1676 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/shims/mutex_deadlock.rs @@ -0,0 +1,42 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows -Zmiri-genmc-verbose +//@normalize-stderr-test: "Verification took .*s" -> "Verification took [TIME]s" + +// Test that we can detect a deadlock involving `std::sync::Mutex` in GenMC mode. +// FIXME(genmc): We cannot detect the deadlock currently. Instead, the deadlocked execution is treated like any other blocked execution. +// This behavior matches GenMC's on an equivalent program, and additional analysis is required to detect such deadlocks. +// This should become a `fail` test once this deadlock can be detected. +// +// FIXME(genmc): use `std::thread` once GenMC mode performance is better and produces fewer warnings for compare_exchange. + +#![no_main] +#![feature(abort_unwind)] + +#[path = "../../../utils/genmc.rs"] +mod genmc; + +use std::sync::Mutex; + +use crate::genmc::*; + +static X: Mutex = Mutex::new(0); +static Y: Mutex = Mutex::new(0); + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + let t0 = spawn_pthread_closure(|| { + let mut x = X.lock().unwrap(); + let mut y = Y.lock().unwrap(); + *x += 1; + *y += 1; + }); + let t1 = spawn_pthread_closure(|| { + let mut y = Y.lock().unwrap(); + let mut x = X.lock().unwrap(); + *x += 1; + *y += 1; + }); + join_pthreads([t0, t1]); + 0 + } +} diff --git a/src/tools/miri/tests/genmc/pass/shims/mutex_deadlock.stderr b/src/tools/miri/tests/genmc/pass/shims/mutex_deadlock.stderr new file mode 100644 index 0000000000000..8b3957d18c01b --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/shims/mutex_deadlock.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 3 executions. No errors found. +Number of complete executions explored: 2 +Number of blocked executions seen: 1 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/shims/mutex_simple.rs b/src/tools/miri/tests/genmc/pass/shims/mutex_simple.rs new file mode 100644 index 0000000000000..1f8bc81d85eb5 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/shims/mutex_simple.rs @@ -0,0 +1,68 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows -Zmiri-genmc-verbose +//@normalize-stderr-test: "Verification took .*s" -> "Verification took [TIME]s" + +// Test various features of the `std::sync::Mutex` API with GenMC. +// Miri running with GenMC intercepts the Mutex functions `lock`, `try_lock` and `unlock`, instead of running their actual implementation. +// This interception should not break any functionality. +// +// FIXME(genmc): Once GenMC supports mixed size accesses, add stack/heap allocated Mutexes to the test. +// FIXME(genmc): Once the actual implementation of mutexes can be used in GenMC mode and there is a setting to disable Mutex interception: Add test revision without interception. +// +// Miri provides annotations to GenMC for the condition required to unblock a thread blocked on a Mutex lock call. +// This massively reduces the number of blocked executions we need to explore (in this test we require zero blocked execution). +// We use verbose output to check that this test always explores zero blocked executions. + +#![no_main] +#![feature(abort_unwind)] + +#[path = "../../../utils/genmc.rs"] +mod genmc; + +use std::sync::Mutex; + +use crate::genmc::*; + +const REPS: u64 = 3; + +static LOCK: Mutex = Mutex::new(0); +static OTHER_LOCK: Mutex = Mutex::new(1234); + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + std::panic::abort_unwind(main_); + 0 +} + +fn main_() { + // Two mutexes should not interfere, holding this guard does not affect the other mutex. + let other_guard = OTHER_LOCK.lock().unwrap(); + + let guard = LOCK.lock().unwrap(); + // Trying to lock should fail if the mutex is already held. + assert!(LOCK.try_lock().is_err()); + // Dropping the guard should unlock the mutex correctly. + drop(guard); + // Trying to lock now should succeed. + assert!(LOCK.try_lock().is_ok()); + + // Spawn multiple threads interacting with the same mutex. + unsafe { + let ids = [ + spawn_pthread_closure(|| { + for _ in 0..REPS { + *LOCK.lock().unwrap() += 2; + } + }), + spawn_pthread_closure(|| { + for _ in 0..REPS { + *LOCK.lock().unwrap() += 4; + } + }), + ]; + join_pthreads(ids); + } + // Due to the Mutex, all increments should be visible in every explored execution. + assert!(*LOCK.lock().unwrap() == REPS * 6); + + drop(other_guard); +} diff --git a/src/tools/miri/tests/genmc/pass/shims/mutex_simple.stderr b/src/tools/miri/tests/genmc/pass/shims/mutex_simple.stderr new file mode 100644 index 0000000000000..76ddd42addf9d --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/shims/mutex_simple.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 20 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.bounded123.stderr b/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.bounded123.stderr new file mode 100644 index 0000000000000..1458300b1110a --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.bounded123.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 232 executions. No errors found. +Number of complete executions explored: 108 +Number of blocked executions seen: 124 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.bounded321.stderr b/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.bounded321.stderr new file mode 100644 index 0000000000000..1458300b1110a --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.bounded321.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 232 executions. No errors found. +Number of complete executions explored: 108 +Number of blocked executions seen: 124 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.replaced123.stderr b/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.replaced123.stderr new file mode 100644 index 0000000000000..c79a87b33a5f5 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.replaced123.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 9 executions. No errors found. +Number of complete executions explored: 1 +Number of blocked executions seen: 8 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.replaced321.stderr b/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.replaced321.stderr new file mode 100644 index 0000000000000..c79a87b33a5f5 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.replaced321.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 9 executions. No errors found. +Number of complete executions explored: 1 +Number of blocked executions seen: 8 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.rs b/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.rs new file mode 100644 index 0000000000000..cf19e92994421 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.rs @@ -0,0 +1,94 @@ +//@ revisions: bounded123 bounded321 replaced123 replaced321 +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows -Zmiri-genmc-verbose +//@normalize-stderr-test: "Verification took .*s" -> "Verification took [TIME]s" + +// This test uses GenMC assume statements to bound or replace spinloops. +// Three threads pass a value to each other, spinning on an atomic FLAG to wait for the previous thread. +// +// There are two variants, one limits the spinloop to three iterations, and one that completely replaces the spin loop. +// Without this loop bounding, this test *cannot* be verified, since GenMC will have to explore infinitely many executions (one per possible number of loop iterations). + +// FIXME(genmc): GenMC provides the `--unroll=N` option, which limits all loops to at most N iterations (at the LLVM IR level). +// Such an option for Miri would allow a variant of this test without manual bounding, using this automatic loop bounding instead. + +// We use different thread orders to ensure it doesn't just pass by chance (each thread order should give the same result). +// We use verbose output to see the number of explored vs blocked executions. + +#![no_main] + +#[path = "../../../utils/genmc.rs"] +mod genmc; +#[path = "../../../utils/mod.rs"] +mod utils; + +use std::sync::atomic::AtomicU64; +use std::sync::atomic::Ordering::*; + +use crate::genmc::*; +use crate::utils::*; + +static mut X: u64 = 0; +static FLAG: AtomicU64 = AtomicU64::new(0); + +/// Unbounded variant of the spinloop. +/// This function causes GenMC to explore infinite executions. +#[allow(unused)] +fn spin_until_unbounded(value: u64) { + while FLAG.load(Acquire) != value { + std::hint::spin_loop(); + } +} + +#[cfg(any(bounded123, bounded321))] +/// We bound the loop to at most 3 iterations. +fn spin_until(value: u64) { + for _ in 0..3 { + if FLAG.load(Acquire) == value { + return; + } + } + unsafe { miri_genmc_assume(false) }; +} + +#[cfg(not(any(bounded123, bounded321)))] +/// For full replacement, we limit it to only 1 load. +fn spin_until(value: u64) { + unsafe { miri_genmc_assume(FLAG.load(Acquire) == value) }; +} + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + let t0 = || { + X = 42; + FLAG.store(1, Release); + + spin_until(3); + let c = X; + if c != 44 { + std::process::abort(); + } + }; + let t1 = || { + spin_until(1); + let a = X; + X = a + 1; + FLAG.store(2, Release); + }; + let t2 = || { + spin_until(2); + let b = X; + X = b + 1; + FLAG.store(3, Release); + }; + // Reverse the order for the second test variant. + #[cfg(any(bounded321, replaced321))] + let (t0, t1, t2) = (t2, t1, t0); + + spawn_pthread_closure(t0); + spawn_pthread_closure(t1); + spawn_pthread_closure(t2); + + 0 + } +} diff --git a/src/tools/miri/tests/genmc/pass/std/arc.check_count.stderr b/src/tools/miri/tests/genmc/pass/std/arc.check_count.stderr new file mode 100644 index 0000000000000..3dccd7059538e --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/arc.check_count.stderr @@ -0,0 +1,163 @@ +Running GenMC Verification... +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU64::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::thread::ThreadId::new` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::option::Option::::unwrap_or_else::<{closure@std::thread::current::id::get_or_init::{closure#0}}>` at RUSTLIB/core/src/option.rs:LL:CC + = note: inside `std::thread::current::id::get_or_init` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::thread::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::rt::init` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | match this.inner().weak.compare_exchange_weak(cur, cur + 1, Acquire, Relaxed) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::::downgrade` at RUSTLIB/alloc/src/sync.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let weak = Arc::downgrade(&data_clone); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | match this.inner().weak.compare_exchange_weak(cur, cur + 1, Acquire, Relaxed) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::::downgrade` at RUSTLIB/alloc/src/sync.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let weak = Arc::downgrade(&data_clone); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let handle = std::thread::spawn(move || { + | __________________^ +... | +LL | | }); + | |______^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let handle = std::thread::spawn(move || { + | __________________^ +... | +LL | | }); + | |______^ + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | if this.inner().weak.compare_exchange(1, usize::MAX, Acquire, Relaxed).is_ok() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::>::is_unique` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::sync::Arc::>::get_mut` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | handle.join().unwrap(); + | ^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU32::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::sync::PLATFORM::futex::Once::call` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::Once::call_once::<{closure@std::rt::cleanup::{closure#0}}>` at RUSTLIB/std/src/sync/poison/once.rs:LL:CC + = note: inside `std::rt::cleanup` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchg::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange::<*mut i32>` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicPtr::::compare_exchange` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::exit_guard::unique_thread_exit` at RUSTLIB/std/src/sys/exit_guard.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +Verification complete with 4 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/std/arc.rs b/src/tools/miri/tests/genmc/pass/std/arc.rs new file mode 100644 index 0000000000000..addf6408c006f --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/arc.rs @@ -0,0 +1,49 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows +//@revisions: check_count try_upgrade + +// Check that various operations on `std::sync::Arc` are handled properly in GenMC mode. +// +// The number of explored executions in the expected output of this test may change if +// the implementation of `Arc` is ever changed, or additional optimizations are added to GenMC mode. +// +// The revision that tries to upgrade the `Weak` should never explore fewer executions compared to the revision that just accesses the `strong_count`, +// since `upgrade` needs to access the `strong_count` internally. +// There should always be more than 1 execution for both, since there is always the possibilility that the `Arc` has already been dropped, or it hasn't. + +use std::sync::Arc; + +fn main() { + let data = Arc::new(42); + + // Clone the Arc, drop the original, check that memory still valid. + let data_clone = Arc::clone(&data); + drop(data); + assert!(*data_clone == 42); + + // Create a Weak reference. + let weak = Arc::downgrade(&data_clone); + + // Spawn a thread that uses the Arc. + let weak_ = weak.clone(); + let handle = std::thread::spawn(move || { + // Try to upgrade weak reference. + // Depending on execution schedule, this may fail or succeed depending on whether this runs before or after the `drop` in the main thread. + + #[cfg(check_count)] + let _strong_count = weak_.strong_count(); + + #[cfg(try_upgrade)] + if let Some(strong) = weak_.upgrade() { + assert_eq!(*strong, 42); + } + }); + + // Drop the last strong reference to the data. + drop(data_clone); + + // Wait for the thread to finish. + handle.join().unwrap(); + + // The upgrade should fail now (all Arcs dropped). + assert!(weak.upgrade().is_none()); +} diff --git a/src/tools/miri/tests/genmc/pass/std/arc.try_upgrade.stderr b/src/tools/miri/tests/genmc/pass/std/arc.try_upgrade.stderr new file mode 100644 index 0000000000000..dc59632558c8b --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/arc.try_upgrade.stderr @@ -0,0 +1,191 @@ +Running GenMC Verification... +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU64::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::thread::ThreadId::new` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::option::Option::::unwrap_or_else::<{closure@std::thread::current::id::get_or_init::{closure#0}}>` at RUSTLIB/core/src/option.rs:LL:CC + = note: inside `std::thread::current::id::get_or_init` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::thread::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::rt::init` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | match this.inner().weak.compare_exchange_weak(cur, cur + 1, Acquire, Relaxed) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::::downgrade` at RUSTLIB/alloc/src/sync.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let weak = Arc::downgrade(&data_clone); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | match this.inner().weak.compare_exchange_weak(cur, cur + 1, Acquire, Relaxed) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::::downgrade` at RUSTLIB/alloc/src/sync.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let weak = Arc::downgrade(&data_clone); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let handle = std::thread::spawn(move || { + | __________________^ +... | +LL | | }); + | |______^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let handle = std::thread::spawn(move || { + | __________________^ +... | +LL | | }); + | |______^ + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | if this.inner().weak.compare_exchange(1, usize::MAX, Acquire, Relaxed).is_ok() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::>::is_unique` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::sync::Arc::>::get_mut` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | handle.join().unwrap(); + | ^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU32::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::sync::PLATFORM::futex::Once::call` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::Once::call_once::<{closure@std::rt::cleanup::{closure#0}}>` at RUSTLIB/std/src/sync/poison/once.rs:LL:CC + = note: inside `std::rt::cleanup` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchg::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange::<*mut i32>` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicPtr::::compare_exchange` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::exit_guard::unique_thread_exit` at RUSTLIB/std/src/sys/exit_guard.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | if self.inner()?.strong.fetch_update(Acquire, Relaxed, checked_increment).is_ok() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE on thread `unnamed-ID`: + = note: inside `std::sync::Weak::::upgrade` at RUSTLIB/alloc/src/sync.rs:LL:CC +note: inside closure + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | if let Some(strong) = weak_.upgrade() { + | ^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | if self.inner()?.strong.fetch_update(Acquire, Relaxed, checked_increment).is_ok() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE on thread `unnamed-ID`: + = note: inside `std::sync::Weak::::upgrade` at RUSTLIB/alloc/src/sync.rs:LL:CC +note: inside closure + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | if let Some(strong) = weak_.upgrade() { + | ^^^^^^^^^^^^^^^ + +Verification complete with 7 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/std/empty_main.rs b/src/tools/miri/tests/genmc/pass/std/empty_main.rs new file mode 100644 index 0000000000000..2ffc3388fb36c --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/empty_main.rs @@ -0,0 +1,5 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// A lot of code runs before main, which we should be able to handle in GenMC mode. + +fn main() {} diff --git a/src/tools/miri/tests/genmc/pass/std/empty_main.stderr b/src/tools/miri/tests/genmc/pass/std/empty_main.stderr new file mode 100644 index 0000000000000..44c307a6b3e41 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/empty_main.stderr @@ -0,0 +1,60 @@ +Running GenMC Verification... +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU64::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::thread::ThreadId::new` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::option::Option::::unwrap_or_else::<{closure@std::thread::current::id::get_or_init::{closure#0}}>` at RUSTLIB/core/src/option.rs:LL:CC + = note: inside `std::thread::current::id::get_or_init` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::thread::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::rt::init` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU32::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::sync::PLATFORM::futex::Once::call` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::Once::call_once::<{closure@std::rt::cleanup::{closure#0}}>` at RUSTLIB/std/src/sync/poison/once.rs:LL:CC + = note: inside `std::rt::cleanup` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchg::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange::<*mut i32>` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicPtr::::compare_exchange` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::exit_guard::unique_thread_exit` at RUSTLIB/std/src/sys/exit_guard.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +Verification complete with 1 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.rs b/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.rs new file mode 100644 index 0000000000000..dadbee47b9860 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.rs @@ -0,0 +1,13 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// We should be able to spawn and join standard library threads in GenMC mode. +// Since these threads do nothing, we should only explore 1 program execution. + +const N: usize = 2; + +fn main() { + let handles: Vec<_> = (0..N).map(|_| std::thread::spawn(thread_func)).collect(); + handles.into_iter().for_each(|handle| handle.join().unwrap()); +} + +fn thread_func() {} diff --git a/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.stderr b/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.stderr new file mode 100644 index 0000000000000..22a58f4e9cef0 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.stderr @@ -0,0 +1,167 @@ +Running GenMC Verification... +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU64::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::thread::ThreadId::new` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::option::Option::::unwrap_or_else::<{closure@std::thread::current::id::get_or_init::{closure#0}}>` at RUSTLIB/core/src/option.rs:LL:CC + = note: inside `std::thread::current::id::get_or_init` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::thread::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::rt::init` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside closure + --> tests/genmc/pass/std/spawn_std_threads.rs:LL:CC + | +LL | let handles: Vec<_> = (0..N).map(|_| std::thread::spawn(thread_func)).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: inside closure at RUSTLIB/core/src/iter/adapters/map.rs:LL:CC + = note: inside ` as std::iter::Iterator>::fold::<(), {closure@std::iter::adapters::map::map_fold, (), {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}, {closure@std::iter::Iterator::for_each::call, {closure@std::vec::Vec>::extend_trusted, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>::{closure#0}}>::{closure#0}}>::{closure#0}}>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC + = note: inside `, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}> as std::iter::Iterator>::fold::<(), {closure@std::iter::Iterator::for_each::call, {closure@std::vec::Vec>::extend_trusted, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>::{closure#0}}>::{closure#0}}>` at RUSTLIB/core/src/iter/adapters/map.rs:LL:CC + = note: inside `, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}> as std::iter::Iterator>::for_each::<{closure@std::vec::Vec>::extend_trusted, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>::{closure#0}}>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC + = note: inside `std::vec::Vec::>::extend_trusted::, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>` at RUSTLIB/alloc/src/vec/mod.rs:LL:CC + = note: inside `> as std::vec::spec_extend::SpecExtend, std::iter::Map, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>>::spec_extend` at RUSTLIB/alloc/src/vec/spec_extend.rs:LL:CC + = note: inside `> as std::vec::spec_from_iter_nested::SpecFromIterNested, std::iter::Map, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>>::from_iter` at RUSTLIB/alloc/src/vec/spec_from_iter_nested.rs:LL:CC + = note: inside `> as std::vec::spec_from_iter::SpecFromIter, std::iter::Map, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>>::from_iter` at RUSTLIB/alloc/src/vec/spec_from_iter.rs:LL:CC + = note: inside `> as std::iter::FromIterator>>::from_iter::, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>` at RUSTLIB/alloc/src/vec/mod.rs:LL:CC + = note: inside `, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}> as std::iter::Iterator>::collect::>>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/spawn_std_threads.rs:LL:CC + | +LL | let handles: Vec<_> = (0..N).map(|_| std::thread::spawn(thread_func)).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside closure + --> tests/genmc/pass/std/spawn_std_threads.rs:LL:CC + | +LL | let handles: Vec<_> = (0..N).map(|_| std::thread::spawn(thread_func)).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: inside closure at RUSTLIB/core/src/iter/adapters/map.rs:LL:CC + = note: inside ` as std::iter::Iterator>::fold::<(), {closure@std::iter::adapters::map::map_fold, (), {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}, {closure@std::iter::Iterator::for_each::call, {closure@std::vec::Vec>::extend_trusted, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>::{closure#0}}>::{closure#0}}>::{closure#0}}>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC + = note: inside `, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}> as std::iter::Iterator>::fold::<(), {closure@std::iter::Iterator::for_each::call, {closure@std::vec::Vec>::extend_trusted, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>::{closure#0}}>::{closure#0}}>` at RUSTLIB/core/src/iter/adapters/map.rs:LL:CC + = note: inside `, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}> as std::iter::Iterator>::for_each::<{closure@std::vec::Vec>::extend_trusted, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>::{closure#0}}>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC + = note: inside `std::vec::Vec::>::extend_trusted::, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>` at RUSTLIB/alloc/src/vec/mod.rs:LL:CC + = note: inside `> as std::vec::spec_extend::SpecExtend, std::iter::Map, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>>::spec_extend` at RUSTLIB/alloc/src/vec/spec_extend.rs:LL:CC + = note: inside `> as std::vec::spec_from_iter_nested::SpecFromIterNested, std::iter::Map, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>>::from_iter` at RUSTLIB/alloc/src/vec/spec_from_iter_nested.rs:LL:CC + = note: inside `> as std::vec::spec_from_iter::SpecFromIter, std::iter::Map, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>>::from_iter` at RUSTLIB/alloc/src/vec/spec_from_iter.rs:LL:CC + = note: inside `> as std::iter::FromIterator>>::from_iter::, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>` at RUSTLIB/alloc/src/vec/mod.rs:LL:CC + = note: inside `, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}> as std::iter::Iterator>::collect::>>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/spawn_std_threads.rs:LL:CC + | +LL | let handles: Vec<_> = (0..N).map(|_| std::thread::spawn(thread_func)).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | if this.inner().weak.compare_exchange(1, usize::MAX, Acquire, Relaxed).is_ok() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::>::is_unique` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::sync::Arc::>::get_mut` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside closure + --> tests/genmc/pass/std/spawn_std_threads.rs:LL:CC + | +LL | handles.into_iter().for_each(|handle| handle.join().unwrap()); + | ^^^^^^^^^^^^^ + = note: inside closure at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC + = note: inside `> as std::iter::Iterator>::fold::<(), {closure@std::iter::Iterator::for_each::call, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>::{closure#0}}>` at RUSTLIB/alloc/src/vec/into_iter.rs:LL:CC + = note: inside `> as std::iter::Iterator>::for_each::<{closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/spawn_std_threads.rs:LL:CC + | +LL | handles.into_iter().for_each(|handle| handle.join().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU32::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::sync::PLATFORM::futex::Once::call` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::Once::call_once::<{closure@std::rt::cleanup::{closure#0}}>` at RUSTLIB/std/src/sync/poison/once.rs:LL:CC + = note: inside `std::rt::cleanup` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchg::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange::<*mut i32>` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicPtr::::compare_exchange` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::exit_guard::unique_thread_exit` at RUSTLIB/std/src/sys/exit_guard.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +Verification complete with 1 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/std/thread_locals.rs b/src/tools/miri/tests/genmc/pass/std/thread_locals.rs new file mode 100644 index 0000000000000..d76975d2e92c2 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/thread_locals.rs @@ -0,0 +1,35 @@ +//@compile-flags: -Zmiri-ignore-leaks -Zmiri-genmc -Zmiri-disable-stacked-borrows + +use std::alloc::{Layout, alloc}; +use std::cell::Cell; +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +static X: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); + +thread_local! { + static R: Cell<*mut u64> = Cell::new(std::ptr::null_mut()); +} + +pub unsafe fn malloc() -> *mut u64 { + alloc(Layout::new::()) as *mut u64 +} + +fn main() { + let handles = [ + std::thread::spawn(|| { + R.set(unsafe { malloc() }); + let r_ptr = R.get(); + let _ = X.compare_exchange(std::ptr::null_mut(), r_ptr, SeqCst, SeqCst); + }), + std::thread::spawn(|| { + R.set(unsafe { malloc() }); + }), + std::thread::spawn(|| { + R.set(unsafe { malloc() }); + let r_ptr = R.get(); + let _ = X.compare_exchange(std::ptr::null_mut(), r_ptr, SeqCst, SeqCst); + }), + ]; + handles.into_iter().for_each(|handle| handle.join().unwrap()); +} diff --git a/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr b/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr new file mode 100644 index 0000000000000..40faedf49c6e1 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr @@ -0,0 +1,150 @@ +Running GenMC Verification... +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU64::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::thread::ThreadId::new` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::option::Option::::unwrap_or_else::<{closure@std::thread::current::id::get_or_init::{closure#0}}>` at RUSTLIB/core/src/option.rs:LL:CC + = note: inside `std::thread::current::id::get_or_init` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::thread::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::rt::init` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/thread_locals.rs:LL:CC + | +LL | / std::thread::spawn(|| { +LL | | R.set(unsafe { malloc() }); +LL | | let r_ptr = R.get(); +LL | | let _ = X.compare_exchange(std::ptr::null_mut(), r_ptr, SeqCst, SeqCst); +LL | | }), + | |__________^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/thread_locals.rs:LL:CC + | +LL | / std::thread::spawn(|| { +LL | | R.set(unsafe { malloc() }); +LL | | let r_ptr = R.get(); +LL | | let _ = X.compare_exchange(std::ptr::null_mut(), r_ptr, SeqCst, SeqCst); +LL | | }), + | |__________^ + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | if this.inner().weak.compare_exchange(1, usize::MAX, Acquire, Relaxed).is_ok() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::>::is_unique` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::sync::Arc::>::get_mut` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside closure + --> tests/genmc/pass/std/thread_locals.rs:LL:CC + | +LL | handles.into_iter().for_each(|handle| handle.join().unwrap()); + | ^^^^^^^^^^^^^ + = note: inside closure at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC + = note: inside closure at RUSTLIB/core/src/ops/try_trait.rs:LL:CC + = note: inside closure at RUSTLIB/core/src/array/iter/iter_inner.rs:LL:CC + = note: inside `::try_fold::<(), {closure@std::array::iter::iter_inner::PolymorphicIter<[std::mem::MaybeUninit>]>::try_fold<(), {closure@std::ops::try_trait::NeverShortCircuit<()>::wrap_mut_2<(), std::thread::JoinHandle<()>, {closure@std::iter::Iterator::for_each::call, {closure@tests/genmc/pass/std/thread_locals.rs:LL:CC}>::{closure#0}}>::{closure#0}}, std::ops::try_trait::NeverShortCircuit<()>>::{closure#0}}, std::ops::try_trait::NeverShortCircuit<()>>` at RUSTLIB/core/src/ops/index_range.rs:LL:CC + = note: inside `std::array::iter::iter_inner::PolymorphicIter::<[std::mem::MaybeUninit>]>::try_fold::<(), {closure@std::ops::try_trait::NeverShortCircuit<()>::wrap_mut_2<(), std::thread::JoinHandle<()>, {closure@std::iter::Iterator::for_each::call, {closure@tests/genmc/pass/std/thread_locals.rs:LL:CC}>::{closure#0}}>::{closure#0}}, std::ops::try_trait::NeverShortCircuit<()>>` at RUSTLIB/core/src/array/iter/iter_inner.rs:LL:CC + = note: inside `std::array::iter::iter_inner::PolymorphicIter::<[std::mem::MaybeUninit>]>::fold::<(), {closure@std::iter::Iterator::for_each::call, {closure@tests/genmc/pass/std/thread_locals.rs:LL:CC}>::{closure#0}}>` at RUSTLIB/core/src/array/iter/iter_inner.rs:LL:CC + = note: inside `, 3> as std::iter::Iterator>::fold::<(), {closure@std::iter::Iterator::for_each::call, {closure@tests/genmc/pass/std/thread_locals.rs:LL:CC}>::{closure#0}}>` at RUSTLIB/core/src/array/iter.rs:LL:CC + = note: inside `, 3> as std::iter::Iterator>::for_each::<{closure@tests/genmc/pass/std/thread_locals.rs:LL:CC}>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/thread_locals.rs:LL:CC + | +LL | handles.into_iter().for_each(|handle| handle.join().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU32::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::sync::PLATFORM::futex::Once::call` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::Once::call_once::<{closure@std::rt::cleanup::{closure#0}}>` at RUSTLIB/std/src/sync/poison/once.rs:LL:CC + = note: inside `std::rt::cleanup` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchg::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange::<*mut i32>` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicPtr::::compare_exchange` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::exit_guard::unique_thread_exit` at RUSTLIB/std/src/sys/exit_guard.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +Verification complete with 2 executions. No errors found. diff --git a/src/tools/miri/tests/panic/mir-validation.rs b/src/tools/miri/tests/panic/mir-validation.rs index 2d0d530754d8c..11c4e395920c0 100644 --- a/src/tools/miri/tests/panic/mir-validation.rs +++ b/src/tools/miri/tests/panic/mir-validation.rs @@ -13,7 +13,7 @@ use core::intrinsics::mir::*; #[custom_mir(dialect = "runtime", phase = "optimized")] -pub fn main() { +fn main() { mir! { let x: i32; let tuple: (*mut i32,); diff --git a/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs b/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs index b57386000404f..df42780021610 100644 --- a/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs +++ b/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs @@ -29,7 +29,7 @@ pub unsafe fn set(key: Key, value: *mut u8) { assert_eq!(r, 0); } -pub fn record(r: usize) { +fn record(r: usize) { assert!(r < 10); unsafe { RECORD = RECORD * 10 + r }; } diff --git a/src/tools/miri/tests/pass-dep/foreign-fn-linkname.rs b/src/tools/miri/tests/pass-dep/foreign-fn-linkname.rs index 9f090a4eff5d8..afd5bc0d3b564 100644 --- a/src/tools/miri/tests/pass-dep/foreign-fn-linkname.rs +++ b/src/tools/miri/tests/pass-dep/foreign-fn-linkname.rs @@ -14,7 +14,7 @@ fn strlen(str: String) -> usize { unsafe { mlibc::my_strlen(s.as_ptr()) as usize } } -pub fn main() { +fn main() { let len = strlen("Rust".to_string()); assert_eq!(len, 4); } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-mem.rs b/src/tools/miri/tests/pass-dep/libc/libc-mem.rs index 727533a9de61d..531d637d1f24d 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-mem.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-mem.rs @@ -78,6 +78,47 @@ fn test_strcpy() { } } +fn test_memset() { + unsafe { + let val = 1; + let dest = libc::calloc(3, 1); + libc::memset(dest, val, 3); + let slc = std::slice::from_raw_parts(dest as *const i8, 3); + assert_eq!(*slc, [1i8, 1, 1]); + libc::free(dest); + } + + unsafe { + let val = 1; + let dest = libc::calloc(4, 1); + libc::memset(dest, val, 3); + let slc = std::slice::from_raw_parts(dest as *const i8, 4); + assert_eq!(*slc, [1i8, 1, 1, 0]); + libc::free(dest); + } + + unsafe { + let val = 1; + let mut dest = 0_i8; + libc::memset(&mut dest as *mut i8 as *mut libc::c_void, val, mem::size_of::()); + assert_eq!(dest, val as i8); + } + + unsafe { + let val = 1; + let mut dest = 0_i16; + libc::memset(&mut dest as *mut i16 as *mut libc::c_void, val, mem::size_of::()); + assert_eq!(dest, 257); + } + + unsafe { + let val = 257; + let mut dest = 0_i16; + libc::memset(&mut dest as *mut i16 as *mut libc::c_void, val, mem::size_of::()); + assert_eq!(dest, 257); + } +} + fn test_malloc() { // Test that small allocations sometimes *are* not very aligned. let saw_unaligned = (0..64).any(|_| unsafe { @@ -310,4 +351,5 @@ fn main() { test_memcpy(); test_strcpy(); + test_memset(); } diff --git a/src/tools/miri/tests/pass-dep/regions-mock-trans.rs b/src/tools/miri/tests/pass-dep/regions-mock-trans.rs index 57f1b75f4d52b..7defea3c4876e 100644 --- a/src/tools/miri/tests/pass-dep/regions-mock-trans.rs +++ b/src/tools/miri/tests/pass-dep/regions-mock-trans.rs @@ -39,7 +39,7 @@ fn f(ccx: &Ccx) { return g(&fcx); } -pub fn main() { +fn main() { let ccx = Ccx { x: 0 }; f(&ccx); } diff --git a/src/tools/miri/tests/pass-dep/wcslen.rs b/src/tools/miri/tests/pass-dep/wcslen.rs index c5c9d99247965..2ec4da79d727b 100644 --- a/src/tools/miri/tests/pass-dep/wcslen.rs +++ b/src/tools/miri/tests/pass-dep/wcslen.rs @@ -13,7 +13,7 @@ fn to_c_wchar_t_str(s: &str) -> Vec { r } -pub fn main() { +fn main() { let s = to_c_wchar_t_str("Rust"); let len = unsafe { libc::wcslen(s.as_ptr()) }; assert_eq!(len, 4); diff --git a/src/tools/miri/tests/pass/0weak_memory/consistency.rs b/src/tools/miri/tests/pass/0weak_memory/consistency.rs index 16a38ebd9d4da..83674cca1bde4 100644 --- a/src/tools/miri/tests/pass/0weak_memory/consistency.rs +++ b/src/tools/miri/tests/pass/0weak_memory/consistency.rs @@ -212,7 +212,7 @@ fn test_single_thread() { fn test_sync_through_rmw_and_fences() { // Example from https://github.com/llvm/llvm-project/issues/56450#issuecomment-1183695905 #[no_mangle] - pub fn rdmw(storing: &AtomicI32, sync: &AtomicI32, loading: &AtomicI32) -> i32 { + fn rdmw(storing: &AtomicI32, sync: &AtomicI32, loading: &AtomicI32) -> i32 { storing.store(1, Relaxed); fence(Release); sync.fetch_add(0, Relaxed); @@ -245,7 +245,7 @@ fn test_sync_through_rmw_and_fences() { assert_ne!((a, b), (0, 0)); } -pub fn main() { +fn main() { for _ in 0..50 { test_single_thread(); test_mixed_access(); diff --git a/src/tools/miri/tests/pass/0weak_memory/consistency_sc.rs b/src/tools/miri/tests/pass/0weak_memory/consistency_sc.rs index cb8535b8ad74b..d92c0d1779971 100644 --- a/src/tools/miri/tests/pass/0weak_memory/consistency_sc.rs +++ b/src/tools/miri/tests/pass/0weak_memory/consistency_sc.rs @@ -348,7 +348,7 @@ fn test_sc_relaxed() { assert!(!bad); } -pub fn main() { +fn main() { for _ in 0..32 { test_sc_store_buffering(); test_iriw_sc_rlx(); diff --git a/src/tools/miri/tests/pass/0weak_memory/extra_cpp.rs b/src/tools/miri/tests/pass/0weak_memory/extra_cpp.rs index 94df730808066..6791382f8e0f9 100644 --- a/src/tools/miri/tests/pass/0weak_memory/extra_cpp.rs +++ b/src/tools/miri/tests/pass/0weak_memory/extra_cpp.rs @@ -72,7 +72,7 @@ fn from_mut_split() { assert_eq!(x_lo_atomic.load(Relaxed), u16::from_be(0xfafa)); } -pub fn main() { +fn main() { get_mut_write(); from_mut_split(); assign_to_mut(); diff --git a/src/tools/miri/tests/pass/0weak_memory/weak.rs b/src/tools/miri/tests/pass/0weak_memory/weak.rs index 611733d0dac52..e329bbff1aaed 100644 --- a/src/tools/miri/tests/pass/0weak_memory/weak.rs +++ b/src/tools/miri/tests/pass/0weak_memory/weak.rs @@ -115,7 +115,7 @@ fn initialization_write(add_fence: bool) { fn faa_replaced_by_load() { check_all_outcomes([true, false], || { // Example from https://github.com/llvm/llvm-project/issues/56450#issuecomment-1183695905 - pub fn rdmw(storing: &AtomicUsize, sync: &AtomicUsize, loading: &AtomicUsize) -> usize { + fn rdmw(storing: &AtomicUsize, sync: &AtomicUsize, loading: &AtomicUsize) -> usize { storing.store(1, Relaxed); fence(Release); // sync.fetch_add(0, Relaxed); @@ -226,7 +226,7 @@ fn old_release_store() { }); } -pub fn main() { +fn main() { relaxed(); seq_cst(); initialization_write(false); diff --git a/src/tools/miri/tests/pass/align_repeat_into_well_aligned_array.rs b/src/tools/miri/tests/pass/align_repeat_into_well_aligned_array.rs index 82a74e392b9cc..6c0b180ac8320 100644 --- a/src/tools/miri/tests/pass/align_repeat_into_well_aligned_array.rs +++ b/src/tools/miri/tests/pass/align_repeat_into_well_aligned_array.rs @@ -25,7 +25,7 @@ pub const KEYBYTES: usize = 8 * size_of::(); pub const BLOCKBYTES: usize = 16 * size_of::(); impl Params { - pub fn new() -> Self { + fn new() -> Self { Self { hash_length: OUTBYTES as u8, key_length: 0, diff --git a/src/tools/miri/tests/pass/async-closure-captures.rs b/src/tools/miri/tests/pass/async-closure-captures.rs index ed6b7b205b540..785ff2bc02128 100644 --- a/src/tools/miri/tests/pass/async-closure-captures.rs +++ b/src/tools/miri/tests/pass/async-closure-captures.rs @@ -6,7 +6,7 @@ use std::future::Future; use std::pin::pin; use std::task::*; -pub fn block_on(fut: impl Future) -> T { +fn block_on(fut: impl Future) -> T { let mut fut = pin!(fut); let ctx = &mut Context::from_waker(Waker::noop()); diff --git a/src/tools/miri/tests/pass/async-closure-drop.rs b/src/tools/miri/tests/pass/async-closure-drop.rs index 105aa434b0ad9..d1fd92814d950 100644 --- a/src/tools/miri/tests/pass/async-closure-drop.rs +++ b/src/tools/miri/tests/pass/async-closure-drop.rs @@ -4,7 +4,7 @@ use std::future::Future; use std::pin::pin; use std::task::*; -pub fn block_on(fut: impl Future) -> T { +fn block_on(fut: impl Future) -> T { let mut fut = pin!(fut); let ctx = &mut Context::from_waker(Waker::noop()); @@ -29,7 +29,7 @@ impl Drop for DropMe { } } -pub fn main() { +fn main() { block_on(async { let b = DropMe("hello"); let async_closure = async move |a: DropMe| { diff --git a/src/tools/miri/tests/pass/async-closure.rs b/src/tools/miri/tests/pass/async-closure.rs index 4c0fb356f9db7..1b38f06eb7cd9 100644 --- a/src/tools/miri/tests/pass/async-closure.rs +++ b/src/tools/miri/tests/pass/async-closure.rs @@ -6,7 +6,7 @@ use std::ops::{AsyncFn, AsyncFnMut, AsyncFnOnce}; use std::pin::pin; use std::task::*; -pub fn block_on(fut: impl Future) -> T { +fn block_on(fut: impl Future) -> T { let mut fut = pin!(fut); let ctx = &mut Context::from_waker(Waker::noop()); @@ -38,7 +38,7 @@ async fn call_normal_mut>(f: &mut impl FnMut(i32) -> F) { f(1).await; } -pub fn main() { +fn main() { block_on(async { let b = 2i32; let mut async_closure = async move |a: i32| { diff --git a/src/tools/miri/tests/pass/binops.rs b/src/tools/miri/tests/pass/binops.rs index fcbe6c85b7b8f..fa8993c342193 100644 --- a/src/tools/miri/tests/pass/binops.rs +++ b/src/tools/miri/tests/pass/binops.rs @@ -71,7 +71,7 @@ fn test_class() { assert!(q != r); } -pub fn main() { +fn main() { test_nil(); test_bool(); test_ptr(); diff --git a/src/tools/miri/tests/pass/btreemap.rs b/src/tools/miri/tests/pass/btreemap.rs index 7af6d7b5551c7..cfd01ce28719d 100644 --- a/src/tools/miri/tests/pass/btreemap.rs +++ b/src/tools/miri/tests/pass/btreemap.rs @@ -25,7 +25,7 @@ fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator } } -pub fn main() { +fn main() { let mut b = BTreeSet::new(); b.insert(Foo::A("\'")); b.insert(Foo::A("/=")); diff --git a/src/tools/miri/tests/pass/calls.rs b/src/tools/miri/tests/pass/calls.rs index 8db3d3590cc1e..4f7b27c41c5cd 100644 --- a/src/tools/miri/tests/pass/calls.rs +++ b/src/tools/miri/tests/pass/calls.rs @@ -35,7 +35,7 @@ fn const_fn_call() -> i64 { } fn call_return_into_passed_reference() { - pub fn func(v: &mut T, f: fn(&T) -> T) { + fn func(v: &mut T, f: fn(&T) -> T) { // MIR building will introduce a temporary, so this becomes // `let temp = f(v); *v = temp;`. // If this got optimized to `*v = f(v)` on the MIR level we'd have UB diff --git a/src/tools/miri/tests/pass/concurrency/data_race.rs b/src/tools/miri/tests/pass/concurrency/data_race.rs index d5dd1deb2d9dd..9c434e433d80f 100644 --- a/src/tools/miri/tests/pass/concurrency/data_race.rs +++ b/src/tools/miri/tests/pass/concurrency/data_race.rs @@ -57,7 +57,7 @@ fn test_multiple_reads() { assert_eq!(var, 10); } -pub fn test_rmw_no_block() { +fn test_rmw_no_block() { static SYNC: AtomicUsize = AtomicUsize::new(0); let mut a = 0u32; @@ -89,7 +89,7 @@ pub fn test_rmw_no_block() { } } -pub fn test_simple_release() { +fn test_simple_release() { static SYNC: AtomicUsize = AtomicUsize::new(0); let mut a = 0u32; @@ -214,7 +214,7 @@ fn failing_rmw_is_read() { }); } -pub fn main() { +fn main() { test_fence_sync(); test_multiple_reads(); test_rmw_no_block(); diff --git a/src/tools/miri/tests/pass/concurrency/disable_data_race_detector.rs b/src/tools/miri/tests/pass/concurrency/disable_data_race_detector.rs index ecc4ca59bd18a..26c63161b55b5 100644 --- a/src/tools/miri/tests/pass/concurrency/disable_data_race_detector.rs +++ b/src/tools/miri/tests/pass/concurrency/disable_data_race_detector.rs @@ -10,7 +10,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = 0u32; let b = &mut a as *mut u32; let c = EvilSend(b); diff --git a/src/tools/miri/tests/pass/const-vec-of-fns.rs b/src/tools/miri/tests/pass/const-vec-of-fns.rs index 7f0782fe32247..4bed4bb849e26 100644 --- a/src/tools/miri/tests/pass/const-vec-of-fns.rs +++ b/src/tools/miri/tests/pass/const-vec-of-fns.rs @@ -8,7 +8,7 @@ fn f() {} static mut CLOSURES: &'static mut [fn()] = &mut [f as fn(), f as fn()]; -pub fn main() { +fn main() { unsafe { for closure in &mut *CLOSURES { (*closure)() diff --git a/src/tools/miri/tests/pass/drop_type_without_drop_glue.rs b/src/tools/miri/tests/pass/drop_type_without_drop_glue.rs index 2f8665e8d62c0..9c4121baa637e 100644 --- a/src/tools/miri/tests/pass/drop_type_without_drop_glue.rs +++ b/src/tools/miri/tests/pass/drop_type_without_drop_glue.rs @@ -15,7 +15,7 @@ fn drop_in_place_with_terminator(ptr: *mut i32) { } } -pub fn main() { +fn main() { drop_in_place_with_terminator(std::ptr::without_provenance_mut(0)); drop_in_place_with_terminator(std::ptr::without_provenance_mut(1)); } diff --git a/src/tools/miri/tests/pass/dst-raw.rs b/src/tools/miri/tests/pass/dst-raw.rs index 3d0b843b3da22..7ffef3320e029 100644 --- a/src/tools/miri/tests/pass/dst-raw.rs +++ b/src/tools/miri/tests/pass/dst-raw.rs @@ -19,7 +19,7 @@ struct Foo { f: T, } -pub fn main() { +fn main() { // raw trait object let x = A { f: 42 }; let z: *const dyn Trait = &x; diff --git a/src/tools/miri/tests/pass/dst-struct-sole.rs b/src/tools/miri/tests/pass/dst-struct-sole.rs index 4b25fbb063006..931601bcd9547 100644 --- a/src/tools/miri/tests/pass/dst-struct-sole.rs +++ b/src/tools/miri/tests/pass/dst-struct-sole.rs @@ -33,7 +33,7 @@ impl ToBar for Bar { } } -pub fn main() { +fn main() { // With a vec of ints. let f1 = Fat { ptr: [1, 2, 3] }; foo(&f1); diff --git a/src/tools/miri/tests/pass/dst-struct.rs b/src/tools/miri/tests/pass/dst-struct.rs index 59763bbbfdd36..cf92ebfa04454 100644 --- a/src/tools/miri/tests/pass/dst-struct.rs +++ b/src/tools/miri/tests/pass/dst-struct.rs @@ -48,7 +48,7 @@ impl ToBar for Bar { } } -pub fn main() { +fn main() { // With a vec of ints. let f1: Fat<[isize; 3]> = Fat { f1: 5, f2: "some str", ptr: [1, 2, 3] }; foo(&f1); diff --git a/src/tools/miri/tests/pass/enum-nullable-const-null-with-fields.rs b/src/tools/miri/tests/pass/enum-nullable-const-null-with-fields.rs index 86f30f42b6293..8a320fcdb4d43 100644 --- a/src/tools/miri/tests/pass/enum-nullable-const-null-with-fields.rs +++ b/src/tools/miri/tests/pass/enum-nullable-const-null-with-fields.rs @@ -3,6 +3,6 @@ static C: Result<(), Box> = Ok(()); // This is because of yet another bad assertion (ICE) about the null side of a nullable enum. // So we won't actually compile if the bug is present, but we check the value in main anyway. -pub fn main() { +fn main() { assert!(C.is_ok()); } diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs index 67a14c2b38950..7b23518d73dad 100644 --- a/src/tools/miri/tests/pass/float.rs +++ b/src/tools/miri/tests/pass/float.rs @@ -1037,7 +1037,7 @@ fn mul_add() { assert_eq!(f.to_bits(), f32::to_bits(-0.0)); } -pub fn libm() { +fn libm() { fn ldexp(a: f64, b: i32) -> f64 { extern "C" { fn ldexp(x: f64, n: i32) -> f64; @@ -1300,7 +1300,7 @@ fn test_fast() { use std::intrinsics::{fadd_fast, fdiv_fast, fmul_fast, frem_fast, fsub_fast}; #[inline(never)] - pub fn test_operations_f16(a: f16, b: f16) { + fn test_operations_f16(a: f16, b: f16) { // make sure they all map to the correct operation unsafe { assert_approx_eq!(fadd_fast(a, b), a + b); @@ -1312,7 +1312,7 @@ fn test_fast() { } #[inline(never)] - pub fn test_operations_f32(a: f32, b: f32) { + fn test_operations_f32(a: f32, b: f32) { // make sure they all map to the correct operation unsafe { assert_approx_eq!(fadd_fast(a, b), a + b); @@ -1324,7 +1324,7 @@ fn test_fast() { } #[inline(never)] - pub fn test_operations_f64(a: f64, b: f64) { + fn test_operations_f64(a: f64, b: f64) { // make sure they all map to the correct operation unsafe { assert_approx_eq!(fadd_fast(a, b), a + b); @@ -1336,7 +1336,7 @@ fn test_fast() { } #[inline(never)] - pub fn test_operations_f128(a: f128, b: f128) { + fn test_operations_f128(a: f128, b: f128) { // make sure they all map to the correct operation unsafe { assert_approx_eq!(fadd_fast(a, b), a + b); @@ -1363,7 +1363,7 @@ fn test_algebraic() { }; #[inline(never)] - pub fn test_operations_f16(a: f16, b: f16) { + fn test_operations_f16(a: f16, b: f16) { // make sure they all map to the correct operation assert_approx_eq!(fadd_algebraic(a, b), a + b); assert_approx_eq!(fsub_algebraic(a, b), a - b); @@ -1373,7 +1373,7 @@ fn test_algebraic() { } #[inline(never)] - pub fn test_operations_f32(a: f32, b: f32) { + fn test_operations_f32(a: f32, b: f32) { // make sure they all map to the correct operation assert_approx_eq!(fadd_algebraic(a, b), a + b); assert_approx_eq!(fsub_algebraic(a, b), a - b); @@ -1383,7 +1383,7 @@ fn test_algebraic() { } #[inline(never)] - pub fn test_operations_f64(a: f64, b: f64) { + fn test_operations_f64(a: f64, b: f64) { // make sure they all map to the correct operation assert_approx_eq!(fadd_algebraic(a, b), a + b); assert_approx_eq!(fsub_algebraic(a, b), a - b); @@ -1393,7 +1393,7 @@ fn test_algebraic() { } #[inline(never)] - pub fn test_operations_f128(a: f128, b: f128) { + fn test_operations_f128(a: f128, b: f128) { // make sure they all map to the correct operation assert_approx_eq!(fadd_algebraic(a, b), a + b); assert_approx_eq!(fsub_algebraic(a, b), a - b); @@ -1418,12 +1418,12 @@ fn test_fmuladd() { // FIXME(f16_f128): add when supported #[inline(never)] - pub fn test_operations_f32(a: f32, b: f32, c: f32) { + fn test_operations_f32(a: f32, b: f32, c: f32) { assert_approx_eq!(fmuladdf32(a, b, c), a * b + c); } #[inline(never)] - pub fn test_operations_f64(a: f64, b: f64, c: f64) { + fn test_operations_f64(a: f64, b: f64, c: f64) { assert_approx_eq!(fmuladdf64(a, b, c), a * b + c); } @@ -1468,10 +1468,10 @@ fn test_non_determinism() { }; } - pub fn test_operations_f16(a: f16, b: f16) { + fn test_operations_f16(a: f16, b: f16) { test_operations_f!(a, b); } - pub fn test_operations_f32(a: f32, b: f32) { + fn test_operations_f32(a: f32, b: f32) { test_operations_f!(a, b); check_nondet(|| a.powf(b)); check_nondet(|| a.powi(2)); @@ -1507,7 +1507,7 @@ fn test_non_determinism() { check_nondet(|| 5.0f32.erf()); check_nondet(|| 5.0f32.erfc()); } - pub fn test_operations_f64(a: f64, b: f64) { + fn test_operations_f64(a: f64, b: f64) { test_operations_f!(a, b); check_nondet(|| a.powf(b)); check_nondet(|| a.powi(2)); @@ -1538,7 +1538,7 @@ fn test_non_determinism() { check_nondet(|| 5.0f64.erf()); check_nondet(|| 5.0f64.erfc()); } - pub fn test_operations_f128(a: f128, b: f128) { + fn test_operations_f128(a: f128, b: f128) { test_operations_f!(a, b); } diff --git a/src/tools/miri/tests/pass/function_calls/return_place_on_heap.rs b/src/tools/miri/tests/pass/function_calls/return_place_on_heap.rs index 04a55d7007ce7..6eee5f21e1bb1 100644 --- a/src/tools/miri/tests/pass/function_calls/return_place_on_heap.rs +++ b/src/tools/miri/tests/pass/function_calls/return_place_on_heap.rs @@ -5,7 +5,7 @@ use std::intrinsics::mir::*; // Make sure calls with the return place "on the heap" work. #[custom_mir(dialect = "runtime", phase = "optimized")] -pub fn main() { +fn main() { mir! { { let x = 0; diff --git a/src/tools/miri/tests/pass/integer-ops.rs b/src/tools/miri/tests/pass/integer-ops.rs index 3f8ac34e7d10a..1792d16734fdd 100644 --- a/src/tools/miri/tests/pass/integer-ops.rs +++ b/src/tools/miri/tests/pass/integer-ops.rs @@ -56,7 +56,7 @@ fn basic() { assert_eq!(match_int_range(), 4); } -pub fn main() { +fn main() { basic(); // This tests that we do (not) do sign extension properly when loading integers diff --git a/src/tools/miri/tests/pass/intrinsics/integer.rs b/src/tools/miri/tests/pass/intrinsics/integer.rs index 8727b6d3c87ec..a67c52f7b420a 100644 --- a/src/tools/miri/tests/pass/intrinsics/integer.rs +++ b/src/tools/miri/tests/pass/intrinsics/integer.rs @@ -4,7 +4,7 @@ #![feature(core_intrinsics, funnel_shifts)] use std::intrinsics::*; -pub fn main() { +fn main() { unsafe { [assert_eq!(ctpop(0u8), 0), assert_eq!(ctpop(0i8), 0)]; [assert_eq!(ctpop(0u16), 0), assert_eq!(ctpop(0i16), 0)]; diff --git a/src/tools/miri/tests/pass/intrinsics/volatile.rs b/src/tools/miri/tests/pass/intrinsics/volatile.rs index c9799801455c6..b72020cb83d21 100644 --- a/src/tools/miri/tests/pass/intrinsics/volatile.rs +++ b/src/tools/miri/tests/pass/intrinsics/volatile.rs @@ -2,7 +2,7 @@ #![feature(core_intrinsics)] use std::intrinsics::{volatile_load, volatile_store}; -pub fn main() { +fn main() { unsafe { let i: &mut (isize, isize) = &mut (0, 0); volatile_store(i, (1, 2)); diff --git a/src/tools/miri/tests/pass/issues/issue-30530.rs b/src/tools/miri/tests/pass/issues/issue-30530.rs index b50a43ffd83b0..af338e8032d12 100644 --- a/src/tools/miri/tests/pass/issues/issue-30530.rs +++ b/src/tools/miri/tests/pass/issues/issue-30530.rs @@ -21,7 +21,7 @@ fn main() { } #[inline(never)] -pub fn take(h: Handler, f: Box) -> Box { +fn take(h: Handler, f: Box) -> Box { unsafe { match h { Handler::Custom(ptr) => *Box::from_raw(ptr), diff --git a/src/tools/miri/tests/pass/issues/issue-3794.rs b/src/tools/miri/tests/pass/issues/issue-3794.rs index 860d72bb586ff..3cc78b5f99d71 100644 --- a/src/tools/miri/tests/pass/issues/issue-3794.rs +++ b/src/tools/miri/tests/pass/issues/issue-3794.rs @@ -22,7 +22,7 @@ fn print_s(s: &S) { s.print(); } -pub fn main() { +fn main() { let s: Box = Box::new(S { s: 5 }); print_s(&*s); let t: Box = s as Box; diff --git a/src/tools/miri/tests/pass/issues/issue-5917.rs b/src/tools/miri/tests/pass/issues/issue-5917.rs index f7bbb4350e2be..9155c859b0505 100644 --- a/src/tools/miri/tests/pass/issues/issue-5917.rs +++ b/src/tools/miri/tests/pass/issues/issue-5917.rs @@ -1,6 +1,6 @@ struct T(&'static [isize]); static STATIC: T = T(&[5, 4, 3]); -pub fn main() { +fn main() { let T(ref v) = STATIC; assert_eq!(v[0], 5); } diff --git a/src/tools/miri/tests/pass/issues/issue-miri-184.rs b/src/tools/miri/tests/pass/issues/issue-miri-184.rs index 964d850298fbf..5233b441b990f 100644 --- a/src/tools/miri/tests/pass/issues/issue-miri-184.rs +++ b/src/tools/miri/tests/pass/issues/issue-miri-184.rs @@ -1,5 +1,5 @@ #![allow(unnecessary_transmutes)] -pub fn main() { +fn main() { let bytes: [u8; 8] = unsafe { ::std::mem::transmute(0u64) }; let _val: &[u8] = &bytes; } diff --git a/src/tools/miri/tests/pass/issues/issue-miri-2068.rs b/src/tools/miri/tests/pass/issues/issue-miri-2068.rs index 1931b6c9d79fb..471031e59ac3e 100644 --- a/src/tools/miri/tests/pass/issues/issue-miri-2068.rs +++ b/src/tools/miri/tests/pass/issues/issue-miri-2068.rs @@ -2,7 +2,7 @@ use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll, Waker}; -pub fn fuzzing_block_on>(fut: F) -> O { +fn fuzzing_block_on>(fut: F) -> O { let mut fut = std::pin::pin!(fut); let mut context = Context::from_waker(Waker::noop()); loop { diff --git a/src/tools/miri/tests/pass/issues/issue-miri-3541-dyn-vtable-trait-normalization.rs b/src/tools/miri/tests/pass/issues/issue-miri-3541-dyn-vtable-trait-normalization.rs index 123fe6ed6425c..d88df18295ab0 100644 --- a/src/tools/miri/tests/pass/issues/issue-miri-3541-dyn-vtable-trait-normalization.rs +++ b/src/tools/miri/tests/pass/issues/issue-miri-3541-dyn-vtable-trait-normalization.rs @@ -25,7 +25,7 @@ where } } -pub fn box_new_with() +fn box_new_with() where T: ?Sized, { diff --git a/src/tools/miri/tests/pass/last-use-in-cap-clause.rs b/src/tools/miri/tests/pass/last-use-in-cap-clause.rs index 2160aea16346f..d71593bfcbdf2 100644 --- a/src/tools/miri/tests/pass/last-use-in-cap-clause.rs +++ b/src/tools/miri/tests/pass/last-use-in-cap-clause.rs @@ -12,6 +12,6 @@ fn foo() -> Box isize + 'static> { Box::new(result) } -pub fn main() { +fn main() { assert_eq!(foo()(), 22); } diff --git a/src/tools/miri/tests/pass/loop-break-value.rs b/src/tools/miri/tests/pass/loop-break-value.rs index bc4c967d26a5b..74ab487b342a1 100644 --- a/src/tools/miri/tests/pass/loop-break-value.rs +++ b/src/tools/miri/tests/pass/loop-break-value.rs @@ -8,7 +8,7 @@ fn never_returns() { } } -pub fn main() { +fn main() { let value = 'outer: loop { if 1 == 1 { break 13; diff --git a/src/tools/miri/tests/pass/move-arg-2-unique.rs b/src/tools/miri/tests/pass/move-arg-2-unique.rs index de21d67eb4f62..7792b8bb1162d 100644 --- a/src/tools/miri/tests/pass/move-arg-2-unique.rs +++ b/src/tools/miri/tests/pass/move-arg-2-unique.rs @@ -2,7 +2,7 @@ fn test(foo: Box>) { assert_eq!((*foo)[0], 10); } -pub fn main() { +fn main() { let x = Box::new(vec![10]); // Test forgetting a local by move-in test(x); diff --git a/src/tools/miri/tests/pass/move-arg-3-unique.rs b/src/tools/miri/tests/pass/move-arg-3-unique.rs index 6025481c32e24..1b6e7ba7cf32e 100644 --- a/src/tools/miri/tests/pass/move-arg-3-unique.rs +++ b/src/tools/miri/tests/pass/move-arg-3-unique.rs @@ -1,4 +1,4 @@ -pub fn main() { +fn main() { let x = Box::new(10); let y = x; assert_eq!(*y, 10); diff --git a/src/tools/miri/tests/pass/mpsc.rs b/src/tools/miri/tests/pass/mpsc.rs index 3824a0de907cf..b60d32002db25 100644 --- a/src/tools/miri/tests/pass/mpsc.rs +++ b/src/tools/miri/tests/pass/mpsc.rs @@ -1,6 +1,6 @@ use std::sync::mpsc::channel; -pub fn main() { +fn main() { let (tx, rx) = channel::>(); tx.send(Box::new(100)).unwrap(); let v = rx.recv().unwrap(); diff --git a/src/tools/miri/tests/pass/panic/unwind_dwarf.rs b/src/tools/miri/tests/pass/panic/unwind_dwarf.rs index ca90e4f4d94f7..1e0cd3a43a2f8 100644 --- a/src/tools/miri/tests/pass/panic/unwind_dwarf.rs +++ b/src/tools/miri/tests/pass/panic/unwind_dwarf.rs @@ -15,7 +15,7 @@ struct Exception { cause: Box, } -pub fn panic(data: Box) -> u32 { +fn panic(data: Box) -> u32 { extern "C" fn exception_cleanup( _unwind_code: uw::_Unwind_Reason_Code, _exception: *mut uw::_Unwind_Exception, @@ -53,7 +53,7 @@ fn miri_exception_class() -> uw::_Unwind_Exception_Class { 0x4d4f5a_00_4d495249 } -pub fn catch_unwind R>(f: F) -> Result> { +fn catch_unwind R>(f: F) -> Result> { struct Data { f: Option, r: Option, diff --git a/src/tools/miri/tests/pass/regions-lifetime-nonfree-late-bound.rs b/src/tools/miri/tests/pass/regions-lifetime-nonfree-late-bound.rs index 445dd43febb13..8645fde77f01e 100644 --- a/src/tools/miri/tests/pass/regions-lifetime-nonfree-late-bound.rs +++ b/src/tools/miri/tests/pass/regions-lifetime-nonfree-late-bound.rs @@ -12,7 +12,7 @@ // doing region-folding, when really all clients of the region-folding // case only want to see *free* lifetime variables, not bound ones. -pub fn main() { +fn main() { fn explicit() { fn test(_x: Option>) where diff --git a/src/tools/miri/tests/pass/sendable-class.rs b/src/tools/miri/tests/pass/sendable-class.rs index a05278f1855a2..5784bc3a39314 100644 --- a/src/tools/miri/tests/pass/sendable-class.rs +++ b/src/tools/miri/tests/pass/sendable-class.rs @@ -12,7 +12,7 @@ fn foo(i: isize, j: char) -> Foo { Foo { i: i, j: j } } -pub fn main() { +fn main() { let (tx, rx) = channel(); tx.send(foo(42, 'c')).unwrap(); let _val = rx; diff --git a/src/tools/miri/tests/pass/tag-align-dyn-u64.rs b/src/tools/miri/tests/pass/tag-align-dyn-u64.rs index 81a43cc8bcc6b..e4abc3895008c 100644 --- a/src/tools/miri/tests/pass/tag-align-dyn-u64.rs +++ b/src/tools/miri/tests/pass/tag-align-dyn-u64.rs @@ -24,7 +24,7 @@ fn is_u64_aligned(u: &Tag) -> bool { return (p & (u64_align - 1)) == 0; } -pub fn main() { +fn main() { let x = mk_rec(); assert!(is_u64_aligned(&x.t)); } diff --git a/src/tools/miri/tests/pass/tls/tls_leak_main_thread_allowed.rs b/src/tools/miri/tests/pass/tls/tls_leak_main_thread_allowed.rs index abc0968f7c4c6..634b8af02aa93 100644 --- a/src/tools/miri/tests/pass/tls/tls_leak_main_thread_allowed.rs +++ b/src/tools/miri/tests/pass/tls/tls_leak_main_thread_allowed.rs @@ -6,7 +6,7 @@ use std::cell::Cell; // as long as the program does), so make sure we treat them the same for leak purposes. // // The test covers both TLS statics and the TLS macro. -pub fn main() { +fn main() { #[thread_local] static TLS: Cell> = Cell::new(None); diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.rs b/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.rs index 4a868455c8497..ed8cbbf0e273b 100644 --- a/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.rs +++ b/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.rs @@ -6,7 +6,7 @@ mod utils; use std::cell::UnsafeCell; -pub fn main() { +fn main() { let cell = UnsafeCell::new(42); let box1 = Box::new(cell); diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.rs b/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.rs index fd68685a2f442..adeedc653b994 100644 --- a/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.rs +++ b/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.rs @@ -12,7 +12,7 @@ struct Foo { field2: Cell, } -pub fn main() { +fn main() { let root = Foo { field1: 42, field2: Cell::new(88) }; unsafe { let a = &root; diff --git a/src/tools/miri/tests/pass/tree_borrows/copy-nonoverlapping.rs b/src/tools/miri/tests/pass/tree_borrows/copy-nonoverlapping.rs index 23250d6e6dfc5..4edf80e9561e3 100644 --- a/src/tools/miri/tests/pass/tree_borrows/copy-nonoverlapping.rs +++ b/src/tools/miri/tests/pass/tree_borrows/copy-nonoverlapping.rs @@ -2,7 +2,7 @@ // copy_nonoverlapping works regardless of the order in which we construct // the arguments. -pub fn main() { +fn main() { test_to_from(); test_from_to(); } diff --git a/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs b/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs index 87eb447049d61..0ed1dbacc7070 100644 --- a/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs +++ b/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs @@ -28,7 +28,7 @@ fn local_addr_of_mut() { // Tree Borrows has no issue with several mutable references existing // at the same time, as long as they are used only immutably. // I.e. multiple Reserved can coexist. -pub fn aliasing_read_only_mutable_refs() { +fn aliasing_read_only_mutable_refs() { unsafe { let base = &mut 42u64; let r1 = &mut *(base as *mut u64); @@ -38,7 +38,7 @@ pub fn aliasing_read_only_mutable_refs() { } } -pub fn string_as_mut_ptr() { +fn string_as_mut_ptr() { // This errors in Stacked Borrows since as_mut_ptr restricts the provenance, // but with Tree Borrows it should work. unsafe { diff --git a/src/tools/miri/tests/pass/unsized.rs b/src/tools/miri/tests/pass/unsized.rs index 6ad0635430297..1e62cd7f3239e 100644 --- a/src/tools/miri/tests/pass/unsized.rs +++ b/src/tools/miri/tests/pass/unsized.rs @@ -4,10 +4,10 @@ #![feature(custom_mir, core_intrinsics)] fn unsized_params() { - pub fn f0(_f: dyn FnOnce()) {} - pub fn f1(_s: str) {} - pub fn f2(_x: i32, _y: [i32]) {} - pub fn f3(_p: dyn Send) {} + fn f0(_f: dyn FnOnce()) {} + fn f1(_s: str) {} + fn f2(_x: i32, _y: [i32]) {} + fn f3(_p: dyn Send) {} let c: Box = Box::new(|| {}); f0(*c); diff --git a/src/tools/miri/tests/pass/vec-matching-fold.rs b/src/tools/miri/tests/pass/vec-matching-fold.rs index 3a869703bf96a..48e750a3102aa 100644 --- a/src/tools/miri/tests/pass/vec-matching-fold.rs +++ b/src/tools/miri/tests/pass/vec-matching-fold.rs @@ -29,7 +29,7 @@ where } } -pub fn main() { +fn main() { let x = &[1, 2, 3, 4, 5]; let product = foldl(x, 1, |a, b| a * *b); diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs index efaaf9fc84170..1f8d98a4d3392 100644 --- a/src/tools/miri/tests/ui.rs +++ b/src/tools/miri/tests/ui.rs @@ -30,11 +30,6 @@ fn miri_path() -> PathBuf { PathBuf::from(env::var("MIRI").unwrap_or_else(|_| env!("CARGO_BIN_EXE_miri").into())) } -pub fn flagsplit(flags: &str) -> Vec { - // This code is taken from `RUSTFLAGS` handling in cargo. - flags.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string).collect() -} - // Build the shared object file for testing native function calls. fn build_native_lib(target: &str) -> PathBuf { // Loosely follow the logic of the `cc` crate for finding the compiler. diff --git a/src/tools/miri/tests/utils/miri_extern.rs b/src/tools/miri/tests/utils/miri_extern.rs index d6c43b1882195..633f337f7e7cd 100644 --- a/src/tools/miri/tests/utils/miri_extern.rs +++ b/src/tools/miri/tests/utils/miri_extern.rs @@ -147,4 +147,7 @@ extern "Rust" { /// "symbolic" alignment checks. Will fail if the pointer is not actually aligned or `align` is /// not a power of two. Has no effect when alignment checks are concrete (which is the default). pub fn miri_promise_symbolic_alignment(ptr: *const (), align: usize); + + /// Blocks the current execution if the argument is false + pub fn miri_genmc_assume(condition: bool); }