From fd5777c4c5c8fbc54d33d15afb29479180c532be Mon Sep 17 00:00:00 2001 From: schvv31n Date: Tue, 4 Jun 2024 11:53:59 +0100 Subject: [PATCH 1/2] impl OsString::leak & PathBuf::leak --- library/std/src/ffi/os_str.rs | 19 +++++++++++++++++++ library/std/src/ffi/os_str/tests.rs | 7 +++++++ library/std/src/path.rs | 19 +++++++++++++++++++ library/std/src/path/tests.rs | 7 +++++++ library/std/src/sys/os_str/bytes.rs | 5 +++++ library/std/src/sys/os_str/wtf8.rs | 5 +++++ library/std/src/sys_common/wtf8.rs | 5 +++++ 7 files changed, 67 insertions(+) diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs index 9dd3d7d3fa16a..f6b9de26c1c4d 100644 --- a/library/std/src/ffi/os_str.rs +++ b/library/std/src/ffi/os_str.rs @@ -533,6 +533,25 @@ impl OsString { unsafe { Box::from_raw(rw) } } + /// Consumes and leaks the `OsString`, returning a mutable reference to the contents, + /// `&'a mut OsStr`. + /// + /// The caller has free choice over the returned lifetime, including 'static. + /// Indeed, this function is ideally used for data that lives for the remainder of + /// the program’s life, as dropping the returned reference will cause a memory leak. + /// + /// It does not reallocate or shrink the `OsString`, so the leaked allocation may include + /// unused capacity that is not part of the returned slice. If you want to discard excess + /// capacity, call [`into_boxed_os_str`], and then [`Box::leak`] instead. + /// However, keep in mind that trimming the capacity may result in a reallocation and copy. + /// + /// [`into_boxed_os_str`]: Self::into_boxed_os_str + #[unstable(feature = "os_string_pathbuf_leak", issue = "125965")] + #[inline] + pub fn leak<'a>(self) -> &'a mut OsStr { + OsStr::from_inner_mut(self.inner.leak()) + } + /// Part of a hack to make PathBuf::push/pop more efficient. #[inline] pub(crate) fn as_mut_vec_for_path_buf(&mut self) -> &mut Vec { diff --git a/library/std/src/ffi/os_str/tests.rs b/library/std/src/ffi/os_str/tests.rs index b020e05eaab20..2379f0ba489e3 100644 --- a/library/std/src/ffi/os_str/tests.rs +++ b/library/std/src/ffi/os_str/tests.rs @@ -23,6 +23,13 @@ fn test_os_string_clear() { assert_eq!(0, os_string.inner.as_inner().len()); } +#[test] +fn test_os_string_leak() { + let os_string = OsString::from("have a cake"); + let leaked = os_string.leak(); + assert_eq!(leaked.as_encoded_bytes(), b"have a cake"); +} + #[test] fn test_os_string_capacity() { let os_string = OsString::with_capacity(0); diff --git a/library/std/src/path.rs b/library/std/src/path.rs index f4e1e2a38c106..adbcdcd2e67e3 100644 --- a/library/std/src/path.rs +++ b/library/std/src/path.rs @@ -1226,6 +1226,25 @@ impl PathBuf { self } + /// Consumes and leaks the `PathBuf`, returning a mutable reference to the contents, + /// `&'a mut Path`. + /// + /// The caller has free choice over the returned lifetime, including 'static. + /// Indeed, this function is ideally used for data that lives for the remainder of + /// the program’s life, as dropping the returned reference will cause a memory leak. + /// + /// It does not reallocate or shrink the `PathBuf`, so the leaked allocation may include + /// unused capacity that is not part of the returned slice. If you want to discard excess + /// capacity, call [`into_boxed_path`], and then [`Box::leak`] instead. + /// However, keep in mind that trimming the capacity may result in a reallocation and copy. + /// + /// [`into_boxed_path`]: Self::into_boxed_path + #[unstable(feature = "os_string_pathbuf_leak", issue = "125965")] + #[inline] + pub fn leak<'a>(self) -> &'a mut Path { + Path::from_inner_mut(self.inner.leak()) + } + /// Extends `self` with `path`. /// /// If `path` is absolute, it replaces the current path. diff --git a/library/std/src/path/tests.rs b/library/std/src/path/tests.rs index 2d8e50d1f88e9..d29f895ba38ff 100644 --- a/library/std/src/path/tests.rs +++ b/library/std/src/path/tests.rs @@ -126,6 +126,13 @@ fn into() { assert_eq!(static_cow_path, owned_cow_path); } +#[test] +fn test_pathbuf_leak() { + let buf = PathBuf::from("/have/a/cake".to_owned()); + let leaked = buf.leak(); + assert_eq!(leaked.as_os_str().as_encoded_bytes(), b"/have/a/cake"); +} + #[test] #[cfg(unix)] pub fn test_decompositions_unix() { diff --git a/library/std/src/sys/os_str/bytes.rs b/library/std/src/sys/os_str/bytes.rs index 18b969bca85a6..f7c6b0877aa62 100644 --- a/library/std/src/sys/os_str/bytes.rs +++ b/library/std/src/sys/os_str/bytes.rs @@ -176,6 +176,11 @@ impl Buf { self.inner.extend_from_slice(&s.inner) } + #[inline] + pub fn leak<'a>(self) -> &'a mut Slice { + unsafe { mem::transmute(self.inner.leak()) } + } + #[inline] pub fn into_box(self) -> Box { unsafe { mem::transmute(self.inner.into_boxed_slice()) } diff --git a/library/std/src/sys/os_str/wtf8.rs b/library/std/src/sys/os_str/wtf8.rs index b3ceb55802dc5..dfff4dd4fb023 100644 --- a/library/std/src/sys/os_str/wtf8.rs +++ b/library/std/src/sys/os_str/wtf8.rs @@ -138,6 +138,11 @@ impl Buf { self.inner.shrink_to(min_capacity) } + #[inline] + pub fn leak<'a>(self) -> &'a mut Slice { + unsafe { mem::transmute(self.inner.leak()) } + } + #[inline] pub fn into_box(self) -> Box { unsafe { mem::transmute(self.inner.into_box()) } diff --git a/library/std/src/sys_common/wtf8.rs b/library/std/src/sys_common/wtf8.rs index 38e15f9f54960..bb1e505285bf1 100644 --- a/library/std/src/sys_common/wtf8.rs +++ b/library/std/src/sys_common/wtf8.rs @@ -325,6 +325,11 @@ impl Wtf8Buf { self.bytes.shrink_to(min_capacity) } + #[inline] + pub fn leak<'a>(self) -> &'a mut Wtf8 { + unsafe { Wtf8::from_mut_bytes_unchecked(self.bytes.leak()) } + } + /// Returns the number of bytes that this string buffer can hold without reallocating. #[inline] pub fn capacity(&self) -> usize { From 2bdc53b153f16af9d81f8b68bdf27575ff82dc71 Mon Sep 17 00:00:00 2001 From: schvv31n Date: Thu, 6 Jun 2024 23:16:39 +0100 Subject: [PATCH 2/2] fixed memory leaks in PathBuf::leak & OsString::leak tests --- library/std/src/ffi/os_str/tests.rs | 2 ++ library/std/src/path/tests.rs | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/library/std/src/ffi/os_str/tests.rs b/library/std/src/ffi/os_str/tests.rs index 2379f0ba489e3..5b39b9e34d8c7 100644 --- a/library/std/src/ffi/os_str/tests.rs +++ b/library/std/src/ffi/os_str/tests.rs @@ -26,8 +26,10 @@ fn test_os_string_clear() { #[test] fn test_os_string_leak() { let os_string = OsString::from("have a cake"); + let (len, cap) = (os_string.len(), os_string.capacity()); let leaked = os_string.leak(); assert_eq!(leaked.as_encoded_bytes(), b"have a cake"); + unsafe { drop(String::from_raw_parts(leaked as *mut OsStr as _, len, cap)) } } #[test] diff --git a/library/std/src/path/tests.rs b/library/std/src/path/tests.rs index d29f895ba38ff..92702b395dfe1 100644 --- a/library/std/src/path/tests.rs +++ b/library/std/src/path/tests.rs @@ -128,9 +128,12 @@ fn into() { #[test] fn test_pathbuf_leak() { - let buf = PathBuf::from("/have/a/cake".to_owned()); + let string = "/have/a/cake".to_owned(); + let (len, cap) = (string.len(), string.capacity()); + let buf = PathBuf::from(string); let leaked = buf.leak(); assert_eq!(leaked.as_os_str().as_encoded_bytes(), b"/have/a/cake"); + unsafe { drop(String::from_raw_parts(leaked.as_mut_os_str() as *mut OsStr as _, len, cap)) } } #[test]