use core::cell::UnsafeCell; use core::mem::MaybeUninit; use crate::fs as Fs; use crate::{MAX_PATH_BYTES, PathBuffer, SEP, SEP_POSIX, SEP_WINDOWS}; use bun_core::{ZStr, strings}; // Thread-local scratch buffers. Stored in `UnsafeCell` (not `RefCell`) // because callers must receive a raw `&mut` slice that outlives the `.with` closure // — the contract is "valid until next call on this thread". RefCell's // runtime borrow tracking cannot express that contract and would force an // unsafe-lifetime-extend through `RefCell::as_ptr` (PORTING.md §Forbidden). // SAFETY invariant: each buffer has at most one live mutable borrow per thread; // callers must not re-enter the accessor while a previous borrow is alive. thread_local! { static PARSER_JOIN_INPUT_BUFFER: UnsafeCell<[u8; PARSER_JOIN_INPUT_BUFFER_LEN]> = const { UnsafeCell::new([0u8; PARSER_JOIN_INPUT_BUFFER_LEN]) }; static PARSER_BUFFER: UnsafeCell<[u8; PARSER_BUFFER_LEN]> = const { UnsafeCell::new([0u8; PARSER_BUFFER_LEN]) }; // One per family of thread-local helpers, see `tl_long_result`. static PARSER_JOIN_INPUT_LONG: LongResults = const { UnsafeCell::new(Vec::new()) }; static JOIN_LONG: LongResults = const { UnsafeCell::new(Vec::new()) }; static RELATIVE_LONG: LongResults = const { UnsafeCell::new(Vec::new()) }; } /// Fixed output capacity of [`join_abs_string`] / [`join_abs_string_z`]. A longer result goes to the heap. const PARSER_JOIN_INPUT_BUFFER_LEN: usize = 4096; /// Output capacity of [`normalize_string`]. const PARSER_BUFFER_LEN: usize = 1024; /// The blocks that have held a family's long results. Only the last one, the largest, is written. type LongResults = UnsafeCell>>; /// Keeps `result`, which the caller computed in heap scratch because it may not fit the family's /// fixed buffer. It replaces the family's previous long result in place, like a short result in the /// fixed buffer. A block that is too small stays allocated until the thread exits, so a caller that /// still holds an earlier result reads stale bytes and never freed ones. #[inline(never)] fn tl_long_result( slot: &'static std::thread::LocalKey, result: &[u8], ) -> &'static [u8] { slot.with(|cell| { // SAFETY: as in `tl_buf_mut`. let blocks: &'static mut Vec> = unsafe { &mut *cell.get() }; if blocks.last().is_none_or(|block| block.len() < result.len()) { blocks.push(vec![0u8; result.len().next_power_of_two()]); } let last = blocks.len() - 1; let kept = &mut blocks[last][..result.len()]; kept.copy_from_slice(result); &*kept }) } /// Keeps the code for a long result out of line, so that a short one only pays for the length check. #[cold] #[inline(never)] fn cold(long_result: impl FnOnce() -> R) -> R { long_result() } /// Project `&'static mut` into a thread-local `UnsafeCell<[u8; N]>` scratch /// buffer. One `unsafe` site for all `PARSER_BUFFER` / `PARSER_JOIN_INPUT_BUFFER` /// / `JOIN_BUF` accessors (nonnull-asref reduction: 6 sites → 1). /// /// The `'static` output lifetime is the honest contract: the buffer is /// thread-local storage that lives for the thread's lifetime, and the returned /// slice is "valid until the next call on this thread" (see module comment /// above). Callers uphold the /// single-live-borrow-per-thread invariant. #[inline] fn tl_buf_mut(b: &UnsafeCell<[u8; N]>) -> &'static mut [u8; N] { // SAFETY: thread-local UnsafeCell ⇒ this thread is the sole accessor; // callers never re-enter while holding the borrow (see fn doc). unsafe { &mut *b.get() } } pub fn z<'a>(input: &[u8], output: &'a mut PathBuffer) -> &'a ZStr { if input.len() >= MAX_PATH_BYTES { if cfg!(debug_assertions) { panic!("path too long"); } // SAFETY: empty static string with NUL at [0] return ZStr::EMPTY; } output[..input.len()].copy_from_slice(input); output[input.len()] = 0; ZStr::from_buf(output, input.len()) } type IsSeparatorFunc = fn(char: u8) -> bool; // Rust cannot express "fn(T) -> bool" as a value, so the generic-`T` // callers dispatch via Platform methods instead of fn pointers. #[inline(always)] fn is_dotdot_with_type(slice: &[T]) -> bool { // PERF: the generic path checks elementwise (no specialized T==u8 wide // compare) — profile if it shows up on a hot path. slice.len() >= 2 && slice[0] == T::from_u8(b'.') && slice[1] == T::from_u8(b'.') } #[derive(Copy, Clone, PartialEq, Eq)] pub enum ParentEqual { Parent, Equal, Unrelated, } pub fn is_parent_or_equal(parent_: &[u8], child: &[u8]) -> ParentEqual { let mut parent = parent_; while !parent.is_empty() && is_sep_any(parent[parent.len() - 1]) { parent = &parent[..parent.len() - 1]; } #[cfg(not(any(target_os = "linux", target_os = "android")))] let starts_with = strings::starts_with_case_insensitive_ascii; #[cfg(any(target_os = "linux", target_os = "android"))] let starts_with = strings::starts_with; if !starts_with(child, parent) { return ParentEqual::Unrelated; } if child.len() == parent.len() { return ParentEqual::Equal; } if child.len() > parent.len() && is_sep_any(child[parent.len()]) { return ParentEqual::Parent; } ParentEqual::Unrelated } fn get_if_exists_longest_common_path_generic<'a, P: PlatformT>( input: &[&'a [u8]], ) -> Option<&'a [u8]> { let is_path_separator = P::P.get_separator_func(); let nql_at_index_fn: fn(usize, usize, &[&[u8]]) -> bool = match P::P { Platform::Windows => |n, i, inp| nql_at_index_case_insensitive_dyn(n, i, inp), _ => |n, i, inp| nql_at_index_dyn(n, i, inp), }; // PERF: uses a runtime `n` (no per-count unrolling). Profile if it shows // up on a hot path. let mut min_length: usize = usize::MAX; for str in input { min_length = str.len().min(min_length); } let mut index: usize = 0; let mut last_common_separator: Option = None; match input.len() { 0 => return Some(b""), 1 => return Some(input[0]), n @ 2.. => { while index < min_length { if nql_at_index_fn(n, index, input) { last_common_separator?; break; } if is_path_separator(input[0][index]) { last_common_separator = Some(index); } index += 1; } } } if index == 0 { return Some(P::P.separator_string().as_bytes()); } if last_common_separator.is_none() { return Some(b"."); } // The above won't work for a case like this: // /app/public/index.js // /app/public // It will return: // /app/ // It should return: // /app/public/ // To detect /app/public is actually a folder, we do one more loop through the strings // and say, "do one of you have a path separator after what we thought was the end?" // Up to 8 strings, one yes is enough, as in every release. More than 8 all have to say // yes: /app/publicx/a.js keeps /app/. let is_folder = input.len() <= 8 || input .iter() .all(|str| str.len() <= index || is_path_separator(str[index])); if is_folder { for str in input { if str.len() > index { if is_path_separator(str[index]) { return Some(&str[0..index + 1]); } } } } Some(&input[0][0..last_common_separator.unwrap() + 1]) } // Runtime helpers for the nql checks above. #[inline] fn nql_at_index_dyn(string_count: usize, index: usize, input: &[&[u8]]) -> bool { for s in 1..string_count { if input[0][index] != input[s][index] { return true; } } false } #[inline] fn nql_at_index_case_insensitive_dyn(string_count: usize, index: usize, input: &[&[u8]]) -> bool { for s in 1..string_count { if !input[0][index].eq_ignore_ascii_case(&input[s][index]) { return true; } } false } // TODO: is it faster to determine longest_common_separator in the while loop // or as an extra step at the end? // only boether to check if this function appears in benchmarking fn longest_common_path_generic<'a, P: PlatformT>(input: &[&'a [u8]]) -> &'a [u8] { let is_path_separator = P::P.get_separator_func(); let nql_at_index_fn: fn(usize, usize, &[&[u8]]) -> bool = match P::P { Platform::Windows => nql_at_index_case_insensitive_dyn, _ => nql_at_index_dyn, }; // PERF: no per-count unrolling — profile if hot let mut min_length: usize = usize::MAX; for str in input { min_length = str.len().min(min_length); } let mut index: usize = 0; let mut last_common_separator: usize = 0; match input.len() { 0 => return b"", 1 => return input[0], n @ 2..=8 => { // If volume IDs do not match on windows, we can't have a common path if P::P == Platform::Windows { let first_root = windows_filesystem_root(input[0]); let mut i = 1; while i < n { let root = windows_filesystem_root(input[i]); if !strings::eql_case_insensitive_ascii_check_length(first_root, root) { return b""; } i += 1; } } while index < min_length { if nql_at_index_fn(n, index, input) { break; } if is_path_separator(input[0][index]) { last_common_separator = index; } index += 1; } } _ => { // If volume IDs do not match on windows, we can't have a common path if P::P == Platform::Windows { let first_root = windows_filesystem_root(input[0]); let mut i: usize = 1; while i < input.len() { let root = windows_filesystem_root(input[i]); if !strings::eql_case_insensitive_ascii_check_length(first_root, root) { return b""; } i += 1; } } let mut string_index: usize = 1; // `index -= 1` below needs a byte in every input. while min_length > 0 && string_index < input.len() { while index < min_length { if P::P == Platform::Windows { if !input[0][index].eq_ignore_ascii_case(&input[string_index][index]) { break; } } else { if input[0][index] != input[string_index][index] { break; } } index += 1; } if index == min_length { index -= 1; } if is_path_separator(input[0][index]) { last_common_separator = index; } string_index += 1; } } } if index == 0 { return P::P.separator_string().as_bytes(); } // The above won't work for a case like this: // /app/public/index.js // /app/public // It will return: // /app/ // It should return: // /app/public/ // To detect /app/public is actually a folder, we do one more loop through the strings // and say, "do one of you have a path separator after what we thought was the end?" let mut idx = input.len(); // Use this value as an invalid value. for (i, str) in input.iter().enumerate() { if str.len() > index { if is_path_separator(str[index]) { idx = i; } else { idx = input.len(); break; } } } if idx != input.len() { return &input[idx][0..index + 1]; } &input[0][0..last_common_separator + 1] } pub fn longest_common_path<'a>(input: &[&'a [u8]]) -> &'a [u8] { longest_common_path_generic::(input) } pub fn get_if_exists_longest_common_path<'a>(input: &[&'a [u8]]) -> Option<&'a [u8]> { get_if_exists_longest_common_path_generic::(input) } // bun.ThreadlocalBuffers(struct {...}) heap-allocates on first use and // stores only a pointer in TLS. Represented as three independent lazily-boxed // thread-locals so that `relative_platform_buf` can hold disjoint `&mut` to // from/to buffers while `relative_to_common_path_buf()` is borrowed elsewhere // without aliasing a single parent payload. Only 3×8 bytes in static TLS instead // of 3×PathBuffer (see test/js/bun/binary/tls-segment-size). struct LazyPathBuf(core::cell::Cell<*mut PathBuffer>); impl Drop for LazyPathBuf { fn drop(&mut self) { let p = self.0.get(); if !p.is_null() { // SAFETY: `p` came from `heap::into_raw` in `lazy_path_buf`; sole accessor. unsafe { drop(bun_core::heap::take(p)) }; } } } thread_local! { static RELATIVE_TO_COMMON_PATH_BUF: LazyPathBuf = const { LazyPathBuf(core::cell::Cell::new(core::ptr::null_mut())) }; static RELATIVE_FROM_BUF: LazyPathBuf = const { LazyPathBuf(core::cell::Cell::new(core::ptr::null_mut())) }; static RELATIVE_TO_BUF: LazyPathBuf = const { LazyPathBuf(core::cell::Cell::new(core::ptr::null_mut())) }; } /// Lazily allocate (on first use) and borrow a thread-local `PathBuffer`. One /// `unsafe` site for all `RELATIVE_*_BUF` accessors (nonnull-asref reduction: /// 5 sites → 1). #[inline] fn lazy_path_buf(c: &LazyPathBuf) -> &'static mut PathBuffer { let mut p = c.0.get(); if p.is_null() { p = bun_core::heap::into_raw(Box::new(PathBuffer::ZEROED)); c.0.set(p); } // SAFETY: `p` is non-null after the init branch; this thread is the sole accessor. unsafe { &mut *p } } /// Raw pointer into the thread-local scratch buffer. Callers reborrow /// per-access — PORTING.md §Global mutable state. Valid until the next call on /// this thread; do not hold across re-entry. #[inline] pub fn relative_to_common_path_buf() -> *mut PathBuffer { RELATIVE_TO_COMMON_PATH_BUF.with(lazy_path_buf) } /// Find a relative path from a common path // Loosely based on Node.js' implementation of path.relative // https://github.com/nodejs/node/blob/9a7cbe25de88d87429a69050a1a1971234558d97/lib/path.js#L1250-L1259 fn relative_to_common_path<'a, const ALWAYS_COPY: bool, P: PlatformT>( common_path_: &[u8], normalized_from_: &[u8], normalized_to_: &'a [u8], buf: &'a mut [u8], ) -> &'a [u8] { let mut normalized_from = normalized_from_; let mut normalized_to = normalized_to_; let win_root_len: Option = if P::P == Platform::Windows { 'k: { let from_root = windows_filesystem_root(normalized_from_); let to_root = windows_filesystem_root(normalized_to_); if common_path_.is_empty() { // the only case path.relative can return not a relative string if !strings::eql_case_insensitive_ascii_check_length(from_root, to_root) { if normalized_to_.len() > to_root.len() && normalized_to_[normalized_to_.len() - 1] == b'\\' { if ALWAYS_COPY { let n = normalized_to_.len() - 1; buf[..n].copy_from_slice(&normalized_to_[..n]); return &buf[..n]; } else { return &normalized_to_[..normalized_to_.len() - 1]; } } else { if ALWAYS_COPY { buf[..normalized_to_.len()].copy_from_slice(normalized_to_); return &buf[..normalized_to_.len()]; } else { return normalized_to_; } } } } normalized_from = &normalized_from_[from_root.len()..]; normalized_to = &normalized_to_[to_root.len()..]; break 'k Some(from_root.len()); } } else { None }; let separator = P::P.separator(); let common_path = if P::P == Platform::Windows { &common_path_[win_root_len.unwrap()..] } else if crate::is_absolute_posix(common_path_) { &common_path_[1..] } else { common_path_ }; let shortest = normalized_from.len().min(normalized_to.len()); if shortest == common_path.len() { if normalized_to.len() >= normalized_from.len() { if common_path.is_empty() { if P::P == Platform::Windows && normalized_to.len() > 3 && normalized_to[normalized_to.len() - 1] == separator { normalized_to = &normalized_to[..normalized_to.len() - 1]; } // We get here if `from` is the root // For example: from='/'; to='/foo' if ALWAYS_COPY { buf[..normalized_to.len()].copy_from_slice(normalized_to); return &buf[..normalized_to.len()]; } else { return normalized_to; } } if normalized_to[common_path.len() - 1] == separator { let slice = &normalized_to[common_path.len()..]; let without_trailing_slash = if P::P == Platform::Windows && slice.len() > 3 && slice[slice.len() - 1] == separator { &slice[..slice.len() - 1] } else { slice }; if ALWAYS_COPY { // We get here if `from` is the exact base path for `to`. // For example: from='/foo/bar'; to='/foo/bar/baz' buf[..without_trailing_slash.len()].copy_from_slice(without_trailing_slash); return &buf[..without_trailing_slash.len()]; } else { return without_trailing_slash; } } } } let last_common_separator = strings::last_index_of_char_t( if P::P == Platform::Windows { common_path } else { common_path_ }, separator, ) .unwrap_or(0); // Generate the relative path based on the path difference between `to` // and `from`. // Track length and re-slice into `buf` at the end (borrowck-friendly). let mut out_len: usize = 0; if !normalized_from.is_empty() { let mut i: usize = (P::P.is_separator(normalized_from[0]) as usize) + 1 + last_common_separator; while i <= normalized_from.len() { if i == normalized_from.len() || (normalized_from[i] == separator && i + 1 < normalized_from.len()) { if out_len == 0 { buf[0..2].copy_from_slice(b".."); out_len = 2; } else { buf[out_len] = separator; buf[out_len + 1..out_len + 3].copy_from_slice(b".."); out_len += 3; } } i += 1; } } if normalized_to.len() > last_common_separator + 1 { let mut tail = &normalized_to[last_common_separator..]; if !normalized_from.is_empty() && (last_common_separator == normalized_from.len() || last_common_separator == normalized_from.len() - 1) { if P::P.is_separator(tail[0]) { tail = &tail[1..]; } } // avoid making non-absolute paths absolute let insert_leading_slash = !P::P.is_separator(tail[0]) && out_len > 0 && !P::P.is_separator(buf[out_len - 1]); if insert_leading_slash { buf[out_len] = separator; out_len += 1; } // Lastly, append the rest of the destination (`to`) path that comes after // the common path parts. buf[out_len..out_len + tail.len()].copy_from_slice(tail); out_len += tail.len(); } if out_len > 3 && buf[out_len - 1] == separator { out_len -= 1; } &buf[..out_len] } pub fn relative_normalized_buf<'a, P: PlatformT, const ALWAYS_COPY: bool>( buf: &'a mut [u8], from: &'a [u8], to: &'a [u8], ) -> &'a [u8] { let equal = if P::P == Platform::Windows { strings::eql_case_insensitive_ascii(from, to, true) } else { from.len() == to.len() && strings::eql_long(from, to, false) }; if equal { return b""; } let two: [&[u8]; 2] = [from, to]; let common_path = longest_common_path_generic::

