diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index bd495f6ec1acb..d4667a09af6d9 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -16,7 +16,6 @@ use rustc_errors::{ pluralize, }; use rustc_session::errors::ExprParenthesesNeeded; -use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::source_map::Spanned; use rustc_span::symbol::used_keywords; use rustc_span::{BytePos, DUMMY_SP, Ident, Span, SpanSnippetError, Symbol, kw, sym}; @@ -222,6 +221,8 @@ impl std::fmt::Display for UnaryFixity { style = "verbose" )] struct MisspelledKw { + // We use a String here because `Symbol::into_diag_arg` calls `Symbol::to_ident_string`, which + // prefix the keyword with a `r#` because it aims to print the symbol as an identifier. similar_kw: String, #[primary_span] span: Span, @@ -229,20 +230,15 @@ struct MisspelledKw { } /// Checks if the given `lookup` identifier is similar to any keyword symbol in `candidates`. +/// +/// This is a specialized version of [`Symbol::find_similar`] that constructs an error when a +/// candidate is found. fn find_similar_kw(lookup: Ident, candidates: &[Symbol]) -> Option { - let lowercase = lookup.name.as_str().to_lowercase(); - let lowercase_sym = Symbol::intern(&lowercase); - if candidates.contains(&lowercase_sym) { - Some(MisspelledKw { similar_kw: lowercase, span: lookup.span, is_incorrect_case: true }) - } else if let Some(similar_sym) = find_best_match_for_name(candidates, lookup.name, None) { - Some(MisspelledKw { - similar_kw: similar_sym.to_string(), - span: lookup.span, - is_incorrect_case: false, - }) - } else { - None - } + lookup.name.find_similar(candidates).map(|(similar_kw, is_incorrect_case)| MisspelledKw { + similar_kw: similar_kw.to_string(), + is_incorrect_case, + span: lookup.span, + }) } struct MultiSugg { diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 09ac3ae1c3a92..70e91c081776a 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -421,6 +421,8 @@ passes_missing_panic_handler = passes_missing_stability_attr = {$descr} has missing stability attribute +passes_misspelled_feature = there is a feature with a similar name: `{$actual_name}` + passes_mixed_export_name_and_no_mangle = `{$no_mangle_attr}` attribute may not be used in combination with `{$export_name_attr}` .label = `{$no_mangle_attr}` is ignored .note = `{$export_name_attr}` takes precedence diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index e4826cccd32f8..6aa0f5212af70 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1183,6 +1183,21 @@ pub(crate) struct UnknownFeature { #[primary_span] pub span: Span, pub feature: Symbol, + #[subdiagnostic] + pub suggestion: Option, +} + +#[derive(Subdiagnostic)] +#[suggestion( + passes_misspelled_feature, + style = "verbose", + code = "{actual_name}", + applicability = "maybe-incorrect" +)] +pub(crate) struct MisspelledFeature { + #[primary_span] + pub span: Span, + pub actual_name: Symbol, } #[derive(Diagnostic)] diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs index 39830db2b11db..9d94c4cc62256 100644 --- a/compiler/rustc_passes/src/stability.rs +++ b/compiler/rustc_passes/src/stability.rs @@ -6,7 +6,7 @@ use std::num::NonZero; use rustc_ast_lowering::stability::extern_abi_stability; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::unord::{ExtendUnord, UnordMap, UnordSet}; -use rustc_feature::{EnabledLangFeature, EnabledLibFeature}; +use rustc_feature::{EnabledLangFeature, EnabledLibFeature, UNSTABLE_LANG_FEATURES}; use rustc_hir::attrs::{AttributeKind, DeprecatedSince}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalDefId, LocalModDefId}; @@ -1062,11 +1062,13 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) { // no unknown features, because the collection also does feature attribute validation. let local_defined_features = tcx.lib_features(LOCAL_CRATE); if !remaining_lib_features.is_empty() || !remaining_implications.is_empty() { + let crates = tcx.crates(()); + // Loading the implications of all crates is unavoidable to be able to emit the partial // stabilization diagnostic, but it can be avoided when there are no // `remaining_lib_features`. let mut all_implications = remaining_implications.clone(); - for &cnum in tcx.crates(()) { + for &cnum in crates { all_implications .extend_unord(tcx.stability_implications(cnum).items().map(|(k, v)| (*k, *v))); } @@ -1079,7 +1081,7 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) { &all_implications, ); - for &cnum in tcx.crates(()) { + for &cnum in crates { if remaining_lib_features.is_empty() && remaining_implications.is_empty() { break; } @@ -1091,10 +1093,26 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) { &all_implications, ); } - } - for (feature, span) in remaining_lib_features { - tcx.dcx().emit_err(errors::UnknownFeature { span, feature }); + if !remaining_lib_features.is_empty() { + let lang_features = + UNSTABLE_LANG_FEATURES.iter().map(|feature| feature.name).collect::>(); + let lib_features = crates + .into_iter() + .flat_map(|&cnum| { + tcx.lib_features(cnum).stability.keys().copied().into_sorted_stable_ord() + }) + .collect::>(); + + let valid_feature_names = [lang_features, lib_features].concat(); + + for (feature, span) in remaining_lib_features { + let suggestion = feature + .find_similar(&valid_feature_names) + .map(|(actual_name, _)| errors::MisspelledFeature { span, actual_name }); + tcx.dcx().emit_err(errors::UnknownFeature { span, feature, suggestion }); + } + } } for (&implied_by, &feature) in remaining_implications.to_sorted_stable_ord() { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 7e513160de0c3..171539b42a6e2 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -14,6 +14,7 @@ use rustc_data_structures::stable_hasher::{ use rustc_data_structures::sync::Lock; use rustc_macros::{Decodable, Encodable, HashStable_Generic, symbols}; +use crate::edit_distance::find_best_match_for_name; use crate::{DUMMY_SP, Edition, Span, with_session_globals}; #[cfg(test)] @@ -2843,6 +2844,27 @@ impl Symbol { // Avoid creating an empty identifier, because that asserts in debug builds. if self == sym::empty { String::new() } else { Ident::with_dummy_span(self).to_string() } } + + /// Checks if `self` is similar to any symbol in `candidates`. + /// + /// The returned boolean represents whether the candidate is the same symbol with a different + /// casing. + /// + /// All the candidates are assumed to be lowercase. + pub fn find_similar( + self, + candidates: &[Symbol], + ) -> Option<(Symbol, /* is incorrect case */ bool)> { + let lowercase = self.as_str().to_lowercase(); + let lowercase_sym = Symbol::intern(&lowercase); + if candidates.contains(&lowercase_sym) { + Some((lowercase_sym, true)) + } else if let Some(similar_sym) = find_best_match_for_name(candidates, self, None) { + Some((similar_sym, false)) + } else { + None + } + } } impl fmt::Debug for Symbol { diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index 2adce8d270398..d2bc5d9bce39d 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -745,6 +745,59 @@ impl Vec { unsafe { Self::from_parts_in(ptr, length, capacity, Global) } } + /// Creates a `Vec` where each element is produced by calling `f` with + /// that element's index while walking forward through the `Vec`. + /// + /// This is essentially the same as writing + /// + /// ```text + /// vec![f(0), f(1), f(2), …, f(length - 2), f(length - 1)] + /// ``` + /// and is similar to `(0..i).map(f)`, just for `Vec`s not iterators. + /// + /// If `length == 0`, this produces an empty `Vec` without ever calling `f`. + /// + /// # Example + /// + /// ```rust + /// #![feature(vec_from_fn)] + /// + /// let vec = Vec::from_fn(5, |i| i); + /// + /// // indexes are: 0 1 2 3 4 + /// assert_eq!(vec, [0, 1, 2, 3, 4]); + /// + /// let vec2 = Vec::from_fn(8, |i| i * 2); + /// + /// // indexes are: 0 1 2 3 4 5 6 7 + /// assert_eq!(vec2, [0, 2, 4, 6, 8, 10, 12, 14]); + /// + /// let bool_vec = Vec::from_fn(5, |i| i % 2 == 0); + /// + /// // indexes are: 0 1 2 3 4 + /// assert_eq!(bool_vec, [true, false, true, false, true]); + /// ``` + /// + /// The `Vec` is generated in ascending index order, starting from the front + /// and going towards the back, so you can use closures with mutable state: + /// ``` + /// #![feature(vec_from_fn)] + /// + /// let mut state = 1; + /// let a = Vec::from_fn(6, |_| { let x = state; state *= 2; x }); + /// + /// assert_eq!(a, [1, 2, 4, 8, 16, 32]); + /// ``` + #[cfg(not(no_global_oom_handling))] + #[inline] + #[unstable(feature = "vec_from_fn", reason = "new API", issue = "149698")] + pub fn from_fn(length: usize, f: F) -> Self + where + F: FnMut(usize) -> T, + { + (0..length).map(f).collect() + } + /// Decomposes a `Vec` into its raw components: `(pointer, length, capacity)`. /// /// Returns the raw pointer to the underlying data, the length of diff --git a/src/doc/rustc/src/platform-support/windows-gnullvm.md b/src/doc/rustc/src/platform-support/windows-gnullvm.md index b469af0153130..e5db656e89f7a 100644 --- a/src/doc/rustc/src/platform-support/windows-gnullvm.md +++ b/src/doc/rustc/src/platform-support/windows-gnullvm.md @@ -2,9 +2,11 @@ **Tier: 2 (with host tools)** -Windows targets similar to `*-windows-gnu` but using UCRT as the runtime and various LLVM tools/libraries instead of GCC/Binutils. +Windows targets similar to `*-windows-gnu` but using UCRT as the runtime and various LLVM tools/libraries instead of +GCC/Binutils. Target triples available so far: + - `aarch64-pc-windows-gnullvm` - `i686-pc-windows-gnullvm` - `x86_64-pc-windows-gnullvm` @@ -16,10 +18,11 @@ Target triples available so far: ## Requirements -The easiest way to obtain these targets is cross-compilation, but native build from `x86_64-pc-windows-gnu` is possible with few hacks which I don't recommend. -Std support is expected to be on par with `*-windows-gnu`. +Building those targets requires an LLVM-based C toolchain, for example, [llvm-mingw][1] or [MSYS2][2] with CLANG* +environment. -Binaries for this target should be at least on par with `*-windows-gnu` in terms of requirements and functionality. +Binaries for this target should be at least on par with `*-windows-gnu` in terms of requirements and functionality, +except for implicit self-contained mode (explained in [the section below](#building-rust-programs)). Those targets follow Windows calling convention for `extern "C"`. @@ -27,37 +30,32 @@ Like with any other Windows target, created binaries are in PE format. ## Building the target -These targets can be easily cross-compiled -using [llvm-mingw](https://github.com/mstorsjo/llvm-mingw) toolchain or [MSYS2 CLANG*](https://www.msys2.org/docs/environments/) environments. -Just fill `[target.*]` sections for both build and resulting compiler and set installation prefix in `bootstrap.toml`. -Then run `./x.py install`. -In my case I had ran `./x.py install --host x86_64-pc-windows-gnullvm --target x86_64-pc-windows-gnullvm` inside MSYS2 MINGW64 shell -so `x86_64-pc-windows-gnu` was my build toolchain. - -Native bootstrapping is doable in two ways: -- cross-compile gnullvm host toolchain and use it as build toolchain for the next build, -- copy libunwind libraries and rename them to mimic libgcc like here: https://github.com/msys2/MINGW-packages/blob/68e640756df2df6df6afa60f025e3f936e7b977c/mingw-w64-rust/PKGBUILD#L108-L109, stage0 compiler will be mostly broken but good enough to build the next stage. - -The second option might stop working anytime, so it's not recommended. +Both native and cross-compilation builds are supported and function similarly to other Rust targets. ## Building Rust programs -Rust does ship a pre-compiled std library for those targets. -That means one can easily cross-compile for those targets from other hosts if C proper toolchain is installed. +Rust ships both std and host tools for those targets. That allows using them as both the host and the target. -Alternatively full toolchain can be built as described in the previous section. +When used as the host and building pure Rust programs, no additional C toolchain is required. +The only requirements are to install `rust-mingw` component and to set `rust-lld` as the linker. +Otherwise, you will need to install the C toolchain mentioned previously. +There is no automatic fallback to `rust-lld` when the C toolchain is missing yet, but it may be added in the future. ## Testing -Created binaries work fine on Windows or Wine using native hardware. Testing AArch64 on x86_64 is problematic though and requires spending some time with QEMU. -Most of x86_64 testsuite does pass when cross-compiling, -with exception for `rustdoc` and `ui-fulldeps` that fail with and error regarding a missing library, -they do pass in native builds though. -The only failing test is std's `process::tests::test_proc_thread_attributes` for unknown reason. +Created binaries work fine on Windows and Linux with Wine using native hardware. +Testing AArch64 on x86_64 is problematic, though, and requires launching a whole AArch64 system with QEMU. + +Most of the x86_64 testsuite does pass, but because it isn't run on CI, different failures are expected over time. ## Cross-compilation toolchains and C code -Compatible C code can be built with Clang's `aarch64-pc-windows-gnu`, `i686-pc-windows-gnullvm` and `x86_64-pc-windows-gnu` targets as long as LLVM-based C toolchains are used. -Those include: -- [llvm-mingw](https://github.com/mstorsjo/llvm-mingw) -- [MSYS2 with CLANG* environment](https://www.msys2.org/docs/environments) +Compatible C code can be built with Clang's `aarch64-pc-windows-gnu`, `i686-pc-windows-gnullvm` and +`x86_64-pc-windows-gnu` targets as long as LLVM-based C toolchains are used. Those include: + +- [llvm-mingw][1] +- [MSYS2][2] with CLANG* environment + +[1]: https://github.com/mstorsjo/llvm-mingw + +[2]: https://www.msys2.org/docs/environments diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index da6840c72f5b1..a4cbef8c39177 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -24,6 +24,7 @@ use tracing::instrument; use crate::clean::types::{Function, Generics, ItemId, Type, WherePredicate}; use crate::clean::{self, utils}; +use crate::config::ShouldMerge; use crate::error::Error; use crate::formats::cache::{Cache, OrphanImplItem}; use crate::formats::item_type::ItemType; @@ -721,7 +722,9 @@ impl SerializedSearchIndex { } }, ), - self.alias_pointers[id].and_then(|alias| map.get(&alias).copied()), + self.alias_pointers[id].and_then(|alias| { + if self.names[alias].is_empty() { None } else { map.get(&alias).copied() } + }), ); } new.generic_inverted_index = self @@ -1248,6 +1251,7 @@ pub(crate) fn build_index( tcx: TyCtxt<'_>, doc_root: &Path, resource_suffix: &str, + should_merge: &ShouldMerge, ) -> Result { let mut search_index = std::mem::take(&mut cache.search_index); @@ -1298,7 +1302,11 @@ pub(crate) fn build_index( // // if there's already a search index, load it into memory and add the new entries to it // otherwise, do nothing - let mut serialized_index = SerializedSearchIndex::load(doc_root, resource_suffix)?; + let mut serialized_index = if should_merge.read_rendered_cci { + SerializedSearchIndex::load(doc_root, resource_suffix)? + } else { + SerializedSearchIndex::default() + }; // The crate always goes first in this list let crate_name = krate.name(tcx); diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 9a8df53931394..6bf116c3b75ad 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -66,8 +66,14 @@ pub(crate) fn write_shared( // Write shared runs within a flock; disable thread dispatching of IO temporarily. let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file); - let search_index = - build_index(krate, &mut cx.shared.cache, tcx, &cx.dst, &cx.shared.resource_suffix)?; + let search_index = build_index( + krate, + &mut cx.shared.cache, + tcx, + &cx.dst, + &cx.shared.resource_suffix, + &opt.should_merge, + )?; let crate_name = krate.name(cx.tcx()); let crate_name = crate_name.as_str(); // rand diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index d6c702848494f..4eeba30228924 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -31,13 +31,13 @@ jobs: os: ubuntu-24.04-arm multiarch: armhf gcc_cross: arm-linux-gnueabihf - - host_target: riscv64gc-unknown-linux-gnu - os: ubuntu-latest - multiarch: riscv64 - gcc_cross: riscv64-linux-gnu - qemu: true # Ubuntu mirrors are not reliable enough for these architectures # (see ). + # - host_target: riscv64gc-unknown-linux-gnu + # os: ubuntu-latest + # multiarch: riscv64 + # gcc_cross: riscv64-linux-gnu + # qemu: true # - host_target: s390x-unknown-linux-gnu # os: ubuntu-latest # multiarch: s390x diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 1c6a2daa093d9..32494141589ac 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -624,6 +624,7 @@ Definite bugs found: * [Mockall reading uninitialized memory when mocking `std::io::Read::read`, even if all expectations are satisfied](https://github.com/asomers/mockall/issues/647) (caught by Miri running Tokio's test suite) * [`ReentrantLock` not correctly dealing with reuse of addresses for TLS storage of different threads](https://github.com/rust-lang/rust/pull/141248) * [Rare Deadlock in the thread (un)parking example code](https://github.com/rust-lang/rust/issues/145816) +* [`winit` registering a global constructor with the wrong ABI on Windows](https://github.com/rust-windowing/winit/issues/4435) Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment): diff --git a/src/tools/miri/etc/rust_analyzer_helix.toml b/src/tools/miri/etc/rust_analyzer_helix.toml index c46b246049ffd..dd222c50431a7 100644 --- a/src/tools/miri/etc/rust_analyzer_helix.toml +++ b/src/tools/miri/etc/rust_analyzer_helix.toml @@ -28,5 +28,7 @@ overrideCommand = [ "./miri", "check", "--no-default-features", + "-Zunstable-options", + "--compile-time-deps", "--message-format=json", ] diff --git a/src/tools/miri/etc/rust_analyzer_vscode.json b/src/tools/miri/etc/rust_analyzer_vscode.json index 8e647f5331f06..97ba212f8ef97 100644 --- a/src/tools/miri/etc/rust_analyzer_vscode.json +++ b/src/tools/miri/etc/rust_analyzer_vscode.json @@ -22,6 +22,8 @@ "./miri", "check", "--no-default-features", + "-Zunstable-options", + "--compile-time-deps", "--message-format=json", ], } diff --git a/src/tools/miri/etc/rust_analyzer_zed.json b/src/tools/miri/etc/rust_analyzer_zed.json index 839914c8b68ed..7f60a931c46fd 100644 --- a/src/tools/miri/etc/rust_analyzer_zed.json +++ b/src/tools/miri/etc/rust_analyzer_zed.json @@ -31,6 +31,8 @@ "./miri", "check", "--no-default-features", + "-Zunstable-options", + "--compile-time-deps", "--message-format=json" ] } diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 1efb31457bab1..b6a1415f1834c 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -1eb0657f78777f0b4d6bcc49c126d5d35212cae5 +36b2369c91d32c2659887ed6fe3d570640f44fd2 diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 4bf51f83c4f73..efe707156c40c 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -272,18 +272,13 @@ impl rustc_driver::Callbacks for MiriCompilerCalls { } } -struct MiriBeRustCompilerCalls { - target_crate: bool, -} +/// This compiler produces rlibs that are meant for later consumption by Miri. +struct MiriDepCompilerCalls; -impl rustc_driver::Callbacks for MiriBeRustCompilerCalls { +impl rustc_driver::Callbacks for MiriDepCompilerCalls { #[allow(rustc::potential_query_instability)] // rustc_codegen_ssa (where this code is copied from) also allows this lint fn config(&mut self, config: &mut Config) { - if !self.target_crate { - // For a host crate, we fully behave like rustc. - return; - } - // For a target crate, we emit an rlib that Miri can later consume. + // We don't need actual codegen, we just emit an rlib that Miri can later consume. config.make_codegen_backend = Some(Box::new(make_miri_codegen_backend)); // Avoid warnings about unsupported crate types. However, only do that we we are *not* being @@ -367,16 +362,12 @@ impl rustc_driver::Callbacks for MiriBeRustCompilerCalls { _: &rustc_interface::interface::Compiler, tcx: TyCtxt<'tcx>, ) -> Compilation { - if self.target_crate { - // cargo-miri has patched the compiler flags to make these into check-only builds, - // but we are still emulating regular rustc builds, which would perform post-mono - // const-eval during collection. So let's also do that here, even if we might be - // running with `--emit=metadata`. In particular this is needed to make - // `compile_fail` doc tests trigger post-mono errors. - // In general `collect_and_partition_mono_items` is not safe to call in check-only - // builds, but we are setting `-Zalways-encode-mir` which avoids those issues. - let _ = tcx.collect_and_partition_mono_items(()); - } + // While the dummy codegen backend doesn't do any codegen, we are still emulating + // regular rustc builds, which would perform post-mono const-eval during collection. + // So let's also do that here. In particular this is needed to make `compile_fail` + // doc tests trigger post-mono errors. + let _ = tcx.collect_and_partition_mono_items(()); + Compilation::Continue } } @@ -457,32 +448,28 @@ fn main() { // If the environment asks us to actually be rustc, then do that. if let Some(crate_kind) = env::var_os("MIRI_BE_RUSTC") { + if crate_kind == "host" { + // For host crates like proc macros and build scripts, we are an entirely normal rustc. + // These eventually produce actual binaries and never run in Miri. + match rustc_driver::main() { + // Empty match proves this function will never return. + } + } else if crate_kind != "target" { + panic!("invalid `MIRI_BE_RUSTC` value: {crate_kind:?}") + }; + // Earliest rustc setup. rustc_driver::install_ice_hook(rustc_driver::DEFAULT_BUG_REPORT_URL, |_| ()); rustc_driver::init_rustc_env_logger(&early_dcx); - let target_crate = if crate_kind == "target" { - true - } else if crate_kind == "host" { - false - } else { - panic!("invalid `MIRI_BE_RUSTC` value: {crate_kind:?}") - }; - let mut args = args; - // Don't insert `MIRI_DEFAULT_ARGS`, in particular, `--cfg=miri`, if we are building - // a "host" crate. That may cause procedural macros (and probably build scripts) to - // depend on Miri-only symbols, such as `miri_resolve_frame`: - // https://github.com/rust-lang/miri/issues/1760 - if target_crate { - // Splice in the default arguments after the program name. - // Some options have different defaults in Miri than in plain rustc; apply those by making - // them the first arguments after the binary name (but later arguments can overwrite them). - args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string)); - } + // Splice in the default arguments after the program name. + // Some options have different defaults in Miri than in plain rustc; apply those by making + // them the first arguments after the binary name (but later arguments can overwrite them). + args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string)); // We cannot use `rustc_driver::main` as we want it to use `args` as the CLI arguments. - run_compiler_and_exit(&args, &mut MiriBeRustCompilerCalls { target_crate }) + run_compiler_and_exit(&args, &mut MiriDepCompilerCalls) } // Add an ICE bug report hook. diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs index c454bb43a2f59..a91f35a9dcad3 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs @@ -488,6 +488,8 @@ struct DisplayFmtPadding { indent_middle: S, /// Indentation for the last child. indent_last: S, + /// Replaces `join_last` for a wildcard root. + wildcard_root: S, } /// How to show whether a location has been accessed /// @@ -561,6 +563,11 @@ impl DisplayFmt { }) .unwrap_or("") } + + /// Print extra text if the tag is exposed. + fn print_exposed(&self, exposed: bool) -> S { + if exposed { " (exposed)" } else { "" } + } } /// Track the indentation of the tree. @@ -607,23 +614,21 @@ fn char_repeat(c: char, n: usize) -> String { struct DisplayRepr { tag: BorTag, name: Option, + exposed: bool, rperm: Vec>, children: Vec, } impl DisplayRepr { - fn from(tree: &Tree, show_unnamed: bool) -> Option { + fn from(tree: &Tree, root: UniIndex, show_unnamed: bool) -> Option { let mut v = Vec::new(); - extraction_aux(tree, tree.root, show_unnamed, &mut v); + extraction_aux(tree, root, show_unnamed, &mut v); let Some(root) = v.pop() else { if show_unnamed { unreachable!( "This allocation contains no tags, not even a root. This should not happen." ); } - eprintln!( - "This allocation does not contain named tags. Use `miri_print_borrow_state(_, true)` to also print unnamed tags." - ); return None; }; assert!(v.is_empty()); @@ -637,6 +642,7 @@ impl DisplayRepr { ) { let node = tree.nodes.get(idx).unwrap(); let name = node.debug_info.name.clone(); + let exposed = node.is_exposed; let children_sorted = { let mut children = node.children.iter().cloned().collect::>(); children.sort_by_key(|idx| tree.nodes.get(*idx).unwrap().tag); @@ -661,12 +667,13 @@ impl DisplayRepr { for child_idx in children_sorted { extraction_aux(tree, child_idx, show_unnamed, &mut children); } - acc.push(DisplayRepr { tag: node.tag, name, rperm, children }); + acc.push(DisplayRepr { tag: node.tag, name, rperm, children, exposed }); } } } fn print( - &self, + main_root: &Option, + wildcard_subtrees: &[DisplayRepr], fmt: &DisplayFmt, indenter: &mut DisplayIndent, protected_tags: &FxHashMap, @@ -703,15 +710,41 @@ impl DisplayRepr { block.push(s); } // This is the actual work - print_aux( - self, - &range_padding, - fmt, - indenter, - protected_tags, - true, /* root _is_ the last child */ - &mut block, - ); + if let Some(root) = main_root { + print_aux( + root, + &range_padding, + fmt, + indenter, + protected_tags, + true, /* root _is_ the last child */ + false, /* not a wildcard_root*/ + &mut block, + ); + } + for tree in wildcard_subtrees.iter() { + let mut gap_line = String::new(); + gap_line.push_str(fmt.perm.open); + for (i, &pad) in range_padding.iter().enumerate() { + if i > 0 { + gap_line.push_str(fmt.perm.sep); + } + gap_line.push_str(&format!("{}{}", char_repeat(' ', pad), " ")); + } + gap_line.push_str(fmt.perm.close); + block.push(gap_line); + + print_aux( + tree, + &range_padding, + fmt, + indenter, + protected_tags, + true, /* root _is_ the last child */ + true, /* wildcard_root*/ + &mut block, + ); + } // Then it's just prettifying it with a border of dashes. { let wr = &fmt.wrapper; @@ -741,6 +774,7 @@ impl DisplayRepr { indent: &mut DisplayIndent, protected_tags: &FxHashMap, is_last_child: bool, + is_wildcard_root: bool, acc: &mut Vec, ) { let mut line = String::new(); @@ -760,7 +794,9 @@ impl DisplayRepr { indent.write(&mut line); { // padding - line.push_str(if is_last_child { + line.push_str(if is_wildcard_root { + fmt.padding.wildcard_root + } else if is_last_child { fmt.padding.join_last } else { fmt.padding.join_middle @@ -777,12 +813,22 @@ impl DisplayRepr { line.push_str(&fmt.print_tag(tree.tag, &tree.name)); let protector = protected_tags.get(&tree.tag); line.push_str(fmt.print_protector(protector)); + line.push_str(fmt.print_exposed(tree.exposed)); // Push the line to the accumulator then recurse. acc.push(line); let nb_children = tree.children.len(); for (i, child) in tree.children.iter().enumerate() { indent.increment(fmt, is_last_child); - print_aux(child, padding, fmt, indent, protected_tags, i + 1 == nb_children, acc); + print_aux( + child, + padding, + fmt, + indent, + protected_tags, + /* is_last_child */ i + 1 == nb_children, + /* is_wildcard_root */ false, + acc, + ); indent.decrement(fmt); } } @@ -803,6 +849,7 @@ const DEFAULT_FORMATTER: DisplayFmt = DisplayFmt { indent_last: " ", join_haschild: "┬", join_default: "─", + wildcard_root: "*", }, accessed: DisplayFmtAccess { yes: " ", no: "?", meh: "-" }, }; @@ -816,15 +863,27 @@ impl<'tcx> Tree { ) -> InterpResult<'tcx> { let mut indenter = DisplayIndent::new(); let ranges = self.locations.iter_all().map(|(range, _loc)| range).collect::>(); - if let Some(repr) = DisplayRepr::from(self, show_unnamed) { - repr.print( - &DEFAULT_FORMATTER, - &mut indenter, - protected_tags, - ranges, - /* print warning message about tags not shown */ !show_unnamed, + let main_tree = DisplayRepr::from(self, self.roots[0], show_unnamed); + let wildcard_subtrees = self.roots[1..] + .iter() + .filter_map(|root| DisplayRepr::from(self, *root, show_unnamed)) + .collect::>(); + + if main_tree.is_none() && wildcard_subtrees.is_empty() { + eprintln!( + "This allocation does not contain named tags. Use `miri_print_borrow_state(_, true)` to also print unnamed tags." ); } + + DisplayRepr::print( + &main_tree, + wildcard_subtrees.as_slice(), + &DEFAULT_FORMATTER, + &mut indenter, + protected_tags, + ranges, + /* print warning message about tags not shown */ !show_unnamed, + ); interp_ok(()) } } 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 2a1c98e5266bf..018421ad1064f 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs @@ -13,6 +13,7 @@ pub mod diagnostics; mod foreign_access_skipping; mod perms; mod tree; +mod tree_visitor; mod unimap; mod wildcard; @@ -239,18 +240,14 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return interp_ok(new_prov); } }; + let new_prov = Provenance::Concrete { alloc_id, tag: new_tag }; log_creation(this, Some((alloc_id, base_offset, parent_prov)))?; - let orig_tag = match parent_prov { - ProvenanceExtra::Wildcard => return interp_ok(place.ptr().provenance), // TODO: handle retagging wildcard pointers - ProvenanceExtra::Concrete(tag) => tag, - }; - trace!( "reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}", new_tag, - orig_tag, + parent_prov, place.layout.ty, interpret::Pointer::new(alloc_id, base_offset), ptr_size.bytes() @@ -281,7 +278,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here // There's not actually any bytes here where accesses could even be tracked. // Just produce the new provenance, nothing else to do. - return interp_ok(Some(Provenance::Concrete { alloc_id, tag: new_tag })); + return interp_ok(Some(new_prov)); } let protected = new_perm.protector.is_some(); @@ -367,11 +364,10 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } } - // Record the parent-child pair in the tree. tree_borrows.new_child( base_offset, - orig_tag, + parent_prov, new_tag, inside_perms, new_perm.outside_perm, @@ -380,7 +376,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; drop(tree_borrows); - interp_ok(Some(Provenance::Concrete { alloc_id, tag: new_tag })) + interp_ok(Some(new_prov)) } fn tb_retag_place( diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs index 07edf20fc46cd..ca68dc8910200 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs @@ -18,15 +18,15 @@ use rustc_data_structures::fx::FxHashSet; use rustc_span::Span; use smallvec::SmallVec; -use super::diagnostics::AccessCause; -use super::wildcard::WildcardState; -use crate::borrow_tracker::tree_borrows::Permission; -use crate::borrow_tracker::tree_borrows::diagnostics::{ - self, NodeDebugInfo, TbError, TransitionError, no_valid_exposed_references_error, +use super::Permission; +use super::diagnostics::{ + self, AccessCause, NodeDebugInfo, TbError, TransitionError, no_valid_exposed_references_error, }; -use crate::borrow_tracker::tree_borrows::foreign_access_skipping::IdempotentForeignAccess; -use crate::borrow_tracker::tree_borrows::perms::PermTransition; -use crate::borrow_tracker::tree_borrows::unimap::{UniIndex, UniKeyMap, UniValMap}; +use super::foreign_access_skipping::IdempotentForeignAccess; +use super::perms::PermTransition; +use super::tree_visitor::{ChildrenVisitMode, ContinueTraversal, NodeAppArgs, TreeVisitor}; +use super::unimap::{UniIndex, UniKeyMap, UniValMap}; +use super::wildcard::WildcardState; use crate::borrow_tracker::{AccessKind, GlobalState, ProtectorKind}; use crate::*; @@ -91,11 +91,11 @@ impl LocationState { nodes: &mut UniValMap, wildcard_accesses: &mut UniValMap, access_kind: AccessKind, - access_cause: AccessCause, - access_range: Option, + access_cause: AccessCause, //diagnostics + access_range: Option, //diagnostics relatedness: AccessRelatedness, - span: Span, - location_range: Range, + span: Span, //diagnostics + location_range: Range, //diagnostics protected: bool, ) -> Result<(), TransitionError> { // Call this function now (i.e. only if we know `relatedness`), which @@ -294,8 +294,22 @@ pub struct Tree { pub(super) nodes: UniValMap, /// Associates with each location its state and wildcard access tracking. pub(super) locations: DedupRangeMap, - /// The index of the root node. - pub(super) root: UniIndex, + /// Contains both the root of the main tree as well as the roots of the wildcard subtrees. + /// + /// If we reborrow a reference which has wildcard provenance, then we do not know where in + /// the tree to attach them. Instead we create a new additional tree for this allocation + /// with this new reference as a root. We call this additional tree a wildcard subtree. + /// + /// The actual structure should be a single tree but with wildcard provenance we approximate + /// this with this ordered set of trees. Each wildcard subtree is the direct child of *some* exposed + /// tag (that is smaller than the root), but we do not know which. This also means that it can only be the + /// child of a tree that comes before it in the vec ensuring we don't have any cycles in our + /// approximated tree. + /// + /// Sorted according to `BorTag` from low to high. This also means the main root is `root[0]`. + /// + /// Has array size 2 because that still ensures the minimum size for SmallVec. + pub(super) roots: SmallVec<[UniIndex; 2]>, } /// A node in the borrow tree. Each node is uniquely identified by a tag via @@ -325,262 +339,6 @@ pub(super) struct Node { pub debug_info: NodeDebugInfo, } -/// Data given to the transition function -struct NodeAppArgs<'visit> { - /// The index of the current node. - idx: UniIndex, - /// Relative position of the access. - rel_pos: AccessRelatedness, - /// The node map of this tree. - nodes: &'visit mut UniValMap, - /// The permissions map of this tree. - loc: &'visit mut LocationTree, -} -/// Internal contents of `Tree` with the minimum of mutable access for -/// For soundness do not modify the children or parent indexes of nodes -/// during traversal. -struct TreeVisitor<'tree> { - nodes: &'tree mut UniValMap, - loc: &'tree mut LocationTree, -} - -/// Whether to continue exploring the children recursively or not. -enum ContinueTraversal { - Recurse, - SkipSelfAndChildren, -} - -#[derive(Clone, Copy)] -pub enum ChildrenVisitMode { - VisitChildrenOfAccessed, - SkipChildrenOfAccessed, -} - -enum RecursionState { - BeforeChildren, - AfterChildren, -} - -/// Stack of nodes left to explore in a tree traversal. -/// See the docs of `traverse_this_parents_children_other` for details on the -/// traversal order. -struct TreeVisitorStack { - /// Function describing whether to continue at a tag. - /// This is only invoked for foreign accesses. - f_continue: NodeContinue, - /// Function to apply to each tag. - f_propagate: NodeApp, - /// Mutable state of the visit: the tags left to handle. - /// Every tag pushed should eventually be handled, - /// and the precise order is relevant for diagnostics. - /// Since the traversal is piecewise bottom-up, we need to - /// remember whether we're here initially, or after visiting all children. - /// The last element indicates this. - /// This is just an artifact of how you hand-roll recursion, - /// it does not have a deeper meaning otherwise. - stack: Vec<(UniIndex, AccessRelatedness, RecursionState)>, -} - -impl TreeVisitorStack -where - NodeContinue: Fn(&NodeAppArgs<'_>) -> ContinueTraversal, - NodeApp: Fn(NodeAppArgs<'_>) -> Result<(), Err>, -{ - fn should_continue_at( - &self, - this: &mut TreeVisitor<'_>, - idx: UniIndex, - rel_pos: AccessRelatedness, - ) -> ContinueTraversal { - let args = NodeAppArgs { idx, rel_pos, nodes: this.nodes, loc: this.loc }; - (self.f_continue)(&args) - } - - fn propagate_at( - &mut self, - this: &mut TreeVisitor<'_>, - idx: UniIndex, - rel_pos: AccessRelatedness, - ) -> Result<(), Err> { - (self.f_propagate)(NodeAppArgs { idx, rel_pos, nodes: this.nodes, loc: this.loc }) - } - - fn go_upwards_from_accessed( - &mut self, - this: &mut TreeVisitor<'_>, - accessed_node: UniIndex, - visit_children: ChildrenVisitMode, - ) -> Result<(), Err> { - // We want to visit the accessed node's children first. - // However, we will below walk up our parents and push their children (our cousins) - // onto the stack. To ensure correct iteration order, this method thus finishes - // by reversing the stack. This only works if the stack is empty initially. - assert!(self.stack.is_empty()); - // First, handle accessed node. A bunch of things need to - // be handled differently here compared to the further parents - // of `accesssed_node`. - { - self.propagate_at(this, accessed_node, AccessRelatedness::LocalAccess)?; - if matches!(visit_children, ChildrenVisitMode::VisitChildrenOfAccessed) { - let accessed_node = this.nodes.get(accessed_node).unwrap(); - // We `rev()` here because we reverse the entire stack later. - for &child in accessed_node.children.iter().rev() { - self.stack.push(( - child, - AccessRelatedness::ForeignAccess, - RecursionState::BeforeChildren, - )); - } - } - } - // Then, handle the accessed node's parents. Here, we need to - // make sure we only mark the "cousin" subtrees for later visitation, - // not the subtree that contains the accessed node. - let mut last_node = accessed_node; - while let Some(current) = this.nodes.get(last_node).unwrap().parent { - self.propagate_at(this, current, AccessRelatedness::LocalAccess)?; - let node = this.nodes.get(current).unwrap(); - // We `rev()` here because we reverse the entire stack later. - for &child in node.children.iter().rev() { - if last_node == child { - continue; - } - self.stack.push(( - child, - AccessRelatedness::ForeignAccess, - RecursionState::BeforeChildren, - )); - } - last_node = current; - } - // Reverse the stack, as discussed above. - self.stack.reverse(); - Ok(()) - } - - fn finish_foreign_accesses(&mut self, this: &mut TreeVisitor<'_>) -> Result<(), Err> { - while let Some((idx, rel_pos, step)) = self.stack.last_mut() { - let idx = *idx; - let rel_pos = *rel_pos; - match *step { - // How to do bottom-up traversal, 101: Before you handle a node, you handle all children. - // For this, you must first find the children, which is what this code here does. - RecursionState::BeforeChildren => { - // Next time we come back will be when all the children are handled. - *step = RecursionState::AfterChildren; - // Now push the children, except if we are told to skip this subtree. - let handle_children = self.should_continue_at(this, idx, rel_pos); - match handle_children { - ContinueTraversal::Recurse => { - let node = this.nodes.get(idx).unwrap(); - for &child in node.children.iter() { - self.stack.push((child, rel_pos, RecursionState::BeforeChildren)); - } - } - ContinueTraversal::SkipSelfAndChildren => { - // skip self - self.stack.pop(); - continue; - } - } - } - // All the children are handled, let's actually visit this node - RecursionState::AfterChildren => { - self.stack.pop(); - self.propagate_at(this, idx, rel_pos)?; - } - } - } - Ok(()) - } - - fn new(f_continue: NodeContinue, f_propagate: NodeApp) -> Self { - Self { f_continue, f_propagate, stack: Vec::new() } - } -} - -impl<'tree> TreeVisitor<'tree> { - /// Applies `f_propagate` to every vertex of the tree in a piecewise bottom-up way: First, visit - /// all ancestors of `start_idx` (starting with `start_idx` itself), then children of `start_idx`, then the rest, - /// going bottom-up in each of these two "pieces" / sections. - /// This ensures that errors are triggered in the following order - /// - first invalid accesses with insufficient permissions, closest to the accessed node first, - /// - then protector violations, bottom-up, starting with the children of the accessed node, and then - /// going upwards and outwards. - /// - /// The following graphic visualizes it, with numbers indicating visitation order and `start_idx` being - /// the node that is visited first ("1"): - /// - /// ```text - /// 3 - /// /| - /// / | - /// 9 2 - /// | |\ - /// | | \ - /// 8 1 7 - /// / \ - /// 4 6 - /// | - /// 5 - /// ``` - /// - /// `f_propagate` should follow the following format: for a given `Node` it updates its - /// `Permission` depending on the position relative to `start_idx` (given by an - /// `AccessRelatedness`). - /// `f_continue` is called earlier on foreign nodes, and describes whether to even start - /// visiting the subtree at that node. If it e.g. returns `SkipSelfAndChildren` on node 6 - /// above, then nodes 5 _and_ 6 would not be visited by `f_propagate`. It is not used for - /// notes having a child access (nodes 1, 2, 3). - /// - /// Finally, remember that the iteration order is not relevant for UB, it only affects - /// diagnostics. It also affects tree traversal optimizations built on top of this, so - /// those need to be reviewed carefully as well whenever this changes. - fn traverse_this_parents_children_other( - mut self, - start_idx: UniIndex, - f_continue: impl Fn(&NodeAppArgs<'_>) -> ContinueTraversal, - f_propagate: impl Fn(NodeAppArgs<'_>) -> Result<(), Err>, - ) -> Result<(), Err> { - let mut stack = TreeVisitorStack::new(f_continue, f_propagate); - // Visits the accessed node itself, and all its parents, i.e. all nodes - // undergoing a child access. Also pushes the children and the other - // cousin nodes (i.e. all nodes undergoing a foreign access) to the stack - // to be processed later. - stack.go_upwards_from_accessed( - &mut self, - start_idx, - ChildrenVisitMode::VisitChildrenOfAccessed, - )?; - // Now visit all the foreign nodes we remembered earlier. - // For this we go bottom-up, but also allow f_continue to skip entire - // subtrees from being visited if it would be a NOP. - stack.finish_foreign_accesses(&mut self) - } - - /// Like `traverse_this_parents_children_other`, but skips the children of `start_idx`. - fn traverse_nonchildren( - mut self, - start_idx: UniIndex, - f_continue: impl Fn(&NodeAppArgs<'_>) -> ContinueTraversal, - f_propagate: impl Fn(NodeAppArgs<'_>) -> Result<(), Err>, - ) -> Result<(), Err> { - let mut stack = TreeVisitorStack::new(f_continue, f_propagate); - // Visits the accessed node itself, and all its parents, i.e. all nodes - // undergoing a child access. Also pushes the other cousin nodes to the - // stack, but not the children of the accessed node. - stack.go_upwards_from_accessed( - &mut self, - start_idx, - ChildrenVisitMode::SkipChildrenOfAccessed, - )?; - // Now visit all the foreign nodes we remembered earlier. - // For this we go bottom-up, but also allow f_continue to skip entire - // subtrees from being visited if it would be a NOP. - stack.finish_foreign_accesses(&mut self) - } -} - impl Tree { /// Create a new tree, with only a root pointer. pub fn new(root_tag: BorTag, size: Size, span: Span) -> Self { @@ -625,7 +383,7 @@ impl Tree { let wildcard_accesses = UniValMap::default(); DedupRangeMap::new(size, LocationTree { perms, wildcard_accesses }) }; - Self { root: root_idx, nodes, locations, tag_mapping } + Self { roots: SmallVec::from_slice(&[root_idx]), nodes, locations, tag_mapping } } } @@ -639,7 +397,7 @@ impl<'tcx> Tree { pub(super) fn new_child( &mut self, base_offset: Size, - parent_tag: BorTag, + parent_prov: ProvenanceExtra, new_tag: BorTag, inside_perms: DedupRangeMap, outside_perm: Permission, @@ -647,7 +405,11 @@ impl<'tcx> Tree { span: Span, ) -> InterpResult<'tcx> { let idx = self.tag_mapping.insert(new_tag); - let parent_idx = self.tag_mapping.get(&parent_tag).unwrap(); + let parent_idx = match parent_prov { + ProvenanceExtra::Concrete(parent_tag) => + Some(self.tag_mapping.get(&parent_tag).unwrap()), + ProvenanceExtra::Wildcard => None, + }; assert!(outside_perm.is_initial()); let default_strongest_idempotent = @@ -657,7 +419,7 @@ impl<'tcx> Tree { idx, Node { tag: new_tag, - parent: Some(parent_idx), + parent: parent_idx, children: SmallVec::default(), default_initial_perm: outside_perm, default_initial_idempotent_foreign_access: default_strongest_idempotent, @@ -665,9 +427,17 @@ impl<'tcx> Tree { debug_info: NodeDebugInfo::new(new_tag, outside_perm, span), }, ); - let parent_node = self.nodes.get_mut(parent_idx).unwrap(); - // Register new_tag as a child of parent_tag - parent_node.children.push(idx); + if let Some(parent_idx) = parent_idx { + let parent_node = self.nodes.get_mut(parent_idx).unwrap(); + // Register new_tag as a child of parent_tag + parent_node.children.push(idx); + } else { + // If the parent had wildcard provenance, then register the idx + // as a new wildcard root. + // This preserves the orderedness of `roots` because a newly created + // tag is greater than all previous tags. + self.roots.push(idx); + } // We need to know the weakest SIFA for `update_idempotent_foreign_access_after_retag`. let mut min_sifa = default_strongest_idempotent; @@ -691,19 +461,27 @@ impl<'tcx> Tree { // We need to ensure the consistency of the wildcard access tracking data structure. // For this, we insert the correct entry for this tag based on its parent, if it exists. + // If we are inserting a new wildcard root (with Wildcard as parent_prov) then we insert + // the special wildcard root initial state instead. for (_range, loc) in self.locations.iter_mut_all() { - if let Some(parent_access) = loc.wildcard_accesses.get(parent_idx) { - loc.wildcard_accesses.insert(idx, parent_access.for_new_child()); + if let Some(parent_idx) = parent_idx { + if let Some(parent_access) = loc.wildcard_accesses.get(parent_idx) { + loc.wildcard_accesses.insert(idx, parent_access.for_new_child()); + } + } else { + loc.wildcard_accesses.insert(idx, WildcardState::for_wildcard_root()); } } - - // Inserting the new perms might have broken the SIFA invariant (see - // `foreign_access_skipping.rs`) if the SIFA we inserted is weaker than that of some parent. - // We now weaken the recorded SIFA for our parents, until the invariant is restored. We - // could weaken them all to `None`, but it is more efficient to compute the SIFA for the new - // permission statically, and use that. For this we need the *minimum* SIFA (`None` needs - // more fixup than `Write`). - self.update_idempotent_foreign_access_after_retag(parent_idx, min_sifa); + // If the parent is a wildcard pointer, then it doesn't track SIFA and doesn't need to be updated. + if let Some(parent_idx) = parent_idx { + // Inserting the new perms might have broken the SIFA invariant (see + // `foreign_access_skipping.rs`) if the SIFA we inserted is weaker than that of some parent. + // We now weaken the recorded SIFA for our parents, until the invariant is restored. We + // could weaken them all to `None`, but it is more efficient to compute the SIFA for the new + // permission statically, and use that. For this we need the *minimum* SIFA (`None` needs + // more fixup than `Write`). + self.update_idempotent_foreign_access_after_retag(parent_idx, min_sifa); + } interp_ok(()) } @@ -772,52 +550,68 @@ impl<'tcx> Tree { span, )?; - // The order in which we check if any nodes are invalidated only - // matters to diagnostics, so we use the root as a default tag. let start_idx = match prov { - ProvenanceExtra::Concrete(tag) => self.tag_mapping.get(&tag).unwrap(), - ProvenanceExtra::Wildcard => self.root, + ProvenanceExtra::Concrete(tag) => Some(self.tag_mapping.get(&tag).unwrap()), + ProvenanceExtra::Wildcard => None, }; // Check if this breaks any strong protector. // (Weak protectors are already handled by `perform_access`.) for (loc_range, loc) in self.locations.iter_mut(access_range.start, access_range.size) { - TreeVisitor { nodes: &mut self.nodes, loc }.traverse_this_parents_children_other( - start_idx, - // Visit all children, skipping none. - |_| ContinueTraversal::Recurse, - |args: NodeAppArgs<'_>| { - let node = args.nodes.get(args.idx).unwrap(); - let perm = args.loc.perms.entry(args.idx); - - let perm = perm.get().copied().unwrap_or_else(|| node.default_location_state()); - if global.borrow().protected_tags.get(&node.tag) - == Some(&ProtectorKind::StrongProtector) - // Don't check for protector if it is a Cell (see `unsafe_cell_deallocate` in `interior_mutability.rs`). - // Related to https://github.com/rust-lang/rust/issues/55005. - && !perm.permission.is_cell() - // Only trigger UB if the accessed bit is set, i.e. if the protector is actually protecting this offset. See #4579. - && perm.accessed - { - Err(TbError { - conflicting_info: &node.debug_info, - access_cause: diagnostics::AccessCause::Dealloc, - alloc_id, - error_offset: loc_range.start, - error_kind: TransitionError::ProtectedDealloc, - accessed_info: match prov { - ProvenanceExtra::Concrete(_) => - Some(&args.nodes.get(start_idx).unwrap().debug_info), - // We don't know from where the access came during a wildcard access. - ProvenanceExtra::Wildcard => None, - }, - } - .build()) - } else { - Ok(()) - } - }, - )?; + // Checks the tree containing `idx` for strong protector violations. + // It does this in traversal order. + let mut check_tree = |idx| { + TreeVisitor { nodes: &mut self.nodes, data: loc } + .traverse_this_parents_children_other( + idx, + // Visit all children, skipping none. + |_| ContinueTraversal::Recurse, + |args: NodeAppArgs<'_, _>| { + let node = args.nodes.get(args.idx).unwrap(); + + let perm = args + .data + .perms + .get(args.idx) + .copied() + .unwrap_or_else(|| node.default_location_state()); + if global.borrow().protected_tags.get(&node.tag) + == Some(&ProtectorKind::StrongProtector) + // Don't check for protector if it is a Cell (see `unsafe_cell_deallocate` in `interior_mutability.rs`). + // Related to https://github.com/rust-lang/rust/issues/55005. + && !perm.permission.is_cell() + // Only trigger UB if the accessed bit is set, i.e. if the protector is actually protecting this offset. See #4579. + && perm.accessed + { + Err(TbError { + conflicting_info: &node.debug_info, + access_cause: diagnostics::AccessCause::Dealloc, + alloc_id, + error_offset: loc_range.start, + error_kind: TransitionError::ProtectedDealloc, + accessed_info: start_idx + .map(|idx| &args.nodes.get(idx).unwrap().debug_info), + } + .build()) + } else { + Ok(()) + } + }, + ) + }; + // If we have a start index we first check its subtree in traversal order. + // This results in us showing the error of the closest node instead of an + // arbitrary one. + let accessed_root = start_idx.map(&mut check_tree).transpose()?; + // Afterwards we check all other trees. + // We iterate over the list in reverse order to ensure that we do not visit + // a parent before its child. + for &root in self.roots.iter().rev() { + if Some(root) == accessed_root { + continue; + } + check_tree(root)?; + } } interp_ok(()) } @@ -849,20 +643,20 @@ impl<'tcx> Tree { span: Span, // diagnostics ) -> InterpResult<'tcx> { #[cfg(feature = "expensive-consistency-checks")] - if matches!(prov, ProvenanceExtra::Wildcard) { + if self.roots.len() > 1 || matches!(prov, ProvenanceExtra::Wildcard) { self.verify_wildcard_consistency(global); } + let source_idx = match prov { ProvenanceExtra::Concrete(tag) => Some(self.tag_mapping.get(&tag).unwrap()), ProvenanceExtra::Wildcard => None, }; - if let Some((access_range, access_kind, access_cause)) = access_range_and_kind { // Default branch: this is a "normal" access through a known range. // We iterate over affected locations and traverse the tree for each of them. for (loc_range, loc) in self.locations.iter_mut(access_range.start, access_range.size) { loc.perform_access( - self.root, + self.roots.iter().copied(), &mut self.nodes, source_idx, loc_range, @@ -898,7 +692,7 @@ impl<'tcx> Tree { { let access_cause = diagnostics::AccessCause::FnExit(access_kind); loc.perform_access( - self.root, + self.roots.iter().copied(), &mut self.nodes, Some(source_idx), loc_range, @@ -920,7 +714,9 @@ impl<'tcx> Tree { /// Integration with the BorTag garbage collector impl Tree { pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet) { - self.remove_useless_children(self.root, live_tags); + for i in 0..(self.roots.len()) { + self.remove_useless_children(self.roots[i], live_tags); + } // Right after the GC runs is a good moment to check if we can // merge some adjacent ranges that were made equal by the removal of some // tags (this does not necessarily mean that they have identical internal representations, @@ -1073,20 +869,20 @@ impl<'tcx> LocationTree { /// * `visit_children`: Whether to skip updating the children of `access_source`. fn perform_access( &mut self, - root: UniIndex, + roots: impl Iterator, nodes: &mut UniValMap, access_source: Option, - loc_range: Range, - access_range: Option, + loc_range: Range, // diagnostics + access_range: Option, // diagnostics access_kind: AccessKind, - access_cause: diagnostics::AccessCause, + access_cause: diagnostics::AccessCause, // diagnostics global: &GlobalState, alloc_id: AllocId, // diagnostics span: Span, // diagnostics visit_children: ChildrenVisitMode, ) -> InterpResult<'tcx> { - if let Some(idx) = access_source { - self.perform_normal_access( + let accessed_root = if let Some(idx) = access_source { + Some(self.perform_normal_access( idx, nodes, loc_range.clone(), @@ -1097,13 +893,38 @@ impl<'tcx> LocationTree { alloc_id, span, visit_children, - ) + )?) } else { - // `SkipChildrenOfAccessed` only gets set on protector release. - // Since a wildcard reference are never protected this assert shouldn't fail. + // `SkipChildrenOfAccessed` only gets set on protector release, which only + // occurs on a known node. assert!(matches!(visit_children, ChildrenVisitMode::VisitChildrenOfAccessed)); + None + }; + + let accessed_root_tag = accessed_root.map(|idx| nodes.get(idx).unwrap().tag); + if matches!(visit_children, ChildrenVisitMode::SkipChildrenOfAccessed) { + // FIXME: approximate which roots could be children of the accessed node and only skip them instead of all other trees. + return interp_ok(()); + } + for root in roots { + // We don't perform a wildcard access on the tree we already performed a + // normal access on. + if Some(root) == accessed_root { + continue; + } + // The choice of `max_local_tag` requires some thought. + // This can only be a local access for nodes that are a parent of the accessed node + // and are therefore smaller, so the accessed node itself is a valid choice for `max_local_tag`. + // However, using `accessed_root` is better since that will be smaller. It is still a valid choice + // because for nodes *in other trees*, if they are a parent of the accessed node then they + // are a parent of `accessed_root`. + // + // As a consequence of this, since the root of the main tree is the smallest tag in the entire + // allocation, if the access occurred in the main tree then other subtrees will only see foreign accesses. self.perform_wildcard_access( root, + access_source, + /*max_local_tag*/ accessed_root_tag, nodes, loc_range.clone(), access_range, @@ -1112,11 +933,14 @@ impl<'tcx> LocationTree { global, alloc_id, span, - ) + )?; } + interp_ok(()) } /// Performs a normal access on the tree containing `access_source`. + /// + /// Returns the root index of this tree. /// * `access_source`: The index of the tag being accessed. /// * `visit_children`: Whether to skip the children of `access_source` /// during the access. Used for protector end access. @@ -1124,15 +948,15 @@ impl<'tcx> LocationTree { &mut self, access_source: UniIndex, nodes: &mut UniValMap, - loc_range: Range, - access_range: Option, + loc_range: Range, // diagnostics + access_range: Option, // diagnostics access_kind: AccessKind, - access_cause: diagnostics::AccessCause, + access_cause: diagnostics::AccessCause, // diagnostics global: &GlobalState, alloc_id: AllocId, // diagnostics span: Span, // diagnostics visit_children: ChildrenVisitMode, - ) -> InterpResult<'tcx> { + ) -> InterpResult<'tcx, UniIndex> { // Performs the per-node work: // - insert the permission if it does not exist // - perform the access @@ -1141,18 +965,18 @@ impl<'tcx> LocationTree { // - skip the traversal of the children in some cases // - do not record noop transitions // - // `perms_range` is only for diagnostics (it is the range of + // `loc_range` is only for diagnostics (it is the range of // the `RangeMap` on which we are currently working). - let node_skipper = |args: &NodeAppArgs<'_>| -> ContinueTraversal { + let node_skipper = |args: &NodeAppArgs<'_, LocationTree>| -> ContinueTraversal { let node = args.nodes.get(args.idx).unwrap(); - let perm = args.loc.perms.get(args.idx); + let perm = args.data.perms.get(args.idx); let old_state = perm.copied().unwrap_or_else(|| node.default_location_state()); old_state.skip_if_known_noop(access_kind, args.rel_pos) }; - let node_app = |args: NodeAppArgs<'_>| -> Result<(), _> { + let node_app = |args: NodeAppArgs<'_, LocationTree>| { let node = args.nodes.get_mut(args.idx).unwrap(); - let mut perm = args.loc.perms.entry(args.idx); + let mut perm = args.data.perms.entry(args.idx); let state = perm.or_insert(node.default_location_state()); @@ -1161,10 +985,10 @@ impl<'tcx> LocationTree { .perform_transition( args.idx, args.nodes, - &mut args.loc.wildcard_accesses, + &mut args.data.wildcard_accesses, access_kind, access_cause, - /* access_range */ access_range, + access_range, args.rel_pos, span, loc_range.clone(), @@ -1182,7 +1006,8 @@ impl<'tcx> LocationTree { .build() }) }; - let visitor = TreeVisitor { nodes, loc: self }; + + let visitor = TreeVisitor { nodes, data: self }; match visit_children { ChildrenVisitMode::VisitChildrenOfAccessed => visitor.traverse_this_parents_children_other(access_source, node_skipper, node_app), @@ -1191,31 +1016,61 @@ impl<'tcx> LocationTree { } .into() } + /// Performs a wildcard access on the tree with root `root`. Takes the `access_relatedness` /// for each node from the `WildcardState` datastructure. /// * `root`: Root of the tree being accessed. + /// * `access_source`: the index of the accessed tag, if any. + /// This is only used for printing the correct tag on errors. + /// * `max_local_tag`: The access can only be local for nodes whose tag is + /// at most `max_local_tag`. fn perform_wildcard_access( &mut self, root: UniIndex, + access_source: Option, + max_local_tag: Option, nodes: &mut UniValMap, - loc_range: Range, - access_range: Option, + loc_range: Range, // diagnostics + access_range: Option, // diagnostics access_kind: AccessKind, - access_cause: diagnostics::AccessCause, + access_cause: diagnostics::AccessCause, // diagnostics global: &GlobalState, alloc_id: AllocId, // diagnostics span: Span, // diagnostics ) -> InterpResult<'tcx> { - let f_continue = - |idx: UniIndex, nodes: &UniValMap, loc: &LocationTree| -> ContinueTraversal { - let node = nodes.get(idx).unwrap(); - let perm = loc.perms.get(idx); - let wildcard_state = loc.wildcard_accesses.get(idx).cloned().unwrap_or_default(); + let get_relatedness = |idx: UniIndex, node: &Node, loc: &LocationTree| { + let wildcard_state = loc.wildcard_accesses.get(idx).cloned().unwrap_or_default(); + // If the tag is larger than `max_local_tag` then the access can only be foreign. + let only_foreign = max_local_tag.is_some_and(|max_local_tag| max_local_tag < node.tag); + wildcard_state.access_relatedness(access_kind, only_foreign) + }; + + // This does a traversal across the tree updating children before their parents. The + // difference to `perform_normal_access` is that we take the access relatedness from + // the wildcard tracking state of the node instead of from the visitor itself. + // + // Unlike for a normal access, the iteration order is important for improving the + // accuracy of wildcard accesses if `max_local_tag` is `Some`: processing the effects of this + // access further down the tree can cause exposed nodes to lose permissions, thus updating + // the wildcard data structure, which will be taken into account when processing the parent + // nodes. Also see the test `cross_tree_update_older_invalid_exposed2.rs` + // (Doing accesses in the opposite order cannot help with precision but the reasons are complicated; + // see .) + // + // Note, however, that this is an approximation: there can be situations where a node is + // marked as having an exposed foreign node, but actually that foreign node cannot be + // the source of the access due to `max_local_tag`. The wildcard tracking cannot know + // about `max_local_tag` so we will incorrectly assume that this might be a foreign access. + TreeVisitor { data: self, nodes }.traverse_children_this( + root, + |args| -> ContinueTraversal { + let node = args.nodes.get(args.idx).unwrap(); + let perm = args.data.perms.get(args.idx); let old_state = perm.copied().unwrap_or_else(|| node.default_location_state()); // If we know where, relative to this node, the wildcard access occurs, // then check if we can skip the entire subtree. - if let Some(relatedness) = wildcard_state.access_relatedness(access_kind) + if let Some(relatedness) = get_relatedness(args.idx, node, args.data) && let Some(relatedness) = relatedness.to_relatedness() { // We can use the usual SIFA machinery to skip nodes. @@ -1223,78 +1078,64 @@ impl<'tcx> LocationTree { } else { ContinueTraversal::Recurse } - }; - // This does a traversal starting from the root through the tree updating - // the permissions of each node. - // The difference to `perform_access` is that we take the access - // relatedness from the wildcard tracking state of the node instead of - // from the visitor itself. - TreeVisitor { loc: self, nodes } - .traverse_this_parents_children_other( - root, - |args| f_continue(args.idx, args.nodes, args.loc), - |args| { - let node = args.nodes.get_mut(args.idx).unwrap(); - let mut entry = args.loc.perms.entry(args.idx); - let perm = entry.or_insert(node.default_location_state()); - - let protected = global.borrow().protected_tags.contains_key(&node.tag); + }, + |args| { + let node = args.nodes.get_mut(args.idx).unwrap(); + + let protected = global.borrow().protected_tags.contains_key(&node.tag); + + let Some(wildcard_relatedness) = get_relatedness(args.idx, node, args.data) else { + // There doesn't exist a valid exposed reference for this access to + // happen through. + // This can only happen if `root` is the main root: We set + // `max_foreign_access==Write` on all wildcard roots, so at least a foreign access + // is always possible on all nodes in a wildcard subtree. + return Err(no_valid_exposed_references_error( + alloc_id, + loc_range.start, + access_cause, + )); + }; - let Some(wildcard_relatedness) = args - .loc - .wildcard_accesses - .get(args.idx) - .and_then(|s| s.access_relatedness(access_kind)) - else { - // There doesn't exist a valid exposed reference for this access to - // happen through. - // If this fails for one id, then it fails for all ids so this. - // Since we always check the root first, this means it should always - // fail on the root. - assert_eq!(root, args.idx); - return Err(no_valid_exposed_references_error( - alloc_id, - loc_range.start, - access_cause, - )); - }; + let Some(relatedness) = wildcard_relatedness.to_relatedness() else { + // If the access type is Either, then we do not apply any transition + // to this node, but we still update each of its children. + // This is an imprecision! In the future, maybe we can still do some sort + // of best-effort update here. + return Ok(()); + }; - let Some(relatedness) = wildcard_relatedness.to_relatedness() else { - // If the access type is Either, then we do not apply any transition - // to this node, but we still update each of its children. - // This is an imprecision! In the future, maybe we can still do some sort - // of best-effort update here. - return Ok(()); - }; - // We know the exact relatedness, so we can actually do precise checks. - perm.perform_transition( - args.idx, - args.nodes, - &mut args.loc.wildcard_accesses, - access_kind, + let mut entry = args.data.perms.entry(args.idx); + let perm = entry.or_insert(node.default_location_state()); + // We know the exact relatedness, so we can actually do precise checks. + perm.perform_transition( + args.idx, + args.nodes, + &mut args.data.wildcard_accesses, + access_kind, + access_cause, + access_range, + relatedness, + span, + loc_range.clone(), + protected, + ) + .map_err(|trans| { + let node = args.nodes.get(args.idx).unwrap(); + TbError { + conflicting_info: &node.debug_info, access_cause, - access_range, - relatedness, - span, - loc_range.clone(), - protected, - ) - .map_err(|trans| { - let node = args.nodes.get(args.idx).unwrap(); - TbError { - conflicting_info: &node.debug_info, - access_cause, - alloc_id, - error_offset: loc_range.start, - error_kind: trans, - // We don't know from where the access came during a wildcard access. - accessed_info: None, - } - .build() - }) - }, - ) - .into() + alloc_id, + error_offset: loc_range.start, + error_kind: trans, + accessed_info: access_source + .map(|idx| &args.nodes.get(idx).unwrap().debug_info), + } + .build() + }) + }, + )?; + interp_ok(()) } } @@ -1309,10 +1150,11 @@ impl Node { impl VisitProvenance for Tree { fn visit_provenance(&self, visit: &mut VisitWith<'_>) { - // To ensure that the root never gets removed, we visit it - // (the `root` node of `Tree` is not an `Option<_>`) - visit(None, Some(self.nodes.get(self.root).unwrap().tag)); - + // To ensure that the roots never get removed, we visit them. + // FIXME: it should be possible to GC wildcard tree roots. + for id in self.roots.iter().copied() { + visit(None, Some(self.nodes.get(id).unwrap().tag)); + } // We also need to keep around any exposed tags through which // an access could still happen. for (_id, node) in self.nodes.iter() { diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/tree_visitor.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/tree_visitor.rs new file mode 100644 index 0000000000000..b1ceeecf577de --- /dev/null +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/tree_visitor.rs @@ -0,0 +1,289 @@ +use std::marker::PhantomData; + +use super::tree::{AccessRelatedness, Node}; +use super::unimap::{UniIndex, UniValMap}; + +/// Data given to the transition function +pub struct NodeAppArgs<'visit, T> { + /// The index of the current node. + pub idx: UniIndex, + /// Relative position of the access. + pub rel_pos: AccessRelatedness, + /// The node map of this tree. + pub nodes: &'visit mut UniValMap, + /// Additional data we want to be able to modify in f_propagate and read in f_continue. + pub data: &'visit mut T, +} +/// Internal contents of `Tree` with the minimum of mutable access for +/// For soundness do not modify the children or parent indexes of nodes +/// during traversal. +pub struct TreeVisitor<'tree, T> { + pub nodes: &'tree mut UniValMap, + pub data: &'tree mut T, +} + +/// Whether to continue exploring the children recursively or not. +#[derive(Debug)] +pub enum ContinueTraversal { + Recurse, + SkipSelfAndChildren, +} + +#[derive(Clone, Copy, Debug)] +pub enum ChildrenVisitMode { + VisitChildrenOfAccessed, + SkipChildrenOfAccessed, +} + +enum RecursionState { + BeforeChildren, + AfterChildren, +} + +/// Stack of nodes left to explore in a tree traversal. +/// See the docs of `traverse_this_parents_children_other` for details on the +/// traversal order. +struct TreeVisitorStack { + /// Function describing whether to continue at a tag. + /// This is only invoked for foreign accesses. + f_continue: NodeContinue, + /// Function to apply to each tag. + f_propagate: NodeApp, + /// Mutable state of the visit: the tags left to handle. + /// Every tag pushed should eventually be handled, + /// and the precise order is relevant for diagnostics. + /// Since the traversal is piecewise bottom-up, we need to + /// remember whether we're here initially, or after visiting all children. + /// The last element indicates this. + /// This is just an artifact of how you hand-roll recursion, + /// it does not have a deeper meaning otherwise. + stack: Vec<(UniIndex, AccessRelatedness, RecursionState)>, + phantom: PhantomData, +} + +impl TreeVisitorStack +where + NodeContinue: Fn(&NodeAppArgs<'_, T>) -> ContinueTraversal, + NodeApp: FnMut(NodeAppArgs<'_, T>) -> Result<(), Err>, +{ + fn should_continue_at( + &self, + this: &mut TreeVisitor<'_, T>, + idx: UniIndex, + rel_pos: AccessRelatedness, + ) -> ContinueTraversal { + let args = NodeAppArgs { idx, rel_pos, nodes: this.nodes, data: this.data }; + (self.f_continue)(&args) + } + + fn propagate_at( + &mut self, + this: &mut TreeVisitor<'_, T>, + idx: UniIndex, + rel_pos: AccessRelatedness, + ) -> Result<(), Err> { + (self.f_propagate)(NodeAppArgs { idx, rel_pos, nodes: this.nodes, data: this.data }) + } + + /// Returns the root of this tree. + fn go_upwards_from_accessed( + &mut self, + this: &mut TreeVisitor<'_, T>, + accessed_node: UniIndex, + visit_children: ChildrenVisitMode, + ) -> Result { + // We want to visit the accessed node's children first. + // However, we will below walk up our parents and push their children (our cousins) + // onto the stack. To ensure correct iteration order, this method thus finishes + // by reversing the stack. This only works if the stack is empty initially. + assert!(self.stack.is_empty()); + // First, handle accessed node. A bunch of things need to + // be handled differently here compared to the further parents + // of `accesssed_node`. + { + self.propagate_at(this, accessed_node, AccessRelatedness::LocalAccess)?; + if matches!(visit_children, ChildrenVisitMode::VisitChildrenOfAccessed) { + let accessed_node = this.nodes.get(accessed_node).unwrap(); + // We `rev()` here because we reverse the entire stack later. + for &child in accessed_node.children.iter().rev() { + self.stack.push(( + child, + AccessRelatedness::ForeignAccess, + RecursionState::BeforeChildren, + )); + } + } + } + // Then, handle the accessed node's parents. Here, we need to + // make sure we only mark the "cousin" subtrees for later visitation, + // not the subtree that contains the accessed node. + let mut last_node = accessed_node; + while let Some(current) = this.nodes.get(last_node).unwrap().parent { + self.propagate_at(this, current, AccessRelatedness::LocalAccess)?; + let node = this.nodes.get(current).unwrap(); + // We `rev()` here because we reverse the entire stack later. + for &child in node.children.iter().rev() { + if last_node == child { + continue; + } + self.stack.push(( + child, + AccessRelatedness::ForeignAccess, + RecursionState::BeforeChildren, + )); + } + last_node = current; + } + // Reverse the stack, as discussed above. + self.stack.reverse(); + Ok(last_node) + } + + fn finish_foreign_accesses(&mut self, this: &mut TreeVisitor<'_, T>) -> Result<(), Err> { + while let Some((idx, rel_pos, step)) = self.stack.last_mut() { + let idx = *idx; + let rel_pos = *rel_pos; + match *step { + // How to do bottom-up traversal, 101: Before you handle a node, you handle all children. + // For this, you must first find the children, which is what this code here does. + RecursionState::BeforeChildren => { + // Next time we come back will be when all the children are handled. + *step = RecursionState::AfterChildren; + // Now push the children, except if we are told to skip this subtree. + let handle_children = self.should_continue_at(this, idx, rel_pos); + match handle_children { + ContinueTraversal::Recurse => { + let node = this.nodes.get(idx).unwrap(); + for &child in node.children.iter() { + self.stack.push((child, rel_pos, RecursionState::BeforeChildren)); + } + } + ContinueTraversal::SkipSelfAndChildren => { + // skip self + self.stack.pop(); + continue; + } + } + } + // All the children are handled, let's actually visit this node + RecursionState::AfterChildren => { + self.stack.pop(); + self.propagate_at(this, idx, rel_pos)?; + } + } + } + Ok(()) + } + + fn new(f_continue: NodeContinue, f_propagate: NodeApp) -> Self { + Self { f_continue, f_propagate, stack: Vec::new(), phantom: PhantomData } + } +} + +impl<'tree, T> TreeVisitor<'tree, T> { + /// Applies `f_propagate` to every vertex of the tree in a piecewise bottom-up way: First, visit + /// all ancestors of `start_idx` (starting with `start_idx` itself), then children of `start_idx`, then the rest, + /// going bottom-up in each of these two "pieces" / sections. + /// This ensures that errors are triggered in the following order + /// - first invalid accesses with insufficient permissions, closest to the accessed node first, + /// - then protector violations, bottom-up, starting with the children of the accessed node, and then + /// going upwards and outwards. + /// + /// The following graphic visualizes it, with numbers indicating visitation order and `start_idx` being + /// the node that is visited first ("1"): + /// + /// ```text + /// 3 + /// /| + /// / | + /// 9 2 + /// | |\ + /// | | \ + /// 8 1 7 + /// / \ + /// 4 6 + /// | + /// 5 + /// ``` + /// + /// `f_propagate` should follow the following format: for a given `Node` it updates its + /// `Permission` depending on the position relative to `start_idx` (given by an + /// `AccessRelatedness`). + /// `f_continue` is called earlier on foreign nodes, and describes whether to even start + /// visiting the subtree at that node. If it e.g. returns `SkipSelfAndChildren` on node 6 + /// above, then nodes 5 _and_ 6 would not be visited by `f_propagate`. It is not used for + /// notes having a child access (nodes 1, 2, 3). + /// + /// Finally, remember that the iteration order is not relevant for UB, it only affects + /// diagnostics. It also affects tree traversal optimizations built on top of this, so + /// those need to be reviewed carefully as well whenever this changes. + /// + /// Returns the index of the root of the accessed tree. + pub fn traverse_this_parents_children_other( + mut self, + start_idx: UniIndex, + f_continue: impl Fn(&NodeAppArgs<'_, T>) -> ContinueTraversal, + f_propagate: impl FnMut(NodeAppArgs<'_, T>) -> Result<(), Err>, + ) -> Result { + let mut stack = TreeVisitorStack::new(f_continue, f_propagate); + // Visits the accessed node itself, and all its parents, i.e. all nodes + // undergoing a child access. Also pushes the children and the other + // cousin nodes (i.e. all nodes undergoing a foreign access) to the stack + // to be processed later. + let root = stack.go_upwards_from_accessed( + &mut self, + start_idx, + ChildrenVisitMode::VisitChildrenOfAccessed, + )?; + // Now visit all the foreign nodes we remembered earlier. + // For this we go bottom-up, but also allow f_continue to skip entire + // subtrees from being visited if it would be a NOP. + stack.finish_foreign_accesses(&mut self)?; + Ok(root) + } + + /// Like `traverse_this_parents_children_other`, but skips the children of `start_idx`. + /// + /// Returns the index of the root of the accessed tree. + pub fn traverse_nonchildren( + mut self, + start_idx: UniIndex, + f_continue: impl Fn(&NodeAppArgs<'_, T>) -> ContinueTraversal, + f_propagate: impl FnMut(NodeAppArgs<'_, T>) -> Result<(), Err>, + ) -> Result { + let mut stack = TreeVisitorStack::new(f_continue, f_propagate); + // Visits the accessed node itself, and all its parents, i.e. all nodes + // undergoing a child access. Also pushes the other cousin nodes to the + // stack, but not the children of the accessed node. + let root = stack.go_upwards_from_accessed( + &mut self, + start_idx, + ChildrenVisitMode::SkipChildrenOfAccessed, + )?; + // Now visit all the foreign nodes we remembered earlier. + // For this we go bottom-up, but also allow f_continue to skip entire + // subtrees from being visited if it would be a NOP. + stack.finish_foreign_accesses(&mut self)?; + Ok(root) + } + + /// Traverses all children of `start_idx` including `start_idx` itself. + /// Uses `f_continue` to filter out subtrees and then processes each node + /// with `f_propagate` so that the children get processed before their + /// parents. + pub fn traverse_children_this( + mut self, + start_idx: UniIndex, + f_continue: impl Fn(&NodeAppArgs<'_, T>) -> ContinueTraversal, + f_propagate: impl FnMut(NodeAppArgs<'_, T>) -> Result<(), Err>, + ) -> Result<(), Err> { + let mut stack = TreeVisitorStack::new(f_continue, f_propagate); + + stack.stack.push(( + start_idx, + AccessRelatedness::ForeignAccess, + RecursionState::BeforeChildren, + )); + stack.finish_foreign_accesses(&mut self) + } +} diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/wildcard.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/wildcard.rs index 56a85e6f4ced3..3b55a9e36ea6f 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/wildcard.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/wildcard.rs @@ -88,10 +88,26 @@ impl WildcardState { } /// From where relative to the node with this wildcard info a read or write access could happen. - pub fn access_relatedness(&self, kind: AccessKind) -> Option { - match kind { + /// If `only_foreign` is true then we treat `LocalAccess` as impossible. This means we return + /// `None` if only a `LocalAccess` is possible, and we treat `EitherAccess` as a + /// `ForeignAccess`. + pub fn access_relatedness( + &self, + kind: AccessKind, + only_foreign: bool, + ) -> Option { + let rel = match kind { AccessKind::Read => self.read_access_relatedness(), AccessKind::Write => self.write_access_relatedness(), + }; + if only_foreign { + use WildcardAccessRelatedness as E; + match rel { + Some(E::EitherAccess | E::ForeignAccess) => Some(E::ForeignAccess), + Some(E::LocalAccess) | None => None, + } + } else { + rel } } @@ -131,6 +147,15 @@ impl WildcardState { ..Default::default() } } + /// Crates the initial `WildcardState` for a wildcard root. + /// This has `max_foreign_access==Write` as it actually is the child of *some* exposed node + /// through which we can receive foreign accesses. + /// + /// This is different from the main root which has `max_foreign_access==None`, since there + /// cannot be a foreign access to the root of the allocation. + pub fn for_wildcard_root() -> Self { + Self { max_foreign_access: WildcardAccessLevel::Write, ..Default::default() } + } /// Pushes the nodes of `children` onto the stack who's `max_foreign_access` /// needs to be updated. @@ -435,6 +460,10 @@ impl Tree { /// Checks that the wildcard tracking data structure is internally consistent and /// has the correct `exposed_as` values. pub fn verify_wildcard_consistency(&self, global: &GlobalState) { + // We rely on the fact that `roots` is ordered according to tag from low to high. + assert!(self.roots.is_sorted_by_key(|idx| self.nodes.get(*idx).unwrap().tag)); + let main_root_idx = self.roots[0]; + let protected_tags = &global.borrow().protected_tags; for (_, loc) in self.locations.iter_all() { let wildcard_accesses = &loc.wildcard_accesses; @@ -447,7 +476,8 @@ impl Tree { let state = wildcard_accesses.get(id).unwrap(); let expected_exposed_as = if node.is_exposed { - let perm = perms.get(id).unwrap(); + let perm = + perms.get(id).copied().unwrap_or_else(|| node.default_location_state()); perm.permission() .strongest_allowed_child_access(protected_tags.contains_key(&node.tag)) @@ -477,7 +507,16 @@ impl Tree { .max(parent_state.max_foreign_access) .max(parent_state.exposed_as) } else { - WildcardAccessLevel::None + if main_root_idx == id { + // There can never be a foreign access to the root of the allocation. + // So its foreign access level is always `None`. + WildcardAccessLevel::None + } else { + // For wildcard roots any access on a different subtree can be foreign + // to it. So a wildcard root has the maximum possible foreign access + // level. + WildcardAccessLevel::Write + } }; // Count how many children can be the source of wildcard reads or writes diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index c7ae335d04795..5016e3b66ca6a 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -14,7 +14,7 @@ use rustc_hir::def_id::DefId; use rustc_index::{Idx, IndexVec}; use rustc_middle::mir::Mutability; use rustc_middle::ty::layout::TyAndLayout; -use rustc_span::Span; +use rustc_span::{DUMMY_SP, Span}; use rustc_target::spec::Os; use crate::concurrency::GlobalDataRaceHandler; @@ -174,6 +174,10 @@ pub struct Thread<'tcx> { /// The virtual call stack. stack: Vec>>, + /// A span that explains where the thread (or more specifically, its current root + /// frame) "comes from". + pub(crate) origin_span: Span, + /// The function to call when the stack ran empty, to figure out what to do next. /// Conceptually, this is the interpreter implementation of the things that happen 'after' the /// Rust language entry point for this thread returns (usually implemented by the C or OS runtime). @@ -303,6 +307,7 @@ impl<'tcx> Thread<'tcx> { state: ThreadState::Enabled, thread_name: name.map(|name| Vec::from(name.as_bytes())), stack: Vec::new(), + origin_span: DUMMY_SP, top_user_relevant_frame: None, join_status: ThreadJoinStatus::Joinable, unwind_payloads: Vec::new(), @@ -318,6 +323,7 @@ impl VisitProvenance for Thread<'_> { unwind_payloads: panic_payload, last_error, stack, + origin_span: _, top_user_relevant_frame: _, state: _, thread_name: _, @@ -584,6 +590,10 @@ impl<'tcx> ThreadManager<'tcx> { &self.threads[self.active_thread] } + pub fn thread_ref(&self, thread_id: ThreadId) -> &Thread<'tcx> { + &self.threads[thread_id] + } + /// Mark the thread as detached, which means that no other thread will try /// to join it and the thread is responsible for cleaning up. /// @@ -704,8 +714,9 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { #[inline] fn run_on_stack_empty(&mut self) -> InterpResult<'tcx, Poll<()>> { let this = self.eval_context_mut(); - let mut callback = this - .active_thread_mut() + let active_thread = this.active_thread_mut(); + active_thread.origin_span = DUMMY_SP; // reset, the old value no longer applied + let mut callback = active_thread .on_stack_empty .take() .expect("`on_stack_empty` not set up, or already running"); @@ -891,11 +902,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); // Create the new thread + let current_span = this.machine.current_user_relevant_span(); let new_thread_id = this.machine.threads.create_thread({ let mut state = tls::TlsDtorsState::default(); Box::new(move |m| state.on_stack_empty(m)) }); - let current_span = this.machine.current_user_relevant_span(); match &mut this.machine.data_race { GlobalDataRaceHandler::None => {} GlobalDataRaceHandler::Vclocks(data_race) => @@ -934,12 +945,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // it. let ret_place = this.allocate(ret_layout, MiriMemoryKind::Machine.into())?; - this.call_function( + this.call_thread_root_function( instance, start_abi, &[func_arg], Some(&ret_place), - ReturnContinuation::Stop { cleanup: true }, + current_span, )?; // Restore the old active thread frame. diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 8e252d306b29b..01f77f261d703 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -444,7 +444,11 @@ pub fn report_result<'tcx>( write!(primary_msg, "{}", format_interp_error(ecx.tcx.dcx(), res)).unwrap(); if labels.is_empty() { - labels.push(format!("{} occurred here", title.unwrap_or("error"))); + labels.push(format!( + "{} occurred {}", + title.unwrap_or("error"), + if stacktrace.is_empty() { "due to this code" } else { "here" } + )); } report_msg( @@ -552,7 +556,14 @@ pub fn report_msg<'tcx>( thread: Option, machine: &MiriMachine<'tcx>, ) { - let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span); + let span = match stacktrace.first() { + Some(fi) => fi.span, + None => + match thread { + Some(thread_id) => machine.threads.thread_ref(thread_id).origin_span, + None => DUMMY_SP, + }, + }; let sess = machine.tcx.sess; let level = match diag_level { DiagLevel::Error => Level::Error, @@ -620,6 +631,12 @@ pub fn report_msg<'tcx>( err.note(format!("{frame_info} at {span}")); } } + } else if stacktrace.len() == 0 && !span.is_dummy() { + err.note(format!( + "this {} occurred while pushing a call frame onto an empty stack", + level.to_str() + )); + err.note("the span indicates which code caused the function to be called, but may not be the literal call site"); } err.emit(); diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 383a4e2ea4b04..75b7e9f5966fb 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -472,6 +472,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) } + /// Call a function in an "empty" thread. + fn call_thread_root_function( + &mut self, + f: ty::Instance<'tcx>, + caller_abi: ExternAbi, + args: &[ImmTy<'tcx>], + dest: Option<&MPlaceTy<'tcx>>, + span: Span, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + assert!(this.active_thread_stack().is_empty()); + assert!(this.active_thread_ref().origin_span.is_dummy()); + this.active_thread_mut().origin_span = span; + this.call_function(f, caller_abi, args, dest, ReturnContinuation::Stop { cleanup: true }) + } + /// Visits the memory covered by `place`, sensitive to freezing: the 2nd parameter /// of `action` will be true if this is frozen, false if this is in an `UnsafeCell`. /// The range is relative to `place`. @@ -995,11 +1011,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) } - /// Lookup an array of immediates from any linker sections matching the provided predicate. + /// Lookup an array of immediates from any linker sections matching the provided predicate, + /// with the spans of where they were found. fn lookup_link_section( &mut self, include_name: impl Fn(&str) -> bool, - ) -> InterpResult<'tcx, Vec>> { + ) -> InterpResult<'tcx, Vec<(ImmTy<'tcx>, Span)>> { let this = self.eval_context_mut(); let tcx = this.tcx.tcx; @@ -1012,6 +1029,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; if include_name(link_section.as_str()) { let instance = ty::Instance::mono(tcx, def_id); + let span = tcx.def_span(def_id); let const_val = this.eval_global(instance).unwrap_or_else(|err| { panic!( "failed to evaluate static in required link_section: {def_id:?}\n{err:?}" @@ -1019,12 +1037,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }); match const_val.layout.ty.kind() { ty::FnPtr(..) => { - array.push(this.read_immediate(&const_val)?); + array.push((this.read_immediate(&const_val)?, span)); } ty::Array(elem_ty, _) if matches!(elem_ty.kind(), ty::FnPtr(..)) => { let mut elems = this.project_array_fields(&const_val)?; while let Some((_idx, elem)) = elems.next(this)? { - array.push(this.read_immediate(&elem)?); + array.push((this.read_immediate(&elem)?, span)); } } _ => diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index b30395b738b17..fe501b8d7b30d 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -39,6 +39,7 @@ clippy::needless_question_mark, clippy::needless_lifetimes, clippy::too_long_first_doc_paragraph, + clippy::len_zero, // We don't use translatable diagnostics rustc::diagnostic_outside_of_impl, // We are not implementing queries here so it's fine diff --git a/src/tools/miri/src/shims/global_ctor.rs b/src/tools/miri/src/shims/global_ctor.rs index c56251bbe63ae..d3296e4445f75 100644 --- a/src/tools/miri/src/shims/global_ctor.rs +++ b/src/tools/miri/src/shims/global_ctor.rs @@ -3,6 +3,7 @@ use std::task::Poll; use rustc_abi::ExternAbi; +use rustc_span::Span; use rustc_target::spec::BinaryFormat; use crate::*; @@ -15,7 +16,7 @@ enum GlobalCtorStatePriv<'tcx> { #[default] Init, /// The list of constructor functions that we still have to call. - Ctors(Vec>), + Ctors(Vec<(ImmTy<'tcx>, Span)>), Done, } @@ -67,19 +68,19 @@ impl<'tcx> GlobalCtorState<'tcx> { break 'new_state Ctors(ctors); } Ctors(ctors) => { - if let Some(ctor) = ctors.pop() { + if let Some((ctor, span)) = ctors.pop() { let this = this.eval_context_mut(); let ctor = ctor.to_scalar().to_pointer(this)?; let thread_callback = this.get_ptr_fn(ctor)?.as_instance()?; // The signature of this function is `unsafe extern "C" fn()`. - this.call_function( + this.call_thread_root_function( thread_callback, ExternAbi::C { unwind: false }, &[], None, - ReturnContinuation::Stop { cleanup: true }, + span, )?; return interp_ok(Poll::Pending); // we stay in this state (but `ctors` got shorter) diff --git a/src/tools/miri/src/shims/tls.rs b/src/tools/miri/src/shims/tls.rs index 2159c41ab16cb..3ecd9b1ead38e 100644 --- a/src/tools/miri/src/shims/tls.rs +++ b/src/tools/miri/src/shims/tls.rs @@ -6,6 +6,7 @@ use std::task::Poll; use rustc_abi::{ExternAbi, HasDataLayout, Size}; use rustc_middle::ty; +use rustc_span::Span; use rustc_target::spec::Os; use crate::*; @@ -17,7 +18,7 @@ pub struct TlsEntry<'tcx> { /// The data for this key. None is used to represent NULL. /// (We normalize this early to avoid having to do a NULL-ptr-test each time we access the data.) data: BTreeMap, - dtor: Option>, + dtor: Option<(ty::Instance<'tcx>, Span)>, } #[derive(Default, Debug)] @@ -38,7 +39,7 @@ pub struct TlsData<'tcx> { /// On macOS, each thread holds a list of destructor functions with their /// respective data arguments. - macos_thread_dtors: BTreeMap, Scalar)>>, + macos_thread_dtors: BTreeMap, Scalar, Span)>>, } impl<'tcx> Default for TlsData<'tcx> { @@ -57,7 +58,7 @@ impl<'tcx> TlsData<'tcx> { #[expect(clippy::arithmetic_side_effects)] pub fn create_tls_key( &mut self, - dtor: Option>, + dtor: Option<(ty::Instance<'tcx>, Span)>, max_size: Size, ) -> InterpResult<'tcx, TlsKey> { let new_key = self.next_key; @@ -126,8 +127,9 @@ impl<'tcx> TlsData<'tcx> { thread: ThreadId, dtor: ty::Instance<'tcx>, data: Scalar, + span: Span, ) -> InterpResult<'tcx> { - self.macos_thread_dtors.entry(thread).or_default().push((dtor, data)); + self.macos_thread_dtors.entry(thread).or_default().push((dtor, data, span)); interp_ok(()) } @@ -154,7 +156,7 @@ impl<'tcx> TlsData<'tcx> { &mut self, key: Option, thread_id: ThreadId, - ) -> Option<(ty::Instance<'tcx>, Scalar, TlsKey)> { + ) -> Option<(ty::Instance<'tcx>, Scalar, TlsKey, Span)> { use std::ops::Bound::*; let thread_local = &mut self.keys; @@ -172,11 +174,10 @@ impl<'tcx> TlsData<'tcx> { for (&key, TlsEntry { data, dtor }) in thread_local.range_mut((start, Unbounded)) { match data.entry(thread_id) { BTreeEntry::Occupied(entry) => { - if let Some(dtor) = dtor { + if let Some((dtor, span)) = dtor { // Set TLS data to NULL, and call dtor with old value. let data_scalar = entry.remove(); - let ret = Some((*dtor, data_scalar, key)); - return ret; + return Some((*dtor, data_scalar, key, *span)); } } BTreeEntry::Vacant(_) => {} @@ -205,7 +206,7 @@ impl VisitProvenance for TlsData<'_> { for scalar in keys.values().flat_map(|v| v.data.values()) { scalar.visit_provenance(visit); } - for (_, scalar) in macos_thread_dtors.values().flatten() { + for (_, scalar, _) in macos_thread_dtors.values().flatten() { scalar.visit_provenance(visit); } } @@ -222,7 +223,7 @@ enum TlsDtorsStatePriv<'tcx> { PthreadDtors(RunningDtorState), /// For Windows Dtors, we store the list of functions that we still have to call. /// These are functions from the magic `.CRT$XLB` linker section. - WindowsDtors(Vec>), + WindowsDtors(Vec<(ImmTy<'tcx>, Span)>), Done, } @@ -273,8 +274,8 @@ impl<'tcx> TlsDtorsState<'tcx> { } } WindowsDtors(dtors) => { - if let Some(dtor) = dtors.pop() { - this.schedule_windows_tls_dtor(dtor)?; + if let Some((dtor, span)) = dtors.pop() { + this.schedule_windows_tls_dtor(dtor, span)?; return interp_ok(Poll::Pending); // we stay in this state (but `dtors` got shorter) } else { // No more destructors to run. @@ -297,7 +298,7 @@ impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {} trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Schedule TLS destructors for Windows. /// On windows, TLS destructors are managed by std. - fn lookup_windows_tls_dtors(&mut self) -> InterpResult<'tcx, Vec>> { + fn lookup_windows_tls_dtors(&mut self) -> InterpResult<'tcx, Vec<(ImmTy<'tcx>, Span)>> { let this = self.eval_context_mut(); // Windows has a special magic linker section that is run on certain events. @@ -305,7 +306,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(this.lookup_link_section(|section| section == ".CRT$XLB")?) } - fn schedule_windows_tls_dtor(&mut self, dtor: ImmTy<'tcx>) -> InterpResult<'tcx> { + fn schedule_windows_tls_dtor(&mut self, dtor: ImmTy<'tcx>, span: Span) -> InterpResult<'tcx> { let this = self.eval_context_mut(); let dtor = dtor.to_scalar().to_pointer(this)?; @@ -320,12 +321,12 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // The signature of this function is `unsafe extern "system" fn(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID)`. // FIXME: `h` should be a handle to the current module and what `pv` should be is unknown // but both are ignored by std. - this.call_function( + this.call_thread_root_function( thread_callback, ExternAbi::System { unwind: false }, &[null_ptr.clone(), ImmTy::from_scalar(reason, this.machine.layouts.u32), null_ptr], None, - ReturnContinuation::Stop { cleanup: true }, + span, )?; interp_ok(()) } @@ -338,15 +339,15 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // registers another destructor, it will be run next. // See https://github.com/apple-oss-distributions/dyld/blob/d552c40cd1de105f0ec95008e0e0c0972de43456/dyld/DyldRuntimeState.cpp#L2277 let dtor = this.machine.tls.macos_thread_dtors.get_mut(&thread_id).and_then(Vec::pop); - if let Some((instance, data)) = dtor { + if let Some((instance, data, span)) = dtor { trace!("Running macos dtor {:?} on {:?} at {:?}", instance, data, thread_id); - this.call_function( + this.call_thread_root_function( instance, ExternAbi::C { unwind: false }, &[ImmTy::from_scalar(data, this.machine.layouts.mut_raw_ptr)], None, - ReturnContinuation::Stop { cleanup: true }, + span, )?; return interp_ok(Poll::Pending); @@ -370,7 +371,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // We ran each dtor once, start over from the beginning. None => this.machine.tls.fetch_tls_dtor(None, active_thread), }; - if let Some((instance, ptr, key)) = dtor { + if let Some((instance, ptr, key, span)) = dtor { state.last_key = Some(key); trace!("Running TLS dtor {:?} on {:?} at {:?}", instance, ptr, active_thread); assert!( @@ -378,12 +379,12 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "data can't be NULL when dtor is called!" ); - this.call_function( + this.call_thread_root_function( instance, ExternAbi::C { unwind: false }, &[ImmTy::from_scalar(ptr, this.machine.layouts.mut_raw_ptr)], None, - ReturnContinuation::Stop { cleanup: true }, + span, )?; return interp_ok(Poll::Pending); diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 114f1a321faaf..64b8376ff4aaa 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -235,33 +235,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { trace!("Called pwrite({:?}, {:?}, {:?}, {:?})", fd, buf, count, offset); this.write(fd, buf, count, Some(offset), dest)?; } - "pread64" => { - let [fd, buf, count, offset] = this.check_shim_sig( - shim_sig!(extern "C" fn(i32, *mut _, usize, libc::off64_t) -> isize), - link_name, - abi, - args, - )?; - let fd = this.read_scalar(fd)?.to_i32()?; - let buf = this.read_pointer(buf)?; - let count = this.read_target_usize(count)?; - let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?; - this.read(fd, buf, count, Some(offset), dest)?; - } - "pwrite64" => { - let [fd, buf, n, offset] = this.check_shim_sig( - shim_sig!(extern "C" fn(i32, *const _, usize, libc::off64_t) -> isize), - link_name, - abi, - args, - )?; - let fd = this.read_scalar(fd)?.to_i32()?; - let buf = this.read_pointer(buf)?; - let count = this.read_target_usize(n)?; - let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?; - trace!("Called pwrite64({:?}, {:?}, {:?}, {:?})", fd, buf, count, offset); - this.write(fd, buf, count, Some(offset), dest)?; - } "close" => { let [fd] = this.check_shim_sig( shim_sig!(extern "C" fn(i32) -> i32), @@ -317,7 +290,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } // File and file system access - "open" | "open64" => { + "open" => { // `open` is variadic, the third argument is only present when the second argument // has O_CREAT (or on linux O_TMPFILE, but miri doesn't support that) set let ([path_raw, flag], varargs) = @@ -345,6 +318,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.symlink(target, linkpath)?; this.write_scalar(result, dest)?; } + "fstat" => { + let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let result = this.fstat(fd, buf)?; + this.write_scalar(result, dest)?; + } "rename" => { let [oldpath, newpath] = this.check_shim_sig( shim_sig!(extern "C" fn(*const _, *const _) -> i32), @@ -395,18 +373,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.closedir(dirp)?; this.write_scalar(result, dest)?; } - "lseek64" => { - let [fd, offset, whence] = this.check_shim_sig( - shim_sig!(extern "C" fn(i32, libc::off64_t, i32) -> libc::off64_t), - link_name, - abi, - args, - )?; - let fd = this.read_scalar(fd)?.to_i32()?; - let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?; - let whence = this.read_scalar(whence)?.to_i32()?; - this.lseek64(fd, offset, whence, dest)?; - } "lseek" => { let [fd, offset, whence] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, libc::off_t, i32) -> libc::off_t), @@ -419,18 +385,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let whence = this.read_scalar(whence)?.to_i32()?; this.lseek64(fd, offset, whence, dest)?; } - "ftruncate64" => { - let [fd, length] = this.check_shim_sig( - shim_sig!(extern "C" fn(i32, libc::off64_t) -> i32), - link_name, - abi, - args, - )?; - let fd = this.read_scalar(fd)?.to_i32()?; - let length = this.read_scalar(length)?.to_int(length.layout.size)?; - let result = this.ftruncate64(fd, length)?; - this.write_scalar(result, dest)?; - } "ftruncate" => { let [fd, length] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, libc::off_t) -> i32), @@ -511,24 +465,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } - "posix_fallocate64" => { - // posix_fallocate64 is only supported on Linux and Android - this.check_target_os(&[Os::Linux, Os::Android], link_name)?; - let [fd, offset, len] = this.check_shim_sig( - shim_sig!(extern "C" fn(i32, libc::off64_t, libc::off64_t) -> i32), - link_name, - abi, - args, - )?; - - let fd = this.read_scalar(fd)?.to_i32()?; - let offset = this.read_scalar(offset)?.to_i64()?; - let len = this.read_scalar(len)?.to_i64()?; - - let result = this.posix_fallocate(fd, offset, len)?; - this.write_scalar(result, dest)?; - } - "realpath" => { let [path, resolved_path] = this.check_shim_sig( shim_sig!(extern "C" fn(*const _, *mut _) -> *mut _), @@ -698,7 +634,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Extract the function type out of the signature (that seems easier than constructing it ourselves). let dtor = if !this.ptr_is_null(dtor)? { - Some(this.get_ptr_fn(dtor)?.as_instance()?) + Some(( + this.get_ptr_fn(dtor)?.as_instance()?, + this.machine.current_user_relevant_span(), + )) } else { None }; diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs index 413df85ee3aae..c48301c724167 100644 --- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -148,15 +148,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.macos_fbsd_solarish_lstat(path, buf)?; this.write_scalar(result, dest)?; } - "fstat" | "fstat@FBSD_1.0" => { + "fstat@FBSD_1.0" => { let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.macos_fbsd_solarish_fstat(fd, buf)?; - this.write_scalar(result, dest)?; - } - "readdir_r" | "readdir_r@FBSD_1.0" => { - let [dirp, entry, result] = - this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.macos_fbsd_readdir_r(dirp, entry, result)?; + let result = this.fstat(fd, buf)?; this.write_scalar(result, dest)?; } "readdir" | "readdir@FBSD_1.0" => { diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 16214d7ef14ed..650972be5574e 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -118,7 +118,7 @@ impl UnixFileDescription for FileHandle { impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {} trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { - fn macos_fbsd_solarish_write_stat_buf( + fn write_stat_buf( &mut self, metadata: FileMetadata, buf_op: &OpTy<'tcx>, @@ -130,7 +130,11 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0)); let mode = metadata.mode.to_uint(this.libc_ty_layout("mode_t").size)?; - let buf = this.deref_pointer_as(buf_op, this.libc_ty_layout("stat"))?; + // We do *not* use `deref_pointer_as` here since determining the right pointee type + // is highly non-trivial: it depends on which exact alias of the function was invoked + // (e.g. `fstat` vs `fstat64`), and then on FreeBSD it also depends on the ABI level + // which can be different between the libc used by std and the libc used by everyone else. + let buf = this.deref_pointer(buf_op)?; this.write_int_fields_named( &[ ("st_dev", metadata.dev.into()), @@ -141,8 +145,11 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { ("st_gid", metadata.gid.into()), ("st_rdev", 0), ("st_atime", access_sec.into()), + ("st_atime_nsec", access_nsec.into()), ("st_mtime", modified_sec.into()), + ("st_mtime_nsec", modified_nsec.into()), ("st_ctime", 0), + ("st_ctime_nsec", 0), ("st_size", metadata.size.into()), ("st_blocks", 0), ("st_blksize", 0), @@ -153,9 +160,6 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) { this.write_int_fields_named( &[ - ("st_atime_nsec", access_nsec.into()), - ("st_mtime_nsec", modified_nsec.into()), - ("st_ctime_nsec", 0), ("st_birthtime", created_sec.into()), ("st_birthtime_nsec", created_nsec.into()), ("st_flags", 0), @@ -550,7 +554,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Err(err) => return this.set_last_error_and_return_i32(err), }; - interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?)) + interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?)) } // `lstat` is used to get symlink metadata. @@ -583,22 +587,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Err(err) => return this.set_last_error_and_return_i32(err), }; - interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?)) + interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?)) } - fn macos_fbsd_solarish_fstat( - &mut self, - fd_op: &OpTy<'tcx>, - buf_op: &OpTy<'tcx>, - ) -> InterpResult<'tcx, Scalar> { + fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos) - { - panic!( - "`macos_fbsd_solaris_fstat` should not be called on {}", - this.tcx.sess.target.os - ); + if !matches!( + &this.tcx.sess.target.os, + Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux + ) { + panic!("`fstat` should not be called on {}", this.tcx.sess.target.os); } let fd = this.read_scalar(fd_op)?.to_i32()?; @@ -614,7 +613,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Ok(metadata) => metadata, Err(err) => return this.set_last_error_and_return_i32(err), }; - interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?)) + interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?)) } fn linux_statx( @@ -1031,7 +1030,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(Scalar::from_maybe_pointer(entry.unwrap_or_else(Pointer::null), this)) } - fn macos_fbsd_readdir_r( + fn macos_readdir_r( &mut self, dirp_op: &OpTy<'tcx>, entry_op: &OpTy<'tcx>, @@ -1039,9 +1038,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) { - panic!("`macos_fbsd_readdir_r` should not be called on {}", this.tcx.sess.target.os); - } + this.assert_target_os(Os::MacOs, "readdir_r"); let dirp = this.read_target_usize(dirp_op)?; let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?; @@ -1097,39 +1094,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let file_type = this.file_type_to_d_type(dir_entry.file_type())?; - // Common fields. this.write_int_fields_named( &[ ("d_reclen", 0), ("d_namlen", file_name_len.into()), ("d_type", file_type.into()), + ("d_ino", ino.into()), + ("d_seekoff", 0), ], &entry_place, )?; - // Special fields. - match this.tcx.sess.target.os { - Os::MacOs => { - #[rustfmt::skip] - this.write_int_fields_named( - &[ - ("d_ino", ino.into()), - ("d_seekoff", 0), - ], - &entry_place, - )?; - } - Os::FreeBsd => { - #[rustfmt::skip] - this.write_int_fields_named( - &[ - ("d_fileno", ino.into()), - ("d_off", 0), - ], - &entry_place, - )?; - } - _ => unreachable!(), - } this.write_scalar(this.read_scalar(entry_op)?, &result_place)?; Scalar::from_i32(0) diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs index 79052698f4bad..a7cb2ed11b2b3 100644 --- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -36,6 +36,80 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match link_name.as_str() { // File related shims + "open64" => { + // `open64` is variadic, the third argument is only present when the second argument + // has O_CREAT (or on linux O_TMPFILE, but miri doesn't support that) set + let ([path_raw, flag], varargs) = + this.check_shim_sig_variadic_lenient(abi, CanonAbi::C, link_name, args)?; + let result = this.open(path_raw, flag, varargs)?; + this.write_scalar(result, dest)?; + } + "pread64" => { + let [fd, buf, count, offset] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *mut _, usize, libc::off64_t) -> isize), + link_name, + abi, + args, + )?; + let fd = this.read_scalar(fd)?.to_i32()?; + let buf = this.read_pointer(buf)?; + let count = this.read_target_usize(count)?; + let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?; + this.read(fd, buf, count, Some(offset), dest)?; + } + "pwrite64" => { + let [fd, buf, n, offset] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *const _, usize, libc::off64_t) -> isize), + link_name, + abi, + args, + )?; + let fd = this.read_scalar(fd)?.to_i32()?; + let buf = this.read_pointer(buf)?; + let count = this.read_target_usize(n)?; + let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?; + trace!("Called pwrite64({:?}, {:?}, {:?}, {:?})", fd, buf, count, offset); + this.write(fd, buf, count, Some(offset), dest)?; + } + "lseek64" => { + let [fd, offset, whence] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, libc::off64_t, i32) -> libc::off64_t), + link_name, + abi, + args, + )?; + let fd = this.read_scalar(fd)?.to_i32()?; + let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?; + let whence = this.read_scalar(whence)?.to_i32()?; + this.lseek64(fd, offset, whence, dest)?; + } + "ftruncate64" => { + let [fd, length] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, libc::off64_t) -> i32), + link_name, + abi, + args, + )?; + let fd = this.read_scalar(fd)?.to_i32()?; + let length = this.read_scalar(length)?.to_int(length.layout.size)?; + let result = this.ftruncate64(fd, length)?; + this.write_scalar(result, dest)?; + } + "posix_fallocate64" => { + let [fd, offset, len] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, libc::off64_t, libc::off64_t) -> i32), + link_name, + abi, + args, + )?; + + let fd = this.read_scalar(fd)?.to_i32()?; + let offset = this.read_scalar(offset)?.to_i64()?; + let len = this.read_scalar(len)?.to_i64()?; + + let result = this.posix_fallocate(fd, offset, len)?; + this.write_scalar(result, dest)?; + } "readdir64" => { let [dirp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.readdir64("dirent64", dirp)?; @@ -53,7 +127,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.linux_statx(dirfd, pathname, flags, mask, statxbuf)?; this.write_scalar(result, dest)?; } - // epoll, eventfd "epoll_create1" => { let [flag] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index 1b273593de63e..dd7b95bdc82be 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -46,19 +46,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.close(result)?; this.write_scalar(result, dest)?; } - "stat" | "stat64" | "stat$INODE64" => { + "stat" | "stat$INODE64" => { let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.macos_fbsd_solarish_stat(path, buf)?; this.write_scalar(result, dest)?; } - "lstat" | "lstat64" | "lstat$INODE64" => { + "lstat" | "lstat$INODE64" => { let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.macos_fbsd_solarish_lstat(path, buf)?; this.write_scalar(result, dest)?; } - "fstat" | "fstat64" | "fstat$INODE64" => { + "fstat$INODE64" => { let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.macos_fbsd_solarish_fstat(fd, buf)?; + let result = this.fstat(fd, buf)?; this.write_scalar(result, dest)?; } "opendir$INODE64" => { @@ -69,7 +69,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "readdir_r" | "readdir_r$INODE64" => { let [dirp, entry, result] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.macos_fbsd_readdir_r(dirp, entry, result)?; + let result = this.macos_readdir_r(dirp, entry, result)?; this.write_scalar(result, dest)?; } "realpath$DARWIN_EXTSN" => { @@ -158,7 +158,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let dtor = this.get_ptr_fn(dtor)?.as_instance()?; let data = this.read_scalar(data)?; let active_thread = this.active_thread(); - this.machine.tls.add_macos_thread_dtor(active_thread, dtor, data)?; + this.machine.tls.add_macos_thread_dtor( + active_thread, + dtor, + data, + this.machine.current_user_relevant_span(), + )?; } // Querying system information diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs index 6335e6bc9662c..ae7230877a71b 100644 --- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs @@ -90,21 +90,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } // File related shims - "stat" | "stat64" => { + "stat" => { let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.macos_fbsd_solarish_stat(path, buf)?; this.write_scalar(result, dest)?; } - "lstat" | "lstat64" => { + "lstat" => { let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.macos_fbsd_solarish_lstat(path, buf)?; this.write_scalar(result, dest)?; } - "fstat" | "fstat64" => { - let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.macos_fbsd_solarish_fstat(fd, buf)?; - this.write_scalar(result, dest)?; - } "readdir" => { let [dirp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.readdir64("dirent", dirp)?; diff --git a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_few_args.rs b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_few_args.rs index 520bc9572f865..6fec6500cc961 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_few_args.rs +++ b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_few_args.rs @@ -1,5 +1,4 @@ //@ignore-target: windows # No pthreads on Windows -//~^ERROR: calling a function with more arguments than it expected //! The thread function must have exactly one argument. @@ -17,6 +16,7 @@ fn main() { mem::transmute(thread_start); assert_eq!( libc::pthread_create(&mut native, ptr::null(), thread_start, ptr::null_mut()), + //~^ERROR: calling a function with more arguments than it expected 0 ); assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); diff --git a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_few_args.stderr b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_few_args.stderr index 4d5a80c828cd5..fef91e85470d9 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_few_args.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_few_args.stderr @@ -1,9 +1,13 @@ error: Undefined Behavior: calling a function with more arguments than it expected + --> tests/fail-dep/concurrency/libc_pthread_create_too_few_args.rs:LL:CC + | +LL | libc::pthread_create(&mut native, ptr::null(), thread_start, ptr::null_mut()), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred due to this code | - = note: Undefined Behavior occurred here - = note: (no span available) = 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: this error occurred while pushing a call frame onto an empty stack + = note: the span indicates which code caused the function to be called, but may not be the literal call site error: aborting due to 1 previous error diff --git a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_many_args.rs b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_many_args.rs index 92d8a765e5111..cb55b0f92ee6b 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_many_args.rs +++ b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_many_args.rs @@ -1,5 +1,4 @@ //@ignore-target: windows # No pthreads on Windows -//~^ERROR: calling a function with fewer arguments than it requires //! The thread function must have exactly one argument. @@ -17,6 +16,7 @@ fn main() { mem::transmute(thread_start); assert_eq!( libc::pthread_create(&mut native, ptr::null(), thread_start, ptr::null_mut()), + //~^ERROR: calling a function with fewer arguments than it requires 0 ); assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); diff --git a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_many_args.stderr b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_many_args.stderr index 1bc79411197c3..4d70576d784c1 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_many_args.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_create_too_many_args.stderr @@ -1,9 +1,13 @@ error: Undefined Behavior: calling a function with fewer arguments than it requires + --> tests/fail-dep/concurrency/libc_pthread_create_too_many_args.rs:LL:CC + | +LL | libc::pthread_create(&mut native, ptr::null(), thread_start, ptr::null_mut()), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred due to this code | - = note: Undefined Behavior occurred here - = note: (no span available) = 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: this error occurred while pushing a call frame onto an empty stack + = note: the span indicates which code caused the function to be called, but may not be the literal call site error: aborting due to 1 previous error diff --git a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_read_while_queued.rs b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_read_while_queued.rs index 555d765d24ba3..418259ca4e8ed 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_read_while_queued.rs +++ b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_read_while_queued.rs @@ -1,5 +1,5 @@ //@ignore-target: windows # No pthreads on Windows -//@compile-flags: -Zmiri-fixed-schedule +//@compile-flags: -Zmiri-deterministic-concurrency use std::cell::UnsafeCell; use std::sync::atomic::*; diff --git a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_write_while_queued.rs b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_write_while_queued.rs index 00274f7080f33..0778f06ff6fe1 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_write_while_queued.rs +++ b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_write_while_queued.rs @@ -1,5 +1,5 @@ //@ignore-target: windows # No pthreads on Windows -//@compile-flags: -Zmiri-fixed-schedule +//@compile-flags: -Zmiri-deterministic-concurrency use std::cell::UnsafeCell; use std::sync::atomic::*; diff --git a/src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.rs b/src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.rs new file mode 100644 index 0000000000000..b5eec2db201cb --- /dev/null +++ b/src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.rs @@ -0,0 +1,21 @@ +//@ignore-target: windows # No pthreads on Windows + +use std::{mem, ptr}; + +pub type Key = libc::pthread_key_t; + +pub unsafe fn create(dtor: unsafe fn(*mut u8)) -> Key { + let mut key = 0; + assert_eq!(libc::pthread_key_create(&mut key, mem::transmute(dtor)), 0); + //~^ERROR: calling a function with calling convention "Rust" + key +} + +unsafe fn dtor(_ptr: *mut u8) {} + +fn main() { + unsafe { + let key = create(dtor); + libc::pthread_setspecific(key, ptr::without_provenance(1)); + } +} diff --git a/src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.stderr b/src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.stderr new file mode 100644 index 0000000000000..32e92319cdf35 --- /dev/null +++ b/src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: calling a function with calling convention "Rust" using calling convention "C" + --> tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.rs:LL:CC + | +LL | assert_eq!(libc::pthread_key_create(&mut key, mem::transmute(dtor)), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred due to this code + | + = 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: this error occurred while pushing a call frame onto an empty stack + = note: the span indicates which code caused the function to be called, but may not be the literal call site + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed1.rs similarity index 70% rename from src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs rename to src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed1.rs index 76516b7d924be..8cd72b589b8e2 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs +++ b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed1.rs @@ -1,4 +1,6 @@ +//@revisions: stack tree //@compile-flags: -Zmiri-permissive-provenance +//@[tree]compile-flags: -Zmiri-tree-borrows fn main() { unsafe { @@ -12,6 +14,8 @@ fn main() { // And we test that it has uniqueness by doing a conflicting write. *exposed_ptr = 0; // Stack: Unknown( at ALLOC[0x0], but that tag does not exist in the borrow stack for this location - --> tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs:LL:CC + --> tests/fail/both_borrows/illegal_read_despite_exposed1.rs:LL:CC | LL | let _val = *root2; | ^^^^^^ this error occurs as part of an access at ALLOC[0x0..0x4] @@ -7,12 +7,12 @@ LL | let _val = *root2; = 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 help: was created by a Unique retag at offsets [0x0..0x4] - --> tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs:LL:CC + --> tests/fail/both_borrows/illegal_read_despite_exposed1.rs:LL:CC | LL | let root2 = &mut *exposed_ptr; | ^^^^^^^^^^^^^^^^^ help: was later invalidated at offsets [0x0..0x4] by a write access - --> tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs:LL:CC + --> tests/fail/both_borrows/illegal_read_despite_exposed1.rs:LL:CC | LL | *exposed_ptr = 0; | ^^^^^^^^^^^^^^^^ diff --git a/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed1.tree.stderr new file mode 100644 index 0000000000000..e6c9a142c3f2f --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed1.tree.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/both_borrows/illegal_read_despite_exposed1.rs:LL:CC + | +LL | let _val = *root2; + | ^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/both_borrows/illegal_read_despite_exposed1.rs:LL:CC + | +LL | let root2 = &mut *exposed_ptr; + | ^^^^^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/both_borrows/illegal_read_despite_exposed1.rs:LL:CC + | +LL | *exposed_ptr = 0; + | ^^^^^^^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + +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/stacked_borrows/illegal_read_despite_exposed2.rs b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed2.rs similarity index 65% rename from src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs rename to src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed2.rs index 97e0bf40c0dd3..9e8c94031ce8c 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs +++ b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed2.rs @@ -1,4 +1,6 @@ +//@revisions: stack tree //@compile-flags: -Zmiri-permissive-provenance +//@[tree]compile-flags: -Zmiri-tree-borrows fn main() { unsafe { @@ -7,6 +9,8 @@ fn main() { let exposed_ptr = addr as *mut i32; // From the exposed ptr, we get a new unique ptr. let root2 = &mut *exposed_ptr; + // Activate the reference (unnecessary on Stacked Borrows). + *root2 = 42; // let _fool = root2 as *mut _; // this would fool us, since SRW(N+1) remains on the stack // Stack: Unknown( at ALLOC[0x0], but that tag does not exist in the borrow stack for this location - --> tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs:LL:CC +error: Undefined Behavior: attempting a write access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> tests/fail/both_borrows/illegal_read_despite_exposed2.rs:LL:CC | -LL | let _val = *root2; - | ^^^^^^ this error occurs as part of an access at ALLOC[0x0..0x4] +LL | *root2 = 3; + | ^^^^^^^^^^ this error occurs as part of an access 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 help: was created by a Unique retag at offsets [0x0..0x4] - --> tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs:LL:CC + --> tests/fail/both_borrows/illegal_read_despite_exposed2.rs:LL:CC | LL | let root2 = &mut *exposed_ptr; | ^^^^^^^^^^^^^^^^^ help: was later invalidated at offsets [0x0..0x4] by a read access - --> tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs:LL:CC + --> tests/fail/both_borrows/illegal_read_despite_exposed2.rs:LL:CC | LL | let _val = *exposed_ptr; | ^^^^^^^^^^^^ diff --git a/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed2.tree.stderr new file mode 100644 index 0000000000000..17ae8bc513dab --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed2.tree.stderr @@ -0,0 +1,31 @@ +error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden + --> tests/fail/both_borrows/illegal_read_despite_exposed2.rs:LL:CC + | +LL | *root2 = 3; + | ^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Frozen which forbids this child write access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/both_borrows/illegal_read_despite_exposed2.rs:LL:CC + | +LL | let root2 = &mut *exposed_ptr; + | ^^^^^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Unique due to a child write access at offsets [0x0..0x4] + --> tests/fail/both_borrows/illegal_read_despite_exposed2.rs:LL:CC + | +LL | *root2 = 42; + | ^^^^^^^^^^^ + = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference +help: the accessed tag later transitioned to Frozen due to a foreign read access at offsets [0x0..0x4] + --> tests/fail/both_borrows/illegal_read_despite_exposed2.rs:LL:CC + | +LL | let _val = *exposed_ptr; + | ^^^^^^^^^^^^ + = help: this transition corresponds to a loss of write permissions + +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/stacked_borrows/illegal_write_despite_exposed1.rs b/src/tools/miri/tests/fail/both_borrows/illegal_write_despite_exposed1.rs similarity index 69% rename from src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs rename to src/tools/miri/tests/fail/both_borrows/illegal_write_despite_exposed1.rs index 0e34c5c98fc1f..a6c67f89980cf 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs +++ b/src/tools/miri/tests/fail/both_borrows/illegal_write_despite_exposed1.rs @@ -1,4 +1,6 @@ +//@revisions: stack tree //@compile-flags: -Zmiri-permissive-provenance +//@[tree]compile-flags: -Zmiri-tree-borrows fn main() { unsafe { @@ -12,6 +14,9 @@ fn main() { // (The write is still fine, using the `root as *mut i32` provenance which got exposed.) *exposed_ptr = 0; // Stack: Unknown( at ALLOC[0x0], but that tag does not exist in the borrow stack for this location - --> tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs:LL:CC + --> tests/fail/both_borrows/illegal_write_despite_exposed1.rs:LL:CC | LL | let _val = *root2; | ^^^^^^ this error occurs as part of an access at ALLOC[0x0..0x4] @@ -7,12 +7,12 @@ LL | let _val = *root2; = 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 help: was created by a SharedReadOnly retag at offsets [0x0..0x4] - --> tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs:LL:CC + --> tests/fail/both_borrows/illegal_write_despite_exposed1.rs:LL:CC | LL | let root2 = &*exposed_ptr; | ^^^^^^^^^^^^^ help: was later invalidated at offsets [0x0..0x4] by a write access - --> tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs:LL:CC + --> tests/fail/both_borrows/illegal_write_despite_exposed1.rs:LL:CC | LL | *exposed_ptr = 0; | ^^^^^^^^^^^^^^^^ diff --git a/src/tools/miri/tests/fail/both_borrows/illegal_write_despite_exposed1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_write_despite_exposed1.tree.stderr new file mode 100644 index 0000000000000..2f6f7eb5e9db0 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/illegal_write_despite_exposed1.tree.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/both_borrows/illegal_write_despite_exposed1.rs:LL:CC + | +LL | let _val = *root2; + | ^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Frozen + --> tests/fail/both_borrows/illegal_write_despite_exposed1.rs:LL:CC + | +LL | let root2 = &*exposed_ptr; + | ^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/both_borrows/illegal_write_despite_exposed1.rs:LL:CC + | +LL | *exposed_ptr = 0; + | ^^^^^^^^^^^^^^^^ + = help: this transition corresponds to a loss of read permissions + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.rs b/src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.rs index 1e10f682e71e6..3629e4387e7fd 100644 --- a/src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.rs +++ b/src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.rs @@ -1,5 +1,4 @@ unsafe extern "C" fn ctor() -> i32 { - //~^ERROR: calling a function with return type i32 passing return place of type () 0 } @@ -31,6 +30,7 @@ macro_rules! ctor { )] #[used] static $ident: unsafe extern "C" fn() -> i32 = $ctor; + //~^ERROR: calling a function with return type i32 passing return place of type () }; } diff --git a/src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.stderr b/src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.stderr index 68c56044be886..1b4cd23e41a8e 100644 --- a/src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.stderr +++ b/src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.stderr @@ -1,11 +1,19 @@ error: Undefined Behavior: calling a function with return type i32 passing return place of type () + --> tests/fail/shims/ctor_wrong_ret_type.rs:LL:CC + | +LL | static $ident: unsafe extern "C" fn() -> i32 = $ctor; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred due to this code +... +LL | ctor! { CTOR = ctor } + | --------------------- in this macro invocation | - = note: Undefined Behavior occurred here - = note: (no span available) = 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: this means these two types are not *guaranteed* to be ABI-compatible across all targets = help: if you think this code should be accepted anyway, please report an issue with Miri + = note: this error occurred while pushing a call frame onto an empty stack + = note: the span indicates which code caused the function to be called, but may not be the literal call site + = note: this error originates in the macro `ctor` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 1 previous error diff --git a/src/tools/miri/tests/fail/shims/macos_tlv_atexit_wrong_abi.rs b/src/tools/miri/tests/fail/shims/macos_tlv_atexit_wrong_abi.rs new file mode 100644 index 0000000000000..5d5cb749f896b --- /dev/null +++ b/src/tools/miri/tests/fail/shims/macos_tlv_atexit_wrong_abi.rs @@ -0,0 +1,18 @@ +//@only-target: darwin + +use std::{mem, ptr}; + +extern "C" { + fn _tlv_atexit(dtor: unsafe extern "C" fn(*mut u8), arg: *mut u8); +} + +fn register(dtor: unsafe fn(*mut u8)) { + unsafe { + _tlv_atexit(mem::transmute(dtor), ptr::null_mut()); + //~^ERROR: calling a function with calling convention "Rust" + } +} + +fn main() { + register(|_| ()); +} diff --git a/src/tools/miri/tests/fail/shims/macos_tlv_atexit_wrong_abi.stderr b/src/tools/miri/tests/fail/shims/macos_tlv_atexit_wrong_abi.stderr new file mode 100644 index 0000000000000..0fc87f13f16dd --- /dev/null +++ b/src/tools/miri/tests/fail/shims/macos_tlv_atexit_wrong_abi.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: calling a function with calling convention "Rust" using calling convention "C" + --> tests/fail/shims/macos_tlv_atexit_wrong_abi.rs:LL:CC + | +LL | _tlv_atexit(mem::transmute(dtor), ptr::null_mut()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred due to this code + | + = 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: this error occurred while pushing a call frame onto an empty stack + = note: the span indicates which code caused the function to be called, but may not be the literal call site + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_from_main.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_from_main.rs new file mode 100644 index 0000000000000..40099076f6279 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_from_main.rs @@ -0,0 +1,36 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This test checks the case where the access is to the main tree. +pub fn main() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let ref1 = unsafe { &mut *ptr_base }; + let ref2 = unsafe { &mut *ptr_base }; + + let int1 = ref1 as *mut u32 as usize; + let wild = int1 as *mut u32; + + let reb3 = unsafe { &mut *wild }; + + // ┌────────────┐ + // │ │ + // │ ptr_base ├───────────┐ * + // │ │ │ │ + // └──────┬─────┘ │ │ + // │ │ │ + // │ │ │ + // ▼ ▼ ▼ + // ┌────────────┐ ┌───────────┐ ┌───────────┐ + // │ │ │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res) │ │ reb3(Res) │ + // │ │ │ │ │ │ + // └────────────┘ └───────────┘ └───────────┘ + + // ref2 is part of the main tree and therefore foreign to all subtrees. + // Therefore, this disables reb3. + *ref2 = 13; + + let _fail = *reb3; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_from_main.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_from_main.stderr new file mode 100644 index 0000000000000..bdd75027c1f31 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_from_main.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_from_main.rs:LL:CC + | +LL | let _fail = *reb3; + | ^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/cross_tree_from_main.rs:LL:CC + | +LL | let reb3 = unsafe { &mut *wild }; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/cross_tree_from_main.rs:LL:CC + | +LL | *ref2 = 13; + | ^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + +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/tree_borrows/wildcard/cross_tree_update_main.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main.rs new file mode 100644 index 0000000000000..a21dcfe2acf5f --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main.rs @@ -0,0 +1,37 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This tests how main is effected by an access through a subtree. +pub fn main() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let ref1 = unsafe { &mut *ptr_base }; + let ref2 = unsafe { &mut *ptr_base }; + + let int1 = ref1 as *mut u32 as usize; + let wild = int1 as *mut u32; + + let reb = unsafe { &mut *wild }; + + // ┌────────────┐ + // │ │ + // │ ptr_base ├───────────┐ * + // │ │ │ │ + // └──────┬─────┘ │ │ + // │ │ │ + // │ │ │ + // ▼ ▼ ▼ + // ┌────────────┐ ┌───────────┐ ┌───────────┐ + // │ │ │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res) │ │ reb(Res) │ + // │ │ │ │ │ │ + // └────────────┘ └───────────┘ └───────────┘ + + // Writes through the reborrowed reference causing a wildcard + // write on the main tree. This disables ref2 as it doesn't + // have any exposed children. + *reb = 13; + + let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main.stderr new file mode 100644 index 0000000000000..285ffc4763df4 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_update_main.rs:LL:CC + | +LL | let _fail = *ref2; + | ^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/cross_tree_update_main.rs:LL:CC + | +LL | let ref2 = unsafe { &mut *ptr_base }; + | ^^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/cross_tree_update_main.rs:LL:CC + | +LL | *reb = 13; + | ^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + +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/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.rs new file mode 100644 index 0000000000000..641ffb4304d58 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.rs @@ -0,0 +1,44 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This test checks that an access from a subtree performs a +/// wildcard access on all earlier trees, and that local +/// accesses are treated as access errors for tags that are +/// larger than the root of the accessed subtree. +pub fn main() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let ref1 = unsafe { &mut *ptr_base }; + + // Activates ref1. + *ref1 = 4; + + let int1 = ref1 as *mut u32 as usize; + let wild = int1 as *mut u32; + + let ref2 = unsafe { &mut *wild }; + + // Freezes ref1. + let ref3 = unsafe { &mut *ptr_base }; + let _int3 = ref3 as *mut u32 as usize; + + // ┌──────────────┐ + // │ │ + // │ptr_base(Act) ├───────────┐ * + // │ │ │ │ + // └──────┬───────┘ │ │ + // │ │ │ + // │ │ │ + // ▼ ▼ ▼ + // ┌─────────────┐ ┌────────────┐ ┌───────────┐ + // │ │ │ │ │ │ + // │ ref1(Frz)* │ │ ref3(Res)* │ │ ref2(Res) │ + // │ │ │ │ │ │ + // └─────────────┘ └────────────┘ └───────────┘ + + // Performs a wildcard access on the main root. However, as there are + // no exposed tags with write permissions and a tag smaller than ref2 + // this access fails. + *ref2 = 13; //~ ERROR: /write access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.stderr new file mode 100644 index 0000000000000..3d91ec4db8820 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.stderr @@ -0,0 +1,14 @@ +error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.rs:LL:CC + | +LL | *ref2 = 13; + | ^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: there are no exposed tags which may perform this access here + +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/tree_borrows/wildcard/cross_tree_update_newer.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer.rs new file mode 100644 index 0000000000000..a65508c78b597 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer.rs @@ -0,0 +1,47 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This test checks that an access from an earlier created subtree +/// is foreign to a later created one. +pub fn main() { + let mut x: u32 = 42; + + let ref_base = &mut x; + + let int0 = ref_base as *mut u32 as usize; + let wild = int0 as *mut u32; + + let reb1 = unsafe { &mut *wild }; + + let reb2 = unsafe { &mut *wild }; + + let ref3 = &mut *reb1; + let _int3 = ref3 as *mut u32 as usize; + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * * + // │ │ │ │ + // └──────────────┘ │ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ reb1(Res) ├ │ reb2(Res) ├ + // │ │ │ │ + // └──────┬─────┘ └────────────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref3(Res)* │ + // │ │ + // └────────────┘ + + // This access disables reb2 because ref3 cannot be a child of it + // as reb2 both has a higher tag and doesn't have any exposed children. + *ref3 = 13; + + let _fail = *reb2; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer.stderr new file mode 100644 index 0000000000000..c1ab12f3405b9 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_update_newer.rs:LL:CC + | +LL | let _fail = *reb2; + | ^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/cross_tree_update_newer.rs:LL:CC + | +LL | let reb2 = unsafe { &mut *wild }; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/cross_tree_update_newer.rs:LL:CC + | +LL | *ref3 = 13; + | ^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + +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/tree_borrows/wildcard/cross_tree_update_newer_exposed.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.rs new file mode 100644 index 0000000000000..666eac1e7e618 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.rs @@ -0,0 +1,48 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This test checks that an access from an earlier created subtree +/// is foreign to a later created one. +pub fn main() { + let mut x: u32 = 42; + + let ref_base = &mut x; + + let int0 = ref_base as *mut u32 as usize; + let wild = int0 as *mut u32; + + let reb1 = unsafe { &mut *wild }; + + let reb2 = unsafe { &mut *wild }; + let _int2 = reb2 as *mut u32 as usize; + + let ref3 = &mut *reb1; + let _int3 = ref3 as *mut u32 as usize; + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * * + // │ │ │ │ + // └──────────────┘ │ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ reb1(Res) │ │ reb2(Res)* │ + // │ │ │ │ + // └──────┬─────┘ └────────────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref3(Res)* │ + // │ │ + // └────────────┘ + + // This access disables reb2 because ref3 cannot be a child of it + // as ref3's root has a lower tag than reb2. + *ref3 = 13; + + let _fail = *reb2; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.stderr new file mode 100644 index 0000000000000..e1232c7511929 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.rs:LL:CC + | +LL | let _fail = *reb2; + | ^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.rs:LL:CC + | +LL | let reb2 = unsafe { &mut *wild }; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.rs:LL:CC + | +LL | *ref3 = 13; + | ^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + +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/tree_borrows/wildcard/cross_tree_update_older.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older.rs new file mode 100644 index 0000000000000..35fbc30ff50bc --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older.rs @@ -0,0 +1,47 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This test checks that an access from a newer created subtree +/// performs a wildcard access on all earlier trees. +pub fn main() { + let mut x: u32 = 42; + + let ref_base = &mut x; + + let int0 = ref_base as *mut u32 as usize; + let wild = int0 as *mut u32; + + let reb1 = unsafe { &mut *wild }; + + let reb2 = unsafe { &mut *wild }; + + let ref3 = &mut *reb2; + let _int3 = ref3 as *mut u32 as usize; + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * * + // │ │ │ │ + // └──────────────┘ │ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ reb1(Res) ├ │ reb2(Res) ├ + // │ │ │ │ + // └────────────┘ └──────┬─────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref3(Res)* │ + // │ │ + // └────────────┘ + + // this access disables reb2 because ref3 cannot be a child of it + // as reb1 does not have any exposed children. + *ref3 = 13; + + let _fail = *reb1; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older.stderr new file mode 100644 index 0000000000000..54f041b555cbe --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_update_older.rs:LL:CC + | +LL | let _fail = *reb1; + | ^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/cross_tree_update_older.rs:LL:CC + | +LL | let reb1 = unsafe { &mut *wild }; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/cross_tree_update_older.rs:LL:CC + | +LL | *ref3 = 13; + | ^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + +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/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.rs new file mode 100644 index 0000000000000..0f87bb445fcdd --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.rs @@ -0,0 +1,53 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This test checks that an access from a newer created subtree +/// performs a wildcard access on all earlier trees, and that +/// either accesses are treated as foreign for tags that are +/// larger than the root of the accessed subtree. +pub fn main() { + let mut x: u32 = 42; + + let ref_base = &mut x; + + let int0 = ref_base as *mut u32 as usize; + let wild = int0 as *mut u32; + + let reb1 = unsafe { &mut *wild }; + + let reb2 = unsafe { &mut *wild }; + + let ref3 = &mut *reb1; + let _int3 = ref3 as *mut u32 as usize; + + let ref4 = &mut *reb2; + let _int4 = ref4 as *mut u32 as usize; + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * * + // │ │ │ │ + // └──────────────┘ │ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ reb1(Res) ├ │ reb2(Res) ├ + // │ │ │ │ + // └──────┬─────┘ └──────┬─────┘ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ ref3(Res)* │ │ ref4(Res)* │ + // │ │ │ │ + // └────────────┘ └────────────┘ + + // This access disables ref3 and reb1 because ref4 cannot be a child of it + // as reb2 has a smaller tag than ref3. + *ref4 = 13; + + // Fails because ref3 is disabled. + let _fail = *ref3; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.stderr new file mode 100644 index 0000000000000..66787ef72f144 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.rs:LL:CC + | +LL | let _fail = *ref3; + | ^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.rs:LL:CC + | +LL | let ref3 = &mut *reb1; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.rs:LL:CC + | +LL | *ref4 = 13; + | ^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + +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/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.rs new file mode 100644 index 0000000000000..f01ac26dc69c0 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.rs @@ -0,0 +1,59 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This test checks that an access from a newer created subtree +/// performs a wildcard access on all earlier trees, and that +/// either accesses are treated as foreign for tags that are +/// larger than the root of the accessed subtree. +/// This tests the special case where these updates get propagated +/// up the tree. +pub fn main() { + let mut x: u32 = 42; + + let ref_base = &mut x; + + let int0 = ref_base as *mut u32 as usize; + let wild = int0 as *mut u32; + + let reb1 = unsafe { &mut *wild }; + + let reb2 = unsafe { &mut *wild }; + + let ref3 = &mut *reb1; + let _int3 = ref3 as *mut u32 as usize; + + let ref4 = &mut *reb2; + let _int4 = ref4 as *mut u32 as usize; + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * * + // │ │ │ │ + // └──────────────┘ │ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ reb1(Res) ├ │ reb2(Res) ├ + // │ │ │ │ + // └──────┬─────┘ └──────┬─────┘ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ ref3(Res)* │ │ ref4(Res)* │ + // │ │ │ │ + // └────────────┘ └────────────┘ + + // This access disables ref3 and reb1 because ref4 cannot be a child of it + // as reb2 has a smaller tag than ref3. + // + // Because of the update order during a wildcard access (child before parent) + // ref3 gets disabled before we update reb1. So reb1 has no exposed children + // with write access at the time it gets updated so it also gets disabled. + *ref4 = 13; + + // Fails because reb1 is disabled. + let _fail = *reb1; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.stderr new file mode 100644 index 0000000000000..644d30cae0356 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.rs:LL:CC + | +LL | let _fail = *reb1; + | ^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.rs:LL:CC + | +LL | let reb1 = unsafe { &mut *wild }; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.rs:LL:CC + | +LL | *ref4 = 13; + | ^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + +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/tree_borrows/wildcard/protected_wildcard.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.rs new file mode 100644 index 0000000000000..18fe931b9e356 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.rs @@ -0,0 +1,39 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks if we pass a reference derived from a wildcard pointer +/// that it gets correctly protected. +pub fn main() { + let mut x: u32 = 32; + let ref1 = &mut x; + + let ref2 = &mut *ref1; + let int2 = ref2 as *mut u32 as usize; + + let wild = int2 as *mut u32; + let wild_ref = unsafe { &mut *wild }; + + let mut protect = |_arg: &mut u32| { + // _arg is a protected pointer with wildcard parent. + + // ┌────────────┐ + // │ │ + // │ ref1(Res) │ * + // │ │ │ + // └──────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ ref2(Res)* │ │ _arg(Res) │ + // │ │ │ │ + // └────────────┘ └────────────┘ + + // Writes to ref1, causing a foreign write to ref2 and _arg. + // Since _arg is protected this is UB. + *ref1 = 13; //~ ERROR: /write access through .* is forbidden/ + }; + + // We pass a pointer with wildcard provenance to the function. + protect(wild_ref); +} diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.stderr new file mode 100644 index 0000000000000..e257a3511f75e --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.stderr @@ -0,0 +1,37 @@ +error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/protected_wildcard.rs:LL:CC + | +LL | *ref1 = 13; + | ^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag is foreign to the protected tag (i.e., it is not a child) + = help: this foreign write access would cause the protected tag (currently Reserved) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> tests/fail/tree_borrows/wildcard/protected_wildcard.rs:LL:CC + | +LL | let mut protect = |_arg: &mut u32| { + | _______________________^ +... | +LL | | *ref1 = 13; +LL | | }; + | |_____^ +help: the protected tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/protected_wildcard.rs:LL:CC + | +LL | let mut protect = |_arg: &mut u32| { + | ^^^^ + = note: BACKTRACE (of the first span): + = note: inside closure at tests/fail/tree_borrows/wildcard/protected_wildcard.rs:LL:CC +note: inside `main` + --> tests/fail/tree_borrows/wildcard/protected_wildcard.rs:LL:CC + | +LL | protect(wild_ref); + | ^^^^^^^^^^^^^^^^^ + +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/tree_borrows/wildcard/strongly_protected_wildcard.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr index de92cf91e3e85..6e115b22feb11 100644 --- a/src/tools/miri/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr @@ -1,4 +1,4 @@ -error: Undefined Behavior: deallocation through at ALLOC[0x0] is forbidden +error: Undefined Behavior: deallocation through at ALLOC[0x0] is forbidden --> RUSTLIB/alloc/src/boxed.rs:LL:CC | LL | self.1.deallocate(From::from(ptr.cast()), layout); @@ -6,8 +6,13 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout); | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information - = help: the allocation of the accessed tag also contains the strongly protected tag + = help: the allocation of the accessed tag also contains the strongly protected tag = help: the strongly protected tag disallows deallocations +help: the accessed tag was created here + --> tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC + | +LL | drop(unsafe { Box::from_raw(raw as *mut i32) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: the strongly protected tag was created here, in the initial state Reserved --> tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC | diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.rs new file mode 100644 index 0000000000000..7ce243abcefc7 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.rs @@ -0,0 +1,44 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +// Checks if we correctly infer the relatedness of nodes that are +// part of the same wildcard root. +pub fn main() { + let mut x: u32 = 42; + + let ref_base = &mut x; + + let int1 = ref_base as *mut u32 as usize; + let wild = int1 as *mut u32; + + let reb = unsafe { &mut *wild }; + let ptr_reb = reb as *mut u32; + let ref1 = unsafe { &mut *ptr_reb }; + let ref2 = unsafe { &mut *ptr_reb }; + + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * + // │ │ │ + // └──────────────┘ │ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ reb(Res) ├───────────┐ + // │ │ │ + // └──────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌───────────┐ + // │ │ │ │ + // │ ref1(Res) │ │ ref2(Res) │ + // │ │ │ │ + // └────────────┘ └───────────┘ + + // ref1 is foreign to ref2, so this should disable ref2. + *ref1 = 13; + + let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.stderr new file mode 100644 index 0000000000000..4b4f73a73b79d --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.rs:LL:CC + | +LL | let _fail = *ref2; + | ^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.rs:LL:CC + | +LL | let ref2 = unsafe { &mut *ptr_reb }; + | ^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.rs:LL:CC + | +LL | *ref1 = 13; + | ^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + +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/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.rs new file mode 100644 index 0000000000000..7b115f788ae66 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.rs @@ -0,0 +1,49 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +// Checks if we correctly infer the relatedness of nodes that are +// part of the same wildcard root during a wildcard access. +pub fn main() { + let mut x: u32 = 42; + + let ref_base = &mut x; + + let int = ref_base as *mut u32 as usize; + let wild = int as *mut u32; + + let reb = unsafe { &mut *wild }; + let ptr_reb = reb as *mut u32; + let ref1 = unsafe { &mut *ptr_reb }; + let _int1 = ref1 as *mut u32 as usize; + let ref2 = unsafe { &mut *ptr_reb }; + + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * + // │ │ │ + // └──────────────┘ │ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ reb(Res) ├───────────┐ + // │ │ │ + // └──────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌───────────┐ + // │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res) │ + // │ │ │ │ + // └────────────┘ └───────────┘ + + // Writes either through ref1 or ptr_base. + // This disables ref2 as the access is foreign to it in either case. + unsafe { *wild = 13 }; + + // This is fine because the earlier write could have come from ref1. + let _succ = *ref1; + // ref2 is disabled so this fails. + let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.stderr new file mode 100644 index 0000000000000..e1a8bebe91c93 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.rs:LL:CC + | +LL | let _fail = *ref2; + | ^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.rs:LL:CC + | +LL | let ref2 = unsafe { &mut *ptr_reb }; + | ^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.rs:LL:CC + | +LL | unsafe { *wild = 13 }; + | ^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + +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/tree_borrows/write_to_shr.stderr b/src/tools/miri/tests/fail/tree_borrows/write_to_shr.stderr deleted file mode 100644 index f57b28cbf44c6..0000000000000 --- a/src/tools/miri/tests/fail/tree_borrows/write_to_shr.stderr +++ /dev/null @@ -1,26 +0,0 @@ -error: Undefined Behavior: write access through is forbidden - --> $DIR/write_to_shr.rs:LL:CC - | -LL | *xmut = 31; - | ^^^^^^^^^^ write access through is forbidden - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental - = help: the accessed tag is a child of the conflicting tag - = help: the conflicting tag has state Frozen which forbids child write accesses -help: the accessed tag was created here - --> $DIR/write_to_shr.rs:LL:CC - | -LL | let xmut = unsafe { &mut *(xref as *const u64 as *mut u64) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: the conflicting tag was created here, in the initial state Frozen - --> $DIR/write_to_shr.rs:LL:CC - | -LL | let xref = unsafe { &*(x as *mut u64) }; - | ^^^^^^^^^^^^^^^^^ - = note: BACKTRACE (of the first span): - = note: inside `main` at $DIR/write_to_shr.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/genmc/fail/shims/mutex_diff_thread_unlock.stderr b/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr index e74b76ea415e6..e2148bedd3187 100644 --- 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 @@ -18,7 +18,7 @@ LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquir = 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 at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC note: inside `miri_start` --> tests/genmc/fail/shims/mutex_diff_thread_unlock.rs:LL:CC | @@ -48,7 +48,7 @@ LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquir = 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 at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC note: inside `miri_start` --> tests/genmc/fail/shims/mutex_diff_thread_unlock.rs:LL:CC | 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 index 695aa3f5aeaf8..fdbb9eff2faa6 100644 --- a/src/tools/miri/tests/genmc/pass/std/arc.check_count.stderr +++ b/src/tools/miri/tests/genmc/pass/std/arc.check_count.stderr @@ -1,6 +1,6 @@ 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/std/src/thread/mod.rs:LL:CC + --> RUSTLIB/std/src/thread/id.rs:LL:CC | LL | match COUNTER.compare_exchange_weak(last, id, Ordering::Relaxed, Ordering::Relaxed) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code @@ -46,7 +46,7 @@ LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquir | = note: BACKTRACE: = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC - = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC note: inside `main` --> tests/genmc/pass/std/arc.rs:LL:CC | @@ -67,7 +67,7 @@ LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquir | = note: BACKTRACE: = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC - = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC note: inside `main` --> tests/genmc/pass/std/arc.rs:LL:CC | 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 index 8609c2c5c8645..a5423f9a398bb 100644 --- a/src/tools/miri/tests/genmc/pass/std/arc.try_upgrade.stderr +++ b/src/tools/miri/tests/genmc/pass/std/arc.try_upgrade.stderr @@ -1,6 +1,6 @@ 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/std/src/thread/mod.rs:LL:CC + --> RUSTLIB/std/src/thread/id.rs:LL:CC | LL | match COUNTER.compare_exchange_weak(last, id, Ordering::Relaxed, Ordering::Relaxed) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code @@ -46,7 +46,7 @@ LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquir | = note: BACKTRACE: = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC - = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC note: inside `main` --> tests/genmc/pass/std/arc.rs:LL:CC | @@ -67,7 +67,7 @@ LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquir | = note: BACKTRACE: = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC - = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC note: inside `main` --> tests/genmc/pass/std/arc.rs:LL:CC | diff --git a/src/tools/miri/tests/genmc/pass/std/empty_main.stderr b/src/tools/miri/tests/genmc/pass/std/empty_main.stderr index b1413ee6d574b..de07943b0d8d1 100644 --- a/src/tools/miri/tests/genmc/pass/std/empty_main.stderr +++ b/src/tools/miri/tests/genmc/pass/std/empty_main.stderr @@ -1,6 +1,6 @@ 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/std/src/thread/mod.rs:LL:CC + --> RUSTLIB/std/src/thread/id.rs:LL:CC | LL | match COUNTER.compare_exchange_weak(last, id, Ordering::Relaxed, Ordering::Relaxed) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code 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 index 87d3c60ef02e7..701934a7cd7f1 100644 --- a/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.stderr +++ b/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.stderr @@ -1,6 +1,6 @@ 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/std/src/thread/mod.rs:LL:CC + --> RUSTLIB/std/src/thread/id.rs:LL:CC | LL | match COUNTER.compare_exchange_weak(last, id, Ordering::Relaxed, Ordering::Relaxed) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code @@ -20,7 +20,7 @@ LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquir | = note: BACKTRACE: = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC - = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC note: inside closure --> tests/genmc/pass/std/spawn_std_threads.rs:LL:CC | @@ -52,7 +52,7 @@ LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquir | = note: BACKTRACE: = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC - = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC note: inside closure --> tests/genmc/pass/std/spawn_std_threads.rs:LL:CC | diff --git a/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr b/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr index 91d2f5daa146f..fd6538fd70faa 100644 --- a/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr +++ b/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr @@ -1,6 +1,6 @@ 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/std/src/thread/mod.rs:LL:CC + --> RUSTLIB/std/src/thread/id.rs:LL:CC | LL | match COUNTER.compare_exchange_weak(last, id, Ordering::Relaxed, Ordering::Relaxed) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code @@ -20,7 +20,7 @@ LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquir | = note: BACKTRACE: = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC - = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC note: inside `main` --> tests/genmc/pass/std/thread_locals.rs:LL:CC | @@ -42,7 +42,7 @@ LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquir | = note: BACKTRACE: = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC - = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC note: inside `main` --> tests/genmc/pass/std/thread_locals.rs:LL:CC | diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index 41c3e3a122464..99685d6d976b6 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -38,10 +38,11 @@ fn main() { test_posix_fadvise(); #[cfg(not(target_os = "macos"))] test_posix_fallocate::(libc::posix_fallocate); - #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg(target_os = "linux")] test_posix_fallocate::(libc::posix_fallocate64); #[cfg(target_os = "linux")] test_sync_file_range(); + test_fstat(); test_isatty(); test_read_and_uninit(); test_nofollow_not_symlink(); @@ -452,6 +453,41 @@ fn test_sync_file_range() { assert_eq!(result_2, 0); } +fn test_fstat() { + use std::mem::MaybeUninit; + use std::os::unix::io::AsRawFd; + + let path = utils::prepare_with_content("miri_test_libc_fstat.txt", b"hello"); + let file = File::open(&path).unwrap(); + let fd = file.as_raw_fd(); + + let mut stat = MaybeUninit::::uninit(); + let res = unsafe { libc::fstat(fd, stat.as_mut_ptr()) }; + assert_eq!(res, 0); + let stat = unsafe { stat.assume_init_ref() }; + + assert_eq!(stat.st_size, 5); + assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFREG); + + // Check that all fields are initialized. + let _st_nlink = stat.st_nlink; + let _st_blksize = stat.st_blksize; + let _st_blocks = stat.st_blocks; + let _st_ino = stat.st_ino; + let _st_dev = stat.st_dev; + let _st_uid = stat.st_uid; + let _st_gid = stat.st_gid; + let _st_rdev = stat.st_rdev; + let _st_atime = stat.st_atime; + let _st_mtime = stat.st_mtime; + let _st_ctime = stat.st_ctime; + let _st_atime_nsec = stat.st_atime_nsec; + let _st_mtime_nsec = stat.st_mtime_nsec; + let _st_ctime_nsec = stat.st_ctime_nsec; + + remove_file(&path).unwrap(); +} + fn test_isatty() { // Testing whether our isatty shim returns the right value would require controlling whether // these streams are actually TTYs, which is hard. diff --git a/src/tools/miri/tests/pass/tree_borrows/wildcard/formatting.rs b/src/tools/miri/tests/pass/tree_borrows/wildcard/formatting.rs new file mode 100644 index 0000000000000..1f6b797a3d776 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/wildcard/formatting.rs @@ -0,0 +1,36 @@ +// We disable the GC for this test because it would change what is printed. +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance -Zmiri-provenance-gc=0 + +#[path = "../../../utils/mod.rs"] +#[macro_use] +mod utils; + +fn main() { + unsafe { + let x = &0u8; + name!(x); + let xa = &*x; + name!(xa); + let xb = &*x; + name!(xb); + let wild = xb as *const u8 as usize as *const u8; + + let y = &*wild; + name!(y); + let ya = &*y; + name!(ya); + let yb = &*y; + name!(yb); + let _int = ya as *const u8 as usize; + + let z = &*wild; + name!(z); + + let u = &*wild; + name!(u); + let ua = &*u; + name!(ua); + let alloc_id = alloc_id!(x); + print_state!(alloc_id); + } +} diff --git a/src/tools/miri/tests/pass/tree_borrows/wildcard/formatting.stderr b/src/tools/miri/tests/pass/tree_borrows/wildcard/formatting.stderr new file mode 100644 index 0000000000000..583c84534395c --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/wildcard/formatting.stderr @@ -0,0 +1,17 @@ +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 1 +| Act | └─┬── +| Frz | └─┬── +| Frz | ├──── +| Frz | └──── (exposed) +| | +| Frz | *─┬── +| Frz | ├──── (exposed) +| Frz | └──── +| | +| Frz | *──── +| | +| Frz | *─┬── +| Frz | └──── +────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/wildcard/reborrow.rs b/src/tools/miri/tests/pass/tree_borrows/wildcard/reborrow.rs new file mode 100644 index 0000000000000..fbb50d5acd27a --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/wildcard/reborrow.rs @@ -0,0 +1,224 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +pub fn main() { + multiple_exposed_siblings1(); + multiple_exposed_siblings2(); + reborrow3(); + returned_mut_is_usable(); + only_foreign_is_temporary(); +} + +/// Checks that accessing through a reborrowed wildcard doesn't +/// disable any exposed reference. +fn multiple_exposed_siblings1() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + + let ref1 = unsafe { &mut *ptr_base }; + let int1 = ref1 as *mut u32 as usize; + + let ref2 = unsafe { &mut *ptr_base }; + let _int2 = ref2 as *mut u32 as usize; + + let wild = int1 as *mut u32; + + let reb = unsafe { &mut *wild }; + + // ┌────────────┐ + // │ │ + // │ ptr_base ├────────────┐ * + // │ │ │ │ + // └──────┬─────┘ │ │ + // │ │ │ + // │ │ │ + // ▼ ▼ ▼ + // ┌────────────┐ ┌────────────┐ ┌────────────┐ + // │ │ │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res)* │ │ reb(Res) │ + // │ │ │ │ │ │ + // └────────────┘ └────────────┘ └────────────┘ + + // Could either have as a parent ref1 or ref2. + // So we can't disable either of them. + *reb = 13; + + // We can still access either ref1 or ref2. + // Although it is actually UB to access both of them. + assert_eq!(*ref2, 13); + assert_eq!(*ref1, 13); +} + +/// Checks that wildcard accesses do not invalidate any exposed +/// nodes through which the access could have happened. +/// It checks this for the case where some reborrowed wildcard +/// pointers are exposed as well. +fn multiple_exposed_siblings2() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let int = ptr_base as usize; + + let wild = int as *mut u32; + + let reb_ptr = unsafe { &mut *wild } as *mut u32; + + let ref1 = unsafe { &mut *reb_ptr }; + let _int1 = ref1 as *mut u32 as usize; + + let ref2 = unsafe { &mut *reb_ptr }; + let _int2 = ref2 as *mut u32 as usize; + + // ┌────────────┐ + // │ │ + // │ ptr_base* │ * + // │ │ │ + // └────────────┘ │ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ reb ├────────────┐ + // │ │ │ + // └──────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res)* │ + // │ │ │ │ + // └────────────┘ └────────────┘ + + // Writes either through ref1, ref2 or ptr_base, which are all exposed. + // Since we don't know which we do not apply any transitions to any of + // the references. + unsafe { wild.write(13) }; + + // We should be able to access either ref1 or ref2. + // Although it is actually UB to access ref1 and ref2 together. + assert_eq!(*ref2, 13); + assert_eq!(*ref1, 13); +} + +/// Checks that accessing a reborrowed wildcard reference doesn't +/// invalidate other reborrowed wildcard references, if they +/// are also exposed. +fn reborrow3() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let int = ptr_base as usize; + + let wild = int as *mut u32; + + let reb1 = unsafe { &mut *wild }; + let ref2 = &mut *reb1; + let _int = ref2 as *mut u32 as usize; + + let reb3 = unsafe { &mut *wild }; + + // ┌────────────┐ + // │ │ + // │ ptr_base* │ * * + // │ │ │ │ + // └────────────┘ │ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ reb1(Res) | │ reb3(Res) | + // │ │ │ │ + // └──────┬─────┘ └────────────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref2(Res)* │ + // │ │ + // └────────────┘ + + // This is the only valid ordering these accesses can happen in. + + // reb3 could be a child of ref2 so we don't disable ref2, reb1. + *reb3 = 1; + // Disables reb3 as it cannot be an ancestor of ref2. + *ref2 = 2; + // Disables ref2 (and reb3 if it wasn't already). + *reb1 = 3; +} + +/// Analogous to same test in `../tree-borrows.rs` but with returning a +/// reborrowed wildcard reference. +fn returned_mut_is_usable() { + let mut x: u32 = 32; + let ref1 = &mut x; + + let y = protect(ref1); + + fn protect(arg: &mut u32) -> &mut u32 { + // Reborrow `arg` through a wildcard. + let int = arg as *mut u32 as usize; + let wild = int as *mut u32; + let ref2 = unsafe { &mut *wild }; + + // Activate the reference so that it is vulnerable to foreign reads. + *ref2 = 42; + + ref2 + // An implicit read through `arg` is inserted here. + } + + *y = 4; +} + +/// When accessing an allocation through a tag that was created from wildcard reference +/// we treat nodes with a larger tag as if the access could only have been foreign to them. +/// This change in access relatedness should not be visible in later accesses. +fn only_foreign_is_temporary() { + let mut x = 0u32; + let wild = &mut x as *mut u32 as usize as *mut u32; + + let reb1 = unsafe { &mut *wild }; + let reb2 = unsafe { &mut *wild }; + let ref3 = &mut *reb1; + let _int = ref3 as *mut u32 as usize; + + let reb4 = unsafe { &mut *wild }; + + // + // + // * * * + // │ │ │ + // │ │ │ + // │ │ │ + // │ │ │ + // ▼ ▼ ▼ + // ┌────────────┐ ┌────────────┐ ┌────────────┐ + // │ │ │ │ │ │ + // │ reb1(Res) │ │ reb2(Res) │ │ reb4(Res) │ + // │ │ │ │ │ │ + // └──────┬─────┘ └────────────┘ └────────────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref3(Res)* │ + // │ │ + // └────────────┘ + + // Performs a foreign read on ref3 and doesn't update reb1. + // This temporarily treats ref3 as if only foreign accesses are possible to + // it. This is because the accessed tag reb2 has a larger tag than ref3. + let _x = *reb2; + // Should not update ref3, reb1 as we don't know if the access is local or foreign. + // This should stop treating ref3 as only foreign because the accessed tag reb4 + // has a larger tag than ref3. + *reb4 = 32; + // The previous write could have been local to ref3, so this access should still work. + *ref3 = 4; +} diff --git a/src/tools/miri/tests/pass/tree_borrows/wildcard/undetected_ub.rs b/src/tools/miri/tests/pass/tree_borrows/wildcard/undetected_ub.rs index c34fbcb50119b..73392543f7d2a 100644 --- a/src/tools/miri/tests/pass/tree_borrows/wildcard/undetected_ub.rs +++ b/src/tools/miri/tests/pass/tree_borrows/wildcard/undetected_ub.rs @@ -4,7 +4,7 @@ pub fn main() { uncertain_provenance(); protected_exposed(); - protected_wildcard(); + cross_tree_update_older_invalid_exposed(); } /// Currently, if we do not know for a tag if an access is local or foreign, @@ -101,45 +101,61 @@ pub fn protected_exposed() { } protect(ref1); - // ref2 is disabled, so this read causes UB, but we currently don't protect this. + // ref2 should be disabled, so this read causes UB, but we currently don't detect this. let _fail = *ref2; } -/// Currently, we do not assign protectors to wildcard references. -/// This test has UB because it does a foreign write to a protected reference. -/// However, that reference is a wildcard, so this doesn't get detected. -#[allow(unused_variables)] -pub fn protected_wildcard() { - let mut x: u32 = 32; - let ref1 = &mut x; - let ref2 = &mut *ref1; - - let int = ref2 as *mut u32 as usize; - let wild = int as *mut u32; - let wild_ref = unsafe { &mut *wild }; - - let mut protect = |arg: &mut u32| { - // arg is a protected pointer with wildcard provenance. - - // ┌────────────┐ - // │ │ - // │ ref1(Res) │ - // │ │ - // └──────┬─────┘ - // │ - // │ - // ▼ - // ┌────────────┐ - // │ │ - // │ ref2(Res)* │ - // │ │ - // └────────────┘ - - // Writes to ref1, disabling ref2, i.e. disabling all exposed references. - // Since a wildcard reference is protected, this is UB. But we currently don't detect this. - *ref1 = 13; - }; - - // We pass a pointer with wildcard provenance to the function. - protect(wild_ref); +/// Checks how accesses from one subtree affect other subtrees. +/// This test shows an example where we don't update a node whose exposed +/// children are greater than `max_local_tag`. +pub fn cross_tree_update_older_invalid_exposed() { + let mut x: [u32; 2] = [42, 43]; + + let ref_base = &mut x; + + let int0 = ref_base as *mut u32 as usize; + let wild = int0 as *mut u32; + + let reb1 = unsafe { &mut *wild }; + *reb1 = 44; + + // We need this reference to be at a different location, + // so that creating it doesn't freeze reb1. + let reb2 = unsafe { &mut *wild.wrapping_add(1) }; + let reb2_ptr = (reb2 as *mut u32).wrapping_sub(1); + + let ref3 = &mut *reb1; + let _int3 = ref3 as *mut u32 as usize; + + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * * + // │ │ │ │ + // └──────────────┘ │ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ reb1(Act) │ │ reb2(Res) │ + // │ │ │ │ + // └──────┬─────┘ └────────────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref3(Res)* │ + // │ │ + // └────────────┘ + + // This access doesn't freeze reb1 even though no access could have come from its + // child ref3 (since ref3>reb2). This is because ref3 doesnt get disabled during this + // access. + // + // ref3 doesn't get frozen because it's still reserved. + let _y = unsafe { *reb2_ptr }; + + // reb1 should be frozen so a write should be UB. But we currently don't detect this. + *reb1 = 4; } diff --git a/src/tools/miri/tests/pass/tree_borrows/wildcard/wildcard.rs b/src/tools/miri/tests/pass/tree_borrows/wildcard/wildcard.rs index c406bfe01f62c..01385313dc1ed 100644 --- a/src/tools/miri/tests/pass/tree_borrows/wildcard/wildcard.rs +++ b/src/tools/miri/tests/pass/tree_borrows/wildcard/wildcard.rs @@ -151,7 +151,6 @@ fn protector_conflicted_release() { /// Analogous to same test in `../tree-borrows.rs` but with a protected wildcard reference. fn returned_mut_is_usable() { - // NOTE: Currently we ignore protectors on wildcard references. fn reborrow(x: &mut u8) -> &mut u8 { let y = &mut *x; // Activate the reference so that it is vulnerable to foreign reads. diff --git a/src/tools/miri/tests/utils/macros.rs b/src/tools/miri/tests/utils/macros.rs index 3f5b9f78ee01e..25f40ed994f68 100644 --- a/src/tools/miri/tests/utils/macros.rs +++ b/src/tools/miri/tests/utils/macros.rs @@ -9,7 +9,7 @@ /// The id obtained can be passed directly to `print_state!`. macro_rules! alloc_id { ($ptr:expr) => { - $crate::utils::miri_get_alloc_id($ptr as *const u8 as *const ()) + $crate::utils::miri_get_alloc_id($ptr as *const _ as *const ()) }; } @@ -52,6 +52,6 @@ macro_rules! name { }; ($ptr:expr => $nth_parent:expr, $name:expr) => { let name = $name.as_bytes(); - $crate::utils::miri_pointer_name($ptr as *const u8 as *const (), $nth_parent, name); + $crate::utils::miri_pointer_name($ptr as *const _ as *const (), $nth_parent, name); }; } diff --git a/tests/run-make/rustdoc-merge-directory-alias/dep1.rs b/tests/run-make/rustdoc-merge-directory-alias/dep1.rs new file mode 100644 index 0000000000000..b62f31c982b7f --- /dev/null +++ b/tests/run-make/rustdoc-merge-directory-alias/dep1.rs @@ -0,0 +1,10 @@ +pub struct Dep1; +pub struct Dep2; +pub struct Dep3; +pub struct Dep4; + +//@ hasraw crates.js 'dep1' +//@ hasraw search.index/name/*.js 'Dep1' +//@ has dep1/index.html +#[doc(alias = "dep1_missing")] +pub struct Dep5; diff --git a/tests/run-make/rustdoc-merge-directory-alias/dep2.rs b/tests/run-make/rustdoc-merge-directory-alias/dep2.rs new file mode 100644 index 0000000000000..2df4452413c21 --- /dev/null +++ b/tests/run-make/rustdoc-merge-directory-alias/dep2.rs @@ -0,0 +1,4 @@ +//@ hasraw crates.js 'dep2' +//@ hasraw search.index/name/*.js 'Second' +//@ has dep2/index.html +pub struct Second; diff --git a/tests/run-make/rustdoc-merge-directory-alias/dep_missing.rs b/tests/run-make/rustdoc-merge-directory-alias/dep_missing.rs new file mode 100644 index 0000000000000..74236aef47ea5 --- /dev/null +++ b/tests/run-make/rustdoc-merge-directory-alias/dep_missing.rs @@ -0,0 +1,4 @@ +//@ !hasraw crates.js 'dep_missing' +//@ !hasraw search.index/name/*.js 'DepMissing' +//@ has dep_missing/index.html +pub struct DepMissing; diff --git a/tests/run-make/rustdoc-merge-directory-alias/rmake.rs b/tests/run-make/rustdoc-merge-directory-alias/rmake.rs new file mode 100644 index 0000000000000..096eb4a487c15 --- /dev/null +++ b/tests/run-make/rustdoc-merge-directory-alias/rmake.rs @@ -0,0 +1,88 @@ +// Running --merge=finalize without an input crate root should not trigger ICE. +// Issue: https://github.com/rust-lang/rust/issues/146646 + +//@ needs-target-std + +use run_make_support::{htmldocck, path, rustdoc}; + +fn main() { + let out_dir = path("out"); + let merged_dir = path("merged"); + let parts_out_dir = path("parts"); + + rustdoc() + .input("dep1.rs") + .out_dir(&out_dir) + .arg("-Zunstable-options") + .arg(format!("--parts-out-dir={}", parts_out_dir.display())) + .arg("--merge=none") + .run(); + assert!(parts_out_dir.join("dep1.json").exists()); + + let output = rustdoc() + .arg("-Zunstable-options") + .out_dir(&out_dir) + .arg(format!("--include-parts-dir={}", parts_out_dir.display())) + .arg("--merge=finalize") + .run(); + output.assert_stderr_not_contains("error: the compiler unexpectedly panicked. this is a bug."); + + rustdoc() + .input("dep2.rs") + .out_dir(&out_dir) + .arg("-Zunstable-options") + .arg(format!("--parts-out-dir={}", parts_out_dir.display())) + .arg("--merge=none") + .run(); + assert!(parts_out_dir.join("dep2.json").exists()); + + let output2 = rustdoc() + .arg("-Zunstable-options") + .out_dir(&out_dir) + .arg(format!("--include-parts-dir={}", parts_out_dir.display())) + .arg("--merge=finalize") + .run(); + output2.assert_stderr_not_contains("error: the compiler unexpectedly panicked. this is a bug."); + + rustdoc() + .input("dep1.rs") + .out_dir(&out_dir) + .arg("-Zunstable-options") + .arg(format!("--parts-out-dir={}", parts_out_dir.display())) + .arg("--merge=none") + .run(); + assert!(parts_out_dir.join("dep1.json").exists()); + + let output3 = rustdoc() + .arg("-Zunstable-options") + .out_dir(&out_dir) + .arg(format!("--include-parts-dir={}", parts_out_dir.display())) + .arg("--merge=finalize") + .run(); + output3.assert_stderr_not_contains("error: the compiler unexpectedly panicked. this is a bug."); + + // dep_missing is different, because --parts-out-dir is not supplied + rustdoc().input("dep_missing.rs").out_dir(&out_dir).run(); + assert!(parts_out_dir.join("dep2.json").exists()); + + rustdoc() + .input("dep1.rs") + .out_dir(&out_dir) + .arg("-Zunstable-options") + .arg(format!("--parts-out-dir={}", parts_out_dir.display())) + .arg("--merge=none") + .run(); + assert!(parts_out_dir.join("dep1.json").exists()); + + let output4 = rustdoc() + .arg("-Zunstable-options") + .out_dir(&out_dir) + .arg(format!("--include-parts-dir={}", parts_out_dir.display())) + .arg("--merge=finalize") + .run(); + output4.assert_stderr_not_contains("error: the compiler unexpectedly panicked. this is a bug."); + + htmldocck().arg(&out_dir).arg("dep1.rs").run(); + htmldocck().arg(&out_dir).arg("dep2.rs").run(); + htmldocck().arg(&out_dir).arg("dep_missing.rs").run(); +} diff --git a/tests/ui/feature-gates/unknown-feature.rs b/tests/ui/feature-gates/unknown-feature.rs index 20fd932d4c2f6..a9e8e046eb168 100644 --- a/tests/ui/feature-gates/unknown-feature.rs +++ b/tests/ui/feature-gates/unknown-feature.rs @@ -1,3 +1,16 @@ -#![feature(unknown_rust_feature)] //~ ERROR unknown feature +#![feature( + unknown_rust_feature, + //~^ ERROR unknown feature + + // Typo for lang feature + associated_types_default, + //~^ ERROR unknown feature + //~| HELP there is a feature with a similar name + + // Typo for lib feature + core_intrnisics, + //~^ ERROR unknown feature + //~| HELP there is a feature with a similar name +)] fn main() {} diff --git a/tests/ui/feature-gates/unknown-feature.stderr b/tests/ui/feature-gates/unknown-feature.stderr index 0a731ddcc0b62..1e5b953e99cab 100644 --- a/tests/ui/feature-gates/unknown-feature.stderr +++ b/tests/ui/feature-gates/unknown-feature.stderr @@ -1,9 +1,33 @@ error[E0635]: unknown feature `unknown_rust_feature` - --> $DIR/unknown-feature.rs:1:12 + --> $DIR/unknown-feature.rs:2:5 | -LL | #![feature(unknown_rust_feature)] - | ^^^^^^^^^^^^^^^^^^^^ +LL | unknown_rust_feature, + | ^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 1 previous error +error[E0635]: unknown feature `associated_types_default` + --> $DIR/unknown-feature.rs:6:5 + | +LL | associated_types_default, + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: there is a feature with a similar name: `associated_type_defaults` + | +LL - associated_types_default, +LL + associated_type_defaults, + | + +error[E0635]: unknown feature `core_intrnisics` + --> $DIR/unknown-feature.rs:11:5 + | +LL | core_intrnisics, + | ^^^^^^^^^^^^^^^ + | +help: there is a feature with a similar name: `core_intrinsics` + | +LL - core_intrnisics, +LL + core_intrinsics, + | + +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0635`. diff --git a/tests/ui/suggestions/deref-path-method.stderr b/tests/ui/suggestions/deref-path-method.stderr index dc2f6f66437e0..0dec424555ed5 100644 --- a/tests/ui/suggestions/deref-path-method.stderr +++ b/tests/ui/suggestions/deref-path-method.stderr @@ -9,7 +9,7 @@ note: if you're trying to build a new `Vec<_, _>` consider using one of the foll Vec::::with_capacity Vec::::try_with_capacity Vec::::from_raw_parts - and 6 others + and 7 others --> $SRC_DIR/alloc/src/vec/mod.rs:LL:COL help: the function `contains` is implemented on `[_]` | diff --git a/tests/ui/ufcs/bad-builder.stderr b/tests/ui/ufcs/bad-builder.stderr index a3528cb1e7d80..2504a3d09253c 100644 --- a/tests/ui/ufcs/bad-builder.stderr +++ b/tests/ui/ufcs/bad-builder.stderr @@ -9,7 +9,7 @@ note: if you're trying to build a new `Vec` consider using one of the followi Vec::::with_capacity Vec::::try_with_capacity Vec::::from_raw_parts - and 6 others + and 7 others --> $SRC_DIR/alloc/src/vec/mod.rs:LL:COL help: there is an associated function `new` with a similar name |