Auto merge of #149717 - matthiaskrgr:rollup-spntobh, r=matthiaskrgr

Rollup of 5 pull requests

Successful merges:

 - rust-lang/rust#149659 (Look for typos when reporting an unknown nightly feature)
 - rust-lang/rust#149699 (Implement `Vec::from_fn`)
 - rust-lang/rust#149700 (rustdoc: fix bugs with search aliases and merging)
 - rust-lang/rust#149713 (Update windows-gnullvm platform support doc)
 - rust-lang/rust#149716 (miri subtree update)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors
2025-12-06 21:42:15 +00:00
99 changed files with 2637 additions and 863 deletions
+10 -14
View File
@@ -16,7 +16,6 @@
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 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
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<MisspelledKw> {
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 {
+2
View File
@@ -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
+15
View File
@@ -1183,6 +1183,21 @@ pub(crate) struct UnknownFeature {
#[primary_span]
pub span: Span,
pub feature: Symbol,
#[subdiagnostic]
pub suggestion: Option<MisspelledFeature>,
}
#[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)]
+24 -6
View File
@@ -6,7 +6,7 @@
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 @@ fn check_features<'tcx>(
// 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 @@ fn check_features<'tcx>(
&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 @@ fn check_features<'tcx>(
&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::<Vec<_>>();
let lib_features = crates
.into_iter()
.flat_map(|&cnum| {
tcx.lib_features(cnum).stability.keys().copied().into_sorted_stable_ord()
})
.collect::<Vec<_>>();
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() {
+22
View File
@@ -14,6 +14,7 @@
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 @@ pub fn to_ident_string(self) -> String {
// 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 {
+53
View File
@@ -745,6 +745,59 @@ pub unsafe fn from_parts(ptr: NonNull<T>, length: usize, capacity: usize) -> Sel
unsafe { Self::from_parts_in(ptr, length, capacity, Global) }
}
/// Creates a `Vec<T>` where each element is produced by calling `f` with
/// that element's index while walking forward through the `Vec<T>`.
///
/// 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<T>`s not iterators.
///
/// If `length == 0`, this produces an empty `Vec<T>` 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<T>` 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<F>(length: usize, f: F) -> Self
where
F: FnMut(usize) -> T,
{
(0..length).map(f).collect()
}
/// Decomposes a `Vec<T>` into its raw components: `(pointer, length, capacity)`.
///
/// Returns the raw pointer to the underlying data, the length of
@@ -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
+10 -2
View File
@@ -24,6 +24,7 @@
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 @@ fn map_fn_sig_item(map: &FxHashMap<usize, usize>, ty: &mut RenderType) {
}
},
),
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<SerializedSearchIndex, Error> {
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);
+8 -2
View File
@@ -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
+5 -5
View File
@@ -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 <https://bugs.launchpad.net/ubuntu/+bug/2130309>).
# - 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
+1
View File
@@ -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):
@@ -28,5 +28,7 @@ overrideCommand = [
"./miri",
"check",
"--no-default-features",
"-Zunstable-options",
"--compile-time-deps",
"--message-format=json",
]
@@ -22,6 +22,8 @@
"./miri",
"check",
"--no-default-features",
"-Zunstable-options",
"--compile-time-deps",
"--message-format=json",
],
}
@@ -31,6 +31,8 @@
"./miri",
"check",
"--no-default-features",
"-Zunstable-options",
"--compile-time-deps",
"--message-format=json"
]
}
+1 -1
View File
@@ -1 +1 @@
1eb0657f78777f0b4d6bcc49c126d5d35212cae5
36b2369c91d32c2659887ed6fe3d570640f44fd2
+25 -38
View File
@@ -272,18 +272,13 @@ fn after_analysis<'tcx>(
}
}
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 @@ fn after_analysis<'tcx>(
_: &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.
@@ -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 @@ fn print_protector(&self, protector: Option<&ProtectorKind>) -> &'static str {
})
.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<String>,
exposed: bool,
rperm: Vec<Option<LocationState>>,
children: Vec<DisplayRepr>,
}
impl DisplayRepr {
fn from(tree: &Tree, show_unnamed: bool) -> Option<Self> {
fn from(tree: &Tree, root: UniIndex, show_unnamed: bool) -> Option<Self> {
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 @@ fn extraction_aux(
) {
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::<Vec<_>>();
children.sort_by_key(|idx| tree.nodes.get(*idx).unwrap().tag);
@@ -661,12 +667,13 @@ fn extraction_aux(
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<DisplayRepr>,
wildcard_subtrees: &[DisplayRepr],
fmt: &DisplayFmt,
indenter: &mut DisplayIndent,
protected_tags: &FxHashMap<BorTag, ProtectorKind>,
@@ -703,15 +710,41 @@ fn print(
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 @@ fn print_aux(
indent: &mut DisplayIndent,
protected_tags: &FxHashMap<BorTag, ProtectorKind>,
is_last_child: bool,
is_wildcard_root: bool,
acc: &mut Vec<String>,
) {
let mut line = String::new();
@@ -760,7 +794,9 @@ fn print_aux(
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 @@ fn print_aux(
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 @@ fn print_aux(
indent_last: " ",
join_haschild: "",
join_default: "",
wildcard_root: "*",
},
accessed: DisplayFmtAccess { yes: " ", no: "?", meh: "-" },
};
@@ -816,15 +863,27 @@ pub fn print_tree(
) -> InterpResult<'tcx> {
let mut indenter = DisplayIndent::new();
let ranges = self.locations.iter_all().map(|(range, _loc)| range).collect::<Vec<_>>();
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::<Vec<_>>();
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(())
}
}
@@ -13,6 +13,7 @@
mod foreign_access_skipping;
mod perms;
mod tree;
mod tree_visitor;
mod unimap;
mod wildcard;
@@ -239,18 +240,14 @@ fn tb_reborrow(
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 @@ fn tb_reborrow(
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 @@ fn tb_reborrow(
}
}
}
// 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 @@ fn tb_reborrow(
)?;
drop(tree_borrows);
interp_ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }))
interp_ok(Some(new_prov))
}
fn tb_retag_place(
@@ -18,15 +18,15 @@
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 @@ fn perform_transition(
nodes: &mut UniValMap<Node>,
wildcard_accesses: &mut UniValMap<WildcardState>,
access_kind: AccessKind,
access_cause: AccessCause,
access_range: Option<AllocRange>,
access_cause: AccessCause, //diagnostics
access_range: Option<AllocRange>, //diagnostics
relatedness: AccessRelatedness,
span: Span,
location_range: Range<u64>,
span: Span, //diagnostics
location_range: Range<u64>, //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<Node>,
/// Associates with each location its state and wildcard access tracking.
pub(super) locations: DedupRangeMap<LocationTree>,
/// 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<Node>,
/// 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<Node>,
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<NodeContinue, NodeApp> {
/// 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<NodeContinue, NodeApp, Err> TreeVisitorStack<NodeContinue, NodeApp>
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<Err>(
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<Err>(
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 @@ pub fn new(root_tag: BorTag, size: Size, span: Span) -> Self {
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<LocationState>,
outside_perm: Permission,
@@ -647,7 +405,11 @@ pub(super) fn new_child(
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 @@ pub(super) fn new_child(
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 @@ pub(super) fn new_child(
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 @@ pub(super) fn new_child(
// 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 @@ pub fn dealloc(
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);
// 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 = 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(())
}
},
)?;
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 @@ pub fn perform_access(
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 @@ pub fn perform_access(
{
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 @@ pub fn perform_access(
/// Integration with the BorTag garbage collector
impl Tree {
pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet<BorTag>) {
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<Item = UniIndex>,
nodes: &mut UniValMap<Node>,
access_source: Option<UniIndex>,
loc_range: Range<u64>,
access_range: Option<AllocRange>,
loc_range: Range<u64>, // diagnostics
access_range: Option<AllocRange>, // 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 @@ fn perform_access(
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 @@ fn perform_access(
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 @@ fn perform_normal_access(
&mut self,
access_source: UniIndex,
nodes: &mut UniValMap<Node>,
loc_range: Range<u64>,
access_range: Option<AllocRange>,
loc_range: Range<u64>, // diagnostics
access_range: Option<AllocRange>, // 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 @@ fn perform_normal_access(
// - 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 @@ fn perform_normal_access(
.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 @@ fn perform_normal_access(
.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 @@ fn perform_normal_access(
}
.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<UniIndex>,
max_local_tag: Option<BorTag>,
nodes: &mut UniValMap<Node>,
loc_range: Range<u64>,
access_range: Option<AllocRange>,
loc_range: Range<u64>, // diagnostics
access_range: Option<AllocRange>, // 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<Node>, 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 <https://github.com/rust-lang/miri/pull/4707#discussion_r2581661123>.)
//
// 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 @@ fn perform_wildcard_access(
} 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());
},
|args| {
let node = args.nodes.get_mut(args.idx).unwrap();
let protected = global.borrow().protected_tags.contains_key(&node.tag);
let protected = global.borrow().protected_tags.contains_key(&node.tag);
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(());
};
// 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 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,
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()
));
};
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 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,
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 @@ pub fn default_location_state(&self) -> LocationState {
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() {
@@ -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<Node>,
/// 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<Node>,
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<NodeContinue, NodeApp, T> {
/// 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<T>,
}
impl<NodeContinue, NodeApp, T, Err> TreeVisitorStack<NodeContinue, NodeApp, T>
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<UniIndex, 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(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<Err>(
mut self,
start_idx: UniIndex,
f_continue: impl Fn(&NodeAppArgs<'_, T>) -> ContinueTraversal,
f_propagate: impl FnMut(NodeAppArgs<'_, T>) -> Result<(), Err>,
) -> Result<UniIndex, 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.
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<Err>(
mut self,
start_idx: UniIndex,
f_continue: impl Fn(&NodeAppArgs<'_, T>) -> ContinueTraversal,
f_propagate: impl FnMut(NodeAppArgs<'_, T>) -> Result<(), Err>,
) -> Result<UniIndex, 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.
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<Err>(
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)
}
}
@@ -88,10 +88,26 @@ fn max_local_access(&self) -> WildcardAccessLevel {
}
/// 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<WildcardAccessRelatedness> {
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<WildcardAccessRelatedness> {
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 @@ pub fn for_new_child(&self) -> Self {
..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 @@ pub fn verify_wildcard_consistency(&self, global: &GlobalState) {
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 @@ pub fn verify_wildcard_consistency(&self, global: &GlobalState) {
.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
+17 -6
View File
@@ -14,7 +14,7 @@
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<Frame<'tcx, Provenance, FrameExtra<'tcx>>>,
/// 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 @@ fn new(name: Option<&str>, on_stack_empty: Option<StackEmptyCallback<'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 @@ fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
unwind_payloads: panic_payload,
last_error,
stack,
origin_span: _,
top_user_relevant_frame: _,
state: _,
thread_name: _,
@@ -584,6 +590,10 @@ pub fn active_thread_ref(&self) -> &Thread<'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 @@ fn run_timeout_callback(&mut self) -> InterpResult<'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 @@ fn start_regular_thread(
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 @@ fn start_regular_thread(
// 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.
+19 -2
View File
@@ -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<ThreadId>,
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();
+22 -4
View File
@@ -472,6 +472,22 @@ fn call_function(
)
}
/// 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 @@ fn expect_target_feature_for_intrinsic(
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<ImmTy<'tcx>>> {
) -> InterpResult<'tcx, Vec<(ImmTy<'tcx>, Span)>> {
let this = self.eval_context_mut();
let tcx = this.tcx.tcx;
@@ -1012,6 +1029,7 @@ fn lookup_link_section(
};
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 @@ fn lookup_link_section(
});
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));
}
}
_ =>
+1
View File
@@ -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
+5 -4
View File
@@ -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<ImmTy<'tcx>>),
Ctors(Vec<(ImmTy<'tcx>, Span)>),
Done,
}
@@ -67,19 +68,19 @@ pub fn on_stack_empty(
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)
+23 -22
View File
@@ -6,6 +6,7 @@
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<ThreadId, Scalar>,
dtor: Option<ty::Instance<'tcx>>,
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<ThreadId, Vec<(ty::Instance<'tcx>, Scalar)>>,
macos_thread_dtors: BTreeMap<ThreadId, Vec<(ty::Instance<'tcx>, 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<ty::Instance<'tcx>>,
dtor: Option<(ty::Instance<'tcx>, Span)>,
max_size: Size,
) -> InterpResult<'tcx, TlsKey> {
let new_key = self.next_key;
@@ -126,8 +127,9 @@ pub fn add_macos_thread_dtor(
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 @@ fn fetch_tls_dtor(
&mut self,
key: Option<TlsKey>,
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 @@ fn fetch_tls_dtor(
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 @@ fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
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<ImmTy<'tcx>>),
WindowsDtors(Vec<(ImmTy<'tcx>, Span)>),
Done,
}
@@ -273,8 +274,8 @@ pub fn on_stack_empty(
}
}
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<ImmTy<'tcx>>> {
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 @@ fn lookup_windows_tls_dtors(&mut self) -> InterpResult<'tcx, Vec<ImmTy<'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 @@ fn schedule_windows_tls_dtor(&mut self, dtor: ImmTy<'tcx>) -> InterpResult<'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 @@ fn schedule_macos_tls_dtor(&mut self) -> InterpResult<'tcx, Poll<()>> {
// 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 @@ fn schedule_next_pthread_tls_dtor(
// We ran each dtor once, start over from the beginning.
None => this.machine.tls.fetch_tls_dtor(None, active_thread),
};
if let Some((instance, ptr, key)) = dtor {
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 @@ fn schedule_next_pthread_tls_dtor(
"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);
+10 -71
View File
@@ -235,33 +235,6 @@ fn emulate_foreign_item_inner(
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 @@ fn emulate_foreign_item_inner(
}
// 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 @@ fn emulate_foreign_item_inner(
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 @@ fn emulate_foreign_item_inner(
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 @@ fn emulate_foreign_item_inner(
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 @@ fn emulate_foreign_item_inner(
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 @@ fn emulate_foreign_item_inner(
// 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
};
@@ -148,15 +148,9 @@ fn emulate_foreign_item_inner(
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" => {
+22 -48
View File
@@ -118,7 +118,7 @@ fn flock<'tcx>(
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 @@ fn macos_fbsd_solarish_write_stat_buf(
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 @@ fn macos_fbsd_solarish_write_stat_buf(
("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 @@ fn macos_fbsd_solarish_write_stat_buf(
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 @@ fn macos_fbsd_solarish_stat(
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 @@ fn macos_fbsd_solarish_lstat(
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 @@ fn macos_fbsd_solarish_fstat(
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 @@ fn readdir64(&mut self, dirent_type: &str, dirp_op: &OpTy<'tcx>) -> InterpResult
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 @@ fn macos_fbsd_readdir_r(
) -> 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 @@ fn macos_fbsd_readdir_r(
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)
@@ -36,6 +36,80 @@ fn emulate_foreign_item_inner(
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 @@ fn emulate_foreign_item_inner(
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)?;
@@ -46,19 +46,19 @@ fn emulate_foreign_item_inner(
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 @@ fn emulate_foreign_item_inner(
"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 @@ fn emulate_foreign_item_inner(
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
@@ -90,21 +90,16 @@ fn emulate_foreign_item_inner(
}
// 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)?;
@@ -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);
@@ -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
@@ -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);
@@ -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
@@ -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::*;
@@ -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::*;
@@ -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));
}
}
@@ -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
@@ -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(<N)
let _val = *root2; //~ ERROR: /read access .* tag does not exist in the borrow stack/
let _val = *root2;
//~[stack]^ ERROR: /read access .* tag does not exist in the borrow stack/
//~[tree]| ERROR: /read access through .* is forbidden/
}
}
@@ -1,5 +1,5 @@
error: Undefined Behavior: attempting a read access using <TAG> 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: <TAG> 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: <TAG> 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;
| ^^^^^^^^^^^^^^^^
@@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
help: the accessed tag <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 <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
@@ -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(<N), Unique(N)
// Stack if _fool existed: Unknown(<N), Unique(N), SRW(N+1)
@@ -15,6 +19,10 @@ fn main() {
// Stack: Unknown(<N), Disabled(N)
// collapsed to Unknown(<N)
// Stack if _fool existed: Unknown(<N), Disabled(N), SRW(N+1); collapsed to Unknown(<N+2) which would not cause an ERROR
let _val = *root2; //~ ERROR: /read access .* tag does not exist in the borrow stack/
// Stack borrows would also fail if we replaced this with a read, but tree borrows would let it pass.
*root2 = 3;
//~[stack]^ ERROR: /write access .* tag does not exist in the borrow stack/
//~[tree]| ERROR: /write access through .* is forbidden/
}
}
@@ -1,18 +1,18 @@
error: Undefined Behavior: attempting a read access using <TAG> 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 <TAG> 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: <TAG> 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: <TAG> 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;
| ^^^^^^^^^^^^
@@ -0,0 +1,31 @@
error: Undefined Behavior: write access through <TAG> 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 <TAG> has state Frozen which forbids this child write access
help: the accessed tag <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 <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 <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
@@ -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(<N)
let _val = *root2; //~ ERROR: /read access .* tag does not exist in the borrow stack/
let _val = *root2;
//~[stack]^ ERROR: /read access .* tag does not exist in the borrow stack/
//~[tree]| ERROR: /read access through .* is forbidden/
}
}
@@ -1,5 +1,5 @@
error: Undefined Behavior: attempting a read access using <TAG> 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: <TAG> 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: <TAG> 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;
| ^^^^^^^^^^^^^^^^
@@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
help: the accessed tag <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 <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
@@ -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 ()
};
}
@@ -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
@@ -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(|_| ());
}
@@ -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
@@ -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/
}
@@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
help: the accessed tag <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 <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
@@ -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/
}
@@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
help: the accessed tag <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 <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
@@ -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/
}
@@ -0,0 +1,14 @@
error: Undefined Behavior: write access through <wildcard> 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
@@ -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/
}
@@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
help: the accessed tag <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 <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
@@ -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/
}
@@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
help: the accessed tag <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 <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
@@ -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/
}
@@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
help: the accessed tag <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 <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
@@ -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/
}
@@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
help: the accessed tag <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 <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
@@ -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/
}
@@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
help: the accessed tag <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 <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
@@ -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);
}
@@ -0,0 +1,37 @@
error: Undefined Behavior: write access through <TAG> 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 <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
= help: this foreign write access would cause the protected tag <TAG> (currently Reserved) to become Disabled
= help: protected tags must never be Disabled
help: the accessed tag <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 <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
@@ -1,4 +1,4 @@
error: Undefined Behavior: deallocation through <wildcard> at ALLOC[0x0] is forbidden
error: Undefined Behavior: deallocation through <TAG> 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 <wildcard> also contains the strongly protected tag <TAG>
= help: the allocation of the accessed tag <TAG> also contains the strongly protected tag <TAG>
= help: the strongly protected tag <TAG> disallows deallocations
help: the accessed tag <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 <TAG> was created here, in the initial state Reserved
--> tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC
|
@@ -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/
}
@@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
help: the accessed tag <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 <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
@@ -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/
}
@@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
help: the accessed tag <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 <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
@@ -1,26 +0,0 @@
error: Undefined Behavior: write access through <TAG> is forbidden
--> $DIR/write_to_shr.rs:LL:CC
|
LL | *xmut = 31;
| ^^^^^^^^^^ write access through <TAG> 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 <TAG> is a child of the conflicting tag <TAG>
= help: the conflicting tag <TAG> has state Frozen which forbids child write accesses
help: the accessed tag <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 <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
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
@@ -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
|
@@ -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
|
+37 -1
View File
@@ -38,10 +38,11 @@ fn main() {
test_posix_fadvise();
#[cfg(not(target_os = "macos"))]
test_posix_fallocate::<libc::off_t>(libc::posix_fallocate);
#[cfg(any(target_os = "linux", target_os = "android"))]
#[cfg(target_os = "linux")]
test_posix_fallocate::<libc::off64_t>(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::<libc::stat>::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.
@@ -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);
}
}
@@ -0,0 +1,17 @@
──────────────────────────────────────────────────
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1
| Act | └─┬──<TAG=root of the allocation>
| Frz | └─┬──<TAG=x>
| Frz | ├────<TAG=xa>
| Frz | └────<TAG=xb> (exposed)
| |
| Frz | *─┬──<TAG=y>
| Frz | ├────<TAG=ya> (exposed)
| Frz | └────<TAG=yb>
| |
| Frz | *────<TAG=z>
| |
| Frz | *─┬──<TAG=u>
| Frz | └────<TAG=ua>
──────────────────────────────────────────────────
@@ -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;
}
@@ -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 @@ fn protect(ref3: &mut u32) {
}
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;
/// 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 int = ref2 as *mut u32 as usize;
let wild = int as *mut u32;
let wild_ref = unsafe { &mut *wild };
let ref_base = &mut x;
let mut protect = |arg: &mut u32| {
// arg is a protected pointer with wildcard provenance.
let int0 = ref_base as *mut u32 as usize;
let wild = int0 as *mut u32;
// ┌────────────┐
// │ │
// │ ref1(Res) │
// │ │
// └──────┬─────┘
// │
// │
// ▼
// ┌────────────┐
// │ │
// │ ref2(Res)* │
// │ │
// └────────────┘
let reb1 = unsafe { &mut *wild };
*reb1 = 44;
// 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 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);
// We pass a pointer with wildcard provenance to the function.
protect(wild_ref);
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;
}
@@ -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.
+2 -2
View File
@@ -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);
};
}
@@ -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;
@@ -0,0 +1,4 @@
//@ hasraw crates.js 'dep2'
//@ hasraw search.index/name/*.js 'Second'
//@ has dep2/index.html
pub struct Second;
@@ -0,0 +1,4 @@
//@ !hasraw crates.js 'dep_missing'
//@ !hasraw search.index/name/*.js 'DepMissing'
//@ has dep_missing/index.html
pub struct DepMissing;
@@ -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();
}
+14 -1
View File
@@ -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() {}
+28 -4
View File
@@ -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`.
@@ -9,7 +9,7 @@ note: if you're trying to build a new `Vec<_, _>` consider using one of the foll
Vec::<T>::with_capacity
Vec::<T>::try_with_capacity
Vec::<T>::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 `[_]`
|
+1 -1
View File
@@ -9,7 +9,7 @@ note: if you're trying to build a new `Vec<Q>` consider using one of the followi
Vec::<T>::with_capacity
Vec::<T>::try_with_capacity
Vec::<T>::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
|