(&two); relative_to_common_path::(common_path, from, to, buf) } // result borrows either the thread-local common-path buf ('static) // or `to` (when !ALWAYS_COPY and result==to). Return lifetime is `'a` (=to's), // since 'static: 'a. "Valid until next call" still applies for // the buf-backed case. pub fn relative_normalized<'a, P: PlatformT, const ALWAYS_COPY: bool>( from: &'a [u8], to: &'a [u8], ) -> &'a [u8] { let mut needed = relative_result_capacity(from.len(), to.len()); if needed > MAX_PATH_BYTES { needed = relative_result_capacity(count_separators(from), to.len()); } if needed > MAX_PATH_BYTES { return cold(move || { tl_long_result( &RELATIVE_LONG, relative_normalized_buf::(&mut vec![0u8; needed], from, to), ) }); } // SAFETY: thread-local scratch; single live borrow per thread. relative_normalized_buf::( RELATIVE_TO_COMMON_PATH_BUF.with(lazy_path_buf), from, to, ) } pub fn dirname(str: &[u8]) -> &[u8] { match P::P { Platform::Loose => { let Some(separator) = last_index_of_separator_loose(str) else { return b""; }; &str[..separator] } Platform::Posix => { let Some(separator) = last_index_of_separator_posix(str) else { return b""; }; if separator == 0 { return b"/"; } if separator == str.len() - 1 { return dirname::

(&str[..str.len() - 1]); } &str[..separator] } Platform::Windows => { let Some(separator) = last_index_of_separator_windows(str) else { return crate::disk_designator_windows(str); }; &str[..separator] } Platform::Nt => unreachable!("not implemented"), } } pub fn dirname_w(str: &[u16]) -> &[u16] { let Some(separator) = last_index_of_separator_windows_t::(str) else { // return disk designator instead if str.len() < 2 { return &[]; } if str[1] != b':' as u16 { return &[]; } if !is_drive_letter_t::(str[0]) { return &[]; } return &str[0..2]; }; &str[..separator] } pub fn relative(from: &[u8], to: &[u8]) -> &'static [u8] { relative_platform::(from, to) } pub fn relative_buf_z<'a>(buf: &'a mut [u8], from: &[u8], to: &[u8]) -> &'a ZStr { let rel = relative_platform_buf::(buf, from, to); let len = rel.len(); // reshaped for borrowck — drop `rel` borrow before mutating buf buf[len] = 0; // SAFETY: buf[len] == 0 written above ZStr::from_buf(&buf[..], len) } pub fn relative_platform_buf<'a, P: PlatformT, const ALWAYS_COPY: bool>( buf: &'a mut [u8], from: &[u8], to: &[u8], ) -> &'a [u8] { // RELATIVE_FROM_BUF and RELATIVE_TO_BUF are independent allocations so the // two `&mut` borrows below are disjoint. let relative_from_buf = RELATIVE_FROM_BUF.with(lazy_path_buf); let relative_to_buf = RELATIVE_TO_BUF.with(lazy_path_buf); relative_platform_in::( buf, &mut relative_from_buf[..], &mut relative_to_buf[..], relative_cwd::

(from, to), from, to, ) } /// [`relative_platform_buf`] with the caller's buffers for the normalized `from` and `to`, and /// the result of [`relative_cwd`]. fn relative_platform_in<'a, P: PlatformT, const ALWAYS_COPY: bool>( buf: &'a mut [u8], relative_from_buf: &'a mut [u8], relative_to_buf: &'a mut [u8], cwd: &'a [u8], from: &[u8], to: &[u8], ) -> &'a [u8] { let normalized_from: &[u8] = if P::P.is_absolute(from) { 'brk: { if P::P == Platform::Loose && cfg!(windows) { // we want to invoke the windows resolution behavior but end up with a // string with forward slashes. let normalized = normalize_string_buf::( from, &mut relative_from_buf[1..], ); platform_to_posix_in_place::(normalized); break 'brk &*normalized; } // reshaped for borrowck — capture len, drop inner &mut, re-slice let path_len = normalize_string_buf::(from, &mut relative_from_buf[1..]).len(); if P::P == Platform::Windows { break 'brk &relative_from_buf[1..1 + path_len]; } relative_from_buf[0] = P::P.separator(); break 'brk &relative_from_buf[0..path_len + 1]; } } else { // Avoid aliasing relative_from_buf as both input (normalize result) // and output (join target): normalize into relative_to_buf scratch, // then join into relative_from_buf. Safe because normalized_to is computed // afterwards (overwrites relative_to_buf anyway). let norm_len = normalize_string_buf::(from, &mut relative_to_buf[..]).len(); join_abs_string_buf::

(cwd, relative_from_buf, &[&relative_to_buf[..norm_len]]) }; let normalized_to: &[u8] = if P::P.is_absolute(to) { 'brk: { if P::P == Platform::Loose && cfg!(windows) { let normalized = normalize_string_buf::( to, &mut relative_to_buf[1..], ); platform_to_posix_in_place::(normalized); break 'brk &*normalized; } // reshaped for borrowck — capture len, drop inner &mut, re-slice let path_len = normalize_string_buf::(to, &mut relative_to_buf[1..]).len(); if P::P == Platform::Windows { break 'brk &relative_to_buf[1..1 + path_len]; } relative_to_buf[0] = P::P.separator(); break 'brk &relative_to_buf[0..path_len + 1]; } } else { // Avoid aliasing relative_to_buf as both input (normalize result) // and output (join target): normalize into `buf` scratch (caller // output buffer, untouched until the final relative_normalized_buf call // and disjoint from both threadlocals), then join into relative_to_buf. let norm_len = normalize_string_buf::(to, buf).len(); join_abs_string_buf::

(cwd, relative_to_buf, &[&buf[..norm_len]]) }; relative_normalized_buf::(buf, normalized_from, normalized_to) } pub fn relative_platform( from: &[u8], to: &[u8], ) -> &'static [u8] { let cwd = relative_cwd::

(from, to); let from_needed = relative_operand_needed::

(cwd, from); let to_needed = relative_operand_needed::

(cwd, to); // A path has at most as many separators as bytes. Count them only if that bound does not fit. let mut needed = relative_result_capacity(from_needed, to_needed); if needed > MAX_PATH_BYTES { needed = relative_result_capacity(relative_operand_separators::

