1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
//! In some parts of lockfile serialization, Bun will use the equivalent of `std.mem.sliceAsBytes` to convert a
//! struct into raw bytes to write. This makes lockfile serialization/deserialization much simpler/faster, at the
//! cost of not having any pointers within these structs.
//!
//! One major caveat of this is that if any of these structs have uninitialized memory, then that can leak
//! garbage memory into the lockfile. See https://github.com/oven-sh/bun/issues/4319
//!
//! The obvious way to introduce undefined memory into a struct is via `.field = MaybeUninit::uninit()`, but a
//! much more subtle way is to have implicit padding in a `#[repr(C)]` struct. For example:
//! ```ignore
//! #[repr(C)]
//! struct Demo {
//! a: u8, // size_of == 1, offset_of == 0
//! b: u64, // size_of == 8, offset_of == 8
//! }
//! ```
//!
//! `a` is only one byte long, but due to the alignment of `b`, there is 7 bytes of padding between `a` and `b`,
//! which is considered *undefined memory*.
//!
//! The solution is to have it explicitly initialized to zero bytes, like:
//! ```ignore
//! #[repr(C)]
//! struct Demo {
//! a: u8,
//! _padding: [u8; 7], // = [0; 7] in Default
//! b: u64, // same offset as before
//! }
//! ```
//!
//! There is one other way to introduce undefined memory into a struct, which this does not check for, and that is
//! a union with unequal size fields.
// TODO(port): The Zig implementation is pure `comptime` reflection over `@typeInfo(T)` —
// it walks struct/union/array/optional/pointer field trees, recurses into children, and
// `@compileError`s on any gap between `@offsetOf(T, field) + @sizeOf(field)` and the next
// field's offset (and between the last field's end and `@sizeOf(T)`).
//
// Rust has no `@typeInfo` equivalent. TODO(port): provide this as a proc-macro derive
// (`#[derive(AssertNoUninitializedPadding)]`) that emits the `const _: () = assert!(...)`
// checks below per-field, plus a marker trait so `assert_no_uninitialized_padding::<T>()`
// is bounded on it. The free function here is kept as the call-site-compatible entry point.
/// Marker trait asserting that `Self` is `#[repr(C)]` (or `#[repr(transparent)]`/packed),
/// contains no pointer fields, and has no implicit padding bytes anywhere in its layout
/// (recursively). Implemented by `#[derive(AssertNoUninitializedPadding)]`.
///
/// # Safety
/// Implementing this by hand asserts the layout invariants above without the derive's
/// compile-time checks. Only do so for primitives and manually-audited `#[repr(C)]` types.
pub unsafe
/// Assertion that `T` has no uninitialized padding. See module docs.
///
/// In Zig this walked `@typeInfo(T)` at comptime and emitted `@compileError` on gaps,
/// with an `else => return` arm that silently accepted any non-aggregate type and a
/// `.pointer => |ptr| assertNoUninitializedPadding(ptr.child)` arm so callers could
/// pass `@TypeOf(slice)` directly.
///
/// In Rust the actual layout checking lives in `#[derive(AssertNoUninitializedPadding)]`
/// on each serialized struct; this function is a zero-cost call-site marker that
/// documents intent. It takes a type-witness value so call sites can mirror the Zig
/// `assertNoUninitializedPadding(@TypeOf(value))` pattern (pass any value of `T` —
/// or name `T` explicitly via turbofish and reference the fn item without calling).
///
/// The trait bound is intentionally *not* applied here: Zig's `else => return` accepts
/// all leaf types, and bounding the generic would force every `write_array<T>` caller
/// to propagate `T: AssertNoUninitializedPadding` before the derive exists.
// TODO(port): proc-macro — the derive should expand roughly to the following per type
// (shown as a declarative helper for reference; not invoked anywhere yet):
//
// For each adjacent field pair (prev, field) in declaration order:
// const _: () = assert!(
// core::mem::offset_of!(T, field)
// == core::mem::offset_of!(T, prev) + core::mem::size_of::<PrevTy>(),
// concat!(
// "Expected no possibly uninitialized bytes of memory in '", stringify!(T),
// "', but found a byte gap between fields '", stringify!(prev), "' and '",
// stringify!(field), "'. This can be fixed by adding a padding field to the ",
// "struct like `_padding: [u8; N] = [0; N],` between these fields. For more ",
// "information, look at `padding_checker.rs`",
// ),
// );
//
// And for the trailing gap:
// const _: () = assert!(
// core::mem::offset_of!(T, last) + core::mem::size_of::<LastTy>()
// == core::mem::size_of::<T>(),
// concat!(
// "Expected no possibly uninitialized bytes of memory in '", stringify!(T),
// "', but found a byte gap at the end of the struct. This can be fixed by ",
// "adding a padding field to the struct like `_padding: [u8; N] = [0; N],` ",
// "at the end. For more information, look at `padding_checker.rs`",
// ),
// );
//
// Recursion rules (mirroring the Zig `switch (@typeInfo(...))`):
// - struct / union field → require `FieldTy: AssertNoUninitializedPadding`
// - [T; N] field → require `T: AssertNoUninitializedPadding`
// - Option<T> field → require `T: AssertNoUninitializedPadding`
// - pointer field → compile_error!("Expected no pointer types in ...")
// - anything else → ok
//
// Unions: recurse into field types but skip the offset-gap scan (matches Zig's
// `if (info_ == .@"union") return;` before the offset loop).
// Blanket impls for leaf types the Zig version's `else => return` arm accepted.
// SAFETY: u8 is a single value byte; no padding by definition.
unsafe
// SAFETY: u16 is a fixed-width integer; all 2 bytes are value bytes, no padding.
unsafe
// SAFETY: u32 is a fixed-width integer; all 4 bytes are value bytes, no padding.
unsafe
// SAFETY: u64 is a fixed-width integer; all 8 bytes are value bytes, no padding.
unsafe
// SAFETY: usize is a fixed-width integer; all bytes are value bytes, no padding.
unsafe
// SAFETY: i8 is a single value byte; no padding by definition.
unsafe
// SAFETY: i16 is a fixed-width integer; all 2 bytes are value bytes, no padding.
unsafe
// SAFETY: i32 is a fixed-width integer; all 4 bytes are value bytes, no padding.
unsafe
// SAFETY: i64 is a fixed-width integer; all 8 bytes are value bytes, no padding.
unsafe
// SAFETY: isize is a fixed-width integer; all bytes are value bytes, no padding.
unsafe
// SAFETY: bool occupies exactly one byte (value 0 or 1); no padding.
unsafe
// Arrays: Zig's `.array => |a| assertNoUninitializedPadding(a.child)`.
// SAFETY: `[T; N]` has no inter-element padding when `T` itself has none
// (array stride == size_of::<T>() always; any tail padding would be inside T and
// already rejected by T's own impl).
unsafe
// ──────────────────────────────────────────────────────────────────────────
// Cross-runtime layout pins
//
// Every type below is `std.mem.sliceAsBytes`-serialised into either `bun.lockb`
// (the binary lockfile) or the `.npm` manifest cache. Their sizes/alignments
// are therefore an ABI contract with Zig-built Bun: a Zig-written lockfile
// must round-trip through this build and vice versa. The expected values are
// computed by hand from the `extern struct` declarations in the corresponding
// `.zig` files (no `@typeInfo` available in Rust). If any assert fires the
// on-disk format has drifted — either fix the Rust `#[repr(C)]` layout or
// bump the relevant format version (`bun.lockb` `format_version` /
// `PackageManifest::Serializer::VERSION`).
//
// The asserts are gated to 64-bit little-endian targets because that is the
// only ABI the binary formats are defined for (Zig hard-codes `.little` and
// `@alignOf([*]u8) == 8` in the lockfile header).
// ──────────────────────────────────────────────────────────────────────────
// ported from: src/install/padding_checker.zig