move tests that are identical between SB and TB to shared files
This commit is contained in:
parent
f8a8bbd4c0
commit
3f0d24ad4c
8 changed files with 326 additions and 764 deletions
|
|
@ -1,3 +1,6 @@
|
|||
//@revisions: stack tree
|
||||
//@[tree]compile-flags: -Zmiri-tree-borrows
|
||||
|
||||
// FIXME: this miscompiles with optimizations, see <https://github.com/rust-lang/rust/issues/132898>.
|
||||
//@compile-flags: -Zmir-opt-level=0
|
||||
|
||||
|
|
@ -59,52 +62,12 @@ fn two_phase_overlapping2() {
|
|||
x.add_assign(x + *l);
|
||||
}
|
||||
|
||||
fn with_interior_mutability() {
|
||||
use std::cell::Cell;
|
||||
|
||||
trait Thing: Sized {
|
||||
fn do_the_thing(&mut self, _s: i32) {}
|
||||
}
|
||||
|
||||
impl<T> Thing for Cell<T> {}
|
||||
|
||||
let mut x = Cell::new(1);
|
||||
let l = &x;
|
||||
|
||||
x.do_the_thing({
|
||||
x.set(3);
|
||||
l.set(4);
|
||||
x.get() + l.get()
|
||||
});
|
||||
}
|
||||
|
||||
// This one really shouldn't be accepted, but since we treat 2phase as raw, we do accept it.
|
||||
// Tree Borrows rejects it.
|
||||
fn aliasing_violation() {
|
||||
struct Foo(u64);
|
||||
impl Foo {
|
||||
fn add(&mut self, n: u64) -> u64 {
|
||||
self.0 + n
|
||||
}
|
||||
}
|
||||
|
||||
let mut f = Foo(0);
|
||||
let alias = &mut f.0 as *mut u64;
|
||||
let res = f.add(unsafe {
|
||||
*alias = 42;
|
||||
0
|
||||
});
|
||||
assert_eq!(res, 42);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
two_phase1();
|
||||
two_phase2();
|
||||
two_phase3(false);
|
||||
two_phase3(true);
|
||||
two_phase_raw();
|
||||
with_interior_mutability();
|
||||
two_phase_overlapping1();
|
||||
two_phase_overlapping2();
|
||||
aliasing_violation();
|
||||
}
|
||||
272
src/tools/miri/tests/pass/both_borrows/basic_aliasing_model.rs
Normal file
272
src/tools/miri/tests/pass/both_borrows/basic_aliasing_model.rs
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
//@revisions: stack tree
|
||||
//@[tree]compile-flags: -Zmiri-tree-borrows
|
||||
#![feature(allocator_api)]
|
||||
use std::ptr;
|
||||
|
||||
// Test various aliasing-model-related things.
|
||||
fn main() {
|
||||
read_does_not_invalidate1();
|
||||
read_does_not_invalidate2();
|
||||
mut_raw_then_mut_shr();
|
||||
mut_shr_then_mut_raw();
|
||||
mut_raw_mut();
|
||||
partially_invalidate_mut();
|
||||
drop_after_sharing();
|
||||
// direct_mut_to_const_raw();
|
||||
two_raw();
|
||||
shr_and_raw();
|
||||
disjoint_mutable_subborrows();
|
||||
raw_ref_to_part();
|
||||
array_casts();
|
||||
mut_below_shr();
|
||||
wide_raw_ptr_in_tuple();
|
||||
not_unpin_not_protected();
|
||||
write_does_not_invalidate_all_aliases();
|
||||
box_into_raw_allows_interior_mutable_alias();
|
||||
}
|
||||
|
||||
// Make sure that reading from an `&mut` does, like reborrowing to `&`,
|
||||
// NOT invalidate other reborrows.
|
||||
fn read_does_not_invalidate1() {
|
||||
fn foo(x: &mut (i32, i32)) -> &i32 {
|
||||
let xraw = x as *mut (i32, i32);
|
||||
let ret = unsafe { &(*xraw).1 };
|
||||
let _val = x.1; // we just read, this does NOT invalidate the reborrows.
|
||||
ret
|
||||
}
|
||||
assert_eq!(*foo(&mut (1, 2)), 2);
|
||||
}
|
||||
// Same as above, but this time we first create a raw, then read from `&mut`
|
||||
// and then freeze from the raw.
|
||||
fn read_does_not_invalidate2() {
|
||||
fn foo(x: &mut (i32, i32)) -> &i32 {
|
||||
let xraw = x as *mut (i32, i32);
|
||||
let _val = x.1; // we just read, this does NOT invalidate the raw reborrow.
|
||||
let ret = unsafe { &(*xraw).1 };
|
||||
ret
|
||||
}
|
||||
assert_eq!(*foo(&mut (1, 2)), 2);
|
||||
}
|
||||
|
||||
// Escape a mut to raw, then share the same mut and use the share, then the raw.
|
||||
// That should work.
|
||||
fn mut_raw_then_mut_shr() {
|
||||
let mut x = 2;
|
||||
let xref = &mut x;
|
||||
let xraw = &mut *xref as *mut _;
|
||||
let xshr = &*xref;
|
||||
assert_eq!(*xshr, 2);
|
||||
unsafe {
|
||||
*xraw = 4;
|
||||
}
|
||||
assert_eq!(x, 4);
|
||||
}
|
||||
|
||||
// Create first a shared reference and then a raw pointer from a `&mut`
|
||||
// should permit mutation through that raw pointer.
|
||||
fn mut_shr_then_mut_raw() {
|
||||
let xref = &mut 2;
|
||||
let _xshr = &*xref;
|
||||
let xraw = xref as *mut _;
|
||||
unsafe {
|
||||
*xraw = 3;
|
||||
}
|
||||
assert_eq!(*xref, 3);
|
||||
}
|
||||
|
||||
// Ensure that if we derive from a mut a raw, and then from that a mut,
|
||||
// and then read through the original mut, that does not invalidate the raw.
|
||||
// This shows that the read-exception for `&mut` applies even if the `Shr` item
|
||||
// on the stack is not at the top.
|
||||
fn mut_raw_mut() {
|
||||
let mut x = 2;
|
||||
{
|
||||
let xref1 = &mut x;
|
||||
let xraw = xref1 as *mut _;
|
||||
let _xref2 = unsafe { &mut *xraw };
|
||||
let _val = *xref1;
|
||||
unsafe {
|
||||
*xraw = 4;
|
||||
}
|
||||
// we can now use both xraw and xref1, for reading
|
||||
assert_eq!(*xref1, 4);
|
||||
assert_eq!(unsafe { *xraw }, 4);
|
||||
assert_eq!(*xref1, 4);
|
||||
assert_eq!(unsafe { *xraw }, 4);
|
||||
// we cannot use xref2; see `compile-fail/stacked-borrows/illegal_read4.rs`
|
||||
}
|
||||
assert_eq!(x, 4);
|
||||
}
|
||||
|
||||
fn partially_invalidate_mut() {
|
||||
let data = &mut (0u8, 0u8);
|
||||
let reborrow = &mut *data as *mut (u8, u8);
|
||||
let shard = unsafe { &mut (*reborrow).0 };
|
||||
data.1 += 1; // the deref overlaps with `shard`, but that is ok; the access does not overlap.
|
||||
*shard += 1; // so we can still use `shard`.
|
||||
assert_eq!(*data, (1, 1));
|
||||
}
|
||||
|
||||
// Make sure that we can handle the situation where a location is frozen when being dropped.
|
||||
fn drop_after_sharing() {
|
||||
let x = String::from("hello!");
|
||||
let _len = x.len();
|
||||
}
|
||||
|
||||
// Make sure that we can create two raw pointers from a mutable reference and use them both.
|
||||
fn two_raw() {
|
||||
unsafe {
|
||||
let x = &mut 0;
|
||||
let y1 = x as *mut _;
|
||||
let y2 = x as *mut _;
|
||||
*y1 += 2;
|
||||
*y2 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that creating a *mut does not invalidate existing shared references.
|
||||
fn shr_and_raw() {
|
||||
unsafe {
|
||||
use std::mem;
|
||||
let x = &mut 0;
|
||||
let y1: &i32 = mem::transmute(&*x); // launder lifetimes
|
||||
let y2 = x as *mut _;
|
||||
let _val = *y1;
|
||||
*y2 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn disjoint_mutable_subborrows() {
|
||||
struct Foo {
|
||||
a: String,
|
||||
b: Vec<u32>,
|
||||
}
|
||||
|
||||
unsafe fn borrow_field_a<'a>(this: *mut Foo) -> &'a mut String {
|
||||
&mut (*this).a
|
||||
}
|
||||
|
||||
unsafe fn borrow_field_b<'a>(this: *mut Foo) -> &'a mut Vec<u32> {
|
||||
&mut (*this).b
|
||||
}
|
||||
|
||||
let mut foo = Foo { a: "hello".into(), b: vec![0, 1, 2] };
|
||||
|
||||
let ptr = &mut foo as *mut Foo;
|
||||
|
||||
let a = unsafe { borrow_field_a(ptr) };
|
||||
let b = unsafe { borrow_field_b(ptr) };
|
||||
b.push(4);
|
||||
a.push_str(" world");
|
||||
assert_eq!(format!("{:?} {:?}", a, b), r#""hello world" [0, 1, 2, 4]"#);
|
||||
}
|
||||
|
||||
fn raw_ref_to_part() {
|
||||
struct Part {
|
||||
_lame: i32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct Whole {
|
||||
part: Part,
|
||||
extra: i32,
|
||||
}
|
||||
|
||||
let it = Box::new(Whole { part: Part { _lame: 0 }, extra: 42 });
|
||||
let whole = ptr::addr_of_mut!(*Box::leak(it));
|
||||
let part = unsafe { ptr::addr_of_mut!((*whole).part) };
|
||||
let typed = unsafe { &mut *(part as *mut Whole) };
|
||||
assert!(typed.extra == 42);
|
||||
drop(unsafe { Box::from_raw(whole) });
|
||||
}
|
||||
|
||||
/// When casting an array reference to a raw element ptr, that should cover the whole array.
|
||||
fn array_casts() {
|
||||
let mut x: [usize; 2] = [0, 0];
|
||||
let p = &mut x as *mut usize;
|
||||
unsafe {
|
||||
*p.add(1) = 1;
|
||||
}
|
||||
|
||||
let x: [usize; 2] = [0, 1];
|
||||
let p = &x as *const usize;
|
||||
assert_eq!(unsafe { *p.add(1) }, 1);
|
||||
}
|
||||
|
||||
/// Transmuting &&i32 to &&mut i32 is fine.
|
||||
fn mut_below_shr() {
|
||||
let x = 0;
|
||||
let y = &x;
|
||||
let p = unsafe { core::mem::transmute::<&&i32, &&mut i32>(&y) };
|
||||
let r = &**p;
|
||||
let _val = *r;
|
||||
}
|
||||
|
||||
fn wide_raw_ptr_in_tuple() {
|
||||
let mut x: Box<dyn std::any::Any> = Box::new("ouch");
|
||||
let r = &mut *x as *mut dyn std::any::Any;
|
||||
// This triggers the visitor-based recursive retagging. It is *not* supposed to retag raw
|
||||
// pointers, but then the visitor might recurse into the "fields" of a wide raw pointer and
|
||||
// finds a reference (to a vtable) there that it wants to retag... and that would be Wrong.
|
||||
let pair = (r, &0);
|
||||
let r = unsafe { &mut *pair.0 };
|
||||
// Make sure the fn ptr part of the vtable is still fine.
|
||||
r.type_id();
|
||||
}
|
||||
|
||||
fn not_unpin_not_protected() {
|
||||
// `&mut !Unpin`, at least for now, does not get `noalias` nor `dereferenceable`, so we also
|
||||
// don't add protectors. (We could, but until we have a better idea for where we want to go with
|
||||
// the self-referential-coroutine situation, it does not seem worth the potential trouble.)
|
||||
use std::marker::PhantomPinned;
|
||||
|
||||
pub struct NotUnpin(#[allow(dead_code)] i32, PhantomPinned);
|
||||
|
||||
fn inner(x: &mut NotUnpin, f: fn(&mut NotUnpin)) {
|
||||
// `f` is allowed to deallocate `x`.
|
||||
f(x)
|
||||
}
|
||||
|
||||
inner(Box::leak(Box::new(NotUnpin(0, PhantomPinned))), |x| {
|
||||
let raw = x as *mut _;
|
||||
drop(unsafe { Box::from_raw(raw) });
|
||||
});
|
||||
}
|
||||
|
||||
fn write_does_not_invalidate_all_aliases() {
|
||||
mod other {
|
||||
/// Some private memory to store stuff in.
|
||||
static mut S: *mut i32 = 0 as *mut i32;
|
||||
|
||||
pub fn lib1(x: &&mut i32) {
|
||||
unsafe {
|
||||
S = (x as *const &mut i32).cast::<*mut i32>().read();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lib2() {
|
||||
unsafe {
|
||||
*S = 1337;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let x = &mut 0;
|
||||
other::lib1(&x);
|
||||
*x = 42; // a write to x -- invalidates other pointers?
|
||||
other::lib2();
|
||||
assert_eq!(*x, 1337); // oops, the value changed! I guess not all pointers were invalidated
|
||||
}
|
||||
|
||||
fn box_into_raw_allows_interior_mutable_alias() {
|
||||
unsafe {
|
||||
let b = Box::new(std::cell::Cell::new(42));
|
||||
let raw = Box::into_raw(b);
|
||||
let c = &*raw;
|
||||
let d = raw.cast::<i32>(); // bypassing `Cell` -- only okay in Miri tests
|
||||
// `c` and `d` should permit arbitrary aliasing with each other now.
|
||||
*d = 1;
|
||||
c.set(2);
|
||||
drop(Box::from_raw(raw));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows
|
||||
//@revisions: stack tree
|
||||
//@[tree]compile-flags: -Zmiri-tree-borrows
|
||||
#![allow(dangerous_implicit_autorefs)]
|
||||
|
||||
use std::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell};
|
||||
use std::mem::{self, MaybeUninit};
|
||||
|
||||
|
|
@ -14,6 +16,7 @@ fn main() {
|
|||
ref_protector();
|
||||
ref_mut_protector();
|
||||
rust_issue_68303();
|
||||
two_phase();
|
||||
}
|
||||
|
||||
fn aliasing_mut_and_shr() {
|
||||
|
|
@ -101,13 +104,15 @@ fn unsafe_cell_invalidate() {
|
|||
let raw1 = &mut x as *mut _;
|
||||
let ref1 = unsafe { &mut *raw1 };
|
||||
let raw2 = ref1 as *mut _;
|
||||
// Now the borrow tree is:
|
||||
// Now the borrow stack is: raw1, ref2, raw2.
|
||||
//
|
||||
// For TB, the tree is
|
||||
//
|
||||
// Act x
|
||||
// Res `- raw1
|
||||
// Res `- ref1, raw2
|
||||
//
|
||||
// So using raw1 invalidates raw2.
|
||||
// Either way, using raw1 invalidates raw2.
|
||||
f(unsafe { mem::transmute(raw2) }, raw1);
|
||||
}
|
||||
|
||||
|
|
@ -179,3 +184,27 @@ fn rust_issue_68303() {
|
|||
assert!(optional.is_some());
|
||||
*handle = true;
|
||||
}
|
||||
|
||||
fn two_phase() {
|
||||
use std::cell::Cell;
|
||||
|
||||
trait Thing: Sized {
|
||||
fn do_the_thing(&mut self, _s: i32) {}
|
||||
}
|
||||
|
||||
impl<T> Thing for Cell<T> {}
|
||||
|
||||
let mut x = Cell::new(1);
|
||||
let l = &x;
|
||||
|
||||
x.do_the_thing({
|
||||
// In TB terms:
|
||||
// Several Foreign accesses (both Reads and Writes) to the location
|
||||
// being reborrowed. Reserved + unprotected + interior mut
|
||||
// makes the pointer immune to everything as long as all accesses
|
||||
// are child accesses to its parent pointer x.
|
||||
x.set(3);
|
||||
l.set(4);
|
||||
x.get() + l.get()
|
||||
});
|
||||
}
|
||||
|
|
@ -1,176 +0,0 @@
|
|||
#![allow(dangerous_implicit_autorefs)]
|
||||
|
||||
use std::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell};
|
||||
use std::mem::{self, MaybeUninit};
|
||||
|
||||
fn main() {
|
||||
aliasing_mut_and_shr();
|
||||
aliasing_frz_and_shr();
|
||||
into_interior_mutability();
|
||||
unsafe_cell_2phase();
|
||||
unsafe_cell_deallocate();
|
||||
unsafe_cell_invalidate();
|
||||
refcell_basic();
|
||||
ref_protector();
|
||||
ref_mut_protector();
|
||||
rust_issue_68303();
|
||||
}
|
||||
|
||||
fn aliasing_mut_and_shr() {
|
||||
fn inner(rc: &RefCell<i32>, aliasing: &mut i32) {
|
||||
*aliasing += 4;
|
||||
let _escape_to_raw = rc as *const _;
|
||||
*aliasing += 4;
|
||||
let _shr = &*rc;
|
||||
*aliasing += 4;
|
||||
// also turning this into a frozen ref now must work
|
||||
let aliasing = &*aliasing;
|
||||
let _val = *aliasing;
|
||||
let _escape_to_raw = rc as *const _; // this must NOT unfreeze
|
||||
let _val = *aliasing;
|
||||
let _shr = &*rc; // this must NOT unfreeze
|
||||
let _val = *aliasing;
|
||||
}
|
||||
|
||||
let rc = RefCell::new(23);
|
||||
let mut bmut = rc.borrow_mut();
|
||||
inner(&rc, &mut *bmut);
|
||||
drop(bmut);
|
||||
assert_eq!(*rc.borrow(), 23 + 12);
|
||||
}
|
||||
|
||||
fn aliasing_frz_and_shr() {
|
||||
fn inner(rc: &RefCell<i32>, aliasing: &i32) {
|
||||
let _val = *aliasing;
|
||||
let _escape_to_raw = rc as *const _; // this must NOT unfreeze
|
||||
let _val = *aliasing;
|
||||
let _shr = &*rc; // this must NOT unfreeze
|
||||
let _val = *aliasing;
|
||||
}
|
||||
|
||||
let rc = RefCell::new(23);
|
||||
let bshr = rc.borrow();
|
||||
inner(&rc, &*bshr);
|
||||
assert_eq!(*rc.borrow(), 23);
|
||||
}
|
||||
|
||||
// Getting a pointer into a union with interior mutability used to be tricky
|
||||
// business (https://github.com/rust-lang/miri/issues/615), but it should work
|
||||
// now.
|
||||
fn into_interior_mutability() {
|
||||
let mut x: MaybeUninit<(Cell<u32>, u32)> = MaybeUninit::uninit();
|
||||
x.as_ptr();
|
||||
x.write((Cell::new(0), 1));
|
||||
let ptr = unsafe { x.assume_init_ref() };
|
||||
assert_eq!(ptr.1, 1);
|
||||
}
|
||||
|
||||
// Two-phase borrows of the pointer returned by UnsafeCell::get() should not
|
||||
// invalidate aliases.
|
||||
fn unsafe_cell_2phase() {
|
||||
unsafe {
|
||||
let x = &UnsafeCell::new(vec![]);
|
||||
let x2 = &*x;
|
||||
(*x.get()).push(0);
|
||||
let _val = (*x2.get()).get(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Make sure we can deallocate an UnsafeCell that was passed to an active fn call.
|
||||
/// (This is the fix for https://github.com/rust-lang/rust/issues/55005.)
|
||||
fn unsafe_cell_deallocate() {
|
||||
fn f(x: &UnsafeCell<i32>) {
|
||||
let b: Box<i32> = unsafe { Box::from_raw(x as *const _ as *mut i32) };
|
||||
drop(b)
|
||||
}
|
||||
|
||||
let b = Box::new(0i32);
|
||||
f(unsafe { mem::transmute(Box::into_raw(b)) });
|
||||
}
|
||||
|
||||
/// As a side-effect of the above, we also allow this -- at least for now.
|
||||
fn unsafe_cell_invalidate() {
|
||||
fn f(_x: &UnsafeCell<i32>, y: *mut i32) {
|
||||
// Writing to y invalidates x, but that is okay.
|
||||
unsafe {
|
||||
*y += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut x = 0i32;
|
||||
let raw1 = &mut x as *mut _;
|
||||
let ref1 = unsafe { &mut *raw1 };
|
||||
let raw2 = ref1 as *mut _;
|
||||
// Now the borrow stack is: raw1, ref2, raw2.
|
||||
// So using raw1 invalidates raw2.
|
||||
f(unsafe { mem::transmute(raw2) }, raw1);
|
||||
}
|
||||
|
||||
fn refcell_basic() {
|
||||
let c = RefCell::new(42);
|
||||
{
|
||||
let s1 = c.borrow();
|
||||
let _x: i32 = *s1;
|
||||
let s2 = c.borrow();
|
||||
let _x: i32 = *s1;
|
||||
let _y: i32 = *s2;
|
||||
let _x: i32 = *s1;
|
||||
let _y: i32 = *s2;
|
||||
}
|
||||
{
|
||||
let mut m = c.borrow_mut();
|
||||
let _z: i32 = *m;
|
||||
{
|
||||
let s: &i32 = &*m;
|
||||
let _x = *s;
|
||||
}
|
||||
*m = 23;
|
||||
let _z: i32 = *m;
|
||||
}
|
||||
{
|
||||
let s1 = c.borrow();
|
||||
let _x: i32 = *s1;
|
||||
let s2 = c.borrow();
|
||||
let _x: i32 = *s1;
|
||||
let _y: i32 = *s2;
|
||||
let _x: i32 = *s1;
|
||||
let _y: i32 = *s2;
|
||||
}
|
||||
}
|
||||
|
||||
// Adding a Stacked Borrows protector for `Ref` would break this
|
||||
fn ref_protector() {
|
||||
fn break_it(rc: &RefCell<i32>, r: Ref<'_, i32>) {
|
||||
// `r` has a shared reference, it is passed in as argument and hence
|
||||
// a protector is added that marks this memory as read-only for the entire
|
||||
// duration of this function.
|
||||
drop(r);
|
||||
// *oops* here we can mutate that memory.
|
||||
*rc.borrow_mut() = 2;
|
||||
}
|
||||
|
||||
let rc = RefCell::new(0);
|
||||
break_it(&rc, rc.borrow())
|
||||
}
|
||||
|
||||
fn ref_mut_protector() {
|
||||
fn break_it(rc: &RefCell<i32>, r: RefMut<'_, i32>) {
|
||||
// `r` has a shared reference, it is passed in as argument and hence
|
||||
// a protector is added that marks this memory as inaccessible for the entire
|
||||
// duration of this function
|
||||
drop(r);
|
||||
// *oops* here we can mutate that memory.
|
||||
*rc.borrow_mut() = 2;
|
||||
}
|
||||
|
||||
let rc = RefCell::new(0);
|
||||
break_it(&rc, rc.borrow_mut())
|
||||
}
|
||||
|
||||
/// Make sure we do not have bad enum layout optimizations.
|
||||
fn rust_issue_68303() {
|
||||
let optional = Some(RefCell::new(false));
|
||||
let mut handle = optional.as_ref().unwrap().borrow_mut();
|
||||
assert!(optional.is_some());
|
||||
*handle = true;
|
||||
}
|
||||
|
|
@ -1,100 +1,9 @@
|
|||
#![feature(allocator_api)]
|
||||
use std::ptr;
|
||||
|
||||
// Test various stacked-borrows-related things.
|
||||
// Test various stacked-borrows-specific things
|
||||
// (i.e., these do not work the same under TB).
|
||||
fn main() {
|
||||
read_does_not_invalidate1();
|
||||
read_does_not_invalidate2();
|
||||
mut_raw_then_mut_shr();
|
||||
mut_shr_then_mut_raw();
|
||||
mut_raw_mut();
|
||||
mut_raw_mut2();
|
||||
partially_invalidate_mut();
|
||||
drop_after_sharing();
|
||||
// direct_mut_to_const_raw();
|
||||
two_raw();
|
||||
shr_and_raw();
|
||||
disjoint_mutable_subborrows();
|
||||
raw_ref_to_part();
|
||||
array_casts();
|
||||
mut_below_shr();
|
||||
wide_raw_ptr_in_tuple();
|
||||
not_unpin_not_protected();
|
||||
write_does_not_invalidate_all_aliases();
|
||||
box_into_raw_allows_interior_mutable_alias();
|
||||
}
|
||||
|
||||
// Make sure that reading from an `&mut` does, like reborrowing to `&`,
|
||||
// NOT invalidate other reborrows.
|
||||
fn read_does_not_invalidate1() {
|
||||
fn foo(x: &mut (i32, i32)) -> &i32 {
|
||||
let xraw = x as *mut (i32, i32);
|
||||
let ret = unsafe { &(*xraw).1 };
|
||||
let _val = x.1; // we just read, this does NOT invalidate the reborrows.
|
||||
ret
|
||||
}
|
||||
assert_eq!(*foo(&mut (1, 2)), 2);
|
||||
}
|
||||
// Same as above, but this time we first create a raw, then read from `&mut`
|
||||
// and then freeze from the raw.
|
||||
fn read_does_not_invalidate2() {
|
||||
fn foo(x: &mut (i32, i32)) -> &i32 {
|
||||
let xraw = x as *mut (i32, i32);
|
||||
let _val = x.1; // we just read, this does NOT invalidate the raw reborrow.
|
||||
let ret = unsafe { &(*xraw).1 };
|
||||
ret
|
||||
}
|
||||
assert_eq!(*foo(&mut (1, 2)), 2);
|
||||
}
|
||||
|
||||
// Escape a mut to raw, then share the same mut and use the share, then the raw.
|
||||
// That should work.
|
||||
fn mut_raw_then_mut_shr() {
|
||||
let mut x = 2;
|
||||
let xref = &mut x;
|
||||
let xraw = &mut *xref as *mut _;
|
||||
let xshr = &*xref;
|
||||
assert_eq!(*xshr, 2);
|
||||
unsafe {
|
||||
*xraw = 4;
|
||||
}
|
||||
assert_eq!(x, 4);
|
||||
}
|
||||
|
||||
// Create first a shared reference and then a raw pointer from a `&mut`
|
||||
// should permit mutation through that raw pointer.
|
||||
fn mut_shr_then_mut_raw() {
|
||||
let xref = &mut 2;
|
||||
let _xshr = &*xref;
|
||||
let xraw = xref as *mut _;
|
||||
unsafe {
|
||||
*xraw = 3;
|
||||
}
|
||||
assert_eq!(*xref, 3);
|
||||
}
|
||||
|
||||
// Ensure that if we derive from a mut a raw, and then from that a mut,
|
||||
// and then read through the original mut, that does not invalidate the raw.
|
||||
// This shows that the read-exception for `&mut` applies even if the `Shr` item
|
||||
// on the stack is not at the top.
|
||||
fn mut_raw_mut() {
|
||||
let mut x = 2;
|
||||
{
|
||||
let xref1 = &mut x;
|
||||
let xraw = xref1 as *mut _;
|
||||
let _xref2 = unsafe { &mut *xraw };
|
||||
let _val = *xref1;
|
||||
unsafe {
|
||||
*xraw = 4;
|
||||
}
|
||||
// we can now use both xraw and xref1, for reading
|
||||
assert_eq!(*xref1, 4);
|
||||
assert_eq!(unsafe { *xraw }, 4);
|
||||
assert_eq!(*xref1, 4);
|
||||
assert_eq!(unsafe { *xraw }, 4);
|
||||
// we cannot use xref2; see `compile-fail/stacked-borrows/illegal_read4.rs`
|
||||
}
|
||||
assert_eq!(x, 4);
|
||||
two_phase_aliasing_violation();
|
||||
}
|
||||
|
||||
// A variant of `mut_raw_mut` that does *not* get accepted by Tree Borrows.
|
||||
|
|
@ -109,21 +18,6 @@ fn mut_raw_mut2() {
|
|||
}
|
||||
}
|
||||
|
||||
fn partially_invalidate_mut() {
|
||||
let data = &mut (0u8, 0u8);
|
||||
let reborrow = &mut *data as *mut (u8, u8);
|
||||
let shard = unsafe { &mut (*reborrow).0 };
|
||||
data.1 += 1; // the deref overlaps with `shard`, but that is ok; the access does not overlap.
|
||||
*shard += 1; // so we can still use `shard`.
|
||||
assert_eq!(*data, (1, 1));
|
||||
}
|
||||
|
||||
// Make sure that we can handle the situation where a location is frozen when being dropped.
|
||||
fn drop_after_sharing() {
|
||||
let x = String::from("hello!");
|
||||
let _len = x.len();
|
||||
}
|
||||
|
||||
// Make sure that coercing &mut T to *const T produces a writeable pointer.
|
||||
// TODO: This is currently disabled, waiting on a decision on <https://github.com/rust-lang/rust/issues/56604>
|
||||
/*fn direct_mut_to_const_raw() {
|
||||
|
|
@ -133,160 +27,21 @@ fn drop_after_sharing() {
|
|||
assert_eq!(*x, 1);
|
||||
}*/
|
||||
|
||||
// Make sure that we can create two raw pointers from a mutable reference and use them both.
|
||||
fn two_raw() {
|
||||
unsafe {
|
||||
let x = &mut 0;
|
||||
let y1 = x as *mut _;
|
||||
let y2 = x as *mut _;
|
||||
*y1 += 2;
|
||||
*y2 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that creating a *mut does not invalidate existing shared references.
|
||||
fn shr_and_raw() {
|
||||
unsafe {
|
||||
use std::mem;
|
||||
let x = &mut 0;
|
||||
let y1: &i32 = mem::transmute(&*x); // launder lifetimes
|
||||
let y2 = x as *mut _;
|
||||
let _val = *y1;
|
||||
*y2 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn disjoint_mutable_subborrows() {
|
||||
struct Foo {
|
||||
a: String,
|
||||
b: Vec<u32>,
|
||||
// This one really shouldn't be accepted, but since we treat 2phase as raw, we do accept it.
|
||||
// Tree Borrows rejects it.
|
||||
fn two_phase_aliasing_violation() {
|
||||
struct Foo(u64);
|
||||
impl Foo {
|
||||
fn add(&mut self, n: u64) -> u64 {
|
||||
self.0 + n
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn borrow_field_a<'a>(this: *mut Foo) -> &'a mut String {
|
||||
&mut (*this).a
|
||||
}
|
||||
|
||||
unsafe fn borrow_field_b<'a>(this: *mut Foo) -> &'a mut Vec<u32> {
|
||||
&mut (*this).b
|
||||
}
|
||||
|
||||
let mut foo = Foo { a: "hello".into(), b: vec![0, 1, 2] };
|
||||
|
||||
let ptr = &mut foo as *mut Foo;
|
||||
|
||||
let a = unsafe { borrow_field_a(ptr) };
|
||||
let b = unsafe { borrow_field_b(ptr) };
|
||||
b.push(4);
|
||||
a.push_str(" world");
|
||||
eprintln!("{:?} {:?}", a, b);
|
||||
}
|
||||
|
||||
fn raw_ref_to_part() {
|
||||
struct Part {
|
||||
_lame: i32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct Whole {
|
||||
part: Part,
|
||||
extra: i32,
|
||||
}
|
||||
|
||||
let it = Box::new(Whole { part: Part { _lame: 0 }, extra: 42 });
|
||||
let whole = ptr::addr_of_mut!(*Box::leak(it));
|
||||
let part = unsafe { ptr::addr_of_mut!((*whole).part) };
|
||||
let typed = unsafe { &mut *(part as *mut Whole) };
|
||||
assert!(typed.extra == 42);
|
||||
drop(unsafe { Box::from_raw(whole) });
|
||||
}
|
||||
|
||||
/// When casting an array reference to a raw element ptr, that should cover the whole array.
|
||||
fn array_casts() {
|
||||
let mut x: [usize; 2] = [0, 0];
|
||||
let p = &mut x as *mut usize;
|
||||
unsafe {
|
||||
*p.add(1) = 1;
|
||||
}
|
||||
|
||||
let x: [usize; 2] = [0, 1];
|
||||
let p = &x as *const usize;
|
||||
assert_eq!(unsafe { *p.add(1) }, 1);
|
||||
}
|
||||
|
||||
/// Transmuting &&i32 to &&mut i32 is fine.
|
||||
fn mut_below_shr() {
|
||||
let x = 0;
|
||||
let y = &x;
|
||||
let p = unsafe { core::mem::transmute::<&&i32, &&mut i32>(&y) };
|
||||
let r = &**p;
|
||||
let _val = *r;
|
||||
}
|
||||
|
||||
fn wide_raw_ptr_in_tuple() {
|
||||
let mut x: Box<dyn std::any::Any> = Box::new("ouch");
|
||||
let r = &mut *x as *mut dyn std::any::Any;
|
||||
// This triggers the visitor-based recursive retagging. It is *not* supposed to retag raw
|
||||
// pointers, but then the visitor might recurse into the "fields" of a wide raw pointer and
|
||||
// finds a reference (to a vtable) there that it wants to retag... and that would be Wrong.
|
||||
let pair = (r, &0);
|
||||
let r = unsafe { &mut *pair.0 };
|
||||
// Make sure the fn ptr part of the vtable is still fine.
|
||||
r.type_id();
|
||||
}
|
||||
|
||||
fn not_unpin_not_protected() {
|
||||
// `&mut !Unpin`, at least for now, does not get `noalias` nor `dereferenceable`, so we also
|
||||
// don't add protectors. (We could, but until we have a better idea for where we want to go with
|
||||
// the self-referential-coroutine situation, it does not seem worth the potential trouble.)
|
||||
use std::marker::PhantomPinned;
|
||||
|
||||
pub struct NotUnpin(#[allow(dead_code)] i32, PhantomPinned);
|
||||
|
||||
fn inner(x: &mut NotUnpin, f: fn(&mut NotUnpin)) {
|
||||
// `f` may mutate, but it may not deallocate!
|
||||
f(x)
|
||||
}
|
||||
|
||||
inner(Box::leak(Box::new(NotUnpin(0, PhantomPinned))), |x| {
|
||||
let raw = x as *mut _;
|
||||
drop(unsafe { Box::from_raw(raw) });
|
||||
let mut f = Foo(0);
|
||||
let alias = &mut f.0 as *mut u64;
|
||||
let res = f.add(unsafe {
|
||||
*alias = 42;
|
||||
0
|
||||
});
|
||||
}
|
||||
|
||||
fn write_does_not_invalidate_all_aliases() {
|
||||
mod other {
|
||||
/// Some private memory to store stuff in.
|
||||
static mut S: *mut i32 = 0 as *mut i32;
|
||||
|
||||
pub fn lib1(x: &&mut i32) {
|
||||
unsafe {
|
||||
S = (x as *const &mut i32).cast::<*mut i32>().read();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lib2() {
|
||||
unsafe {
|
||||
*S = 1337;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let x = &mut 0;
|
||||
other::lib1(&x);
|
||||
*x = 42; // a write to x -- invalidates other pointers?
|
||||
other::lib2();
|
||||
assert_eq!(*x, 1337); // oops, the value changed! I guess not all pointers were invalidated
|
||||
}
|
||||
|
||||
fn box_into_raw_allows_interior_mutable_alias() {
|
||||
unsafe {
|
||||
let b = Box::new(std::cell::Cell::new(42));
|
||||
let raw = Box::into_raw(b);
|
||||
let c = &*raw;
|
||||
let d = raw.cast::<i32>(); // bypassing `Cell` -- only okay in Miri tests
|
||||
// `c` and `d` should permit arbitrary aliasing with each other now.
|
||||
*d = 1;
|
||||
c.set(2);
|
||||
drop(Box::from_raw(raw));
|
||||
}
|
||||
assert_eq!(res, 42);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
"hello world" [0, 1, 2, 4]
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows
|
||||
|
||||
// Counterpart to tests/fail/tree-borrows/write-during-2phase.rs,
|
||||
// this is the opposite situation: the Write is not problematic because
|
||||
// the Protector has not yet been added and the Reserved has interior
|
||||
// mutability.
|
||||
use core::cell::Cell;
|
||||
|
||||
trait Thing: Sized {
|
||||
fn do_the_thing(&mut self, _s: i32) {}
|
||||
}
|
||||
impl<T> Thing for Cell<T> {}
|
||||
|
||||
fn main() {
|
||||
let mut x = Cell::new(1);
|
||||
let l = &x;
|
||||
|
||||
x.do_the_thing({
|
||||
// Several Foreign accesses (both Reads and Writes) to the location
|
||||
// being reborrowed. Reserved + unprotected + interior mut
|
||||
// makes the pointer immune to everything as long as all accesses
|
||||
// are child accesses to its parent pointer x.
|
||||
x.set(3);
|
||||
l.set(4);
|
||||
x.get() + l.get()
|
||||
});
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
use std::{mem, ptr};
|
||||
|
||||
// Test various tree-borrows-specific things
|
||||
// (i.e., these do not work the same under SB).
|
||||
fn main() {
|
||||
aliasing_read_only_mutable_refs();
|
||||
string_as_mut_ptr();
|
||||
|
|
@ -10,24 +12,6 @@ fn main() {
|
|||
direct_mut_to_const_raw();
|
||||
local_addr_of_mut();
|
||||
returned_mut_is_usable();
|
||||
|
||||
// Stacked Borrows tests
|
||||
read_does_not_invalidate1();
|
||||
read_does_not_invalidate2();
|
||||
mut_raw_then_mut_shr();
|
||||
mut_shr_then_mut_raw();
|
||||
mut_raw_mut();
|
||||
partially_invalidate_mut();
|
||||
drop_after_sharing();
|
||||
two_raw();
|
||||
shr_and_raw();
|
||||
disjoint_mutable_subborrows();
|
||||
raw_ref_to_part();
|
||||
array_casts();
|
||||
mut_below_shr();
|
||||
wide_raw_ptr_in_tuple();
|
||||
not_unpin_not_protected();
|
||||
write_does_not_invalidate_all_aliases();
|
||||
}
|
||||
|
||||
#[allow(unused_assignments)]
|
||||
|
|
@ -109,96 +93,6 @@ fn returned_mut_is_usable() {
|
|||
*y = 1;
|
||||
}
|
||||
|
||||
// ----- The tests below were taken from Stacked Borrows ----
|
||||
|
||||
// Make sure that reading from an `&mut` does, like reborrowing to `&`,
|
||||
// NOT invalidate other reborrows.
|
||||
fn read_does_not_invalidate1() {
|
||||
fn foo(x: &mut (i32, i32)) -> &i32 {
|
||||
let xraw = x as *mut (i32, i32);
|
||||
let ret = unsafe { &(*xraw).1 };
|
||||
let _val = x.1; // we just read, this does NOT invalidate the reborrows.
|
||||
ret
|
||||
}
|
||||
assert_eq!(*foo(&mut (1, 2)), 2);
|
||||
}
|
||||
// Same as above, but this time we first create a raw, then read from `&mut`
|
||||
// and then freeze from the raw.
|
||||
fn read_does_not_invalidate2() {
|
||||
fn foo(x: &mut (i32, i32)) -> &i32 {
|
||||
let xraw = x as *mut (i32, i32);
|
||||
let _val = x.1; // we just read, this does NOT invalidate the raw reborrow.
|
||||
let ret = unsafe { &(*xraw).1 };
|
||||
ret
|
||||
}
|
||||
assert_eq!(*foo(&mut (1, 2)), 2);
|
||||
}
|
||||
|
||||
// Escape a mut to raw, then share the same mut and use the share, then the raw.
|
||||
// That should work.
|
||||
fn mut_raw_then_mut_shr() {
|
||||
let mut x = 2;
|
||||
let xref = &mut x;
|
||||
let xraw = &mut *xref as *mut _;
|
||||
let xshr = &*xref;
|
||||
assert_eq!(*xshr, 2);
|
||||
unsafe {
|
||||
*xraw = 4;
|
||||
}
|
||||
assert_eq!(x, 4);
|
||||
}
|
||||
|
||||
// Create first a shared reference and then a raw pointer from a `&mut`
|
||||
// should permit mutation through that raw pointer.
|
||||
fn mut_shr_then_mut_raw() {
|
||||
let xref = &mut 2;
|
||||
let _xshr = &*xref;
|
||||
let xraw = xref as *mut _;
|
||||
unsafe {
|
||||
*xraw = 3;
|
||||
}
|
||||
assert_eq!(*xref, 3);
|
||||
}
|
||||
|
||||
// Ensure that if we derive from a mut a raw, and then from that a mut,
|
||||
// and then read through the original mut, that does not invalidate the raw.
|
||||
// This shows that the read-exception for `&mut` applies even if the `Shr` item
|
||||
// on the stack is not at the top.
|
||||
fn mut_raw_mut() {
|
||||
let mut x = 2;
|
||||
{
|
||||
let xref1 = &mut x;
|
||||
let xraw = xref1 as *mut _;
|
||||
let _xref2 = unsafe { &mut *xraw };
|
||||
let _val = *xref1;
|
||||
unsafe {
|
||||
*xraw = 4;
|
||||
}
|
||||
// we can now use both xraw and xref1, for reading
|
||||
assert_eq!(*xref1, 4);
|
||||
assert_eq!(unsafe { *xraw }, 4);
|
||||
assert_eq!(*xref1, 4);
|
||||
assert_eq!(unsafe { *xraw }, 4);
|
||||
// we cannot use xref2; see `compile-fail/stacked-borrows/illegal_read4.rs`
|
||||
}
|
||||
assert_eq!(x, 4);
|
||||
}
|
||||
|
||||
fn partially_invalidate_mut() {
|
||||
let data = &mut (0u8, 0u8);
|
||||
let reborrow = &mut *data as *mut (u8, u8);
|
||||
let shard = unsafe { &mut (*reborrow).0 };
|
||||
data.1 += 1; // the deref overlaps with `shard`, but that is ok; the access does not overlap.
|
||||
*shard += 1; // so we can still use `shard`.
|
||||
assert_eq!(*data, (1, 1));
|
||||
}
|
||||
|
||||
// Make sure that we can handle the situation where a location is frozen when being dropped.
|
||||
fn drop_after_sharing() {
|
||||
let x = String::from("hello!");
|
||||
let _len = x.len();
|
||||
}
|
||||
|
||||
// Make sure that coercing &mut T to *const T produces a writeable pointer.
|
||||
fn direct_mut_to_const_raw() {
|
||||
let x = &mut 0;
|
||||
|
|
@ -208,150 +102,3 @@ fn direct_mut_to_const_raw() {
|
|||
}
|
||||
assert_eq!(*x, 1);
|
||||
}
|
||||
|
||||
// Make sure that we can create two raw pointers from a mutable reference and use them both.
|
||||
fn two_raw() {
|
||||
unsafe {
|
||||
let x = &mut 0;
|
||||
let y1 = x as *mut _;
|
||||
let y2 = x as *mut _;
|
||||
*y1 += 2;
|
||||
*y2 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that creating a *mut does not invalidate existing shared references.
|
||||
fn shr_and_raw() {
|
||||
unsafe {
|
||||
let x = &mut 0;
|
||||
let y1: &i32 = mem::transmute(&*x); // launder lifetimes
|
||||
let y2 = x as *mut _;
|
||||
let _val = *y1;
|
||||
*y2 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn disjoint_mutable_subborrows() {
|
||||
struct Foo {
|
||||
a: String,
|
||||
b: Vec<u32>,
|
||||
}
|
||||
|
||||
unsafe fn borrow_field_a<'a>(this: *mut Foo) -> &'a mut String {
|
||||
&mut (*this).a
|
||||
}
|
||||
|
||||
unsafe fn borrow_field_b<'a>(this: *mut Foo) -> &'a mut Vec<u32> {
|
||||
&mut (*this).b
|
||||
}
|
||||
|
||||
let mut foo = Foo { a: "hello".into(), b: vec![0, 1, 2] };
|
||||
|
||||
let ptr = &mut foo as *mut Foo;
|
||||
|
||||
let a = unsafe { borrow_field_a(ptr) };
|
||||
let b = unsafe { borrow_field_b(ptr) };
|
||||
b.push(4);
|
||||
a.push_str(" world");
|
||||
assert_eq!(format!("{:?} {:?}", a, b), r#""hello world" [0, 1, 2, 4]"#);
|
||||
}
|
||||
|
||||
fn raw_ref_to_part() {
|
||||
struct Part {
|
||||
_lame: i32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct Whole {
|
||||
part: Part,
|
||||
extra: i32,
|
||||
}
|
||||
|
||||
let it = Box::new(Whole { part: Part { _lame: 0 }, extra: 42 });
|
||||
let whole = ptr::addr_of_mut!(*Box::leak(it));
|
||||
let part = unsafe { ptr::addr_of_mut!((*whole).part) };
|
||||
let typed = unsafe { &mut *(part as *mut Whole) };
|
||||
assert!(typed.extra == 42);
|
||||
drop(unsafe { Box::from_raw(whole) });
|
||||
}
|
||||
|
||||
/// When casting an array reference to a raw element ptr, that should cover the whole array.
|
||||
fn array_casts() {
|
||||
let mut x: [usize; 2] = [0, 0];
|
||||
let p = &mut x as *mut usize;
|
||||
unsafe {
|
||||
*p.add(1) = 1;
|
||||
}
|
||||
|
||||
let x: [usize; 2] = [0, 1];
|
||||
let p = &x as *const usize;
|
||||
assert_eq!(unsafe { *p.add(1) }, 1);
|
||||
}
|
||||
|
||||
/// Transmuting &&i32 to &&mut i32 is fine.
|
||||
fn mut_below_shr() {
|
||||
let x = 0;
|
||||
let y = &x;
|
||||
let p = unsafe { core::mem::transmute::<&&i32, &&mut i32>(&y) };
|
||||
let r = &**p;
|
||||
let _val = *r;
|
||||
}
|
||||
|
||||
fn wide_raw_ptr_in_tuple() {
|
||||
let mut x: Box<dyn std::any::Any> = Box::new("ouch");
|
||||
let r = &mut *x as *mut dyn std::any::Any;
|
||||
// This triggers the visitor-based recursive retagging. It is *not* supposed to retag raw
|
||||
// pointers, but then the visitor might recurse into the "fields" of a wide raw pointer and
|
||||
// finds a reference (to a vtable) there that it wants to retag... and that would be Wrong.
|
||||
let pair = (r, &0);
|
||||
let r = unsafe { &mut *pair.0 };
|
||||
// Make sure the fn ptr part of the vtable is still fine.
|
||||
r.type_id();
|
||||
}
|
||||
|
||||
fn not_unpin_not_protected() {
|
||||
// `&mut !Unpin`, at least for now, does not get `noalias` nor `dereferenceable`, so we also
|
||||
// don't add protectors. (We could, but until we have a better idea for where we want to go with
|
||||
// the self-referential-coroutine situation, it does not seem worth the potential trouble.)
|
||||
use std::marker::PhantomPinned;
|
||||
|
||||
pub struct NotUnpin(#[allow(dead_code)] i32, PhantomPinned);
|
||||
|
||||
fn inner(x: &mut NotUnpin, f: fn(&mut NotUnpin)) {
|
||||
// `f` is allowed to deallocate `x`.
|
||||
f(x)
|
||||
}
|
||||
|
||||
inner(Box::leak(Box::new(NotUnpin(0, PhantomPinned))), |x| {
|
||||
let raw = x as *mut _;
|
||||
drop(unsafe { Box::from_raw(raw) });
|
||||
});
|
||||
}
|
||||
|
||||
fn write_does_not_invalidate_all_aliases() {
|
||||
// In TB there are other ways to do that (`addr_of!(*x)` has the same tag as `x`),
|
||||
// but let's still make sure this SB test keeps working.
|
||||
|
||||
mod other {
|
||||
/// Some private memory to store stuff in.
|
||||
static mut S: *mut i32 = 0 as *mut i32;
|
||||
|
||||
pub fn lib1(x: &&mut i32) {
|
||||
unsafe {
|
||||
S = (x as *const &mut i32).cast::<*mut i32>().read();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lib2() {
|
||||
unsafe {
|
||||
*S = 1337;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let x = &mut 0;
|
||||
other::lib1(&x);
|
||||
*x = 42; // a write to x -- invalidates other pointers?
|
||||
other::lib2();
|
||||
assert_eq!(*x, 1337); // oops, the value changed! I guess not all pointers were invalidated
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue