Auto merge of #113151 - RalfJung:miri, r=RalfJung,oli-obk

update Miri

r? `@ghost`
This commit is contained in:
bors 2023-06-29 10:55:40 +00:00
commit e69c7306e2
206 changed files with 3206 additions and 474 deletions

View File

@ -363,7 +363,6 @@ dependencies = [
"cargo_metadata",
"directories",
"rustc-build-sysroot",
"rustc-workspace-hack",
"rustc_tools_util",
"rustc_version",
"serde",
@ -2191,7 +2190,6 @@ dependencies = [
"measureme",
"rand",
"regex",
"rustc-workspace-hack",
"rustc_version",
"smallvec",
"ui_test",
@ -2920,12 +2918,6 @@ dependencies = [
"std",
]
[[package]]
name = "rustc-workspace-hack"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb"
[[package]]
name = "rustc_abi"
version = "0.0.0"
@ -5096,9 +5088,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
[[package]]
name = "ui_test"
version = "0.10.0"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "191a442639ea102fa62671026047e51d574bfda44b7fdf32151d7314624c1cd2"
checksum = "24a2e70adc9d18b9b4dd80ea57aeec447103c6fbb354a07c080adad451c645e1"
dependencies = [
"bstr",
"cargo-platform",

View File

@ -156,13 +156,13 @@ jobs:
- name: mark the job as a failure
run: exit 1
# Send a Zulip notification when a cron job fails
cron-fail-notify:
name: cronjob failure notification
runs-on: ubuntu-latest
needs: [build, style]
if: github.event_name == 'schedule' && (failure() || cancelled())
steps:
# Send a Zulip notification
- name: Install zulip-send
run: pip3 install zulip
- name: Send Zulip notification
@ -185,3 +185,27 @@ jobs:
Sincerely,
The Miri Cronjobs Bot' \
--user $ZULIP_BOT_EMAIL --api-key $ZULIP_API_TOKEN --site https://rust-lang.zulipchat.com
# Attempt to auto-sync with rustc
- name: install josh-proxy
run: cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r22.12.06
- name: start josh-proxy
run: josh-proxy --local=$HOME/.cache/josh --remote=https://github.com --no-background &
- name: setup bot git name and email
run: |
git config --global user.name 'The Miri Conjob Bot'
git config --global user.email 'miri@cron.bot'
- name: get changes from rustc
run: ./miri rustc-pull
- name: format changes (if any)
run: |
./miri toolchain
./miri fmt --check || (./miri fmt && git commit -am "fmt")
- name: Push changes to a branch
run: |
git switch -c "rustup$(date -u +%Y-%m)"
git push
- name: Create Pull Request
run: gh pr create -B master --title 'Automatic sync from rustc' --body ''
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -442,7 +442,6 @@ dependencies = [
"measureme",
"rand",
"regex",
"rustc-workspace-hack",
"rustc_version",
"smallvec",
"ui_test",
@ -628,12 +627,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-workspace-hack"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb"
[[package]]
name = "rustc_version"
version = "0.4.0"
@ -849,9 +842,9 @@ dependencies = [
[[package]]
name = "ui_test"
version = "0.10.0"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "191a442639ea102fa62671026047e51d574bfda44b7fdf32151d7314624c1cd2"
checksum = "24a2e70adc9d18b9b4dd80ea57aeec447103c6fbb354a07c080adad451c645e1"
dependencies = [
"bstr",
"cargo-platform",

View File

@ -24,10 +24,6 @@ log = "0.4"
rand = "0.8"
smallvec = "1.7"
# A noop dependency that changes in the Rust repository, it's a bit of a hack.
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
# for more information.
rustc-workspace-hack = "1.0.0"
measureme = "10.0.0"
ctrlc = "3.2.5"
@ -40,7 +36,7 @@ libloading = "0.7"
[dev-dependencies]
colored = "2"
ui_test = "0.10"
ui_test = "0.11.6"
rustc_version = "0.4"
# Features chosen to match those required by env_logger, to avoid rebuilds
regex = { version = "1.5.5", default-features = false, features = ["perf", "std"] }

View File

@ -435,6 +435,9 @@ to Miri failing to detect cases of undefined behavior in a program.
so with this flag.
* `-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.
* `-Zmiri-unique-is-unique` performs additional aliasing checks for `core::ptr::Unique` to ensure
that it could theoretically be considered `noalias`. This flag is experimental and has
an effect only when used with `-Zmiri-tree-borrows`.
[function ABI]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier
@ -575,6 +578,7 @@ Definite bugs found:
* [`crossbeam-epoch` calling `assume_init` on a partly-initialized `MaybeUninit`](https://github.com/crossbeam-rs/crossbeam/pull/779)
* [`integer-encoding` dereferencing a misaligned pointer](https://github.com/dermesser/integer-encoding-rs/pull/23)
* [`rkyv` constructing a `Box<[u8]>` from an overaligned allocation](https://github.com/rkyv/rkyv/commit/a9417193a34757e12e24263178be8b2eebb72456)
* [Data race in `arc-swap`](https://github.com/vorner/arc-swap/issues/76)
* [Data race in `thread::scope`](https://github.com/rust-lang/rust/issues/98498)
* [`regex` incorrectly handling unaligned `Vec<u8>` buffers](https://www.reddit.com/r/rust/comments/vq3mmu/comment/ienc7t0?context=3)
* [Incorrect use of `compare_exchange_weak` in `once_cell`](https://github.com/matklad/once_cell/issues/186)

View File

@ -30,7 +30,6 @@ dependencies = [
"cargo_metadata",
"directories",
"rustc-build-sysroot",
"rustc-workspace-hack",
"rustc_tools_util",
"rustc_version",
"serde",
@ -235,12 +234,6 @@ dependencies = [
"tempfile",
]
[[package]]
name = "rustc-workspace-hack"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb"
[[package]]
name = "rustc_tools_util"
version = "0.3.0"

View File

@ -20,11 +20,6 @@ serde_json = "1.0.40"
cargo_metadata = "0.15.0"
rustc-build-sysroot = "0.4.1"
# A noop dependency that changes in the Rust repository, it's a bit of a hack.
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
# for more information.
rustc-workspace-hack = "1.0.0"
# Enable some feature flags that dev-dependencies need but dependencies
# do not. This makes `./miri install` after `./miri build` faster.
serde = { version = "*", features = ["derive"] }

View File

@ -1 +1 @@
33c3d101280c8eb3cd8af421bfb56a8afcc3881d
75726cae37317c7262b69d3e9fd11a3496a88d04

View File

@ -31,9 +31,9 @@ use rustc_middle::{
query::{ExternProviders, LocalCrate},
ty::TyCtxt,
};
use rustc_session::{EarlyErrorHandler, CtfeBacktrace};
use rustc_session::config::{OptLevel, CrateType, ErrorOutputType};
use rustc_session::config::{CrateType, ErrorOutputType, OptLevel};
use rustc_session::search_paths::PathKind;
use rustc_session::{CtfeBacktrace, EarlyErrorHandler};
use miri::{BacktraceStyle, BorrowTrackerMethod, ProvenanceMode, RetagFields};
@ -343,6 +343,8 @@ fn main() {
miri_config.borrow_tracker = None;
} else if arg == "-Zmiri-tree-borrows" {
miri_config.borrow_tracker = Some(BorrowTrackerMethod::TreeBorrows);
} else if arg == "-Zmiri-unique-is-unique" {
miri_config.unique_is_unique = true;
} else if arg == "-Zmiri-disable-data-race-detector" {
miri_config.data_race_detector = false;
miri_config.weak_memory_emulation = false;
@ -560,6 +562,14 @@ fn main() {
rustc_args.push(arg);
}
}
// `-Zmiri-unique-is-unique` should only be used with `-Zmiri-tree-borrows`
if miri_config.unique_is_unique
&& !matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows))
{
show_error!(
"-Zmiri-unique-is-unique only has an effect when -Zmiri-tree-borrows is also used"
);
}
debug!("rustc arguments: {:?}", rustc_args);
debug!("crate arguments: {:?}", miri_config.args);

View File

@ -103,6 +103,8 @@ pub struct GlobalStateInner {
pub tracked_call_ids: FxHashSet<CallId>,
/// Whether to recurse into datatypes when searching for pointers to retag.
pub retag_fields: RetagFields,
/// Whether `core::ptr::Unique` gets special (`Box`-like) handling.
pub unique_is_unique: bool,
}
impl VisitTags for GlobalStateInner {
@ -170,6 +172,7 @@ impl GlobalStateInner {
tracked_pointer_tags: FxHashSet<BorTag>,
tracked_call_ids: FxHashSet<CallId>,
retag_fields: RetagFields,
unique_is_unique: bool,
) -> Self {
GlobalStateInner {
borrow_tracker_method,
@ -180,6 +183,7 @@ impl GlobalStateInner {
tracked_pointer_tags,
tracked_call_ids,
retag_fields,
unique_is_unique,
}
}
@ -244,6 +248,7 @@ impl BorrowTrackerMethod {
config.tracked_pointer_tags.clone(),
config.tracked_call_ids.clone(),
config.retag_fields,
config.unique_is_unique,
))
}
}

View File

@ -12,16 +12,46 @@ use crate::borrow_tracker::tree_borrows::{
use crate::borrow_tracker::{AccessKind, ProtectorKind};
use crate::*;
/// Cause of an access: either a real access or one
/// inserted by Tree Borrows due to a reborrow or a deallocation.
#[derive(Clone, Copy, Debug)]
pub enum AccessCause {
Explicit(AccessKind),
Reborrow,
Dealloc,
}
impl fmt::Display for AccessCause {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Explicit(kind) => write!(f, "{kind}"),
Self::Reborrow => write!(f, "reborrow"),
Self::Dealloc => write!(f, "deallocation"),
}
}
}
impl AccessCause {
fn print_as_access(self, is_foreign: bool) -> String {
let rel = if is_foreign { "foreign" } else { "child" };
match self {
Self::Explicit(kind) => format!("{rel} {kind}"),
Self::Reborrow => format!("reborrow (acting as a {rel} read access)"),
Self::Dealloc => format!("deallocation (acting as a {rel} write access)"),
}
}
}
/// Complete data for an event:
#[derive(Clone, Debug)]
pub struct Event {
/// Transformation of permissions that occured because of this event
/// Transformation of permissions that occured because of this event.
pub transition: PermTransition,
/// Kind of the access that triggered this event
pub access_kind: AccessKind,
/// Relative position of the tag to the one used for the access
/// Kind of the access that triggered this event.
pub access_cause: AccessCause,
/// Relative position of the tag to the one used for the access.
pub is_foreign: bool,
/// User-visible range of the access
/// User-visible range of the access.
pub access_range: AllocRange,
/// The transition recorded by this event only occured on a subrange of
/// `access_range`: a single access on `access_range` triggers several events,
@ -36,7 +66,7 @@ pub struct Event {
/// the `TbError`, which should satisfy
/// `event.transition_range.contains(error.error_offset)`.
pub transition_range: Range<u64>,
/// Line of code that triggered this event
/// Line of code that triggered this event.
pub span: Span,
}
@ -83,8 +113,8 @@ impl HistoryData {
self.events.push((Some(created.0.data()), msg_creation));
for &Event {
transition,
access_kind,
is_foreign,
access_cause,
access_range,
span,
transition_range: _,
@ -92,8 +122,10 @@ impl HistoryData {
{
// NOTE: `transition_range` is explicitly absent from the error message, it has no significance
// to the user. The meaningful one is `access_range`.
self.events.push((Some(span.data()), format!("{this} later transitioned to {endpoint} due to a {rel} {access_kind} at offsets {access_range:?}", endpoint = transition.endpoint(), rel = if is_foreign { "foreign" } else { "child" })));
self.events.push((None, format!("this corresponds to {}", transition.summary())));
let access = access_cause.print_as_access(is_foreign);
self.events.push((Some(span.data()), format!("{this} later transitioned to {endpoint} due to a {access} at offsets {access_range:?}", endpoint = transition.endpoint())));
self.events
.push((None, format!("this transition corresponds to {}", transition.summary())));
}
}
}
@ -238,9 +270,8 @@ pub(super) struct TbError<'node> {
/// On accesses rejected due to insufficient permissions, this is the
/// tag that lacked those permissions.
pub conflicting_info: &'node NodeDebugInfo,
/// Whether this was a Read or Write access. This field is ignored
/// when the error was triggered by a deallocation.
pub access_kind: AccessKind,
// What kind of access caused this error (read, write, reborrow, deallocation)
pub access_cause: AccessCause,
/// Which tag the access that caused this error was made through, i.e.
/// which tag was used to read/write/deallocate.
pub accessed_info: &'node NodeDebugInfo,
@ -250,38 +281,39 @@ impl TbError<'_> {
/// Produce a UB error.
pub fn build<'tcx>(self) -> InterpError<'tcx> {
use TransitionError::*;
let kind = self.access_kind;
let cause = self.access_cause;
let accessed = self.accessed_info;
let conflicting = self.conflicting_info;
let accessed_is_conflicting = accessed.tag == conflicting.tag;
let title = format!("{cause} through {accessed} is forbidden");
let (title, details, conflicting_tag_name) = match self.error_kind {
ChildAccessForbidden(perm) => {
let conflicting_tag_name =
if accessed_is_conflicting { "accessed" } else { "conflicting" };
let title = format!("{kind} through {accessed} is forbidden");
let mut details = Vec::new();
if !accessed_is_conflicting {
details.push(format!(
"the accessed tag {accessed} is a child of the conflicting tag {conflicting}"
));
}
let access = cause.print_as_access(/* is_foreign */ false);
details.push(format!(
"the {conflicting_tag_name} tag {conflicting} has state {perm} which forbids child {kind}es"
"the {conflicting_tag_name} tag {conflicting} has state {perm} which forbids this {access}"
));
(title, details, conflicting_tag_name)
}
ProtectedTransition(transition) => {
let conflicting_tag_name = "protected";
let title = format!("{kind} through {accessed} is forbidden");
let access = cause.print_as_access(/* is_foreign */ true);
let details = vec![
format!(
"the accessed tag {accessed} is foreign to the {conflicting_tag_name} tag {conflicting} (i.e., it is not a child)"
),
format!(
"the access would cause the {conflicting_tag_name} tag {conflicting} to transition {transition}"
"this {access} would cause the {conflicting_tag_name} tag {conflicting} to transition {transition}"
),
format!(
"this is {loss}, which is not allowed for protected tags",
"this transition would be {loss}, which is not allowed for protected tags",
loss = transition.summary(),
),
];
@ -289,7 +321,6 @@ impl TbError<'_> {
}
ProtectedDealloc => {
let conflicting_tag_name = "strongly protected";
let title = format!("deallocation through {accessed} is forbidden");
let details = vec![
format!(
"the allocation of the accessed tag {accessed} also contains the {conflicting_tag_name} tag {conflicting}"

View File

@ -11,6 +11,7 @@ use rustc_middle::{
Ty,
},
};
use rustc_span::def_id::DefId;
use crate::*;
@ -62,7 +63,14 @@ impl<'tcx> Tree {
};
let global = machine.borrow_tracker.as_ref().unwrap();
let span = machine.current_span();
self.perform_access(access_kind, tag, range, global, span)
self.perform_access(
access_kind,
tag,
range,
global,
span,
diagnostics::AccessCause::Explicit(access_kind),
)
}
/// Check that this pointer has permission to deallocate this range.
@ -92,9 +100,9 @@ impl<'tcx> Tree {
/// Policy for a new borrow.
#[derive(Debug, Clone, Copy)]
struct NewPermission {
/// Whether this borrow requires a read access on its parent.
/// `perform_read_access` is `true` for all pointers marked `dereferenceable`.
perform_read_access: bool,
/// Optionally ignore the actual size to do a zero-size reborrow.
/// If this is set then `dereferenceable` is not enforced.
zero_size: bool,
/// Which permission should the pointer start with.
initial_state: Permission,
/// Whether this pointer is part of the arguments of a function call.
@ -121,20 +129,19 @@ impl<'tcx> NewPermission {
// `&`s, which are excluded above.
_ => return None,
};
// This field happens to be redundant since right now we always do a read,
// but it could be useful in the future.
let perform_read_access = true;
let protector = (kind == RetagKind::FnEntry).then_some(ProtectorKind::StrongProtector);
Some(Self { perform_read_access, initial_state, protector })
Some(Self { zero_size: false, initial_state, protector })
}
// Boxes are not handled by `from_ref_ty`, they need special behavior
// implemented here.
fn from_box_ty(
/// Compute permission for `Box`-like type (`Box` always, and also `Unique` if enabled).
/// These pointers allow deallocation so need a different kind of protector not handled
/// by `from_ref_ty`.
fn from_unique_ty(
ty: Ty<'tcx>,
kind: RetagKind,
cx: &crate::MiriInterpCx<'_, 'tcx>,
zero_size: bool,
) -> Option<Self> {
let pointee = ty.builtin_deref(true).unwrap().ty;
pointee.is_unpin(*cx.tcx, cx.param_env()).then_some(()).map(|()| {
@ -142,7 +149,7 @@ impl<'tcx> NewPermission {
// because it is valid to deallocate it within the function.
let ty_is_freeze = ty.is_freeze(*cx.tcx, cx.param_env());
Self {
perform_read_access: true,
zero_size,
initial_state: Permission::new_unique_2phase(ty_is_freeze),
protector: (kind == RetagKind::FnEntry).then_some(ProtectorKind::WeakProtector),
}
@ -194,6 +201,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
Ok(())
};
trace!("Reborrow of size {:?}", ptr_size);
let (alloc_id, base_offset, parent_prov) = if ptr_size > Size::ZERO {
this.ptr_get_alloc_id(place.ptr)?
} else {
@ -269,11 +277,18 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
let range = alloc_range(base_offset, ptr_size);
let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
if new_perm.perform_read_access {
// Count this reborrow as a read access
// All reborrows incur a (possibly zero-sized) read access to the parent
{
let global = &this.machine.borrow_tracker.as_ref().unwrap();
let span = this.machine.current_span();
tree_borrows.perform_access(AccessKind::Read, orig_tag, range, global, span)?;
tree_borrows.perform_access(
AccessKind::Read,
orig_tag,
range,
global,
span,
diagnostics::AccessCause::Reborrow,
)?;
if let Some(data_race) = alloc_extra.data_race.as_ref() {
data_race.read(alloc_id, range, &this.machine)?;
}
@ -294,12 +309,19 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
// We want a place for where the ptr *points to*, so we get one.
let place = this.ref_to_mplace(val)?;
// Get a lower bound of the size of this place.
// (When `extern type` are involved, use the size of the known prefix.)
let size = this
.size_and_align_of_mplace(&place)?
.map(|(size, _)| size)
.unwrap_or(place.layout.size);
// Determine the size of the reborrow.
// For most types this is the entire size of the place, however
// - when `extern type` is involved we use the size of the known prefix,
// - if the pointer is not reborrowed (raw pointer) or if `zero_size` is set
// then we override the size to do a zero-length reborrow.
let reborrow_size = match new_perm {
Some(NewPermission { zero_size: false, .. }) =>
this.size_and_align_of_mplace(&place)?
.map(|(size, _)| size)
.unwrap_or(place.layout.size),
_ => Size::from_bytes(0),
};
trace!("Creating new permission: {:?} with size {:?}", new_perm, reborrow_size);
// This new tag is not guaranteed to actually be used.
//
@ -310,7 +332,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
// Compute the actual reborrow.
let reborrowed = this.tb_reborrow(&place, size, new_perm, new_tag)?;
let reborrowed = this.tb_reborrow(&place, reborrow_size, new_perm, new_tag)?;
// Adjust pointer.
let new_place = place.map_provenance(|p| {
@ -345,10 +367,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
val: &ImmTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
let this = self.eval_context_mut();
let new_perm = if let &ty::Ref(_, pointee, mutability) = val.layout.ty.kind() {
NewPermission::from_ref_ty(pointee, mutability, kind, this)
} else {
None
let new_perm = match val.layout.ty.kind() {
&ty::Ref(_, pointee, mutability) =>
NewPermission::from_ref_ty(pointee, mutability, kind, this),
_ => None,
};
this.tb_retag_reference(val, new_perm)
}
@ -360,8 +382,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
place: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let retag_fields = this.machine.borrow_tracker.as_mut().unwrap().get_mut().retag_fields;
let mut visitor = RetagVisitor { ecx: this, kind, retag_fields };
let options = this.machine.borrow_tracker.as_mut().unwrap().get_mut();
let retag_fields = options.retag_fields;
let unique_did =
options.unique_is_unique.then(|| this.tcx.lang_items().ptr_unique()).flatten();
let mut visitor = RetagVisitor { ecx: this, kind, retag_fields, unique_did };
return visitor.visit_value(place);
// The actual visitor.
@ -369,6 +394,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
ecx: &'ecx mut MiriInterpCx<'mir, 'tcx>,
kind: RetagKind,
retag_fields: RetagFields,
unique_did: Option<DefId>,
}
impl<'ecx, 'mir, 'tcx> RetagVisitor<'ecx, 'mir, 'tcx> {
#[inline(always)] // yes this helps in our benchmarks
@ -393,8 +419,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
self.ecx
}
/// Regardless of how `Unique` is handled, Boxes are always reborrowed.
/// When `Unique` is also reborrowed, then it behaves exactly like `Box`
/// except for the fact that `Box` has a non-zero-sized reborrow.
fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
let new_perm = NewPermission::from_box_ty(place.layout.ty, self.kind, self.ecx);
let new_perm = NewPermission::from_unique_ty(
place.layout.ty,
self.kind,
self.ecx,
/* zero_size */ false,
);
self.retag_ptr_inplace(place, new_perm)
}
@ -426,6 +460,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// even if field retagging is not enabled. *shrug*)
self.walk_value(place)?;
}
ty::Adt(adt, _) if self.unique_did == Some(adt.did()) => {
let place = inner_ptr_of_unique(self.ecx, place)?;
let new_perm = NewPermission::from_unique_ty(
place.layout.ty,
self.kind,
self.ecx,
/* zero_size */ true,
);
self.retag_ptr_inplace(&place, new_perm)?;
}
_ => {
// Not a reference/pointer/box. Only recurse if configured appropriately.
let recurse = match self.retag_fields {
@ -442,7 +486,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}
}
}
Ok(())
}
}
@ -472,7 +515,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// FIXME: do we truly want a 2phase borrow here?
let new_perm = Some(NewPermission {
initial_state: Permission::new_unique_2phase(/*freeze*/ false),
perform_read_access: true,
zero_size: false,
protector: Some(ProtectorKind::StrongProtector),
});
let val = this.tb_retag_reference(&val, new_perm)?;
@ -538,3 +581,27 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
tree_borrows.give_pointer_debug_name(tag, nth_parent, name)
}
}
/// Takes a place for a `Unique` and turns it into a place with the inner raw pointer.
/// I.e. input is what you get from the visitor upon encountering an `adt` that is `Unique`,
/// and output can be used by `retag_ptr_inplace`.
fn inner_ptr_of_unique<'tcx>(
ecx: &mut MiriInterpCx<'_, 'tcx>,
place: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, PlaceTy<'tcx, Provenance>> {
// Follows the same layout as `interpret/visitor.rs:walk_value` for `Box` in
// `rustc_const_eval`, just with one fewer layer.
// Here we have a `Unique(NonNull(*mut), PhantomData)`
assert_eq!(place.layout.fields.count(), 2, "Unique must have exactly 2 fields");
let (nonnull, phantom) = (ecx.place_field(place, 0)?, ecx.place_field(place, 1)?);
assert!(
phantom.layout.ty.ty_adt_def().is_some_and(|adt| adt.is_phantom_data()),
"2nd field of `Unique` should be `PhantomData` but is `{:?}`",
phantom.layout.ty,
);
// Now down to `NonNull(*mut)`
assert_eq!(nonnull.layout.fields.count(), 1, "NonNull must have exactly 1 field");
let ptr = ecx.place_field(&nonnull, 0)?;
// Finally a plain `*mut`
Ok(ptr)
}

View File

@ -349,7 +349,14 @@ impl<'tcx> Tree {
global: &GlobalState,
span: Span, // diagnostics
) -> InterpResult<'tcx> {
self.perform_access(AccessKind::Write, tag, access_range, global, span)?;
self.perform_access(
AccessKind::Write,
tag,
access_range,
global,
span,
diagnostics::AccessCause::Dealloc,
)?;
for (perms_range, perms) in self.rperms.iter_mut(access_range.start, access_range.size) {
TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms }
.traverse_parents_this_children_others(
@ -368,7 +375,7 @@ impl<'tcx> Tree {
let ErrHandlerArgs { error_kind, conflicting_info, accessed_info } = args;
TbError {
conflicting_info,
access_kind: AccessKind::Write,
access_cause: diagnostics::AccessCause::Dealloc,
error_offset: perms_range.start,
error_kind,
accessed_info,
@ -391,7 +398,8 @@ impl<'tcx> Tree {
tag: BorTag,
access_range: AllocRange,
global: &GlobalState,
span: Span, // diagnostics
span: Span, // diagnostics
access_cause: diagnostics::AccessCause, // diagnostics
) -> InterpResult<'tcx> {
for (perms_range, perms) in self.rperms.iter_mut(access_range.start, access_range.size) {
TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms }
@ -456,8 +464,8 @@ impl<'tcx> Tree {
if !transition.is_noop() {
node.debug_info.history.push(diagnostics::Event {
transition,
access_kind,
is_foreign: rel_pos.is_foreign(),
access_cause,
access_range,
transition_range: perms_range.clone(),
span,
@ -472,7 +480,7 @@ impl<'tcx> Tree {
let ErrHandlerArgs { error_kind, conflicting_info, accessed_info } = args;
TbError {
conflicting_info,
access_kind,
access_cause,
error_offset: perms_range.start,
error_kind,
accessed_info,

View File

@ -714,7 +714,8 @@ impl VClockAlloc {
MiriMemoryKind::Rust
| MiriMemoryKind::Miri
| MiriMemoryKind::C
| MiriMemoryKind::WinHeap,
| MiriMemoryKind::WinHeap
| MiriMemoryKind::Mmap,
)
| MemoryKind::Stack => {
let (alloc_index, clocks) = global.current_thread_state(thread_mgr);

View File

@ -2,6 +2,7 @@ use std::collections::VecDeque;
use std::num::NonZeroU32;
use rustc_index::Idx;
use rustc_middle::ty::layout::TyAndLayout;
use super::sync::EvalContextExtPriv as _;
use super::thread::MachineCallback;
@ -94,10 +95,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn init_once_get_or_create_id(
&mut self,
lock_op: &OpTy<'tcx, Provenance>,
lock_layout: TyAndLayout<'tcx>,
offset: u64,
) -> InterpResult<'tcx, InitOnceId> {
let this = self.eval_context_mut();
this.init_once_get_or_create(|ecx, next_id| ecx.get_or_create_id(next_id, lock_op, offset))
this.init_once_get_or_create(|ecx, next_id| {
ecx.get_or_create_id(next_id, lock_op, lock_layout, offset)
})
}
/// Provides the closure with the next InitOnceId. Creates that InitOnce if the closure returns None,

View File

@ -6,6 +6,7 @@ use log::trace;
use rustc_data_structures::fx::FxHashMap;
use rustc_index::{Idx, IndexVec};
use rustc_middle::ty::layout::TyAndLayout;
use super::init_once::InitOnce;
use super::vector_clock::VClock;
@ -200,11 +201,12 @@ pub(super) trait EvalContextExtPriv<'mir, 'tcx: 'mir>:
&mut self,
next_id: Id,
lock_op: &OpTy<'tcx, Provenance>,
lock_layout: TyAndLayout<'tcx>,
offset: u64,
) -> InterpResult<'tcx, Option<Id>> {
let this = self.eval_context_mut();
let value_place =
this.deref_operand_and_offset(lock_op, offset, this.machine.layouts.u32)?;
this.deref_operand_and_offset(lock_op, offset, lock_layout, this.machine.layouts.u32)?;
// Since we are lazy, this update has to be atomic.
let (old, success) = this
@ -278,28 +280,37 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn mutex_get_or_create_id(
&mut self,
lock_op: &OpTy<'tcx, Provenance>,
lock_layout: TyAndLayout<'tcx>,
offset: u64,
) -> InterpResult<'tcx, MutexId> {
let this = self.eval_context_mut();
this.mutex_get_or_create(|ecx, next_id| ecx.get_or_create_id(next_id, lock_op, offset))
this.mutex_get_or_create(|ecx, next_id| {
ecx.get_or_create_id(next_id, lock_op, lock_layout, offset)
})
}
fn rwlock_get_or_create_id(
&mut self,
lock_op: &OpTy<'tcx, Provenance>,
lock_layout: TyAndLayout<'tcx>,
offset: u64,
) -> InterpResult<'tcx, RwLockId> {
let this = self.eval_context_mut();
this.rwlock_get_or_create(|ecx, next_id| ecx.get_or_create_id(next_id, lock_op, offset))
this.rwlock_get_or_create(|ecx, next_id| {
ecx.get_or_create_id(next_id, lock_op, lock_layout, offset)
})
}
fn condvar_get_or_create_id(
&mut self,
lock_op: &OpTy<'tcx, Provenance>,
lock_layout: TyAndLayout<'tcx>,
offset: u64,
) -> InterpResult<'tcx, CondvarId> {
let this = self.eval_context_mut();
this.condvar_get_or_create(|ecx, next_id| ecx.get_or_create_id(next_id, lock_op, offset))
this.condvar_get_or_create(|ecx, next_id| {
ecx.get_or_create_id(next_id, lock_op, lock_layout, offset)
})
}
#[inline]

View File

@ -90,6 +90,10 @@ pub struct MiriConfig {
pub validate: bool,
/// Determines if Stacked Borrows or Tree Borrows is enabled.
pub borrow_tracker: Option<BorrowTrackerMethod>,
/// Whether `core::ptr::Unique` receives special treatment.
/// If `true` then `Unique` is reborrowed with its own new tag and permission,
/// otherwise `Unique` is just another raw pointer.
pub unique_is_unique: bool,
/// Controls alignment checking.
pub check_alignment: AlignmentCheck,
/// Controls function [ABI](Abi) checking.
@ -156,6 +160,7 @@ impl Default for MiriConfig {
env: vec![],
validate: true,
borrow_tracker: Some(BorrowTrackerMethod::StackedBorrows),
unique_is_unique: false,
check_alignment: AlignmentCheck::Int,
check_abi: true,
isolated_op: IsolatedOp::Reject(RejectOpWith::Abort),

View File

@ -730,20 +730,51 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}
}
/// Dereference a pointer operand to a place using `layout` instead of the pointer's declared type
fn deref_operand_as(
&self,
op: &OpTy<'tcx, Provenance>,
layout: TyAndLayout<'tcx>,
) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
let this = self.eval_context_ref();
let ptr = this.read_pointer(op)?;
let mplace = MPlaceTy::from_aligned_ptr(ptr, layout);
this.check_mplace(mplace)?;
Ok(mplace)
}
fn deref_pointer_as(
&self,
val: &ImmTy<'tcx, Provenance>,
layout: TyAndLayout<'tcx>,
) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
let this = self.eval_context_ref();
let mut mplace = this.ref_to_mplace(val)?;
mplace.layout = layout;
mplace.align = layout.align.abi;
Ok(mplace)
}
/// Calculates the MPlaceTy given the offset and layout of an access on an operand
fn deref_operand_and_offset(
&self,
op: &OpTy<'tcx, Provenance>,
offset: u64,
layout: TyAndLayout<'tcx>,
base_layout: TyAndLayout<'tcx>,
value_layout: TyAndLayout<'tcx>,
) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
let this = self.eval_context_ref();
let op_place = this.deref_operand(op)?; // FIXME: we still deref with the original type!
let op_place = this.deref_operand_as(op, base_layout)?;
let offset = Size::from_bytes(offset);
// Ensure that the access is within bounds.
assert!(op_place.layout.size >= offset + layout.size);
let value_place = op_place.offset(offset, layout, this)?;
assert!(base_layout.size >= offset + value_layout.size);
let value_place = op_place.offset(offset, value_layout, this)?;
Ok(value_place)
}
@ -751,10 +782,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
&self,
op: &OpTy<'tcx, Provenance>,
offset: u64,
layout: TyAndLayout<'tcx>,
base_layout: TyAndLayout<'tcx>,
value_layout: TyAndLayout<'tcx>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_ref();
let value_place = this.deref_operand_and_offset(op, offset, layout)?;
let value_place = this.deref_operand_and_offset(op, offset, base_layout, value_layout)?;
this.read_scalar(&value_place.into())
}
@ -763,10 +795,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
op: &OpTy<'tcx, Provenance>,
offset: u64,
value: impl Into<Scalar<Provenance>>,
layout: TyAndLayout<'tcx>,
base_layout: TyAndLayout<'tcx>,
value_layout: TyAndLayout<'tcx>,
) -> InterpResult<'tcx, ()> {
let this = self.eval_context_mut();
let value_place = this.deref_operand_and_offset(op, offset, layout)?;
let value_place = this.deref_operand_and_offset(op, offset, base_layout, value_layout)?;
this.write_scalar(value, &value_place.into())
}

View File

@ -8,6 +8,7 @@
#![feature(nonzero_ops)]
#![feature(local_key_cell_methods)]
#![feature(round_ties_even)]
#![feature(os_str_bytes)]
// Configure clippy and other lints
#![allow(
clippy::collapsible_else_if,

View File

@ -112,6 +112,8 @@ pub enum MiriMemoryKind {
/// Memory for thread-local statics.
/// This memory may leak.
Tls,
/// Memory mapped directly by the program
Mmap,
}
impl From<MiriMemoryKind> for MemoryKind<MiriMemoryKind> {
@ -127,7 +129,7 @@ impl MayLeak for MiriMemoryKind {
use self::MiriMemoryKind::*;
match self {
Rust | Miri | C | WinHeap | Runtime => false,
Machine | Global | ExternStatic | Tls => true,
Machine | Global | ExternStatic | Tls | Mmap => true,
}
}
}
@ -145,6 +147,7 @@ impl fmt::Display for MiriMemoryKind {
Global => write!(f, "global (static or const)"),
ExternStatic => write!(f, "extern static"),
Tls => write!(f, "thread-local static"),
Mmap => write!(f, "mmap"),
}
}
}
@ -726,6 +729,15 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
// will panic when given the file.
drop(self.profiler.take());
}
pub(crate) fn round_up_to_multiple_of_page_size(&self, length: u64) -> Option<u64> {
#[allow(clippy::arithmetic_side_effects)] // page size is nonzero
(length.checked_add(self.page_size - 1)? / self.page_size).checked_mul(self.page_size)
}
pub(crate) fn page_align(&self) -> Align {
Align::from_bytes(self.page_size).unwrap()
}
}
impl VisitTags for MiriMachine<'_, '_> {

View File

@ -409,14 +409,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// &mut self,
// arg1: &OpTy<'tcx, Provenance>,
// arg2: &OpTy<'tcx, Provenance>,
// arg3: &OpTy<'tcx, Provenance>)
// arg3: &OpTy<'tcx, Provenance>,
// arg4: &OpTy<'tcx, Provenance>)
// -> InterpResult<'tcx, Scalar<Provenance>> {
// let this = self.eval_context_mut();
//
// // First thing: load all the arguments. Details depend on the shim.
// let arg1 = this.read_scalar(arg1)?.to_u32()?;
// let arg2 = this.read_pointer(arg2)?; // when you need to work with the pointer directly
// let arg3 = this.deref_operand(arg3)?; // when you want to load/store through the pointer at its declared type
// let arg3 = this.deref_operand_as(arg3, this.libc_ty_layout("some_libc_struct"))?; // when you want to load/store
// // through the pointer and supply the type information yourself
// let arg4 = this.deref_operand(arg4)?; // when you want to load/store through the pointer and trust
// // the user-given type (which you shouldn't usually do)
//
// // ...
//

View File

@ -17,28 +17,13 @@ pub enum PathConversion {
TargetToHost,
}
#[cfg(unix)]
pub fn os_str_to_bytes<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, &[u8]> {
Ok(os_str.as_bytes())
}
#[cfg(not(unix))]
pub fn os_str_to_bytes<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, &[u8]> {
// On non-unix platforms the best we can do to transform bytes from/to OS strings is to do the
// intermediate transformation into strings. Which invalidates non-utf8 paths that are actually
// valid.
os_str
.to_str()
.map(|s| s.as_bytes())
.ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into())
}
#[cfg(unix)]
pub fn bytes_to_os_str<'tcx>(bytes: &[u8]) -> InterpResult<'tcx, &OsStr> {
Ok(OsStr::from_bytes(bytes))
}
#[cfg(not(unix))]
pub fn bytes_to_os_str<'tcx>(bytes: &[u8]) -> InterpResult<'tcx, &OsStr> {
// We cannot use `from_os_str_bytes_unchecked` here since we can't trust `bytes`.
let s = std::str::from_utf8(bytes)
.map_err(|_| err_unsup_format!("{:?} is not a valid utf-8 string", bytes))?;
Ok(OsStr::new(s))
@ -97,7 +82,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
ptr: Pointer<Option<Provenance>>,
size: u64,
) -> InterpResult<'tcx, (bool, u64)> {
let bytes = os_str_to_bytes(os_str)?;
let bytes = os_str.as_os_str_bytes();
self.eval_context_mut().write_c_str(bytes, ptr, size)
}

View File

@ -25,6 +25,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.assert_target_os_is_unix("clock_gettime");
let clk_id = this.read_scalar(clk_id_op)?.to_i32()?;
let tp = this.deref_operand_as(tp_op, this.libc_ty_layout("timespec"))?;
let absolute_clocks;
let mut relative_clocks;
@ -76,7 +77,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let tv_sec = duration.as_secs();
let tv_nsec = duration.subsec_nanos();
this.write_int_fields(&[tv_sec.into(), tv_nsec.into()], &this.deref_operand(tp_op)?)?;
this.write_int_fields(&[tv_sec.into(), tv_nsec.into()], &tp)?;
Ok(Scalar::from_i32(0))
}
@ -91,6 +92,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.assert_target_os_is_unix("gettimeofday");
this.check_no_isolation("`gettimeofday`")?;
let tv = this.deref_operand_as(tv_op, this.libc_ty_layout("timeval"))?;
// Using tz is obsolete and should always be null
let tz = this.read_pointer(tz_op)?;
if !this.ptr_is_null(tz)? {
@ -103,7 +106,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let tv_sec = duration.as_secs();
let tv_usec = duration.subsec_micros();
this.write_int_fields(&[tv_sec.into(), tv_usec.into()], &this.deref_operand(tv_op)?)?;
this.write_int_fields(&[tv_sec.into(), tv_usec.into()], &tv)?;
Ok(0)
}
@ -118,6 +121,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.assert_target_os("windows", "GetSystemTimeAsFileTime");
this.check_no_isolation("`GetSystemTimeAsFileTime`")?;
let filetime = this.deref_operand_as(LPFILETIME_op, this.windows_ty_layout("FILETIME"))?;
let NANOS_PER_SEC = this.eval_windows_u64("time", "NANOS_PER_SEC");
let INTERVALS_PER_SEC = this.eval_windows_u64("time", "INTERVALS_PER_SEC");
let INTERVALS_TO_UNIX_EPOCH = this.eval_windows_u64("time", "INTERVALS_TO_UNIX_EPOCH");
@ -131,10 +136,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let dwLowDateTime = u32::try_from(duration_ticks & 0x00000000FFFFFFFF).unwrap();
let dwHighDateTime = u32::try_from((duration_ticks & 0xFFFFFFFF00000000) >> 32).unwrap();
this.write_int_fields(
&[dwLowDateTime.into(), dwHighDateTime.into()],
&this.deref_operand(LPFILETIME_op)?,
)?;
this.write_int_fields(&[dwLowDateTime.into(), dwHighDateTime.into()], &filetime)?;
Ok(())
}
@ -177,7 +179,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// and thus 10^9 counts per second.
this.write_scalar(
Scalar::from_i64(1_000_000_000),
&this.deref_operand(lpFrequency_op)?.into(),
&this.deref_operand_as(lpFrequency_op, this.machine.layouts.u64)?.into(),
)?;
Ok(Scalar::from_i32(-1)) // Return non-zero on success
}
@ -204,7 +206,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.assert_target_os("macos", "mach_timebase_info");
let info = this.deref_operand(info_op)?;
let info = this.deref_operand_as(info_op, this.libc_ty_layout("mach_timebase_info"))?;
// Since our emulated ticks in `mach_absolute_time` *are* nanoseconds,
// no scaling needs to happen.
@ -223,7 +225,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.assert_target_os_is_unix("nanosleep");
let duration = match this.read_timespec(&this.deref_operand(req_op)?)? {
let req = this.deref_operand_as(req_op, this.libc_ty_layout("timespec"))?;
let duration = match this.read_timespec(&req)? {
Some(duration) => duration,
None => {
let einval = this.eval_libc("EINVAL");

View File

@ -10,6 +10,7 @@ use rustc_target::spec::abi::Abi;
use crate::*;
use shims::foreign_items::EmulateByNameResult;
use shims::unix::fs::EvalContextExt as _;
use shims::unix::mem::EvalContextExt as _;
use shims::unix::sync::EvalContextExt as _;
use shims::unix::thread::EvalContextExt as _;
@ -213,6 +214,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}
}
"mmap" => {
let [addr, length, prot, flags, fd, offset] = this.check_shim(abi, Abi::C {unwind: false}, link_name, args)?;
let ptr = this.mmap(addr, length, prot, flags, fd, offset)?;
this.write_scalar(ptr, dest)?;
}
"munmap" => {
let [addr, length] = this.check_shim(abi, Abi::C {unwind: false}, link_name, args)?;
let result = this.munmap(addr, length)?;
this.write_scalar(result, dest)?;
}
// Dynamic symbol loading
"dlsym" => {
let [handle, symbol] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
@ -259,7 +271,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// Thread-local storage
"pthread_key_create" => {
let [key, dtor] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let key_place = this.deref_operand(key)?;
let key_place = this.deref_operand_as(key, this.libc_ty_layout("pthread_key_t"))?;
let dtor = this.read_pointer(dtor)?;
// Extract the function type out of the signature (that seems easier than constructing it ourselves).
@ -520,7 +532,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// Hence we can mostly ignore the input `attr_place`.
let [attr_place, addr_place, size_place] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let _attr_place = this.deref_operand(attr_place)?;
let _attr_place = this.deref_operand_as(attr_place, this.libc_ty_layout("pthread_attr_t"))?;
let addr_place = this.deref_operand(addr_place)?;
let size_place = this.deref_operand(size_place)?;
@ -563,7 +575,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.check_no_isolation("`getpwuid_r`")?;
let uid = this.read_scalar(uid)?.to_u32()?;
let pwd = this.deref_operand(pwd)?;
let pwd = this.deref_operand_as(pwd, this.libc_ty_layout("passwd"))?;
let buf = this.read_pointer(buf)?;
let buflen = this.read_target_usize(buflen)?;
let result = this.deref_operand(result)?;

View File

@ -16,7 +16,6 @@ use rustc_target::abi::{Align, Size};
use crate::shims::os_str::bytes_to_os_str;
use crate::*;
use shims::os_str::os_str_to_bytes;
use shims::time::system_time_to_duration;
#[derive(Debug)]
@ -345,7 +344,8 @@ trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx
let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
let buf = this.deref_operand(buf_op)?;
let buf = this.deref_operand_as(buf_op, this.libc_ty_layout("stat"))?;
this.write_int_fields_named(
&[
("st_dev", 0),
@ -1014,15 +1014,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
return Ok(-1);
}
// Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a
// proper `MemPlace` and then write the results of this function to it. However, the
// `syscall` function is untyped. This means that all the `statx` parameters are provided
// as `isize`s instead of having the proper types. Thus, we have to recover the layout of
// `statxbuf_op` by using the `libc::statx` struct type.
let statxbuf = {
let statx_layout = this.libc_ty_layout("statx");
MPlaceTy::from_aligned_ptr(statxbuf_ptr, statx_layout)
};
let statxbuf = this.deref_operand_as(statxbuf_op, this.libc_ty_layout("statx"))?;
let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
// See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
@ -1339,7 +1331,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let mut name = dir_entry.file_name(); // not a Path as there are no separators!
name.push("\0"); // Add a NUL terminator
let name_bytes = os_str_to_bytes(&name)?;
let name_bytes = name.as_os_str_bytes();
let name_len = u64::try_from(name_bytes.len()).unwrap();
let dirent64_layout = this.libc_ty_layout("dirent64");
@ -1428,7 +1420,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// pub d_name: [c_char; 1024],
// }
let entry_place = this.deref_operand(entry_op)?;
let entry_place = this.deref_operand_as(entry_op, this.libc_ty_layout("dirent"))?;
let name_place = this.mplace_field(&entry_place, 5)?;
let file_name = dir_entry.file_name(); // not a Path as there are no separators!
@ -1444,8 +1436,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
);
}
let entry_place = this.deref_operand(entry_op)?;
// If the host is a Unix system, fill in the inode number with its real value.
// If not, use 0 as a fallback value.
#[cfg(unix)]
@ -1695,7 +1685,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
Cow::Borrowed(resolved.as_ref()),
crate::shims::os_str::PathConversion::HostToTarget,
);
let mut path_bytes = crate::shims::os_str::os_str_to_bytes(resolved.as_ref())?;
let mut path_bytes = resolved.as_os_str_bytes();
let bufsize: usize = bufsize.try_into().unwrap();
if path_bytes.len() > bufsize {
path_bytes = &path_bytes[..bufsize]

View File

@ -71,7 +71,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let epoll_ctl_del = this.eval_libc_i32("EPOLL_CTL_DEL");
if op == epoll_ctl_add || op == epoll_ctl_mod {
let event = this.deref_operand(event)?;
let event = this.deref_operand_as(event, this.libc_ty_layout("epoll_event"))?;
let events = this.mplace_field(&event, 0)?;
let events = this.read_scalar(&events.into())?.to_u32()?;

View File

@ -7,6 +7,7 @@ use crate::*;
use shims::foreign_items::EmulateByNameResult;
use shims::unix::fs::EvalContextExt as _;
use shims::unix::linux::fd::EvalContextExt as _;
use shims::unix::linux::mem::EvalContextExt as _;
use shims::unix::linux::sync::futex;
use shims::unix::sync::EvalContextExt as _;
use shims::unix::thread::EvalContextExt as _;
@ -68,6 +69,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let result = this.eventfd(val, flag)?;
this.write_scalar(result, dest)?;
}
"mremap" => {
let [old_address, old_size, new_size, flags] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let ptr = this.mremap(old_address, old_size, new_size, flags)?;
this.write_scalar(ptr, dest)?;
}
"socketpair" => {
let [domain, type_, protocol, sv] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
@ -191,7 +198,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.read_scalar(pid)?.to_i32()?;
this.read_target_usize(cpusetsize)?;
this.deref_operand(mask)?;
this.deref_operand_as(mask, this.libc_ty_layout("cpu_set_t"))?;
// FIXME: we just return an error; `num_cpus` then falls back to `sysconf`.
let einval = this.eval_libc("EINVAL");
this.set_last_error(einval)?;

View File

@ -0,0 +1,67 @@
//! This follows the pattern in src/shims/unix/mem.rs: We only support uses of mremap that would
//! correspond to valid uses of realloc.
use crate::*;
use rustc_target::abi::Size;
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn mremap(
&mut self,
old_address: &OpTy<'tcx, Provenance>,
old_size: &OpTy<'tcx, Provenance>,
new_size: &OpTy<'tcx, Provenance>,
flags: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let old_address = this.read_target_usize(old_address)?;
let old_size = this.read_target_usize(old_size)?;
let new_size = this.read_target_usize(new_size)?;
let flags = this.read_scalar(flags)?.to_i32()?;
// old_address must be a multiple of the page size
#[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero
if old_address % this.machine.page_size != 0 || new_size == 0 {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
return Ok(this.eval_libc("MAP_FAILED"));
}
if flags & this.eval_libc_i32("MREMAP_FIXED") != 0 {
throw_unsup_format!("Miri does not support mremap wth MREMAP_FIXED");
}
if flags & this.eval_libc_i32("MREMAP_DONTUNMAP") != 0 {
throw_unsup_format!("Miri does not support mremap wth MREMAP_DONTUNMAP");
}
if flags & this.eval_libc_i32("MREMAP_MAYMOVE") == 0 {
// We only support MREMAP_MAYMOVE, so not passing the flag is just a failure
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
return Ok(Scalar::from_maybe_pointer(Pointer::null(), this));
}
let old_address = Machine::ptr_from_addr_cast(this, old_address)?;
let align = this.machine.page_align();
let ptr = this.reallocate_ptr(
old_address,
Some((Size::from_bytes(old_size), align)),
Size::from_bytes(new_size),
align,
MiriMemoryKind::Mmap.into(),
)?;
if let Some(increase) = new_size.checked_sub(old_size) {
// We just allocated this, the access is definitely in-bounds and fits into our address space.
// mmap guarantees new mappings are zero-init.
this.write_bytes_ptr(
ptr.offset(Size::from_bytes(old_size), this).unwrap().into(),
std::iter::repeat(0u8).take(usize::try_from(increase).unwrap()),
)
.unwrap();
}
// Memory mappings are always exposed
Machine::expose_ptr(this, ptr)?;
Ok(Scalar::from_pointer(ptr, this))
}
}

View File

@ -1,4 +1,5 @@
pub mod dlsym;
pub mod fd;
pub mod foreign_items;
pub mod mem;
pub mod sync;

View File

@ -85,8 +85,10 @@ pub fn futex<'tcx>(
return Ok(());
}
// `deref_operand` but not actually dereferencing the ptr yet (it might be NULL!).
let timeout = this.ref_to_mplace(&this.read_immediate(&args[3])?)?;
let timeout = this.deref_pointer_as(
&this.read_immediate(&args[3])?,
this.libc_ty_layout("timespec"),
)?;
let timeout_time = if this.ptr_is_null(timeout.ptr)? {
None
} else {

View File

@ -197,16 +197,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.write_scalar(res, dest)?;
}
// Incomplete shims that we "stub out" just to get pre-main initialization code to work.
// These shims are enabled only when the caller is in the standard library.
"mmap" if this.frame_in_std() => {
// This is a horrible hack, but since the guard page mechanism calls mmap and expects a particular return value, we just give it that value.
let [addr, _, _, _, _, _] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let addr = this.read_scalar(addr)?;
this.write_scalar(addr, dest)?;
}
_ => return Ok(EmulateByNameResult::NotSupported),
};

View File

@ -0,0 +1,158 @@
//! This is an incomplete implementation of mmap/munmap which is restricted in order to be
//! implementable on top of the existing memory system. The point of these function as-written is
//! to allow memory allocators written entirely in Rust to be executed by Miri. This implementation
//! does not support other uses of mmap such as file mappings.
//!
//! mmap/munmap behave a lot like alloc/dealloc, and for simple use they are exactly
//! equivalent. That is the only part we support: no MAP_FIXED or MAP_SHARED or anything
//! else that goes beyond a basic allocation API.
use crate::*;
use rustc_target::abi::Size;
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn mmap(
&mut self,
addr: &OpTy<'tcx, Provenance>,
length: &OpTy<'tcx, Provenance>,
prot: &OpTy<'tcx, Provenance>,
flags: &OpTy<'tcx, Provenance>,
fd: &OpTy<'tcx, Provenance>,
offset: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
// We do not support MAP_FIXED, so the addr argument is always ignored (except for the MacOS hack)
let addr = this.read_target_usize(addr)?;
let length = this.read_target_usize(length)?;
let prot = this.read_scalar(prot)?.to_i32()?;
let flags = this.read_scalar(flags)?.to_i32()?;
let fd = this.read_scalar(fd)?.to_i32()?;
let offset = this.read_target_usize(offset)?;
let map_private = this.eval_libc_i32("MAP_PRIVATE");
let map_anonymous = this.eval_libc_i32("MAP_ANONYMOUS");
let map_shared = this.eval_libc_i32("MAP_SHARED");
let map_fixed = this.eval_libc_i32("MAP_FIXED");
// This is a horrible hack, but on MacOS the guard page mechanism uses mmap
// in a way we do not support. We just give it the return value it expects.
if this.frame_in_std() && this.tcx.sess.target.os == "macos" && (flags & map_fixed) != 0 {
return Ok(Scalar::from_maybe_pointer(Pointer::from_addr_invalid(addr), this));
}
let prot_read = this.eval_libc_i32("PROT_READ");
let prot_write = this.eval_libc_i32("PROT_WRITE");
// First, we do some basic argument validation as required by mmap
if (flags & (map_private | map_shared)).count_ones() != 1 {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
return Ok(Scalar::from_maybe_pointer(Pointer::null(), this));
}
if length == 0 {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
return Ok(Scalar::from_maybe_pointer(Pointer::null(), this));
}
// If a user tries to map a file, we want to loudly inform them that this is not going
// to work. It is possible that POSIX gives us enough leeway to return an error, but the
// outcome for the user (I need to add cfg(miri)) is the same, just more frustrating.
if fd != -1 {
throw_unsup_format!("Miri does not support file-backed memory mappings");
}
// POSIX says:
// [ENOTSUP]
// * MAP_FIXED or MAP_PRIVATE was specified in the flags argument and the implementation
// does not support this functionality.
// * The implementation does not support the combination of accesses requested in the
// prot argument.
//
// Miri doesn't support MAP_FIXED or any any protections other than PROT_READ|PROT_WRITE.
if flags & map_fixed != 0 || prot != prot_read | prot_write {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("ENOTSUP")))?;
return Ok(Scalar::from_maybe_pointer(Pointer::null(), this));
}
// Miri does not support shared mappings, or any of the other extensions that for example
// Linux has added to the flags arguments.
if flags != map_private | map_anonymous {
throw_unsup_format!(
"Miri only supports calls to mmap which set the flags argument to MAP_PRIVATE|MAP_ANONYMOUS"
);
}
// This is only used for file mappings, which we don't support anyway.
if offset != 0 {
throw_unsup_format!("Miri does not support non-zero offsets to mmap");
}
let align = this.machine.page_align();
let map_length = this.machine.round_up_to_multiple_of_page_size(length).unwrap_or(u64::MAX);
let ptr =
this.allocate_ptr(Size::from_bytes(map_length), align, MiriMemoryKind::Mmap.into())?;
// We just allocated this, the access is definitely in-bounds and fits into our address space.
// mmap guarantees new mappings are zero-init.
this.write_bytes_ptr(
ptr.into(),
std::iter::repeat(0u8).take(usize::try_from(map_length).unwrap()),
)
.unwrap();
// Memory mappings don't use provenance, and are always exposed.
Machine::expose_ptr(this, ptr)?;
Ok(Scalar::from_pointer(ptr, this))
}
fn munmap(
&mut self,
addr: &OpTy<'tcx, Provenance>,
length: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let addr = this.read_target_usize(addr)?;
let length = this.read_target_usize(length)?;
// addr must be a multiple of the page size
#[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero
if addr % this.machine.page_size != 0 {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
return Ok(Scalar::from_i32(-1));
}
let length = this.machine.round_up_to_multiple_of_page_size(length).unwrap_or(u64::MAX);
let ptr = Machine::ptr_from_addr_cast(this, addr)?;
let Ok(ptr) = ptr.into_pointer_or_addr() else {
throw_unsup_format!("Miri only supports munmap on memory allocated directly by mmap");
};
let Some((alloc_id, offset, _prov)) = Machine::ptr_get_alloc(this, ptr) else {
throw_unsup_format!("Miri only supports munmap on memory allocated directly by mmap");
};
// Elsewhere in this function we are careful to check what we can and throw an unsupported
// error instead of Undefined Behavior when use of this function falls outside of the
// narrow scope we support. We deliberately do not check the MemoryKind of this allocation,
// because we want to report UB on attempting to unmap memory that Rust "understands", such
// the stack, heap, or statics.
let (_kind, alloc) = this.memory.alloc_map().get(alloc_id).unwrap();
if offset != Size::ZERO || alloc.len() as u64 != length {
throw_unsup_format!(
"Miri only supports munmap calls that exactly unmap a region previously returned by mmap"
);
}
let len = Size::from_bytes(alloc.len() as u64);
this.deallocate_ptr(
ptr.into(),
Some((len, this.machine.page_align())),
MemoryKind::Machine(MiriMemoryKind::Mmap),
)?;
Ok(Scalar::from_i32(0))
}
}

View File

@ -2,6 +2,7 @@ pub mod dlsym;
pub mod foreign_items;
mod fs;
mod mem;
mod sync;
mod thread;

View File

@ -36,7 +36,13 @@ fn mutexattr_get_kind<'mir, 'tcx: 'mir>(
ecx: &MiriInterpCx<'mir, 'tcx>,
attr_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
ecx.read_scalar_at_offset(attr_op, 0, ecx.machine.layouts.i32)?.to_i32()
ecx.read_scalar_at_offset(
attr_op,
0,
ecx.libc_ty_layout("pthread_mutexattr_t"),
ecx.machine.layouts.i32,
)?
.to_i32()
}
fn mutexattr_set_kind<'mir, 'tcx: 'mir>(
@ -44,7 +50,13 @@ fn mutexattr_set_kind<'mir, 'tcx: 'mir>(
attr_op: &OpTy<'tcx, Provenance>,
kind: i32,
) -> InterpResult<'tcx, ()> {
ecx.write_scalar_at_offset(attr_op, 0, Scalar::from_i32(kind), ecx.machine.layouts.i32)
ecx.write_scalar_at_offset(
attr_op,
0,
Scalar::from_i32(kind),
ecx.libc_ty_layout("pthread_mutexattr_t"),
ecx.machine.layouts.i32,
)
}
// pthread_mutex_t is between 24 and 48 bytes, depending on the platform.
@ -60,14 +72,20 @@ fn mutex_get_id<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
mutex_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, MutexId> {
ecx.mutex_get_or_create_id(mutex_op, 4)
ecx.mutex_get_or_create_id(mutex_op, ecx.libc_ty_layout("pthread_mutex_t"), 4)
}
fn mutex_reset_id<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
mutex_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
ecx.write_scalar_at_offset(mutex_op, 4, Scalar::from_i32(0), ecx.machine.layouts.u32)
ecx.write_scalar_at_offset(
mutex_op,
4,
Scalar::from_i32(0),
ecx.libc_ty_layout("pthread_mutex_t"),
ecx.machine.layouts.u32,
)
}
fn mutex_get_kind<'mir, 'tcx: 'mir>(
@ -75,7 +93,13 @@ fn mutex_get_kind<'mir, 'tcx: 'mir>(
mutex_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 };
ecx.read_scalar_at_offset(mutex_op, offset, ecx.machine.layouts.i32)?.to_i32()
ecx.read_scalar_at_offset(
mutex_op,
offset,
ecx.libc_ty_layout("pthread_mutex_t"),
ecx.machine.layouts.i32,
)?
.to_i32()
}
fn mutex_set_kind<'mir, 'tcx: 'mir>(
@ -84,7 +108,13 @@ fn mutex_set_kind<'mir, 'tcx: 'mir>(
kind: i32,
) -> InterpResult<'tcx, ()> {
let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 };
ecx.write_scalar_at_offset(mutex_op, offset, Scalar::from_i32(kind), ecx.machine.layouts.i32)
ecx.write_scalar_at_offset(
mutex_op,
offset,
Scalar::from_i32(kind),
ecx.libc_ty_layout("pthread_mutex_t"),
ecx.machine.layouts.i32,
)
}
// pthread_rwlock_t is between 32 and 56 bytes, depending on the platform.
@ -98,7 +128,7 @@ fn rwlock_get_id<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
rwlock_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, RwLockId> {
ecx.rwlock_get_or_create_id(rwlock_op, 4)
ecx.rwlock_get_or_create_id(rwlock_op, ecx.libc_ty_layout("pthread_rwlock_t"), 4)
}
// pthread_condattr_t
@ -111,7 +141,13 @@ fn condattr_get_clock_id<'mir, 'tcx: 'mir>(
ecx: &MiriInterpCx<'mir, 'tcx>,
attr_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
ecx.read_scalar_at_offset(attr_op, 0, ecx.machine.layouts.i32)?.to_i32()
ecx.read_scalar_at_offset(
attr_op,
0,
ecx.libc_ty_layout("pthread_condattr_t"),
ecx.machine.layouts.i32,
)?
.to_i32()
}
fn condattr_set_clock_id<'mir, 'tcx: 'mir>(
@ -119,7 +155,13 @@ fn condattr_set_clock_id<'mir, 'tcx: 'mir>(
attr_op: &OpTy<'tcx, Provenance>,
clock_id: i32,
) -> InterpResult<'tcx, ()> {
ecx.write_scalar_at_offset(attr_op, 0, Scalar::from_i32(clock_id), ecx.machine.layouts.i32)
ecx.write_scalar_at_offset(
attr_op,
0,
Scalar::from_i32(clock_id),
ecx.libc_ty_layout("pthread_condattr_t"),
ecx.machine.layouts.i32,
)
}
// pthread_cond_t
@ -135,21 +177,33 @@ fn cond_get_id<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
cond_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, CondvarId> {
ecx.condvar_get_or_create_id(cond_op, 4)
ecx.condvar_get_or_create_id(cond_op, ecx.libc_ty_layout("pthread_cond_t"), 4)
}
fn cond_reset_id<'mir, 'tcx: 'mir>(
ecx: &mut MiriInterpCx<'mir, 'tcx>,
cond_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
ecx.write_scalar_at_offset(cond_op, 4, Scalar::from_i32(0), ecx.machine.layouts.u32)
ecx.write_scalar_at_offset(
cond_op,
4,
Scalar::from_i32(0),
ecx.libc_ty_layout("pthread_cond_t"),
ecx.machine.layouts.u32,
)
}
fn cond_get_clock_id<'mir, 'tcx: 'mir>(
ecx: &MiriInterpCx<'mir, 'tcx>,
cond_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, i32> {
ecx.read_scalar_at_offset(cond_op, 8, ecx.machine.layouts.i32)?.to_i32()
ecx.read_scalar_at_offset(
cond_op,
8,
ecx.libc_ty_layout("pthread_cond_t"),
ecx.machine.layouts.i32,
)?
.to_i32()
}
fn cond_set_clock_id<'mir, 'tcx: 'mir>(
@ -157,7 +211,13 @@ fn cond_set_clock_id<'mir, 'tcx: 'mir>(
cond_op: &OpTy<'tcx, Provenance>,
clock_id: i32,
) -> InterpResult<'tcx, ()> {
ecx.write_scalar_at_offset(cond_op, 8, Scalar::from_i32(clock_id), ecx.machine.layouts.i32)
ecx.write_scalar_at_offset(
cond_op,
8,
Scalar::from_i32(clock_id),
ecx.libc_ty_layout("pthread_cond_t"),
ecx.machine.layouts.i32,
)
}
/// Try to reacquire the mutex associated with the condition variable after we
@ -285,7 +345,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// However, the way libstd uses the pthread APIs works in our favor here, so we can get away with this.
// This can always be revisited to have some external state to catch double-destroys
// but not complain about the above code. See https://github.com/rust-lang/miri/pull/1933
this.write_uninit(&this.deref_operand(attr_op)?.into())?;
this.write_uninit(
&this.deref_operand_as(attr_op, this.libc_ty_layout("pthread_mutexattr_t"))?.into(),
)?;
Ok(0)
}
@ -437,7 +499,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
mutex_get_id(this, mutex_op)?;
// This might lead to false positives, see comment in pthread_mutexattr_destroy
this.write_uninit(&this.deref_operand(mutex_op)?.into())?;
this.write_uninit(
&this.deref_operand_as(mutex_op, this.libc_ty_layout("pthread_mutex_t"))?.into(),
)?;
// FIXME: delete interpreter state associated with this mutex.
Ok(0)
@ -560,7 +624,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
rwlock_get_id(this, rwlock_op)?;
// This might lead to false positives, see comment in pthread_mutexattr_destroy
this.write_uninit(&this.deref_operand(rwlock_op)?.into())?;
this.write_uninit(
&this.deref_operand_as(rwlock_op, this.libc_ty_layout("pthread_rwlock_t"))?.into(),
)?;
// FIXME: delete interpreter state associated with this rwlock.
Ok(0)
@ -624,7 +690,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
condattr_get_clock_id(this, attr_op)?;
// This might lead to false positives, see comment in pthread_mutexattr_destroy
this.write_uninit(&this.deref_operand(attr_op)?.into())?;
this.write_uninit(
&this.deref_operand_as(attr_op, this.libc_ty_layout("pthread_condattr_t"))?.into(),
)?;
Ok(0)
}
@ -715,7 +783,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// Extract the timeout.
let clock_id = cond_get_clock_id(this, cond_op)?;
let duration = match this.read_timespec(&this.deref_operand(abstime_op)?)? {
let duration = match this
.read_timespec(&this.deref_operand_as(abstime_op, this.libc_ty_layout("timespec"))?)?
{
Some(duration) => duration,
None => {
let einval = this.eval_libc("EINVAL");
@ -797,7 +867,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
cond_get_clock_id(this, cond_op)?;
// This might lead to false positives, see comment in pthread_mutexattr_destroy
this.write_uninit(&this.deref_operand(cond_op)?.into())?;
this.write_uninit(
&this.deref_operand_as(cond_op, this.libc_ty_layout("pthread_cond_t"))?.into(),
)?;
// FIXME: delete interpreter state associated with this condvar.
Ok(0)

View File

@ -13,7 +13,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let thread_info_place = this.deref_operand(thread)?;
let thread_info_place = this.deref_operand_as(thread, this.libc_ty_layout("pthread_t"))?;
let start_routine = this.read_pointer(start_routine)?;

View File

@ -10,8 +10,6 @@ use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle};
use shims::windows::sync::EvalContextExt as _;
use shims::windows::thread::EvalContextExt as _;
use smallvec::SmallVec;
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn emulate_foreign_item_by_name(
@ -92,7 +90,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let buf = this.read_pointer(buf)?;
let n = this.read_scalar(n)?.to_u32()?;
let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer
let io_status_block = this.deref_operand(io_status_block)?;
let io_status_block = this
.deref_operand_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
if byte_offset != 0 {
throw_unsup_format!(
@ -187,54 +186,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// Also called from `page_size` crate.
let [system_info] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let system_info = this.deref_operand(system_info)?;
let system_info =
this.deref_operand_as(system_info, this.windows_ty_layout("SYSTEM_INFO"))?;
// Initialize with `0`.
this.write_bytes_ptr(
system_info.ptr,
iter::repeat(0u8).take(system_info.layout.size.bytes_usize()),
)?;
// Set selected fields.
let word_layout = this.machine.layouts.u16;
let dword_layout = this.machine.layouts.u32;
let usize_layout = this.machine.layouts.usize;
// Using `mplace_field` is error-prone, see: https://github.com/rust-lang/miri/issues/2136.
// Pointer fields have different sizes on different targets.
// To avoid all these issue we calculate the offsets ourselves.
let field_sizes = [
word_layout.size, // 0, wProcessorArchitecture : WORD
word_layout.size, // 1, wReserved : WORD
dword_layout.size, // 2, dwPageSize : DWORD
usize_layout.size, // 3, lpMinimumApplicationAddress : LPVOID
usize_layout.size, // 4, lpMaximumApplicationAddress : LPVOID
usize_layout.size, // 5, dwActiveProcessorMask : DWORD_PTR
dword_layout.size, // 6, dwNumberOfProcessors : DWORD
dword_layout.size, // 7, dwProcessorType : DWORD
dword_layout.size, // 8, dwAllocationGranularity : DWORD
word_layout.size, // 9, wProcessorLevel : WORD
word_layout.size, // 10, wProcessorRevision : WORD
];
let field_offsets: SmallVec<[Size; 11]> = field_sizes
.iter()
.copied()
.scan(Size::ZERO, |a, x| {
let res = Some(*a);
*a = a.checked_add(x, this).unwrap();
res
})
.collect();
// Set page size.
let page_size = system_info.offset(field_offsets[2], dword_layout, &this.tcx)?;
this.write_scalar(
Scalar::from_int(this.machine.page_size, dword_layout.size),
&page_size.into(),
)?;
// Set number of processors.
let num_cpus = system_info.offset(field_offsets[6], dword_layout, &this.tcx)?;
this.write_scalar(
Scalar::from_int(this.machine.num_cpus, dword_layout.size),
&num_cpus.into(),
this.write_int_fields_named(
&[
("dwPageSize", this.machine.page_size.into()),
("dwNumberOfProcessors", this.machine.num_cpus.into()),
],
&system_info,
)?;
}
@ -426,6 +391,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let [console, buffer_info] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.read_target_isize(console)?;
// FIXME: this should use deref_operand_as, but CONSOLE_SCREEN_BUFFER_INFO is not in std
this.deref_operand(buffer_info)?;
// Indicate an error.
// FIXME: we should set last_error, but to what?

View File

@ -7,10 +7,6 @@ use crate::concurrency::sync::{CondvarLock, RwLockMode};
use crate::concurrency::thread::MachineCallback;
use crate::*;
const SRWLOCK_ID_OFFSET: u64 = 0;
const INIT_ONCE_ID_OFFSET: u64 = 0;
const CONDVAR_ID_OFFSET: u64 = 0;
impl<'mir, 'tcx> EvalContextExtPriv<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Try to reacquire the lock associated with the condition variable after we
@ -41,6 +37,33 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
Ok(())
}
// Windows sync primitives are pointer sized.
// We only use the first 4 bytes for the id.
fn srwlock_get_id(
&mut self,
rwlock_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, RwLockId> {
let this = self.eval_context_mut();
this.rwlock_get_or_create_id(rwlock_op, this.windows_ty_layout("SRWLOCK"), 0)
}
fn init_once_get_id(
&mut self,
init_once_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, InitOnceId> {
let this = self.eval_context_mut();
this.init_once_get_or_create_id(init_once_op, this.windows_ty_layout("INIT_ONCE"), 0)
}
fn condvar_get_id(
&mut self,
condvar_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, CondvarId> {
let this = self.eval_context_mut();
this.condvar_get_or_create_id(condvar_op, this.windows_ty_layout("CONDITION_VARIABLE"), 0)
}
}
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
@ -48,7 +71,7 @@ impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx>
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn AcquireSRWLockExclusive(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?;
let id = this.srwlock_get_id(lock_op)?;
let active_thread = this.get_active_thread();
if this.rwlock_is_locked(id) {
@ -72,7 +95,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
lock_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?;
let id = this.srwlock_get_id(lock_op)?;
let active_thread = this.get_active_thread();
if this.rwlock_is_locked(id) {
@ -86,7 +109,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn ReleaseSRWLockExclusive(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?;
let id = this.srwlock_get_id(lock_op)?;
let active_thread = this.get_active_thread();
if !this.rwlock_writer_unlock(id, active_thread) {
@ -101,7 +124,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn AcquireSRWLockShared(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?;
let id = this.srwlock_get_id(lock_op)?;
let active_thread = this.get_active_thread();
if this.rwlock_is_write_locked(id) {
@ -118,7 +141,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
lock_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?;
let id = this.srwlock_get_id(lock_op)?;
let active_thread = this.get_active_thread();
if this.rwlock_is_write_locked(id) {
@ -131,7 +154,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn ReleaseSRWLockShared(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?;
let id = this.srwlock_get_id(lock_op)?;
let active_thread = this.get_active_thread();
if !this.rwlock_reader_unlock(id, active_thread) {
@ -154,7 +177,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let this = self.eval_context_mut();
let active_thread = this.get_active_thread();
let id = this.init_once_get_or_create_id(init_once_op, INIT_ONCE_ID_OFFSET)?;
let id = this.init_once_get_id(init_once_op)?;
let flags = this.read_scalar(flags_op)?.to_u32()?;
let pending_place = this.deref_operand(pending_op)?.into();
let context = this.read_pointer(context_op)?;
@ -229,7 +252,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let id = this.init_once_get_or_create_id(init_once_op, INIT_ONCE_ID_OFFSET)?;
let id = this.init_once_get_id(init_once_op)?;
let flags = this.read_scalar(flags_op)?.to_u32()?;
let context = this.read_pointer(context_op)?;
@ -372,8 +395,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let condvar_id = this.condvar_get_or_create_id(condvar_op, CONDVAR_ID_OFFSET)?;
let lock_id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?;
let condvar_id = this.condvar_get_id(condvar_op)?;
let lock_id = this.srwlock_get_id(lock_op)?;
let timeout_ms = this.read_scalar(timeout_op)?.to_u32()?;
let flags = this.read_scalar(flags_op)?.to_u32()?;
@ -456,7 +479,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn WakeConditionVariable(&mut self, condvar_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let condvar_id = this.condvar_get_or_create_id(condvar_op, CONDVAR_ID_OFFSET)?;
let condvar_id = this.condvar_get_id(condvar_op)?;
if let Some((thread, lock)) = this.condvar_signal(condvar_id) {
if let CondvarLock::RwLock { id, mode } = lock {
@ -475,7 +498,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
condvar_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let condvar_id = this.condvar_get_or_create_id(condvar_op, CONDVAR_ID_OFFSET)?;
let condvar_id = this.condvar_get_id(condvar_op)?;
while let Some((thread, lock)) = this.condvar_signal(condvar_id) {
if let CondvarLock::RwLock { id, mode } = lock {

View File

@ -3,9 +3,8 @@ use regex::bytes::Regex;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::{env, process::Command};
use ui_test::status_emitter::StatusEmitter;
use ui_test::CommandBuilder;
use ui_test::{color_eyre::Result, Config, Match, Mode, OutputConflictHandling};
use ui_test::{status_emitter, CommandBuilder};
fn miri_path() -> PathBuf {
PathBuf::from(option_env!("MIRI").unwrap_or(env!("CARGO_BIN_EXE_miri")))
@ -76,7 +75,7 @@ fn test_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) ->
let skip_ui_checks = env::var_os("MIRI_SKIP_UI_CHECKS").is_some();
let output_conflict_handling = match (env::var_os("MIRI_BLESS").is_some(), skip_ui_checks) {
(false, false) => OutputConflictHandling::Error,
(false, false) => OutputConflictHandling::Error("./miri bless".into()),
(true, false) => OutputConflictHandling::Bless,
(false, true) => OutputConflictHandling::Ignore,
(true, true) => panic!("cannot use MIRI_BLESS and MIRI_SKIP_UI_CHECKS at the same time"),
@ -86,13 +85,12 @@ fn test_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) ->
target: Some(target.to_owned()),
stderr_filters: STDERR.clone(),
stdout_filters: STDOUT.clone(),
root_dir: PathBuf::from(path),
mode,
program,
output_conflict_handling,
quiet: false,
out_dir: PathBuf::from(std::env::var_os("CARGO_TARGET_DIR").unwrap()).join("ui"),
edition: Some("2021".into()),
..Config::default()
..Config::rustc(path.into())
};
let use_std = env::var_os("MIRI_NO_STD").is_none();
@ -113,39 +111,55 @@ fn test_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) ->
}
fn run_tests(mode: Mode, path: &str, target: &str, with_dependencies: bool) -> Result<()> {
let mut config = test_config(target, path, mode, with_dependencies);
let config = test_config(target, path, mode, with_dependencies);
// Handle command-line arguments.
let mut after_dashdash = false;
config.path_filter.extend(std::env::args().skip(1).filter(|arg| {
if after_dashdash {
// Just propagate everything.
return true;
}
match &**arg {
"--quiet" => {
config.quiet = true;
false
let mut quiet = false;
let filters = std::env::args()
.skip(1)
.filter(|arg| {
if after_dashdash {
// Just propagate everything.
return true;
}
"--" => {
after_dashdash = true;
false
match &**arg {
"--quiet" => {
quiet = true;
false
}
"--" => {
after_dashdash = true;
false
}
s if s.starts_with('-') => {
panic!("unknown compiletest flag `{s}`");
}
_ => true,
}
s if s.starts_with('-') => {
panic!("unknown compiletest flag `{s}`");
}
_ => true,
}
}));
})
.collect::<Vec<_>>();
eprintln!(" Compiler: {}", config.program.display());
ui_test::run_tests_generic(
config,
// The files we're actually interested in (all `.rs` files).
|path| path.extension().is_some_and(|ext| ext == "rs"),
|path| {
path.extension().is_some_and(|ext| ext == "rs")
&& (filters.is_empty() || filters.iter().any(|f| path.starts_with(f)))
},
// This could be used to overwrite the `Config` on a per-test basis.
|_, _| None,
TextAndGha,
(
if quiet {
Box::<status_emitter::Quiet>::default()
as Box<dyn status_emitter::StatusEmitter + Send>
} else {
Box::new(status_emitter::Text)
},
status_emitter::Gha::</* GHA Actions groups*/ false> {
name: format!("{mode:?} {path} ({target})"),
},
),
)
}
@ -270,45 +284,3 @@ fn run_dep_mode(target: String, mut args: impl Iterator<Item = OsString>) -> Res
cmd.args(args);
if cmd.spawn()?.wait()?.success() { Ok(()) } else { std::process::exit(1) }
}
/// This is a custom renderer for `ui_test` output that does not emit github actions
/// `group`s, while still producing regular github actions messages on test failures.
struct TextAndGha;
impl StatusEmitter for TextAndGha {
fn failed_test<'a>(
&'a self,
revision: &'a str,
path: &'a Path,
cmd: &'a Command,
stderr: &'a [u8],
) -> Box<dyn std::fmt::Debug + 'a> {
Box::new((
ui_test::status_emitter::Gha::<false>.failed_test(revision, path, cmd, stderr),
ui_test::status_emitter::Text.failed_test(revision, path, cmd, stderr),
))
}
fn run_tests(&self, _config: &Config) -> Box<dyn ui_test::status_emitter::DuringTestRun> {
Box::new(TextAndGha)
}
fn finalize(
&self,
failures: usize,
succeeded: usize,
ignored: usize,
filtered: usize,
) -> Box<dyn ui_test::status_emitter::Summary> {
Box::new((
ui_test::status_emitter::Gha::<false>.finalize(failures, succeeded, ignored, filtered),
ui_test::status_emitter::Text.finalize(failures, succeeded, ignored, filtered),
))
}
}
impl ui_test::status_emitter::DuringTestRun for TextAndGha {
fn test_result(&mut self, path: &Path, revision: &str, result: &ui_test::TestResult) {
ui_test::status_emitter::Text.test_result(path, revision, result);
ui_test::status_emitter::Gha::<false>.test_result(path, revision, result);
}
}

View File

@ -1,3 +1,6 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
// This makes a ref that was passed to us via &mut alias with things it should not alias with
fn retarget(x: &mut &u32, target: &mut u32) {
unsafe {
@ -11,5 +14,7 @@ fn main() {
retarget(&mut target_alias, target);
// now `target_alias` points to the same thing as `target`
*target = 13;
let _val = *target_alias; //~ ERROR: /read access .* tag does not exist in the borrow stack/
let _val = *target_alias;
//~[stack]^ ERROR: /read access .* tag does not exist in the borrow stack/
//~[tree]| ERROR: /read access through .* is forbidden/
}

View File

@ -0,0 +1,32 @@
error: Undefined Behavior: read access through <TAG> is forbidden
--> $DIR/alias_through_mutation.rs:LL:CC
|
LL | let _val = *target_alias;
| ^^^^^^^^^^^^^ read 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 Disabled which forbids this child read access
help: the accessed tag <TAG> was created here
--> $DIR/alias_through_mutation.rs:LL:CC
|
LL | *x = &mut *(target as *mut _);
| ^^^^^^^^^^^^^^^^^^^^^^^^
help: the conflicting tag <TAG> was created here, in the initial state Reserved
--> $DIR/alias_through_mutation.rs:LL:CC
|
LL | retarget(&mut target_alias, target);
| ^^^^^^
help: the conflicting tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
--> $DIR/alias_through_mutation.rs:LL:CC
|
LL | *target = 13;
| ^^^^^^^^^^^^
= help: this transition corresponds to a loss of read and write permissions
= note: BACKTRACE (of the first span):
= note: inside `main` at $DIR/alias_through_mutation.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,6 +1,12 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
use std::mem;
pub fn safe(_x: &mut i32, _y: &mut i32) {} //~ ERROR: protect
pub fn safe(x: &mut i32, y: &mut i32) {
//~[stack]^ ERROR: protect
*x = 1; //~[tree] ERROR: /write access through .* is forbidden/
*y = 2;
}
fn main() {
let mut x = 0;

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
--> $DIR/aliasing_mut1.rs:LL:CC
|
LL | pub fn safe(_x: &mut i32, _y: &mut i32) {}
| ^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
LL | pub fn safe(x: &mut i32, y: &mut i32) {
| ^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
@ -14,8 +14,8 @@ LL | let xraw: *mut i32 = unsafe { mem::transmute(&mut x) };
help: <TAG> is this argument
--> $DIR/aliasing_mut1.rs:LL:CC
|
LL | pub fn safe(_x: &mut i32, _y: &mut i32) {}
| ^^
LL | pub fn safe(x: &mut i32, y: &mut i32) {
| ^
= note: BACKTRACE (of the first span):
= note: inside `safe` at $DIR/aliasing_mut1.rs:LL:CC
note: inside `main`

View File

@ -0,0 +1,31 @@
error: Undefined Behavior: write access through <TAG> is forbidden
--> $DIR/aliasing_mut1.rs:LL:CC
|
LL | *x = 1;
| ^^^^^^ 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> has state Frozen which forbids this child write access
help: the accessed tag <TAG> was created here, in the initial state Reserved
--> $DIR/aliasing_mut1.rs:LL:CC
|
LL | pub fn safe(x: &mut i32, y: &mut i32) {
| ^
help: the accessed tag <TAG> later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4]
--> $DIR/aliasing_mut1.rs:LL:CC
|
LL | pub fn safe(x: &mut i32, y: &mut i32) {
| ^
= help: this transition corresponds to a loss of write permissions
= note: BACKTRACE (of the first span):
= note: inside `safe` at $DIR/aliasing_mut1.rs:LL:CC
note: inside `main`
--> $DIR/aliasing_mut1.rs:LL:CC
|
LL | safe_raw(xraw, xraw);
| ^^^^^^^^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,6 +1,12 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
use std::mem;
pub fn safe(_x: &i32, _y: &mut i32) {} //~ ERROR: protect
pub fn safe(x: &i32, y: &mut i32) {
//~[stack]^ ERROR: protect
let _v = *x;
*y = 2; //~[tree] ERROR: /write access through .* is forbidden/
}
fn main() {
let mut x = 0;

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected because it is an argument of call ID
--> $DIR/aliasing_mut2.rs:LL:CC
|
LL | pub fn safe(_x: &i32, _y: &mut i32) {}
| ^^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected because it is an argument of call ID
LL | pub fn safe(x: &i32, y: &mut i32) {
| ^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected because it is an argument of call ID
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
@ -14,8 +14,8 @@ LL | let xref = &mut x;
help: <TAG> is this argument
--> $DIR/aliasing_mut2.rs:LL:CC
|
LL | pub fn safe(_x: &i32, _y: &mut i32) {}
| ^^
LL | pub fn safe(x: &i32, y: &mut i32) {
| ^
= note: BACKTRACE (of the first span):
= note: inside `safe` at $DIR/aliasing_mut2.rs:LL:CC
note: inside `main`

View File

@ -0,0 +1,31 @@
error: Undefined Behavior: write access through <TAG> is forbidden
--> $DIR/aliasing_mut2.rs:LL:CC
|
LL | *y = 2;
| ^^^^^^ 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> has state Frozen which forbids this child write access
help: the accessed tag <TAG> was created here, in the initial state Reserved
--> $DIR/aliasing_mut2.rs:LL:CC
|
LL | pub fn safe(x: &i32, y: &mut i32) {
| ^
help: the accessed tag <TAG> later transitioned to Frozen due to a foreign read access at offsets [0x0..0x4]
--> $DIR/aliasing_mut2.rs:LL:CC
|
LL | let _v = *x;
| ^^
= help: this transition corresponds to a loss of write permissions
= note: BACKTRACE (of the first span):
= note: inside `safe` at $DIR/aliasing_mut2.rs:LL:CC
note: inside `main`
--> $DIR/aliasing_mut2.rs:LL:CC
|
LL | safe_raw(xshr, xraw);
| ^^^^^^^^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,6 +1,12 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
use std::mem;
pub fn safe(_x: &mut i32, _y: &i32) {} //~ ERROR: borrow stack
pub fn safe(x: &mut i32, y: &i32) {
//~[stack]^ ERROR: borrow stack
*x = 1; //~[tree] ERROR: /write access through .* is forbidden/
let _v = *y;
}
fn main() {
let mut x = 0;

View File

@ -1,11 +1,11 @@
error: Undefined Behavior: trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
--> $DIR/aliasing_mut3.rs:LL:CC
|
LL | pub fn safe(_x: &mut i32, _y: &i32) {}
| ^^
| |
| trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
| this error occurs as part of function-entry retag at ALLOC[0x0..0x4]
LL | pub fn safe(x: &mut i32, y: &i32) {
| ^
| |
| trying to retag from <TAG> for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
| this error occurs as part of function-entry retag at ALLOC[0x0..0x4]
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information

View File

@ -0,0 +1,31 @@
error: Undefined Behavior: write access through <TAG> is forbidden
--> $DIR/aliasing_mut3.rs:LL:CC
|
LL | *x = 1;
| ^^^^^^ 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> has state Frozen which forbids this child write access
help: the accessed tag <TAG> was created here, in the initial state Reserved
--> $DIR/aliasing_mut3.rs:LL:CC
|
LL | pub fn safe(x: &mut i32, y: &i32) {
| ^
help: the accessed tag <TAG> later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4]
--> $DIR/aliasing_mut3.rs:LL:CC
|
LL | pub fn safe(x: &mut i32, y: &i32) {
| ^
= help: this transition corresponds to a loss of write permissions
= note: BACKTRACE (of the first span):
= note: inside `safe` at $DIR/aliasing_mut3.rs:LL:CC
note: inside `main`
--> $DIR/aliasing_mut3.rs:LL:CC
|
LL | safe_raw(xraw, xshr);
| ^^^^^^^^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,8 +1,15 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
//@[tree]error-in-other-file: /write access through .* is forbidden/
use std::cell::Cell;
use std::mem;
// Make sure &mut UnsafeCell also is exclusive
pub fn safe(_x: &i32, _y: &mut Cell<i32>) {} //~ ERROR: protect
pub fn safe(x: &i32, y: &mut Cell<i32>) {
//~[stack]^ ERROR: protect
y.set(1);
let _ = *x;
}
fn main() {
let mut x = 0;

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected because it is an argument of call ID
--> $DIR/aliasing_mut4.rs:LL:CC
|
LL | pub fn safe(_x: &i32, _y: &mut Cell<i32>) {}
| ^^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected because it is an argument of call ID
LL | pub fn safe(x: &i32, y: &mut Cell<i32>) {
| ^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected because it is an argument of call ID
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
@ -14,8 +14,8 @@ LL | let xref = &mut x;
help: <TAG> is this argument
--> $DIR/aliasing_mut4.rs:LL:CC
|
LL | pub fn safe(_x: &i32, _y: &mut Cell<i32>) {}
| ^^
LL | pub fn safe(x: &i32, y: &mut Cell<i32>) {
| ^
= note: BACKTRACE (of the first span):
= note: inside `safe` at $DIR/aliasing_mut4.rs:LL:CC
note: inside `main`

View File

@ -0,0 +1,39 @@
error: Undefined Behavior: write access through <TAG> is forbidden
--> RUSTLIB/core/src/mem/mod.rs:LL:CC
|
LL | ptr::write(dest, src);
| ^^^^^^^^^^^^^^^^^^^^^ 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 foreign to the protected tag <TAG> (i.e., it is not a child)
= help: this foreign write access would cause the protected tag <TAG> to transition from Frozen to Disabled
= help: this transition would be a loss of read permissions, which is not allowed for protected tags
help: the accessed tag <TAG> was created here
--> $DIR/aliasing_mut4.rs:LL:CC
|
LL | y.set(1);
| ^^^^^^^^
help: the protected tag <TAG> was created here, in the initial state Frozen
--> $DIR/aliasing_mut4.rs:LL:CC
|
LL | pub fn safe(x: &i32, y: &mut Cell<i32>) {
| ^
= note: BACKTRACE (of the first span):
= note: inside `std::mem::replace::<i32>` at RUSTLIB/core/src/mem/mod.rs:LL:CC
= note: inside `std::cell::Cell::<i32>::replace` at RUSTLIB/core/src/cell.rs:LL:CC
= note: inside `std::cell::Cell::<i32>::set` at RUSTLIB/core/src/cell.rs:LL:CC
note: inside `safe`
--> $DIR/aliasing_mut4.rs:LL:CC
|
LL | y.set(1);
| ^^^^^^^^
note: inside `main`
--> $DIR/aliasing_mut4.rs:LL:CC
|
LL | safe_raw(xshr, xraw as *mut _);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,3 +1,6 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
fn demo_box_advanced_unique(mut our: Box<i32>) -> i32 {
unknown_code_1(&*our);
@ -24,7 +27,9 @@ fn unknown_code_1(x: &i32) {
fn unknown_code_2() {
unsafe {
*LEAK = 7; //~ ERROR: /write access .* tag does not exist in the borrow stack/
*LEAK = 7;
//~[stack]^ ERROR: /write access .* tag does not exist in the borrow stack/
//~[tree]| ERROR: /write access through .* is forbidden/
}
}

View File

@ -0,0 +1,42 @@
error: Undefined Behavior: write access through <TAG> is forbidden
--> $DIR/box_exclusive_violation1.rs:LL:CC
|
LL | *LEAK = 7;
| ^^^^^^^^^ 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 Disabled which forbids this child write access
help: the accessed tag <TAG> was created here
--> $DIR/box_exclusive_violation1.rs:LL:CC
|
LL | fn unknown_code_1(x: &i32) {
| ^
help: the conflicting tag <TAG> was created here, in the initial state Frozen
--> $DIR/box_exclusive_violation1.rs:LL:CC
|
LL | unknown_code_1(&*our);
| ^^^^^
help: the conflicting tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
--> $DIR/box_exclusive_violation1.rs:LL:CC
|
LL | *our = 5;
| ^^^^^^^^
= help: this transition corresponds to a loss of read permissions
= note: BACKTRACE (of the first span):
= note: inside `unknown_code_2` at $DIR/box_exclusive_violation1.rs:LL:CC
note: inside `demo_box_advanced_unique`
--> $DIR/box_exclusive_violation1.rs:LL:CC
|
LL | unknown_code_2();
| ^^^^^^^^^^^^^^^^
note: inside `main`
--> $DIR/box_exclusive_violation1.rs:LL:CC
|
LL | demo_box_advanced_unique(Box::new(0));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,8 +1,13 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
unsafe fn test(mut x: Box<i32>, y: *const i32) -> i32 {
// We will call this in a way that x and y alias.
*x = 5;
std::mem::forget(x);
*y //~ERROR: weakly protected
*y
//~[stack]^ ERROR: weakly protected
//~[tree]| ERROR: /read access through .* is forbidden/
}
fn main() {

View File

@ -0,0 +1,38 @@
error: Undefined Behavior: read access through <TAG> is forbidden
--> $DIR/box_noalias_violation.rs:LL:CC
|
LL | *y
| ^^ read 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 foreign to the protected tag <TAG> (i.e., it is not a child)
= help: this foreign read access would cause the protected tag <TAG> to transition from Active to Frozen
= help: this transition would be a loss of write permissions, which is not allowed for protected tags
help: the accessed tag <TAG> was created here
--> $DIR/box_noalias_violation.rs:LL:CC
|
LL | let ptr = &mut v as *mut i32;
| ^^^^^^
help: the protected tag <TAG> was created here, in the initial state Reserved
--> $DIR/box_noalias_violation.rs:LL:CC
|
LL | unsafe fn test(mut x: Box<i32>, y: *const i32) -> i32 {
| ^^^^^
help: the protected tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x4]
--> $DIR/box_noalias_violation.rs:LL:CC
|
LL | *x = 5;
| ^^^^^^
= help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
= note: BACKTRACE (of the first span):
= note: inside `test` at $DIR/box_noalias_violation.rs:LL:CC
note: inside `main`
--> $DIR/box_noalias_violation.rs:LL:CC
|
LL | test(Box::from_raw(ptr), ptr);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,3 +1,5 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
mod safe {
use std::slice::from_raw_parts_mut;
@ -9,7 +11,9 @@ mod safe {
fn main() {
let v = vec![0, 1, 2];
let v1 = safe::as_mut_slice(&v);
let _v2 = safe::as_mut_slice(&v);
let v2 = safe::as_mut_slice(&v);
v1[1] = 5;
//~^ ERROR: /write access .* tag does not exist in the borrow stack/
//~[stack]^ ERROR: /write access .* tag does not exist in the borrow stack/
v2[1] = 7;
//~[tree]^ ERROR: /write access through .* is forbidden/
}

View File

@ -0,0 +1,32 @@
error: Undefined Behavior: write access through <TAG> is forbidden
--> $DIR/buggy_as_mut_slice.rs:LL:CC
|
LL | v2[1] = 7;
| ^^^^^^^^^ 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 Disabled which forbids this child write access
help: the accessed tag <TAG> was created here
--> $DIR/buggy_as_mut_slice.rs:LL:CC
|
LL | let v2 = safe::as_mut_slice(&v);
| ^^^^^^^^^^^^^^^^^^^^^^
help: the conflicting tag <TAG> was created here, in the initial state Reserved
--> $DIR/buggy_as_mut_slice.rs:LL:CC
|
LL | unsafe { from_raw_parts_mut(self_.as_ptr() as *mut T, self_.len()) }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: the conflicting tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x4..0x8]
--> $DIR/buggy_as_mut_slice.rs:LL:CC
|
LL | v1[1] = 5;
| ^^^^^^^^^
= help: this transition corresponds to a loss of read and write permissions
= note: BACKTRACE (of the first span):
= note: inside `main` at $DIR/buggy_as_mut_slice.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,3 +1,5 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
mod safe {
use std::slice::from_raw_parts_mut;
@ -19,7 +21,8 @@ mod safe {
fn main() {
let mut array = [1, 2, 3, 4];
let (a, b) = safe::split_at_mut(&mut array, 0);
//~^ ERROR: /retag .* tag does not exist in the borrow stack/
//~[stack]^ ERROR: /retag .* tag does not exist in the borrow stack/
a[1] = 5;
b[1] = 6;
//~[tree]^ ERROR: /write access through .* is forbidden/
}

View File

@ -0,0 +1,32 @@
error: Undefined Behavior: write access through <TAG> is forbidden
--> $DIR/buggy_split_at_mut.rs:LL:CC
|
LL | b[1] = 6;
| ^^^^^^^^ 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 Disabled which forbids this child write access
help: the accessed tag <TAG> was created here
--> $DIR/buggy_split_at_mut.rs:LL:CC
|
LL | let (a, b) = safe::split_at_mut(&mut array, 0);
| ^
help: the conflicting tag <TAG> was created here, in the initial state Reserved
--> $DIR/buggy_split_at_mut.rs:LL:CC
|
LL | from_raw_parts_mut(ptr.offset(mid as isize), len - mid),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: the conflicting tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x4..0x8]
--> $DIR/buggy_split_at_mut.rs:LL:CC
|
LL | a[1] = 5;
| ^^^^^^^^
= help: this transition corresponds to a loss of read and write permissions
= note: BACKTRACE (of the first span):
= note: inside `main` at $DIR/buggy_split_at_mut.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -0,0 +1,14 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
fn main() {
let target = Box::new(42); // has an implicit raw
let xref = &*target;
{
let x: *mut u32 = xref as *const _ as *mut _;
unsafe { *x = 42 };
//~[stack]^ ERROR: /write access .* tag only grants SharedReadOnly permission/
//~[tree]| ERROR: /write access through .* is forbidden/
}
let _x = *xref;
}

View File

@ -0,0 +1,20 @@
error: Undefined Behavior: write access through <TAG> is forbidden
--> $DIR/illegal_write1.rs:LL:CC
|
LL | unsafe { *x = 42 };
| ^^^^^^^ 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> has state Frozen which forbids this child write access
help: the accessed tag <TAG> was created here, in the initial state Frozen
--> $DIR/illegal_write1.rs:LL:CC
|
LL | let xref = &*target;
| ^^^^^^^^
= note: BACKTRACE (of the first span):
= note: inside `main` at $DIR/illegal_write1.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,3 +1,6 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
// A callee may not write to the destination of our `&mut` without us noticing.
fn main() {
@ -8,7 +11,8 @@ fn main() {
callee(xraw);
// ... though any use of raw value will invalidate our ref.
let _val = *xref;
//~^ ERROR: /read access .* tag does not exist in the borrow stack/
//~[stack]^ ERROR: /read access .* tag does not exist in the borrow stack/
//~[tree]| ERROR: /read access through .* is forbidden/
}
fn callee(xraw: *mut i32) {

View File

@ -0,0 +1,32 @@
error: Undefined Behavior: read access through <TAG> is forbidden
--> $DIR/illegal_write5.rs:LL:CC
|
LL | let _val = *xref;
| ^^^^^ read 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 Disabled which forbids this child read access
help: the accessed tag <TAG> was created here
--> $DIR/illegal_write5.rs:LL:CC
|
LL | let xref = unsafe { &mut *xraw };
| ^^^^^^^^^^
help: the conflicting tag <TAG> was created here, in the initial state Reserved
--> $DIR/illegal_write5.rs:LL:CC
|
LL | let xref = unsafe { &mut *xraw };
| ^^^^^^^^^^
help: the conflicting tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
--> $DIR/illegal_write5.rs:LL:CC
|
LL | unsafe { *xraw = 15 };
| ^^^^^^^^^^
= help: this transition corresponds to a loss of read and write permissions
= note: BACKTRACE (of the first span):
= note: inside `main` at $DIR/illegal_write5.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -0,0 +1,17 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
fn main() {
let x = &mut 0u32;
let p = x as *mut u32;
foo(x, p);
}
fn foo(a: &mut u32, y: *mut u32) -> u32 {
*a = 1;
let _b = &*a;
unsafe { *y = 2 };
//~[stack]^ ERROR: /not granting access .* because that would remove .* which is strongly protected/
//~[tree]| ERROR: /write access through .* is forbidden/
return *a;
}

View File

@ -0,0 +1,38 @@
error: Undefined Behavior: write access through <TAG> is forbidden
--> $DIR/illegal_write6.rs:LL:CC
|
LL | unsafe { *y = 2 };
| ^^^^^^ 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 foreign to the protected tag <TAG> (i.e., it is not a child)
= help: this foreign write access would cause the protected tag <TAG> to transition from Active to Disabled
= help: this transition would be a loss of read and write permissions, which is not allowed for protected tags
help: the accessed tag <TAG> was created here
--> $DIR/illegal_write6.rs:LL:CC
|
LL | let x = &mut 0u32;
| ^^^^^^^^^
help: the protected tag <TAG> was created here, in the initial state Reserved
--> $DIR/illegal_write6.rs:LL:CC
|
LL | fn foo(a: &mut u32, y: *mut u32) -> u32 {
| ^
help: the protected tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x4]
--> $DIR/illegal_write6.rs:LL:CC
|
LL | *a = 1;
| ^^^^^^
= help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
= note: BACKTRACE (of the first span):
= note: inside `foo` at $DIR/illegal_write6.rs:LL:CC
note: inside `main`
--> $DIR/illegal_write6.rs:LL:CC
|
LL | foo(x, p);
| ^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,8 +1,13 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
fn inner(x: *mut i32, _y: &i32) {
// If `x` and `y` alias, retagging is fine with this... but we really
// shouldn't be allowed to write to `x` at all because `y` was assumed to be
// immutable for the duration of this call.
unsafe { *x = 0 }; //~ ERROR: protect
unsafe { *x = 0 };
//~[stack]^ ERROR: protect
//~[tree]| ERROR: /write access through .* is forbidden/
}
fn main() {

View File

@ -0,0 +1,32 @@
error: Undefined Behavior: write access through <TAG> is forbidden
--> $DIR/invalidate_against_protector2.rs:LL:CC
|
LL | unsafe { *x = 0 };
| ^^^^^^ 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 foreign to the protected tag <TAG> (i.e., it is not a child)
= help: this foreign write access would cause the protected tag <TAG> to transition from Frozen to Disabled
= help: this transition would be a loss of read permissions, which is not allowed for protected tags
help: the accessed tag <TAG> was created here
--> $DIR/invalidate_against_protector2.rs:LL:CC
|
LL | let xraw = &mut x as *mut _;
| ^^^^^^
help: the protected tag <TAG> was created here, in the initial state Frozen
--> $DIR/invalidate_against_protector2.rs:LL:CC
|
LL | fn inner(x: *mut i32, _y: &i32) {
| ^^
= note: BACKTRACE (of the first span):
= note: inside `inner` at $DIR/invalidate_against_protector2.rs:LL:CC
note: inside `main`
--> $DIR/invalidate_against_protector2.rs:LL:CC
|
LL | inner(xraw, xref);
| ^^^^^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,10 +1,15 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
use std::alloc::{alloc, Layout};
fn inner(x: *mut i32, _y: &i32) {
// If `x` and `y` alias, retagging is fine with this... but we really
// shouldn't be allowed to write to `x` at all because `y` was assumed to be
// immutable for the duration of this call.
unsafe { *x = 0 }; //~ ERROR: protect
unsafe { *x = 0 };
//~[stack]^ ERROR: protect
//~[tree]| ERROR: /write access through .* is forbidden/
}
fn main() {

View File

@ -0,0 +1,32 @@
error: Undefined Behavior: write access through <TAG> (root of the allocation) is forbidden
--> $DIR/invalidate_against_protector3.rs:LL:CC
|
LL | unsafe { *x = 0 };
| ^^^^^^ write access through <TAG> (root of the allocation) 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> (root of the allocation) 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> to transition from Frozen to Disabled
= help: this transition would be a loss of read permissions, which is not allowed for protected tags
help: the accessed tag <TAG> was created here
--> $DIR/invalidate_against_protector3.rs:LL:CC
|
LL | let ptr = alloc(Layout::for_value(&0i32)) as *mut i32;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: the protected tag <TAG> was created here, in the initial state Frozen
--> $DIR/invalidate_against_protector3.rs:LL:CC
|
LL | fn inner(x: *mut i32, _y: &i32) {
| ^^
= note: BACKTRACE (of the first span):
= note: inside `inner` at $DIR/invalidate_against_protector3.rs:LL:CC
note: inside `main`
--> $DIR/invalidate_against_protector3.rs:LL:CC
|
LL | inner(ptr, &*ptr);
| ^^^^^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,3 +1,5 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
//@error-in-other-file: pointer to 4 bytes starting at offset 0 is out-of-bounds
fn main() {

View File

@ -0,0 +1,21 @@
error: Undefined Behavior: out-of-bounds pointer use: ALLOC has size 2, so pointer to 4 bytes starting at offset 0 is out-of-bounds
--> RUSTLIB/alloc/src/boxed.rs:LL:CC
|
LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: ALLOC has size 2, so pointer to 4 bytes starting at offset 0 is out-of-bounds
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `std::boxed::Box::<u32>::from_raw_in` at RUSTLIB/alloc/src/boxed.rs:LL:CC
= note: inside `std::boxed::Box::<u32>::from_raw` at RUSTLIB/alloc/src/boxed.rs:LL:CC
note: inside `main`
--> $DIR/issue-miri-1050-1.rs:LL:CC
|
LL | drop(Box::from_raw(ptr as *mut u32));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,3 +1,5 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
//@error-in-other-file: is a dangling pointer
use std::ptr::NonNull;

View File

@ -0,0 +1,21 @@
error: Undefined Behavior: out-of-bounds pointer use: 0x4[noalloc] is a dangling pointer (it has no provenance)
--> RUSTLIB/alloc/src/boxed.rs:LL:CC
|
LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: 0x4[noalloc] is a dangling pointer (it has no provenance)
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `std::boxed::Box::<i32>::from_raw_in` at RUSTLIB/alloc/src/boxed.rs:LL:CC
= note: inside `std::boxed::Box::<i32>::from_raw` at RUSTLIB/alloc/src/boxed.rs:LL:CC
note: inside `main`
--> $DIR/issue-miri-1050-2.rs:LL:CC
|
LL | drop(Box::from_raw(ptr.as_ptr()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,3 +1,5 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
// Make sure we catch this even without validation
//@compile-flags: -Zmiri-disable-validation
@ -8,5 +10,7 @@ fn main() {
let xref = unsafe { &*xraw };
let xref_in_mem = Box::new(xref);
unsafe { *xraw = 42 }; // unfreeze
let _val = *xref_in_mem; //~ ERROR: /retag .* tag does not exist in the borrow stack/
let _val = *xref_in_mem;
//~[stack]^ ERROR: /retag .* tag does not exist in the borrow stack/
//~[tree]| ERROR: /reborrow through .* is forbidden/
}

View File

@ -0,0 +1,32 @@
error: Undefined Behavior: reborrow through <TAG> is forbidden
--> $DIR/load_invalid_shr.rs:LL:CC
|
LL | let _val = *xref_in_mem;
| ^^^^^^^^^^^^ reborrow 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 Disabled which forbids this reborrow (acting as a child read access)
help: the accessed tag <TAG> was created here
--> $DIR/load_invalid_shr.rs:LL:CC
|
LL | let xref_in_mem = Box::new(xref);
| ^^^^^^^^^^^^^^
help: the conflicting tag <TAG> was created here, in the initial state Frozen
--> $DIR/load_invalid_shr.rs:LL:CC
|
LL | let xref = unsafe { &*xraw };
| ^^^^^^
help: the conflicting tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
--> $DIR/load_invalid_shr.rs:LL:CC
|
LL | unsafe { *xraw = 42 }; // unfreeze
| ^^^^^^^^^^
= help: this transition corresponds to a loss of read permissions
= note: BACKTRACE (of the first span):
= note: inside `main` at $DIR/load_invalid_shr.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,3 +1,6 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
fn demo_mut_advanced_unique(our: &mut i32) -> i32 {
unknown_code_1(&*our);
@ -24,7 +27,9 @@ fn unknown_code_1(x: &i32) {
fn unknown_code_2() {
unsafe {
*LEAK = 7; //~ ERROR: /write access .* tag does not exist in the borrow stack/
*LEAK = 7;
//~[stack]^ ERROR: /write access .* tag does not exist in the borrow stack/
//~[tree]| ERROR: /write access through .* is forbidden/
}
}

View File

@ -0,0 +1,42 @@
error: Undefined Behavior: write access through <TAG> is forbidden
--> $DIR/mut_exclusive_violation1.rs:LL:CC
|
LL | *LEAK = 7;
| ^^^^^^^^^ 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 Disabled which forbids this child write access
help: the accessed tag <TAG> was created here
--> $DIR/mut_exclusive_violation1.rs:LL:CC
|
LL | fn unknown_code_1(x: &i32) {
| ^
help: the conflicting tag <TAG> was created here, in the initial state Frozen
--> $DIR/mut_exclusive_violation1.rs:LL:CC
|
LL | unknown_code_1(&*our);
| ^^^^^
help: the conflicting tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
--> $DIR/mut_exclusive_violation1.rs:LL:CC
|
LL | *our = 5;
| ^^^^^^^^
= help: this transition corresponds to a loss of read permissions
= note: BACKTRACE (of the first span):
= note: inside `unknown_code_2` at $DIR/mut_exclusive_violation1.rs:LL:CC
note: inside `demo_mut_advanced_unique`
--> $DIR/mut_exclusive_violation1.rs:LL:CC
|
LL | unknown_code_2();
| ^^^^^^^^^^^^^^^^
note: inside `main`
--> $DIR/mut_exclusive_violation1.rs:LL:CC
|
LL | demo_mut_advanced_unique(&mut 0);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -0,0 +1,16 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
use std::ptr::NonNull;
fn main() {
unsafe {
let x = &mut 0;
let mut ptr1 = NonNull::from(x);
let mut ptr2 = ptr1.clone();
let raw1 = ptr1.as_mut();
let raw2 = ptr2.as_mut();
let _val = *raw1; //~[stack] ERROR: /read access .* tag does not exist in the borrow stack/
*raw2 = 2;
*raw1 = 3; //~[tree] ERROR: /write access through .* is forbidden/
}
}

View File

@ -17,8 +17,8 @@ LL | let raw1 = ptr1.as_mut();
help: <TAG> was later invalidated at offsets [0x0..0x4] by a Unique retag
--> $DIR/mut_exclusive_violation2.rs:LL:CC
|
LL | let _raw2 = ptr2.as_mut();
| ^^^^^^^^^^^^^
LL | let raw2 = ptr2.as_mut();
| ^^^^^^^^^^^^^
= note: BACKTRACE (of the first span):
= note: inside `main` at $DIR/mut_exclusive_violation2.rs:LL:CC

View File

@ -0,0 +1,32 @@
error: Undefined Behavior: write access through <TAG> is forbidden
--> $DIR/mut_exclusive_violation2.rs:LL:CC
|
LL | *raw1 = 3;
| ^^^^^^^^^ 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 Disabled which forbids this child write access
help: the accessed tag <TAG> was created here
--> $DIR/mut_exclusive_violation2.rs:LL:CC
|
LL | let raw1 = ptr1.as_mut();
| ^^^^^^^^^^^^^
help: the conflicting tag <TAG> was created here, in the initial state Reserved
--> $DIR/mut_exclusive_violation2.rs:LL:CC
|
LL | let raw1 = ptr1.as_mut();
| ^^^^^^^^^^^^^
help: the conflicting tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
--> $DIR/mut_exclusive_violation2.rs:LL:CC
|
LL | *raw2 = 2;
| ^^^^^^^^^
= help: this transition corresponds to a loss of read and write permissions
= note: BACKTRACE (of the first span):
= note: inside `main` at $DIR/mut_exclusive_violation2.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,4 +1,8 @@
//@error-in-other-file: which is strongly protected
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
//@[stack]error-in-other-file: which is strongly protected
//@[tree]error-in-other-file: /deallocation through .* is forbidden/
struct Newtype<'a>(&'a mut i32, i32);
fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) {

View File

@ -0,0 +1,55 @@
error: Undefined Behavior: deallocation through <TAG> is forbidden
--> RUSTLIB/alloc/src/alloc.rs:LL:CC
|
LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ deallocation 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 foreign to the protected tag <TAG> (i.e., it is not a child)
= help: this deallocation (acting as a foreign write access) would cause the protected tag <TAG> to transition from Frozen to Disabled
= help: this transition would be a loss of read permissions, which is not allowed for protected tags
help: the accessed tag <TAG> was created here
--> $DIR/newtype_pair_retagging.rs:LL:CC
|
LL | || drop(Box::from_raw(ptr)),
| ^^^^^^^^^^^^^^^^^^^^^^^^
help: the protected tag <TAG> was created here, in the initial state Reserved
--> $DIR/newtype_pair_retagging.rs:LL:CC
|
LL | fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) {
| ^^
help: the protected tag <TAG> later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4]
--> $DIR/newtype_pair_retagging.rs:LL:CC
|
LL | || drop(Box::from_raw(ptr)),
| ^^^^^^^^^^^^^^^^^^
= help: this transition corresponds to a loss of write permissions
= note: BACKTRACE (of the first span):
= note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
= note: inside `<std::alloc::Global as std::alloc::Allocator>::deallocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC
= note: inside `<std::boxed::Box<i32> as std::ops::Drop>::drop` at RUSTLIB/alloc/src/boxed.rs:LL:CC
= note: inside `std::ptr::drop_in_place::<std::boxed::Box<i32>> - shim(Some(std::boxed::Box<i32>))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC
= note: inside `std::mem::drop::<std::boxed::Box<i32>>` at RUSTLIB/core/src/mem/mod.rs:LL:CC
note: inside closure
--> $DIR/newtype_pair_retagging.rs:LL:CC
|
LL | || drop(Box::from_raw(ptr)),
| ^^^^^^^^^^^^^^^^^^^^^^^^
note: inside `dealloc_while_running::<[closure@$DIR/newtype_pair_retagging.rs:LL:CC]>`
--> $DIR/newtype_pair_retagging.rs:LL:CC
|
LL | dealloc();
| ^^^^^^^^^
note: inside `main`
--> $DIR/newtype_pair_retagging.rs:LL:CC
|
LL | / dealloc_while_running(
LL | | Newtype(&mut *ptr, 0),
LL | | || drop(Box::from_raw(ptr)),
LL | | )
| |_________^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,4 +1,9 @@
//@error-in-other-file: which is strongly protected
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
//@[stack]error-in-other-file: which is strongly protected
//@[tree]error-in-other-file: /deallocation through .* is forbidden/
struct Newtype<'a>(&'a mut i32);
fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) {

Some files were not shown because too many files have changed in this diff Show More