about summary refs log tree commit diff
path: root/library/std/src
diff options
context:
space:
mode:
Diffstat (limited to 'library/std/src')
-rw-r--r--library/std/src/env.rs3
-rw-r--r--library/std/src/ffi/mod.rs7
-rw-r--r--library/std/src/ffi/os_str.rs43
-rw-r--r--library/std/src/ffi/os_str/tests.rs68
-rw-r--r--library/std/src/fs.rs18
-rw-r--r--library/std/src/os/unix/fs.rs1
-rw-r--r--library/std/src/os/wasi/fs.rs14
-rw-r--r--library/std/src/panicking.rs5
-rw-r--r--library/std/src/sys/os_str/bytes.rs43
-rw-r--r--library/std/src/sys/os_str/wtf8.rs7
-rw-r--r--library/std/src/sys/pal/common/small_c_string.rs29
-rw-r--r--library/std/src/sys/pal/common/tests.rs12
-rw-r--r--library/std/src/sys/pal/common/thread_local/fast_local.rs5
-rw-r--r--library/std/src/sys/pal/common/thread_local/static_local.rs5
-rw-r--r--library/std/src/sys/pal/hermit/fs.rs4
-rw-r--r--library/std/src/sys/pal/solid/os.rs8
-rw-r--r--library/std/src/sys/pal/unix/fs.rs42
-rw-r--r--library/std/src/sys/pal/unix/os.rs10
-rw-r--r--library/std/src/sys/pal/wasi/fs.rs2
-rw-r--r--library/std/src/sys/pal/wasi/os.rs10
-rw-r--r--library/std/src/sys_common/net.rs2
-rw-r--r--library/std/src/sys_common/wtf8.rs36
-rw-r--r--library/std/src/sys_common/wtf8/tests.rs62
-rw-r--r--library/std/src/thread/local.rs4
24 files changed, 323 insertions, 117 deletions
diff --git a/library/std/src/env.rs b/library/std/src/env.rs
index 30ac0512348..5bd20ebe208 100644
--- a/library/std/src/env.rs
+++ b/library/std/src/env.rs
@@ -78,7 +78,7 @@ pub fn current_dir() -> io::Result<PathBuf> {
 /// assert!(env::set_current_dir(&root).is_ok());
 /// println!("Successfully changed working directory to {}!", root.display());
 /// ```
-#[doc(alias = "chdir")]
+#[doc(alias = "chdir", alias = "SetCurrentDirectory", alias = "SetCurrentDirectoryW")]
 #[stable(feature = "env", since = "1.0.0")]
 pub fn set_current_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
     os_imp::chdir(path.as_ref())
@@ -655,6 +655,7 @@ pub fn home_dir() -> Option<PathBuf> {
 /// }
 /// ```
 #[must_use]
+#[doc(alias = "GetTempPath", alias = "GetTempPath2")]
 #[stable(feature = "env", since = "1.0.0")]
 pub fn temp_dir() -> PathBuf {
     os_imp::temp_dir()
diff --git a/library/std/src/ffi/mod.rs b/library/std/src/ffi/mod.rs
index 97e78d17786..818571ddaaa 100644
--- a/library/std/src/ffi/mod.rs
+++ b/library/std/src/ffi/mod.rs
@@ -127,6 +127,11 @@
 //! trait, which provides a [`from_wide`] method to convert a native Windows
 //! string (without the terminating nul character) to an [`OsString`].
 //!
+//! ## Other platforms
+//!
+//! Many other platforms provide their own extension traits in a
+//! `std::os::*::ffi` module.
+//!
 //! ## On all platforms
 //!
 //! On all platforms, [`OsStr`] consists of a sequence of bytes that is encoded as a superset of
@@ -135,6 +140,8 @@
 //! For limited, inexpensive conversions from and to bytes, see [`OsStr::as_encoded_bytes`] and
 //! [`OsStr::from_encoded_bytes_unchecked`].
 //!
+//! For basic string processing, see [`OsStr::slice_encoded_bytes`].
+//!
 //! [Unicode scalar value]: https://www.unicode.org/glossary/#unicode_scalar_value
 //! [Unicode code point]: https://www.unicode.org/glossary/#code_point
 //! [`env::set_var()`]: crate::env::set_var "env::set_var"
diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs
index 81973182148..28747ad8f34 100644
--- a/library/std/src/ffi/os_str.rs
+++ b/library/std/src/ffi/os_str.rs
@@ -9,7 +9,7 @@ use crate::hash::{Hash, Hasher};
 use crate::ops::{self, Range};
 use crate::rc::Rc;
 use crate::slice;
-use crate::str::{from_utf8 as str_from_utf8, FromStr};
+use crate::str::FromStr;
 use crate::sync::Arc;
 
 use crate::sys::os_str::{Buf, Slice};
@@ -997,42 +997,15 @@ impl OsStr {
     /// ```
     #[unstable(feature = "os_str_slice", issue = "118485")]
     pub fn slice_encoded_bytes<R: ops::RangeBounds<usize>>(&self, range: R) -> &Self {
-        #[track_caller]
-        fn check_valid_boundary(bytes: &[u8], index: usize) {
-            if index == 0 || index == bytes.len() {
-                return;
-            }
-
-            // Fast path
-            if bytes[index - 1].is_ascii() || bytes[index].is_ascii() {
-                return;
-            }
-
-            let (before, after) = bytes.split_at(index);
-
-            // UTF-8 takes at most 4 bytes per codepoint, so we don't
-            // need to check more than that.
-            let after = after.get(..4).unwrap_or(after);
-            match str_from_utf8(after) {
-                Ok(_) => return,
-                Err(err) if err.valid_up_to() != 0 => return,
-                Err(_) => (),
-            }
-
-            for len in 2..=4.min(index) {
-                let before = &before[index - len..];
-                if str_from_utf8(before).is_ok() {
-                    return;
-                }
-            }
-
-            panic!("byte index {index} is not an OsStr boundary");
-        }
-
         let encoded_bytes = self.as_encoded_bytes();
         let Range { start, end } = slice::range(range, ..encoded_bytes.len());
-        check_valid_boundary(encoded_bytes, start);
-        check_valid_boundary(encoded_bytes, end);
+
+        // `check_public_boundary` should panic if the index does not lie on an
+        // `OsStr` boundary as described above. It's possible to do this in an
+        // encoding-agnostic way, but details of the internal encoding might
+        // permit a more efficient implementation.
+        self.inner.check_public_boundary(start);
+        self.inner.check_public_boundary(end);
 
         // SAFETY: `slice::range` ensures that `start` and `end` are valid
         let slice = unsafe { encoded_bytes.get_unchecked(start..end) };
diff --git a/library/std/src/ffi/os_str/tests.rs b/library/std/src/ffi/os_str/tests.rs
index 60cde376d32..b020e05eaab 100644
--- a/library/std/src/ffi/os_str/tests.rs
+++ b/library/std/src/ffi/os_str/tests.rs
@@ -194,15 +194,65 @@ fn slice_encoded_bytes() {
 }
 
 #[test]
-#[should_panic(expected = "byte index 2 is not an OsStr boundary")]
+#[should_panic]
+fn slice_out_of_bounds() {
+    let crab = OsStr::new("🦀");
+    let _ = crab.slice_encoded_bytes(..5);
+}
+
+#[test]
+#[should_panic]
 fn slice_mid_char() {
     let crab = OsStr::new("🦀");
     let _ = crab.slice_encoded_bytes(..2);
 }
 
+#[cfg(unix)]
+#[test]
+#[should_panic(expected = "byte index 1 is not an OsStr boundary")]
+fn slice_invalid_data() {
+    use crate::os::unix::ffi::OsStrExt;
+
+    let os_string = OsStr::from_bytes(b"\xFF\xFF");
+    let _ = os_string.slice_encoded_bytes(1..);
+}
+
+#[cfg(unix)]
+#[test]
+#[should_panic(expected = "byte index 1 is not an OsStr boundary")]
+fn slice_partial_utf8() {
+    use crate::os::unix::ffi::{OsStrExt, OsStringExt};
+
+    let part_crab = OsStr::from_bytes(&"🦀".as_bytes()[..3]);
+    let mut os_string = OsString::from_vec(vec![0xFF]);
+    os_string.push(part_crab);
+    let _ = os_string.slice_encoded_bytes(1..);
+}
+
+#[cfg(unix)]
+#[test]
+fn slice_invalid_edge() {
+    use crate::os::unix::ffi::{OsStrExt, OsStringExt};
+
+    let os_string = OsStr::from_bytes(b"a\xFFa");
+    assert_eq!(os_string.slice_encoded_bytes(..1), "a");
+    assert_eq!(os_string.slice_encoded_bytes(1..), OsStr::from_bytes(b"\xFFa"));
+    assert_eq!(os_string.slice_encoded_bytes(..2), OsStr::from_bytes(b"a\xFF"));
+    assert_eq!(os_string.slice_encoded_bytes(2..), "a");
+
+    let os_string = OsStr::from_bytes(&"abc🦀".as_bytes()[..6]);
+    assert_eq!(os_string.slice_encoded_bytes(..3), "abc");
+    assert_eq!(os_string.slice_encoded_bytes(3..), OsStr::from_bytes(b"\xF0\x9F\xA6"));
+
+    let mut os_string = OsString::from_vec(vec![0xFF]);
+    os_string.push("🦀");
+    assert_eq!(os_string.slice_encoded_bytes(..1), OsStr::from_bytes(b"\xFF"));
+    assert_eq!(os_string.slice_encoded_bytes(1..), "🦀");
+}
+
 #[cfg(windows)]
 #[test]
-#[should_panic(expected = "byte index 3 is not an OsStr boundary")]
+#[should_panic(expected = "byte index 3 lies between surrogate codepoints")]
 fn slice_between_surrogates() {
     use crate::os::windows::ffi::OsStringExt;
 
@@ -216,10 +266,14 @@ fn slice_between_surrogates() {
 fn slice_surrogate_edge() {
     use crate::os::windows::ffi::OsStringExt;
 
-    let os_string = OsString::from_wide(&[0xD800]);
-    let mut with_crab = os_string.clone();
-    with_crab.push("🦀");
+    let surrogate = OsString::from_wide(&[0xD800]);
+    let mut pre_crab = surrogate.clone();
+    pre_crab.push("🦀");
+    assert_eq!(pre_crab.slice_encoded_bytes(..3), surrogate);
+    assert_eq!(pre_crab.slice_encoded_bytes(3..), "🦀");
 
-    assert_eq!(with_crab.slice_encoded_bytes(..3), os_string);
-    assert_eq!(with_crab.slice_encoded_bytes(3..), "🦀");
+    let mut post_crab = OsString::from("🦀");
+    post_crab.push(&surrogate);
+    assert_eq!(post_crab.slice_encoded_bytes(..4), "🦀");
+    assert_eq!(post_crab.slice_encoded_bytes(4..), surrogate);
 }
diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs
index db8de1b1e3d..6b1dd1b5af4 100644
--- a/library/std/src/fs.rs
+++ b/library/std/src/fs.rs
@@ -656,6 +656,7 @@ impl File {
     ///
     /// Note that this method alters the permissions of the underlying file,
     /// even though it takes `&self` rather than `&mut self`.
+    #[doc(alias = "fchmod", alias = "SetFileInformationByHandle")]
     #[stable(feature = "set_permissions_atomic", since = "1.16.0")]
     pub fn set_permissions(&self, perm: Permissions) -> io::Result<()> {
         self.inner.set_permissions(perm.0)
@@ -1314,6 +1315,7 @@ impl Metadata {
     ///     Ok(())
     /// }
     /// ```
+    #[doc(alias = "mtime", alias = "ftLastWriteTime")]
     #[stable(feature = "fs_time", since = "1.10.0")]
     pub fn modified(&self) -> io::Result<SystemTime> {
         self.0.modified().map(FromInner::from_inner)
@@ -1349,6 +1351,7 @@ impl Metadata {
     ///     Ok(())
     /// }
     /// ```
+    #[doc(alias = "atime", alias = "ftLastAccessTime")]
     #[stable(feature = "fs_time", since = "1.10.0")]
     pub fn accessed(&self) -> io::Result<SystemTime> {
         self.0.accessed().map(FromInner::from_inner)
@@ -1381,6 +1384,7 @@ impl Metadata {
     ///     Ok(())
     /// }
     /// ```
+    #[doc(alias = "btime", alias = "birthtime", alias = "ftCreationTime")]
     #[stable(feature = "fs_time", since = "1.10.0")]
     pub fn created(&self) -> io::Result<SystemTime> {
         self.0.created().map(FromInner::from_inner)
@@ -1879,6 +1883,7 @@ impl AsInner<fs_imp::DirEntry> for DirEntry {
 ///     Ok(())
 /// }
 /// ```
+#[doc(alias = "rm", alias = "unlink", alias = "DeleteFile")]
 #[stable(feature = "rust1", since = "1.0.0")]
 pub fn remove_file<P: AsRef<Path>>(path: P) -> io::Result<()> {
     fs_imp::unlink(path.as_ref())
@@ -1917,6 +1922,7 @@ pub fn remove_file<P: AsRef<Path>>(path: P) -> io::Result<()> {
 ///     Ok(())
 /// }
 /// ```
+#[doc(alias = "stat")]
 #[stable(feature = "rust1", since = "1.0.0")]
 pub fn metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> {
     fs_imp::stat(path.as_ref()).map(Metadata)
@@ -1951,6 +1957,7 @@ pub fn metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> {
 ///     Ok(())
 /// }
 /// ```
+#[doc(alias = "lstat")]
 #[stable(feature = "symlink_metadata", since = "1.1.0")]
 pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> {
     fs_imp::lstat(path.as_ref()).map(Metadata)
@@ -1994,6 +2001,7 @@ pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> {
 ///     Ok(())
 /// }
 /// ```
+#[doc(alias = "mv", alias = "MoveFile", alias = "MoveFileEx")]
 #[stable(feature = "rust1", since = "1.0.0")]
 pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
     fs_imp::rename(from.as_ref(), to.as_ref())
@@ -2052,6 +2060,9 @@ pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()>
 ///     Ok(())
 /// }
 /// ```
+#[doc(alias = "cp")]
+#[doc(alias = "CopyFile", alias = "CopyFileEx")]
+#[doc(alias = "fclonefileat", alias = "fcopyfile")]
 #[stable(feature = "rust1", since = "1.0.0")]
 pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<u64> {
     fs_imp::copy(from.as_ref(), to.as_ref())
@@ -2096,6 +2107,7 @@ pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<u64> {
 ///     Ok(())
 /// }
 /// ```
+#[doc(alias = "CreateHardLink", alias = "linkat")]
 #[stable(feature = "rust1", since = "1.0.0")]
 pub fn hard_link<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io::Result<()> {
     fs_imp::link(original.as_ref(), link.as_ref())
@@ -2245,7 +2257,7 @@ pub fn canonicalize<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
 ///     Ok(())
 /// }
 /// ```
-#[doc(alias = "mkdir")]
+#[doc(alias = "mkdir", alias = "CreateDirectory")]
 #[stable(feature = "rust1", since = "1.0.0")]
 #[cfg_attr(not(test), rustc_diagnostic_item = "fs_create_dir")]
 pub fn create_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
@@ -2326,7 +2338,7 @@ pub fn create_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
 ///     Ok(())
 /// }
 /// ```
-#[doc(alias = "rmdir")]
+#[doc(alias = "rmdir", alias = "RemoveDirectory")]
 #[stable(feature = "rust1", since = "1.0.0")]
 pub fn remove_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
     fs_imp::rmdir(path.as_ref())
@@ -2449,6 +2461,7 @@ pub fn remove_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
 ///     Ok(())
 /// }
 /// ```
+#[doc(alias = "ls", alias = "opendir", alias = "FindFirstFile", alias = "FindNextFile")]
 #[stable(feature = "rust1", since = "1.0.0")]
 pub fn read_dir<P: AsRef<Path>>(path: P) -> io::Result<ReadDir> {
     fs_imp::readdir(path.as_ref()).map(ReadDir)
@@ -2484,6 +2497,7 @@ pub fn read_dir<P: AsRef<Path>>(path: P) -> io::Result<ReadDir> {
 ///     Ok(())
 /// }
 /// ```
+#[doc(alias = "chmod", alias = "SetFileAttributes")]
 #[stable(feature = "set_permissions", since = "1.1.0")]
 pub fn set_permissions<P: AsRef<Path>>(path: P, perm: Permissions) -> io::Result<()> {
     fs_imp::set_perm(path.as_ref(), perm.0)
diff --git a/library/std/src/os/unix/fs.rs b/library/std/src/os/unix/fs.rs
index e995d5133f8..058e9b90cc7 100644
--- a/library/std/src/os/unix/fs.rs
+++ b/library/std/src/os/unix/fs.rs
@@ -397,7 +397,6 @@ pub trait OpenOptionsExt {
     ///
     /// ```no_run
     /// # #![feature(rustc_private)]
-    /// use libc;
     /// use std::fs::OpenOptions;
     /// use std::os::unix::fs::OpenOptionsExt;
     ///
diff --git a/library/std/src/os/wasi/fs.rs b/library/std/src/os/wasi/fs.rs
index 3da8c835511..4525c3aa914 100644
--- a/library/std/src/os/wasi/fs.rs
+++ b/library/std/src/os/wasi/fs.rs
@@ -173,51 +173,61 @@ pub trait FileExt {
     ///
     /// This corresponds to the `fd_tell` syscall and is similar to
     /// `seek` where you offset 0 bytes from the current position.
+    #[doc(alias = "fd_tell")]
     fn tell(&self) -> io::Result<u64>;
 
     /// Adjust the flags associated with this file.
     ///
     /// This corresponds to the `fd_fdstat_set_flags` syscall.
+    #[doc(alias = "fd_fdstat_set_flags")]
     fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>;
 
     /// Adjust the rights associated with this file.
     ///
     /// This corresponds to the `fd_fdstat_set_rights` syscall.
+    #[doc(alias = "fd_fdstat_set_rights")]
     fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>;
 
     /// Provide file advisory information on a file descriptor.
     ///
     /// This corresponds to the `fd_advise` syscall.
+    #[doc(alias = "fd_advise")]
     fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()>;
 
     /// Force the allocation of space in a file.
     ///
     /// This corresponds to the `fd_allocate` syscall.
+    #[doc(alias = "fd_allocate")]
     fn allocate(&self, offset: u64, len: u64) -> io::Result<()>;
 
     /// Create a directory.
     ///
     /// This corresponds to the `path_create_directory` syscall.
+    #[doc(alias = "path_create_directory")]
     fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()>;
 
     /// Read the contents of a symbolic link.
     ///
     /// This corresponds to the `path_readlink` syscall.
+    #[doc(alias = "path_readlink")]
     fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf>;
 
     /// Return the attributes of a file or directory.
     ///
     /// This corresponds to the `path_filestat_get` syscall.
+    #[doc(alias = "path_filestat_get")]
     fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata>;
 
     /// Unlink a file.
     ///
     /// This corresponds to the `path_unlink_file` syscall.
+    #[doc(alias = "path_unlink_file")]
     fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
 
     /// Remove a directory.
     ///
     /// This corresponds to the `path_remove_directory` syscall.
+    #[doc(alias = "path_remove_directory")]
     fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
 }
 
@@ -359,6 +369,7 @@ pub trait OpenOptionsExt {
     /// Open a file or directory.
     ///
     /// This corresponds to the `path_open` syscall.
+    #[doc(alias = "path_open")]
     fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File>;
 }
 
@@ -500,6 +511,7 @@ impl DirEntryExt for fs::DirEntry {
 /// Create a hard link.
 ///
 /// This corresponds to the `path_link` syscall.
+#[doc(alias = "path_link")]
 pub fn link<P: AsRef<Path>, U: AsRef<Path>>(
     old_fd: &File,
     old_flags: u32,
@@ -518,6 +530,7 @@ pub fn link<P: AsRef<Path>, U: AsRef<Path>>(
 /// Rename a file or directory.
 ///
 /// This corresponds to the `path_rename` syscall.
+#[doc(alias = "path_rename")]
 pub fn rename<P: AsRef<Path>, U: AsRef<Path>>(
     old_fd: &File,
     old_path: P,
@@ -534,6 +547,7 @@ pub fn rename<P: AsRef<Path>, U: AsRef<Path>>(
 /// Create a symbolic link.
 ///
 /// This corresponds to the `path_symlink` syscall.
+#[doc(alias = "path_symlink")]
 pub fn symlink<P: AsRef<Path>, U: AsRef<Path>>(
     old_path: P,
     fd: &File,
diff --git a/library/std/src/panicking.rs b/library/std/src/panicking.rs
index 8294160b72c..c8306c1b597 100644
--- a/library/std/src/panicking.rs
+++ b/library/std/src/panicking.rs
@@ -337,8 +337,9 @@ pub mod panic_count {
 #[doc(hidden)]
 #[cfg(not(feature = "panic_immediate_abort"))]
 #[unstable(feature = "update_panic_count", issue = "none")]
-// FIXME: Use `SyncUnsafeCell` instead of allowing `static_mut_ref` lint
-#[allow(static_mut_ref)]
+// FIXME: Use `SyncUnsafeCell` instead of allowing `static_mut_refs` lint
+#[cfg_attr(bootstrap, allow(static_mut_ref))]
+#[cfg_attr(not(bootstrap), allow(static_mut_refs))]
 pub mod panic_count {
     use crate::cell::Cell;
     use crate::sync::atomic::{AtomicUsize, Ordering};
diff --git a/library/std/src/sys/os_str/bytes.rs b/library/std/src/sys/os_str/bytes.rs
index 3a75ce9ebb7..4ca3f1cd185 100644
--- a/library/std/src/sys/os_str/bytes.rs
+++ b/library/std/src/sys/os_str/bytes.rs
@@ -211,6 +211,49 @@ impl Slice {
         unsafe { mem::transmute(s) }
     }
 
+    #[track_caller]
+    #[inline]
+    pub fn check_public_boundary(&self, index: usize) {
+        if index == 0 || index == self.inner.len() {
+            return;
+        }
+        if index < self.inner.len()
+            && (self.inner[index - 1].is_ascii() || self.inner[index].is_ascii())
+        {
+            return;
+        }
+
+        slow_path(&self.inner, index);
+
+        /// We're betting that typical splits will involve an ASCII character.
+        ///
+        /// Putting the expensive checks in a separate function generates notably
+        /// better assembly.
+        #[track_caller]
+        #[inline(never)]
+        fn slow_path(bytes: &[u8], index: usize) {
+            let (before, after) = bytes.split_at(index);
+
+            // UTF-8 takes at most 4 bytes per codepoint, so we don't
+            // need to check more than that.
+            let after = after.get(..4).unwrap_or(after);
+            match str::from_utf8(after) {
+                Ok(_) => return,
+                Err(err) if err.valid_up_to() != 0 => return,
+                Err(_) => (),
+            }
+
+            for len in 2..=4.min(index) {
+                let before = &before[index - len..];
+                if str::from_utf8(before).is_ok() {
+                    return;
+                }
+            }
+
+            panic!("byte index {index} is not an OsStr boundary");
+        }
+    }
+
     #[inline]
     pub fn from_str(s: &str) -> &Slice {
         unsafe { Slice::from_encoded_bytes_unchecked(s.as_bytes()) }
diff --git a/library/std/src/sys/os_str/wtf8.rs b/library/std/src/sys/os_str/wtf8.rs
index 237854fac4e..352bd735903 100644
--- a/library/std/src/sys/os_str/wtf8.rs
+++ b/library/std/src/sys/os_str/wtf8.rs
@@ -6,7 +6,7 @@ use crate::fmt;
 use crate::mem;
 use crate::rc::Rc;
 use crate::sync::Arc;
-use crate::sys_common::wtf8::{Wtf8, Wtf8Buf};
+use crate::sys_common::wtf8::{check_utf8_boundary, Wtf8, Wtf8Buf};
 use crate::sys_common::{AsInner, FromInner, IntoInner};
 
 #[derive(Clone, Hash)]
@@ -171,6 +171,11 @@ impl Slice {
         mem::transmute(Wtf8::from_bytes_unchecked(s))
     }
 
+    #[track_caller]
+    pub fn check_public_boundary(&self, index: usize) {
+        check_utf8_boundary(&self.inner, index);
+    }
+
     #[inline]
     pub fn from_str(s: &str) -> &Slice {
         unsafe { mem::transmute(Wtf8::from_str(s)) }
diff --git a/library/std/src/sys/pal/common/small_c_string.rs b/library/std/src/sys/pal/common/small_c_string.rs
index af9b18e372d..37812fc0659 100644
--- a/library/std/src/sys/pal/common/small_c_string.rs
+++ b/library/std/src/sys/pal/common/small_c_string.rs
@@ -15,22 +15,28 @@ const NUL_ERR: io::Error =
     io::const_io_error!(io::ErrorKind::InvalidInput, "file name contained an unexpected NUL byte");
 
 #[inline]
-pub fn run_path_with_cstr<T, F>(path: &Path, f: F) -> io::Result<T>
-where
-    F: FnOnce(&CStr) -> io::Result<T>,
-{
+pub fn run_path_with_cstr<T>(path: &Path, f: &dyn Fn(&CStr) -> io::Result<T>) -> io::Result<T> {
     run_with_cstr(path.as_os_str().as_encoded_bytes(), f)
 }
 
 #[inline]
-pub fn run_with_cstr<T, F>(bytes: &[u8], f: F) -> io::Result<T>
-where
-    F: FnOnce(&CStr) -> io::Result<T>,
-{
+pub fn run_with_cstr<T>(bytes: &[u8], f: &dyn Fn(&CStr) -> io::Result<T>) -> io::Result<T> {
+    // Dispatch and dyn erase the closure type to prevent mono bloat.
+    // See https://github.com/rust-lang/rust/pull/121101.
     if bytes.len() >= MAX_STACK_ALLOCATION {
-        return run_with_cstr_allocating(bytes, f);
+        run_with_cstr_allocating(bytes, f)
+    } else {
+        unsafe { run_with_cstr_stack(bytes, f) }
     }
+}
 
+/// # Safety
+///
+/// `bytes` must have a length less than `MAX_STACK_ALLOCATION`.
+unsafe fn run_with_cstr_stack<T>(
+    bytes: &[u8],
+    f: &dyn Fn(&CStr) -> io::Result<T>,
+) -> io::Result<T> {
     let mut buf = MaybeUninit::<[u8; MAX_STACK_ALLOCATION]>::uninit();
     let buf_ptr = buf.as_mut_ptr() as *mut u8;
 
@@ -47,10 +53,7 @@ where
 
 #[cold]
 #[inline(never)]
-fn run_with_cstr_allocating<T, F>(bytes: &[u8], f: F) -> io::Result<T>
-where
-    F: FnOnce(&CStr) -> io::Result<T>,
-{
+fn run_with_cstr_allocating<T>(bytes: &[u8], f: &dyn Fn(&CStr) -> io::Result<T>) -> io::Result<T> {
     match CString::new(bytes) {
         Ok(s) => f(&s),
         Err(_) => Err(NUL_ERR),
diff --git a/library/std/src/sys/pal/common/tests.rs b/library/std/src/sys/pal/common/tests.rs
index 32dc18ee1cf..e72d02203da 100644
--- a/library/std/src/sys/pal/common/tests.rs
+++ b/library/std/src/sys/pal/common/tests.rs
@@ -7,7 +7,7 @@ use core::iter::repeat;
 #[test]
 fn stack_allocation_works() {
     let path = Path::new("abc");
-    let result = run_path_with_cstr(path, |p| {
+    let result = run_path_with_cstr(path, &|p| {
         assert_eq!(p, &*CString::new(path.as_os_str().as_encoded_bytes()).unwrap());
         Ok(42)
     });
@@ -17,14 +17,14 @@ fn stack_allocation_works() {
 #[test]
 fn stack_allocation_fails() {
     let path = Path::new("ab\0");
-    assert!(run_path_with_cstr::<(), _>(path, |_| unreachable!()).is_err());
+    assert!(run_path_with_cstr::<()>(path, &|_| unreachable!()).is_err());
 }
 
 #[test]
 fn heap_allocation_works() {
     let path = repeat("a").take(384).collect::<String>();
     let path = Path::new(&path);
-    let result = run_path_with_cstr(path, |p| {
+    let result = run_path_with_cstr(path, &|p| {
         assert_eq!(p, &*CString::new(path.as_os_str().as_encoded_bytes()).unwrap());
         Ok(42)
     });
@@ -36,7 +36,7 @@ fn heap_allocation_fails() {
     let mut path = repeat("a").take(384).collect::<String>();
     path.push('\0');
     let path = Path::new(&path);
-    assert!(run_path_with_cstr::<(), _>(path, |_| unreachable!()).is_err());
+    assert!(run_path_with_cstr::<()>(path, &|_| unreachable!()).is_err());
 }
 
 #[bench]
@@ -44,7 +44,7 @@ fn bench_stack_path_alloc(b: &mut test::Bencher) {
     let path = repeat("a").take(383).collect::<String>();
     let p = Path::new(&path);
     b.iter(|| {
-        run_path_with_cstr(p, |cstr| {
+        run_path_with_cstr(p, &|cstr| {
             black_box(cstr);
             Ok(())
         })
@@ -57,7 +57,7 @@ fn bench_heap_path_alloc(b: &mut test::Bencher) {
     let path = repeat("a").take(384).collect::<String>();
     let p = Path::new(&path);
     b.iter(|| {
-        run_path_with_cstr(p, |cstr| {
+        run_path_with_cstr(p, &|cstr| {
             black_box(cstr);
             Ok(())
         })
diff --git a/library/std/src/sys/pal/common/thread_local/fast_local.rs b/library/std/src/sys/pal/common/thread_local/fast_local.rs
index 04c0dd6f750..646dcd7f3a3 100644
--- a/library/std/src/sys/pal/common/thread_local/fast_local.rs
+++ b/library/std/src/sys/pal/common/thread_local/fast_local.rs
@@ -13,8 +13,9 @@ pub macro thread_local_inner {
     (@key $t:ty, const $init:expr) => {{
         #[inline]
         #[deny(unsafe_op_in_unsafe_fn)]
-        // FIXME: Use `SyncUnsafeCell` instead of allowing `static_mut_ref` lint
-        #[allow(static_mut_ref)]
+        // FIXME: Use `SyncUnsafeCell` instead of allowing `static_mut_refs` lint
+        #[cfg_attr(bootstrap, allow(static_mut_ref))]
+        #[cfg_attr(not(bootstrap), allow(static_mut_refs))]
         unsafe fn __getit(
             _init: $crate::option::Option<&mut $crate::option::Option<$t>>,
         ) -> $crate::option::Option<&'static $t> {
diff --git a/library/std/src/sys/pal/common/thread_local/static_local.rs b/library/std/src/sys/pal/common/thread_local/static_local.rs
index 0dde78b14db..4f2b6868962 100644
--- a/library/std/src/sys/pal/common/thread_local/static_local.rs
+++ b/library/std/src/sys/pal/common/thread_local/static_local.rs
@@ -11,8 +11,9 @@ pub macro thread_local_inner {
     (@key $t:ty, const $init:expr) => {{
         #[inline] // see comments below
         #[deny(unsafe_op_in_unsafe_fn)]
-        // FIXME: Use `SyncUnsafeCell` instead of allowing `static_mut_ref` lint
-        #[allow(static_mut_ref)]
+        // FIXME: Use `SyncUnsafeCell` instead of allowing `static_mut_refs` lint
+        #[cfg_attr(bootstrap, allow(static_mut_ref))]
+        #[cfg_attr(not(bootstrap), allow(static_mut_refs))]
         unsafe fn __getit(
             _init: $crate::option::Option<&mut $crate::option::Option<$t>>,
         ) -> $crate::option::Option<&'static $t> {
diff --git a/library/std/src/sys/pal/hermit/fs.rs b/library/std/src/sys/pal/hermit/fs.rs
index 694482a8a30..d4da53fd3df 100644
--- a/library/std/src/sys/pal/hermit/fs.rs
+++ b/library/std/src/sys/pal/hermit/fs.rs
@@ -269,7 +269,7 @@ impl OpenOptions {
 
 impl File {
     pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
-        run_path_with_cstr(path, |path| File::open_c(&path, opts))
+        run_path_with_cstr(path, &|path| File::open_c(&path, opts))
     }
 
     pub fn open_c(path: &CStr, opts: &OpenOptions) -> io::Result<File> {
@@ -421,7 +421,7 @@ pub fn readdir(_p: &Path) -> io::Result<ReadDir> {
 }
 
 pub fn unlink(path: &Path) -> io::Result<()> {
-    run_path_with_cstr(path, |path| cvt(unsafe { abi::unlink(path.as_ptr()) }).map(|_| ()))
+    run_path_with_cstr(path, &|path| cvt(unsafe { abi::unlink(path.as_ptr()) }).map(|_| ()))
 }
 
 pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> {
diff --git a/library/std/src/sys/pal/solid/os.rs b/library/std/src/sys/pal/solid/os.rs
index ff81544ba91..5ceab3b27e0 100644
--- a/library/std/src/sys/pal/solid/os.rs
+++ b/library/std/src/sys/pal/solid/os.rs
@@ -172,7 +172,7 @@ pub fn env() -> Env {
 pub fn getenv(k: &OsStr) -> Option<OsString> {
     // environment variables with a nul byte can't be set, so their value is
     // always None as well
-    run_with_cstr(k.as_bytes(), |k| {
+    run_with_cstr(k.as_bytes(), &|k| {
         let _guard = env_read_lock();
         let v = unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char;
 
@@ -190,8 +190,8 @@ pub fn getenv(k: &OsStr) -> Option<OsString> {
 }
 
 pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
-    run_with_cstr(k.as_bytes(), |k| {
-        run_with_cstr(v.as_bytes(), |v| {
+    run_with_cstr(k.as_bytes(), &|k| {
+        run_with_cstr(v.as_bytes(), &|v| {
             let _guard = ENV_LOCK.write();
             cvt_env(unsafe { libc::setenv(k.as_ptr(), v.as_ptr(), 1) }).map(drop)
         })
@@ -199,7 +199,7 @@ pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
 }
 
 pub fn unsetenv(n: &OsStr) -> io::Result<()> {
-    run_with_cstr(n.as_bytes(), |nbuf| {
+    run_with_cstr(n.as_bytes(), &|nbuf| {
         let _guard = ENV_LOCK.write();
         cvt_env(unsafe { libc::unsetenv(nbuf.as_ptr()) }).map(drop)
     })
diff --git a/library/std/src/sys/pal/unix/fs.rs b/library/std/src/sys/pal/unix/fs.rs
index 6d0b892ea2f..c75323ef775 100644
--- a/library/std/src/sys/pal/unix/fs.rs
+++ b/library/std/src/sys/pal/unix/fs.rs
@@ -1118,7 +1118,7 @@ impl OpenOptions {
 
 impl File {
     pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
-        run_path_with_cstr(path, |path| File::open_c(path, opts))
+        run_path_with_cstr(path, &|path| File::open_c(path, opts))
     }
 
     pub fn open_c(path: &CStr, opts: &OpenOptions) -> io::Result<File> {
@@ -1394,7 +1394,7 @@ impl DirBuilder {
     }
 
     pub fn mkdir(&self, p: &Path) -> io::Result<()> {
-        run_path_with_cstr(p, |p| cvt(unsafe { libc::mkdir(p.as_ptr(), self.mode) }).map(|_| ()))
+        run_path_with_cstr(p, &|p| cvt(unsafe { libc::mkdir(p.as_ptr(), self.mode) }).map(|_| ()))
     }
 
     pub fn set_mode(&mut self, mode: u32) {
@@ -1575,7 +1575,7 @@ impl fmt::Debug for File {
 }
 
 pub fn readdir(path: &Path) -> io::Result<ReadDir> {
-    let ptr = run_path_with_cstr(path, |p| unsafe { Ok(libc::opendir(p.as_ptr())) })?;
+    let ptr = run_path_with_cstr(path, &|p| unsafe { Ok(libc::opendir(p.as_ptr())) })?;
     if ptr.is_null() {
         Err(Error::last_os_error())
     } else {
@@ -1586,27 +1586,27 @@ pub fn readdir(path: &Path) -> io::Result<ReadDir> {
 }
 
 pub fn unlink(p: &Path) -> io::Result<()> {
-    run_path_with_cstr(p, |p| cvt(unsafe { libc::unlink(p.as_ptr()) }).map(|_| ()))
+    run_path_with_cstr(p, &|p| cvt(unsafe { libc::unlink(p.as_ptr()) }).map(|_| ()))
 }
 
 pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
-    run_path_with_cstr(old, |old| {
-        run_path_with_cstr(new, |new| {
+    run_path_with_cstr(old, &|old| {
+        run_path_with_cstr(new, &|new| {
             cvt(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) }).map(|_| ())
         })
     })
 }
 
 pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
-    run_path_with_cstr(p, |p| cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ()))
+    run_path_with_cstr(p, &|p| cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ()))
 }
 
 pub fn rmdir(p: &Path) -> io::Result<()> {
-    run_path_with_cstr(p, |p| cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ()))
+    run_path_with_cstr(p, &|p| cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ()))
 }
 
 pub fn readlink(p: &Path) -> io::Result<PathBuf> {
-    run_path_with_cstr(p, |c_path| {
+    run_path_with_cstr(p, &|c_path| {
         let p = c_path.as_ptr();
 
         let mut buf = Vec::with_capacity(256);
@@ -1635,16 +1635,16 @@ pub fn readlink(p: &Path) -> io::Result<PathBuf> {
 }
 
 pub fn symlink(original: &Path, link: &Path) -> io::Result<()> {
-    run_path_with_cstr(original, |original| {
-        run_path_with_cstr(link, |link| {
+    run_path_with_cstr(original, &|original| {
+        run_path_with_cstr(link, &|link| {
             cvt(unsafe { libc::symlink(original.as_ptr(), link.as_ptr()) }).map(|_| ())
         })
     })
 }
 
 pub fn link(original: &Path, link: &Path) -> io::Result<()> {
-    run_path_with_cstr(original, |original| {
-        run_path_with_cstr(link, |link| {
+    run_path_with_cstr(original, &|original| {
+        run_path_with_cstr(link, &|link| {
             cfg_if::cfg_if! {
                 if #[cfg(any(target_os = "vxworks", target_os = "redox", target_os = "android", target_os = "espidf", target_os = "horizon", target_os = "vita"))] {
                     // VxWorks, Redox and ESP-IDF lack `linkat`, so use `link` instead. POSIX leaves
@@ -1678,7 +1678,7 @@ pub fn link(original: &Path, link: &Path) -> io::Result<()> {
 }
 
 pub fn stat(p: &Path) -> io::Result<FileAttr> {
-    run_path_with_cstr(p, |p| {
+    run_path_with_cstr(p, &|p| {
         cfg_has_statx! {
             if let Some(ret) = unsafe { try_statx(
                 libc::AT_FDCWD,
@@ -1697,7 +1697,7 @@ pub fn stat(p: &Path) -> io::Result<FileAttr> {
 }
 
 pub fn lstat(p: &Path) -> io::Result<FileAttr> {
-    run_path_with_cstr(p, |p| {
+    run_path_with_cstr(p, &|p| {
         cfg_has_statx! {
             if let Some(ret) = unsafe { try_statx(
                 libc::AT_FDCWD,
@@ -1716,7 +1716,7 @@ pub fn lstat(p: &Path) -> io::Result<FileAttr> {
 }
 
 pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
-    let r = run_path_with_cstr(p, |path| unsafe {
+    let r = run_path_with_cstr(p, &|path| unsafe {
         Ok(libc::realpath(path.as_ptr(), ptr::null_mut()))
     })?;
     if r.is_null() {
@@ -1879,7 +1879,7 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
     // Opportunistically attempt to create a copy-on-write clone of `from`
     // using `fclonefileat`.
     if HAS_FCLONEFILEAT.load(Ordering::Relaxed) {
-        let clonefile_result = run_path_with_cstr(to, |to| {
+        let clonefile_result = run_path_with_cstr(to, &|to| {
             cvt(unsafe { fclonefileat(reader.as_raw_fd(), libc::AT_FDCWD, to.as_ptr(), 0) })
         });
         match clonefile_result {
@@ -1925,7 +1925,7 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
 }
 
 pub fn chown(path: &Path, uid: u32, gid: u32) -> io::Result<()> {
-    run_path_with_cstr(path, |path| {
+    run_path_with_cstr(path, &|path| {
         cvt(unsafe { libc::chown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) })
             .map(|_| ())
     })
@@ -1937,7 +1937,7 @@ pub fn fchown(fd: c_int, uid: u32, gid: u32) -> io::Result<()> {
 }
 
 pub fn lchown(path: &Path, uid: u32, gid: u32) -> io::Result<()> {
-    run_path_with_cstr(path, |path| {
+    run_path_with_cstr(path, &|path| {
         cvt(unsafe { libc::lchown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) })
             .map(|_| ())
     })
@@ -1945,7 +1945,7 @@ pub fn lchown(path: &Path, uid: u32, gid: u32) -> io::Result<()> {
 
 #[cfg(not(any(target_os = "fuchsia", target_os = "vxworks")))]
 pub fn chroot(dir: &Path) -> io::Result<()> {
-    run_path_with_cstr(dir, |dir| cvt(unsafe { libc::chroot(dir.as_ptr()) }).map(|_| ()))
+    run_path_with_cstr(dir, &|dir| cvt(unsafe { libc::chroot(dir.as_ptr()) }).map(|_| ()))
 }
 
 pub use remove_dir_impl::remove_dir_all;
@@ -2140,7 +2140,7 @@ mod remove_dir_impl {
         if attr.file_type().is_symlink() {
             crate::fs::remove_file(p)
         } else {
-            run_path_with_cstr(p, |p| remove_dir_all_recursive(None, &p))
+            run_path_with_cstr(p, &|p| remove_dir_all_recursive(None, &p))
         }
     }
 
diff --git a/library/std/src/sys/pal/unix/os.rs b/library/std/src/sys/pal/unix/os.rs
index 881b3a25c51..af2b9db4685 100644
--- a/library/std/src/sys/pal/unix/os.rs
+++ b/library/std/src/sys/pal/unix/os.rs
@@ -186,7 +186,7 @@ pub fn chdir(_p: &path::Path) -> io::Result<()> {
 
 #[cfg(not(target_os = "espidf"))]
 pub fn chdir(p: &path::Path) -> io::Result<()> {
-    let result = run_path_with_cstr(p, |p| unsafe { Ok(libc::chdir(p.as_ptr())) })?;
+    let result = run_path_with_cstr(p, &|p| unsafe { Ok(libc::chdir(p.as_ptr())) })?;
     if result == 0 { Ok(()) } else { Err(io::Error::last_os_error()) }
 }
 
@@ -643,7 +643,7 @@ pub fn env() -> Env {
 pub fn getenv(k: &OsStr) -> Option<OsString> {
     // environment variables with a nul byte can't be set, so their value is
     // always None as well
-    run_with_cstr(k.as_bytes(), |k| {
+    run_with_cstr(k.as_bytes(), &|k| {
         let _guard = env_read_lock();
         let v = unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char;
 
@@ -661,8 +661,8 @@ pub fn getenv(k: &OsStr) -> Option<OsString> {
 }
 
 pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
-    run_with_cstr(k.as_bytes(), |k| {
-        run_with_cstr(v.as_bytes(), |v| {
+    run_with_cstr(k.as_bytes(), &|k| {
+        run_with_cstr(v.as_bytes(), &|v| {
             let _guard = ENV_LOCK.write();
             cvt(unsafe { libc::setenv(k.as_ptr(), v.as_ptr(), 1) }).map(drop)
         })
@@ -670,7 +670,7 @@ pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
 }
 
 pub fn unsetenv(n: &OsStr) -> io::Result<()> {
-    run_with_cstr(n.as_bytes(), |nbuf| {
+    run_with_cstr(n.as_bytes(), &|nbuf| {
         let _guard = ENV_LOCK.write();
         cvt(unsafe { libc::unsetenv(nbuf.as_ptr()) }).map(drop)
     })
diff --git a/library/std/src/sys/pal/wasi/fs.rs b/library/std/src/sys/pal/wasi/fs.rs
index e8238665452..529b82e0198 100644
--- a/library/std/src/sys/pal/wasi/fs.rs
+++ b/library/std/src/sys/pal/wasi/fs.rs
@@ -698,7 +698,7 @@ fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result<File> {
 /// Note that this can fail if `p` doesn't look like it can be opened relative
 /// to any pre-opened file descriptor.
 fn open_parent(p: &Path) -> io::Result<(ManuallyDrop<WasiFd>, PathBuf)> {
-    run_path_with_cstr(p, |p| {
+    run_path_with_cstr(p, &|p| {
         let mut buf = Vec::<u8>::with_capacity(512);
         loop {
             unsafe {
diff --git a/library/std/src/sys/pal/wasi/os.rs b/library/std/src/sys/pal/wasi/os.rs
index 530d3602172..d62ff8a2f18 100644
--- a/library/std/src/sys/pal/wasi/os.rs
+++ b/library/std/src/sys/pal/wasi/os.rs
@@ -95,7 +95,7 @@ pub fn getcwd() -> io::Result<PathBuf> {
 }
 
 pub fn chdir(p: &path::Path) -> io::Result<()> {
-    let result = run_path_with_cstr(p, |p| unsafe { Ok(libc::chdir(p.as_ptr())) })?;
+    let result = run_path_with_cstr(p, &|p| unsafe { Ok(libc::chdir(p.as_ptr())) })?;
     match result == (0 as libc::c_int) {
         true => Ok(()),
         false => Err(io::Error::last_os_error()),
@@ -227,7 +227,7 @@ pub fn env() -> Env {
 pub fn getenv(k: &OsStr) -> Option<OsString> {
     // environment variables with a nul byte can't be set, so their value is
     // always None as well
-    run_with_cstr(k.as_bytes(), |k| {
+    run_with_cstr(k.as_bytes(), &|k| {
         let _guard = env_read_lock();
         let v = unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char;
 
@@ -245,8 +245,8 @@ pub fn getenv(k: &OsStr) -> Option<OsString> {
 }
 
 pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
-    run_with_cstr(k.as_bytes(), |k| {
-        run_with_cstr(v.as_bytes(), |v| unsafe {
+    run_with_cstr(k.as_bytes(), &|k| {
+        run_with_cstr(v.as_bytes(), &|v| unsafe {
             let _guard = env_write_lock();
             cvt(libc::setenv(k.as_ptr(), v.as_ptr(), 1)).map(drop)
         })
@@ -254,7 +254,7 @@ pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
 }
 
 pub fn unsetenv(n: &OsStr) -> io::Result<()> {
-    run_with_cstr(n.as_bytes(), |nbuf| unsafe {
+    run_with_cstr(n.as_bytes(), &|nbuf| unsafe {
         let _guard = env_write_lock();
         cvt(libc::unsetenv(nbuf.as_ptr())).map(drop)
     })
diff --git a/library/std/src/sys_common/net.rs b/library/std/src/sys_common/net.rs
index 8712bd2eca7..de7d31baaaf 100644
--- a/library/std/src/sys_common/net.rs
+++ b/library/std/src/sys_common/net.rs
@@ -199,7 +199,7 @@ impl<'a> TryFrom<(&'a str, u16)> for LookupHost {
     fn try_from((host, port): (&'a str, u16)) -> io::Result<LookupHost> {
         init();
 
-        run_with_cstr(host.as_bytes(), |c_host| {
+        run_with_cstr(host.as_bytes(), &|c_host| {
             let mut hints: c::addrinfo = unsafe { mem::zeroed() };
             hints.ai_socktype = c::SOCK_STREAM;
             let mut res = ptr::null_mut();
diff --git a/library/std/src/sys_common/wtf8.rs b/library/std/src/sys_common/wtf8.rs
index 67db5ebd89c..2dbd19d7171 100644
--- a/library/std/src/sys_common/wtf8.rs
+++ b/library/std/src/sys_common/wtf8.rs
@@ -885,15 +885,43 @@ fn decode_surrogate_pair(lead: u16, trail: u16) -> char {
     unsafe { char::from_u32_unchecked(code_point) }
 }
 
-/// Copied from core::str::StrPrelude::is_char_boundary
+/// Copied from str::is_char_boundary
 #[inline]
 pub fn is_code_point_boundary(slice: &Wtf8, index: usize) -> bool {
-    if index == slice.len() {
+    if index == 0 {
         return true;
     }
     match slice.bytes.get(index) {
-        None => false,
-        Some(&b) => b < 128 || b >= 192,
+        None => index == slice.len(),
+        Some(&b) => (b as i8) >= -0x40,
+    }
+}
+
+/// Verify that `index` is at the edge of either a valid UTF-8 codepoint
+/// (i.e. a codepoint that's not a surrogate) or of the whole string.
+///
+/// These are the cases currently permitted by `OsStr::slice_encoded_bytes`.
+/// Splitting between surrogates is valid as far as WTF-8 is concerned, but
+/// we do not permit it in the public API because WTF-8 is considered an
+/// implementation detail.
+#[track_caller]
+#[inline]
+pub fn check_utf8_boundary(slice: &Wtf8, index: usize) {
+    if index == 0 {
+        return;
+    }
+    match slice.bytes.get(index) {
+        Some(0xED) => (), // Might be a surrogate
+        Some(&b) if (b as i8) >= -0x40 => return,
+        Some(_) => panic!("byte index {index} is not a codepoint boundary"),
+        None if index == slice.len() => return,
+        None => panic!("byte index {index} is out of bounds"),
+    }
+    if slice.bytes[index + 1] >= 0xA0 {
+        // There's a surrogate after index. Now check before index.
+        if index >= 3 && slice.bytes[index - 3] == 0xED && slice.bytes[index - 2] >= 0xA0 {
+            panic!("byte index {index} lies between surrogate codepoints");
+        }
     }
 }
 
diff --git a/library/std/src/sys_common/wtf8/tests.rs b/library/std/src/sys_common/wtf8/tests.rs
index 28a426648e5..6a1cc41a8fb 100644
--- a/library/std/src/sys_common/wtf8/tests.rs
+++ b/library/std/src/sys_common/wtf8/tests.rs
@@ -663,3 +663,65 @@ fn wtf8_to_owned() {
     assert_eq!(string.bytes, b"\xED\xA0\x80");
     assert!(!string.is_known_utf8);
 }
+
+#[test]
+fn wtf8_valid_utf8_boundaries() {
+    let mut string = Wtf8Buf::from_str("aé 💩");
+    string.push(CodePoint::from_u32(0xD800).unwrap());
+    string.push(CodePoint::from_u32(0xD800).unwrap());
+    check_utf8_boundary(&string, 0);
+    check_utf8_boundary(&string, 1);
+    check_utf8_boundary(&string, 3);
+    check_utf8_boundary(&string, 4);
+    check_utf8_boundary(&string, 8);
+    check_utf8_boundary(&string, 14);
+    assert_eq!(string.len(), 14);
+
+    string.push_char('a');
+    check_utf8_boundary(&string, 14);
+    check_utf8_boundary(&string, 15);
+
+    let mut string = Wtf8Buf::from_str("a");
+    string.push(CodePoint::from_u32(0xD800).unwrap());
+    check_utf8_boundary(&string, 1);
+
+    let mut string = Wtf8Buf::from_str("\u{D7FF}");
+    string.push(CodePoint::from_u32(0xD800).unwrap());
+    check_utf8_boundary(&string, 3);
+
+    let mut string = Wtf8Buf::new();
+    string.push(CodePoint::from_u32(0xD800).unwrap());
+    string.push_char('\u{D7FF}');
+    check_utf8_boundary(&string, 3);
+}
+
+#[test]
+#[should_panic(expected = "byte index 4 is out of bounds")]
+fn wtf8_utf8_boundary_out_of_bounds() {
+    let string = Wtf8::from_str("aé");
+    check_utf8_boundary(&string, 4);
+}
+
+#[test]
+#[should_panic(expected = "byte index 1 is not a codepoint boundary")]
+fn wtf8_utf8_boundary_inside_codepoint() {
+    let string = Wtf8::from_str("é");
+    check_utf8_boundary(&string, 1);
+}
+
+#[test]
+#[should_panic(expected = "byte index 1 is not a codepoint boundary")]
+fn wtf8_utf8_boundary_inside_surrogate() {
+    let mut string = Wtf8Buf::new();
+    string.push(CodePoint::from_u32(0xD800).unwrap());
+    check_utf8_boundary(&string, 1);
+}
+
+#[test]
+#[should_panic(expected = "byte index 3 lies between surrogate codepoints")]
+fn wtf8_utf8_boundary_between_surrogates() {
+    let mut string = Wtf8Buf::new();
+    string.push(CodePoint::from_u32(0xD800).unwrap());
+    string.push(CodePoint::from_u32(0xD800).unwrap());
+    check_utf8_boundary(&string, 3);
+}
diff --git a/library/std/src/thread/local.rs b/library/std/src/thread/local.rs
index 83d5d63556f..d1213e2f166 100644
--- a/library/std/src/thread/local.rs
+++ b/library/std/src/thread/local.rs
@@ -180,8 +180,8 @@ impl<T: 'static> fmt::Debug for LocalKey<T> {
 #[stable(feature = "rust1", since = "1.0.0")]
 #[cfg_attr(not(test), rustc_diagnostic_item = "thread_local_macro")]
 #[allow_internal_unstable(thread_local_internals)]
-// FIXME: Use `SyncUnsafeCell` instead of allowing `static_mut_ref` lint
-#[allow(static_mut_ref)]
+// FIXME: Use `SyncUnsafeCell` instead of allowing `static_mut_refs` lint
+#[cfg_attr(not(bootstrap), allow(static_mut_refs))]
 macro_rules! thread_local {
     // empty (base case for the recursion)
     () => {};