hopper_native/pod.rs
1//! Substrate-level `Pod` marker.
2//!
3//! The Hopper Safety Audit asked for every zero-copy access path -
4//! all the way down to the native substrate, to require a real Pod
5//! bound rather than the loose `T: Copy`. This module is that marker.
6//!
7//! ## Bytemuck-backed safety (default)
8//!
9//! With the `bytemuck` feature enabled (default), `Pod` is declared
10//! as a **sub-trait** of `bytemuck::Pod + bytemuck::Zeroable`. That
11//! raises the bar exactly the way the audit recommends: every
12//! `unsafe impl Pod for T {}` must be accompanied by a
13//! `#[derive(bytemuck::Pod, bytemuck::Zeroable)]` (or hand-written
14//! impls that satisfy bytemuck's machine-checked obligations).
15//! Bytemuck's derive emits a compile-time proof that **every field**
16//! of `T` is itself `Pod`, which mechanically rejects:
17//!
18//! - `bool`, `char`, references, not all bit patterns valid
19//! - padded `#[repr(C)]` structs, padding bytes aren't accounted for
20//! - non-alignment-1 primitives when alignment-1 was claimed
21//! - enums with niches and non-zero variants
22//!
23//! This is the **Must-Fix #5** the audit flagged: "enforce field-level
24//! Pod proof at macro expansion time". Hopper's `#[hopper::pod]` derive
25//! and `#[hopper::state]` macro emit the machine-checked proof so users
26//! never need to name bytemuck in their own sources. State layouts keep
27//! their `#[derive(Clone, Copy)]` visible, and the macro verifies it.
28//!
29//! ## Disable-able for zero-dep builds
30//!
31//! Programs that want to avoid any external dependency can turn off
32//! the `bytemuck` feature. In that mode `Pod` is a standalone marker
33//! with the documented four-point contract; the compile-time
34//! obligation falls entirely on the `unsafe impl`. Existing primitive
35//! impls continue to work either way.
36//!
37//! See [`hopper_runtime::pod::Pod`] (downstream re-export) for the
38//! runtime-side view.
39
40/// Marker for types that can be safely overlaid on raw account bytes.
41///
42/// # Safety
43///
44/// Implementing `Pod` for a type `T` asserts all of:
45///
46/// 1. Every `[u8; size_of::<T>()]` bit pattern decodes to a valid `T`.
47/// 2. `align_of::<T>() == 1`.
48/// 3. `T` contains no padding.
49/// 4. `T` contains no internal pointers or references.
50///
51/// With `feature = "bytemuck"` on (default), the trait is sealed so
52/// callers must **also** prove `T: bytemuck::Pod + bytemuck::Zeroable`,
53/// which gets them obligations 1, 3, and 4 mechanically via bytemuck's
54/// derive. Obligation 2 (alignment) is still a Hopper-specific
55/// constraint enforced by the `#[hopper::pod]` / `#[hopper::state]`
56/// compile-time asserts.
57#[cfg(feature = "bytemuck")]
58pub unsafe trait Pod: Copy + Sized + bytemuck::Pod + bytemuck::Zeroable {}
59
60/// Marker for types that can be safely overlaid on raw account bytes.
61///
62/// `bytemuck` feature disabled: the four-point contract must be
63/// satisfied by the `unsafe impl` alone.
64#[cfg(not(feature = "bytemuck"))]
65pub unsafe trait Pod: Copy + Sized {}
66
67// ── Primitive implementations ───────────────────────────────────────
68//
69// Both feature configurations get the same set of blanket impls.
70// With `bytemuck` on these compile because bytemuck also has blanket
71// impls for the same primitive types.
72
73unsafe impl Pod for u8 {}
74unsafe impl Pod for u16 {}
75unsafe impl Pod for u32 {}
76unsafe impl Pod for u64 {}
77unsafe impl Pod for u128 {}
78unsafe impl Pod for i8 {}
79unsafe impl Pod for i16 {}
80unsafe impl Pod for i32 {}
81unsafe impl Pod for i64 {}
82unsafe impl Pod for i128 {}
83unsafe impl<const N: usize> Pod for [u8; N] {}
84unsafe impl Pod for () {}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 fn require<T: Pod>() {}
91
92 #[test]
93 fn primitives_are_pod() {
94 require::<u8>();
95 require::<u64>();
96 require::<i128>();
97 require::<[u8; 32]>();
98 }
99
100 /// Demonstrates that `bool`, `Copy + Sized` but not all bit
101 /// patterns valid, is **not** `Pod` under Hopper's contract.
102 /// With the `bytemuck` feature on this is enforced mechanically
103 /// because `bool` isn't `bytemuck::Pod`. Without the feature the
104 /// trait is a plain marker and the rejection relies on the user
105 /// not writing `unsafe impl Pod for bool`.
106 #[test]
107 fn bool_is_not_pod() {
108 trait NotPod {}
109 impl<T> NotPod for T {}
110 // Compiles, bool has `NotPod` blanket impl.
111 fn _f<T: NotPod>() {}
112 _f::<bool>();
113 }
114}