Struct vstd::simple_pptr::PPtr
source · pub struct PPtr<V>(pub usize, pub PhantomData<V>);Expand description
PPtr (which stands for “permissioned pointer”)
is a wrapper around a raw pointer to a heap-allocated V.
This is designed to be simpler to use that Verus’s
more general pointer support,
but also to serve as a better introductory point.
Historically, PPtr was positioned as a “trusted primitive” of Verus,
but now, it is implemented and verified from the more general pointer support,
which operates on similar principles, but which is much precise to Rust’s
pointer semantics.
A PPtr is equvialent to its usize-based address. The type paramter V technically
doesn’t matter, and you can freely convert between PPtr<V> and PPtr<W> by casting
to and from the usize address. What really matters is the type paramter of the
PointsTo<V>.
In order to access (read or write) the value behind the pointer, the user needs
a special ghost permission token, PointsTo<V>. This object is tracked,
which means that it is “only a proof construct” that does not appear in compiled code,
but its uses are checked by the borrow-checker. This ensures memory safety,
data-race-freedom, prohibits use-after-free, etc.
§PointsTo objects.
The PointsTo object represents both the ability to access (read or write)
the data behind the pointer and the ability to free it
(return it to the memory allocator).
The perm: PointsTo<V> object tracks two pieces of data:
perm.pptr()is the pointer that the permission is associated to.perm.mem_contents()is the memory contents, which is one of either:MemContents::Uninitif the memory pointed-to by by the pointer is uninitialized.MemContents::Init(v)if the memory points-to the the valuev.
Your access to the PointsTo object determines what operations you can safely perform
with the pointer:
- You can read from the pointer as long as you have read access to the
PointsToobject, e.g.,&PointsTo<V>. - You can write to the pointer as long as you have mutable access to the
PointsToobject, e.g.,&mut PointsTo<V> - You can call
freeto deallocate the memory as long as you have full onwership of thePointsToobject (i.e., the ability to move it).
For those familiar with separation logic, the PointsTo object plays a role
similar to that of the “points-to” operator, ptr ↦ value.
§Example
fn main() {
unsafe {
// ALLOCATE
// p: PPtr<u64>, points_to: PointsTo<u64>
let (p, Tracked(mut points_to)) = PPtr::<u64>::empty();
assert(points_to.mem_contents() === MemContents::Uninit);
assert(points_to.pptr() == p);
// unsafe { *p = 5; }
p.write(Tracked(&mut points_to), 5);
assert(points_to.mem_contents() === MemContents::Init(5));
assert(points_to.pptr() == p);
// let x = unsafe { *p };
let x = p.read(Tracked(&points_to));
assert(x == 5);
// DEALLOCATE
let y = p.into_inner(Tracked(points_to));
assert(y == 5);
}
}§Examples of incorrect usage
The following code has a use-after-free bug, and it is rejected by Verus because it fails to satisfy Rust’s ownership-checker.
fn main() {
unsafe {
// ALLOCATE
// p: PPtr<u64>, points_to: PointsTo<u64>
let (p, Tracked(mut points_to)) = PPtr::<u64>::empty();
// unsafe { *p = 5; }
p.write(Tracked(&mut points_to), 5);
// let x = unsafe { *p };
let x = p.read(Tracked(&points_to));
// DEALLOCATE
p.free(Tracked(points_to)); // `points_to` is moved here
// READ-AFTER-FREE
let x2 = p.read(Tracked(&mut points_to)); // so it can't be used here
}
}The following doesn’t violate Rust’s ownership-checking, but it “mixes up” the PointsTo
objects, attempting to use the wrong PointsTo for the given pointer.
This violates the precondition on p.read().
fn main() {
unsafe {
// ALLOCATE p
let (p, Tracked(mut perm_p)) = PPtr::<u64>::empty();
// ALLOCATE q
let (q, Tracked(mut perm_q)) = PPtr::<u64>::empty();
// DEALLOCATE p
p.free(Tracked(perm_p));
// READ-AFTER-FREE (read from p, try to use q's permission object)
let x = p.read(Tracked(&mut perm_q));
}
}§Differences from PCell.
PPtr is similar to cell::PCell, but has a few key differences:
- In
PCell<V>, the typeVis placed internally to thePCell, whereas withPPtr, the typeVis placed at some location on the heap. - Since
PPtris just a pointer (represented by an integer), it can beCopy. - The
ptr::PointsTotoken represents not just the permission to read/write the contents, but also to deallocate.
§Simplifications relative to more general pointer API
- Pointers are only represented by addresses and don’t have a general notion of provenance
- Pointers are untyped (only
PointsTois typed). - The
PointsToalso encapsulates the permission to free a pointer. PointsTotokens are non-fungible. They can’t be broken up or made variable-sized.
Tuple Fields§
§0: usize§1: PhantomData<V>Implementations§
source§impl<V> PPtr<V>
impl<V> PPtr<V>
sourcepub exec fn from_addr(u: usize) -> s : Self
pub exec fn from_addr(u: usize) -> s : Self
u == s.addr(),Cast an integer to a pointer.
Note that this does not require or ensure that the pointer is valid.
Of course, if the user creates an invalid pointer, they would still not be able to
create a valid PointsTo token for it, and thus they would never
be able to access the data behind the pointer.
This is analogous to normal Rust, where casting to a pointer is always possible,
but dereferencing a pointer is an unsafe operation.
With PPtr, casting to a pointer is likewise always possible,
while dereferencing it is only allowed when the right preconditions are met.
source§impl<V> PPtr<V>
impl<V> PPtr<V>
sourcepub exec fn empty() -> pt : (PPtr<V>, Tracked<PointsTo<V>>)
pub exec fn empty() -> pt : (PPtr<V>, Tracked<PointsTo<V>>)
pt.1@.pptr() == pt.0,pt.1@.is_uninit(),Allocates heap memory for type V, leaving it uninitialized.
sourcepub exec fn new(v: V) -> pt : (PPtr<V>, Tracked<PointsTo<V>>)
pub exec fn new(v: V) -> pt : (PPtr<V>, Tracked<PointsTo<V>>)
pt.1@.pptr() == pt.0,pt.1@.mem_contents() == MemContents::Init(v),Allocates heap memory for type V, leaving it initialized
with the given value v.
sourcepub exec fn free(self, Tracked(perm): Tracked<PointsTo<V>>)
pub exec fn free(self, Tracked(perm): Tracked<PointsTo<V>>)
perm.pptr() == self,perm.is_uninit(),Free the memory pointed to be perm.
Requires the memory to be uninitialized.
This consumes perm, since it will no longer be safe to access
that memory location.
sourcepub exec fn into_inner(self, Tracked(perm): Tracked<PointsTo<V>>) -> v : V
pub exec fn into_inner(self, Tracked(perm): Tracked<PointsTo<V>>) -> v : V
perm.pptr() == self,perm.is_init(),ensuresv == perm.value(),Free the memory pointed to be perm and return the
value that was previously there.
Requires the memory to be initialized.
This consumes the PointsTo token, since the user is giving up
access to the memory by freeing it.
sourcepub exec fn put(self, Tracked(perm): Tracked<&mut PointsTo<V>>, v: V)
pub exec fn put(self, Tracked(perm): Tracked<&mut PointsTo<V>>, v: V)
old(perm).pptr() == self,old(perm).mem_contents() == MemContents::Uninit::<V>,ensuresperm.pptr() == old(perm).pptr(),perm.mem_contents() == MemContents::Init(v),Moves v into the location pointed to by the pointer self.
Requires the memory to be uninitialized, and leaves it initialized.
In the ghost perspective, this updates perm.mem_contents()
from MemContents::Uninit to MemContents::Init(v).
sourcepub exec fn take(self, Tracked(perm): Tracked<&mut PointsTo<V>>) -> v : V
pub exec fn take(self, Tracked(perm): Tracked<&mut PointsTo<V>>) -> v : V
old(perm).pptr() == self,old(perm).is_init(),ensuresperm.pptr() == old(perm).pptr(),perm.mem_contents() == MemContents::Uninit::<V>,v == old(perm).value(),Moves v out of the location pointed to by the pointer self
and returns it.
Requires the memory to be initialized, and leaves it uninitialized.
In the ghost perspective, this updates perm.value
from Some(v) to None,
while returning the v as an exec value.
sourcepub exec fn replace(self, Tracked(perm): Tracked<&mut PointsTo<V>>, in_v: V) -> out_v : V
pub exec fn replace(self, Tracked(perm): Tracked<&mut PointsTo<V>>, in_v: V) -> out_v : V
old(perm).pptr() == self,old(perm).is_init(),ensuresperm.pptr() == old(perm).pptr(),perm.mem_contents() == MemContents::Init(in_v),out_v == old(perm).value(),Swaps the in_v: V passed in as an argument with the value in memory.
Requires the memory to be initialized, and leaves it initialized with the new value.
sourcepub exec fn borrow<'a>(self, Tracked(perm): Tracked<&'a PointsTo<V>>) -> v : &'a V
pub exec fn borrow<'a>(self, Tracked(perm): Tracked<&'a PointsTo<V>>) -> v : &'a V
perm.pptr() == self,perm.is_init(),ensures*v === perm.value(),Given a shared borrow of the PointsTo<V>, obtain a shared borrow of V.