Skip to content

Commit 388bff8

Browse files
authored
Rollup merge of rust-lang#95556 - declanvk:nonnull-provenance, r=dtolnay
Implement provenance preserving methods on NonNull ### Description Add the `addr`, `with_addr`, `map_addr` methods to the `NonNull` type, and map the address type to `NonZeroUsize`. ### Motivation The `NonNull` type is useful for implementing pointer types which have the 0-niche. It is currently possible to implement these provenance preserving functions by calling `NonNull::as_ptr` and `new_unchecked`. The adding these methods makes it more ergonomic. ### Testing Added a unit test of a non-null tagged pointer type. This is based on some real code I have elsewhere, that currently routes the pointer through a `NonZeroUsize` and back out to produce a usable pointer. I wanted to produce an ideal version of the same tagged pointer struct that preserved pointer provenance. ### Related Extension of APIs proposed in rust-lang#95228 . I can also split this out into a separate tracking issue if that is better (though I may need some pointers on how to do that).
2 parents 116f8bd + 2a82763 commit 388bff8

File tree

3 files changed

+127
-0
lines changed

3 files changed

+127
-0
lines changed

library/core/src/ptr/non_null.rs

+48
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::fmt;
44
use crate::hash;
55
use crate::marker::Unsize;
66
use crate::mem::{self, MaybeUninit};
7+
use crate::num::NonZeroUsize;
78
use crate::ops::{CoerceUnsized, DispatchFromDyn};
89
use crate::ptr::Unique;
910
use crate::slice::{self, SliceIndex};
@@ -253,6 +254,53 @@ impl<T: ?Sized> NonNull<T> {
253254
(self.cast(), super::metadata(self.as_ptr()))
254255
}
255256

257+
/// Gets the "address" portion of the pointer.
258+
///
259+
/// This API and its claimed semantics are part of the Strict Provenance experiment,
260+
/// see the [module documentation][crate::ptr] for details.
261+
#[must_use]
262+
#[inline]
263+
#[unstable(feature = "strict_provenance", issue = "95228")]
264+
pub fn addr(self) -> NonZeroUsize
265+
where
266+
T: Sized,
267+
{
268+
// SAFETY: The pointer is guaranteed by the type to be non-null,
269+
// meaning that the address will be non-zero.
270+
unsafe { NonZeroUsize::new_unchecked(self.pointer.addr()) }
271+
}
272+
273+
/// Creates a new pointer with the given address.
274+
///
275+
/// This API and its claimed semantics are part of the Strict Provenance experiment,
276+
/// see the [module documentation][crate::ptr] for details.
277+
#[must_use]
278+
#[inline]
279+
#[unstable(feature = "strict_provenance", issue = "95228")]
280+
pub fn with_addr(self, addr: NonZeroUsize) -> Self
281+
where
282+
T: Sized,
283+
{
284+
// SAFETY: The result of `ptr::from::with_addr` is non-null because `addr` is guaranteed to be non-zero.
285+
unsafe { NonNull::new_unchecked(self.pointer.with_addr(addr.get()) as *mut _) }
286+
}
287+
288+
/// Creates a new pointer by mapping `self`'s address to a new one.
289+
///
290+
/// This is a convenience for [`with_addr`][Self::with_addr], see that method for details.
291+
///
292+
/// This API and its claimed semantics are part of the Strict Provenance experiment,
293+
/// see the [module documentation][crate::ptr] for details.
294+
#[must_use]
295+
#[inline]
296+
#[unstable(feature = "strict_provenance", issue = "95228")]
297+
pub fn map_addr(self, f: impl FnOnce(NonZeroUsize) -> NonZeroUsize) -> Self
298+
where
299+
T: Sized,
300+
{
301+
self.with_addr(f(self.addr()))
302+
}
303+
256304
/// Acquires the underlying `*mut` pointer.
257305
///
258306
/// # Examples