(cwd, from), to_needed) .max(from_needed + 1); } if needed > MAX_PATH_BYTES { return cold(move || { tl_long_result( &RELATIVE_LONG, relative_platform_in::( &mut vec![0u8; needed], &mut vec![0u8; from_needed + 1], // A relative `from` is normalized here first. &mut vec![0u8; to_needed.max(from_needed) + 1], cwd, from, to, ), ) }); } relative_platform_in::( RELATIVE_TO_COMMON_PATH_BUF.with(lazy_path_buf), RELATIVE_FROM_BUF.with(lazy_path_buf), RELATIVE_TO_BUF.with(lazy_path_buf), cwd, from, to, ) } /// What `relative` resolves a relative operand against. It is read once per call, because /// `process.chdir()` on another thread must not change it between the bound and the join. #[inline(always)] fn relative_cwd(from: &[u8], to: &[u8]) -> &'static [u8] { if P::P.is_absolute(from) && P::P.is_absolute(to) { return b""; } Fs::FileSystem::instance().top_level_dir() } /// The most bytes of a buffer that `relative` uses for `path`. #[inline] fn relative_operand_needed(cwd: &[u8], path: &[u8]) -> usize { if !P::P.is_absolute(path) { // One byte more when normalized, then joined as `join_abs_string_buf_checked` bounds it. join_abs_needed(cwd.len(), &[path]) + 2 } else if P::P == Platform::Posix { // The root is byte 0 and the normalized rest follows it. path.len() } else { // Byte 0 stays free, and `C:\..` or a bare UNC volume normalizes to one byte more. path.len() + 2 } } /// The most separators that `path` has after `relative` made it absolute and normalized it. fn relative_operand_separators(cwd: &[u8], path: &[u8]) -> usize { if P::P.is_absolute(path) { // `C:\..` or a bare UNC volume gains one. count_separators(path) + 1 } else { // The join adds one, and a root if `cwd` has none. count_separators(cwd) + count_separators(path) + 2 } } /// Normalizing turns `\` into a separator on every platform. #[cold] #[inline(never)] fn count_separators(path: &[u8]) -> usize { strings::count_char(path, SEP_POSIX) + strings::count_char(path, SEP_WINDOWS) } /// Bounds a `relative_to_common_path` result: a `../` per component of `from`, then `to`. #[inline] fn relative_result_capacity(from_separators: usize, to_len: usize) -> usize { (from_separators + 1) * 3 + to_len } pub fn relative_alloc(from: &[u8], to: &[u8]) -> Result, bun_alloc::AllocError> { let result = relative_platform::(from, to); Ok(Box::<[u8]>::from(result)) } // This function is based on Go's volumeNameLen function // https://cs.opensource.google/go/go/+/refs/tags/go1.17.6:src/path/filepath/path_windows.go;l=57 // volumeNameLen returns length of the leading volume name on Windows. pub fn windows_volume_name_len(path: &[u8]) -> (usize, usize) { windows_volume_name_len_t::(path) } fn windows_volume_name_len_t(path: &[T]) -> (usize, usize) { if path.len() < 2 { return (0, 0); } // with drive letter let c = path[0]; if path[1] == T::from_u8(b':') { if is_drive_letter_t::(c) { return (2, 0); } } // UNC if path.len() >= 5 && Platform::Windows.is_separator_t::(path[0]) && Platform::Windows.is_separator_t::(path[1]) && !Platform::Windows.is_separator_t::(path[2]) && path[2] != T::from_u8(b'.') { if let Some(idx) = strings::index_of_any_t::(&path[3..], T::lit(b"/\\")) { // TODO: handle input "//abc//def" should be picked up as a unc path if path.len() > idx + 4 && !Platform::Windows.is_separator_t::(path[idx + 4]) { if let Some(idx2) = strings::index_of_any_t::(&path[idx + 4..], T::lit(b"/\\")) { return (idx + idx2 + 4, idx + 3); } else { return (path.len(), idx + 3); } } } return (path.len(), 0); } (0, 0) } pub fn windows_filesystem_root(path: &[u8]) -> &[u8] { windows_filesystem_root_t::(path) } pub fn is_drive_letter(c: u8) -> bool { is_drive_letter_t::(c) } pub fn is_drive_letter_t(c: T) -> bool { (T::from_u8(b'a') <= c && c <= T::from_u8(b'z')) || (T::from_u8(b'A') <= c && c <= T::from_u8(b'Z')) } pub fn has_any_illegal_chars(maybe_path: &[u8]) -> bool { if !cfg!(windows) { return false; } let mut maybe_path_ = maybe_path; // check for disk discrimnator; remove it since it has a ':' if starts_with_disk_discriminator(maybe_path_) { maybe_path_ = &maybe_path_[2..]; } // guard against OBJECT_NAME_INVALID => unreachable strings::index_of_any(maybe_path_, b"<>:\"|?*").is_some() } fn starts_with_disk_discriminator(maybe_path: &[u8]) -> bool { if !cfg!(windows) { return false; } if maybe_path.len() < 3 { return false; } if !is_drive_letter(maybe_path[0]) { return false; } if maybe_path[1] != b':' { return false; } if maybe_path[2] != b'\\' { return false; } true } // path.relative lets you do relative across different share drives fn windows_filesystem_root_t(path: &[T]) -> &[T] { if path.is_empty() { return &path[..0]; } // minimum: `C:` if path.len() < 2 { return if is_sep_any_t::(path[0]) { &path[0..1] } else { &path[0..0] }; } // with drive letter let c = path[0]; if path[1] == T::from_u8(b':') { if is_drive_letter_t::(c) { if path.len() > 2 && is_sep_any_t::(path[2]) { return &path[0..3]; } else { return &path[0..2]; } } } // UNC and device paths if path.len() >= 5 && Platform::Windows.is_separator_t::(path[0]) && Platform::Windows.is_separator_t::(path[1]) && !Platform::Windows.is_separator_t::(path[2]) { // device path if path[2] == T::from_u8(b'.') && Platform::Windows.is_separator_t::(path[3]) { return &path[0..4]; } // UNC if let Some(idx) = strings::index_of_any_t::(&path[3..], T::lit(b"/\\")) { if let Some(idx_second) = strings::index_of_any_t::(&path[4 + idx..], T::lit(b"/\\")) { return &path[0..idx + idx_second + 4 + 1]; // +1 to skip second separator } } return &path[0..]; } if is_sep_any_t::(path[0]) { return &path[0..1]; } &path[0..0] } pub fn normalize_string_generic_t< 'a, T: PathChar, const ALLOW_ABOVE_ROOT: bool, const PRESERVE_TRAILING_SLASH: bool, >( path_: &[T], buf: &'a mut [T], separator: T, is_separator_t: impl Fn(T) -> bool + Copy, ) -> &'a mut [T] { normalize_string_generic_tz::( path_, buf, separator, is_separator_t, ) } // Rust cannot vary the return type on a const-generic bool without // specialization, so // we always return `&mut [T]` and write the NUL when `ZERO_TERMINATE`. Callers // that need `&ZStr`/`&WStr` re-wrap with `from_raw`. pub fn normalize_string_generic_tz< 'a, T: PathChar, const ALLOW_ABOVE_ROOT: bool, const PRESERVE_TRAILING_SLASH: bool, const ZERO_TERMINATE: bool, const ADD_NT_PREFIX: bool, >( path_: &[T], buf: &'a mut [T], separator: T, is_separator: impl Fn(T) -> bool + Copy, ) -> &'a mut [T] { let is_windows = separator == T::from_u8(SEP_WINDOWS); // sep_str: single-char slice [separator], built per-call. if is_windows && cfg!(debug_assertions) { // this is here to catch a potential mistake by the caller // // since it is theoretically possible to get here in release // we will not do this check in release. debug_assert!(!strings::has_prefix_t::(path_, T::lit(b":\\"))); } let mut buf_i: usize = 0; let mut dotdot: usize = 0; let mut path_begin: usize = 0; let (vol_len, index_of_third_unc_slash) = if is_windows { windows_volume_name_len_t::(path_) } else { (0, 0) }; if is_windows { if vol_len > 0 { if ADD_NT_PREFIX { buf[buf_i..buf_i + 4].copy_from_slice(T::lit(b"\\??\\")); buf_i += 4; } if path_[1] != T::from_u8(b':') { // UNC paths if ADD_NT_PREFIX { // "UNC" ++ sep_str buf[buf_i..buf_i + 3].copy_from_slice(T::lit(b"UNC")); buf[buf_i + 3] = separator; buf_i += 2; } else { buf[buf_i] = separator; buf[buf_i + 1] = separator; } if index_of_third_unc_slash > 0 { // we have the ending slash buf[buf_i + 2..buf_i + index_of_third_unc_slash + 1] .copy_from_slice(&path_[2..index_of_third_unc_slash + 1]); buf[buf_i + index_of_third_unc_slash] = separator; buf[buf_i + index_of_third_unc_slash + 1..buf_i + vol_len] .copy_from_slice(&path_[index_of_third_unc_slash + 1..vol_len]); } else { // we dont have the ending slash buf[buf_i + 2..buf_i + vol_len].copy_from_slice(&path_[2..vol_len]); } buf[buf_i + vol_len] = separator; buf_i += vol_len + 1; path_begin = vol_len + 1; // it is just a volume name if path_begin >= path_.len() { if ZERO_TERMINATE { buf[buf_i] = T::from_u8(0); } return &mut buf[0..buf_i]; } } else { // drive letter buf[buf_i] = T::to_ascii_upper(path_[0]); buf[buf_i + 1] = T::from_u8(b':'); buf_i += 2; dotdot = buf_i; path_begin = 2; } } else if !path_.is_empty() && is_separator(path_[0]) { buf[buf_i] = separator; buf_i += 1; dotdot = buf_i; path_begin = 1; } } let mut r: usize = 0; let (path, buf_start) = if is_windows { (&path_[path_begin..], buf_i) } else { (path_, 0usize) }; let n = path.len(); if is_windows && (ALLOW_ABOVE_ROOT || vol_len > 0) { // consume leading slashes on windows if r < n && is_separator(path[r]) { r += 1; buf[buf_i] = separator; buf_i += 1; // win32.resolve("C:\\Users\\bun", "C:\\Users\\bun", "/..\\bar") // should be "C:\\bar" not "C:bar" dotdot = buf_i; } } while r < n { // empty path element // or // . element if is_separator(path[r]) { r += 1; continue; } if path[r] == T::from_u8(b'.') && (r + 1 == n || is_separator(path[r + 1])) { // skipping two is a windows-specific bugfix r += 1; continue; } if is_dotdot_with_type::(&path[r..]) && (r + 2 == n || is_separator(path[r + 2])) { r += 2; // .. element: remove to last separator if buf_i > dotdot { buf_i -= 1; while buf_i > dotdot && !is_separator(buf[buf_i]) { buf_i -= 1; } } else if ALLOW_ABOVE_ROOT { if buf_i > buf_start { // sep_str ++ ".." buf[buf_i] = separator; buf[buf_i + 1] = T::from_u8(b'.'); buf[buf_i + 2] = T::from_u8(b'.'); buf_i += 3; } else { buf[buf_i] = T::from_u8(b'.'); buf[buf_i + 1] = T::from_u8(b'.'); buf_i += 2; } dotdot = buf_i; } continue; } // real path element. // add slash if needed if buf_i != buf_start && buf_i > 0 && !is_separator(buf[buf_i - 1]) { buf[buf_i] = separator; buf_i += 1; } let from = r; while r < n && !is_separator(path[r]) { r += 1; } let count = r - from; buf[buf_i..buf_i + count].copy_from_slice(&path[from..from + count]); buf_i += count; } if PRESERVE_TRAILING_SLASH { // Was there a trailing slash? Let's keep it. if buf_i > 0 && path_[path_.len() - 1] == separator && buf[buf_i - 1] != separator { buf[buf_i] = separator; buf_i += 1; } } if is_windows && buf_i == 2 && buf[1] == T::from_u8(b':') { // If the original path is just a relative path with a drive letter, // add . buf[buf_i] = if !path.is_empty() && path[0] == T::from_u8(b'\\') { T::from_u8(b'\\') } else { T::from_u8(b'.') }; buf_i += 1; } if ZERO_TERMINATE { buf[buf_i] = T::from_u8(0); } &mut buf[0..buf_i] } #[derive(Copy, Clone, PartialEq, Eq, Debug, core::marker::ConstParamTy)] pub enum Platform { Loose, Windows, Posix, Nt, } // Nightly // `adt_const_params` is now enabled crate-wide (see lib.rs), so `Platform` // derives `ConstParamTy` and `` is the preferred // form for new code. The `PlatformT` sealed-trait shim below is kept for // existing call sites that haven't been migrated yet — both monomorphize // identically (`P::P` is a true `const Platform`). mod sealed { pub trait Sealed {} } pub trait PlatformT: Copy + sealed::Sealed + 'static { const P: Platform; } macro_rules! platform_variant { ($name:ident => $variant:ident) => { #[derive(Copy, Clone)] pub struct $name; impl sealed::Sealed for $name {} impl PlatformT for $name { const P: Platform = Platform::$variant; } }; } pub mod platform { use super::*; platform_variant!(Loose => Loose); platform_variant!(Windows => Windows); platform_variant!(Posix => Posix); platform_variant!(Nt => Nt); #[cfg(windows)] pub type Auto = Windows; #[cfg(unix)] pub type Auto = Posix; #[cfg(all(not(windows), not(unix)))] pub type Auto = Loose; } impl Platform { // Match the `platform::Auto` type alias above: pick by `windows`/`unix`/else // rather than enumerating OSes, so a new POSIX target (e.g. Android) doesn't // silently leave `Platform::AUTO` undefined. #[cfg(windows)] pub const AUTO: Platform = Platform::Windows; #[cfg(unix)] pub const AUTO: Platform = Platform::Posix; #[cfg(all(not(windows), not(unix)))] pub const AUTO: Platform = Platform::Loose; pub fn is_absolute(self, path: &[u8]) -> bool { self.is_absolute_t::(path) } // dropped `const` — PathChar trait methods aren't const-callable // on stable. pub fn is_absolute_t(self, path: &[T]) -> bool { match self { Platform::Posix => !path.is_empty() && path[0] == T::from_u8(b'/'), Platform::Nt | Platform::Windows | Platform::Loose => { crate::is_absolute_windows_t::(path) } } } #[inline] pub const fn separator(self) -> u8 { match self { Platform::Loose | Platform::Posix => SEP_POSIX, Platform::Nt | Platform::Windows => SEP_WINDOWS, } } #[inline] pub(crate) const fn separator_string(self) -> &'static str { match self { Platform::Loose | Platform::Posix => "/", Platform::Nt | Platform::Windows => "\\", } } pub(crate) const fn get_separator_func(self) -> IsSeparatorFunc { match self { Platform::Loose => is_sep_any, Platform::Nt | Platform::Windows => is_sep_any, Platform::Posix => is_sep_posix, } } #[inline(always)] pub fn is_separator(self, char: u8) -> bool { self.is_separator_t::(char) } #[inline(always)] pub(crate) fn is_separator_t(self, char: T) -> bool { match self { Platform::Loose => is_sep_any_t::(char), Platform::Nt | Platform::Windows => is_sep_any_t::(char), Platform::Posix => is_sep_posix_t::(char), } } pub(crate) const fn trailing_separator(self) -> [u8; 2] { match self { Platform::Nt | Platform::Windows => *b".\\", Platform::Posix | Platform::Loose => *b"./", } } pub(crate) fn leading_separator_index(self, path: &[T]) -> Option { match self { Platform::Nt | Platform::Windows => { if path.len() < 1 { return None; } if path[0] == T::from_u8(b'/') { return Some(0); } if path[0] == T::from_u8(b'\\') { return Some(0); } if path.len() < 3 { return None; } // C:\ // C:/ if path[0] >= T::from_u8(b'A') && path[0] <= T::from_u8(b'Z') && path[1] == T::from_u8(b':') { if path[2] == T::from_u8(b'/') { return Some(2); } if path[2] == T::from_u8(b'\\') { return Some(2); } return Some(1); } None } Platform::Posix => { if !path.is_empty() && path[0] == T::from_u8(b'/') { Some(0) } else { None } } Platform::Loose => Platform::Windows .leading_separator_index(path) .or_else(|| Platform::Posix.leading_separator_index(path)), } } } /// `str` must normalize into [`PARSER_BUFFER_LEN`] bytes; for input of /// arbitrary length use [`normalize_string_spill`]. pub fn normalize_string(str: &[u8]) -> &mut [u8] { // returns slice into thread-local PARSER_BUFFER; valid until the // next call on this thread. PARSER_BUFFER.with(|b| normalize_string_buf::(str, tl_buf_mut(b))) } /// [`normalize_string`] (thread-local buffer) when the result fits, otherwise /// into `spill` (grown as needed). `spill` is untouched in the common case. pub fn normalize_string_spill<'a, const ALLOW_ABOVE_ROOT: bool, P: PlatformT>( spill: &'a mut Vec, str: &'a [u8], ) -> &'a [u8] { // Normalizing only removes bytes, except that on Windows a drive-relative // `C:` becomes `C:.` and a bare UNC volume gains its trailing separator. let needed = str.len() + 1; if needed <= PARSER_BUFFER_LEN { return normalize_string::(str); } normalize_string_buf::(str, grow(spill, needed)) } pub fn normalize_buf<'a, P: PlatformT>(str: &[u8], buf: &'a mut [u8]) -> &'a mut [u8] { normalize_buf_t::(str, buf) } pub fn normalize_buf_z<'a, P: PlatformT>(str: &[u8], buf: &'a mut [u8]) -> &'a mut ZStr { let norm = normalize_buf_t::(str, buf); let len = norm.len(); buf[len] = 0; // SAFETY: buf[len] == 0 written above unsafe { ZStr::from_raw_mut(buf.as_mut_ptr(), len) } } /// [`normalize_buf`], `None` when the result does not fit `buf`. pub fn normalize_buf_checked<'a, P: PlatformT>( str: &[u8], buf: &'a mut [u8], ) -> Option<&'a mut [u8]> { // Normalizing grows a path by at most one byte (`""` -> `.`, `C:` -> `C:.`). let needed = str.len() + 1; if needed <= buf.len() { return Some(normalize_buf::

(str, buf)); } let mut scratch = vec![0u8; needed]; let normalized = normalize_buf::

(str, &mut scratch); let out = buf.get_mut(..normalized.len())?; out.copy_from_slice(normalized); Some(out) } /// [`normalize_buf_z`] into `buf` when the result fits, otherwise into `spill` (grown as needed). pub fn normalize_buf_z_spill<'a, P: PlatformT>( buf: &'a mut [u8], spill: &'a mut Vec, str: &[u8], ) -> &'a ZStr { // Normalizing grows a path by at most one byte (`""` -> `.`, `C:` -> `C:.`), plus the NUL. normalize_buf_z::

(str, buf_or_spill(buf, spill, str.len() + 2)) } /// `buf` when it holds `needed` bytes, otherwise `spill` grown to `needed`. pub fn buf_or_spill<'a>(buf: &'a mut [u8], spill: &'a mut Vec, needed: usize) -> &'a mut [u8] { if needed <= buf.len() { buf } else { grow(spill, needed) } } fn grow(spill: &mut Vec, needed: usize) -> &mut [u8] { if spill.len() < needed { spill.resize(needed, 0); } spill } pub fn normalize_buf_t<'a, T: PathChar, P: PlatformT>(str: &[T], buf: &'a mut [T]) -> &'a mut [T] { if str.is_empty() { buf[0] = T::from_u8(b'.'); return &mut buf[0..1]; } let is_absolute = P::P.is_absolute_t::(str); let trailing_separator = match P::P { Platform::Loose => last_index_of_separator_loose_t::(str), Platform::Nt | Platform::Windows => last_index_of_separator_windows_t::(str), Platform::Posix => last_index_of_separator_posix_t::(str), } == Some(str.len() - 1); if is_absolute && trailing_separator { return normalize_string_buf_t::(str, buf); } if is_absolute && !trailing_separator { return normalize_string_buf_t::(str, buf); } if !is_absolute && !trailing_separator { return normalize_string_buf_t::(str, buf); } normalize_string_buf_t::(str, buf) } pub fn normalize_string_buf< 'a, const ALLOW_ABOVE_ROOT: bool, P: PlatformT, const PRESERVE_TRAILING_SLASH: bool, >( str: &[u8], buf: &'a mut [u8], ) -> &'a mut [u8] { normalize_string_buf_t::(str, buf) } fn normalize_string_buf_t< 'a, T: PathChar, const ALLOW_ABOVE_ROOT: bool, P: PlatformT, const PRESERVE_TRAILING_SLASH: bool, >( str: &[T], buf: &'a mut [T], ) -> &'a mut [T] { match P::P { Platform::Nt => unreachable!("not implemented"), Platform::Windows => { normalize_string_windows_t::(str, buf) } Platform::Posix => { normalize_string_loose_buf_t::(str, buf) } Platform::Loose => { normalize_string_loose_buf_t::(str, buf) } } } pub fn join_abs<'a, P: PlatformT>(cwd: &'a [u8], part: &[u8]) -> &'a [u8] { join_abs_string::

(cwd, &[part]) } /// Convert parts of potentially invalid file paths into a single valid filpeath /// without querying the filesystem /// This is the equivalent of path.resolve /// /// Returned path is stored in a temporary buffer. It must be copied if it needs to be stored. // result borrows the thread-local buffer ('static) OR returns `cwd` // directly when `parts.is_empty()`. Return tied to `cwd`'s lifetime ('static: 'a). pub fn join_abs_string<'a, P: PlatformT>(cwd: &'a [u8], parts: &[&[u8]]) -> &'a [u8] { let needed = join_abs_needed(cwd.len(), parts) + nt_prefix_len::

