-
Notifications
You must be signed in to change notification settings - Fork 108
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
Invariant-parameterize Ptr
and make is_bit_valid
safe
#699
Conversation
Ptr
s point to validly-aligned dataPtr
s point to validly-aligned data
a6b1487
to
c1141bf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some invariants are now invalid - e.g., in as_ref
, the safety comment assumes that self.ptr
is aligned even though it's inside a block in which ASSUME_ALIGN
can be either true or false.
src/util.rs
Outdated
/// The argument to `TryFromBytes::is_bit_valid`. | ||
pub type MaybeValid<'a, T> = Ptr<'a, T, false>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO the name MaybeValid
implies that the only thing that's not guaranteed is validity, but here lifetime and aliasing are both not guaranteed. I think we should just use Ptr
directly since it's more clear that the user should go look at the type rather than guess its semantics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ptr
does guarantee lifetime (item 8):
/// INVARIANTS:
/// 1. `ptr` is derived from some valid Rust allocation, `A`
/// 2. `ptr` has the same provenance as `A`
/// 3. `ptr` addresses a byte range which is entirely contained in `A`
/// 4. `ptr` addresses a byte range whose length fits in an `isize`
/// 5. `ptr` addresses a byte range which does not wrap around the address
/// space
/// 6. If `ASSUME_ALIGNED`, `ptr` is validly-aligned for `T`
/// 7. `A` is guaranteed to live for at least `'a`
/// 8. `T: 'a`
...but not aliasing. However, this type only appears as an argument to is_bit_valid
(and is documented as such), and is_bit_valid
, as a pre-condition, guarantees the appropriate aliasing requirements. The name MaybeValid
, then, does convey the salient quality of the argument.
c1141bf
to
e7534bc
Compare
Ptr
s point to validly-aligned dataPtr
and make is_bit_valid safe
b3a0929
to
200757e
Compare
200757e
to
a02f5ce
Compare
src/pointer.rs
Outdated
|
||
/// A shorthand for a maybe-valid, maybe-aligned reference. Used as the argument | ||
/// to [`TryFromBytes::is_bit_valid`][crate::TryFromBytes::is_bit_valid]. | ||
pub type MaybeValid<'a, T> = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we call this MaybeAlignedValid
or something? I was confused by the naming when reading lib.rs
until I came and looked at this doc comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about Maybe<T>
? Its reading is its meaning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to Maybe<T>
. >:]
I'm signing off for the night, but feel free to bikeshed the name without me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a separate #[doc(hidden)]
attribute here (so that it remains even if we remove the outer one on the module) and leave a TODO comment to bikeshed this name if we're planning on making this public?
// SAFETY: We uphold the safety contract of `is_bit_valid(elem)`, by | ||
// precondition on the surrounding call to `is_bit_valid`. The | ||
// memory referenced by `elem` is contained entirely within `c`, and | ||
// satisfies the preconditions satisfied by `c`. By axiom, we assume | ||
// that `Iterator:all` does not invalidate these preconditions | ||
// (e.g., by writing to `elem`.) Since `elem` is derived from `c`, | ||
// it is only possible for uninitialized bytes to occur in `elem` at | ||
// the same bytes they occur within `c`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<3
a02f5ce
to
a11d01c
Compare
src/pointer/ptr.rs
Outdated
/// Note that this method does not consume `self`. Callers should be | ||
/// aware that safe code may assume that the aliasing invariants of | ||
/// `self` are not violated, and that the `NonNull` may be used in ways | ||
/// that violate those assumptions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha. I usually expect to see "assume" used in the context of unsafe
code because it's the behavior of the code that matters (by contrast, with safe code, even things unrelated to the code's behavior matter because the compiler itself can make assumptions that could be broken).
I also think that the "Non null
may be used" phrasing is confusing - I read it as "is allowed to be used", but I think you meant it to be read "could be used".
Maybe just something like:
Note that this method does not consume
self
. The caller should watch out forunsafe
code which uses the returnedNonNull
in a way that violates the safety invariants ofself
.
(I originally said "it is the caller's responsibility...", but that feels weird given that this method is safe, and safe methods are unsound if their callers are required to do anything in order to uphold safety.)
src/pointer/ptr.rs
Outdated
mod sealed { | ||
pub trait Sealed {} | ||
|
||
/// No aliasing invariant. | ||
pub const AnyAliasing: Aliasing = 1 << 0; | ||
impl<$($set,)*> Sealed for ($($set,)*) | ||
where | ||
$($set: super::$set,)* | ||
{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also just put a single Sealed
trait in util::sealed
or something. We already have a top-level sealed
module which we could move to util::sealed
.
4268cf9
to
c2f0b92
Compare
Ptr
and make is_bit_valid safe
Ptr
and make is_bit_valid
safe
2439de6
to
f8fb38a
Compare
src/util.rs
Outdated
let raw = self.ptr.ptr.as_ptr(); | ||
// SAFETY: By invariant on `MaybeAligned`, `raw` contains | ||
// validly-initialized data for `T`. The value is safe to read and | ||
// return, because `T` is copy. | ||
unsafe { core::ptr::read_unaligned(raw) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you leave a TODO comment on read_unaligned
to consider loosening the requirements at some point in the future? In particular, I could see us introducing a trait which represents "at least shared" aliasing, implementing it for both invariant::Shared
and invariant::Exclusive
, and using that trait (rather than a particular concrete type) as the aliasing bound.
src/pointer.rs
Outdated
#[inline] | ||
pub fn read_unaligned(self) -> T | ||
where | ||
T: Copy, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe let's open an issue to track having Ptr
store the "original" type of its data? Probably not worth holding up this PR for that, and I suspect there are a lot of subtleties.
f8fb38a
to
925ec0a
Compare
Closes #715.