diff --git a/src/bun_core/deprecated.rs b/src/bun_core/deprecated.rs index 19747c15da5..0f0e0ad28ed 100644 --- a/src/bun_core/deprecated.rs +++ b/src/bun_core/deprecated.rs @@ -1,5 +1,3 @@ -use core::ptr; - // ────────────────────────────────────────────────────────────────────────── // BufferedReader // ────────────────────────────────────────────────────────────────────────── @@ -100,235 +98,27 @@ pub fn buffered_reader_size( // ────────────────────────────────────────────────────────────────────────── // DoublyLinkedList // ────────────────────────────────────────────────────────────────────────── - -/// A doubly-linked list has a pair of pointers to both the head and -/// tail of the list. List elements have pointers to both the previous -/// and next elements in the sequence. The list can be traversed both -/// forward and backward. Some operations that take linear O(n) time -/// with a singly-linked list can be done without traversal in constant -/// O(1) time with a doubly-linked list: -/// -/// - Removing an element. -/// - Inserting a new element before an existing element. -/// - Pushing or popping an element from the end of the list. -pub struct DoublyLinkedList { - pub first: *mut DoublyLinkedNode, - pub last: *mut DoublyLinkedNode, - pub len: usize, -} - -/// Node inside the linked list wrapping the actual data. -// In Zig this is `DoublyLinkedList(T).Node`. -pub struct DoublyLinkedNode { - pub prev: *mut DoublyLinkedNode, - pub next: *mut DoublyLinkedNode, - pub data: T, -} - -impl Default for DoublyLinkedList { - fn default() -> Self { - Self { - first: ptr::null_mut(), - last: ptr::null_mut(), - len: 0, - } - } -} - -impl DoublyLinkedList { - /// Insert a new node after an existing one. - /// - /// Arguments: - /// node: Pointer to a node in the list. - /// new_node: Pointer to the new node to insert. - pub unsafe fn insert_after( - &mut self, - node: *mut DoublyLinkedNode, - new_node: *mut DoublyLinkedNode, - ) { - // SAFETY: caller guarantees `node` is in this list and `new_node` is valid+unlinked. - unsafe { - (*new_node).prev = node; - let next_node = (*node).next; - if !next_node.is_null() { - // Intermediate node. - (*new_node).next = next_node; - (*next_node).prev = new_node; - } else { - // Last element of the list. - (*new_node).next = ptr::null_mut(); - self.last = new_node; - } - (*node).next = new_node; - } - - self.len += 1; - } - - /// Insert a new node before an existing one. - /// - /// Arguments: - /// node: Pointer to a node in the list. - /// new_node: Pointer to the new node to insert. - pub unsafe fn insert_before( - &mut self, - node: *mut DoublyLinkedNode, - new_node: *mut DoublyLinkedNode, - ) { - // SAFETY: caller guarantees `node` is in this list and `new_node` is valid+unlinked. - unsafe { - (*new_node).next = node; - let prev_node = (*node).prev; - if !prev_node.is_null() { - // Intermediate node. - (*new_node).prev = prev_node; - (*prev_node).next = new_node; - } else { - // First element of the list. - (*new_node).prev = ptr::null_mut(); - self.first = new_node; - } - (*node).prev = new_node; - } - - self.len += 1; - } - - /// Concatenate list2 onto the end of list1, removing all entries from the former. - /// - /// Arguments: - /// list1: the list to concatenate onto - /// list2: the list to be concatenated - pub unsafe fn concat_by_moving(&mut self, list2: &mut Self) { - let l2_first = list2.first; - if l2_first.is_null() { - return; - } - let l1_last = self.last; - if !l1_last.is_null() { - // SAFETY: `l1_last` and `l2_first` are non-null linked nodes. - unsafe { - (*l1_last).next = list2.first; - (*l2_first).prev = self.last; - } - self.len += list2.len; - } else { - // list1 was empty - self.first = list2.first; - self.len = list2.len; - } - self.last = list2.last; - list2.first = ptr::null_mut(); - list2.last = ptr::null_mut(); - list2.len = 0; - } - - /// Insert a new node at the end of the list. - /// - /// Arguments: - /// new_node: Pointer to the new node to insert. - pub unsafe fn append(&mut self, new_node: *mut DoublyLinkedNode) { - let last = self.last; - if !last.is_null() { - // Insert after last. - // SAFETY: `last` is a valid node in this list. - unsafe { self.insert_after(last, new_node) }; - } else { - // Empty list. - // SAFETY: forwards caller's guarantee on `new_node`. - unsafe { self.prepend(new_node) }; - } - } - - /// Insert a new node at the beginning of the list. - /// - /// Arguments: - /// new_node: Pointer to the new node to insert. - pub unsafe fn prepend(&mut self, new_node: *mut DoublyLinkedNode) { - let first = self.first; - if !first.is_null() { - // Insert before first. - // SAFETY: `first` is a valid node in this list. - unsafe { self.insert_before(first, new_node) }; - } else { - // Empty list. - self.first = new_node; - self.last = new_node; - // SAFETY: caller guarantees `new_node` is valid. - unsafe { - (*new_node).prev = ptr::null_mut(); - (*new_node).next = ptr::null_mut(); - } - - self.len = 1; - } - } - - /// Remove a node from the list. - /// - /// Arguments: - /// node: Pointer to the node to be removed. - pub unsafe fn remove(&mut self, node: *mut DoublyLinkedNode) { - // SAFETY: caller guarantees `node` is a valid node currently in this list. - unsafe { - let prev_node = (*node).prev; - if !prev_node.is_null() { - // Intermediate node. - (*prev_node).next = (*node).next; - } else { - // First element of the list. - self.first = (*node).next; - } - - let next_node = (*node).next; - if !next_node.is_null() { - // Intermediate node. - (*next_node).prev = (*node).prev; - } else { - // Last element of the list. - self.last = (*node).prev; - } - } - - self.len -= 1; - debug_assert!(self.len == 0 || (!self.first.is_null() && !self.last.is_null())); - } - - /// Remove and return the last node in the list. - /// - /// Returns: - /// A pointer to the last node in the list. - pub unsafe fn pop(&mut self) -> *mut DoublyLinkedNode { - let last = self.last; - if last.is_null() { - return ptr::null_mut(); - } - // SAFETY: `last` is a valid node in this list. - unsafe { self.remove(last) }; - last - } - - /// Remove and return the first node in the list. - /// - /// Returns: - /// A pointer to the first node in the list. - pub unsafe fn pop_first(&mut self) -> *mut DoublyLinkedNode { - let first = self.first; - if first.is_null() { - return ptr::null_mut(); - } - // SAFETY: `first` is a valid node in this list. - unsafe { self.remove(first) }; - first - } -} +// +// The Rust port of `std.DoublyLinkedList` / `DoublyLinkedNode` was removed +// after its in-tree unit test failed under Miri (Stacked Borrows): callers +// hand the list `&mut node` references whose tags are then invalidated by +// later `&mut node` re-borrows on the same stack-local, while the list +// still traverses the stale raw `*mut node` links. The struct had no +// callers outside its own unit test, so deletion is the safe fix. The one +// in-tree comment that referenced the type (`src/jsc/web_worker.rs`'s +// `TODO(port): std.DoublyLinkedList` for `WebWorker.live_{next,prev}`) +// remains a TODO. Future intrusive-list needs should pick a design that +// does not interleave `*mut node` and `&mut node` on the same allocation +// (for example pinned/list-owned nodes, an `intrusive-collections` adapter, +// or a `Box`-owning list). // ────────────────────────────────────────────────────────────────────────── // RapidHash // ────────────────────────────────────────────────────────────────────────── // Canonical impl lives in the leaf `bun_hash` crate; re-export so the -// historical `crate::deprecated::RapidHash` path keeps resolving. +// historical `crate::deprecated::RapidHash` path keeps resolving. Test +// vectors live alongside the canonical impl in `bun_hash::rapidhash`. pub use bun_hash::RapidHash; // ────────────────────────────────────────────────────────────────────────── @@ -349,149 +139,4 @@ pub const fn auto_format_label() -> &'static str { auto_format_label_fallback::("{s}") } -// ────────────────────────────────────────────────────────────────────────── -// tests -// ────────────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - - fn dnode(data: u32) -> DoublyLinkedNode { - DoublyLinkedNode { - prev: ptr::null_mut(), - next: ptr::null_mut(), - data, - } - } - - #[test] - fn basic_doubly_linked_list_test() { - // SAFETY: all nodes are stack-locals that outlive the list; intrusive-list invariants upheld by test sequencing - unsafe { - let mut list: DoublyLinkedList = DoublyLinkedList::default(); - - let mut one = dnode(1); - let mut two = dnode(2); - let mut three = dnode(3); - let mut four = dnode(4); - let mut five = dnode(5); - - list.append(&mut two); // {2} - list.append(&mut five); // {2, 5} - list.prepend(&mut one); // {1, 2, 5} - list.insert_before(&mut five, &mut four); // {1, 2, 4, 5} - list.insert_after(&mut two, &mut three); // {1, 2, 3, 4, 5} - - // Traverse forwards. - { - let mut it = list.first; - let mut index: u32 = 1; - while !it.is_null() { - assert!((*it).data == index); - index += 1; - it = (*it).next; - } - } - - // Traverse backwards. - { - let mut it = list.last; - let mut index: u32 = 1; - while !it.is_null() { - assert!((*it).data == (6 - index)); - index += 1; - it = (*it).prev; - } - } - - let _ = list.pop_first(); // {2, 3, 4, 5} - let _ = list.pop(); // {2, 3, 4} - list.remove(&mut three); // {2, 4} - - assert!((*list.first).data == 2); - assert!((*list.last).data == 4); - assert!(list.len == 2); - } - } - - #[test] - fn doubly_linked_list_concatenation() { - // SAFETY: all nodes are stack-locals that outlive the list; intrusive-list invariants upheld by test sequencing - unsafe { - let mut list1: DoublyLinkedList = DoublyLinkedList::default(); - let mut list2: DoublyLinkedList = DoublyLinkedList::default(); - - let mut one = dnode(1); - let mut two = dnode(2); - let mut three = dnode(3); - let mut four = dnode(4); - let mut five = dnode(5); - - list1.append(&mut one); - list1.append(&mut two); - list2.append(&mut three); - list2.append(&mut four); - list2.append(&mut five); - - list1.concat_by_moving(&mut list2); - - assert!(list1.last == core::ptr::from_mut(&mut five)); - assert!(list1.len == 5); - assert!(list2.first.is_null()); - assert!(list2.last.is_null()); - assert!(list2.len == 0); - - // Traverse forwards. - { - let mut it = list1.first; - let mut index: u32 = 1; - while !it.is_null() { - assert!((*it).data == index); - index += 1; - it = (*it).next; - } - } - - // Traverse backwards. - { - let mut it = list1.last; - let mut index: u32 = 1; - while !it.is_null() { - assert!((*it).data == (6 - index)); - index += 1; - it = (*it).prev; - } - } - - // Swap them back, this verifies that concatenating to an empty list works. - list2.concat_by_moving(&mut list1); - - // Traverse forwards. - { - let mut it = list2.first; - let mut index: u32 = 1; - while !it.is_null() { - assert!((*it).data == index); - index += 1; - it = (*it).next; - } - } - - // Traverse backwards. - { - let mut it = list2.last; - let mut index: u32 = 1; - while !it.is_null() { - assert!((*it).data == (6 - index)); - index += 1; - it = (*it).prev; - } - } - } - } - - // RapidHash test vectors live alongside the canonical impl in `bun_hash::rapidhash`. -} - // ported from: src/bun_core/deprecated.zig diff --git a/src/errno/lib.rs b/src/errno/lib.rs index 280b5f57a0d..c4aee6a53fc 100644 --- a/src/errno/lib.rs +++ b/src/errno/lib.rs @@ -285,22 +285,35 @@ pub fn e_from_negated(errno: core::ffi::c_int) -> E { } impl SystemErrno { - /// Zig: `@enumFromInt(n)`. Unchecked discriminant cast. + /// Zig: `@enumFromInt(n)`, but as a safe Rust API: validate before + /// constructing the enum so invalid input panics instead of creating an + /// invalid `#[repr(u16)]` enum value. /// - /// On POSIX the enum is dense `0..MAX`, so we debug-assert `n < MAX`. + /// On POSIX the enum is dense `0..MAX`, so we assert `n < MAX`. /// 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. #[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` (Zig `@enumFromInt` precondition). The enum is NOT - // contiguous on Windows; do not assume `n < MAX` implies validity there. - unsafe { core::mem::transmute::(n) } + { + // `as usize` on both sides papers over per-OS `MAX` typing (POSIX + // `u16` vs Windows `usize`) without normalizing the constant. + assert!( + (n as usize) < (Self::MAX as usize), + "invalid SystemErrno discriminant" + ); + // SAFETY: POSIX `SystemErrno` discriminants are dense in `0..MAX`, + // checked above. + unsafe { core::mem::transmute::(n) } + } + #[cfg(windows)] + { + match Self::from_repr(n) { + Some(errno) => errno, + None => panic!("invalid SystemErrno discriminant"), + } + } } } @@ -456,6 +469,30 @@ mod errno_name_tests { assert_eq!(system_errno_name(97), Some("EINTEGRITY")); } + #[test] + #[should_panic(expected = "invalid SystemErrno discriminant")] + fn system_errno_from_raw_rejects_invalid_safe_input() { + let _ = SystemErrno::from_raw(SystemErrno::MAX as u16); + } + + #[cfg(target_os = "linux")] + #[test] + #[should_panic(expected = "invalid Linux raw syscall errno")] + fn linux_raw_syscall_errno_rejects_invalid_tag() { + use crate::GetErrno; + + let invalid_errno = SystemErrno::MAX as isize; + let raw_syscall_ret = (-invalid_errno) as usize; + let _ = raw_syscall_ret.get_errno(); + } + + #[cfg(windows)] + #[test] + #[should_panic(expected = "invalid E discriminant")] + fn e_from_raw_rejects_sparse_hole() { + let _ = E::from_raw(138); + } + #[test] fn coreutils_map() { assert_eq!( diff --git a/src/errno/linux_errno.rs b/src/errno/linux_errno.rs index ae4af879062..2cd5991bd35 100644 --- a/src/errno/linux_errno.rs +++ b/src/errno/linux_errno.rs @@ -188,8 +188,10 @@ impl GetErrno for usize { } else { 0 }; - // SAFETY: int is in [0, 4096); E is #[repr] over the kernel errno range - unsafe { core::mem::transmute::(int as u16) } + // Validate instead of transmuting: the raw syscall errno range is + // wider than the declared `SystemErrno` discriminants. Unknown kernel + // errno values must fail closed instead of being reported as success. + SystemErrno::init(int as i64).expect("invalid Linux raw syscall errno") } } diff --git a/src/errno/windows_errno.rs b/src/errno/windows_errno.rs index 9dd9947e217..4dcf228c283 100644 --- a/src/errno/windows_errno.rs +++ b/src/errno/windows_errno.rs @@ -247,11 +247,10 @@ impl E { // `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` (Zig `@enumFromInt` precondition). Debug-asserted above; for - // untrusted input use `try_from_raw` instead. - unsafe { core::mem::transmute::(n) } + match Self::from_repr(n) { + Some(errno) => errno, + None => panic!("invalid E discriminant"), + } } /// Checked discriminant lookup. Port of Zig `std.meta.intToEnum(E, n)` — diff --git a/src/threading/guarded.rs b/src/threading/guarded.rs index 477c505ac0a..16e68e81345 100644 --- a/src/threading/guarded.rs +++ b/src/threading/guarded.rs @@ -70,7 +70,10 @@ impl GuardedBy { #[inline] pub fn try_lock(&self) -> Option> { if self.mutex.try_lock() { - Some(GuardedLock { guarded: self }) + Some(GuardedLock { + guarded: self, + _not_send: core::marker::PhantomData, + }) } else { None } @@ -99,7 +102,10 @@ impl GuardedBy { /// releases the lock on drop. pub fn lock(&self) -> GuardedLock<'_, Value, M> { self.mutex.lock(); - GuardedLock { guarded: self } + GuardedLock { + guarded: self, + _not_send: core::marker::PhantomData, + } } /// Lock-free mutable access when the caller already has `&mut self` @@ -130,6 +136,10 @@ impl GuardedBy { /// `lock()`/`defer unlock()` pair. pub struct GuardedLock<'a, Value, M: RawMutex> { guarded: &'a GuardedBy, + // Preserve the same `!Send`/`!Sync` surface as sibling mutex guards. + // Some OS backends require unlock on the locking thread, so a guard must + // not be movable to another thread for drop. + _not_send: core::marker::PhantomData<*const ()>, } impl<'a, Value> GuardedLock<'a, Value, Mutex> {