Auto merge of #142292 - RalfJung:miri-sync, r=RalfJung
Miri subtree update r? `@ghost`
This commit is contained in:
commit
1677d46cb1
25 changed files with 420 additions and 229 deletions
|
|
@ -2326,7 +2326,6 @@ dependencies = [
|
|||
"tempfile",
|
||||
"tikv-jemalloc-sys",
|
||||
"ui_test",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -555,7 +555,6 @@ dependencies = [
|
|||
"tempfile",
|
||||
"tikv-jemalloc-sys",
|
||||
"ui_test",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -40,13 +40,6 @@ libc = "0.2"
|
|||
libffi = "4.0.0"
|
||||
libloading = "0.8"
|
||||
|
||||
[target.'cfg(target_family = "windows")'.dependencies]
|
||||
windows-sys = { version = "0.59", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_IO",
|
||||
"Win32_Storage_FileSystem",
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
ui_test = "0.29.1"
|
||||
colored = "2"
|
||||
|
|
|
|||
|
|
@ -396,18 +396,22 @@ to Miri failing to detect cases of undefined behavior in a program.
|
|||
* `-Zmiri-force-intrinsic-fallback` forces the use of the "fallback" body for all intrinsics that
|
||||
have one. This is useful to test the fallback bodies, but should not be used otherwise. It is
|
||||
**unsound** since the fallback body might not be checking for all UB.
|
||||
* `-Zmiri-native-lib=<path to a shared object file>` is an experimental flag for providing support
|
||||
for calling native functions from inside the interpreter via FFI. The flag is supported only on
|
||||
Unix systems. Functions not provided by that file are still executed via the usual Miri shims.
|
||||
* `-Zmiri-native-lib=<path to a shared object file or folder>` is an experimental flag for providing
|
||||
support for calling native functions from inside the interpreter via FFI. The flag is supported
|
||||
only on Unix systems. Functions not provided by that file are still executed via the usual Miri
|
||||
shims. If a path to a directory is specified, all files in that directory are included
|
||||
non-recursively. This flag can be passed multiple times to specify multiple files and/or
|
||||
directories.
|
||||
**WARNING**: If an invalid/incorrect `.so` file is specified, this can cause Undefined Behavior in
|
||||
Miri itself! And of course, Miri cannot do any checks on the actions taken by the native code.
|
||||
Miri itself! And of course, Miri often cannot do any checks on the actions taken by the native code.
|
||||
Note that Miri has its own handling of file descriptors, so if you want to replace *some*
|
||||
functions working on file descriptors, you will have to replace *all* of them, or the two kinds of
|
||||
file descriptors will be mixed up. This is **work in progress**; currently, only integer and
|
||||
pointers arguments and return values are supported and memory allocated by the native code cannot
|
||||
be accessed from Rust (only the other way around). Native code must not spawn threads that keep
|
||||
running in the background after the call has returned to Rust and that access Rust-allocated
|
||||
memory. Finally, the flag is **unsound** in the sense that Miri stops tracking details such as
|
||||
file descriptors will be mixed up.
|
||||
This is **work in progress**; currently, only integer and pointers arguments and return values are
|
||||
supported and memory allocated by the native code cannot be accessed from Rust (only the other way
|
||||
around). Native code must not spawn threads that keep running in the background after the call has
|
||||
returned to Rust and that access Rust-allocated memory.
|
||||
Finally, the flag is **unsound** in the sense that Miri stops tracking details such as
|
||||
initialization and provenance on memory shared with native code, so it is easily possible to write
|
||||
code that has UB which is missed by Miri.
|
||||
* `-Zmiri-measureme=<name>` enables `measureme` profiling for the interpreted program.
|
||||
|
|
@ -458,6 +462,10 @@ to Miri failing to detect cases of undefined behavior in a program.
|
|||
This is much less likely with Stacked Borrows.
|
||||
Using Tree Borrows currently implies `-Zmiri-strict-provenance` because integer-to-pointer
|
||||
casts are not supported in this mode, but that may change in the future.
|
||||
* `-Zmiri-tree-borrows-no-precise-interior-mut` makes Tree Borrows
|
||||
track interior mutable data on the level of references instead of on the
|
||||
byte-level as is done by default. Therefore, with this flag, Tree
|
||||
Borrows will be more permissive.
|
||||
* `-Zmiri-force-page-size=<num>` overrides the default page size for an architecture, in multiples of 1k.
|
||||
`4` is default for most targets. This value should always be a power of 2 and nonzero.
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
337c11e5932275e7d450c1f2e26f289f0ddfa717
|
||||
c6768de2d63de7a41124a0fb8fc78f9e26111c01
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
assert!(!matches!(info.kind, AllocKind::Dead));
|
||||
|
||||
// This allocation does not have a base address yet, pick or reuse one.
|
||||
if this.machine.native_lib.is_some() {
|
||||
if !this.machine.native_lib.is_empty() {
|
||||
// In native lib mode, we use the "real" address of the bytes for this allocation.
|
||||
// This ensures the interpreted program and native code have the same view of memory.
|
||||
let params = this.machine.get_default_alloc_params();
|
||||
|
|
@ -413,7 +413,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
) -> InterpResult<'tcx, MiriAllocBytes> {
|
||||
let this = self.eval_context_ref();
|
||||
assert!(this.tcx.try_get_global_alloc(id).is_some());
|
||||
if this.machine.native_lib.is_some() {
|
||||
if !this.machine.native_lib.is_empty() {
|
||||
// In native lib mode, MiriAllocBytes for global allocations are handled via `prepared_alloc_bytes`.
|
||||
// This additional call ensures that some `MiriAllocBytes` are always prepared, just in case
|
||||
// this function gets called before the first time `addr_from_alloc_id` gets called.
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ use std::sync::{Arc, Once};
|
|||
|
||||
use miri::{
|
||||
BacktraceStyle, BorrowTrackerMethod, GenmcConfig, GenmcCtx, MiriConfig, MiriEntryFnType,
|
||||
ProvenanceMode, RetagFields, ValidationMode,
|
||||
ProvenanceMode, RetagFields, TreeBorrowsParams, ValidationMode,
|
||||
};
|
||||
use rustc_abi::ExternAbi;
|
||||
use rustc_data_structures::sync;
|
||||
|
|
@ -554,8 +554,21 @@ fn main() {
|
|||
} else if arg == "-Zmiri-disable-stacked-borrows" {
|
||||
miri_config.borrow_tracker = None;
|
||||
} else if arg == "-Zmiri-tree-borrows" {
|
||||
miri_config.borrow_tracker = Some(BorrowTrackerMethod::TreeBorrows);
|
||||
miri_config.borrow_tracker =
|
||||
Some(BorrowTrackerMethod::TreeBorrows(TreeBorrowsParams {
|
||||
precise_interior_mut: true,
|
||||
}));
|
||||
miri_config.provenance_mode = ProvenanceMode::Strict;
|
||||
} else if arg == "-Zmiri-tree-borrows-no-precise-interior-mut" {
|
||||
match &mut miri_config.borrow_tracker {
|
||||
Some(BorrowTrackerMethod::TreeBorrows(params)) => {
|
||||
params.precise_interior_mut = false;
|
||||
}
|
||||
_ =>
|
||||
show_error!(
|
||||
"`-Zmiri-tree-borrows` is required before `-Zmiri-tree-borrows-no-precise-interior-mut`"
|
||||
),
|
||||
};
|
||||
} else if arg == "-Zmiri-disable-data-race-detector" {
|
||||
miri_config.data_race_detector = false;
|
||||
miri_config.weak_memory_emulation = false;
|
||||
|
|
@ -692,11 +705,18 @@ fn main() {
|
|||
};
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-native-lib=") {
|
||||
let filename = param.to_string();
|
||||
if std::path::Path::new(&filename).exists() {
|
||||
if let Some(other_filename) = miri_config.native_lib {
|
||||
show_error!("-Zmiri-native-lib is already set to {}", other_filename.display());
|
||||
let file_path = std::path::Path::new(&filename);
|
||||
if file_path.exists() {
|
||||
// For directories, nonrecursively add all normal files inside
|
||||
if let Ok(dir) = file_path.read_dir() {
|
||||
for lib in dir.filter_map(|res| res.ok()) {
|
||||
if lib.file_type().unwrap().is_file() {
|
||||
miri_config.native_lib.push(lib.path().to_owned());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
miri_config.native_lib.push(filename.into());
|
||||
}
|
||||
miri_config.native_lib = Some(filename.into());
|
||||
} else {
|
||||
show_error!("-Zmiri-native-lib `{}` does not exist", filename);
|
||||
}
|
||||
|
|
@ -725,18 +745,19 @@ fn main() {
|
|||
}
|
||||
}
|
||||
// Tree Borrows implies strict provenance, and is not compatible with native calls.
|
||||
if matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows)) {
|
||||
if matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows { .. })) {
|
||||
if miri_config.provenance_mode != ProvenanceMode::Strict {
|
||||
show_error!(
|
||||
"Tree Borrows does not support integer-to-pointer casts, and hence requires strict provenance"
|
||||
);
|
||||
}
|
||||
if miri_config.native_lib.is_some() {
|
||||
if !miri_config.native_lib.is_empty() {
|
||||
show_error!("Tree Borrows is not compatible with calling native functions");
|
||||
}
|
||||
}
|
||||
|
||||
// Native calls and strict provenance are not compatible.
|
||||
if miri_config.native_lib.is_some() && miri_config.provenance_mode == ProvenanceMode::Strict {
|
||||
if !miri_config.native_lib.is_empty() && miri_config.provenance_mode == ProvenanceMode::Strict {
|
||||
show_error!("strict provenance is not compatible with calling native functions");
|
||||
}
|
||||
// You can set either one seed or many.
|
||||
|
|
|
|||
|
|
@ -226,7 +226,13 @@ pub enum BorrowTrackerMethod {
|
|||
/// Stacked Borrows, as implemented in borrow_tracker/stacked_borrows
|
||||
StackedBorrows,
|
||||
/// Tree borrows, as implemented in borrow_tracker/tree_borrows
|
||||
TreeBorrows,
|
||||
TreeBorrows(TreeBorrowsParams),
|
||||
}
|
||||
|
||||
/// Parameters that Tree Borrows can take.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct TreeBorrowsParams {
|
||||
pub precise_interior_mut: bool,
|
||||
}
|
||||
|
||||
impl BorrowTrackerMethod {
|
||||
|
|
@ -237,6 +243,13 @@ impl BorrowTrackerMethod {
|
|||
config.retag_fields,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_tree_borrows_params(self) -> TreeBorrowsParams {
|
||||
match self {
|
||||
BorrowTrackerMethod::TreeBorrows(params) => params,
|
||||
_ => panic!("can only be called when `BorrowTrackerMethod` is `TreeBorrows`"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalStateInner {
|
||||
|
|
@ -252,7 +265,7 @@ impl GlobalStateInner {
|
|||
AllocState::StackedBorrows(Box::new(RefCell::new(Stacks::new_allocation(
|
||||
id, alloc_size, self, kind, machine,
|
||||
)))),
|
||||
BorrowTrackerMethod::TreeBorrows =>
|
||||
BorrowTrackerMethod::TreeBorrows { .. } =>
|
||||
AllocState::TreeBorrows(Box::new(RefCell::new(Tree::new_allocation(
|
||||
id, alloc_size, self, kind, machine,
|
||||
)))),
|
||||
|
|
@ -271,7 +284,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
||||
match method {
|
||||
BorrowTrackerMethod::StackedBorrows => this.sb_retag_ptr_value(kind, val),
|
||||
BorrowTrackerMethod::TreeBorrows => this.tb_retag_ptr_value(kind, val),
|
||||
BorrowTrackerMethod::TreeBorrows { .. } => this.tb_retag_ptr_value(kind, val),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -284,7 +297,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
||||
match method {
|
||||
BorrowTrackerMethod::StackedBorrows => this.sb_retag_place_contents(kind, place),
|
||||
BorrowTrackerMethod::TreeBorrows => this.tb_retag_place_contents(kind, place),
|
||||
BorrowTrackerMethod::TreeBorrows { .. } => this.tb_retag_place_contents(kind, place),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -293,7 +306,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
||||
match method {
|
||||
BorrowTrackerMethod::StackedBorrows => this.sb_protect_place(place),
|
||||
BorrowTrackerMethod::TreeBorrows => this.tb_protect_place(place),
|
||||
BorrowTrackerMethod::TreeBorrows { .. } => this.tb_protect_place(place),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -302,7 +315,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
||||
match method {
|
||||
BorrowTrackerMethod::StackedBorrows => this.sb_expose_tag(alloc_id, tag),
|
||||
BorrowTrackerMethod::TreeBorrows => this.tb_expose_tag(alloc_id, tag),
|
||||
BorrowTrackerMethod::TreeBorrows { .. } => this.tb_expose_tag(alloc_id, tag),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -319,7 +332,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
this.tcx.tcx.dcx().warn("Stacked Borrows does not support named pointers; `miri_pointer_name` is a no-op");
|
||||
interp_ok(())
|
||||
}
|
||||
BorrowTrackerMethod::TreeBorrows =>
|
||||
BorrowTrackerMethod::TreeBorrows { .. } =>
|
||||
this.tb_give_pointer_debug_name(ptr, nth_parent, name),
|
||||
}
|
||||
}
|
||||
|
|
@ -333,7 +346,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let method = borrow_tracker.borrow().borrow_tracker_method;
|
||||
match method {
|
||||
BorrowTrackerMethod::StackedBorrows => this.print_stacks(alloc_id),
|
||||
BorrowTrackerMethod::TreeBorrows => this.print_tree(alloc_id, show_unnamed),
|
||||
BorrowTrackerMethod::TreeBorrows { .. } => this.print_tree(alloc_id, show_unnamed),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -312,8 +312,6 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
|
||||
let span = this.machine.current_span();
|
||||
let alloc_extra = this.get_alloc_extra(alloc_id)?;
|
||||
let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
|
||||
|
||||
// Store initial permissions and their corresponding range.
|
||||
let mut perms_map: RangeMap<LocationState> = RangeMap::new(
|
||||
|
|
@ -342,36 +340,83 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
assert!(new_perm.freeze_access);
|
||||
|
||||
let protected = new_perm.protector.is_some();
|
||||
this.visit_freeze_sensitive(place, ptr_size, |range, frozen| {
|
||||
has_unsafe_cell = has_unsafe_cell || !frozen;
|
||||
let precise_interior_mut = this
|
||||
.machine
|
||||
.borrow_tracker
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.get_mut()
|
||||
.borrow_tracker_method
|
||||
.get_tree_borrows_params()
|
||||
.precise_interior_mut;
|
||||
|
||||
// We are only ever `Frozen` inside the frozen bits.
|
||||
let (perm, access) = if frozen {
|
||||
let default_perm = if !precise_interior_mut {
|
||||
// NOTE: Using `ty_is_freeze` doesn't give the same result as going through the range
|
||||
// and computing `has_unsafe_cell`. This is because of zero-sized `UnsafeCell`, for which
|
||||
// `has_unsafe_cell` is false, but `!ty_is_freeze` is true.
|
||||
let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.typing_env());
|
||||
let (perm, access) = if ty_is_freeze {
|
||||
(new_perm.freeze_perm, new_perm.freeze_access)
|
||||
} else {
|
||||
(new_perm.nonfreeze_perm, new_perm.nonfreeze_access)
|
||||
};
|
||||
let sifa = perm.strongest_idempotent_foreign_access(protected);
|
||||
let new_loc = if access {
|
||||
LocationState::new_accessed(perm, sifa)
|
||||
} else {
|
||||
LocationState::new_non_accessed(perm, sifa)
|
||||
};
|
||||
|
||||
// Store initial permissions.
|
||||
for (_loc_range, loc) in perms_map.iter_mut(range.start, range.size) {
|
||||
for (_loc_range, loc) in perms_map.iter_mut_all() {
|
||||
*loc = new_loc;
|
||||
}
|
||||
|
||||
perm
|
||||
} else {
|
||||
this.visit_freeze_sensitive(place, ptr_size, |range, frozen| {
|
||||
has_unsafe_cell = has_unsafe_cell || !frozen;
|
||||
|
||||
// We are only ever `Frozen` inside the frozen bits.
|
||||
let (perm, access) = if frozen {
|
||||
(new_perm.freeze_perm, new_perm.freeze_access)
|
||||
} else {
|
||||
(new_perm.nonfreeze_perm, new_perm.nonfreeze_access)
|
||||
};
|
||||
let sifa = perm.strongest_idempotent_foreign_access(protected);
|
||||
// NOTE: Currently, `access` is false if and only if `perm` is Cell, so this `if`
|
||||
// doesn't not change whether any code is UB or not. We could just always use
|
||||
// `new_accessed` and everything would stay the same. But that seems conceptually
|
||||
// odd, so we keep the initial "accessed" bit of the `LocationState` in sync with whether
|
||||
// a read access is performed below.
|
||||
if access {
|
||||
*loc = LocationState::new_accessed(perm, sifa);
|
||||
let new_loc = if access {
|
||||
LocationState::new_accessed(perm, sifa)
|
||||
} else {
|
||||
*loc = LocationState::new_non_accessed(perm, sifa);
|
||||
}
|
||||
}
|
||||
LocationState::new_non_accessed(perm, sifa)
|
||||
};
|
||||
|
||||
// Some reborrows incur a read access to the parent.
|
||||
if access {
|
||||
// Store initial permissions.
|
||||
for (_loc_range, loc) in perms_map.iter_mut(range.start, range.size) {
|
||||
*loc = new_loc;
|
||||
}
|
||||
|
||||
interp_ok(())
|
||||
})?;
|
||||
|
||||
// Allow lazily writing to surrounding data if we found an `UnsafeCell`.
|
||||
if has_unsafe_cell { new_perm.nonfreeze_perm } else { new_perm.freeze_perm }
|
||||
};
|
||||
|
||||
let alloc_extra = this.get_alloc_extra(alloc_id)?;
|
||||
let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
|
||||
|
||||
for (perm_range, perm) in perms_map.iter_mut_all() {
|
||||
if perm.is_accessed() {
|
||||
// Some reborrows incur a read access to the parent.
|
||||
// Adjust range to be relative to allocation start (rather than to `place`).
|
||||
let mut range_in_alloc = range;
|
||||
range_in_alloc.start += base_offset;
|
||||
let range_in_alloc = AllocRange {
|
||||
start: Size::from_bytes(perm_range.start) + base_offset,
|
||||
size: Size::from_bytes(perm_range.end - perm_range.start),
|
||||
};
|
||||
|
||||
tree_borrows.perform_access(
|
||||
orig_tag,
|
||||
|
|
@ -382,7 +427,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
)?;
|
||||
|
||||
// Also inform the data race model (but only if any bytes are actually affected).
|
||||
if range.size.bytes() > 0 {
|
||||
if range_in_alloc.size.bytes() > 0 {
|
||||
if let Some(data_race) = alloc_extra.data_race.as_vclocks_ref() {
|
||||
data_race.read(
|
||||
alloc_id,
|
||||
|
|
@ -394,8 +439,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
}
|
||||
}
|
||||
interp_ok(())
|
||||
})?;
|
||||
}
|
||||
|
||||
// Record the parent-child pair in the tree.
|
||||
tree_borrows.new_child(
|
||||
|
|
@ -403,8 +447,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
orig_tag,
|
||||
new_tag,
|
||||
perms_map,
|
||||
// Allow lazily writing to surrounding data if we found an `UnsafeCell`.
|
||||
if has_unsafe_cell { new_perm.nonfreeze_perm } else { new_perm.freeze_perm },
|
||||
default_perm,
|
||||
protected,
|
||||
span,
|
||||
)?;
|
||||
|
|
|
|||
|
|
@ -682,7 +682,10 @@ impl<'tcx> MiriMachine<'tcx> {
|
|||
),
|
||||
];
|
||||
if self.borrow_tracker.as_ref().is_some_and(|b| {
|
||||
matches!(b.borrow().borrow_tracker_method(), BorrowTrackerMethod::TreeBorrows)
|
||||
matches!(
|
||||
b.borrow().borrow_tracker_method(),
|
||||
BorrowTrackerMethod::TreeBorrows { .. }
|
||||
)
|
||||
}) {
|
||||
v.push(
|
||||
note!("Tree Borrows does not support integer-to-pointer casts, so the program is likely to go wrong when this pointer gets used")
|
||||
|
|
|
|||
|
|
@ -148,9 +148,8 @@ pub struct MiriConfig {
|
|||
pub report_progress: Option<u32>,
|
||||
/// Whether Stacked Borrows and Tree Borrows retagging should recurse into fields of datatypes.
|
||||
pub retag_fields: RetagFields,
|
||||
/// The location of a shared object file to load when calling external functions
|
||||
/// FIXME! consider allowing users to specify paths to multiple files, or to a directory
|
||||
pub native_lib: Option<PathBuf>,
|
||||
/// The location of the shared object files to load when calling external functions
|
||||
pub native_lib: Vec<PathBuf>,
|
||||
/// Run a garbage collector for BorTags every N basic blocks.
|
||||
pub gc_interval: u32,
|
||||
/// The number of CPUs to be reported by miri.
|
||||
|
|
@ -197,7 +196,7 @@ impl Default for MiriConfig {
|
|||
preemption_rate: 0.01, // 1%
|
||||
report_progress: None,
|
||||
retag_fields: RetagFields::Yes,
|
||||
native_lib: None,
|
||||
native_lib: vec![],
|
||||
gc_interval: 10_000,
|
||||
num_cpus: 1,
|
||||
page_size: None,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#![feature(unqualified_local_imports)]
|
||||
#![feature(derive_coerce_pointee)]
|
||||
#![feature(arbitrary_self_types)]
|
||||
#![feature(file_lock)]
|
||||
// Configure clippy and other lints
|
||||
#![allow(
|
||||
clippy::collapsible_else_if,
|
||||
|
|
@ -113,7 +114,9 @@ pub use crate::borrow_tracker::stacked_borrows::{
|
|||
EvalContextExt as _, Item, Permission, Stack, Stacks,
|
||||
};
|
||||
pub use crate::borrow_tracker::tree_borrows::{EvalContextExt as _, Tree};
|
||||
pub use crate::borrow_tracker::{BorTag, BorrowTrackerMethod, EvalContextExt as _, RetagFields};
|
||||
pub use crate::borrow_tracker::{
|
||||
BorTag, BorrowTrackerMethod, EvalContextExt as _, RetagFields, TreeBorrowsParams,
|
||||
};
|
||||
pub use crate::clock::{Instant, MonotonicClock};
|
||||
pub use crate::concurrency::cpu_affinity::MAX_CPUS;
|
||||
pub use crate::concurrency::data_race::{
|
||||
|
|
|
|||
|
|
@ -558,9 +558,9 @@ pub struct MiriMachine<'tcx> {
|
|||
|
||||
/// Handle of the optional shared object file for native functions.
|
||||
#[cfg(unix)]
|
||||
pub native_lib: Option<(libloading::Library, std::path::PathBuf)>,
|
||||
pub native_lib: Vec<(libloading::Library, std::path::PathBuf)>,
|
||||
#[cfg(not(unix))]
|
||||
pub native_lib: Option<!>,
|
||||
pub native_lib: Vec<!>,
|
||||
|
||||
/// Run a garbage collector for BorTags every N basic blocks.
|
||||
pub(crate) gc_interval: u32,
|
||||
|
|
@ -720,7 +720,7 @@ impl<'tcx> MiriMachine<'tcx> {
|
|||
extern_statics: FxHashMap::default(),
|
||||
rng: RefCell::new(rng),
|
||||
#[cfg(target_os = "linux")]
|
||||
allocator: if config.native_lib.is_some() {
|
||||
allocator: if !config.native_lib.is_empty() {
|
||||
Some(Rc::new(RefCell::new(crate::alloc::isolated_alloc::IsolatedAlloc::new())))
|
||||
} else { None },
|
||||
tracked_alloc_ids: config.tracked_alloc_ids.clone(),
|
||||
|
|
@ -732,7 +732,7 @@ impl<'tcx> MiriMachine<'tcx> {
|
|||
basic_block_count: 0,
|
||||
monotonic_clock: MonotonicClock::new(config.isolated_op == IsolatedOp::Allow),
|
||||
#[cfg(unix)]
|
||||
native_lib: config.native_lib.as_ref().map(|lib_file_path| {
|
||||
native_lib: config.native_lib.iter().map(|lib_file_path| {
|
||||
let host_triple = rustc_session::config::host_tuple();
|
||||
let target_triple = tcx.sess.opts.target_triple.tuple();
|
||||
// Check if host target == the session target.
|
||||
|
|
@ -752,11 +752,11 @@ impl<'tcx> MiriMachine<'tcx> {
|
|||
},
|
||||
lib_file_path.clone(),
|
||||
)
|
||||
}),
|
||||
}).collect(),
|
||||
#[cfg(not(unix))]
|
||||
native_lib: config.native_lib.as_ref().map(|_| {
|
||||
native_lib: config.native_lib.iter().map(|_| {
|
||||
panic!("calling functions from native libraries via FFI is only supported on Unix")
|
||||
}),
|
||||
}).collect(),
|
||||
gc_interval: config.gc_interval,
|
||||
since_gc: 0,
|
||||
num_cpus: config.num_cpus,
|
||||
|
|
|
|||
|
|
@ -15,11 +15,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// This is given by `alignof(max_align_t)`. The following list is taken from
|
||||
// `library/std/src/sys/alloc/mod.rs` (where this is called `MIN_ALIGN`) and should
|
||||
// be kept in sync.
|
||||
let os = this.tcx.sess.target.os.as_ref();
|
||||
let max_fundamental_align = match this.tcx.sess.target.arch.as_ref() {
|
||||
"x86" | "arm" | "loongarch32" | "mips" | "mips32r6" | "powerpc" | "powerpc64"
|
||||
| "wasm32" => 8,
|
||||
"x86_64" | "aarch64" | "mips64" | "mips64r6" | "s390x" | "sparc64" | "loongarch64" =>
|
||||
16,
|
||||
"riscv32" if matches!(os, "espidf" | "zkvm") => 4,
|
||||
"xtensa" if matches!(os, "espidf") => 4,
|
||||
"x86" | "arm" | "m68k" | "csky" | "loongarch32" | "mips" | "mips32r6" | "powerpc"
|
||||
| "powerpc64" | "sparc" | "wasm32" | "hexagon" | "riscv32" | "xtensa" => 8,
|
||||
"x86_64" | "aarch64" | "arm64ec" | "loongarch64" | "mips64" | "mips64r6" | "s390x"
|
||||
| "sparc64" | "riscv64" | "wasm64" => 16,
|
||||
arch => bug!("unsupported target architecture for malloc: `{}`", arch),
|
||||
};
|
||||
// The C standard only requires sufficient alignment for any *type* with size less than or
|
||||
|
|
|
|||
|
|
@ -169,8 +169,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
_ => throw_unsup_format!("unknown `miri_resolve_frame` flags {}", flags),
|
||||
}
|
||||
|
||||
this.write_scalar(Scalar::from_u32(lineno), &this.project_field(dest, FieldIdx::from_u32(2))?)?;
|
||||
this.write_scalar(Scalar::from_u32(colno), &this.project_field(dest, FieldIdx::from_u32(3))?)?;
|
||||
this.write_scalar(
|
||||
Scalar::from_u32(lineno),
|
||||
&this.project_field(dest, FieldIdx::from_u32(2))?,
|
||||
)?;
|
||||
this.write_scalar(
|
||||
Scalar::from_u32(colno),
|
||||
&this.project_field(dest, FieldIdx::from_u32(3))?,
|
||||
)?;
|
||||
|
||||
// Support a 4-field struct for now - this is deprecated
|
||||
// and slated for removal.
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
|
||||
// First deal with any external C functions in linked .so file.
|
||||
#[cfg(unix)]
|
||||
if this.machine.native_lib.as_ref().is_some() {
|
||||
if !this.machine.native_lib.is_empty() {
|
||||
use crate::shims::native_lib::EvalContextExt as _;
|
||||
// An Ok(false) here means that the function being called was not exported
|
||||
// by the specified `.so` file; we should continue and check if it corresponds to
|
||||
|
|
|
|||
|
|
@ -87,31 +87,35 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
|
||||
/// Get the pointer to the function of the specified name in the shared object file,
|
||||
/// if it exists. The function must be in the shared object file specified: we do *not*
|
||||
/// return pointers to functions in dependencies of the library.
|
||||
/// if it exists. The function must be in one of the shared object files specified:
|
||||
/// we do *not* return pointers to functions in dependencies of libraries.
|
||||
fn get_func_ptr_explicitly_from_lib(&mut self, link_name: Symbol) -> Option<CodePtr> {
|
||||
let this = self.eval_context_mut();
|
||||
// Try getting the function from the shared library.
|
||||
let (lib, lib_path) = this.machine.native_lib.as_ref().unwrap();
|
||||
let func: libloading::Symbol<'_, unsafe extern "C" fn()> =
|
||||
unsafe { lib.get(link_name.as_str().as_bytes()).ok()? };
|
||||
#[expect(clippy::as_conversions)] // fn-ptr to raw-ptr cast needs `as`.
|
||||
let fn_ptr = *func.deref() as *mut std::ffi::c_void;
|
||||
// Try getting the function from one of the shared libraries.
|
||||
for (lib, lib_path) in &this.machine.native_lib {
|
||||
let Ok(func): Result<libloading::Symbol<'_, unsafe extern "C" fn()>, _> =
|
||||
(unsafe { lib.get(link_name.as_str().as_bytes()) })
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
#[expect(clippy::as_conversions)] // fn-ptr to raw-ptr cast needs `as`.
|
||||
let fn_ptr = *func.deref() as *mut std::ffi::c_void;
|
||||
|
||||
// FIXME: this is a hack!
|
||||
// The `libloading` crate will automatically load system libraries like `libc`.
|
||||
// On linux `libloading` is based on `dlsym`: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#202
|
||||
// and `dlsym`(https://linux.die.net/man/3/dlsym) looks through the dependency tree of the
|
||||
// library if it can't find the symbol in the library itself.
|
||||
// So, in order to check if the function was actually found in the specified
|
||||
// `machine.external_so_lib` we need to check its `dli_fname` and compare it to
|
||||
// the specified SO file path.
|
||||
// This code is a reimplementation of the mechanism for getting `dli_fname` in `libloading`,
|
||||
// from: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#411
|
||||
// using the `libc` crate where this interface is public.
|
||||
let mut info = std::mem::MaybeUninit::<libc::Dl_info>::zeroed();
|
||||
unsafe {
|
||||
if libc::dladdr(fn_ptr, info.as_mut_ptr()) != 0 {
|
||||
// FIXME: this is a hack!
|
||||
// The `libloading` crate will automatically load system libraries like `libc`.
|
||||
// On linux `libloading` is based on `dlsym`: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#202
|
||||
// and `dlsym`(https://linux.die.net/man/3/dlsym) looks through the dependency tree of the
|
||||
// library if it can't find the symbol in the library itself.
|
||||
// So, in order to check if the function was actually found in the specified
|
||||
// `machine.external_so_lib` we need to check its `dli_fname` and compare it to
|
||||
// the specified SO file path.
|
||||
// This code is a reimplementation of the mechanism for getting `dli_fname` in `libloading`,
|
||||
// from: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#411
|
||||
// using the `libc` crate where this interface is public.
|
||||
let mut info = std::mem::MaybeUninit::<libc::Dl_info>::zeroed();
|
||||
unsafe {
|
||||
let res = libc::dladdr(fn_ptr, info.as_mut_ptr());
|
||||
assert!(res != 0, "failed to load info about function we already loaded");
|
||||
let info = info.assume_init();
|
||||
#[cfg(target_os = "cygwin")]
|
||||
let fname_ptr = info.dli_fname.as_ptr();
|
||||
|
|
@ -121,13 +125,15 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
if std::ffi::CStr::from_ptr(fname_ptr).to_str().unwrap()
|
||||
!= lib_path.to_str().unwrap()
|
||||
{
|
||||
return None;
|
||||
// The function is not actually in this .so, check the next one.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return a pointer to the function.
|
||||
Some(CodePtr(fn_ptr))
|
||||
// Return a pointer to the function.
|
||||
return Some(CodePtr(fn_ptr));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
use std::borrow::Cow;
|
||||
use std::fs::{
|
||||
DirBuilder, File, FileType, OpenOptions, ReadDir, read_dir, remove_dir, remove_file, rename,
|
||||
DirBuilder, File, FileType, OpenOptions, ReadDir, TryLockError, read_dir, remove_dir,
|
||||
remove_file, rename,
|
||||
};
|
||||
use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
@ -89,103 +90,27 @@ impl UnixFileDescription for FileHandle {
|
|||
communicate_allowed: bool,
|
||||
op: FlockOp,
|
||||
) -> InterpResult<'tcx, io::Result<()>> {
|
||||
// cfg(bootstrap)
|
||||
macro_rules! cfg_select_dispatch {
|
||||
($($tokens:tt)*) => {
|
||||
#[cfg(bootstrap)]
|
||||
cfg_match! { $($tokens)* }
|
||||
|
||||
#[cfg(not(bootstrap))]
|
||||
cfg_select! { $($tokens)* }
|
||||
};
|
||||
}
|
||||
|
||||
assert!(communicate_allowed, "isolation should have prevented even opening a file");
|
||||
cfg_select_dispatch! {
|
||||
all(target_family = "unix", not(target_os = "solaris")) => {
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
use FlockOp::*;
|
||||
// We always use non-blocking call to prevent interpreter from being blocked
|
||||
let (host_op, lock_nb) = match op {
|
||||
SharedLock { nonblocking } => (libc::LOCK_SH | libc::LOCK_NB, nonblocking),
|
||||
ExclusiveLock { nonblocking } => (libc::LOCK_EX | libc::LOCK_NB, nonblocking),
|
||||
Unlock => (libc::LOCK_UN, false),
|
||||
};
|
||||
|
||||
let fd = self.file.as_raw_fd();
|
||||
let ret = unsafe { libc::flock(fd, host_op) };
|
||||
let res = match ret {
|
||||
0 => Ok(()),
|
||||
-1 => {
|
||||
let err = io::Error::last_os_error();
|
||||
if !lock_nb && err.kind() == io::ErrorKind::WouldBlock {
|
||||
throw_unsup_format!("blocking `flock` is not currently supported");
|
||||
}
|
||||
Err(err)
|
||||
}
|
||||
ret => panic!("Unexpected return value from flock: {ret}"),
|
||||
};
|
||||
interp_ok(res)
|
||||
use FlockOp::*;
|
||||
// We must not block the interpreter loop, so we always `try_lock`.
|
||||
let (res, nonblocking) = match op {
|
||||
SharedLock { nonblocking } => (self.file.try_lock_shared(), nonblocking),
|
||||
ExclusiveLock { nonblocking } => (self.file.try_lock(), nonblocking),
|
||||
Unlock => {
|
||||
return interp_ok(self.file.unlock());
|
||||
}
|
||||
target_family = "windows" => {
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
};
|
||||
|
||||
use windows_sys::Win32::Foundation::{
|
||||
ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, FALSE, TRUE,
|
||||
};
|
||||
use windows_sys::Win32::Storage::FileSystem::{
|
||||
LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx, UnlockFile,
|
||||
};
|
||||
|
||||
let fh = self.file.as_raw_handle();
|
||||
|
||||
use FlockOp::*;
|
||||
let (ret, lock_nb) = match op {
|
||||
SharedLock { nonblocking } | ExclusiveLock { nonblocking } => {
|
||||
// We always use non-blocking call to prevent interpreter from being blocked
|
||||
let mut flags = LOCKFILE_FAIL_IMMEDIATELY;
|
||||
if matches!(op, ExclusiveLock { .. }) {
|
||||
flags |= LOCKFILE_EXCLUSIVE_LOCK;
|
||||
}
|
||||
let ret = unsafe { LockFileEx(fh, flags, 0, !0, !0, &mut std::mem::zeroed()) };
|
||||
(ret, nonblocking)
|
||||
}
|
||||
Unlock => {
|
||||
let ret = unsafe { UnlockFile(fh, 0, 0, !0, !0) };
|
||||
(ret, false)
|
||||
}
|
||||
};
|
||||
|
||||
let res = match ret {
|
||||
TRUE => Ok(()),
|
||||
FALSE => {
|
||||
let mut err = io::Error::last_os_error();
|
||||
// This only runs on Windows hosts so we can use `raw_os_error`.
|
||||
// We have to be careful not to forward that error code to target code.
|
||||
let code: u32 = err.raw_os_error().unwrap().try_into().unwrap();
|
||||
if matches!(code, ERROR_IO_PENDING | ERROR_LOCK_VIOLATION) {
|
||||
if lock_nb {
|
||||
// The io error mapping does not know about these error codes,
|
||||
// so we translate it to `WouldBlock` manually.
|
||||
let desc = format!("LockFileEx wouldblock error: {err}");
|
||||
err = io::Error::new(io::ErrorKind::WouldBlock, desc);
|
||||
} else {
|
||||
throw_unsup_format!("blocking `flock` is not currently supported");
|
||||
}
|
||||
}
|
||||
Err(err)
|
||||
}
|
||||
_ => panic!("Unexpected return value: {ret}"),
|
||||
};
|
||||
interp_ok(res)
|
||||
}
|
||||
_ => {
|
||||
let _ = op;
|
||||
throw_unsup_format!(
|
||||
"flock is supported only on UNIX (except Solaris) and Windows hosts"
|
||||
);
|
||||
}
|
||||
match res {
|
||||
Ok(()) => interp_ok(Ok(())),
|
||||
Err(TryLockError::Error(err)) => interp_ok(Err(err)),
|
||||
Err(TryLockError::WouldBlock) =>
|
||||
if nonblocking {
|
||||
interp_ok(Err(ErrorKind::WouldBlock.into()))
|
||||
} else {
|
||||
throw_unsup_format!("blocking `flock` is not currently supported");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -286,7 +286,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
|
||||
if op == epoll_ctl_add || op == epoll_ctl_mod {
|
||||
// Read event bitmask and data from epoll_event passed by caller.
|
||||
let mut events = this.read_scalar(&this.project_field(&event, FieldIdx::ZERO)?)?.to_u32()?;
|
||||
let mut events =
|
||||
this.read_scalar(&this.project_field(&event, FieldIdx::ZERO)?)?.to_u32()?;
|
||||
let data = this.read_scalar(&this.project_field(&event, FieldIdx::ONE)?)?.to_u64()?;
|
||||
|
||||
// Unset the flag we support to discover if any unsupported flags are used.
|
||||
|
|
|
|||
|
|
@ -88,16 +88,17 @@ fn test_dup() {
|
|||
let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>();
|
||||
unsafe {
|
||||
let fd = libc::open(name_ptr, libc::O_RDONLY);
|
||||
let new_fd = libc::dup(fd);
|
||||
let new_fd2 = libc::dup2(fd, 8);
|
||||
|
||||
let mut first_buf = [0u8; 4];
|
||||
libc::read(fd, first_buf.as_mut_ptr() as *mut libc::c_void, 4);
|
||||
assert_eq!(&first_buf, b"dup ");
|
||||
|
||||
let new_fd = libc::dup(fd);
|
||||
let mut second_buf = [0u8; 4];
|
||||
libc::read(new_fd, second_buf.as_mut_ptr() as *mut libc::c_void, 4);
|
||||
assert_eq!(&second_buf, b"and ");
|
||||
|
||||
let new_fd2 = libc::dup2(fd, 8);
|
||||
let mut third_buf = [0u8; 4];
|
||||
libc::read(new_fd2, third_buf.as_mut_ptr() as *mut libc::c_void, 4);
|
||||
assert_eq!(&third_buf, b"dup2");
|
||||
|
|
|
|||
99
src/tools/miri/tests/pass/both_borrows/smallvec.rs
Normal file
99
src/tools/miri/tests/pass/both_borrows/smallvec.rs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
//! This test represents a core part of `SmallVec`'s `extend_impl`.
|
||||
//! What makes it interesting as a test is that it relies on Stacked Borrow's "quirk"
|
||||
//! in a fundamental, hard-to-fix-without-full-trees way.
|
||||
|
||||
//@revisions: stack tree
|
||||
//@[tree]compile-flags: -Zmiri-tree-borrows
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::{ManuallyDrop, MaybeUninit};
|
||||
use std::ptr::NonNull;
|
||||
|
||||
#[repr(C)]
|
||||
pub union RawSmallVec<T, const N: usize> {
|
||||
inline: ManuallyDrop<MaybeUninit<[T; N]>>,
|
||||
heap: (NonNull<T>, usize),
|
||||
}
|
||||
|
||||
impl<T, const N: usize> RawSmallVec<T, N> {
|
||||
const fn new() -> Self {
|
||||
Self::new_inline(MaybeUninit::uninit())
|
||||
}
|
||||
|
||||
const fn new_inline(inline: MaybeUninit<[T; N]>) -> Self {
|
||||
Self { inline: ManuallyDrop::new(inline) }
|
||||
}
|
||||
|
||||
const fn as_mut_ptr_inline(&mut self) -> *mut T {
|
||||
(unsafe { &raw mut self.inline }) as *mut T
|
||||
}
|
||||
|
||||
const unsafe fn as_mut_ptr_heap(&mut self) -> *mut T {
|
||||
self.heap.0.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy)]
|
||||
struct TaggedLen(usize);
|
||||
|
||||
impl TaggedLen {
|
||||
pub const fn new(len: usize, on_heap: bool, is_zst: bool) -> Self {
|
||||
if is_zst {
|
||||
debug_assert!(!on_heap);
|
||||
TaggedLen(len)
|
||||
} else {
|
||||
debug_assert!(len < isize::MAX as usize);
|
||||
TaggedLen((len << 1) | on_heap as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn on_heap(self, is_zst: bool) -> bool {
|
||||
if is_zst { false } else { (self.0 & 1_usize) == 1 }
|
||||
}
|
||||
|
||||
pub const fn value(self, is_zst: bool) -> usize {
|
||||
if is_zst { self.0 } else { self.0 >> 1 }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct SmallVec<T, const N: usize> {
|
||||
len: TaggedLen,
|
||||
raw: RawSmallVec<T, N>,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, const N: usize> SmallVec<T, N> {
|
||||
pub const fn new() -> SmallVec<T, N> {
|
||||
Self {
|
||||
len: TaggedLen::new(0, false, Self::is_zst()),
|
||||
raw: RawSmallVec::new(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
const fn is_zst() -> bool {
|
||||
size_of::<T>() == 0
|
||||
}
|
||||
|
||||
pub const fn as_mut_ptr(&mut self) -> *mut T {
|
||||
if self.len.on_heap(Self::is_zst()) {
|
||||
// SAFETY: see above
|
||||
unsafe { self.raw.as_mut_ptr_heap() }
|
||||
} else {
|
||||
self.raw.as_mut_ptr_inline()
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn len(&self) -> usize {
|
||||
self.len.value(Self::is_zst())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut v = SmallVec::<i32, 4>::new();
|
||||
let ptr = v.as_mut_ptr();
|
||||
let _len = v.len(); // this call incurs a reborrow which just barely does not invalidate `ptr`
|
||||
unsafe { ptr.write(0) };
|
||||
}
|
||||
|
|
@ -45,7 +45,6 @@ macro_rules! assert_approx_eq {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
/// From IEEE 754 a Signaling NaN for single precision has the following representation:
|
||||
/// ```
|
||||
/// s | 1111 1111 | 0x..x
|
||||
|
|
@ -1387,7 +1386,6 @@ fn test_non_determinism() {
|
|||
frem_algebraic, frem_fast, fsub_algebraic, fsub_fast,
|
||||
};
|
||||
use std::{f32, f64};
|
||||
// TODO: Also test powi and powf when the non-determinism is implemented for them
|
||||
|
||||
/// Ensure that the operation is non-deterministic
|
||||
#[track_caller]
|
||||
|
|
@ -1427,21 +1425,23 @@ fn test_non_determinism() {
|
|||
}
|
||||
pub fn test_operations_f32(a: f32, b: f32) {
|
||||
test_operations_f!(a, b);
|
||||
// FIXME: temporarily disabled as it breaks std tests.
|
||||
// ensure_nondet(|| a.log(b));
|
||||
// ensure_nondet(|| a.exp());
|
||||
// ensure_nondet(|| 10f32.exp2());
|
||||
// ensure_nondet(|| f32::consts::E.ln());
|
||||
// FIXME: some are temporarily disabled as it breaks std tests.
|
||||
ensure_nondet(|| a.powf(b));
|
||||
ensure_nondet(|| a.powi(2));
|
||||
ensure_nondet(|| a.log(b));
|
||||
ensure_nondet(|| a.exp());
|
||||
ensure_nondet(|| 10f32.exp2());
|
||||
ensure_nondet(|| f32::consts::E.ln());
|
||||
ensure_nondet(|| 10f32.log10());
|
||||
ensure_nondet(|| 8f32.log2());
|
||||
// ensure_nondet(|| 1f32.ln_1p());
|
||||
// ensure_nondet(|| 10f32.log10());
|
||||
// ensure_nondet(|| 8f32.log2());
|
||||
// ensure_nondet(|| 27.0f32.cbrt());
|
||||
// ensure_nondet(|| 3.0f32.hypot(4.0f32));
|
||||
// ensure_nondet(|| 1f32.sin());
|
||||
// ensure_nondet(|| 0f32.cos());
|
||||
// // On i686-pc-windows-msvc , these functions are implemented by calling the `f64` version,
|
||||
// // which means the little rounding errors Miri introduces are discard by the cast down to `f32`.
|
||||
// // Just skip the test for them.
|
||||
ensure_nondet(|| 1f32.sin());
|
||||
ensure_nondet(|| 1f32.cos());
|
||||
// On i686-pc-windows-msvc , these functions are implemented by calling the `f64` version,
|
||||
// which means the little rounding errors Miri introduces are discarded by the cast down to
|
||||
// `f32`. Just skip the test for them.
|
||||
// if !cfg!(all(target_os = "windows", target_env = "msvc", target_arch = "x86")) {
|
||||
// ensure_nondet(|| 1.0f32.tan());
|
||||
// ensure_nondet(|| 1.0f32.asin());
|
||||
|
|
@ -1462,18 +1462,20 @@ fn test_non_determinism() {
|
|||
}
|
||||
pub fn test_operations_f64(a: f64, b: f64) {
|
||||
test_operations_f!(a, b);
|
||||
// FIXME: temporarily disabled as it breaks std tests.
|
||||
// ensure_nondet(|| a.log(b));
|
||||
// ensure_nondet(|| a.exp());
|
||||
// ensure_nondet(|| 50f64.exp2());
|
||||
// ensure_nondet(|| 3f64.ln());
|
||||
// FIXME: some are temporarily disabled as it breaks std tests.
|
||||
ensure_nondet(|| a.powf(b));
|
||||
ensure_nondet(|| a.powi(2));
|
||||
ensure_nondet(|| a.log(b));
|
||||
ensure_nondet(|| a.exp());
|
||||
ensure_nondet(|| 50f64.exp2());
|
||||
ensure_nondet(|| 3f64.ln());
|
||||
ensure_nondet(|| f64::consts::E.log10());
|
||||
ensure_nondet(|| f64::consts::E.log2());
|
||||
// ensure_nondet(|| 1f64.ln_1p());
|
||||
// ensure_nondet(|| f64::consts::E.log10());
|
||||
// ensure_nondet(|| f64::consts::E.log2());
|
||||
// ensure_nondet(|| 27.0f64.cbrt());
|
||||
// ensure_nondet(|| 3.0f64.hypot(4.0f64));
|
||||
// ensure_nondet(|| 1f64.sin());
|
||||
// ensure_nondet(|| 0f64.cos());
|
||||
ensure_nondet(|| 1f64.sin());
|
||||
ensure_nondet(|| 1f64.cos());
|
||||
// ensure_nondet(|| 1.0f64.tan());
|
||||
// ensure_nondet(|| 1.0f64.asin());
|
||||
// ensure_nondet(|| 5.0f64.acos());
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
#![feature(io_error_more)]
|
||||
#![feature(io_error_uncategorized)]
|
||||
#![feature(file_lock)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::{
|
||||
File, OpenOptions, canonicalize, create_dir, read_dir, remove_dir, remove_dir_all, remove_file,
|
||||
rename,
|
||||
self, File, OpenOptions, create_dir, read_dir, remove_dir, remove_dir_all, remove_file, rename,
|
||||
};
|
||||
use std::io::{Error, ErrorKind, IsTerminal, Read, Result, Seek, SeekFrom, Write};
|
||||
use std::path::Path;
|
||||
|
|
@ -33,6 +33,8 @@ fn main() {
|
|||
test_canonicalize();
|
||||
#[cfg(unix)]
|
||||
test_pread_pwrite();
|
||||
#[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
|
||||
test_flock();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -240,7 +242,7 @@ fn test_canonicalize() {
|
|||
let path = dir_path.join("test_file");
|
||||
drop(File::create(&path).unwrap());
|
||||
|
||||
let p = canonicalize(format!("{}/./test_file", dir_path.to_string_lossy())).unwrap();
|
||||
let p = fs::canonicalize(format!("{}/./test_file", dir_path.to_string_lossy())).unwrap();
|
||||
assert_eq!(p.to_string_lossy().find("/./"), None);
|
||||
|
||||
remove_dir_all(&dir_path).unwrap();
|
||||
|
|
@ -351,3 +353,28 @@ fn test_pread_pwrite() {
|
|||
f.read_exact(&mut buf1).unwrap();
|
||||
assert_eq!(&buf1, b" m");
|
||||
}
|
||||
|
||||
// This function does seem to exist on Illumos but std does not expose it there.
|
||||
#[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
|
||||
fn test_flock() {
|
||||
let bytes = b"Hello, World!\n";
|
||||
let path = utils::prepare_with_content("miri_test_fs_flock.txt", bytes);
|
||||
let file1 = OpenOptions::new().read(true).write(true).open(&path).unwrap();
|
||||
let file2 = OpenOptions::new().read(true).write(true).open(&path).unwrap();
|
||||
|
||||
// Test that we can apply many shared locks
|
||||
file1.lock_shared().unwrap();
|
||||
file2.lock_shared().unwrap();
|
||||
// Test that shared lock prevents exclusive lock
|
||||
assert!(matches!(file1.try_lock().unwrap_err(), fs::TryLockError::WouldBlock));
|
||||
// Unlock shared lock
|
||||
file1.unlock().unwrap();
|
||||
file2.unlock().unwrap();
|
||||
// Take exclusive lock
|
||||
file1.lock().unwrap();
|
||||
// Test that shared lock prevents exclusive and shared locks
|
||||
assert!(matches!(file2.try_lock().unwrap_err(), fs::TryLockError::WouldBlock));
|
||||
assert!(matches!(file2.try_lock_shared().unwrap_err(), fs::TryLockError::WouldBlock));
|
||||
// Unlock exclusive lock
|
||||
file1.unlock().unwrap();
|
||||
}
|
||||
|
|
|
|||
34
src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.rs
Normal file
34
src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
//! The same as `tests/fail/tree-borrows/cell-inside-struct` but with
|
||||
//! precise tracking of interior mutability disabled.
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-no-precise-interior-mut
|
||||
#[path = "../../utils/mod.rs"]
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
struct Foo {
|
||||
field1: u32,
|
||||
field2: Cell<u32>,
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let root = Foo { field1: 42, field2: Cell::new(88) };
|
||||
unsafe {
|
||||
let a = &root;
|
||||
|
||||
name!(a as *const Foo, "a");
|
||||
|
||||
let a: *const Foo = a as *const Foo;
|
||||
let a: *mut Foo = a as *mut Foo;
|
||||
|
||||
let alloc_id = alloc_id!(a);
|
||||
print_state!(alloc_id);
|
||||
|
||||
// Writing to `field2`, which is interior mutable, should be allowed.
|
||||
(*a).field2.set(10);
|
||||
|
||||
// Writing to `field1` should be allowed because it also has the `Cell` permission.
|
||||
(*a).field1 = 88;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
──────────────────────────────────────────────────
|
||||
Warning: this tree is indicative only. Some tags may have been hidden.
|
||||
0.. 8
|
||||
| Act | └─┬──<TAG=root of the allocation>
|
||||
|?Cel | └────<TAG=a>
|
||||
──────────────────────────────────────────────────
|
||||
Loading…
Add table
Add a link
Reference in a new issue