From ab1d5e4c539dcf8a1aec146e1a14a2dcefc58767 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Tue, 8 Sep 2026 23:16:54 +0000 Subject: [PATCH 1/2] Add more PathBuf::push tests for verbatim paths --- library/std/tests/path.rs | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/library/std/tests/path.rs b/library/std/tests/path.rs index 10bfe9eb2bd6c..e17196b79c8c1 100644 --- a/library/std/tests/path.rs +++ b/library/std/tests/path.rs @@ -1759,13 +1759,60 @@ pub fn test_push() { tp!(r"\\?\C:\bar", "../foo", r"\\?\C:\foo"); tp!(r"\\?\C:\bar", "../../foo", r"\\?\C:\foo"); + tp!(r"\\?\C:\foo\bar", "../baz", r"\\?\C:\foo\baz"); + tp!(r"\\?\C:\foo\bar\", "../baz", r"\\?\C:\foo\baz"); + tp!(r"\\?\C:\foo\bar\", "..", r"\\?\C:\foo"); + tp!(r"\\?\C:\foo", "..", r"\\?\C:\"); + tp!(r"\\?\C:\", "..", r"\\?\C:\"); + tp!(r"\\?\C:\foo\..", "..", r"\\?\C:\foo\.."); tp!(r"\\?\C:\", "../foo", r"\\?\C:\foo"); tp!(r"\\?\C:", r"D:\foo/./", r"D:\foo/./"); tp!(r"\\?\C:", r"\\?\D:\foo\.\", r"\\?\D:\foo\.\"); + tp!(r"\\?\C:", r"\..", r"\\?\C:\"); + tp!(r"\\?\C:", r"\\foo\\\..", r"\\?\C:\"); + tp!(r"\\?\C:", r"foo\foo\..\..", r"\\?\C:"); + tp!(r"\\?\C:\foo\..\..\bar", r"..\..\..\", r"\\?\C:\foo\..\.."); + tp!(r"\\?\C:\foo\..", r"..\bar", r"\\?\C:\foo\..\bar"); + tp!(r"\\?\C:\foo\..\..", r"..\bar", r"\\?\C:\foo\..\..\bar"); + tp!(r"\\?\C:\foo\..", r"..\..\..\bar", r"\\?\C:\foo\..\bar"); + tp!(r"\\?\C:\foo\..\", r"..\..\..\bar", r"\\?\C:\foo\..\bar"); + tp!(r"\\?\C:\foo\..\\", r"..", r"\\?\C:\foo\.."); + tp!(r"\\?\C:\foo\.", r"..", r"\\?\C:\foo\."); + tp!(r"\\?\C:\foo\..\", r"..", r"\\?\C:\foo\.."); + tp!(r"\\?\C:\foo\\\\bar", r"..", r"\\?\C:\foo"); + tp!(r"\\?\C:\foo\", r".", r"\\?\C:\foo"); + tp!(r"\\?\C:\foo\\", r"bar", r"\\?\C:\foo\bar"); + tp!(r"\\?\C:\foo\\\\bar", r"baz", r"\\?\C:\foo\bar\baz"); tp!(r"\\?\A:\x\y", "/foo", r"\\?\A:\foo"); tp!(r"\\?\A:", r"..\foo\.", r"\\?\A:\foo"); tp!(r"\\?\A:\x\y", r".\foo\.", r"\\?\A:\x\y\foo"); tp!(r"\\?\A:\x\y", r"", r"\\?\A:\x\y\"); + + tp!(r"\\?\UNC\server\share\foo", r"..", r"\\?\UNC\server\share\"); + tp!(r"\\?\UNC\server\share\", r"..", r"\\?\UNC\server\share\"); + tp!(r"\\?\UNC\server\share\foo\bar", "../baz", r"\\?\UNC\server\share\foo\baz"); + tp!(r"\\?\UNC\server\share\foo\bar\", "../baz", r"\\?\UNC\server\share\foo\baz"); + tp!(r"\\?\UNC\server\share\foo\bar\", "..", r"\\?\UNC\server\share\foo"); + tp!(r"\\?\UNC\server\share\foo", "..", r"\\?\UNC\server\share\"); + tp!(r"\\?\UNC\server\share\", "..", r"\\?\UNC\server\share\"); + tp!(r"\\?\UNC\server\share\foo\..", "..", r"\\?\UNC\server\share\foo\.."); + tp!(r"\\?\UNC\server\share\", "../foo", r"\\?\UNC\server\share\foo"); + tp!(r"\\?\UNC\server\share", r"D:\foo/./", r"D:\foo/./"); + tp!(r"\\?\UNC\server\share", r"\\?\D:\foo\.\", r"\\?\D:\foo\.\"); + tp!(r"\\?\UNC\server\share", r"\..", r"\\?\UNC\server\share\"); + + tp!(r"\\?\PIPE\foo", r"\", r"\\?\PIPE\"); + + // Empty verbatim prefix. + tp!(r"\\?\\foo", r"..", r"\\?\\"); + tp!(r"\\?\\foo", r"../..", r"\\?\\"); + tp!(r"\\?\\foo", r"\", r"\\?\\"); + tp!(r"\\?\\foo", r"../bar", r"\\?\\bar"); + + tp!(r"\\?\C:\foo\foo\/", r"foo", r"\\?\C:\foo\foo\/\foo"); + tp!(r"\\?\/", r"/", r"\\?\/\"); + tp!(r"\\?\foo/", r"/", r"\\?\foo/\"); + tp!(r"\\?\", r"foo\foo\..\..", r"\\?\"); } } From f782871ba267692ee4580209b7df5b42bbd457c2 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Wed, 9 Sep 2026 11:26:45 +0000 Subject: [PATCH 2/2] Optimize PathBuf::push for verbatim paths --- library/std/src/path.rs | 84 +++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/library/std/src/path.rs b/library/std/src/path.rs index f06625613ec00..8308a47040e6e 100644 --- a/library/std/src/path.rs +++ b/library/std/src/path.rs @@ -82,6 +82,7 @@ #![deny(unsafe_op_in_unsafe_fn)] use core::clone::CloneToUninit; +use core::mem; use crate::borrow::{Borrow, Cow}; use crate::collections::TryReserveError; @@ -1344,7 +1345,7 @@ impl PathBuf { let mut need_sep = buf.last().map(|c| !is_sep_byte(*c)).unwrap_or(false); // in the special case of `C:` on Windows, do *not* add a separator - let comps = self.components(); + let mut comps = self.components(); if comps.prefix_len() > 0 && comps.prefix_len() == comps.path.len() @@ -1367,45 +1368,70 @@ impl PathBuf { self.inner.clear(); // verbatim paths need . and .. removed - } else if comps.prefix_verbatim() && !path.inner.is_empty() { - let mut buf: Vec<_> = comps.collect(); + } else if comps.prefix_verbatim() && !path.is_empty() { + let mut prefix_len = comps.prefix_len(); + let has_root_dir = comps.nth(1) == Some(Component::RootDir); + + // collapse multiple verbatim separators in the base path. + let mut temp = mem::take(self); + let mut vector = temp.into_os_string().into_encoded_bytes(); + let mut skip = prefix_len; + let mut prev_sep = false; + vector.retain_mut(|&mut v| { + if skip > 0 { + skip -= 1; + return true; + } + let cur_sep = is_verbatim_sep(v); + let is_duplicate = prev_sep && cur_sep; + prev_sep = cur_sep; + !is_duplicate + }); + // SAFETY: we've only removed the platform's separator characters + temp = unsafe { OsString::from_encoded_bytes_unchecked(vector) }.into(); + *self = mem::take(&mut temp); + + if has_root_dir { + prefix_len += 1; + } for c in path.components() { match c { + // RootDir can only ever appear once. Component::RootDir => { - buf.truncate(1); - buf.push(c); + self.inner.truncate(prefix_len); + if !has_root_dir { + self.inner.push(MAIN_SEPARATOR_STR); + prefix_len += 1; + } } - Component::CurDir => (), + Component::CurDir => self.pop_trailing_sep(), Component::ParentDir => { - if let Some(Component::Normal(_)) = buf.last() { - buf.pop(); + let mut components = self.components(); + // Preserve pre-existing behaviour of stopping at any non-normal component in the base path. + match components.next_back() { + Some(Component::Normal(_)) => { + self.pop(); + if self.components().next_back() == Some(Component::RootDir) { + self.inner.truncate(prefix_len); + } + } + Some(Component::CurDir | Component::ParentDir) => { + self.pop_trailing_sep() + } + _ => {} } } - _ => buf.push(c), - } - } - - let mut res = OsString::new(); - let mut need_sep = false; - - for c in buf { - if need_sep && c != Component::RootDir { - res.push(MAIN_SEPARATOR_STR); - } - res.push(c.as_os_str()); - - need_sep = match c { - Component::RootDir => false, - Component::Prefix(prefix) => { - !prefix.parsed.is_drive() && prefix.parsed.len() > 0 + _ => { + // FIXME: We can't use `push_trailing_sep` here due to a bug + // that treats `/` as a separator when pushing. + if !self.has_trailing_sep() || self == r"\\?\" { + self.inner.push(MAIN_SEPARATOR_STR); + } + self.inner.push(c); } - _ => true, } } - - self.inner = res; return; - // `path` has a root but no prefix, e.g., `\windows` (Windows only) } else if path.has_root() { let prefix_len = self.components().prefix_remaining();