Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 34 additions & 34 deletions src/bun_core/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ impl<T: Copy> Unaligned<T> {
if slice.is_empty() {
return &mut [];
}
debug_assert!(
assert!(
(slice.as_ptr() as usize).is_multiple_of(core::mem::align_of::<T>()),
"Unaligned::slice_align_cast_mut: pointer is not {}-byte aligned",
core::mem::align_of::<T>(),
);
// SAFETY: `#[repr(C, packed)]` around a single `T` gives identical size
// and field offset 0; the debug-asserted alignment upgrades it to `T`'s
// and field offset 0; the alignment asserted above upgrades it to `T`'s
// layout. `&mut` exclusivity is preserved.
unsafe { core::slice::from_raw_parts_mut(slice.as_mut_ptr().cast::<T>(), slice.len()) }
}
Expand Down Expand Up @@ -106,51 +106,53 @@ impl ZStr {
}
/// Wrap a `&'static [u8]` literal that already includes the trailing
/// `\0` (e.g. `b".\0"`). The returned `&ZStr` excludes the NUL from
/// `len()` per the type invariant. Panics in debug if no trailing NUL.
/// `len()` per the type invariant. Panics if there is no trailing NUL.
#[inline]
pub const fn from_static(s: &'static [u8]) -> &'static ZStr {
debug_assert!(!s.is_empty() && s[s.len() - 1] == 0);
// SAFETY: caller-supplied literal ends in NUL; lifetime is 'static.
assert!(
!s.is_empty() && s[s.len() - 1] == 0,
"ZStr::from_static: missing trailing NUL"
);
// SAFETY: `s` ends in NUL (asserted above); lifetime is 'static.
unsafe { Self::from_raw(s.as_ptr(), s.len() - 1) }
}
/// Borrow `buf[..len]` as a `&ZStr`, where `buf[len] == 0`. This is the
/// safe-surface form of [`from_raw`] for the dominant call shape in the
/// install pipeline: a stack `PathBuffer` filled to `len` with a NUL
/// written at `buf[len]`. The slice bound proves `buf[..=len]` is in the
/// same allocation; the NUL is debug-asserted.
/// same allocation; the NUL is asserted.
#[inline]
pub fn from_buf(buf: &[u8], len: usize) -> &ZStr {
debug_assert!(len < buf.len(), "ZStr::from_buf: NUL must lie within buf");
debug_assert_eq!(buf[len], 0, "ZStr::from_buf: missing NUL at buf[len]");
// SAFETY: `buf[..=len]` is in-bounds (debug-asserted above; release
// relies on caller upholding the documented `buf[len] == 0`
// precondition).
assert!(len < buf.len(), "ZStr::from_buf: NUL must lie within buf");
assert!(buf[len] == 0, "ZStr::from_buf: missing NUL at buf[len]");
// SAFETY: `buf[..=len]` is in-bounds and `buf[len] == 0`, both
// asserted above.
unsafe { Self::from_raw(buf.as_ptr(), len) }
}
/// Borrow `buf[..buf.len()-1]` as a `&ZStr`, where the last byte of `buf`
/// is the NUL terminator. This is [`from_buf`] specialized for the second
/// most common call shape: a slice that already includes its trailing NUL
/// (e.g. a `Vec<u8>` with `0` pushed, or `CStr::to_bytes_with_nul`).
/// Debug-asserts the trailing NUL; release relies on the documented
/// precondition.
#[inline]
pub fn from_slice_with_nul(buf: &[u8]) -> &ZStr {
debug_assert!(!buf.is_empty(), "ZStr::from_slice_with_nul: empty slice");
debug_assert_eq!(
buf[buf.len() - 1],
0,
assert!(!buf.is_empty(), "ZStr::from_slice_with_nul: empty slice");
assert!(
buf[buf.len() - 1] == 0,
"ZStr::from_slice_with_nul: missing trailing NUL"
);
// SAFETY: `buf[buf.len()-1] == 0` (debug-asserted; caller contract in
// release) and `buf[..buf.len()-1]` is in-bounds by slice invariant.
// SAFETY: `buf[buf.len()-1] == 0` (asserted above) and
// `buf[..buf.len()-1]` is in-bounds by slice invariant.
unsafe { Self::from_raw(buf.as_ptr(), buf.len() - 1) }
}
/// Mutable variant of [`from_buf`].
#[inline]
pub fn from_buf_mut(buf: &mut [u8], len: usize) -> &mut ZStr {
debug_assert!(len < buf.len());
debug_assert_eq!(buf[len], 0);
// SAFETY: see `from_buf`.
assert!(
len < buf.len(),
"ZStr::from_buf_mut: NUL must lie within buf"
);
assert!(buf[len] == 0, "ZStr::from_buf_mut: missing NUL at buf[len]");
// SAFETY: see `from_buf`; `&mut buf` makes `buf[..=len]` writable.
unsafe { Self::from_raw_mut(buf.as_mut_ptr(), len) }
}
#[inline]
Expand Down Expand Up @@ -445,29 +447,27 @@ impl WStr {
/// Borrow `buf[..len]` as a `&WStr`, where `buf[len] == 0`. Safe-surface
/// form of [`from_raw`] for the dominant call shape: a stack `WPathBuffer`
/// filled to `len` with a NUL written at `buf[len]`. The slice bound proves
/// `buf[..=len]` lies in one allocation; the NUL is debug-asserted (release
/// relies on the documented `buf[len] == 0` precondition). Mirrors
/// `buf[..=len]` lies in one allocation; the NUL is asserted. Mirrors
/// [`ZStr::from_buf`].
#[inline]
pub fn from_buf(buf: &[u16], len: usize) -> &WStr {
debug_assert!(len < buf.len(), "WStr::from_buf: NUL must lie within buf");
debug_assert_eq!(buf[len], 0, "WStr::from_buf: missing NUL at buf[len]");
// SAFETY: `buf[..=len]` is in-bounds (debug-asserted above; caller
// contract in release).
assert!(len < buf.len(), "WStr::from_buf: NUL must lie within buf");
assert!(buf[len] == 0, "WStr::from_buf: missing NUL at buf[len]");
// SAFETY: `buf[..=len]` is in-bounds and `buf[len] == 0`, both
// asserted above.
unsafe { Self::from_raw(buf.as_ptr(), len) }
}
/// Borrow `buf[..buf.len()-1]` as a `&WStr`, where the last unit of `buf`
/// is the NUL terminator. Mirrors [`ZStr::from_slice_with_nul`].
#[inline]
pub fn from_slice_with_nul(buf: &[u16]) -> &WStr {
debug_assert!(!buf.is_empty(), "WStr::from_slice_with_nul: empty slice");
debug_assert_eq!(
buf[buf.len() - 1],
0,
assert!(!buf.is_empty(), "WStr::from_slice_with_nul: empty slice");
assert!(
buf[buf.len() - 1] == 0,
"WStr::from_slice_with_nul: missing trailing NUL"
);
// SAFETY: `buf[buf.len()-1] == 0` (debug-asserted; caller contract in
// release) and `buf[..buf.len()-1]` is in-bounds by slice invariant.
// SAFETY: `buf[buf.len()-1] == 0` (asserted above) and
// `buf[..buf.len()-1]` is in-bounds by slice invariant.
unsafe { Self::from_raw(buf.as_ptr(), buf.len() - 1) }
}
/// Borrow a NUL-terminated FFI wide string as `&WStr`, or [`EMPTY`] if
Expand Down
11 changes: 10 additions & 1 deletion src/errno/darwin_errno.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@ pub use crate::posix::mode_t as Mode;

#[repr(u16)]
#[derive(
Copy, Clone, Eq, PartialEq, Hash, Debug, strum::IntoStaticStr, strum::EnumString, enum_map::Enum,
Copy,
Clone,
Eq,
PartialEq,
Hash,
Debug,
strum::IntoStaticStr,
strum::EnumString,
strum::FromRepr,
enum_map::Enum,
)]
pub enum SystemErrno {
SUCCESS = 0,
Expand Down
11 changes: 10 additions & 1 deletion src/errno/freebsd_errno.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@ pub use crate::posix::mode_t as Mode;

#[repr(u16)]
#[derive(
Copy, Clone, Eq, PartialEq, Hash, Debug, strum::IntoStaticStr, strum::EnumString, enum_map::Enum,
Copy,
Clone,
Eq,
PartialEq,
Hash,
Debug,
strum::IntoStaticStr,
strum::EnumString,
strum::FromRepr,
enum_map::Enum,
)]
pub enum SystemErrno {
SUCCESS = 0,
Expand Down
110 changes: 98 additions & 12 deletions src/errno/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ macro_rules! impl_get_errno_libc {
// against the type's own all-ones value instead (== -1 for
// signed, == MAX for unsigned — both are libc's failure rc).
if self == !(0 as $t) {
$crate::E::from_raw($crate::posix::errno() as u16)
// An errno the enum does not declare collapses to EIO.
$crate::from_errno($crate::posix::errno())
} else {
$crate::E::SUCCESS
}
Expand Down Expand Up @@ -276,22 +277,18 @@ pub fn e_from_negated(errno: core::ffi::c_int) -> E {
}

impl SystemErrno {
/// Unchecked discriminant cast.
/// Variant for a discriminant known to be declared; panics on any other.
///
/// On POSIX the enum is dense `0..MAX`, so we debug-assert `n < MAX`.
/// OS-reported codes go through [`from_errno`] / [`SystemErrno::init`] instead.
/// On Windows the enum is **sparse** (dense `0..=137` plus isolated `UV_E*`
/// discriminants in the ~3000-4095 range — see windows_errno.rs), so the
/// `< MAX` bound does not hold for valid tags and the assert is skipped.
/// check is `from_repr`'s match over the declared variants, not `n < MAX`.
#[inline]
pub const fn from_raw(n: u16) -> SystemErrno {
// `as usize` on both sides papers over per-OS `MAX` typing (POSIX `u16`
// vs Windows `usize`) without normalizing the constant itself.
#[cfg(not(windows))]
debug_assert!((n as usize) < (Self::MAX as usize));
// SAFETY: caller guarantees `n` is a declared `#[repr(u16)]` discriminant
// of `SystemErrno`. The enum is NOT
// contiguous on Windows; do not assume `n < MAX` implies validity there.
unsafe { core::mem::transmute::<u16, SystemErrno>(n) }
match Self::from_repr(n) {
Some(e) => e,
None => panic!("SystemErrno::from_raw: not a declared errno discriminant"),
}
}
}

Expand Down Expand Up @@ -482,6 +479,95 @@ mod errno_name_tests {
assert_eq!(system_errno_name(97), Some("EINTEGRITY"));
}

/// On POSIX the declared set being exactly `0..MAX` is what `init`'s range check relies on.
#[test]
fn from_raw_round_trips_every_declared_discriminant() {
use enum_map::Enum;
for i in 0..SystemErrno::LENGTH {
let e = SystemErrno::from_usize(i);
assert_eq!(SystemErrno::from_raw(e as u16), e);
}
#[cfg(not(windows))]
{
assert_eq!(SystemErrno::LENGTH, usize::from(SystemErrno::MAX));
for n in 0..SystemErrno::MAX {
assert!(SystemErrno::from_repr(n).is_some(), "hole at {n}");
}
}
}

/// One past the dense head is undeclared everywhere (the Windows `UV_*` tail starts near 3000).
#[test]
#[should_panic(expected = "not a declared errno discriminant")]
fn from_raw_rejects_one_past_the_dense_head() {
let _ = SystemErrno::from_raw(system_errno_max_dense() as u16);
}

#[test]
#[should_panic(expected = "not a declared errno discriminant")]
fn from_raw_rejects_u16_max() {
let _ = SystemErrno::from_raw(u16::MAX);
}

#[cfg(not(windows))]
#[test]
fn get_errno_collapses_undeclared_libc_errno_to_eio() {
let set_errno = |value: core::ffi::c_int| {
// SAFETY: the calling thread's own errno slot, valid for the life of the thread.
unsafe { *bun_core::ffi::errno_ptr() = value };
};
set_errno(i32::from(SystemErrno::MAX));
assert_eq!(get_errno(-1i32), SystemErrno::EIO);
set_errno(libc::ENOENT);
assert_eq!(get_errno(-1i32), SystemErrno::ENOENT);
// A successful return never consults errno.
set_errno(i32::from(SystemErrno::MAX));
assert_eq!(get_errno(0i32), SystemErrno::SUCCESS);
}

/// The kernel reports `-errno` in the result itself, anywhere in `-4095..=-1`.
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn get_errno_collapses_undeclared_raw_syscall_errno_to_eio() {
let failed_with = |errno: isize| (-errno) as usize;
assert_eq!(
get_errno(failed_with(SystemErrno::MAX as isize)),
SystemErrno::EIO
);
assert_eq!(get_errno(failed_with(4095)), SystemErrno::EIO);
assert_eq!(
get_errno(failed_with(libc::ENOENT as isize)),
SystemErrno::ENOENT
);
// Anything outside that window is a successful result (a length, an address).
assert_eq!(get_errno(0usize), SystemErrno::SUCCESS);
assert_eq!(get_errno(42usize), SystemErrno::SUCCESS);
assert_eq!(get_errno(failed_with(4096)), SystemErrno::SUCCESS);
}

/// `to_e` and `bun_sys::Error::resolve_system_errno` convert between the enums by discriminant.
#[cfg(windows)]
#[test]
fn e_and_system_errno_declare_the_same_discriminants() {
use enum_map::Enum;
assert_eq!(E::LENGTH, SystemErrno::LENGTH);
for i in 0..SystemErrno::LENGTH {
let s = SystemErrno::from_usize(i);
assert_eq!(s.to_e() as u16, s as u16, "{s:?}");
}
for i in 0..E::LENGTH {
let e = E::from_usize(i);
assert_eq!(SystemErrno::from_raw(e as u16) as u16, e as u16, "{e:?}");
}
}

#[cfg(windows)]
#[test]
#[should_panic(expected = "not a declared errno discriminant")]
fn e_from_raw_rejects_the_gap_before_the_uv_tail() {
let _ = E::from_raw(SystemErrno::MAX as u16);
}

/// `win32_errno_name` translation contract: known `GetLastError()` codes
/// map to POSIX names on Windows, unmapped/out-of-range codes are `None`,
/// and the helper is a constant `None` off Windows.
Expand Down
22 changes: 15 additions & 7 deletions src/errno/linux_errno.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@ pub use crate::posix::mode_t as Mode;

#[repr(u16)]
#[derive(
Copy, Clone, Eq, PartialEq, Hash, Debug, strum::IntoStaticStr, strum::EnumString, enum_map::Enum,
Copy,
Clone,
Eq,
PartialEq,
Hash,
Debug,
strum::IntoStaticStr,
strum::EnumString,
strum::FromRepr,
enum_map::Enum,
)]
pub enum SystemErrno {
SUCCESS = 0,
Expand Down Expand Up @@ -183,13 +192,12 @@ impl GetErrno for usize {
fn get_errno(self) -> E {
// `as` between same-width usize/isize is a bit-reinterpretation
let signed = self as isize;
let int = if signed > -4096 && signed < 0 {
-signed
if signed > -4096 && signed < 0 {
// The kernel's range is wider than the enum; undeclared codes collapse to EIO.
crate::from_errno((-signed) as i32)
} else {
0
};
// SAFETY: int is in [0, 4096); E is #[repr] over the kernel errno range
unsafe { core::mem::transmute::<u16, E>(int as u16) }
E::SUCCESS
}
}
}

Expand Down
17 changes: 7 additions & 10 deletions src/errno/windows_errno.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use bun_libuv_sys as uv;
// [UV_X] — no counterpart (EAI_* resolver codes, UNKNOWN, ERRNO_MAX)
//
// ORDER IS LOAD-BEARING: `enum_map::Enum` derives ordinals from declaration
// order, and `SystemErrno::to_e` transmutes by discriminant, so the two enums
// order, and `SystemErrno::to_e` converts by discriminant, so the two enums
// MUST stay in lockstep. Editing this list updates both atomically.
// ──────────────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -242,16 +242,13 @@ pub enum E {
}} // ← UV_* tail appended by `for_each_uv_errno!`

impl E {
/// Variant for a declared discriminant; panics otherwise. Untrusted input: [`try_from_raw`].
#[inline]
pub(crate) const fn from_raw(n: u16) -> Self {
// `E` is sparse (dense 0..=137 plus isolated UV_* tags ~3000–4095), so
// `n < MAX` is NOT a sufficient validity check. `strum::FromRepr`
// generates a `const fn from_repr` matching every declared variant.
debug_assert!(Self::from_repr(n).is_some(), "invalid E discriminant");
// SAFETY: caller guarantees `n` is a declared `#[repr(u16)]` discriminant
// of `E`. Debug-asserted above; for
// untrusted input use `try_from_raw` instead.
unsafe { core::mem::transmute::<u16, E>(n) }
match Self::from_repr(n) {
Some(e) => e,
None => panic!("E::from_raw: not a declared errno discriminant"),
}
}

/// Checked discriminant lookup —
Expand Down Expand Up @@ -657,7 +654,7 @@ impl SystemErrno {
return Self::init_c_int(-code);
}
// code == 0
Some(SystemErrno::from_raw(0))
Some(SystemErrno::SUCCESS)
}

fn init_numeric(code: u16) -> Option<SystemErrno> {
Expand Down
8 changes: 1 addition & 7 deletions src/paths/string_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,7 @@ pub trait Ch: PathChar + Into<u32> + bun_core::NoUninit {}
impl Ch for u8 {}
impl Ch for u16 {}

/// Borrow `wbuf[..len]` as a `&WStr`, where `wbuf[len] == 0`. Safe-surface
/// form of [`WStr::from_raw`] for the dominant call shape in this module: a
/// stack `WPathBuffer` filled to `len` with a NUL written at `wbuf[len]`.
/// The slice borrow proves `wbuf[..=len]` lies in one allocation and ties the
/// returned lifetime to it; the NUL is debug-asserted (release relies on the
/// caller upholding the documented `wbuf[len] == 0` precondition).
/// Mirrors [`ZStr::from_buf`].
/// [`WStr::from_buf`] (which asserts `wbuf[len] == 0`) for this module's `WPathBuffer` call shape.
#[inline(always)]
fn wstr_in_buf(wbuf: &[u16], len: usize) -> &WStr {
WStr::from_buf(wbuf, len)
Expand Down
Loading
Loading