mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-28 03:07:24 +03:00
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:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 @@
|
||||
1eb0657f78777f0b4d6bcc49c126d5d35212cae5
|
||||
36b2369c91d32c2659887ed6fe3d570640f44fd2
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
_ =>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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" => {
|
||||
|
||||
@@ -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
|
||||
|
||||
+5
-1
@@ -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/
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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
|
||||
|
||||
+9
-1
@@ -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/
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -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
|
||||
|
||||
+6
-1
@@ -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/
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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
|
||||
|
||||
+44
@@ -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/
|
||||
}
|
||||
+14
@@ -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/
|
||||
}
|
||||
+25
@@ -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
|
||||
|
||||
+53
@@ -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/
|
||||
}
|
||||
+25
@@ -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
|
||||
|
||||
+59
@@ -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/
|
||||
}
|
||||
+25
@@ -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
|
||||
|
||||
+49
@@ -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/
|
||||
}
|
||||
+25
@@ -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
|
||||
|
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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 `[_]`
|
||||
|
|
||||
|
||||
@@ -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
|
||||
|
|
||||
|
||||
Reference in New Issue
Block a user