Skip to main content

fbl/
packed_pointer.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! A provenance-safe packed pointer implementation.
6
7/// A pointer wrapper that allows storing a small amount of data in the alignment bits of the
8/// pointer.
9///
10/// The number of bits available for packing (`DATA_BITS`) must be less than or equal to the number
11/// of trailing zero bits in the alignment of `T`.
12///
13/// `PackedPointer` enforces compile-time validation of the alignment constraints using const
14/// assertions, and runtime debug assertions to verify pointer alignment and data limits.
15///
16/// If `CHECK_ALIGNMENT` is set to `false`, the compile-time alignment check is bypassed. This can
17/// be useful when working with types whose alignment cannot be validated at compile-time.
18///
19/// This implementation preserves pointer provenance by using Rust's strict provenance methods
20/// (`addr`, `with_addr`, `map_addr`).
21#[repr(transparent)]
22pub struct PackedPointer<T, const DATA_BITS: usize, const CHECK_ALIGNMENT: bool = true> {
23    ptr: *mut T,
24}
25
26// Manually implement Clone and Copy since raw pointers are copyable.
27impl<T, const DATA_BITS: usize, const CHECK_ALIGNMENT: bool> Clone
28    for PackedPointer<T, DATA_BITS, CHECK_ALIGNMENT>
29{
30    fn clone(&self) -> Self {
31        *self
32    }
33}
34
35impl<T, const DATA_BITS: usize, const CHECK_ALIGNMENT: bool> Copy
36    for PackedPointer<T, DATA_BITS, CHECK_ALIGNMENT>
37{
38}
39
40// Manually implement PartialEq and Eq to compare the packed values.
41impl<T, const DATA_BITS: usize, const CHECK_ALIGNMENT: bool> PartialEq
42    for PackedPointer<T, DATA_BITS, CHECK_ALIGNMENT>
43{
44    fn eq(&self, other: &Self) -> bool {
45        self.ptr == other.ptr
46    }
47}
48
49impl<T, const DATA_BITS: usize, const CHECK_ALIGNMENT: bool> Eq
50    for PackedPointer<T, DATA_BITS, CHECK_ALIGNMENT>
51{
52}
53
54// Implement comparison with raw pointers.
55impl<T, const DATA_BITS: usize, const CHECK_ALIGNMENT: bool> PartialEq<*mut T>
56    for PackedPointer<T, DATA_BITS, CHECK_ALIGNMENT>
57{
58    fn eq(&self, other: &*mut T) -> bool {
59        self.ptr() == *other
60    }
61}
62
63impl<T, const DATA_BITS: usize, const CHECK_ALIGNMENT: bool> PartialEq<*const T>
64    for PackedPointer<T, DATA_BITS, CHECK_ALIGNMENT>
65{
66    fn eq(&self, other: &*const T) -> bool {
67        self.ptr() as *const T == *other
68    }
69}
70
71impl<T, const DATA_BITS: usize, const CHECK_ALIGNMENT: bool>
72    PackedPointer<T, DATA_BITS, CHECK_ALIGNMENT>
73{
74    const DATA_MASK: usize = (1 << DATA_BITS) - 1;
75    const PTR_MASK: usize = !Self::DATA_MASK;
76
77    const _ASSERT: () = {
78        assert!(DATA_BITS > 0, "PackedPointer requires at least one data bit.");
79        assert!(DATA_BITS < usize::BITS as usize, "Too many data bits requested.");
80        if CHECK_ALIGNMENT {
81            assert!(
82                core::mem::align_of::<T>() >= (1 << DATA_BITS),
83                "T has insufficient alignment for the requested number of data bits."
84            );
85        }
86    };
87
88    /// Creates a new `PackedPointer` from a pointer and data.
89    ///
90    /// # Panics
91    ///
92    /// Panics in debug builds if the pointer is not aligned to the required boundary,
93    /// or if the data exceeds the allowed number of bits.
94    pub fn new(ptr: *mut T, data: usize) -> Self {
95        let _ = Self::_ASSERT;
96        debug_assert!(
97            ptr.addr() & Self::DATA_MASK == 0,
98            "Pointer {:?} is not aligned to at least {} bytes",
99            ptr,
100            1 << DATA_BITS
101        );
102        debug_assert!(data & Self::PTR_MASK == 0, "Data {} exceeds {} bits", data, DATA_BITS);
103
104        let packed_addr = (ptr.addr() & Self::PTR_MASK) | (data & Self::DATA_MASK);
105        Self { ptr: ptr.with_addr(packed_addr) }
106    }
107
108    /// Creates a new, empty packed pointer.
109    pub const fn null() -> Self {
110        let _ = Self::_ASSERT;
111        Self { ptr: core::ptr::null_mut() }
112    }
113
114    /// Creates a `PackedPointer` from a pointer with zeroed data bits.
115    pub fn from_ptr(ptr: *mut T) -> Self {
116        Self::new(ptr, 0)
117    }
118
119    /// Creates a `PackedPointer` with a null pointer and specified data.
120    ///
121    /// # Panics
122    ///
123    /// Panics if the data exceeds the allowed number of bits.
124    pub const fn from_data(data: usize) -> Self {
125        let _ = Self::_ASSERT;
126        assert!(data & Self::PTR_MASK == 0, "Data exceeds allowed bits");
127
128        Self { ptr: (data & Self::DATA_MASK) as *mut T }
129    }
130
131    /// Returns the unpacked pointer, preserving its original provenance.
132    pub fn ptr(&self) -> *mut T {
133        self.ptr.map_addr(|addr| addr & Self::PTR_MASK)
134    }
135
136    /// Returns the unpacked data.
137    pub fn data(&self) -> usize {
138        self.ptr.addr() & Self::DATA_MASK
139    }
140
141    /// Sets the pointer, preserving the currently packed data.
142    pub fn set_ptr(&mut self, ptr: *mut T) {
143        debug_assert!(
144            ptr.addr() & Self::DATA_MASK == 0,
145            "Pointer {:?} is not aligned to at least {} bytes",
146            ptr,
147            1 << DATA_BITS
148        );
149        let data = self.data();
150        let packed_addr = (ptr.addr() & Self::PTR_MASK) | data;
151        self.ptr = ptr.with_addr(packed_addr);
152    }
153
154    /// Sets the data, preserving the currently packed pointer and its provenance.
155    pub fn set_data(&mut self, data: usize) {
156        debug_assert!(data & Self::PTR_MASK == 0, "Data {} exceeds {} bits", data, DATA_BITS);
157        let packed_addr = (self.ptr.addr() & Self::PTR_MASK) | (data & Self::DATA_MASK);
158        self.ptr = self.ptr.with_addr(packed_addr);
159    }
160
161    /// Resets the packed pointer to null with zero data.
162    pub fn reset(&mut self) {
163        self.ptr = core::ptr::null_mut();
164    }
165
166    /// Returns `true` if the unpacked pointer is null.
167    pub fn is_null(&self) -> bool {
168        self.ptr().is_null()
169    }
170}
171
172impl<T, const DATA_BITS: usize, const CHECK_ALIGNMENT: bool> Default
173    for PackedPointer<T, DATA_BITS, CHECK_ALIGNMENT>
174{
175    fn default() -> Self {
176        Self::null()
177    }
178}
179
180impl<T, const DATA_BITS: usize, const CHECK_ALIGNMENT: bool> From<*mut T>
181    for PackedPointer<T, DATA_BITS, CHECK_ALIGNMENT>
182{
183    fn from(ptr: *mut T) -> Self {
184        Self::from_ptr(ptr)
185    }
186}
187
188impl<T, const DATA_BITS: usize, const CHECK_ALIGNMENT: bool> core::fmt::Debug
189    for PackedPointer<T, DATA_BITS, CHECK_ALIGNMENT>
190{
191    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
192        f.debug_struct("PackedPointer")
193            .field("ptr", &self.ptr())
194            .field("data", &self.data())
195            .finish()
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[derive(Debug)]
204    #[repr(align(8))]
205    struct Align8(#[allow(dead_code)] u64);
206
207    #[test]
208    fn test_basic_pack_unpack() {
209        let mut val = Align8(42);
210        let raw_ptr = &mut val as *mut Align8;
211
212        let packed = PackedPointer::<Align8, 3>::new(raw_ptr, 5);
213        assert_eq!(packed.ptr(), raw_ptr);
214        assert_eq!(packed.data(), 5);
215        assert!(!packed.is_null());
216
217        unsafe {
218            assert_eq!((*packed.ptr()).0, 42);
219        }
220    }
221
222    #[test]
223    fn test_setters() {
224        let mut val1 = Align8(10);
225        let mut val2 = Align8(20);
226        let raw_ptr1 = &mut val1 as *mut Align8;
227        let raw_ptr2 = &mut val2 as *mut Align8;
228
229        let mut packed = PackedPointer::<Align8, 3>::from_ptr(raw_ptr1);
230        assert_eq!(packed.ptr(), raw_ptr1);
231        assert_eq!(packed.data(), 0);
232
233        packed.set_data(7);
234        assert_eq!(packed.ptr(), raw_ptr1);
235        assert_eq!(packed.data(), 7);
236
237        packed.set_ptr(raw_ptr2);
238        assert_eq!(packed.ptr(), raw_ptr2);
239        assert_eq!(packed.data(), 7);
240
241        packed.reset();
242        assert!(packed.is_null());
243        assert_eq!(packed.data(), 0);
244    }
245
246    #[test]
247    fn test_default() {
248        let packed = PackedPointer::<Align8, 3>::default();
249        assert!(packed.is_null());
250        assert_eq!(packed.data(), 0);
251    }
252
253    #[test]
254    fn test_const_constructors() {
255        const MY_NULL_PTR: PackedPointer<Align8, 3> = PackedPointer::null();
256        const MY_DATA_PTR: PackedPointer<Align8, 3> = PackedPointer::from_data(5);
257
258        assert!(MY_NULL_PTR.is_null());
259        assert_eq!(MY_NULL_PTR.data(), 0);
260
261        assert!(MY_DATA_PTR.is_null());
262        assert_eq!(MY_DATA_PTR.data(), 5);
263    }
264
265    #[test]
266    fn test_pointer_deref() {
267        let mut val = Align8(42);
268        let packed = PackedPointer::<Align8, 3>::from_ptr(&mut val);
269        unsafe {
270            assert_eq!((*packed.ptr()).0, 42);
271            (*packed.ptr()).0 = 100;
272        }
273        assert_eq!(val.0, 100);
274    }
275
276    #[test]
277    fn test_comparisons() {
278        let mut val1 = Align8(10);
279        let mut val2 = Align8(20);
280        let raw_ptr1 = &mut val1 as *mut Align8;
281        let raw_ptr2 = &mut val2 as *mut Align8;
282
283        let ptr1 = PackedPointer::<Align8, 3>::new(raw_ptr1, 1);
284        let ptr1_again = PackedPointer::<Align8, 3>::new(raw_ptr1, 1);
285        let ptr1_diff_data = PackedPointer::<Align8, 3>::new(raw_ptr1, 2);
286        let ptr2 = PackedPointer::<Align8, 3>::new(raw_ptr2, 1);
287
288        assert_eq!(ptr1, ptr1_again);
289        assert_ne!(ptr1, ptr1_diff_data);
290        assert_ne!(ptr1, ptr2);
291
292        let null_ptr = PackedPointer::<Align8, 3>::default();
293        assert_eq!(null_ptr, core::ptr::null_mut());
294        assert_ne!(ptr1, core::ptr::null_mut());
295    }
296
297    #[test]
298    fn test_disabled_alignment_check() {
299        #[derive(Debug)]
300        #[repr(align(4))]
301        struct Align4(#[allow(dead_code)] u32);
302
303        // Align4 only has 4-byte alignment (2 bits), but we request 3 bits (8-byte alignment
304        // requirement).  This compiles because CHECK_ALIGNMENT is false.
305
306        // We must ensure that the actual pointer used at runtime is 8-byte aligned
307        // if we want to avoid panicking inside debug_assert.
308        // Let's allocate an 8-byte aligned buffer and cast a pointer to it.
309        #[repr(align(8))]
310        struct Align8Buffer(#[allow(dead_code)] [u8; 8]);
311        let mut buffer = Align8Buffer([0; 8]);
312        let raw_ptr = &mut buffer as *mut Align8Buffer as *mut Align4;
313
314        let packed = PackedPointer::<Align4, 3, false>::new(raw_ptr, 5);
315        assert_eq!(packed.ptr(), raw_ptr);
316        assert_eq!(packed.data(), 5);
317    }
318
319    #[test]
320    fn test_packed_pointer_clone() {
321        let mut val = Align8(42);
322        let raw_ptr = &mut val as *mut Align8;
323        let packed = PackedPointer::<Align8, 3>::new(raw_ptr, 5);
324        let cloned = packed.clone();
325        assert_eq!(cloned, packed);
326    }
327
328    #[test]
329    fn test_packed_pointer_partial_eq_const_ptr() {
330        let mut val = Align8(42);
331        let raw_ptr = &mut val as *mut Align8;
332        let packed = PackedPointer::<Align8, 3>::new(raw_ptr, 5);
333        let const_ptr: *const Align8 = raw_ptr as *const Align8;
334        assert!(packed == const_ptr);
335    }
336
337    #[test]
338    fn test_packed_pointer_from_mut_ptr() {
339        let mut val = Align8(42);
340        let raw_ptr = &mut val as *mut Align8;
341        let from_ptr = PackedPointer::<Align8, 3>::from(raw_ptr);
342        assert_eq!(from_ptr.ptr(), raw_ptr);
343        assert_eq!(from_ptr.data(), 0);
344    }
345
346    #[test]
347    fn test_packed_pointer_debug_fmt() {
348        extern crate alloc;
349        let mut val = Align8(42);
350        let raw_ptr = &mut val as *mut Align8;
351        let packed = PackedPointer::<Align8, 3>::new(raw_ptr, 5);
352        let debug_str = alloc::format!("{:?}", packed);
353        assert!(debug_str.contains("PackedPointer"));
354        assert!(debug_str.contains("ptr"));
355        assert!(debug_str.contains("data"));
356    }
357
358    #[test]
359    fn test_packed_pointer_from_data() {
360        let from_d = PackedPointer::<Align8, 3>::from_data(6);
361        assert!(from_d.is_null());
362        assert_eq!(from_d.data(), 6);
363    }
364
365    #[test]
366    #[cfg(debug_assertions)]
367    #[should_panic(expected = "is not aligned")]
368    fn test_unaligned_pointer_panics() {
369        let mut val = Align8(42);
370        let raw_ptr = &mut val as *mut Align8;
371        let unaligned_ptr = raw_ptr.with_addr(raw_ptr.addr() | 1);
372        let _ = PackedPointer::<Align8, 3>::new(unaligned_ptr, 0);
373    }
374
375    #[test]
376    #[cfg(debug_assertions)]
377    #[should_panic(expected = "is not aligned")]
378    fn test_unaligned_set_ptr_panics() {
379        let mut val = Align8(42);
380        let mut packed = PackedPointer::<Align8, 3>::from_ptr(&mut val);
381        let raw_ptr = &mut val as *mut Align8;
382        let unaligned_ptr = raw_ptr.with_addr(raw_ptr.addr() | 1);
383        packed.set_ptr(unaligned_ptr);
384    }
385}