library/core/tests/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
#![feature(int_roundings)]
8787
#![feature(slice_group_by)]
8888
#![feature(split_array)]
89+
#![feature(strict_provenance)]
8990
#![feature(trusted_random_access)]
9091
#![feature(unsize)]
9192
#![feature(unzip_option)]

library/core/tests/ptr.rs

+78
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use core::cell::RefCell;
2+
use core::num::NonZeroUsize;
23
use core::ptr;
34
use core::ptr::*;
45
use std::fmt::{Debug, Display};
@@ -691,3 +692,80 @@ fn thin_box() {
691692
}
692693
}
693694
}
695+
696+
#[test]
697+
fn nonnull_tagged_pointer_with_provenance() {
698+
let raw_pointer = Box::into_raw(Box::new(10));
699+
700+
let mut p = TaggedPointer::new(raw_pointer).unwrap();
701+
assert_eq!(p.tag(), 0);
702+
703+
p.set_tag(1);
704+
assert_eq!(p.tag(), 1);
705+
assert_eq!(unsafe { *p.pointer().as_ptr() }, 10);
706+
707+
p.set_tag(3);
708+
assert_eq!(p.tag(), 3);
709+
assert_eq!(unsafe { *p.pointer().as_ptr() }, 10);
710+
711+
unsafe { Box::from_raw(p.pointer().as_ptr()) };
712+
713+
/// A non-null pointer type which carries several bits of metadata and maintains provenance.
714+
#[repr(transparent)]
715+
pub struct TaggedPointer<T>(NonNull<T>);
716+
717+
impl<T> Clone for TaggedPointer<T> {
718+
fn clone(&self) -> Self {
719+
Self(self.0)
720+
}
721+
}
722+
723+
impl<T> Copy for TaggedPointer<T> {}
724+
725+
impl<T> TaggedPointer<T> {
726+
/// The ABI-required minimum alignment of the `P` type.
727+
pub const ALIGNMENT: usize = core::mem::align_of::<T>();
728+
/// A mask for data-carrying bits of the address.
729+
pub const DATA_MASK: usize = !Self::ADDRESS_MASK;
730+
/// Number of available bits of storage in the address.
731+
pub const NUM_BITS: u32 = Self::ALIGNMENT.trailing_zeros();
732+
/// A mask for the non-data-carrying bits of the address.
733+
pub const ADDRESS_MASK: usize = usize::MAX << Self::NUM_BITS;
734+
735+
/// Create a new tagged pointer from a possibly null pointer.
736+
pub fn new(pointer: *mut T) -> Option<TaggedPointer<T>> {
737+
Some(TaggedPointer(NonNull::new(pointer)?))
738+
}
739+
740+
/// Consume this tagged pointer and produce a raw mutable pointer to the
741+
/// memory location.
742+
pub fn pointer(self) -> NonNull<T> {
743+
// SAFETY: The `addr` guaranteed to have bits set in the Self::ADDRESS_MASK, so the result will be non-null.
744+
self.0.map_addr(|addr| unsafe {
745+
NonZeroUsize::new_unchecked(addr.get() & Self::ADDRESS_MASK)
746+
})
747+
}
748+
749+
/// Consume this tagged pointer and produce the data it carries.
750+
pub fn tag(&self) -> usize {
751+
self.0.addr().get() & Self::DATA_MASK
752+
}
753+
754+
/// Update the data this tagged pointer carries to a new value.
755+
pub fn set_tag(&mut self, data: usize) {
756+
assert_eq!(
757+
data & Self::ADDRESS_MASK,
758+
0,
759+
"cannot set more data beyond the lowest NUM_BITS"
760+
);
761+
let data = data & Self::DATA_MASK;
762+
763+
// SAFETY: This value will always be non-zero because the upper bits (from
764+
// ADDRESS_MASK) will always be non-zero. This a property of the type and its
765+
// construction.
766+
self.0 = self.0.map_addr(|addr| unsafe {
767+
NonZeroUsize::new_unchecked((addr.get() & Self::ADDRESS_MASK) | data)
768+
})
769+
}
770+
}
771+
}

0 commit comments

Comments
 (0)