Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TrustCell fixes #223

Merged
merged 4 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
timeout-minutes: 10

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
Expand Down Expand Up @@ -58,3 +58,17 @@ jobs:
with:
command: test
args: --verbose

miri:
name: "Miri"
runs-on: ubuntu-latest
continue-on-error: true # Needed until all Miri errors are fixed
Comment on lines +62 to +65
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added Miri to the CI, however it still runs into some errors in the MetaTable code that is handling vtable pointers. So I set continue-on-error: true for now (hopefully this does what I think it does...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well it seems like there is no way to not show the red X in the PR list: https://github.com/orgs/community/discussions/15452

steps:
- uses: actions/checkout@v3
- name: Install Miri
run: |
rustup toolchain install nightly --component miri
rustup override set nightly
cargo miri setup
- name: Test with Miri
run: cargo miri test
6 changes: 3 additions & 3 deletions benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,9 @@ fn bench_fetching(b: &mut Bencher) {

b.iter(|| {
for _ in 0..100 {
world.fetch::<DeltaTime>();
world.fetch::<VecStorage<Pos>>();
world.fetch::<VecStorage<Spring>>();
black_box(world.fetch::<DeltaTime>());
black_box(world.fetch::<VecStorage<Pos>>());
black_box(world.fetch::<VecStorage<Spring>>());
Comment on lines +235 to +237
Copy link
Contributor Author

@Imberflur Imberflur Feb 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added black_box just to be more sure that the compiler doesn't optimize these out (didn't really see much difference in the benchmark though)

}
})
}
Expand Down
105 changes: 71 additions & 34 deletions src/cell.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Helper module for some internals, most users don't need to interact with it.