(); // Without parts the result is `cwd` itself. if needed > PARSER_JOIN_INPUT_BUFFER_LEN && !parts.is_empty() { return cold(move || { tl_long_result( &PARSER_JOIN_INPUT_LONG, join_abs_string_buf::

(cwd, &mut vec![0u8; needed], parts), ) }); } PARSER_JOIN_INPUT_BUFFER.with(|b| join_abs_string_buf::

(cwd, tl_buf_mut(b), parts)) } /// [`join_abs_string`]; `None` when the normalized result does not fit the thread-local buffer. pub fn join_abs_string_checked<'a, P: PlatformT>( cwd: &'a [u8], parts: &[&[u8]], ) -> Option<&'a [u8]> { PARSER_JOIN_INPUT_BUFFER.with(|b| join_abs_string_buf_checked::

(cwd, tl_buf_mut(b), parts)) } /// [`join_abs_string`] (thread-local buffer) when the result fits, otherwise /// into `spill` (grown as needed). `spill` is untouched in the common case. pub fn join_abs_string_spill<'a, P: PlatformT>( cwd: &'a [u8], spill: &'a mut Vec, parts: &[&[u8]], ) -> &'a [u8] { debug_assert!(!matches!(P::P, Platform::Nt)); let needed = join_abs_needed(cwd.len(), parts); if needed <= PARSER_JOIN_INPUT_BUFFER_LEN { return join_abs_string::

(cwd, parts); } join_abs_string_buf::

(cwd, grow(spill, needed), parts) } /// Convert parts of potentially invalid file paths into a single valid filpeath /// without querying the filesystem /// This is the equivalent of path.resolve /// /// Returned path is stored in a temporary buffer. It must be copied if it needs to be stored. pub fn join_abs_string_z<'a, P: PlatformT>(cwd: &'a [u8], parts: &[&[u8]]) -> &'a ZStr { let needed = join_abs_needed(cwd.len(), parts) + nt_prefix_len::

() + 1; if needed > PARSER_JOIN_INPUT_BUFFER_LEN { return cold(move || { ZStr::from_slice_with_nul(tl_long_result( &PARSER_JOIN_INPUT_LONG, join_abs_string_buf_z::

(cwd, &mut vec![0u8; needed], parts).as_bytes_with_nul(), )) }); } PARSER_JOIN_INPUT_BUFFER.with(|b| join_abs_string_buf_z::

(cwd, tl_buf_mut(b), parts)) } /// Fixed output capacity of [`join`] / [`join_z`]. A longer result goes to the heap. const JOIN_BUF_LEN: usize = 4096; thread_local! { pub(crate) static JOIN_BUF: UnsafeCell<[u8; JOIN_BUF_LEN]> = const { UnsafeCell::new([0u8; JOIN_BUF_LEN]) }; } pub fn join(parts: &[&[u8]]) -> &'static [u8] { let needed = join_needed(parts); if needed > JOIN_BUF_LEN { return cold(move || { tl_long_result( &JOIN_LONG, join_string_buf::

(&mut vec![0u8; needed], parts), ) }); } JOIN_BUF.with(|b| join_string_buf::

(tl_buf_mut(b), parts)) } pub fn join_z(parts: &[&[u8]]) -> &'static ZStr { let needed = join_needed(parts); if needed > JOIN_BUF_LEN { return cold(move || { ZStr::from_slice_with_nul(tl_long_result( &JOIN_LONG, join_z_buf::

(&mut vec![0u8; needed], parts).as_bytes_with_nul(), )) }); } JOIN_BUF.with(|b| join_z_buf::

(tl_buf_mut(b), parts)) } #[inline] fn join_needed(parts: &[&[u8]]) -> usize { parts.iter().map(|p| p.len() + 1).sum::() + 1 } /// [`join_z_buf`], `None` when the result and its NUL do not fit `buf`. pub fn join_z_buf_checked<'a, P: PlatformT>( buf: &'a mut [u8], parts: &[&[u8]], ) -> Option<&'a ZStr> { let needed = join_needed(parts); if needed <= buf.len() { return Some(join_z_buf::

(buf, parts)); } let mut scratch = vec![0u8; needed]; let joined = join_z_buf::

