Auto merge of #113151 - RalfJung:miri, r=RalfJung,oli-obk
update Miri r? `@ghost`
This commit is contained in:
commit
e69c7306e2
12
Cargo.lock
12
Cargo.lock
@ -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",
|
||||
|
26
src/tools/miri/.github/workflows/ci.yml
vendored
26
src/tools/miri/.github/workflows/ci.yml
vendored
@ -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 }}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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"] }
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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"] }
|
||||
|
@ -1 +1 @@
|
||||
33c3d101280c8eb3cd8af421bfb56a8afcc3881d
|
||||
75726cae37317c7262b69d3e9fd11a3496a88d04
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -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}"
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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]
|
||||
|
@ -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),
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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<'_, '_> {
|
||||
|
@ -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)
|
||||
//
|
||||
// // ...
|
||||
//
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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)?;
|
||||
|
@ -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]
|
||||
|
@ -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()?;
|
||||
|
@ -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)?;
|
||||
|
67
src/tools/miri/src/shims/unix/linux/mem.rs
Normal file
67
src/tools/miri/src/shims/unix/linux/mem.rs
Normal 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))
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
pub mod dlsym;
|
||||
pub mod fd;
|
||||
pub mod foreign_items;
|
||||
pub mod mem;
|
||||
pub mod sync;
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
};
|
||||
|
||||
|
158
src/tools/miri/src/shims/unix/mem.rs
Normal file
158
src/tools/miri/src/shims/unix/mem.rs
Normal 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))
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ pub mod dlsym;
|
||||
pub mod foreign_items;
|
||||
|
||||
mod fs;
|
||||
mod mem;
|
||||
mod sync;
|
||||
mod thread;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)?;
|
||||
|
||||
|
@ -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?
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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/
|
||||
}
|
@ -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
|
||||
|
@ -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;
|
@ -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`
|
@ -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
|
||||
|
@ -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;
|
@ -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`
|
@ -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
|
||||
|
@ -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;
|
@ -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
|
@ -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
|
||||
|
@ -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;
|
@ -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`
|
@ -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
|
||||
|
@ -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/
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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() {
|
@ -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
|
||||
|
@ -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/
|
||||
}
|
@ -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
|
||||
|
@ -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/
|
||||
}
|
@ -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
|
||||
|
14
src/tools/miri/tests/fail/both_borrows/illegal_write1.rs
Normal file
14
src/tools/miri/tests/fail/both_borrows/illegal_write1.rs
Normal 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;
|
||||
}
|
@ -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
|
||||
|
@ -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) {
|
@ -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
|
||||
|
17
src/tools/miri/tests/fail/both_borrows/illegal_write6.rs
Normal file
17
src/tools/miri/tests/fail/both_borrows/illegal_write6.rs
Normal 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;
|
||||
}
|
@ -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
|
||||
|
@ -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() {
|
@ -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
|
||||
|
@ -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() {
|
@ -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
|
||||
|
@ -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() {
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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/
|
||||
}
|
@ -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
|
||||
|
@ -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/
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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/
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()) {
|
@ -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
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user