use core::marker::PhantomData;
use core::ptr::NonNull;
use std::{
cell::UnsafeCell,
error::Error,
Expand Down Expand Up @@ -42,7 +44,7 @@ impl Error for InvalidBorrow {
#[derive(Debug)]
pub struct Ref<'a, T: ?Sized + 'a> {
flag: &'a AtomicUsize,
value: &'a T,
value: NonNull<T>,
}

impl<'a, T: ?Sized> Ref<'a, T> {
Expand Down Expand Up @@ -95,7 +97,7 @@ impl<'a, T: ?Sized> Ref<'a, T> {
{
let val = Ref {
flag: self.flag,
value: f(self.value),
value: NonNull::from(f(&*self)),
};

std::mem::forget(self);
Expand All @@ -108,7 +110,8 @@ impl<'a, T: ?Sized> Deref for Ref<'a, T> {
type Target = T;

fn deref(&self) -> &T {
self.value
// SAFETY: `Ref` holds a shared borrow of the value.
unsafe { self.value.as_ref() }
}
}

Expand All @@ -134,7 +137,10 @@ impl<'a, T: ?Sized> Clone for Ref<'a, T> {
#[derive(Debug)]
pub struct RefMut<'a, T: ?Sized + 'a> {
flag: &'a AtomicUsize,
value: &'a mut T,
value: NonNull<T>,
// `NonNull<T>` is covariant over `T` but we use it in the place of a mutable reference so we need to be
// invariant over `T`.
marker: PhantomData<&'a mut T>,
}

impl<'a, T: ?Sized> RefMut<'a, T> {
Expand Down Expand Up @@ -182,7 +188,7 @@ impl<'a, T: ?Sized> RefMut<'a, T> {
/// let b2: RefMut<'_, u32> = RefMut::map(b1, |t| &mut t.0);
/// assert_eq!(*b2, 5);
/// ```
pub fn map<U, F>(self, f: F) -> RefMut<'a, U>
pub fn map<U, F>(mut self, f: F) -> RefMut<'a, U>
where
F: FnOnce(&mut T) -> &mut U,
U: ?Sized,
Expand All @@ -192,7 +198,7 @@ impl<'a, T: ?Sized> RefMut<'a, T> {
// the given `RefMut`, the lifetime we created through turning the
// pointer into a ref is valid.
let flag = self.flag;
let value = unsafe { &mut *(self.value as *mut _) };
let value = NonNull::from(f(&mut *self));

// We have to forget self so that we do not run `Drop`. Further it's safe
// because we are creating a new `RefMut`, with the same flag, which
Expand All @@ -201,7 +207,8 @@ impl<'a, T: ?Sized> RefMut<'a, T> {

RefMut {
flag,
value: f(value),
value,
marker: PhantomData,
}
}
}
Expand All @@ -210,13 +217,17 @@ impl<'a, T: ?Sized> Deref for RefMut<'a, T> {
type Target = T;

fn deref(&self) -> &T {
self.value
// SAFETY: `RefMut` holds an exclusive borrow of the value and we have a shared borrow of
// `RefMut` here.
unsafe { self.value.as_ref() }
}
}

impl<'a, T: ?Sized> DerefMut for RefMut<'a, T> {
fn deref_mut(&mut self) -> &mut T {
self.value
// SAFETY: `RefMut` holds an exclusive borrow of the value and we have an exclusive borrow
// of `RefMut` here.
unsafe { self.value.as_mut() }
}
}

Expand Down Expand Up @@ -261,7 +272,7 @@ impl<T> TrustCell<T> {

Ref {
flag: &self.flag,
value: unsafe { &*self.inner.get() },
value: unsafe { NonNull::new_unchecked(self.inner.get()) },
}
}

Expand All @@ -274,7 +285,7 @@ impl<T> TrustCell<T> {

Ok(Ref {
flag: &self.flag,
value: unsafe { &*self.inner.get() },
value: unsafe { NonNull::new_unchecked(self.inner.get()) },
})
}

Expand All @@ -292,7 +303,8 @@ impl<T> TrustCell<T> {

RefMut {
flag: &self.flag,
value: unsafe { &mut *self.inner.get() },
value: unsafe { NonNull::new_unchecked(self.inner.get()) },
marker: PhantomData,
}
}

Expand All @@ -305,7 +317,8 @@ impl<T> TrustCell<T> {

Ok(RefMut {
flag: &self.flag,
value: unsafe { &mut *self.inner.get() },
value: unsafe { NonNull::new_unchecked(self.inner.get()) },
marker: PhantomData,
})
}

Expand Down Expand Up @@ -351,7 +364,7 @@ impl<T> TrustCell<T> {
}
}

unsafe impl<T> Sync for TrustCell<T> where T: Sync {}
unsafe impl<T> Sync for TrustCell<T> where T: Sync + Send {}

impl<T> Default for TrustCell<T>
where
Expand Down Expand Up @@ -471,22 +484,27 @@ mod tests {
assert!(cell.try_borrow_mut().is_err());
}

fn make_ref<'a, T: ?Sized>(flag: &'a AtomicUsize, value: &'a T) -> Ref<'a, T> {
Ref {
flag,
value: NonNull::from(value),
}
}

#[test]
fn ref_with_non_sized() {
let r: Ref<'_, [i32]> = Ref {
flag: &AtomicUsize::new(1),
value: &[2, 3, 4, 5][..],
};
let value = &[2, 3, 4, 5][..];
let flag = AtomicUsize::new(1);
let r: Ref<'_, [i32]> = make_ref(&flag, value);

assert_eq!(&*r, &[2, 3, 4, 5][..]);
}

#[test]
fn ref_with_non_sized_clone() {
let r: Ref<'_, [i32]> = Ref {
flag: &AtomicUsize::new(1),
value: &[2, 3, 4, 5][..],
};
let value = &[2, 3, 4, 5][..];
let flag = AtomicUsize::new(1);
let r: Ref<'_, [i32]> = make_ref(&flag, value);
let rr = r.clone();

assert_eq!(&*r, &[2, 3, 4, 5][..]);
Expand All @@ -498,30 +516,35 @@ mod tests {

#[test]
fn ref_with_trait_obj() {
let ra: Ref<'_, dyn std::any::Any> = Ref {
flag: &AtomicUsize::new(1),
value: &2i32,
};
let value = &2i32;
let flag = AtomicUsize::new(1);
let ra: Ref<'_, dyn std::any::Any> = make_ref(&flag, value);

assert_eq!(ra.downcast_ref::<i32>().unwrap(), &2i32);
}

fn make_ref_mut<'a, T: ?Sized>(flag: &'a AtomicUsize, value: &'a mut T) -> RefMut<'a, T> {
RefMut {
flag,
value: NonNull::from(value),
marker: PhantomData,
}
}

#[test]
fn ref_mut_with_non_sized() {
let mut r: RefMut<'_, [i32]> = RefMut {
flag: &AtomicUsize::new(1),
value: &mut [2, 3, 4, 5][..],
};
let value: &mut [i32] = &mut [2, 3, 4, 5][..];
let flag = AtomicUsize::new(1);
let mut r: RefMut<'_, [i32]> = make_ref_mut(&flag, value);

assert_eq!(&mut *r, &mut [2, 3, 4, 5][..]);
}

#[test]
fn ref_mut_with_trait_obj() {
let mut ra: RefMut<'_, dyn std::any::Any> = RefMut {
flag: &AtomicUsize::new(1),
value: &mut 2i32,
};
let value = &mut 2i32;
let flag = AtomicUsize::new(1);
let mut ra: RefMut<'_, dyn std::any::Any> = make_ref_mut(&flag, value);

assert_eq!(ra.downcast_mut::<i32>().unwrap(), &mut 2i32);
}
Expand Down Expand Up @@ -613,4 +636,18 @@ mod tests {
drop(r);
assert_eq!(cell.flag.load(Ordering::SeqCst), 0);
}

// Test intended to allow Miri to catch bugs like this scenario:
// https://github.com/rust-lang/rust/issues/63787
#[test]
fn drop_and_borrow_in_fn_call() {
fn drop_and_borrow(cell: &TrustCell<u8>, borrow: Ref<'_, u8>) {
drop(borrow);
*cell.borrow_mut() = 7u8;
}

let a = TrustCell::new(4u8);
let borrow = a.borrow();
drop_and_borrow(&a, borrow);
}
Comment on lines +641 to +652
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this to reproduce the scenario in the issue linked in the comment, however Miri was already reporting UB due to retagging of the reference by the mem::forget call in RefMut::map.

}