(&mut scratch, parts).as_bytes_with_nul(); let out = buf.get_mut(..joined.len())?; out.copy_from_slice(joined); Some(ZStr::from_slice_with_nul(out)) } /// [`join_z_buf`] into `buf` when the result fits, otherwise into `spill` /// (grown as needed). `spill` is untouched in the common case. pub fn join_z_buf_spill<'a, P: PlatformT>( buf: &'a mut [u8], spill: &'a mut Vec, parts: &[&[u8]], ) -> &'a ZStr { join_z_buf::

(buf_or_spill(buf, spill, join_needed(parts)), parts) } /// [`join`] (thread-local buffer) when the result fits, otherwise into /// `spill` (grown as needed). `spill` is untouched in the common case. pub fn join_spill<'a, P: PlatformT>(spill: &'a mut Vec, parts: &[&[u8]]) -> &'a [u8] { let needed = join_needed(parts); if needed <= JOIN_BUF_LEN { return join::

(parts); } join_string_buf::

(grow(spill, needed), parts) } /// [`join_z`] (thread-local buffer) when the result fits, otherwise into /// `spill` (grown as needed). `spill` is untouched in the common case. pub fn join_z_spill<'a, P: PlatformT>(spill: &'a mut Vec, parts: &[&[u8]]) -> &'a ZStr { let needed = join_needed(parts); if needed <= JOIN_BUF_LEN { return join_z::

(parts); } join_z_buf::

(grow(spill, needed), parts) } pub fn join_z_buf<'a, P: PlatformT>(buf: &'a mut [u8], parts: &[&[u8]]) -> &'a ZStr { let buf_base = buf.as_ptr() as usize; let buf_len = buf.len(); let (start_offset, len) = { let joined = join_string_buf::

(&mut buf[..buf_len - 1], parts); (joined.as_ptr() as usize - buf_base, joined.len()) }; buf[start_offset + len] = 0; ZStr::from_slice_with_nul(&buf[start_offset..=start_offset + len]) } pub fn join_string_buf<'a, P: PlatformT>(buf: &'a mut [u8], parts: &[&[u8]]) -> &'a [u8] { join_string_buf_t::(buf, parts) } /// `joinStringBufW` overload for u16 parts (no transcode): the /// `T == u16 && Elem == u16` case. pub fn join_string_buf_w_same<'a, P: PlatformT>(buf: &'a mut [u16], parts: &[&[u16]]) -> &'a [u16] { join_string_buf_t_same::(buf, parts) } const JOIN_TEMP_LEN: usize = 4096; /// Uninitialized scratch for `join_string_buf_t*`'s unnormalized concatenation of `count` units. #[inline] fn join_temp_buf<'a, T>( stack: &'a mut [MaybeUninit; JOIN_TEMP_LEN], heap: &'a mut Vec, count: usize, ) -> &'a mut [MaybeUninit] { if count * 2 > JOIN_TEMP_LEN { heap.reserve_exact(count * 2); return heap.spare_capacity_mut(); } stack } /// Same-width `joinStringBufT`: parts already match `T`, so no UTF-8→16 transcode. /// split out of `join_string_buf_t` because Rust can't monomorphize on the /// parts' element types — callers pick the overload. fn join_string_buf_t_same<'a, T: PathChar, P: PlatformT>( buf: &'a mut [T], parts: &[&[T]], ) -> &'a [T] { let mut written: usize = 0; let mut stack_temp_buf = [const { MaybeUninit::::uninit() }; JOIN_TEMP_LEN]; let mut heap_temp_buf: Vec = Vec::new(); let mut count: usize = 0; for part in parts { if part.is_empty() { continue; } count += part.len() + 1; } let temp_buf = join_temp_buf(&mut stack_temp_buf, &mut heap_temp_buf, count); for part in parts { if part.is_empty() { continue; } if written > 0 { temp_buf[written].write(T::from_u8(P::P.separator())); written += 1; } temp_buf[written..written + part.len()].write_copy_of_slice(part); written += part.len(); } if written == 0 { buf[0] = T::from_u8(b'.'); return &buf[0..1]; } // SAFETY: the loop above wrote every unit of `temp_buf[..written]`. let joined = unsafe { temp_buf[..written].assume_init_ref() }; normalize_string_node_t::(joined, buf) } pub fn join_string_buf_z<'a, P: PlatformT>(buf: &'a mut [u8], parts: &[&[u8]]) -> &'a ZStr { join_z_buf::

(buf, parts) } fn join_string_buf_t<'a, T: PathChar, P: PlatformT>(buf: &'a mut [T], parts: &[&[u8]]) -> &'a [T] { // Takes `&[&[u8]]` — every in-tree caller passes u8 // parts — and transcodes to u16 below when `T == u16`. let mut written: usize = 0; let mut stack_temp_buf = [const { MaybeUninit::::uninit() }; JOIN_TEMP_LEN]; let mut heap_temp_buf: Vec = Vec::new(); let mut count: usize = 0; for part in parts { if part.is_empty() { continue; } count += part.len() + 1; } let temp_buf = join_temp_buf(&mut stack_temp_buf, &mut heap_temp_buf, count); for part in parts { if part.is_empty() { continue; } if written > 0 { temp_buf[written].write(T::from_u8(P::P.separator())); written += 1; } let spare = &mut temp_buf[written..]; // SAFETY: write-only view; `write_u8_part` only stores into it and returns the units written. let dest: &mut [T] = unsafe { core::slice::from_raw_parts_mut(spare.as_mut_ptr().cast::(), spare.len()) }; // Parts are always u8 (see fn-level comment); transcode iff T == u16. written += T::write_u8_part(dest, part); } if written == 0 { buf[0] = T::from_u8(b'.'); return &buf[0..1]; } // SAFETY: the loop above wrote every unit of `temp_buf[..written]`. let joined = unsafe { temp_buf[..written].assume_init_ref() }; normalize_string_node_t::(joined, buf) } /// Buffer length that holds `_join_abs_string_buf`'s concatenation of `cwd` and /// `parts` (one separator each, plus the one a bare Windows root gains) as well /// as its normalized output. #[inline] fn join_abs_needed(cwd_len: usize, parts: &[&[u8]]) -> usize { parts.iter().map(|p| p.len() + 1).sum::() + cwd_len + 2 } /// `_join_abs_string_buf` puts `\\?\` in front of an NT path. #[inline] fn nt_prefix_len() -> usize { if matches!(P::P, Platform::Nt) { 4 } else { 0 } } /// Scratch buffer for `_join_abs_string_buf`'s unnormalized concatenation. /// Draws from the /// thread-local `path_buffer_pool` for the common case and only heap-allocates /// when the concatenation would overflow a single `PathBuffer`. The pooled /// buffer is not re-zeroed — callers write every byte they later read. enum JoinScratch { Pooled(crate::path_buffer_pool::Guard), Heap(Vec), } impl JoinScratch { #[inline] fn init(base: usize, parts: &[&[u8]]) -> Self { let total = join_abs_needed(base, parts); if total <= MAX_PATH_BYTES { JoinScratch::Pooled(crate::path_buffer_pool::get()) } else { JoinScratch::Heap(vec![0u8; total]) } } #[inline] fn buf(&mut self) -> &mut [u8] { match self { JoinScratch::Pooled(g) => &mut g[..], JoinScratch::Heap(v) => &mut v[..], } } } pub fn join_abs_string_buf<'a, P: PlatformT>( cwd: &'a [u8], buf: &'a mut [u8], parts: &[&[u8]], ) -> &'a [u8] { _join_abs_string_buf::(cwd, buf, parts) } /// Like `join_abs_string_buf`, but returns null when the *normalized* result is /// too large for `buf`. Use this when `parts` may contain user-controlled /// input of arbitrary length. `..` segments are handled correctly: a path /// whose unnormalized length exceeds `buf.len` but normalizes down will still /// succeed. pub fn join_abs_string_buf_checked<'a, P: PlatformT>( cwd: &'a [u8], buf: &'a mut [u8], parts: &[&[u8]], ) -> Option<&'a [u8]> { debug_assert!(!matches!(P::P, Platform::Nt)); // Fast path: size check only — don't allocate a JoinScratch here since the // inner join_abs_string_buf already has its own (avoids doubling stack usage). let total = join_abs_needed(cwd.len(), parts); if total < buf.len() { return Some(join_abs_string_buf::

(cwd, buf, parts)); } // Slow path: allocate a large scratch for the result. The inner // join_abs_string_buf will heap-allocate its own temp buffer for the concat // since `total > MAX_PATH_BYTES * 2 > sfa inline size` is likely here. let mut scratch = vec![0u8; total]; let joined = join_abs_string_buf::

(cwd, &mut scratch, parts); if joined.len() > buf.len() { return None; } let len = joined.len(); buf[..len].copy_from_slice(joined); Some(&buf[..len]) } /// [`join_abs_string_buf_checked`], NUL-terminated; the last byte of `buf` is reserved for the NUL. pub fn join_abs_string_buf_z_checked<'a, P: PlatformT>( cwd: &[u8], buf: &'a mut [u8], parts: &[&[u8]], ) -> Option<&'a ZStr> { debug_assert!(!parts.is_empty()); let cap = buf.len() - 1; let len = join_abs_string_buf_checked::

