Make SanitizedPath wrap Path instead of Arc<Path> to avoid allocation (#37106)

Release Notes:

- N/A
This commit is contained in:
Michael Sloan
2025-08-28 13:32:30 -06:00
committed by GitHub
parent 69933d5b81
commit 47aaaa8bcf
14 changed files with 156 additions and 85 deletions
+90 -32
View File
@@ -3,6 +3,7 @@ use regex::Regex;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
use std::mem;
use std::path::StripPrefixError;
use std::sync::{Arc, OnceLock};
use std::{
@@ -99,21 +100,86 @@ impl<T: AsRef<Path>> PathExt for T {
}
}
/// Due to the issue of UNC paths on Windows, which can cause bugs in various parts of Zed, introducing this `SanitizedPath`
/// leverages Rust's type system to ensure that all paths entering Zed are always "sanitized" by removing the `\\\\?\\` prefix.
/// On non-Windows operating systems, this struct is effectively a no-op.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SanitizedPath(pub Arc<Path>);
/// In memory, this is identical to `Path`. On non-Windows conversions to this type are no-ops. On
/// windows, these conversions sanitize UNC paths by removing the `\\\\?\\` prefix.
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd)]
#[repr(transparent)]
pub struct SanitizedPath(Path);
impl SanitizedPath {
pub fn starts_with(&self, prefix: &SanitizedPath) -> bool {
pub fn new<T: AsRef<Path> + ?Sized>(path: &T) -> &Self {
#[cfg(not(target_os = "windows"))]
return Self::unchecked_new(path.as_ref());
#[cfg(target_os = "windows")]
return Self::unchecked_new(dunce::simplified(path.as_ref()));
}
pub fn unchecked_new<T: AsRef<Path> + ?Sized>(path: &T) -> &Self {
// safe because `Path` and `SanitizedPath` have the same repr and Drop impl
unsafe { mem::transmute::<&Path, &Self>(path.as_ref()) }
}
pub fn from_arc(path: Arc<Path>) -> Arc<Self> {
// safe because `Path` and `SanitizedPath` have the same repr and Drop impl
#[cfg(not(target_os = "windows"))]
return unsafe { mem::transmute::<Arc<Path>, Arc<Self>>(path) };
// TODO: could avoid allocating here if dunce::simplified results in the same path
#[cfg(target_os = "windows")]
return Self::new(&path).into();
}
pub fn new_arc<T: AsRef<Path> + ?Sized>(path: &T) -> Arc<Self> {
Self::new(path).into()
}
pub fn cast_arc(path: Arc<Self>) -> Arc<Path> {
// safe because `Path` and `SanitizedPath` have the same repr and Drop impl
unsafe { mem::transmute::<Arc<Self>, Arc<Path>>(path) }
}
pub fn cast_arc_ref(path: &Arc<Self>) -> &Arc<Path> {
// safe because `Path` and `SanitizedPath` have the same repr and Drop impl
unsafe { mem::transmute::<&Arc<Self>, &Arc<Path>>(path) }
}
pub fn starts_with(&self, prefix: &Self) -> bool {
self.0.starts_with(&prefix.0)
}
pub fn as_path(&self) -> &Arc<Path> {
pub fn as_path(&self) -> &Path {
&self.0
}
pub fn file_name(&self) -> Option<&std::ffi::OsStr> {
self.0.file_name()
}
pub fn extension(&self) -> Option<&std::ffi::OsStr> {
self.0.extension()
}
pub fn join<P: AsRef<Path>>(&self, path: P) -> PathBuf {
self.0.join(path)
}
pub fn parent(&self) -> Option<&Self> {
self.0.parent().map(Self::unchecked_new)
}
pub fn strip_prefix(&self, base: &Self) -> Result<&Path, StripPrefixError> {
self.0.strip_prefix(base.as_path())
}
pub fn to_str(&self) -> Option<&str> {
self.0.to_str()
}
pub fn to_path_buf(&self) -> PathBuf {
self.0.to_path_buf()
}
pub fn to_glob_string(&self) -> String {
#[cfg(target_os = "windows")]
{
@@ -124,13 +190,11 @@ impl SanitizedPath {
self.0.to_string_lossy().to_string()
}
}
}
pub fn join(&self, path: &Self) -> Self {
self.0.join(&path.0).into()
}
pub fn strip_prefix(&self, base: &Self) -> Result<&Path, StripPrefixError> {
self.0.strip_prefix(base.as_path())
impl std::fmt::Debug for SanitizedPath {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(&self.0, formatter)
}
}
@@ -140,29 +204,23 @@ impl Display for SanitizedPath {
}
}
impl From<SanitizedPath> for Arc<Path> {
fn from(sanitized_path: SanitizedPath) -> Self {
sanitized_path.0
impl From<&SanitizedPath> for Arc<SanitizedPath> {
fn from(sanitized_path: &SanitizedPath) -> Self {
let path: Arc<Path> = sanitized_path.0.into();
// safe because `Path` and `SanitizedPath` have the same repr and Drop impl
unsafe { mem::transmute(path) }
}
}
impl From<SanitizedPath> for PathBuf {
fn from(sanitized_path: SanitizedPath) -> Self {
sanitized_path.0.as_ref().into()
impl From<&SanitizedPath> for PathBuf {
fn from(sanitized_path: &SanitizedPath) -> Self {
sanitized_path.as_path().into()
}
}
impl<T: AsRef<Path>> From<T> for SanitizedPath {
#[cfg(not(target_os = "windows"))]
fn from(path: T) -> Self {
let path = path.as_ref();
SanitizedPath(path.into())
}
#[cfg(target_os = "windows")]
fn from(path: T) -> Self {
let path = path.as_ref();
SanitizedPath(dunce::simplified(path).into())
impl AsRef<Path> for SanitizedPath {
fn as_ref(&self) -> &Path {
&self.0
}
}
@@ -1195,14 +1253,14 @@ mod tests {
#[cfg(target_os = "windows")]
fn test_sanitized_path() {
let path = Path::new("C:\\Users\\someone\\test_file.rs");
let sanitized_path = SanitizedPath::from(path);
let sanitized_path = SanitizedPath::new(path);
assert_eq!(
sanitized_path.to_string(),
"C:\\Users\\someone\\test_file.rs"
);
let path = Path::new("\\\\?\\C:\\Users\\someone\\test_file.rs");
let sanitized_path = SanitizedPath::from(path);
let sanitized_path = SanitizedPath::new(path);
assert_eq!(
sanitized_path.to_string(),
"C:\\Users\\someone\\test_file.rs"