(cwd, &mut buf[..cap], parts)?.len(); buf[len] = 0; Some(ZStr::from_buf(buf, len)) } pub fn join_abs_string_buf_z<'a, P: PlatformT>( cwd: &'a [u8], buf: &'a mut [u8], parts: &[&[u8]], ) -> &'a ZStr { let r = _join_abs_string_buf::(cwd, buf, parts); // SAFETY: IS_SENTINEL=true wrote NUL at r.len() unsafe { ZStr::from_raw(r.as_ptr(), r.len()) } } // We always return `&[u8]`; when `IS_SENTINEL` a NUL is written // at `result.len()` and callers (e.g. `join_abs_string_buf_z`) re-wrap as `ZStr`. fn _join_abs_string_buf<'a, const IS_SENTINEL: bool, P: PlatformT>( _cwd: &'a [u8], buf: &'a mut [u8], _parts: &[&[u8]], ) -> &'a [u8] { if P::P == Platform::Windows || (cfg!(windows) && P::P == Platform::Loose) { return join_abs_string_buf_windows::(_cwd, buf, _parts); } if P::P == Platform::Nt { let end_path = join_abs_string_buf_windows::(_cwd, &mut buf[4..], _parts); let end_len = end_path.len(); buf[0..4].copy_from_slice(b"\\\\?\\"); if IS_SENTINEL { buf[end_len + 4] = 0; } return &buf[0..end_len + 4]; } let mut parts: &[&[u8]] = _parts; if parts.is_empty() { if IS_SENTINEL { unreachable!(); } return _cwd; } if matches!(P::P, Platform::Loose | Platform::Posix) && parts.len() == 1 && parts[0].len() == 1 && parts[0][0] == SEP_POSIX { // A bare `b"/"` literal is NOT NUL-terminated and not in `buf`, breaking callers // that assume buf-backing (`ZStr::from_raw`, trailing-slash check). // Write into `buf` so the result is always buf-backed and sentinel-safe. buf[0] = b'/'; if IS_SENTINEL { buf[1] = 0; } return &buf[0..1]; } let mut cwd = if cfg!(windows) && _cwd.len() >= 3 && _cwd[1] == b':' { &_cwd[2..] } else { _cwd }; { let mut part_i: u16 = 0; let mut part_len: u16 = parts.len() as u16; while part_i < part_len { if P::P.is_absolute(parts[part_i as usize]) { cwd = parts[part_i as usize]; parts = &parts[part_i as usize + 1..]; part_len = parts.len() as u16; part_i = 0; continue; } part_i += 1; } } let mut scratch = JoinScratch::init(cwd.len(), parts); let temp_buf = scratch.buf(); temp_buf[..cwd.len()].copy_from_slice(cwd); let mut out: usize = cwd.len(); for &_part in parts { if _part.is_empty() { continue; } let part = _part; if out > 0 && temp_buf[out - 1] != P::P.separator() { temp_buf[out] = P::P.separator(); out += 1; } temp_buf[out..out + part.len()].copy_from_slice(part); out += part.len(); } // reshaped for borrowck — stash leading separator into a local // [u8; 8] (max len: NT prefix `\\?\` = 4) so we don't hold a borrow into // temp_buf across the normalize call below. let mut leading_buf = [0u8; 8]; let leading_len: usize = if let Some(i) = P::P.leading_separator_index::(&temp_buf[0..out]) { let outdir = &mut temp_buf[0..i + 1]; if P::P == Platform::Loose { slashes_to_posix_in_place(outdir); } leading_buf[..i + 1].copy_from_slice(&temp_buf[0..i + 1]); i + 1 } else { leading_buf[0] = b'/'; 1 }; // Copy leading separator into buf (order-independent with normalize, // which writes into buf[leading_len..]). buf[..leading_len].copy_from_slice(&leading_buf[..leading_len]); let result = normalize_string_buf::( &temp_buf[leading_len..out], &mut buf[leading_len..], ); let result_len = result.len(); if IS_SENTINEL { buf[result_len + leading_len] = 0; } &buf[0..result_len + leading_len] } fn join_abs_string_buf_windows<'a, const IS_SENTINEL: bool>( cwd: &'a [u8], buf: &'a mut [u8], parts: &[&[u8]], ) -> &'a [u8] { debug_assert!(crate::is_absolute_windows(cwd)); if parts.is_empty() { if IS_SENTINEL { unreachable!(); } return cwd; } // path.resolve is a bit different on Windows, as there are multiple possible filesystem roots. // When you resolve(`C:\hello`, `C:world`), the second arg is a drive letter relative path, so // the result of such is `C:\hello\world`, but if you used D:world, you would switch roots and // end up with `D:\world`. this root handling basically means a different algorithm. // // to complicate things, it seems node.js will first figure out what the last root is, then // in a separate search, figure out the last absolute path. // // Given the case `resolve("/one", "D:two", "three", "F:four", "five")` // Root is "F:", cwd is "/one", then join all paths that dont exist on other drives. // // Also, the special root "/" can match into anything, but we have to resolve it to a real // root at some point. That is what the `root_of_part.len == 0` check is doing. let (root, set_cwd, n_start) = 'base: { let root = 'root: { let mut n = parts.len(); while n > 0 { n -= 1; let len = windows_volume_name_len(parts[n]).0; if len > 0 { break 'root &parts[n][0..len]; } } // use cwd let len = windows_volume_name_len(cwd).0; break 'root &cwd[0..len]; }; let mut n = parts.len(); while n > 0 { n -= 1; if crate::is_absolute_windows(parts[n]) { let root_of_part = &parts[n][0..windows_volume_name_len(parts[n]).0]; if root_of_part.is_empty() || root_of_part == root { break 'base (root, &parts[n][root_of_part.len()..], n + 1); } } } // use cwd only if the root matches let cwd_root = &cwd[0..windows_volume_name_len(cwd).0]; if cwd_root == root { break 'base (root, &cwd[cwd_root.len()..], 0); } else { break 'base (root, b"/".as_slice(), 0); } }; if !set_cwd.is_empty() { debug_assert!(is_sep_any(set_cwd[0])); } let mut scratch = JoinScratch::init(root.len() + set_cwd.len(), &parts[n_start..]); let temp_buf = scratch.buf(); temp_buf[0..root.len()].copy_from_slice(root); temp_buf[root.len()..root.len() + set_cwd.len()].copy_from_slice(set_cwd); let mut out: usize = root.len() + set_cwd.len(); if set_cwd.is_empty() { // when cwd is `//server/share` without a suffix `/`, the path is considered absolute temp_buf[out] = b'\\'; out += 1; } for &part in &parts[n_start..] { if part.is_empty() { continue; } if out > 0 && temp_buf[out - 1] != b'\\' { temp_buf[out] = b'\\'; out += 1; } // skip over volume name let volume = &part[0..windows_volume_name_len(part).0]; if !volume.is_empty() && !strings::eql_long(volume, root, true) { continue; } let part_without_vol = &part[volume.len()..]; temp_buf[out..out + part_without_vol.len()].copy_from_slice(part_without_vol); out += part_without_vol.len(); } let result = normalize_string_buf::(&temp_buf[0..out], buf); let result_len = result.len(); if IS_SENTINEL { buf[result_len] = 0; } &buf[0..result_len] } // Separator predicates live in T0 `bun_core::path_sep`; re-export the full set // so existing `bun_paths::is_sep_*` callers are unchanged. pub use bun_core::path_sep::{ is_sep_any, is_sep_any_t, is_sep_native, is_sep_native_t, is_sep_posix_t, }; #[inline(always)] pub(crate) fn is_sep_posix(c: u8) -> bool { is_sep_posix_t::(c) } fn last_index_of_separator_windows(slice: &[u8]) -> Option { last_index_of_separator_windows_t::(slice) } fn last_index_of_separator_windows_t(slice: &[T]) -> Option { match T::units(slice) { strings::CodeUnits::U8(slice) => strings::last_index_of_any(slice, b"/\\"), // No two-needle reverse kernel for u16. strings::CodeUnits::U16(slice) => slice.iter().rposition(|&c| is_sep_any_t(c)), } } fn last_index_of_separator_posix(slice: &[u8]) -> Option { last_index_of_separator_posix_t::(slice) } fn last_index_of_separator_posix_t(slice: &[T]) -> Option { strings::last_index_of_char_t::(slice, T::from_u8(SEP_POSIX)) } fn last_index_of_separator_loose(slice: &[u8]) -> Option { last_index_of_separator_loose_t::(slice) } fn last_index_of_separator_loose_t(slice: &[T]) -> Option { last_index_of_sep_t::(slice) } fn normalize_string_loose_buf_t< 'a, T: PathChar, const ALLOW_ABOVE_ROOT: bool, const PRESERVE_TRAILING_SLASH: bool, >( str: &[T], buf: &'a mut [T], ) -> &'a mut [T] { normalize_string_generic_t::( str, buf, T::from_u8(SEP_POSIX), is_sep_any_t::, ) } fn normalize_string_windows_t< 'a, T: PathChar, const ALLOW_ABOVE_ROOT: bool, const PRESERVE_TRAILING_SLASH: bool, >( str: &[T], buf: &'a mut [T], ) -> &'a mut [T] { normalize_string_generic_t::( str, buf, T::from_u8(SEP_WINDOWS), is_sep_any_t::, ) } fn normalize_string_node_t<'a, T: PathChar, P: PlatformT>( str: &[T], buf: &'a mut [T], ) -> &'a mut [T] { if str.is_empty() { buf[0] = T::from_u8(b'.'); return &mut buf[0..1]; } let is_absolute = P::P.is_absolute_t::(str); let trailing_separator = P::P.is_separator_t::(str[str.len() - 1]); // `normalize_string_generic` handles absolute path cases for windows // we should not prefix with / // reshaped for borrowck — track an offset instead of reslicing. let buf_off: usize = if P::P == Platform::Windows { 0 } else { 1 }; let separator_t = T::from_u8(P::P.separator()); let is_sep_fn = |c: T| P::P.is_separator_t::(c); let out_len = if !is_absolute { normalize_string_generic_t::( str, &mut buf[buf_off..], separator_t, is_sep_fn, ) .len() } else { normalize_string_generic_t::( str, &mut buf[buf_off..], separator_t, is_sep_fn, ) .len() }; let mut out_len = out_len; if out_len == 0 { if is_absolute { buf[0] = separator_t; return &mut buf[0..1]; } if trailing_separator { let sep = P::P.trailing_separator(); buf[0] = T::from_u8(sep[0]); buf[1] = T::from_u8(sep[1]); return &mut buf[0..2]; } buf[0] = T::from_u8(b'.'); return &mut buf[0..1]; } if trailing_separator { if !P::P.is_separator_t::(buf[buf_off + out_len - 1]) { buf[buf_off + out_len] = separator_t; out_len += 1; } } if is_absolute { if P::P == Platform::Windows { return &mut buf[buf_off..buf_off + out_len]; } buf[0] = separator_t; return &mut buf[0..out_len + 1]; } &mut buf[buf_off..buf_off + out_len] } /// **NOT** plain basename (see /// [`crate::basename`] for that). Differs in two load-bearing ways: treats /// `\` as a separator on all platforms (`is_sep_any`), and returns `b"/"` /// (not `b""`) when the input is all separators. Shell builtins /// (`basename`/`mv`/`cp`) rely on both for POSIX-shell-correct `basename /`. /// Do not dedup against `crate::basename`. pub fn basename(path: &[u8]) -> &[u8] { if path.is_empty() { return &[]; } let mut end_index: usize = path.len() - 1; while is_sep_any(path[end_index]) { if end_index == 0 { return b"/"; } end_index -= 1; } let mut start_index: usize = end_index; end_index += 1; while !is_sep_any(path[start_index]) { if start_index == 0 { return &path[0..end_index]; } start_index -= 1; } &path[start_index + 1..end_index] } pub(crate) fn last_index_of_sep(path: &[u8]) -> Option { last_index_of_sep_t::(path) } fn last_index_of_sep_t(path: &[T]) -> Option { #[cfg(not(windows))] { return strings::last_index_of_char_t::(path, T::from_u8(b'/')); } #[cfg(windows)] { last_index_of_separator_windows_t::(path) } } /// The use case of this is when you do /// "import '/hello/world'" /// The windows disk designator is missing! /// /// Defaulting to C would work but the correct behavior is to use a known disk designator, /// via an absolute path from the referrer or what not. /// /// I've made it so that trying to read a file with a posix path is a debug assertion failure. /// /// To use this, stack allocate the following struct, and then call `resolve`. /// /// ```ignore /// let mut normalizer = PosixToWinNormalizer::default(); /// let result = normalizer.resolve(b"C:\\dev\\bun", b"/dev/bun/test/etc.js"); /// ``` /// /// When you are certain that using the current working directory is fine, you can use /// /// ```ignore /// let result = normalizer.resolve_cwd(b"/dev/bun/test/etc.js"); /// ``` /// /// This API does nothing on Linux (it has a size of zero). On Windows it /// holds a pooled path buffer. #[derive(Default)] pub struct PosixToWinNormalizer { #[cfg(windows)] _raw_bytes: crate::path_buffer_pool::Guard, #[cfg(not(windows))] _raw_bytes: (), } #[cfg(windows)] type PosixToWinBuf = PathBuffer; #[cfg(not(windows))] type PosixToWinBuf = (); impl PosixToWinNormalizer { #[inline] pub fn resolve<'a>(&'a mut self, source_dir: &[u8], maybe_posix_path: &'a [u8]) -> &'a [u8] { Self::resolve_with_external_buf(&mut self._raw_bytes, source_dir, maybe_posix_path) } #[inline] pub fn resolve_z<'a>(&'a mut self, source_dir: &[u8], maybe_posix_path: &'a ZStr) -> &'a ZStr { Self::resolve_with_external_buf_z(&mut self._raw_bytes, source_dir, maybe_posix_path) } #[inline] pub fn resolve_cwd<'a>(&'a mut self, maybe_posix_path: &'a [u8]) -> crate::Result<&'a [u8]> { Self::resolve_cwd_with_external_buf(&mut self._raw_bytes, maybe_posix_path) } // underlying implementation: fn resolve_with_external_buf<'a>( buf: &'a mut PosixToWinBuf, source_dir: &[u8], maybe_posix_path: &'a [u8], ) -> &'a [u8] { debug_assert!(crate::is_absolute_windows(maybe_posix_path)); #[cfg(windows)] { let root = windows_filesystem_root(maybe_posix_path); if root.len() == 1 { debug_assert!(is_sep_any(root[0])); if strings::is_windows_absolute_path_missing_drive_letter::(maybe_posix_path) { let source_root = windows_filesystem_root(source_dir); // The source root (arbitrarily long for UNC dirs) plus // the path must fit `buf` with one byte of headroom — // downstream normalization writes one past the input for // separator-less UNC roots. Such a join can't exist on NT // anyway, so fail safe to the un-joined input (which the // consuming lookup treats as nonexistent) instead of // writing past the buffer. if source_root.len() + maybe_posix_path.len() - 1 >= buf.len() { return maybe_posix_path; } buf[0..source_root.len()].copy_from_slice(source_root); buf[source_root.len()..source_root.len() + maybe_posix_path.len() - 1] .copy_from_slice(&maybe_posix_path[1..]); let res = &buf[0..source_root.len() + maybe_posix_path.len() - 1]; debug_assert!( !strings::is_windows_absolute_path_missing_drive_letter::(res) ); debug_assert!(crate::is_absolute_windows(res)); return res; } } debug_assert!( !strings::is_windows_absolute_path_missing_drive_letter::(maybe_posix_path) ); } let _ = (buf, source_dir); maybe_posix_path } fn resolve_with_external_buf_z<'a>( buf: &'a mut PosixToWinBuf, source_dir: &[u8], maybe_posix_path: &'a ZStr, ) -> &'a ZStr { debug_assert!(crate::is_absolute_windows(maybe_posix_path.as_bytes())); #[cfg(windows)] { let mp = maybe_posix_path.as_bytes(); let root = windows_filesystem_root(mp); if root.len() == 1 { debug_assert!(is_sep_any(root[0])); if strings::is_windows_absolute_path_missing_drive_letter::(mp) { let source_root = windows_filesystem_root(source_dir); // See resolve_with_external_buf: over-long joins fail // safe to the un-joined input (+ NUL accounted for here). if source_root.len() + mp.len() > buf.len() { return maybe_posix_path; } buf[0..source_root.len()].copy_from_slice(source_root); buf[source_root.len()..source_root.len() + mp.len() - 1] .copy_from_slice(&mp[1..]); buf[source_root.len() + mp.len() - 1] = 0; let len = source_root.len() + mp.len() - 1; // SAFETY: NUL written at buf[len] let res = ZStr::from_buf(&buf[..], len); debug_assert!( !strings::is_windows_absolute_path_missing_drive_letter::( res.as_bytes() ) ); debug_assert!(crate::is_absolute_windows(res.as_bytes())); return res; } } debug_assert!(!strings::is_windows_absolute_path_missing_drive_letter::(mp)); } let _ = (buf, source_dir); maybe_posix_path } pub fn resolve_cwd_with_external_buf<'a>( buf: &'a mut PosixToWinBuf, maybe_posix_path: &'a [u8], ) -> crate::Result<&'a [u8]> { debug_assert!(crate::is_absolute_windows(maybe_posix_path)); #[cfg(windows)] { let root = windows_filesystem_root(maybe_posix_path); if root.len() == 1 { debug_assert!(is_sep_any(root[0])); if strings::is_windows_absolute_path_missing_drive_letter::(maybe_posix_path) { // reshaped for borrowck — `getcwd` writes into // `buf` and returns a borrow of it; capture the lengths we // need, drop the borrow, then re-slice `buf`. let sr_len = { let cwd = bun_core::getcwd(buf)?; windows_filesystem_root(cwd.as_bytes()).len() }; // The cwd root (arbitrarily long for UNC cwds) plus the // path must fit `buf` with one byte of headroom: the // joined result feeds `normalize_buf`, whose UNC-root // handling writes one past the input when the cwd is a // bare share root with no trailing separator. Such a // combination can't exist on NT anyway, so error out // instead of writing past a buffer. if sr_len + maybe_posix_path.len() - 1 >= buf.len() { return Err(crate::Error::Sys(bun_errno::SystemErrno::ENAMETOOLONG)); } buf[sr_len..sr_len + maybe_posix_path.len() - 1] .copy_from_slice(&maybe_posix_path[1..]); let res = &buf[0..sr_len + maybe_posix_path.len() - 1]; debug_assert!( !strings::is_windows_absolute_path_missing_drive_letter::(res) ); debug_assert!(crate::is_absolute_windows(res)); return Ok(res); } } debug_assert!( !strings::is_windows_absolute_path_missing_drive_letter::(maybe_posix_path) ); } let _ = buf; Ok(maybe_posix_path) } pub fn resolve_cwd_with_external_buf_z<'a>( buf: &'a mut PathBuffer, maybe_posix_path: &[u8], ) -> crate::Result<&'a mut ZStr> { debug_assert!(crate::is_absolute_windows(maybe_posix_path)); #[cfg(windows)] { let root = windows_filesystem_root(maybe_posix_path); if root.len() == 1 { debug_assert!(is_sep_any(root[0])); if strings::is_windows_absolute_path_missing_drive_letter::(maybe_posix_path) { // reshaped for borrowck — see resolve_cwd above. let sr_len = { let cwd = bun_core::getcwd(buf)?; windows_filesystem_root(cwd.as_bytes()).len() }; // The cwd root (arbitrarily long for UNC cwds) plus the // path and its NUL must fit `buf`; such a combination // can't exist on NT anyway, so error out instead of // writing past it. if sr_len + maybe_posix_path.len() > buf.len() { return Err(crate::Error::Sys(bun_errno::SystemErrno::ENAMETOOLONG)); } buf[sr_len..sr_len + maybe_posix_path.len() - 1] .copy_from_slice(&maybe_posix_path[1..]); buf[sr_len + maybe_posix_path.len() - 1] = 0; let len = sr_len + maybe_posix_path.len() - 1; // SAFETY: NUL at buf[len] let res = unsafe { ZStr::from_raw_mut(buf.as_mut_ptr(), len) }; debug_assert!( !strings::is_windows_absolute_path_missing_drive_letter::( res.as_bytes() ) ); debug_assert!(crate::is_absolute_windows(res.as_bytes())); return Ok(res); } } debug_assert!( !strings::is_windows_absolute_path_missing_drive_letter::(maybe_posix_path) ); } if maybe_posix_path.len() + 1 > buf.len() { return Err(crate::Error::Sys(bun_errno::SystemErrno::ENAMETOOLONG)); } buf[..maybe_posix_path.len()].copy_from_slice(maybe_posix_path); buf[maybe_posix_path.len()] = 0; // SAFETY: NUL at buf[maybe_posix_path.len()] Ok(unsafe { ZStr::from_raw_mut(buf.as_mut_ptr(), maybe_posix_path.len()) }) } } // ResolvePath__joinAbsStringBufCurrentPlatformBunString: see src/jsc/resolve_path_jsc.rs // (reaches into the VM for cwd; paths/ is JSC-free). // ───────────────────────────────────────────────────────────────────────────── // In-place separator rewrites. // // `slashes_to_{posix,windows}_in_place` are the two PRIMITIVES — unconditional, // no host-OS gating, no drive-letter touch. // // The four pre-existing public fns below (`platformToPosixInPlace`, // `dangerouslyConvertPathTo{Posix,Windows}InPlace`, `posixToPlatformInPlace`) // are thin wrappers over the primitives so there isn't a fourth/fifth copy of // the loop body. // // Encoding safety: both 0x2F ('/') and 0x5C ('\\') are single-unit ASCII in // UTF-8 and UTF-16 and never appear as a sub-unit of a multi-unit sequence, so // a scalar replace is sound for `u8` and `u16` alike. // ───────────────────────────────────────────────────────────────────────────── /// Unconditional `'\\' → '/'` in place. No host-OS gate, no drive-letter touch. #[inline] pub fn slashes_to_posix_in_place(path: &mut [T]) { let bslash = T::from_u8(b'\\'); let fslash = T::from_u8(b'/'); for c in path.iter_mut() { if *c == bslash { *c = fslash; } } } /// Unconditional `'/' → '\\'` in place. No host-OS gate. #[inline] pub fn slashes_to_windows_in_place(path: &mut [T]) { let fslash = T::from_u8(b'/'); let bslash = T::from_u8(b'\\'); for c in path.iter_mut() { if *c == fslash { *c = bslash; } } } #[inline] pub fn platform_to_posix_in_place(path_buffer: &mut [T]) { if SEP == b'/' { return; } slashes_to_posix_in_place(path_buffer); } pub fn dangerously_convert_path_to_posix_in_place(path: &mut [T]) { #[cfg(windows)] { if path.len() > 2 && is_drive_letter_t::(path[0]) && path[1] == T::from_u8(b':') && is_sep_any_t::(path[2]) { // Uppercase drive letter (is_drive_letter_t guarantees [A-Za-z]). path[0] = T::to_ascii_upper(path[0]); } } slashes_to_posix_in_place(path); } #[inline] pub fn dangerously_convert_path_to_windows_in_place(path: &mut [T]) { slashes_to_windows_in_place(path); } pub fn path_to_posix_buf<'a, T: PathChar>(path: &[T], buf: &'a mut [T]) -> &'a mut [T] { let mut idx: usize = 0; while let Some(index) = strings::index_of_scalar(&path[idx..], T::from_u8(SEP_WINDOWS)).map(|p| p + idx) { buf[idx..index].copy_from_slice(&path[idx..index]); buf[index] = T::from_u8(SEP_POSIX); idx = index + 1; } buf[idx..path.len()].copy_from_slice(&path[idx..path.len()]); &mut buf[0..path.len()] } pub fn platform_to_posix_buf<'a, T: PathChar>(path: &'a [T], buf: &'a mut [T]) -> &'a [T] { if SEP == b'/' { return path; } let mut idx: usize = 0; while let Some(index) = strings::index_of_scalar(&path[idx..], T::from_u8(SEP)).map(|p| p + idx) { buf[idx..index].copy_from_slice(&path[idx..index]); buf[index] = T::from_u8(b'/'); idx = index + 1; } buf[idx..path.len()].copy_from_slice(&path[idx..path.len()]); &buf[0..path.len()] } #[inline] pub fn posix_to_platform_in_place(path_buffer: &mut [T]) { if SEP == b'/' { return; } slashes_to_windows_in_place(path_buffer); } // `PathChar` is now canonical at `crate::path_char`; re-export for callers // that still path through `resolve_path::PathChar`. pub use crate::PathChar; // Run with `cargo test -p bun_paths` (also the Miri lane, `bun run rust:miri -p bun_paths`). // Normalizing reaches `bun_core::strings`' byte searches, whose highway kernels // are only linked into the full binary, so the ones they reference are satisfied // below with scalar stubs (the simdutf counterparts live in string_paths.rs). // Under Miri the wrappers take their own scalar path and never call these. #[cfg(test)] mod tests { use super::*; /// Returns `haystack_len` when `needle` does not occur, like the kernel. #[unsafe(no_mangle)] unsafe extern "C" fn highway_index_of_char( haystack: *const u8, haystack_len: usize, needle: u8, ) -> usize { // SAFETY: test stub; callers pass a valid (ptr, len) pair. let haystack = unsafe { core::slice::from_raw_parts(haystack, haystack_len) }; haystack .iter() .position(|&b| b == needle) .unwrap_or(haystack_len) } /// Returns `text_len` when no byte of `chars` occurs, like the kernel. #[unsafe(no_mangle)] unsafe extern "C" fn highway_index_of_any_char( text: *const u8, text_len: usize, chars: *const u8, chars_len: usize, ) -> usize { // SAFETY: test stub; callers pass valid (ptr, len) pairs. let (text, chars) = unsafe { ( core::slice::from_raw_parts(text, text_len), core::slice::from_raw_parts(chars, chars_len), ) }; text.iter() .position(|b| chars.contains(b)) .unwrap_or(text_len) } /// `n` when `c` does not occur, like the kernel. #[unsafe(no_mangle)] unsafe extern "C" fn highway_last_index_of_char(h: *const u8, n: usize, c: u8) -> usize { // SAFETY: test stub; callers pass a valid (ptr, len) pair. let h = unsafe { core::slice::from_raw_parts(h, n) }; h.iter().rposition(|&b| b == c).unwrap_or(n) } #[unsafe(no_mangle)] unsafe extern "C" fn highway_count_char(h: *const u8, n: usize, c: u8) -> usize { // SAFETY: test stub; callers pass a valid (ptr, len) pair. let h = unsafe { core::slice::from_raw_parts(h, n) }; h.iter().filter(|&&b| b == c).count() } /// `usize::MAX` when `nd` does not occur, like the kernel. #[unsafe(no_mangle)] unsafe extern "C" fn highway_memrmem16( h: *const u16, n: usize, nd: *const u16, k: usize, ) -> usize { // SAFETY: test stub; callers pass valid (ptr, len) pairs. let (h, nd) = unsafe { ( core::slice::from_raw_parts(h, n), core::slice::from_raw_parts(nd, k), ) }; let last = n .checked_sub(k) .and_then(|end| (0..=end).rev().find(|&i| h[i..i + k] == *nd)); last.unwrap_or(usize::MAX) } fn pages(dir: &str, count: usize) -> Vec { (0..count).map(|i| format!("{dir}page{i}.ts")).collect() } fn common_path<'a>(input: impl IntoIterator) -> Option<&'a str> { let input: Vec<&[u8]> = input.into_iter().map(str::as_bytes).collect(); get_if_exists_longest_common_path(&input).map(|path| core::str::from_utf8(path).unwrap()) } #[test] fn common_path_of_files_in_one_directory_is_that_directory_for_any_count() { for dir in ["pages/", "./pages/", "/app/pages/"] { for count in 2..=20 { let files = pages(dir, count); assert_eq!( common_path(files.iter().map(String::as_str)), Some(dir), "{count} files" ); } } } #[test] fn common_path_is_none_when_the_inputs_differ_before_any_separator() { for count in [2, 9] { let files = pages("", count); assert_eq!( common_path(files.iter().map(String::as_str)), None, "{count} files" ); } } #[test] fn common_path_does_not_depend_on_the_order_of_the_inputs() { let files = pages("pages/", 8); let files = files.iter().map(String::as_str); assert_eq!(common_path(files.clone().chain(["app.ts"])), None); assert_eq!(common_path(["app.ts"].into_iter().chain(files)), None); } #[test] fn common_path_of_more_than_8_inputs_is_an_ancestor_of_every_input() { let files = pages("./pub/", 8); let files = files.iter().map(String::as_str); for sibling in ["./pubx/b.ts", "./pub.ts"] { assert_eq!( common_path(files.clone().chain([sibling])), Some("./"), "8 files, then {sibling}" ); assert_eq!( common_path([sibling].into_iter().chain(files.clone())), Some("./"), "{sibling}, then 8 files" ); } let files = pages("/app/pub/", 8); let files = files.iter().map(String::as_str); assert_eq!( common_path( ["/app/pub"] .into_iter() .chain(files) .chain(["/app/pubx/b.ts"]) ), Some("/app/"), "the directory, 8 files, then a sibling" ); } #[test] fn common_path_of_up_to_8_inputs_is_the_folder_of_one_of_them() { for count in [1, 7] { let files = pages("./pub/", count); let files = files.iter().map(String::as_str); for sibling in ["./pubx/b.ts", "./pub.ts"] { assert_eq!( common_path(files.clone().chain([sibling])), Some("./pub/"), "{count} files, then {sibling}" ); assert_eq!( common_path([sibling].into_iter().chain(files.clone())), Some("./pub/"), "{sibling}, then {count} files" ); } } assert_eq!( common_path(["/app/pub", "/app/pub/page0.ts", "/app/pubx/b.ts"]), Some("/app/pub/") ); } #[test] fn common_path_is_an_input_when_every_longer_input_is_inside_it() { for count in [1, 8] { let files = pages("/app/pages/", count); let files = files.iter().map(String::as_str); assert_eq!( common_path(files.clone().chain(["/app/pages"])), Some("/app/pages/"), "{count} files, then the directory" ); assert_eq!( common_path(["/app/pages"].into_iter().chain(files)), Some("/app/pages/"), "the directory, then {count} files" ); } // `Loose` takes `\` as a separator too. The answer ends as its first input does. assert_eq!( common_path(["/app/public/index.js", "/app/public\\x.js"]), Some("/app/public/") ); } #[test] fn common_path_of_an_empty_input_among_many_is_the_separator() { for count in [1, 8] { let files = pages("/app/pages/", count); let files = files.iter().map(String::as_str); assert_eq!(common_path(files.chain([""])), Some("/"), "{count} files"); } } #[test] fn normalize_string_spill_leaves_spill_untouched_when_the_input_fits() { let mut spill = Vec::new(); let out = normalize_string_spill::(&mut spill, b"./lib/../dist/./x.js"); assert_eq!(out, b"dist/x.js"); assert!(spill.is_empty()); } #[test] fn normalize_string_spill_spills_input_longer_than_the_thread_local_buffer() { let segment = vec![b'b'; PARSER_BUFFER_LEN * 3]; let mut input = b"./".to_vec(); input.extend_from_slice(&segment); input.extend_from_slice(b"/./x.js"); let mut expected = segment; expected.extend_from_slice(b"/x.js"); let mut spill = Vec::new(); let out = normalize_string_spill::(&mut spill, &input); assert_eq!(out, &expected[..]); assert!(!spill.is_empty()); } #[test] fn normalize_string_spill_reuses_the_spill_across_calls() { let long = vec![b'a'; PARSER_BUFFER_LEN + 1]; let longer = vec![b'c'; PARSER_BUFFER_LEN * 2]; let mut spill = Vec::new(); assert_eq!( normalize_string_spill::(&mut spill, &long), &long[..] ); assert_eq!( normalize_string_spill::(&mut spill, &longer), &longer[..] ); // A shorter input after a longer one must not leak the previous tail. assert_eq!( normalize_string_spill::(&mut spill, &long), &long[..] ); } #[test] fn join_abs_string_spill_leaves_spill_untouched_when_the_result_fits() { let mut spill = Vec::new(); let out = join_abs_string_spill::(b"/work", &mut spill, &[b"a/../b.json"]); assert_eq!(out, b"/work/b.json"); assert!(spill.is_empty()); } #[test] fn join_abs_string_spill_spills_a_part_longer_than_the_thread_local_buffer() { let name = vec![b'a'; PARSER_JOIN_INPUT_BUFFER_LEN + 1]; let mut expected = b"/work/".to_vec(); expected.extend_from_slice(&name); let mut spill = Vec::new(); let out = join_abs_string_spill::(b"/work", &mut spill, &[&name]); assert_eq!(out, &expected[..]); assert!(!spill.is_empty()); } #[test] fn join_abs_string_spill_spills_an_absolute_part_and_a_long_cwd_alike() { let mut abs = b"/".to_vec(); abs.resize(PARSER_JOIN_INPUT_BUFFER_LEN * 2, b'a'); let mut spill = Vec::new(); assert_eq!( join_abs_string_spill::(b"/", &mut spill, &[&abs]), &abs[..] ); let mut cwd = b"/".to_vec(); cwd.resize(PARSER_JOIN_INPUT_BUFFER_LEN, b'c'); let mut expected = cwd.clone(); expected.extend_from_slice(b"/x"); let mut spill = Vec::new(); assert_eq!( join_abs_string_spill::(&cwd, &mut spill, &[b"./x"]), &expected[..] ); } #[test] fn join_abs_string_spill_normalizes_a_long_part_that_collapses() { // `sub/../` repeated past the buffer size resolves back to the cwd. let mut part = Vec::new(); while part.len() <= PARSER_JOIN_INPUT_BUFFER_LEN { part.extend_from_slice(b"sub/../"); } part.extend_from_slice(b"sub"); let mut spill = Vec::new(); let out = join_abs_string_spill::(b"/work", &mut spill, &[&part]); assert_eq!(out, b"/work/sub"); } #[test] fn normalize_string_spill_accounts_for_outputs_that_grow_by_one_byte() { // A bare UNC volume exactly as long as the thread-local buffer // normalizes to one byte more than its input. let mut unc = b"\\\\".to_vec(); unc.resize(PARSER_BUFFER_LEN, b's'); let mut spill = Vec::new(); let out = normalize_string_spill::(&mut spill, &unc); assert_eq!(out.len(), PARSER_BUFFER_LEN + 1); assert_eq!(&out[..unc.len()], &unc[..]); assert_eq!(out[unc.len()], SEP_WINDOWS); let mut spill = Vec::new(); assert_eq!( normalize_string_spill::(&mut spill, b"c:"), b"C:." ); } #[test] fn normalize_buf_z_spill_spills_only_what_does_not_fit_with_its_nul() { let mut buf = [0u8; 32]; let mut spill = Vec::new(); let norm = |buf: &mut [u8], spill: &mut Vec, s: &[u8]| { normalize_buf_z_spill::(buf, spill, s) .as_bytes_with_nul() .to_vec() }; assert_eq!( norm(&mut buf, &mut spill, b"./bins/../cli/./x.js"), b"cli/x.js\0" ); assert_eq!(norm(&mut buf, &mut spill, b"./bins/"), b"bins/\0"); assert!(spill.is_empty()); let mut input = b"./".to_vec(); input.resize(2 + buf.len() * 3, b'b'); input.extend_from_slice(b"/./x.js"); let mut expected = vec![b'b'; buf.len() * 3]; expected.extend_from_slice(b"/x.js\0"); assert_eq!(norm(&mut buf, &mut spill, &input), expected); assert!(!spill.is_empty()); // Normalizes to exactly `buf.len()` bytes, leaving no room for the NUL. let mut spill = Vec::new(); let input = vec![b'a'; buf.len()]; assert_eq!(norm(&mut buf, &mut spill, &input)[..buf.len()], input[..]); assert!(!spill.is_empty()); // `""` grows to `.`. let mut spill = Vec::new(); assert_eq!(norm(&mut buf[..1], &mut spill, b""), b".\0"); assert_eq!(spill.len(), 2); } #[test] fn thread_local_helpers_return_results_longer_than_their_buffers() { let name = vec![b'a'; PARSER_JOIN_INPUT_BUFFER_LEN.max(JOIN_BUF_LEN)]; let path = [b"/work/", &name[..]].concat(); let path_z = [&path[..], b"\0"].concat(); assert_eq!( join_abs_string::(b"/work", &[b"x/..", &name]), &path[..] ); assert_eq!( join_abs_string_z::(b"/work", &[&name]).as_bytes_with_nul(), &path_z[..] ); assert_eq!(join::(&[b"/work", &name]), &path[..]); assert_eq!( join_z::(&[b"/work", &name]).as_bytes_with_nul(), &path_z[..] ); } #[test] fn a_long_result_stays_readable_after_a_longer_one() { let first_name = vec![b'a'; PARSER_JOIN_INPUT_BUFFER_LEN]; let second_name = vec![b'b'; PARSER_JOIN_INPUT_BUFFER_LEN * 4]; let first = join_abs_string::(b"/", &[&first_name]); let second = join_abs_string::(b"/", &[&second_name]); assert_eq!(&first[1..], &first_name[..]); assert_eq!(&second[1..], &second_name[..]); } /// Takes an address because borrowing the buffer ends the borrow of a result in it. fn is_in_fixed_relative_buffer(address: usize) -> bool { let start = relative_to_common_path_buf().addr(); (start..start + MAX_PATH_BYTES).contains(&address) } #[test] fn relative_uses_the_fixed_buffer_until_the_bound_of_the_result_exceeds_it() { let from = b"/a".repeat(100); let separators = relative_operand_separators::(b"", &from); let room = MAX_PATH_BYTES - relative_result_capacity(separators, 0); for (to_len, fits) in [(room, true), (room + 1, false)] { let mut to = b"/".to_vec(); to.resize(to_len, b'b'); let expected = [&b"../".repeat(100)[..], &to[1..]].concat(); let result = relative_platform::(&from, &to); assert_eq!(result, &expected[..]); assert_eq!(is_in_fixed_relative_buffer(result.as_ptr().addr()), fits); // Its operands are normalized, so it allows for one separator less. to.extend_from_slice(b"bbb"); let result = relative_normalized::(&from, &to); assert_eq!(result, &[&expected[..], b"bbb"].concat()[..]); assert_eq!(is_in_fixed_relative_buffer(result.as_ptr().addr()), fits); } } #[test] fn relative_uses_the_fixed_buffer_until_from_exceeds_it() { for (from_len, fits) in [(MAX_PATH_BYTES - 1, true), (MAX_PATH_BYTES, false)] { let mut from = b"/".to_vec(); from.resize(from_len, b'a'); let result = relative_platform::(&from, b"/b"); assert_eq!(result, b"../b"); assert_eq!(is_in_fixed_relative_buffer(result.as_ptr().addr()), fits); } } #[test] fn join_string_buf_skips_empty_parts_and_normalizes() { let mut out = [0u8; 64]; assert_eq!( join_string_buf::(&mut out, &[b"foo", b"", b"bar/../baz"]), b"foo/baz" ); assert_eq!( join_string_buf::(&mut out, &[b"", b""]), b"." ); } #[test] fn join_string_buf_spills_parts_longer_than_the_stack_scratch() { let long = vec![b'a'; JOIN_TEMP_LEN]; let mut expected = long.clone(); expected.extend_from_slice(b"/y"); let mut out = vec![0u8; JOIN_TEMP_LEN + 16]; assert_eq!( join_string_buf::(&mut out, &[&long, b"x/../y"]), &expected[..] ); } #[test] fn join_string_buf_w_same_skips_empty_parts_and_normalizes() { let foo: Vec = "foo".encode_utf16().collect(); let rest: Vec = "bar\\..\\baz".encode_utf16().collect(); let expected: Vec = "foo\\baz".encode_utf16().collect(); let mut out = [0u16; 64]; assert_eq!( join_string_buf_w_same::(&mut out, &[&foo, &[], &rest]), &expected[..] ); assert_eq!( join_string_buf_w_same::(&mut out, &[&[], &[]]), &[b'.' as u16][..] ); } #[test] fn join_string_buf_w_same_spills_parts_longer_than_the_stack_scratch() { let long = vec![b'a' as u16; JOIN_TEMP_LEN]; let rest: Vec = "x\\..\\y".encode_utf16().collect(); let mut expected = long.clone(); expected.extend_from_slice(&[b'\\' as u16, b'y' as u16]); let mut out = vec![0u16; JOIN_TEMP_LEN + 16]; assert_eq!( join_string_buf_w_same::(&mut out, &[&long, &rest]), &expected[..] ); } }