From 0cbfdb4c5f3c3aebec5fbc630c129ee872a90371 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 19 Sep 2026 13:58:36 +0200 Subject: [PATCH 01/18] unix DirEntry: make fallback handling more clear --- library/std/src/sys/fs/unix.rs | 129 ++++++++++++++------------------- 1 file changed, 55 insertions(+), 74 deletions(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 46d5d33d23ac2..2e297cd10b537 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1016,89 +1016,70 @@ impl DirEntry { self.file_name_os_str().to_os_string() } - #[cfg(all( - any( - all(target_os = "linux", not(target_env = "musl")), - target_os = "android", - target_os = "fuchsia", - target_os = "hurd", - target_os = "illumos", - target_vendor = "apple", - ), - not(miri) // no dirfd on Miri - ))] pub fn metadata(&self) -> io::Result { - let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?; - let name = self.name.as_ptr(); + cfg_select! { + // Use fstatat (or similar) where possible + all( + any( + all(target_os = "linux", not(target_env = "musl")), + target_os = "android", + target_os = "fuchsia", + target_os = "hurd", + target_os = "illumos", + target_vendor = "apple", + ), + not(miri) // no dirfd on Miri + ) => { + let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?; + let name = self.name.as_ptr(); + + cfg_has_statx! { + if let Some(ret) = unsafe { try_statx( + fd, + name, + libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT, + libc::STATX_BASIC_STATS | libc::STATX_BTIME, + ) } { + return ret; + } + } - cfg_has_statx! { - if let Some(ret) = unsafe { try_statx( - fd, - name, - libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT, - libc::STATX_BASIC_STATS | libc::STATX_BTIME, - ) } { - return ret; + let mut stat: stat64 = unsafe { mem::zeroed() }; + cvt(unsafe { fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW) })?; + Ok(FileAttr::from_stat64(stat)) } - } - - let mut stat: stat64 = unsafe { mem::zeroed() }; - cvt(unsafe { fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW) })?; - Ok(FileAttr::from_stat64(stat)) - } - #[cfg(any( - not(any( - all(target_os = "linux", not(target_env = "musl")), - target_os = "android", - target_os = "fuchsia", - target_os = "hurd", - target_os = "illumos", - target_vendor = "apple", - )), - miri // no dirfd on Miri - ))] - pub fn metadata(&self) -> io::Result { - run_path_with_cstr(&self.path(), &lstat) - } - - #[cfg(any( - target_os = "solaris", - target_os = "illumos", - target_os = "haiku", - target_os = "vxworks", - target_os = "aix", - target_os = "nto", - target_os = "qnx", - target_os = "vita", - target_os = "l4re", - ))] - pub fn file_type(&self) -> io::Result { - self.metadata().map(|m| m.file_type()) + // Fallback based on path + _ => run_path_with_cstr(&self.path(), &lstat), + } } - #[cfg(not(any( - target_os = "solaris", - target_os = "illumos", - target_os = "haiku", - target_os = "vxworks", - target_os = "aix", - target_os = "nto", - target_os = "qnx", - target_os = "vita", - target_os = "l4re", - )))] pub fn file_type(&self) -> io::Result { + // Use `entry.d_type` if available. + #[cfg(not(any( + target_os = "solaris", + target_os = "illumos", + target_os = "haiku", + target_os = "vxworks", + target_os = "aix", + target_os = "nto", + target_os = "qnx", + target_os = "vita", + target_os = "l4re", + )))] match self.entry.d_type { - libc::DT_CHR => Ok(FileType { mode: libc::S_IFCHR }), - libc::DT_FIFO => Ok(FileType { mode: libc::S_IFIFO }), - libc::DT_LNK => Ok(FileType { mode: libc::S_IFLNK }), - libc::DT_REG => Ok(FileType { mode: libc::S_IFREG }), - libc::DT_SOCK => Ok(FileType { mode: libc::S_IFSOCK }), - libc::DT_DIR => Ok(FileType { mode: libc::S_IFDIR }), - libc::DT_BLK => Ok(FileType { mode: libc::S_IFBLK }), - _ => self.metadata().map(|m| m.file_type()), + libc::DT_CHR => return Ok(FileType { mode: libc::S_IFCHR }), + libc::DT_FIFO => return Ok(FileType { mode: libc::S_IFIFO }), + libc::DT_LNK => return Ok(FileType { mode: libc::S_IFLNK }), + libc::DT_REG => return Ok(FileType { mode: libc::S_IFREG }), + libc::DT_SOCK => return Ok(FileType { mode: libc::S_IFSOCK }), + libc::DT_DIR => return Ok(FileType { mode: libc::S_IFDIR }), + libc::DT_BLK => return Ok(FileType { mode: libc::S_IFBLK }), + _ => {} } + + // Fall back to loading the metadata. + self.metadata().map(|m| m.file_type()) } pub fn ino(&self) -> u64 { From 49c0a85c79241bfbff71ae689492b3802c1a2ac0 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 19 Sep 2026 16:14:44 +0200 Subject: [PATCH 02/18] add Dir::(symlink_)metadata_at --- library/std/src/fs.rs | 48 +++++++++++++++++++++++++++ library/std/src/fs/tests.rs | 31 +++++++++++++---- library/std/src/sys/fs/common.rs | 8 +++++ library/std/src/sys/fs/unix/dir.rs | 36 ++++++++++++++++++-- library/std/src/sys/fs/windows.rs | 4 ++- library/std/src/sys/fs/windows/dir.rs | 42 ++++++++++++++++++++++- 6 files changed, 158 insertions(+), 11 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 65b8ed634bc05..e7efa32af6964 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -1891,6 +1891,54 @@ impl Dir { pub fn try_clone(&self) -> io::Result { Ok(Dir { inner: self.inner.duplicate()? }) } + + /// Queries the file system to get information about a file, directory, etc. relative to this + /// directory. + /// + /// This function will traverse symbolic links to query information about the destination file. + /// To query metadata about the path itself without following symbolic links, use + /// [`symlink_metadata_at`][Self::symlink_metadata_at]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::fs::Dir; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::open("foo")?; + /// let metadata = dir.metadata_at("subdir/file.txt")?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn metadata_at>(&self, path: P) -> io::Result { + self.inner.metadata_at(path.as_ref()).map(Metadata) + } + + /// Queries the file system to get information about a file, directory, etc. relative to this + /// directory. + /// + /// This function will return the [`Metadata`] of the exact path without traversing symbolic + /// links to a resolved destination file. Using this function on a path that is a file or + /// directory (not a symbolic link) will behave the same as [`metadata_at`][Self::metadata_at]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::fs::Dir; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::open("foo")?; + /// let metadata = dir.symlink_metadata_at("subdir/file.txt")?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn symlink_metadata_at>(&self, path: P) -> io::Result { + self.inner.symlink_metadata_at(path.as_ref()).map(Metadata) + } } impl AsInner for Dir { diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 250109f277dab..15711652cb05f 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -13,7 +13,7 @@ use crate::os::unix::fs::symlink as symlink_file; use crate::os::unix::fs::symlink as junction_point; #[cfg(windows)] use crate::os::windows::fs::{OpenOptionsExt, junction_point, symlink_dir, symlink_file}; -use crate::path::Path; +use crate::path::{Path, PathBuf}; use crate::sync::Arc; use crate::test_helpers::{TempDir, tmpdir}; use crate::time::{Duration, Instant, SystemTime}; @@ -655,11 +655,6 @@ fn set_get_permissions_nofollows() { not(any(target_os = "espidf", target_os = "horizon", target_os = "wasi")) ))] fn set_get_permissions_nofollows_symlink() { - #[cfg(not(windows))] - use crate::os::unix::fs::symlink as symlink_file; - #[cfg(windows)] - use crate::os::windows::fs::symlink_file; - let tmpdir = tmpdir(); let filename = tmpdir.join("set_get_unix_permissions_file"); let symlink_name = tmpdir.join("set_get_unix_permissions"); @@ -3106,3 +3101,27 @@ fn test_dir_open_dir() { check!(f.read_exact(&mut buf)); assert_eq!(b"baz", &buf); } + +#[test] +fn test_dir_metadata_at() { + let tmpdir = tmpdir(); + let dir = check!(Dir::open(tmpdir.path())); + check!(dir.create_dir("subdir")); + // FIXME: `/` does not work as path separator on Windows. + let barpath = PathBuf::from("subdir").join("bar.txt"); + drop(check!(dir.open_file_with(&barpath, &OpenOptions::new().create(true).write(true)))); + check!(symlink_file(&tmpdir.join("subdir/bar.txt"), &tmpdir.join("link"))); + + let metadata = check!(dir.metadata_at(&barpath)); + assert!(metadata.is_file()); + let metadata = check!(dir.metadata_at("subdir")); + assert!(metadata.is_dir()); + dir.metadata_at("does-not-exist").unwrap_err(); + + let metadata = check!(dir.metadata_at("link")); + assert!(metadata.is_file()); + assert!(!metadata.is_symlink()); + let metadata = check!(dir.symlink_metadata_at("link")); + assert!(!metadata.is_file()); + assert!(metadata.is_symlink()); +} diff --git a/library/std/src/sys/fs/common.rs b/library/std/src/sys/fs/common.rs index b2472665fe465..70cb57ac2915d 100644 --- a/library/std/src/sys/fs/common.rs +++ b/library/std/src/sys/fs/common.rs @@ -108,6 +108,14 @@ impl Dir { pub fn remove_dir(&self, path: &Path) -> io::Result<()> { remove_dir(self.path.join(path)) } + + pub fn metadata_at(&self, path: &Path) -> io::Result { + self.path.join(path).metadata().map(|m| m.into_inner()) + } + + pub fn symlink_metadata_at(&self, path: &Path) -> io::Result { + self.path.join(path).symlink_metadata().map(|m| m.into_inner()) + } } impl fmt::Debug for Dir { diff --git a/library/std/src/sys/fs/unix/dir.rs b/library/std/src/sys/fs/unix/dir.rs index cf0dece265054..a1e336b7fc237 100644 --- a/library/std/src/sys/fs/unix/dir.rs +++ b/library/std/src/sys/fs/unix/dir.rs @@ -26,7 +26,7 @@ use crate::sys::fs::OpenOptions; use crate::sys::fs::unix::{File, FileAttr, debug_path_fd}; use crate::sys::helpers::run_path_with_cstr; use crate::sys::{AsInner, FromInner, IntoInner, cvt, cvt_r}; -use crate::{fmt, fs, io}; +use crate::{fmt, fs, io, mem}; const TRAVERSE_DIRECTORY: i32 = cfg_select! { @@ -68,7 +68,7 @@ impl Dir { } pub fn remove_file(&self, path: &Path) -> io::Result<()> { - run_path_with_cstr(path, &|path| self.remove_c(path, false)) + run_path_with_cstr(path, &|path| self.remove_c(path, /* remove_dir */ false)) } pub fn rename(&self, from: &Path, to_dir: &Self, to: &Path) -> io::Result<()> { @@ -86,7 +86,17 @@ impl Dir { } pub fn remove_dir(&self, path: &Path) -> io::Result<()> { - run_path_with_cstr(path, &|path| self.remove_c(path, true)) + run_path_with_cstr(path, &|path| self.remove_c(path, /* remove_dir */ true)) + } + + pub fn metadata_at(&self, path: &Path) -> io::Result { + run_path_with_cstr(path, &|path| { + self.metadata_at_c(path, /* symlink_nofollow */ false) + }) + } + + pub fn symlink_metadata_at(&self, path: &Path) -> io::Result { + run_path_with_cstr(path, &|path| self.metadata_at_c(path, /* symlink_nofollow */ true)) } fn open_with_c(path: &CStr, opts: &OpenOptions) -> io::Result { @@ -143,6 +153,26 @@ impl Dir { fn create_dir_c(&self, path: &CStr) -> io::Result<()> { cvt(unsafe { mkdirat(self.0.as_raw_fd(), path.as_ptr(), 0o777) }).map(|_| ()) } + + fn metadata_at_c(&self, path: &CStr, symlink_nofollow: bool) -> io::Result { + let fd = self.0.as_raw_fd(); + let flag = if symlink_nofollow { libc::AT_SYMLINK_NOFOLLOW } else { 0 }; + + cfg_has_statx! { + if let Some(ret) = unsafe { super::try_statx( + fd, + path.as_ptr(), + flag | libc::AT_STATX_SYNC_AS_STAT, + libc::STATX_BASIC_STATS | libc::STATX_BTIME, + ) } { + return ret; + } + } + + let mut stat: super::stat64 = unsafe { mem::zeroed() }; + cvt(unsafe { super::fstatat64(fd, path.as_ptr(), &mut stat, flag) })?; + Ok(FileAttr::from_stat64(stat)) + } } impl fmt::Debug for Dir { diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index 399c0749bc21e..473d92aface26 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -1454,7 +1454,9 @@ pub fn link(_original: &WCStr, _link: &WCStr) -> io::Result<()> { pub fn stat(path: &WCStr) -> io::Result { match metadata(path, ReparsePoint::Follow) { Err(err) if err.raw_os_error() == Some(c::ERROR_CANT_ACCESS_FILE as i32) => { - if let Ok(attrs) = lstat(path) { + // Fallback to opening reparse points when following fails. Needed for UNIX domain + // sockets. See . + if let Ok(attrs) = metadata(path, ReparsePoint::Open) { if !attrs.file_type().is_symlink() { return Ok(attrs); } diff --git a/library/std/src/sys/fs/windows/dir.rs b/library/std/src/sys/fs/windows/dir.rs index 6f6d59af31628..7b8cd60fd8d06 100644 --- a/library/std/src/sys/fs/windows/dir.rs +++ b/library/std/src/sys/fs/windows/dir.rs @@ -7,7 +7,7 @@ use crate::os::windows::io::{ }; use crate::path::Path; use crate::sys::api::{UnicodeStrRef, WinError}; -use crate::sys::fs::windows::debug_path_handle; +use crate::sys::fs::windows::{ReparsePoint, debug_path_handle}; use crate::sys::fs::{File, FileAttr, OpenOptions}; use crate::sys::handle::Handle; use crate::sys::path::{WCStr, with_native_path}; @@ -165,6 +165,7 @@ impl Dir { fn rename_native(&self, from: &[u16], to_dir: &Self, to: &[u16]) -> io::Result<()> { let mut opts = OpenOptions::new(); opts.access_mode(c::DELETE); + // FIXME: custom_flags is ignored by `open_file_native`! opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS); let handle = self.open_file_native(from, &opts, /* create_opt */ 0)?; // Calculate the layout of the `FILE_RENAME_INFORMATION` we pass to `NtSetInformationFile` @@ -231,6 +232,45 @@ impl Dir { }); f.file_attr() } + + pub fn metadata_at(&self, path: &Path) -> io::Result { + let path = to_u16s_without_nul(path)?; + // Same as the `stat` logic used for `fs::metadata` + match self.metadata_at_native(&path, ReparsePoint::Follow) { + Err(err) if err.raw_os_error() == Some(c::ERROR_CANT_ACCESS_FILE as i32) => { + // Fallback to opening reparse points when following fails. Needed for UNIX domain + // sockets. See . + if let Ok(attrs) = self.metadata_at_native(&path, ReparsePoint::Open) { + if !attrs.file_type().is_symlink() { + return Ok(attrs); + } + } + Err(err) + } + result => result, + } + } + + pub fn symlink_metadata_at(&self, path: &Path) -> io::Result { + let path = to_u16s_without_nul(path)?; + self.metadata_at_native(&path, ReparsePoint::Open) + } + + fn metadata_at_native(&self, path: &[u16], reparse: ReparsePoint) -> io::Result { + let mut opts = OpenOptions::new(); + // the NT functions need at least c::FILE_READ_ATTRIBUTES + opts.access_mode(c::FILE_READ_ATTRIBUTES); + let create_opt = if reparse == ReparsePoint::Open { c::FILE_OPEN_REPARSE_POINT } else { 0 }; + + let name = UnicodeStrRef::from_slice(path); + let object_attributes = c::OBJECT_ATTRIBUTES { + RootDirectory: self.handle.as_raw_handle(), + ObjectName: name.as_ptr().cast_mut(), + ..c::OBJECT_ATTRIBUTES::with_length() + }; + let handle = unsafe { nt_create_file(&opts, &object_attributes, create_opt)? }; + File { handle }.file_attr() + } } impl fmt::Debug for Dir { From cadaa2e7bf5a4c7ed2f9c4fd1de42d6527f6c8b8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 19 Sep 2026 16:22:23 +0200 Subject: [PATCH 03/18] reuse Dir::metadata_at code for DirEntry::metadata --- library/std/src/sys/fs/unix.rs | 27 +++++++-------------------- library/std/src/sys/fs/unix/dir.rs | 14 +++++++++----- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 2e297cd10b537..988bacd57d1b2 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -17,10 +17,6 @@ use libc::c_char; target_vendor = "apple", ))] use libc::dirfd; -#[cfg(any(target_os = "fuchsia", target_os = "illumos", target_vendor = "apple"))] -use libc::fstatat as fstatat64; -#[cfg(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd"))] -use libc::fstatat64; use libc::{c_int, mode_t}; #[cfg(target_os = "android")] use libc::{ @@ -1018,7 +1014,7 @@ impl DirEntry { pub fn metadata(&self) -> io::Result { cfg_select! { - // Use fstatat (or similar) where possible + // Use directory handle where possible all( any( all(target_os = "linux", not(target_env = "musl")), @@ -1031,22 +1027,13 @@ impl DirEntry { not(miri) // no dirfd on Miri ) => { let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?; - let name = self.name.as_ptr(); - - cfg_has_statx! { - if let Some(ret) = unsafe { try_statx( - fd, - name, - libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT, - libc::STATX_BASIC_STATS | libc::STATX_BTIME, - ) } { - return ret; - } - } - let mut stat: stat64 = unsafe { mem::zeroed() }; - cvt(unsafe { fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW) })?; - Ok(FileAttr::from_stat64(stat)) + // Make this FD into a directory handle. We don't actually drop it, + // so having an `OwnedFd` is fine. + let dir_handle = + mem::ManuallyDrop::new(dir::Dir(unsafe { OwnedFd::from_raw_fd(fd) })); + + dir_handle.metadata_at_c(&self.name, /* symlink_nofollow */ true) } // Fallback based on path diff --git a/library/std/src/sys/fs/unix/dir.rs b/library/std/src/sys/fs/unix/dir.rs index a1e336b7fc237..087916fd9ff63 100644 --- a/library/std/src/sys/fs/unix/dir.rs +++ b/library/std/src/sys/fs/unix/dir.rs @@ -7,10 +7,10 @@ cfg_select! { target_os = "android", target_os = "hurd", )) => { - use libc::{open as open64, openat as openat64}; + use libc::{fstatat as fstatat64, open as open64, openat as openat64}; } _ => { - use libc::{open64, openat64}; + use libc::{fstatat64, open64, openat64}; } } @@ -36,7 +36,7 @@ const TRAVERSE_DIRECTORY: i32 = _ => libc::O_RDONLY, }; -pub struct Dir(OwnedFd); +pub struct Dir(pub(super) OwnedFd); impl Dir { pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { @@ -154,7 +154,11 @@ impl Dir { cvt(unsafe { mkdirat(self.0.as_raw_fd(), path.as_ptr(), 0o777) }).map(|_| ()) } - fn metadata_at_c(&self, path: &CStr, symlink_nofollow: bool) -> io::Result { + pub(super) fn metadata_at_c( + &self, + path: &CStr, + symlink_nofollow: bool, + ) -> io::Result { let fd = self.0.as_raw_fd(); let flag = if symlink_nofollow { libc::AT_SYMLINK_NOFOLLOW } else { 0 }; @@ -170,7 +174,7 @@ impl Dir { } let mut stat: super::stat64 = unsafe { mem::zeroed() }; - cvt(unsafe { super::fstatat64(fd, path.as_ptr(), &mut stat, flag) })?; + cvt(unsafe { fstatat64(fd, path.as_ptr(), &mut stat, flag) })?; Ok(FileAttr::from_stat64(stat)) } } From a0690e7d7e690c3df459cd3fb204d855b06f5678 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 23 Sep 2026 08:36:40 +0200 Subject: [PATCH 04/18] =?UTF-8?q?rename=20metadata=5Fat=20=E2=86=92=20meta?= =?UTF-8?q?data=20(and=20metadata=20=E2=86=92=20self=5Fmetadata)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/std/src/fs.rs | 24 +++++++++++++----------- library/std/src/fs/tests.rs | 16 ++++++++-------- library/std/src/sys/fs/common.rs | 6 +++--- library/std/src/sys/fs/unix.rs | 2 +- library/std/src/sys/fs/unix/dir.rs | 16 ++++++---------- library/std/src/sys/fs/windows/dir.rs | 14 +++++++------- src/tools/miri/src/shims/files.rs | 5 ++++- src/tools/miri/src/shims/windows/fs.rs | 6 +++++- 8 files changed, 47 insertions(+), 42 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index e7efa32af6964..3f0f276551b45 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -1637,6 +1637,8 @@ impl Dir { /// Queries metadata about the underlying directory. /// + /// This is equivalent to `dir.metadata(".")` but can be more efficient. + /// /// # Examples /// /// ```no_run @@ -1645,13 +1647,13 @@ impl Dir { /// /// fn main() -> std::io::Result<()> { /// let dir = Dir::open("foo")?; - /// let metadata = dir.metadata()?; + /// let metadata = dir.self_metadata()?; /// Ok(()) /// } /// ``` #[unstable(feature = "dirfd", issue = "120426")] - pub fn metadata(&self) -> io::Result { - self.inner.metadata().map(Metadata) + pub fn self_metadata(&self) -> io::Result { + self.inner.self_metadata().map(Metadata) } /// Attempts to open a file in read-only mode relative to this directory. @@ -1897,7 +1899,7 @@ impl Dir { /// /// This function will traverse symbolic links to query information about the destination file. /// To query metadata about the path itself without following symbolic links, use - /// [`symlink_metadata_at`][Self::symlink_metadata_at]. + /// [`symlink_metadata`][Self::symlink_metadata]. /// /// # Examples /// @@ -1907,13 +1909,13 @@ impl Dir { /// /// fn main() -> std::io::Result<()> { /// let dir = Dir::open("foo")?; - /// let metadata = dir.metadata_at("subdir/file.txt")?; + /// let metadata = dir.metadata("subdir/file.txt")?; /// Ok(()) /// } /// ``` #[unstable(feature = "dirfd", issue = "120426")] - pub fn metadata_at>(&self, path: P) -> io::Result { - self.inner.metadata_at(path.as_ref()).map(Metadata) + pub fn metadata>(&self, path: P) -> io::Result { + self.inner.metadata(path.as_ref()).map(Metadata) } /// Queries the file system to get information about a file, directory, etc. relative to this @@ -1921,7 +1923,7 @@ impl Dir { /// /// This function will return the [`Metadata`] of the exact path without traversing symbolic /// links to a resolved destination file. Using this function on a path that is a file or - /// directory (not a symbolic link) will behave the same as [`metadata_at`][Self::metadata_at]. + /// directory (not a symbolic link) will behave the same as [`metadata`][Self::metadata]. /// /// # Examples /// @@ -1931,13 +1933,13 @@ impl Dir { /// /// fn main() -> std::io::Result<()> { /// let dir = Dir::open("foo")?; - /// let metadata = dir.symlink_metadata_at("subdir/file.txt")?; + /// let metadata = dir.symlink_metadata("subdir/file.txt")?; /// Ok(()) /// } /// ``` #[unstable(feature = "dirfd", issue = "120426")] - pub fn symlink_metadata_at>(&self, path: P) -> io::Result { - self.inner.symlink_metadata_at(path.as_ref()).map(Metadata) + pub fn symlink_metadata>(&self, path: P) -> io::Result { + self.inner.symlink_metadata(path.as_ref()).map(Metadata) } } diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 15711652cb05f..d1d39d144e4b1 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -3010,10 +3010,10 @@ fn test_dir_clone() { } #[test] -fn test_dir_metadata() { +fn test_dir_self_metadata() { let tmpdir = tmpdir(); let dir = check!(Dir::open(tmpdir.path())); - let metadata = check!(dir.metadata()); + let metadata = check!(dir.self_metadata()); assert!(metadata.is_dir()); } @@ -3103,7 +3103,7 @@ fn test_dir_open_dir() { } #[test] -fn test_dir_metadata_at() { +fn test_dir_metadata() { let tmpdir = tmpdir(); let dir = check!(Dir::open(tmpdir.path())); check!(dir.create_dir("subdir")); @@ -3112,16 +3112,16 @@ fn test_dir_metadata_at() { drop(check!(dir.open_file_with(&barpath, &OpenOptions::new().create(true).write(true)))); check!(symlink_file(&tmpdir.join("subdir/bar.txt"), &tmpdir.join("link"))); - let metadata = check!(dir.metadata_at(&barpath)); + let metadata = check!(dir.metadata(&barpath)); assert!(metadata.is_file()); - let metadata = check!(dir.metadata_at("subdir")); + let metadata = check!(dir.metadata("subdir")); assert!(metadata.is_dir()); - dir.metadata_at("does-not-exist").unwrap_err(); + dir.metadata("does-not-exist").unwrap_err(); - let metadata = check!(dir.metadata_at("link")); + let metadata = check!(dir.metadata("link")); assert!(metadata.is_file()); assert!(!metadata.is_symlink()); - let metadata = check!(dir.symlink_metadata_at("link")); + let metadata = check!(dir.symlink_metadata("link")); assert!(!metadata.is_file()); assert!(metadata.is_symlink()); } diff --git a/library/std/src/sys/fs/common.rs b/library/std/src/sys/fs/common.rs index 70cb57ac2915d..1724e6115462e 100644 --- a/library/std/src/sys/fs/common.rs +++ b/library/std/src/sys/fs/common.rs @@ -85,7 +85,7 @@ impl Dir { File::open(&self.path.join(path), opts) } - pub fn metadata(&self) -> io::Result { + pub fn self_metadata(&self) -> io::Result { self.path.metadata().map(|m| m.into_inner()) } @@ -109,11 +109,11 @@ impl Dir { remove_dir(self.path.join(path)) } - pub fn metadata_at(&self, path: &Path) -> io::Result { + pub fn metadata(&self, path: &Path) -> io::Result { self.path.join(path).metadata().map(|m| m.into_inner()) } - pub fn symlink_metadata_at(&self, path: &Path) -> io::Result { + pub fn symlink_metadata(&self, path: &Path) -> io::Result { self.path.join(path).symlink_metadata().map(|m| m.into_inner()) } } diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 988bacd57d1b2..a20998cca8f70 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1033,7 +1033,7 @@ impl DirEntry { let dir_handle = mem::ManuallyDrop::new(dir::Dir(unsafe { OwnedFd::from_raw_fd(fd) })); - dir_handle.metadata_at_c(&self.name, /* symlink_nofollow */ true) + dir_handle.metadata_c(&self.name, /* symlink_nofollow */ true) } // Fallback based on path diff --git a/library/std/src/sys/fs/unix/dir.rs b/library/std/src/sys/fs/unix/dir.rs index 087916fd9ff63..916767c96e2fe 100644 --- a/library/std/src/sys/fs/unix/dir.rs +++ b/library/std/src/sys/fs/unix/dir.rs @@ -57,7 +57,7 @@ impl Dir { .map(File) } - pub fn metadata(&self) -> io::Result { + pub fn self_metadata(&self) -> io::Result { // Reuse the implementation for files, which should work for all FDs. let fd = self.0.as_raw_fd(); let f = core::mem::ManuallyDrop::new(File( @@ -89,14 +89,14 @@ impl Dir { run_path_with_cstr(path, &|path| self.remove_c(path, /* remove_dir */ true)) } - pub fn metadata_at(&self, path: &Path) -> io::Result { + pub fn metadata(&self, path: &Path) -> io::Result { run_path_with_cstr(path, &|path| { - self.metadata_at_c(path, /* symlink_nofollow */ false) + self.metadata_c(path, /* symlink_nofollow */ false) }) } - pub fn symlink_metadata_at(&self, path: &Path) -> io::Result { - run_path_with_cstr(path, &|path| self.metadata_at_c(path, /* symlink_nofollow */ true)) + pub fn symlink_metadata(&self, path: &Path) -> io::Result { + run_path_with_cstr(path, &|path| self.metadata_c(path, /* symlink_nofollow */ true)) } fn open_with_c(path: &CStr, opts: &OpenOptions) -> io::Result { @@ -154,11 +154,7 @@ impl Dir { cvt(unsafe { mkdirat(self.0.as_raw_fd(), path.as_ptr(), 0o777) }).map(|_| ()) } - pub(super) fn metadata_at_c( - &self, - path: &CStr, - symlink_nofollow: bool, - ) -> io::Result { + pub(super) fn metadata_c(&self, path: &CStr, symlink_nofollow: bool) -> io::Result { let fd = self.0.as_raw_fd(); let flag = if symlink_nofollow { libc::AT_SYMLINK_NOFOLLOW } else { 0 }; diff --git a/library/std/src/sys/fs/windows/dir.rs b/library/std/src/sys/fs/windows/dir.rs index 7b8cd60fd8d06..60f8c2522de26 100644 --- a/library/std/src/sys/fs/windows/dir.rs +++ b/library/std/src/sys/fs/windows/dir.rs @@ -223,7 +223,7 @@ impl Dir { .io_result() } - pub fn metadata(&self) -> io::Result { + pub fn self_metadata(&self) -> io::Result { // Reuse the implementation for files, which should work for all handles. let handle = self.handle.as_raw_handle(); let f = core::mem::ManuallyDrop::new(File { @@ -233,14 +233,14 @@ impl Dir { f.file_attr() } - pub fn metadata_at(&self, path: &Path) -> io::Result { + pub fn metadata(&self, path: &Path) -> io::Result { let path = to_u16s_without_nul(path)?; // Same as the `stat` logic used for `fs::metadata` - match self.metadata_at_native(&path, ReparsePoint::Follow) { + match self.metadata_native(&path, ReparsePoint::Follow) { Err(err) if err.raw_os_error() == Some(c::ERROR_CANT_ACCESS_FILE as i32) => { // Fallback to opening reparse points when following fails. Needed for UNIX domain // sockets. See . - if let Ok(attrs) = self.metadata_at_native(&path, ReparsePoint::Open) { + if let Ok(attrs) = self.metadata_native(&path, ReparsePoint::Open) { if !attrs.file_type().is_symlink() { return Ok(attrs); } @@ -251,12 +251,12 @@ impl Dir { } } - pub fn symlink_metadata_at(&self, path: &Path) -> io::Result { + pub fn symlink_metadata(&self, path: &Path) -> io::Result { let path = to_u16s_without_nul(path)?; - self.metadata_at_native(&path, ReparsePoint::Open) + self.metadata_native(&path, ReparsePoint::Open) } - fn metadata_at_native(&self, path: &[u16], reparse: ReparsePoint) -> io::Result { + fn metadata_native(&self, path: &[u16], reparse: ReparsePoint) -> io::Result { let mut opts = OpenOptions::new(); // the NT functions need at least c::FILE_READ_ATTRIBUTES opts.access_mode(c::FILE_READ_ATTRIBUTES); diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index d73ce57f64f19..cd7a7f4f0250b 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -524,7 +524,10 @@ impl FileDescription for DirHandle { fn metadata<'tcx>( &self, ) -> InterpResult<'tcx, Either, &'static str>> { - interp_ok(Either::Left(self.dir.metadata())) + #[cfg(bootstrap)] + return interp_ok(Either::Left(self.dir.metadata())); + #[cfg(not(bootstrap))] + return interp_ok(Either::Left(self.dir.self_metadata())); } } diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs index 9aae6677faeb2..bab26d80dee70 100644 --- a/src/tools/miri/src/shims/windows/fs.rs +++ b/src/tools/miri/src/shims/windows/fs.rs @@ -225,7 +225,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return interp_ok(Handle::Invalid); } }; - if !dir.metadata().unwrap().is_dir() { + #[cfg(bootstrap)] + let metadata = dir.metadata(); + #[cfg(not(bootstrap))] + let metadata = dir.self_metadata(); + if !metadata.unwrap().is_dir() { // This changed from a directory to a file. Retry. continue; } From 452a7cac6e79b4bff25906e2e45bb820d46f793b Mon Sep 17 00:00:00 2001 From: dianne Date: Tue, 22 Sep 2026 23:53:16 -0700 Subject: [PATCH 05/18] make `RustaceansAreAwesome` satisfy trait bounds --- .../src/traits/dyn_compatibility.rs | 9 ++++ ...tchability-placeholder-satisfies-bounds.rs | 37 ++++++++++++++ .../ice-generics-of-crate-root-152335.rs | 2 + .../ice-generics-of-crate-root-152335.stderr | 50 +++++++++++++++---- .../unhandled-crate-mod-issue-144888.rs | 2 + .../unhandled-crate-mod-issue-144888.stderr | 46 ++++++++++++++--- 6 files changed, 129 insertions(+), 17 deletions(-) create mode 100644 tests/ui/dyn-compatibility/dispatchability-placeholder-satisfies-bounds.rs diff --git a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs index e964c09420b0e..7f05f7e04aadf 100644 --- a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs +++ b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs @@ -749,6 +749,15 @@ fn receiver_is_dispatchable<'tcx>( let trait_predicate = ty::TraitRef::new_from_args(tcx, trait_def_id, args); clauses.push(trait_predicate.upcast(tcx)); + // U satisfies `Trait`'s where-bounds. + clauses.extend( + tcx.clauses_of(trait_def_id) + .instantiate(tcx, args) + .clauses + .into_iter() + .map(Unnormalized::skip_norm_wip), + ); + let meta_sized_predicate = { let meta_sized_did = tcx.require_lang_item(LangItem::MetaSized, DUMMY_SP); ty::TraitRef::new(tcx, meta_sized_did, [unsized_self_ty]) diff --git a/tests/ui/dyn-compatibility/dispatchability-placeholder-satisfies-bounds.rs b/tests/ui/dyn-compatibility/dispatchability-placeholder-satisfies-bounds.rs new file mode 100644 index 0000000000000..421b9a512c1e4 --- /dev/null +++ b/tests/ui/dyn-compatibility/dispatchability-placeholder-satisfies-bounds.rs @@ -0,0 +1,37 @@ +//! Regression tests for . When checking whether +//! method receivers for a trait `Trait` are dynamically dispatchable, we use a placeholder type in +//! place of `dyn Trait` to avoid a cycle (as that would require determining whether `Trait` is dyn- +//! compatible). This placeholder must satisfy `Trait`'s where-bounds to avoid errors in param- +//! environment normalization. +//@ check-pass + +// Test 1 + +use std::ops::Deref; + +trait SignatureToFnPtr { + type Ptr; +} + +trait DerefsToFn +where + (Args, Self::Output): SignatureToFnPtr, + Self: Deref::Ptr>, +{ + type Output; + fn method(&self); +} + +// Test 2 + +trait Associator { + type Point; +} + +trait Marker {} + +trait Trait + ?Sized>: Marker { + fn method(&self); +} + +fn main() {} diff --git a/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.rs b/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.rs index cfafca68bfd2f..188f63fa94bab 100644 --- a/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.rs +++ b/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.rs @@ -26,6 +26,8 @@ where //~| ERROR the size for values of type `Self` cannot be known //~| ERROR type mismatch resolving //~| ERROR the size for values of type `Self` cannot be known + //~| ERROR type mismatch resolving + //~| ERROR the size for values of type `RustaceansAreAwesome` cannot be known } trait Mirror { diff --git a/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.stderr b/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.stderr index b8796f673f4a5..06fd8bced4af1 100644 --- a/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.stderr +++ b/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.stderr @@ -5,11 +5,29 @@ LL | fn transmute(&self, t: T) -> ::Assoc; | ^ not found in this scope error[E0601]: `main` function not found in crate `ice_generics_of_crate_root_152335` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:57 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:57 | LL | impl> Mirror for T {} | ^ consider adding a `main` function to `$DIR/ice-generics-of-crate-root-152335.rs` +error[E0271]: type mismatch resolving `>::Assoc == RustaceansAreAwesome` + --> $DIR/ice-generics-of-crate-root-152335.rs:23:5 + | +LL | fn transmute(&self, t: T) -> ::Assoc; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `RustaceansAreAwesome`, found type parameter `T` + | + = note: expected type parameter `RustaceansAreAwesome` + found type parameter `T` + = note: a type parameter was expected, but a different one was found; you might be missing a type parameter or trait bound + = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters +note: required for `RustaceansAreAwesome` to implement `Mirror` + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 + | +LL | impl> Mirror for T {} + | --------- ^^^^^^ ^ + | | + | unsatisfied trait bound introduced here + error[E0271]: type mismatch resolving `>::Assoc == Self` --> $DIR/ice-generics-of-crate-root-152335.rs:23:5 | @@ -21,13 +39,27 @@ LL | fn transmute(&self, t: T) -> ::Assoc; = note: a type parameter was expected, but a different one was found; you might be missing a type parameter or trait bound = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters note: required for `Self` to implement `Mirror` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:42 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 | LL | impl> Mirror for T {} | --------- ^^^^^^ ^ | | | unsatisfied trait bound introduced here +error[E0277]: the size for values of type `RustaceansAreAwesome` cannot be known at compilation time + --> $DIR/ice-generics-of-crate-root-152335.rs:23:5 + | +LL | fn transmute(&self, t: T) -> ::Assoc; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time + | +note: required for `RustaceansAreAwesome` to implement `Mirror` + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 + | +LL | impl> Mirror for T {} + | - ^^^^^^ ^ + | | + | unsatisfied trait bound implicitly introduced here + error[E0277]: the size for values of type `Self` cannot be known at compilation time --> $DIR/ice-generics-of-crate-root-152335.rs:23:5 | @@ -35,7 +67,7 @@ LL | fn transmute(&self, t: T) -> ::Assoc; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | note: required for `Self` to implement `Mirror` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:42 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 | LL | impl> Mirror for T {} | - ^^^^^^ ^ @@ -69,7 +101,7 @@ LL | trait Foo: Super = note: a type parameter was expected, but a different one was found; you might be missing a type parameter or trait bound = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters note: required for `Self` to implement `Mirror` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:42 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 | LL | impl> Mirror for T {} | --------- ^^^^^^ ^ @@ -83,7 +115,7 @@ LL | trait Foo: Super | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | note: required for `Self` to implement `Mirror` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:42 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 | LL | impl> Mirror for T {} | - ^^^^^^ ^ @@ -95,7 +127,7 @@ LL | trait Foo: Super + Sized | +++++++ error[E0046]: not all trait items implemented, missing: `Assoc` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:1 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:1 | LL | type Assoc: ?Sized; | ------------------ `Assoc` from trait @@ -120,7 +152,7 @@ LL | fn transmute(&self, t: T) -> ::Assoc; = note: a type parameter was expected, but a different one was found; you might be missing a type parameter or trait bound = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters note: required for `Self` to implement `Mirror` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:42 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 | LL | impl> Mirror for T {} | --------- ^^^^^^ ^ @@ -134,7 +166,7 @@ LL | fn transmute(&self, t: T) -> ::Assoc; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | note: required for `Self` to implement `Mirror` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:42 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 | LL | impl> Mirror for T {} | - ^^^^^^ ^ @@ -145,7 +177,7 @@ help: consider further restricting `Self` LL | fn transmute(&self, t: T) -> ::Assoc where Self: Sized; | +++++++++++++++++ -error: aborting due to 10 previous errors +error: aborting due to 12 previous errors Some errors have detailed explanations: E0038, E0046, E0271, E0277, E0405, E0601. For more information about an error, try `rustc --explain E0038`. diff --git a/tests/ui/traits/unhandled-crate-mod-issue-144888.rs b/tests/ui/traits/unhandled-crate-mod-issue-144888.rs index 8c9085ee80b35..41f233398d83b 100644 --- a/tests/ui/traits/unhandled-crate-mod-issue-144888.rs +++ b/tests/ui/traits/unhandled-crate-mod-issue-144888.rs @@ -18,6 +18,8 @@ where //~| ERROR the size for values of type `Self` cannot be known at compilation time //~| ERROR type mismatch resolving //~| ERROR the size for values of type `Self` cannot be known at compilation time + //~| ERROR type mismatch resolving + //~| ERROR the size for values of type `RustaceansAreAwesome` cannot be known at compilation time } trait Mirror { diff --git a/tests/ui/traits/unhandled-crate-mod-issue-144888.stderr b/tests/ui/traits/unhandled-crate-mod-issue-144888.stderr index 3208945a5b732..4cc5b41588efa 100644 --- a/tests/ui/traits/unhandled-crate-mod-issue-144888.stderr +++ b/tests/ui/traits/unhandled-crate-mod-issue-144888.stderr @@ -1,3 +1,19 @@ +error[E0271]: type mismatch resolving `::Assoc == ()` + --> $DIR/unhandled-crate-mod-issue-144888.rs:16:5 + | +LL | fn transmute(&self) {} + | ^^^^^^^^^^^^^^^^^^^ expected `()`, found type parameter `T` + | + = note: expected unit type `()` + found type parameter `T` +note: required for `RustaceansAreAwesome` to implement `Mirror` + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 + | +LL | impl> Mirror for T {} + | ---------- ^^^^^^ ^ + | | + | unsatisfied trait bound introduced here + error[E0271]: type mismatch resolving `::Assoc == ()` --> $DIR/unhandled-crate-mod-issue-144888.rs:16:5 | @@ -7,13 +23,27 @@ LL | fn transmute(&self) {} = note: expected unit type `()` found type parameter `T` note: required for `Self` to implement `Mirror` - --> $DIR/unhandled-crate-mod-issue-144888.rs:27:28 + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 | LL | impl> Mirror for T {} | ---------- ^^^^^^ ^ | | | unsatisfied trait bound introduced here +error[E0277]: the size for values of type `RustaceansAreAwesome` cannot be known at compilation time + --> $DIR/unhandled-crate-mod-issue-144888.rs:16:5 + | +LL | fn transmute(&self) {} + | ^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time + | +note: required for `RustaceansAreAwesome` to implement `Mirror` + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 + | +LL | impl> Mirror for T {} + | - ^^^^^^ ^ + | | + | unsatisfied trait bound implicitly introduced here + error[E0277]: the size for values of type `Self` cannot be known at compilation time --> $DIR/unhandled-crate-mod-issue-144888.rs:16:5 | @@ -21,7 +51,7 @@ LL | fn transmute(&self) {} | ^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | note: required for `Self` to implement `Mirror` - --> $DIR/unhandled-crate-mod-issue-144888.rs:27:28 + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 | LL | impl> Mirror for T {} | - ^^^^^^ ^ @@ -37,7 +67,7 @@ LL | trait Foo: Super = note: expected unit type `()` found type parameter `T` note: required for `Self` to implement `Mirror` - --> $DIR/unhandled-crate-mod-issue-144888.rs:27:28 + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 | LL | impl> Mirror for T {} | ---------- ^^^^^^ ^ @@ -51,7 +81,7 @@ LL | trait Foo: Super | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | note: required for `Self` to implement `Mirror` - --> $DIR/unhandled-crate-mod-issue-144888.rs:27:28 + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 | LL | impl> Mirror for T {} | - ^^^^^^ ^ @@ -63,7 +93,7 @@ LL | trait Foo: Super + Sized | +++++++ error[E0046]: not all trait items implemented, missing: `Assoc` - --> $DIR/unhandled-crate-mod-issue-144888.rs:27:1 + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:1 | LL | type Assoc; | ---------- `Assoc` from trait @@ -83,7 +113,7 @@ LL | fn transmute(&self) {} = note: expected unit type `()` found type parameter `T` note: required for `Self` to implement `Mirror` - --> $DIR/unhandled-crate-mod-issue-144888.rs:27:28 + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 | LL | impl> Mirror for T {} | ---------- ^^^^^^ ^ @@ -97,7 +127,7 @@ LL | fn transmute(&self) {} | ^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | note: required for `Self` to implement `Mirror` - --> $DIR/unhandled-crate-mod-issue-144888.rs:27:28 + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 | LL | impl> Mirror for T {} | - ^^^^^^ ^ @@ -108,7 +138,7 @@ help: consider further restricting `Self` LL | fn transmute(&self) where Self: Sized {} | +++++++++++++++++ -error: aborting due to 7 previous errors +error: aborting due to 9 previous errors Some errors have detailed explanations: E0046, E0271, E0277. For more information about an error, try `rustc --explain E0046`. From 5695ab858ef53df19d937a072dc2a6acee183219 Mon Sep 17 00:00:00 2001 From: joboet Date: Wed, 23 Sep 2026 13:44:32 +0200 Subject: [PATCH 06/18] std: split stack overflow module (duplicate) --- .../handler_cygwin.rs} | 0 .../pal/unix/stack_overflow/handler_none.rs | 844 ++++++++++++++++++ .../pal/unix/stack_overflow/handler_signal.rs | 844 ++++++++++++++++++ .../src/sys/pal/unix/stack_overflow/mod.rs | 844 ++++++++++++++++++ 4 files changed, 2532 insertions(+) rename library/std/src/sys/pal/unix/{stack_overflow.rs => stack_overflow/handler_cygwin.rs} (100%) create mode 100644 library/std/src/sys/pal/unix/stack_overflow/handler_none.rs create mode 100644 library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs create mode 100644 library/std/src/sys/pal/unix/stack_overflow/mod.rs diff --git a/library/std/src/sys/pal/unix/stack_overflow.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs similarity index 100% rename from library/std/src/sys/pal/unix/stack_overflow.rs rename to library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs new file mode 100644 index 0000000000000..5604e3e6dbf42 --- /dev/null +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs @@ -0,0 +1,844 @@ +#![cfg_attr(test, allow(dead_code))] +#![forbid(unsafe_op_in_unsafe_fn)] + +pub use self::imp::init; +use self::imp::{drop_handler, make_handler}; + +pub struct Handler { + data: *mut libc::c_void, +} + +impl Handler { + pub unsafe fn new() -> Handler { + make_handler(false) + } + + fn null() -> Handler { + Handler { data: crate::ptr::null_mut() } + } +} + +impl Drop for Handler { + fn drop(&mut self) { + unsafe { + drop_handler(self.data); + } + } +} + +#[cfg(all( + not(miri), + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ), +))] +mod thread_info; + +// miri doesn't model signals nor stack overflows and this code has some +// synchronization properties that we don't want to expose to user code, +// hence we disable it on miri. +#[cfg(all( + not(miri), + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ) +))] +mod imp { + use libc::{ + MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, + SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, + }; + #[cfg(not(all(target_os = "linux", target_env = "gnu")))] + use libc::{mmap as mmap64, mprotect, munmap}; + #[cfg(all(target_os = "linux", target_env = "gnu"))] + use libc::{mmap64, mprotect, munmap}; + + use super::Handler; + use super::thread_info::{delete_current_info, set_current_info, with_current_info}; + use crate::ops::Range; + use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; + use crate::sys::pal::unix::conf; + use crate::{io, mem, ptr}; + + /// Signal handler for the SIGSEGV and SIGBUS handlers. + /// + /// We've got guard pages (unmapped pages) at the end of every thread's + /// stack, so if a thread ends up running into the guard page it'll trigger + /// this handler. We want to detect these cases and print out a helpful error + /// saying that the stack has overflowed. All other signals, however, should + /// go back to what they were originally supposed to do. + /// + /// This handler currently exists purely to print an informative message + /// whenever a thread overflows its stack. We then abort to exit and + /// indicate a crash, but to avoid a misleading SIGSEGV that might lead + /// users to believe that unsafe code has accessed an invalid pointer; the + /// SIGSEGV encountered when overflowing the stack is expected and + /// well-defined. + /// + /// If this is not a stack overflow, the handler un-registers itself and + /// then returns (to allow the original signal to be delivered again). + /// Returning from this kind of signal handler is technically not defined + /// to work when reading the POSIX spec strictly, but in practice it turns + /// out many large systems and all implementations allow returning from a + /// signal handler to work. For a more detailed explanation see the + /// comments on #26458. + /// + /// # Safety + /// Rust doesn't call this, it *gets called* by the kernel, which we expect + /// to provide valid parameters. Apart from that, this function does not + /// have any other preconditions. + unsafe extern "C" fn signal_handler( + signum: libc::c_int, + info: *mut libc::siginfo_t, + _data: *mut libc::c_void, + ) { + // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. + let fault_addr = unsafe { (*info).si_addr().addr() }; + + // `with_current_info` expects that the process aborts after it is + // called. If the signal was not caused by a memory access, this might + // not be true. We detect this by noticing that the `si_addr` field is + // zero if the signal is synthetic. + if fault_addr != 0 { + with_current_info(|thread_info| { + // If the faulting address is within the guard page, then we print a + // message saying so and abort. + if let Some(thread_info) = thread_info + && thread_info.guard_page_range.contains(&fault_addr) + { + // Hey you! Yes, you modifying the stack overflow message! + // Please make sure that all functions called here are + // actually async-signal-safe. If they're not, try retrieving + // the information beforehand and storing it in `ThreadInfo`. + // Thank you! + // - says Jonas after having had to watch his carefully + // written code get made unsound again. + let tid = thread_info.tid; + let name = thread_info.name.as_deref().unwrap_or(""); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + rtabort!("stack overflow"); + } + }) + } + + // Unregister ourselves by reverting back to the default behavior. + // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" + let mut action: sigaction = unsafe { mem::zeroed() }; + action.sa_sigaction = SIG_DFL; + // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction + unsafe { sigaction(signum, &action, ptr::null_mut()) }; + + // See comment above for why this function returns. + } + + static PAGE_SIZE: Atomic = AtomicUsize::new(0); + // Store a pointer to the allocation for the main thread's altstack so that + // tools like valgrind don't complain about a leaked unreachable allocation. + // + // If the main thread exits, the process will terminate so there's no use in + // freeing resources. It also means that the altstack is still installed + // while TLS destructors are run on the main thread (c.f. #111272). + static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); + static NEED_ALTSTACK: Atomic = AtomicBool::new(false); + + /// # Safety + /// Must be called only once, on the main thread, during program startup. + pub unsafe fn init() { + PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); + + // SAFETY: + // This is only called on the main thread, and since it is still early + // in the programs lifetime there is (almost) certainly enough stack + // space left to install the guard page. + let mut guard_page_range = unsafe { install_main_guard() }; + + // Even for panic=immediate-abort, installing the guard pages is important for soundness. + // That said, we do not care about giving nice stackoverflow messages via our custom + // signal handler, just exit early and let the user enjoy the segfault. + if cfg!(panic = "immediate-abort") { + return; + } + + // SAFETY: C structures are always zero-initializable. + let mut action: sigaction = unsafe { mem::zeroed() }; + for &signal in &[SIGSEGV, SIGBUS] { + // SAFETY: just fetches the current signal handler into action + unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; + // We assume that overriding the signal handler is always safe, + // which might conflict with certain libraries that rely on a + // specific signal behaviour. To prevent problems, we only + // override the handler if it has not been set yet. + if action.sa_sigaction == SIG_DFL { + if !NEED_ALTSTACK.load(Ordering::Relaxed) { + // haven't set up our sigaltstack yet + NEED_ALTSTACK.store(true, Ordering::Release); + let handler = make_handler(true); + MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); + mem::forget(handler); + + if let Some(guard_page_range) = guard_page_range.take() { + set_current_info(guard_page_range); + } + } + + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + action.sa_sigaction = signal_handler + as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) + as sighandler_t; + // SAFETY: + // `&action` describes a valid `sigaction` and `signal_handler` + // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. + unsafe { sigaction(signal, &action, ptr::null_mut()) }; + } + } + } + + fn get_stack() -> libc::stack_t { + // OpenBSD requires this flag for stack mapping + // otherwise the said mapping will fail as a no-op on most systems + // and has a different meaning on FreeBSD + #[cfg(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + ))] + let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; + #[cfg(not(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + )))] + let flags = MAP_PRIVATE | MAP_ANON; + + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + + // SAFETY: this does not unmap any existing pages. + let stackp = unsafe { + mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) + }; + if stackp == MAP_FAILED { + panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); + } + // SAFETY: this only affects the memory we just allocated. + let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; + if guard_result != 0 { + panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); + } + // SAFETY: + // The region was allocated with a larger size than `page_size`, so this + // addition is within bounds. + let stackp = unsafe { stackp.add(page_size) }; + + libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } + } + + pub fn make_handler(main_thread: bool) -> Handler { + if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { + return Handler::null(); + } + + if !main_thread { + if let Some(guard_page_range) = current_guard() { + set_current_info(guard_page_range); + } + } + + // Load the current alternate signal stack to see if we need to install + // our own. + // + // SAFETY: C structures are always zero-initializable. + let mut stack = unsafe { mem::zeroed() }; + // SAFETY: `&mut stack` is valid for writing a `stack_t`. + unsafe { sigaltstack(ptr::null(), &mut stack) }; + + // Configure alternate signal stack, if one is not already set. + if stack.ss_flags & SS_DISABLE != 0 { + let stack = get_stack(); + // SAFETY: + // `stack_t` is a freshly allocated stack that's not used anywhere + // else. It contains a guard page, so stack overflows in signal + // handlers will not cause undefined behaviour. We must make the + // fundamental runtime assumption that it is safe to install an + // alternate signal stack if there is none currently installed. + // This might conflict with foreign libraries that use the existence + // of an alternate signal stack as indication that certain runtime + // initialisation by the library has been performed (e.g. old + // versions of `std` assumed that certain thread-locals were already + // accessed and thus initialized in the thread if the stack overflow + // signal was successfully delivered). Such assumptions in other + // libraries are fundamentally flawed, so we pay no regard to them. + unsafe { sigaltstack(&stack, ptr::null_mut()) }; + Handler { data: stack.ss_sp as *mut libc::c_void } + } else { + Handler::null() + } + } + + /// # Safety + /// Must only be called with a pointer returned by `make_handler`, and only + /// once per `Handler`. + pub unsafe fn drop_handler(data: *mut libc::c_void) { + if !data.is_null() { + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + let disabling_stack = libc::stack_t { + ss_sp: ptr::null_mut(), + ss_flags: SS_DISABLE, + // Workaround for bug in macOS implementation of sigaltstack + // UNIX2003 which returns ENOMEM when disabling a stack while + // passing ss_size smaller than MINSIGSTKSZ. According to POSIX + // both ss_sp and ss_size should be ignored in this case. + ss_size: sigstack_size, + }; + // SAFETY: + // We assume that disabling the alternate signal stack is always + // sound, even if the current alternate signal stack is not the one + // we installed in `make_handler`. Any stack overflows from this + // point on will abort the program when the kernel tries to write + // the signal information to the guard page. + // + // FIXME: detect if the stack has changed, and only uninstall if it hasn't. + unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; + // The stack returned by `get_stack` is part of a mapping that + // started one page earlier, so walk back a page and unmap from + // there. + // + // SAFETY: + // This allocation was created by us in `get_stack` and, as the + // alternate signal stack is now disabled, is no longer in use. + unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; + } + + delete_current_info(); + } + + /// Modern kernels on modern hardware can have dynamic signal stack sizes. + #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] + fn sigstack_size() -> usize { + // SAFETY: `getauxval` is always safe to call. + let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; + // If getauxval couldn't find the entry, it returns 0, + // so take the higher of the "constant" and auxval. + // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ + libc::SIGSTKSZ.max(dynamic_sigstksz as _) + } + + /// Not all OS support hardware where this is needed. + #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] + fn sigstack_size() -> usize { + libc::SIGSTKSZ + } + + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // The pointer is valid for writing a `stack_t`. + assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); + Some(current_stack.ss_sp) + } + + #[cfg(target_os = "macos")] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: always safe to call. + let th = unsafe { libc::pthread_self() }; + // SAFETY: `th` is a valid `pthread_t`. + unsafe { + let stackptr = libc::pthread_get_stackaddr_np(th); + let stacksize = libc::pthread_get_stacksize_np(th); + Some(stackptr.map_addr(|addr| addr - stacksize)) + } + } + + #[cfg(target_os = "openbsd")] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t`. + // * `&mut current_stack` is coerced to a pointer that is valid for writing + // a `stack_t`. + assert_eq!( + unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, + 0 + ); + + let stack_ptr = current_stack.ss_sp; + // SAFETY: this is always safe to call. + let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { + // main thread + stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) + } else { + // new thread + stack_ptr.addr() - current_stack.ss_size + }; + Some(stack_ptr.with_addr(stackaddr)) + } + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "hurd", + target_os = "linux", + target_os = "l4re" + ))] + fn get_stack_start() -> Option<*mut libc::c_void> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); + + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut stackaddr = crate::ptr::null_mut(); + let mut stacksize = 0; + // SAFETY: + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!( + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, + 0 + ); + ret = Some(stackaddr); + } + if e == 0 || cfg!(target_os = "freebsd") { + // SAFETY: + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + } + ret + } + + fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + + // Ensure stackaddr is page aligned! A parent process might + // have reset RLIMIT_STACK to be non-page aligned. The + // pthread_attr_getstack() reports the usable stack area + // stackaddr < stackaddr + stacksize, so if stackaddr is not + // page-aligned, calculate the fix such that stackaddr < + // new_page_aligned_stackaddr < stackaddr + stacksize + let remainder = stackaddr % page_size; + Some(if remainder == 0 { + stackptr + } else { + stackptr.with_addr(stackaddr + page_size - remainder) + }) + } + + /// # Safety + /// This function must only be called from the main thread, and there must + /// be sufficient stack space remaining to place a stack guard. + unsafe fn install_main_guard() -> Option> { + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + + // this way someone on any unix-y OS can check that all these compile + if cfg!(all(target_os = "linux", not(target_env = "musl"))) { + install_main_guard_linux(page_size) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + install_main_guard_linux_musl(page_size) + } else if cfg!(target_os = "freebsd") { + #[cfg(not(target_os = "freebsd"))] + return None; + // The FreeBSD code cannot be checked on non-BSDs. + #[cfg(target_os = "freebsd")] + install_main_guard_freebsd(page_size) + } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { + install_main_guard_bsds(page_size) + } else { + // SAFETY: guaranteed by caller. + unsafe { install_main_guard_default(page_size) } + } + } + + fn install_main_guard_linux(page_size: usize) -> Option> { + // See the corresponding conditional in init(). + // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps + if cfg!(panic = "immediate-abort") { + return None; + } + // Linux doesn't allocate the whole stack right away, and + // the kernel has its own stack-guard mechanism to fault + // when growing too close to an existing mapping. If we map + // our own guard, then the kernel starts enforcing a rather + // large gap above that, rendering much of the possible + // stack space useless. See #43052. + // + // Instead, we'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } + + fn install_main_guard_linux_musl(_page_size: usize) -> Option> { + // For the main thread, the musl's pthread_attr_getstack + // returns the current stack size, rather than maximum size + // it can eventually grow to. It cannot be used to determine + // the position of kernel's stack guard. + None + } + + #[cfg(target_os = "freebsd")] + fn install_main_guard_freebsd(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; + } + // FreeBSD's stack autogrows, and optionally includes a guard page + // at the bottom. If we try to remap the bottom of the stack + // ourselves, FreeBSD's guard page moves upwards. So we'll just use + // the builtin guard page. + let stackptr = stack_start_aligned(page_size)?; + let guardaddr = stackptr.addr(); + // Technically the number of guard pages is tunable and controlled + // by the security.bsd.stack_guard_page sysctl. + // By default it is 1, checking once is enough since it is + // a boot time config value. + // FIXME(joboet): this function is only called once, remove the caching. + static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); + + let pages = PAGES.get_or_init(|| { + let mut guard: usize = 0; + let mut size = size_of_val(&guard); + let oid = c"security.bsd.stack_guard_page"; + + let r = unsafe { + libc::sysctlbyname( + oid.as_ptr(), + (&raw mut guard).cast(), + &raw mut size, + ptr::null_mut(), + 0, + ) + }; + if r == 0 { guard } else { 1 } + }); + Some(guardaddr..guardaddr + pages * page_size) + } + + fn install_main_guard_bsds(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; + } + // OpenBSD stack already includes a guard page, and stack is + // immutable. + // NetBSD stack includes the guard page. + // + // We'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } + + /// # Safety + /// This function must only be called from the main thread, and there must + /// be sufficient stack space remaining to place a stack guard. + unsafe fn install_main_guard_default(page_size: usize) -> Option> { + // Reallocate the last page of the stack. + // This ensures SIGBUS will be raised on + // stack overflow. + // Systems which enforce strict PAX MPROTECT do not allow + // to mprotect() a mapping with less restrictive permissions + // than the initial mmap() used, so we mmap() here with + // read/write permissions and only then mprotect() it to + // no permissions at all. See issue #50313. + let stackptr = stack_start_aligned(page_size)?; + // SAFETY: + // The memory region from `stackptr..stackptr + page_size` belongs to + // the current thread's stack, and the caller has asserted that there + // is sufficient stack space, which means that this will not overwrite + // any existing allocations. + let result = unsafe { + mmap64( + stackptr, + page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + -1, + 0, + ) + }; + if result != stackptr || result == MAP_FAILED { + panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); + } + + // SAFETY: + // Since this function is only called on the main thread, the stack will + // not be reused until program exit, so the runtime will never observe + // that part of the stack has been made unusable in this way. + let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; + if result != 0 { + panic!("failed to protect the guard page: {}", io::Error::last_os_error()); + } + + let guardaddr = stackptr.addr(); + + Some(guardaddr..guardaddr + page_size) + } + + #[cfg(any( + target_os = "macos", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ))] + fn current_guard() -> Option> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) + } + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "hurd", + target_os = "linux", + target_os = "netbsd", + target_os = "l4re" + ))] + fn current_guard() -> Option> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); + + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut guardsize = 0; + // SAFETY: + // `attr` is an initialized attribute object and the pointer is valid + // for writing. + assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); + if guardsize == 0 { + if cfg!(all(target_os = "linux", target_env = "musl")) { + // musl versions before 1.1.19 always reported guard + // size obtained from pthread_attr_get_np as zero. + // Use page size as a fallback. + guardsize = PAGE_SIZE.load(Ordering::Relaxed); + } else { + panic!("there is no guard page"); + } + } + let mut stackptr = crate::ptr::null_mut::(); + let mut size = 0; + // SAFETY: + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!( + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, + 0 + ); + + let stackaddr = stackptr.addr(); + ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) + { + // glibc used to include the guard area within the stack, as noted in the BUGS + // section of `man pthread_attr_getguardsize`. This has been corrected starting + // with glibc 2.27, and in some distro backports, so the guard is now placed at the + // end (below) the stack. There's no easy way for us to know which we have at + // runtime, so we'll just match any fault in the range right above or below the + // stack base to call that fault a stack overflow. + Some(stackaddr - guardsize..stackaddr + guardsize) + } else { + Some(stackaddr..stackaddr + guardsize) + }; + } + if e == 0 || cfg!(target_os = "freebsd") { + // SAFETY: + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + } + ret + } +} + +// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses +// several symbols that might lead to rejections from the App Store, namely +// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. +// +// This might be overly cautious, though it is also what Swift does (and they +// usually have fewer qualms about forwards compatibility, since the runtime +// is shipped with the OS): +// +#[cfg(any( + miri, + not(any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + target_os = "cygwin", + )) +))] +mod imp { + pub unsafe fn init() {} + + pub fn make_handler(_main_thread: bool) -> super::Handler { + super::Handler::null() + } + + pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +} + +#[cfg(target_os = "cygwin")] +mod imp { + mod c { + pub type PVECTORED_EXCEPTION_HANDLER = + Option i32>; + pub type NTSTATUS = i32; + pub type BOOL = i32; + + unsafe extern "system" { + pub fn AddVectoredExceptionHandler( + first: u32, + handler: PVECTORED_EXCEPTION_HANDLER, + ) -> *mut core::ffi::c_void; + pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; + } + + pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; + pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; + + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_POINTERS { + pub ExceptionRecord: *mut EXCEPTION_RECORD, + // We don't need this field here + // pub Context: *mut CONTEXT, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_RECORD { + pub ExceptionCode: NTSTATUS, + pub ExceptionFlags: u32, + pub ExceptionRecord: *mut EXCEPTION_RECORD, + pub ExceptionAddress: *mut core::ffi::c_void, + pub NumberParameters: u32, + pub ExceptionInformation: [usize; 15], + } + } + + /// Reserve stack space for use in stack overflow exceptions. + fn reserve_stack() { + let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; + // Reserving stack space is not critical so we allow it to fail in the released build of libstd. + // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. + debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); + } + + unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { + // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. + unsafe { + let rec = &(*(*ExceptionInfo).ExceptionRecord); + let code = rec.ExceptionCode; + + if code == c::EXCEPTION_STACK_OVERFLOW { + crate::thread::with_current_name(|name| { + let name = name.unwrap_or(""); + let tid = crate::thread::current_os_id(); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + }); + } + c::EXCEPTION_CONTINUE_SEARCH + } + } + + pub unsafe fn init() { + // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. + unsafe { + let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); + // Similar to the above, adding the stack overflow handler is allowed to fail + // but a debug assert is used so CI will still test that it normally works. + debug_assert!(!result.is_null(), "failed to install exception handler"); + } + // Set the thread stack guarantee for the main thread. + reserve_stack(); + } + + pub fn make_handler(main_thread: bool) -> super::Handler { + if !main_thread { + reserve_stack(); + } + super::Handler::null() + } + + pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +} diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs new file mode 100644 index 0000000000000..5604e3e6dbf42 --- /dev/null +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs @@ -0,0 +1,844 @@ +#![cfg_attr(test, allow(dead_code))] +#![forbid(unsafe_op_in_unsafe_fn)] + +pub use self::imp::init; +use self::imp::{drop_handler, make_handler}; + +pub struct Handler { + data: *mut libc::c_void, +} + +impl Handler { + pub unsafe fn new() -> Handler { + make_handler(false) + } + + fn null() -> Handler { + Handler { data: crate::ptr::null_mut() } + } +} + +impl Drop for Handler { + fn drop(&mut self) { + unsafe { + drop_handler(self.data); + } + } +} + +#[cfg(all( + not(miri), + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ), +))] +mod thread_info; + +// miri doesn't model signals nor stack overflows and this code has some +// synchronization properties that we don't want to expose to user code, +// hence we disable it on miri. +#[cfg(all( + not(miri), + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ) +))] +mod imp { + use libc::{ + MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, + SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, + }; + #[cfg(not(all(target_os = "linux", target_env = "gnu")))] + use libc::{mmap as mmap64, mprotect, munmap}; + #[cfg(all(target_os = "linux", target_env = "gnu"))] + use libc::{mmap64, mprotect, munmap}; + + use super::Handler; + use super::thread_info::{delete_current_info, set_current_info, with_current_info}; + use crate::ops::Range; + use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; + use crate::sys::pal::unix::conf; + use crate::{io, mem, ptr}; + + /// Signal handler for the SIGSEGV and SIGBUS handlers. + /// + /// We've got guard pages (unmapped pages) at the end of every thread's + /// stack, so if a thread ends up running into the guard page it'll trigger + /// this handler. We want to detect these cases and print out a helpful error + /// saying that the stack has overflowed. All other signals, however, should + /// go back to what they were originally supposed to do. + /// + /// This handler currently exists purely to print an informative message + /// whenever a thread overflows its stack. We then abort to exit and + /// indicate a crash, but to avoid a misleading SIGSEGV that might lead + /// users to believe that unsafe code has accessed an invalid pointer; the + /// SIGSEGV encountered when overflowing the stack is expected and + /// well-defined. + /// + /// If this is not a stack overflow, the handler un-registers itself and + /// then returns (to allow the original signal to be delivered again). + /// Returning from this kind of signal handler is technically not defined + /// to work when reading the POSIX spec strictly, but in practice it turns + /// out many large systems and all implementations allow returning from a + /// signal handler to work. For a more detailed explanation see the + /// comments on #26458. + /// + /// # Safety + /// Rust doesn't call this, it *gets called* by the kernel, which we expect + /// to provide valid parameters. Apart from that, this function does not + /// have any other preconditions. + unsafe extern "C" fn signal_handler( + signum: libc::c_int, + info: *mut libc::siginfo_t, + _data: *mut libc::c_void, + ) { + // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. + let fault_addr = unsafe { (*info).si_addr().addr() }; + + // `with_current_info` expects that the process aborts after it is + // called. If the signal was not caused by a memory access, this might + // not be true. We detect this by noticing that the `si_addr` field is + // zero if the signal is synthetic. + if fault_addr != 0 { + with_current_info(|thread_info| { + // If the faulting address is within the guard page, then we print a + // message saying so and abort. + if let Some(thread_info) = thread_info + && thread_info.guard_page_range.contains(&fault_addr) + { + // Hey you! Yes, you modifying the stack overflow message! + // Please make sure that all functions called here are + // actually async-signal-safe. If they're not, try retrieving + // the information beforehand and storing it in `ThreadInfo`. + // Thank you! + // - says Jonas after having had to watch his carefully + // written code get made unsound again. + let tid = thread_info.tid; + let name = thread_info.name.as_deref().unwrap_or(""); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + rtabort!("stack overflow"); + } + }) + } + + // Unregister ourselves by reverting back to the default behavior. + // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" + let mut action: sigaction = unsafe { mem::zeroed() }; + action.sa_sigaction = SIG_DFL; + // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction + unsafe { sigaction(signum, &action, ptr::null_mut()) }; + + // See comment above for why this function returns. + } + + static PAGE_SIZE: Atomic = AtomicUsize::new(0); + // Store a pointer to the allocation for the main thread's altstack so that + // tools like valgrind don't complain about a leaked unreachable allocation. + // + // If the main thread exits, the process will terminate so there's no use in + // freeing resources. It also means that the altstack is still installed + // while TLS destructors are run on the main thread (c.f. #111272). + static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); + static NEED_ALTSTACK: Atomic = AtomicBool::new(false); + + /// # Safety + /// Must be called only once, on the main thread, during program startup. + pub unsafe fn init() { + PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); + + // SAFETY: + // This is only called on the main thread, and since it is still early + // in the programs lifetime there is (almost) certainly enough stack + // space left to install the guard page. + let mut guard_page_range = unsafe { install_main_guard() }; + + // Even for panic=immediate-abort, installing the guard pages is important for soundness. + // That said, we do not care about giving nice stackoverflow messages via our custom + // signal handler, just exit early and let the user enjoy the segfault. + if cfg!(panic = "immediate-abort") { + return; + } + + // SAFETY: C structures are always zero-initializable. + let mut action: sigaction = unsafe { mem::zeroed() }; + for &signal in &[SIGSEGV, SIGBUS] { + // SAFETY: just fetches the current signal handler into action + unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; + // We assume that overriding the signal handler is always safe, + // which might conflict with certain libraries that rely on a + // specific signal behaviour. To prevent problems, we only + // override the handler if it has not been set yet. + if action.sa_sigaction == SIG_DFL { + if !NEED_ALTSTACK.load(Ordering::Relaxed) { + // haven't set up our sigaltstack yet + NEED_ALTSTACK.store(true, Ordering::Release); + let handler = make_handler(true); + MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); + mem::forget(handler); + + if let Some(guard_page_range) = guard_page_range.take() { + set_current_info(guard_page_range); + } + } + + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + action.sa_sigaction = signal_handler + as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) + as sighandler_t; + // SAFETY: + // `&action` describes a valid `sigaction` and `signal_handler` + // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. + unsafe { sigaction(signal, &action, ptr::null_mut()) }; + } + } + } + + fn get_stack() -> libc::stack_t { + // OpenBSD requires this flag for stack mapping + // otherwise the said mapping will fail as a no-op on most systems + // and has a different meaning on FreeBSD + #[cfg(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + ))] + let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; + #[cfg(not(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + )))] + let flags = MAP_PRIVATE | MAP_ANON; + + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + + // SAFETY: this does not unmap any existing pages. + let stackp = unsafe { + mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) + }; + if stackp == MAP_FAILED { + panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); + } + // SAFETY: this only affects the memory we just allocated. + let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; + if guard_result != 0 { + panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); + } + // SAFETY: + // The region was allocated with a larger size than `page_size`, so this + // addition is within bounds. + let stackp = unsafe { stackp.add(page_size) }; + + libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } + } + + pub fn make_handler(main_thread: bool) -> Handler { + if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { + return Handler::null(); + } + + if !main_thread { + if let Some(guard_page_range) = current_guard() { + set_current_info(guard_page_range); + } + } + + // Load the current alternate signal stack to see if we need to install + // our own. + // + // SAFETY: C structures are always zero-initializable. + let mut stack = unsafe { mem::zeroed() }; + // SAFETY: `&mut stack` is valid for writing a `stack_t`. + unsafe { sigaltstack(ptr::null(), &mut stack) }; + + // Configure alternate signal stack, if one is not already set. + if stack.ss_flags & SS_DISABLE != 0 { + let stack = get_stack(); + // SAFETY: + // `stack_t` is a freshly allocated stack that's not used anywhere + // else. It contains a guard page, so stack overflows in signal + // handlers will not cause undefined behaviour. We must make the + // fundamental runtime assumption that it is safe to install an + // alternate signal stack if there is none currently installed. + // This might conflict with foreign libraries that use the existence + // of an alternate signal stack as indication that certain runtime + // initialisation by the library has been performed (e.g. old + // versions of `std` assumed that certain thread-locals were already + // accessed and thus initialized in the thread if the stack overflow + // signal was successfully delivered). Such assumptions in other + // libraries are fundamentally flawed, so we pay no regard to them. + unsafe { sigaltstack(&stack, ptr::null_mut()) }; + Handler { data: stack.ss_sp as *mut libc::c_void } + } else { + Handler::null() + } + } + + /// # Safety + /// Must only be called with a pointer returned by `make_handler`, and only + /// once per `Handler`. + pub unsafe fn drop_handler(data: *mut libc::c_void) { + if !data.is_null() { + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + let disabling_stack = libc::stack_t { + ss_sp: ptr::null_mut(), + ss_flags: SS_DISABLE, + // Workaround for bug in macOS implementation of sigaltstack + // UNIX2003 which returns ENOMEM when disabling a stack while + // passing ss_size smaller than MINSIGSTKSZ. According to POSIX + // both ss_sp and ss_size should be ignored in this case. + ss_size: sigstack_size, + }; + // SAFETY: + // We assume that disabling the alternate signal stack is always + // sound, even if the current alternate signal stack is not the one + // we installed in `make_handler`. Any stack overflows from this + // point on will abort the program when the kernel tries to write + // the signal information to the guard page. + // + // FIXME: detect if the stack has changed, and only uninstall if it hasn't. + unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; + // The stack returned by `get_stack` is part of a mapping that + // started one page earlier, so walk back a page and unmap from + // there. + // + // SAFETY: + // This allocation was created by us in `get_stack` and, as the + // alternate signal stack is now disabled, is no longer in use. + unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; + } + + delete_current_info(); + } + + /// Modern kernels on modern hardware can have dynamic signal stack sizes. + #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] + fn sigstack_size() -> usize { + // SAFETY: `getauxval` is always safe to call. + let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; + // If getauxval couldn't find the entry, it returns 0, + // so take the higher of the "constant" and auxval. + // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ + libc::SIGSTKSZ.max(dynamic_sigstksz as _) + } + + /// Not all OS support hardware where this is needed. + #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] + fn sigstack_size() -> usize { + libc::SIGSTKSZ + } + + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // The pointer is valid for writing a `stack_t`. + assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); + Some(current_stack.ss_sp) + } + + #[cfg(target_os = "macos")] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: always safe to call. + let th = unsafe { libc::pthread_self() }; + // SAFETY: `th` is a valid `pthread_t`. + unsafe { + let stackptr = libc::pthread_get_stackaddr_np(th); + let stacksize = libc::pthread_get_stacksize_np(th); + Some(stackptr.map_addr(|addr| addr - stacksize)) + } + } + + #[cfg(target_os = "openbsd")] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t`. + // * `&mut current_stack` is coerced to a pointer that is valid for writing + // a `stack_t`. + assert_eq!( + unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, + 0 + ); + + let stack_ptr = current_stack.ss_sp; + // SAFETY: this is always safe to call. + let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { + // main thread + stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) + } else { + // new thread + stack_ptr.addr() - current_stack.ss_size + }; + Some(stack_ptr.with_addr(stackaddr)) + } + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "hurd", + target_os = "linux", + target_os = "l4re" + ))] + fn get_stack_start() -> Option<*mut libc::c_void> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); + + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut stackaddr = crate::ptr::null_mut(); + let mut stacksize = 0; + // SAFETY: + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!( + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, + 0 + ); + ret = Some(stackaddr); + } + if e == 0 || cfg!(target_os = "freebsd") { + // SAFETY: + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + } + ret + } + + fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + + // Ensure stackaddr is page aligned! A parent process might + // have reset RLIMIT_STACK to be non-page aligned. The + // pthread_attr_getstack() reports the usable stack area + // stackaddr < stackaddr + stacksize, so if stackaddr is not + // page-aligned, calculate the fix such that stackaddr < + // new_page_aligned_stackaddr < stackaddr + stacksize + let remainder = stackaddr % page_size; + Some(if remainder == 0 { + stackptr + } else { + stackptr.with_addr(stackaddr + page_size - remainder) + }) + } + + /// # Safety + /// This function must only be called from the main thread, and there must + /// be sufficient stack space remaining to place a stack guard. + unsafe fn install_main_guard() -> Option> { + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + + // this way someone on any unix-y OS can check that all these compile + if cfg!(all(target_os = "linux", not(target_env = "musl"))) { + install_main_guard_linux(page_size) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + install_main_guard_linux_musl(page_size) + } else if cfg!(target_os = "freebsd") { + #[cfg(not(target_os = "freebsd"))] + return None; + // The FreeBSD code cannot be checked on non-BSDs. + #[cfg(target_os = "freebsd")] + install_main_guard_freebsd(page_size) + } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { + install_main_guard_bsds(page_size) + } else { + // SAFETY: guaranteed by caller. + unsafe { install_main_guard_default(page_size) } + } + } + + fn install_main_guard_linux(page_size: usize) -> Option> { + // See the corresponding conditional in init(). + // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps + if cfg!(panic = "immediate-abort") { + return None; + } + // Linux doesn't allocate the whole stack right away, and + // the kernel has its own stack-guard mechanism to fault + // when growing too close to an existing mapping. If we map + // our own guard, then the kernel starts enforcing a rather + // large gap above that, rendering much of the possible + // stack space useless. See #43052. + // + // Instead, we'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } + + fn install_main_guard_linux_musl(_page_size: usize) -> Option> { + // For the main thread, the musl's pthread_attr_getstack + // returns the current stack size, rather than maximum size + // it can eventually grow to. It cannot be used to determine + // the position of kernel's stack guard. + None + } + + #[cfg(target_os = "freebsd")] + fn install_main_guard_freebsd(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; + } + // FreeBSD's stack autogrows, and optionally includes a guard page + // at the bottom. If we try to remap the bottom of the stack + // ourselves, FreeBSD's guard page moves upwards. So we'll just use + // the builtin guard page. + let stackptr = stack_start_aligned(page_size)?; + let guardaddr = stackptr.addr(); + // Technically the number of guard pages is tunable and controlled + // by the security.bsd.stack_guard_page sysctl. + // By default it is 1, checking once is enough since it is + // a boot time config value. + // FIXME(joboet): this function is only called once, remove the caching. + static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); + + let pages = PAGES.get_or_init(|| { + let mut guard: usize = 0; + let mut size = size_of_val(&guard); + let oid = c"security.bsd.stack_guard_page"; + + let r = unsafe { + libc::sysctlbyname( + oid.as_ptr(), + (&raw mut guard).cast(), + &raw mut size, + ptr::null_mut(), + 0, + ) + }; + if r == 0 { guard } else { 1 } + }); + Some(guardaddr..guardaddr + pages * page_size) + } + + fn install_main_guard_bsds(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; + } + // OpenBSD stack already includes a guard page, and stack is + // immutable. + // NetBSD stack includes the guard page. + // + // We'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } + + /// # Safety + /// This function must only be called from the main thread, and there must + /// be sufficient stack space remaining to place a stack guard. + unsafe fn install_main_guard_default(page_size: usize) -> Option> { + // Reallocate the last page of the stack. + // This ensures SIGBUS will be raised on + // stack overflow. + // Systems which enforce strict PAX MPROTECT do not allow + // to mprotect() a mapping with less restrictive permissions + // than the initial mmap() used, so we mmap() here with + // read/write permissions and only then mprotect() it to + // no permissions at all. See issue #50313. + let stackptr = stack_start_aligned(page_size)?; + // SAFETY: + // The memory region from `stackptr..stackptr + page_size` belongs to + // the current thread's stack, and the caller has asserted that there + // is sufficient stack space, which means that this will not overwrite + // any existing allocations. + let result = unsafe { + mmap64( + stackptr, + page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + -1, + 0, + ) + }; + if result != stackptr || result == MAP_FAILED { + panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); + } + + // SAFETY: + // Since this function is only called on the main thread, the stack will + // not be reused until program exit, so the runtime will never observe + // that part of the stack has been made unusable in this way. + let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; + if result != 0 { + panic!("failed to protect the guard page: {}", io::Error::last_os_error()); + } + + let guardaddr = stackptr.addr(); + + Some(guardaddr..guardaddr + page_size) + } + + #[cfg(any( + target_os = "macos", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ))] + fn current_guard() -> Option> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) + } + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "hurd", + target_os = "linux", + target_os = "netbsd", + target_os = "l4re" + ))] + fn current_guard() -> Option> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); + + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut guardsize = 0; + // SAFETY: + // `attr` is an initialized attribute object and the pointer is valid + // for writing. + assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); + if guardsize == 0 { + if cfg!(all(target_os = "linux", target_env = "musl")) { + // musl versions before 1.1.19 always reported guard + // size obtained from pthread_attr_get_np as zero. + // Use page size as a fallback. + guardsize = PAGE_SIZE.load(Ordering::Relaxed); + } else { + panic!("there is no guard page"); + } + } + let mut stackptr = crate::ptr::null_mut::(); + let mut size = 0; + // SAFETY: + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!( + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, + 0 + ); + + let stackaddr = stackptr.addr(); + ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) + { + // glibc used to include the guard area within the stack, as noted in the BUGS + // section of `man pthread_attr_getguardsize`. This has been corrected starting + // with glibc 2.27, and in some distro backports, so the guard is now placed at the + // end (below) the stack. There's no easy way for us to know which we have at + // runtime, so we'll just match any fault in the range right above or below the + // stack base to call that fault a stack overflow. + Some(stackaddr - guardsize..stackaddr + guardsize) + } else { + Some(stackaddr..stackaddr + guardsize) + }; + } + if e == 0 || cfg!(target_os = "freebsd") { + // SAFETY: + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + } + ret + } +} + +// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses +// several symbols that might lead to rejections from the App Store, namely +// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. +// +// This might be overly cautious, though it is also what Swift does (and they +// usually have fewer qualms about forwards compatibility, since the runtime +// is shipped with the OS): +// +#[cfg(any( + miri, + not(any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + target_os = "cygwin", + )) +))] +mod imp { + pub unsafe fn init() {} + + pub fn make_handler(_main_thread: bool) -> super::Handler { + super::Handler::null() + } + + pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +} + +#[cfg(target_os = "cygwin")] +mod imp { + mod c { + pub type PVECTORED_EXCEPTION_HANDLER = + Option i32>; + pub type NTSTATUS = i32; + pub type BOOL = i32; + + unsafe extern "system" { + pub fn AddVectoredExceptionHandler( + first: u32, + handler: PVECTORED_EXCEPTION_HANDLER, + ) -> *mut core::ffi::c_void; + pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; + } + + pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; + pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; + + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_POINTERS { + pub ExceptionRecord: *mut EXCEPTION_RECORD, + // We don't need this field here + // pub Context: *mut CONTEXT, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_RECORD { + pub ExceptionCode: NTSTATUS, + pub ExceptionFlags: u32, + pub ExceptionRecord: *mut EXCEPTION_RECORD, + pub ExceptionAddress: *mut core::ffi::c_void, + pub NumberParameters: u32, + pub ExceptionInformation: [usize; 15], + } + } + + /// Reserve stack space for use in stack overflow exceptions. + fn reserve_stack() { + let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; + // Reserving stack space is not critical so we allow it to fail in the released build of libstd. + // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. + debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); + } + + unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { + // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. + unsafe { + let rec = &(*(*ExceptionInfo).ExceptionRecord); + let code = rec.ExceptionCode; + + if code == c::EXCEPTION_STACK_OVERFLOW { + crate::thread::with_current_name(|name| { + let name = name.unwrap_or(""); + let tid = crate::thread::current_os_id(); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + }); + } + c::EXCEPTION_CONTINUE_SEARCH + } + } + + pub unsafe fn init() { + // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. + unsafe { + let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); + // Similar to the above, adding the stack overflow handler is allowed to fail + // but a debug assert is used so CI will still test that it normally works. + debug_assert!(!result.is_null(), "failed to install exception handler"); + } + // Set the thread stack guarantee for the main thread. + reserve_stack(); + } + + pub fn make_handler(main_thread: bool) -> super::Handler { + if !main_thread { + reserve_stack(); + } + super::Handler::null() + } + + pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +} diff --git a/library/std/src/sys/pal/unix/stack_overflow/mod.rs b/library/std/src/sys/pal/unix/stack_overflow/mod.rs new file mode 100644 index 0000000000000..5604e3e6dbf42 --- /dev/null +++ b/library/std/src/sys/pal/unix/stack_overflow/mod.rs @@ -0,0 +1,844 @@ +#![cfg_attr(test, allow(dead_code))] +#![forbid(unsafe_op_in_unsafe_fn)] + +pub use self::imp::init; +use self::imp::{drop_handler, make_handler}; + +pub struct Handler { + data: *mut libc::c_void, +} + +impl Handler { + pub unsafe fn new() -> Handler { + make_handler(false) + } + + fn null() -> Handler { + Handler { data: crate::ptr::null_mut() } + } +} + +impl Drop for Handler { + fn drop(&mut self) { + unsafe { + drop_handler(self.data); + } + } +} + +#[cfg(all( + not(miri), + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ), +))] +mod thread_info; + +// miri doesn't model signals nor stack overflows and this code has some +// synchronization properties that we don't want to expose to user code, +// hence we disable it on miri. +#[cfg(all( + not(miri), + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ) +))] +mod imp { + use libc::{ + MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, + SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, + }; + #[cfg(not(all(target_os = "linux", target_env = "gnu")))] + use libc::{mmap as mmap64, mprotect, munmap}; + #[cfg(all(target_os = "linux", target_env = "gnu"))] + use libc::{mmap64, mprotect, munmap}; + + use super::Handler; + use super::thread_info::{delete_current_info, set_current_info, with_current_info}; + use crate::ops::Range; + use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; + use crate::sys::pal::unix::conf; + use crate::{io, mem, ptr}; + + /// Signal handler for the SIGSEGV and SIGBUS handlers. + /// + /// We've got guard pages (unmapped pages) at the end of every thread's + /// stack, so if a thread ends up running into the guard page it'll trigger + /// this handler. We want to detect these cases and print out a helpful error + /// saying that the stack has overflowed. All other signals, however, should + /// go back to what they were originally supposed to do. + /// + /// This handler currently exists purely to print an informative message + /// whenever a thread overflows its stack. We then abort to exit and + /// indicate a crash, but to avoid a misleading SIGSEGV that might lead + /// users to believe that unsafe code has accessed an invalid pointer; the + /// SIGSEGV encountered when overflowing the stack is expected and + /// well-defined. + /// + /// If this is not a stack overflow, the handler un-registers itself and + /// then returns (to allow the original signal to be delivered again). + /// Returning from this kind of signal handler is technically not defined + /// to work when reading the POSIX spec strictly, but in practice it turns + /// out many large systems and all implementations allow returning from a + /// signal handler to work. For a more detailed explanation see the + /// comments on #26458. + /// + /// # Safety + /// Rust doesn't call this, it *gets called* by the kernel, which we expect + /// to provide valid parameters. Apart from that, this function does not + /// have any other preconditions. + unsafe extern "C" fn signal_handler( + signum: libc::c_int, + info: *mut libc::siginfo_t, + _data: *mut libc::c_void, + ) { + // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. + let fault_addr = unsafe { (*info).si_addr().addr() }; + + // `with_current_info` expects that the process aborts after it is + // called. If the signal was not caused by a memory access, this might + // not be true. We detect this by noticing that the `si_addr` field is + // zero if the signal is synthetic. + if fault_addr != 0 { + with_current_info(|thread_info| { + // If the faulting address is within the guard page, then we print a + // message saying so and abort. + if let Some(thread_info) = thread_info + && thread_info.guard_page_range.contains(&fault_addr) + { + // Hey you! Yes, you modifying the stack overflow message! + // Please make sure that all functions called here are + // actually async-signal-safe. If they're not, try retrieving + // the information beforehand and storing it in `ThreadInfo`. + // Thank you! + // - says Jonas after having had to watch his carefully + // written code get made unsound again. + let tid = thread_info.tid; + let name = thread_info.name.as_deref().unwrap_or(""); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + rtabort!("stack overflow"); + } + }) + } + + // Unregister ourselves by reverting back to the default behavior. + // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" + let mut action: sigaction = unsafe { mem::zeroed() }; + action.sa_sigaction = SIG_DFL; + // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction + unsafe { sigaction(signum, &action, ptr::null_mut()) }; + + // See comment above for why this function returns. + } + + static PAGE_SIZE: Atomic = AtomicUsize::new(0); + // Store a pointer to the allocation for the main thread's altstack so that + // tools like valgrind don't complain about a leaked unreachable allocation. + // + // If the main thread exits, the process will terminate so there's no use in + // freeing resources. It also means that the altstack is still installed + // while TLS destructors are run on the main thread (c.f. #111272). + static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); + static NEED_ALTSTACK: Atomic = AtomicBool::new(false); + + /// # Safety + /// Must be called only once, on the main thread, during program startup. + pub unsafe fn init() { + PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); + + // SAFETY: + // This is only called on the main thread, and since it is still early + // in the programs lifetime there is (almost) certainly enough stack + // space left to install the guard page. + let mut guard_page_range = unsafe { install_main_guard() }; + + // Even for panic=immediate-abort, installing the guard pages is important for soundness. + // That said, we do not care about giving nice stackoverflow messages via our custom + // signal handler, just exit early and let the user enjoy the segfault. + if cfg!(panic = "immediate-abort") { + return; + } + + // SAFETY: C structures are always zero-initializable. + let mut action: sigaction = unsafe { mem::zeroed() }; + for &signal in &[SIGSEGV, SIGBUS] { + // SAFETY: just fetches the current signal handler into action + unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; + // We assume that overriding the signal handler is always safe, + // which might conflict with certain libraries that rely on a + // specific signal behaviour. To prevent problems, we only + // override the handler if it has not been set yet. + if action.sa_sigaction == SIG_DFL { + if !NEED_ALTSTACK.load(Ordering::Relaxed) { + // haven't set up our sigaltstack yet + NEED_ALTSTACK.store(true, Ordering::Release); + let handler = make_handler(true); + MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); + mem::forget(handler); + + if let Some(guard_page_range) = guard_page_range.take() { + set_current_info(guard_page_range); + } + } + + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + action.sa_sigaction = signal_handler + as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) + as sighandler_t; + // SAFETY: + // `&action` describes a valid `sigaction` and `signal_handler` + // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. + unsafe { sigaction(signal, &action, ptr::null_mut()) }; + } + } + } + + fn get_stack() -> libc::stack_t { + // OpenBSD requires this flag for stack mapping + // otherwise the said mapping will fail as a no-op on most systems + // and has a different meaning on FreeBSD + #[cfg(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + ))] + let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; + #[cfg(not(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + )))] + let flags = MAP_PRIVATE | MAP_ANON; + + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + + // SAFETY: this does not unmap any existing pages. + let stackp = unsafe { + mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) + }; + if stackp == MAP_FAILED { + panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); + } + // SAFETY: this only affects the memory we just allocated. + let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; + if guard_result != 0 { + panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); + } + // SAFETY: + // The region was allocated with a larger size than `page_size`, so this + // addition is within bounds. + let stackp = unsafe { stackp.add(page_size) }; + + libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } + } + + pub fn make_handler(main_thread: bool) -> Handler { + if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { + return Handler::null(); + } + + if !main_thread { + if let Some(guard_page_range) = current_guard() { + set_current_info(guard_page_range); + } + } + + // Load the current alternate signal stack to see if we need to install + // our own. + // + // SAFETY: C structures are always zero-initializable. + let mut stack = unsafe { mem::zeroed() }; + // SAFETY: `&mut stack` is valid for writing a `stack_t`. + unsafe { sigaltstack(ptr::null(), &mut stack) }; + + // Configure alternate signal stack, if one is not already set. + if stack.ss_flags & SS_DISABLE != 0 { + let stack = get_stack(); + // SAFETY: + // `stack_t` is a freshly allocated stack that's not used anywhere + // else. It contains a guard page, so stack overflows in signal + // handlers will not cause undefined behaviour. We must make the + // fundamental runtime assumption that it is safe to install an + // alternate signal stack if there is none currently installed. + // This might conflict with foreign libraries that use the existence + // of an alternate signal stack as indication that certain runtime + // initialisation by the library has been performed (e.g. old + // versions of `std` assumed that certain thread-locals were already + // accessed and thus initialized in the thread if the stack overflow + // signal was successfully delivered). Such assumptions in other + // libraries are fundamentally flawed, so we pay no regard to them. + unsafe { sigaltstack(&stack, ptr::null_mut()) }; + Handler { data: stack.ss_sp as *mut libc::c_void } + } else { + Handler::null() + } + } + + /// # Safety + /// Must only be called with a pointer returned by `make_handler`, and only + /// once per `Handler`. + pub unsafe fn drop_handler(data: *mut libc::c_void) { + if !data.is_null() { + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + let disabling_stack = libc::stack_t { + ss_sp: ptr::null_mut(), + ss_flags: SS_DISABLE, + // Workaround for bug in macOS implementation of sigaltstack + // UNIX2003 which returns ENOMEM when disabling a stack while + // passing ss_size smaller than MINSIGSTKSZ. According to POSIX + // both ss_sp and ss_size should be ignored in this case. + ss_size: sigstack_size, + }; + // SAFETY: + // We assume that disabling the alternate signal stack is always + // sound, even if the current alternate signal stack is not the one + // we installed in `make_handler`. Any stack overflows from this + // point on will abort the program when the kernel tries to write + // the signal information to the guard page. + // + // FIXME: detect if the stack has changed, and only uninstall if it hasn't. + unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; + // The stack returned by `get_stack` is part of a mapping that + // started one page earlier, so walk back a page and unmap from + // there. + // + // SAFETY: + // This allocation was created by us in `get_stack` and, as the + // alternate signal stack is now disabled, is no longer in use. + unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; + } + + delete_current_info(); + } + + /// Modern kernels on modern hardware can have dynamic signal stack sizes. + #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] + fn sigstack_size() -> usize { + // SAFETY: `getauxval` is always safe to call. + let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; + // If getauxval couldn't find the entry, it returns 0, + // so take the higher of the "constant" and auxval. + // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ + libc::SIGSTKSZ.max(dynamic_sigstksz as _) + } + + /// Not all OS support hardware where this is needed. + #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] + fn sigstack_size() -> usize { + libc::SIGSTKSZ + } + + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // The pointer is valid for writing a `stack_t`. + assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); + Some(current_stack.ss_sp) + } + + #[cfg(target_os = "macos")] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: always safe to call. + let th = unsafe { libc::pthread_self() }; + // SAFETY: `th` is a valid `pthread_t`. + unsafe { + let stackptr = libc::pthread_get_stackaddr_np(th); + let stacksize = libc::pthread_get_stacksize_np(th); + Some(stackptr.map_addr(|addr| addr - stacksize)) + } + } + + #[cfg(target_os = "openbsd")] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t`. + // * `&mut current_stack` is coerced to a pointer that is valid for writing + // a `stack_t`. + assert_eq!( + unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, + 0 + ); + + let stack_ptr = current_stack.ss_sp; + // SAFETY: this is always safe to call. + let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { + // main thread + stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) + } else { + // new thread + stack_ptr.addr() - current_stack.ss_size + }; + Some(stack_ptr.with_addr(stackaddr)) + } + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "hurd", + target_os = "linux", + target_os = "l4re" + ))] + fn get_stack_start() -> Option<*mut libc::c_void> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); + + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut stackaddr = crate::ptr::null_mut(); + let mut stacksize = 0; + // SAFETY: + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!( + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, + 0 + ); + ret = Some(stackaddr); + } + if e == 0 || cfg!(target_os = "freebsd") { + // SAFETY: + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + } + ret + } + + fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + + // Ensure stackaddr is page aligned! A parent process might + // have reset RLIMIT_STACK to be non-page aligned. The + // pthread_attr_getstack() reports the usable stack area + // stackaddr < stackaddr + stacksize, so if stackaddr is not + // page-aligned, calculate the fix such that stackaddr < + // new_page_aligned_stackaddr < stackaddr + stacksize + let remainder = stackaddr % page_size; + Some(if remainder == 0 { + stackptr + } else { + stackptr.with_addr(stackaddr + page_size - remainder) + }) + } + + /// # Safety + /// This function must only be called from the main thread, and there must + /// be sufficient stack space remaining to place a stack guard. + unsafe fn install_main_guard() -> Option> { + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + + // this way someone on any unix-y OS can check that all these compile + if cfg!(all(target_os = "linux", not(target_env = "musl"))) { + install_main_guard_linux(page_size) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + install_main_guard_linux_musl(page_size) + } else if cfg!(target_os = "freebsd") { + #[cfg(not(target_os = "freebsd"))] + return None; + // The FreeBSD code cannot be checked on non-BSDs. + #[cfg(target_os = "freebsd")] + install_main_guard_freebsd(page_size) + } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { + install_main_guard_bsds(page_size) + } else { + // SAFETY: guaranteed by caller. + unsafe { install_main_guard_default(page_size) } + } + } + + fn install_main_guard_linux(page_size: usize) -> Option> { + // See the corresponding conditional in init(). + // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps + if cfg!(panic = "immediate-abort") { + return None; + } + // Linux doesn't allocate the whole stack right away, and + // the kernel has its own stack-guard mechanism to fault + // when growing too close to an existing mapping. If we map + // our own guard, then the kernel starts enforcing a rather + // large gap above that, rendering much of the possible + // stack space useless. See #43052. + // + // Instead, we'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } + + fn install_main_guard_linux_musl(_page_size: usize) -> Option> { + // For the main thread, the musl's pthread_attr_getstack + // returns the current stack size, rather than maximum size + // it can eventually grow to. It cannot be used to determine + // the position of kernel's stack guard. + None + } + + #[cfg(target_os = "freebsd")] + fn install_main_guard_freebsd(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; + } + // FreeBSD's stack autogrows, and optionally includes a guard page + // at the bottom. If we try to remap the bottom of the stack + // ourselves, FreeBSD's guard page moves upwards. So we'll just use + // the builtin guard page. + let stackptr = stack_start_aligned(page_size)?; + let guardaddr = stackptr.addr(); + // Technically the number of guard pages is tunable and controlled + // by the security.bsd.stack_guard_page sysctl. + // By default it is 1, checking once is enough since it is + // a boot time config value. + // FIXME(joboet): this function is only called once, remove the caching. + static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); + + let pages = PAGES.get_or_init(|| { + let mut guard: usize = 0; + let mut size = size_of_val(&guard); + let oid = c"security.bsd.stack_guard_page"; + + let r = unsafe { + libc::sysctlbyname( + oid.as_ptr(), + (&raw mut guard).cast(), + &raw mut size, + ptr::null_mut(), + 0, + ) + }; + if r == 0 { guard } else { 1 } + }); + Some(guardaddr..guardaddr + pages * page_size) + } + + fn install_main_guard_bsds(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; + } + // OpenBSD stack already includes a guard page, and stack is + // immutable. + // NetBSD stack includes the guard page. + // + // We'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } + + /// # Safety + /// This function must only be called from the main thread, and there must + /// be sufficient stack space remaining to place a stack guard. + unsafe fn install_main_guard_default(page_size: usize) -> Option> { + // Reallocate the last page of the stack. + // This ensures SIGBUS will be raised on + // stack overflow. + // Systems which enforce strict PAX MPROTECT do not allow + // to mprotect() a mapping with less restrictive permissions + // than the initial mmap() used, so we mmap() here with + // read/write permissions and only then mprotect() it to + // no permissions at all. See issue #50313. + let stackptr = stack_start_aligned(page_size)?; + // SAFETY: + // The memory region from `stackptr..stackptr + page_size` belongs to + // the current thread's stack, and the caller has asserted that there + // is sufficient stack space, which means that this will not overwrite + // any existing allocations. + let result = unsafe { + mmap64( + stackptr, + page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + -1, + 0, + ) + }; + if result != stackptr || result == MAP_FAILED { + panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); + } + + // SAFETY: + // Since this function is only called on the main thread, the stack will + // not be reused until program exit, so the runtime will never observe + // that part of the stack has been made unusable in this way. + let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; + if result != 0 { + panic!("failed to protect the guard page: {}", io::Error::last_os_error()); + } + + let guardaddr = stackptr.addr(); + + Some(guardaddr..guardaddr + page_size) + } + + #[cfg(any( + target_os = "macos", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ))] + fn current_guard() -> Option> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) + } + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "hurd", + target_os = "linux", + target_os = "netbsd", + target_os = "l4re" + ))] + fn current_guard() -> Option> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); + + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut guardsize = 0; + // SAFETY: + // `attr` is an initialized attribute object and the pointer is valid + // for writing. + assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); + if guardsize == 0 { + if cfg!(all(target_os = "linux", target_env = "musl")) { + // musl versions before 1.1.19 always reported guard + // size obtained from pthread_attr_get_np as zero. + // Use page size as a fallback. + guardsize = PAGE_SIZE.load(Ordering::Relaxed); + } else { + panic!("there is no guard page"); + } + } + let mut stackptr = crate::ptr::null_mut::(); + let mut size = 0; + // SAFETY: + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!( + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, + 0 + ); + + let stackaddr = stackptr.addr(); + ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) + { + // glibc used to include the guard area within the stack, as noted in the BUGS + // section of `man pthread_attr_getguardsize`. This has been corrected starting + // with glibc 2.27, and in some distro backports, so the guard is now placed at the + // end (below) the stack. There's no easy way for us to know which we have at + // runtime, so we'll just match any fault in the range right above or below the + // stack base to call that fault a stack overflow. + Some(stackaddr - guardsize..stackaddr + guardsize) + } else { + Some(stackaddr..stackaddr + guardsize) + }; + } + if e == 0 || cfg!(target_os = "freebsd") { + // SAFETY: + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + } + ret + } +} + +// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses +// several symbols that might lead to rejections from the App Store, namely +// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. +// +// This might be overly cautious, though it is also what Swift does (and they +// usually have fewer qualms about forwards compatibility, since the runtime +// is shipped with the OS): +// +#[cfg(any( + miri, + not(any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + target_os = "cygwin", + )) +))] +mod imp { + pub unsafe fn init() {} + + pub fn make_handler(_main_thread: bool) -> super::Handler { + super::Handler::null() + } + + pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +} + +#[cfg(target_os = "cygwin")] +mod imp { + mod c { + pub type PVECTORED_EXCEPTION_HANDLER = + Option i32>; + pub type NTSTATUS = i32; + pub type BOOL = i32; + + unsafe extern "system" { + pub fn AddVectoredExceptionHandler( + first: u32, + handler: PVECTORED_EXCEPTION_HANDLER, + ) -> *mut core::ffi::c_void; + pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; + } + + pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; + pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; + + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_POINTERS { + pub ExceptionRecord: *mut EXCEPTION_RECORD, + // We don't need this field here + // pub Context: *mut CONTEXT, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_RECORD { + pub ExceptionCode: NTSTATUS, + pub ExceptionFlags: u32, + pub ExceptionRecord: *mut EXCEPTION_RECORD, + pub ExceptionAddress: *mut core::ffi::c_void, + pub NumberParameters: u32, + pub ExceptionInformation: [usize; 15], + } + } + + /// Reserve stack space for use in stack overflow exceptions. + fn reserve_stack() { + let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; + // Reserving stack space is not critical so we allow it to fail in the released build of libstd. + // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. + debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); + } + + unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { + // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. + unsafe { + let rec = &(*(*ExceptionInfo).ExceptionRecord); + let code = rec.ExceptionCode; + + if code == c::EXCEPTION_STACK_OVERFLOW { + crate::thread::with_current_name(|name| { + let name = name.unwrap_or(""); + let tid = crate::thread::current_os_id(); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + }); + } + c::EXCEPTION_CONTINUE_SEARCH + } + } + + pub unsafe fn init() { + // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. + unsafe { + let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); + // Similar to the above, adding the stack overflow handler is allowed to fail + // but a debug assert is used so CI will still test that it normally works. + debug_assert!(!result.is_null(), "failed to install exception handler"); + } + // Set the thread stack guarantee for the main thread. + reserve_stack(); + } + + pub fn make_handler(main_thread: bool) -> super::Handler { + if !main_thread { + reserve_stack(); + } + super::Handler::null() + } + + pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +} From 86d18b46ef59a67b514ef59aa80a0705dadb96c0 Mon Sep 17 00:00:00 2001 From: joboet Date: Wed, 23 Sep 2026 13:46:57 +0200 Subject: [PATCH 07/18] std: split stack overflow module (delete) --- .../pal/unix/stack_overflow/handler_cygwin.rs | 763 ---------------- .../pal/unix/stack_overflow/handler_none.rs | 837 ------------------ .../pal/unix/stack_overflow/handler_signal.rs | 178 ---- .../src/sys/pal/unix/stack_overflow/mod.rs | 754 ---------------- 4 files changed, 2532 deletions(-) diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs index 5604e3e6dbf42..089bd2c95073d 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs @@ -1,765 +1,3 @@ -#![cfg_attr(test, allow(dead_code))] -#![forbid(unsafe_op_in_unsafe_fn)] - -pub use self::imp::init; -use self::imp::{drop_handler, make_handler}; - -pub struct Handler { - data: *mut libc::c_void, -} - -impl Handler { - pub unsafe fn new() -> Handler { - make_handler(false) - } - - fn null() -> Handler { - Handler { data: crate::ptr::null_mut() } - } -} - -impl Drop for Handler { - fn drop(&mut self) { - unsafe { - drop_handler(self.data); - } - } -} - -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ), -))] -mod thread_info; - -// miri doesn't model signals nor stack overflows and this code has some -// synchronization properties that we don't want to expose to user code, -// hence we disable it on miri. -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ) -))] -mod imp { - use libc::{ - MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, - SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, - }; - #[cfg(not(all(target_os = "linux", target_env = "gnu")))] - use libc::{mmap as mmap64, mprotect, munmap}; - #[cfg(all(target_os = "linux", target_env = "gnu"))] - use libc::{mmap64, mprotect, munmap}; - - use super::Handler; - use super::thread_info::{delete_current_info, set_current_info, with_current_info}; - use crate::ops::Range; - use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; - use crate::sys::pal::unix::conf; - use crate::{io, mem, ptr}; - - /// Signal handler for the SIGSEGV and SIGBUS handlers. - /// - /// We've got guard pages (unmapped pages) at the end of every thread's - /// stack, so if a thread ends up running into the guard page it'll trigger - /// this handler. We want to detect these cases and print out a helpful error - /// saying that the stack has overflowed. All other signals, however, should - /// go back to what they were originally supposed to do. - /// - /// This handler currently exists purely to print an informative message - /// whenever a thread overflows its stack. We then abort to exit and - /// indicate a crash, but to avoid a misleading SIGSEGV that might lead - /// users to believe that unsafe code has accessed an invalid pointer; the - /// SIGSEGV encountered when overflowing the stack is expected and - /// well-defined. - /// - /// If this is not a stack overflow, the handler un-registers itself and - /// then returns (to allow the original signal to be delivered again). - /// Returning from this kind of signal handler is technically not defined - /// to work when reading the POSIX spec strictly, but in practice it turns - /// out many large systems and all implementations allow returning from a - /// signal handler to work. For a more detailed explanation see the - /// comments on #26458. - /// - /// # Safety - /// Rust doesn't call this, it *gets called* by the kernel, which we expect - /// to provide valid parameters. Apart from that, this function does not - /// have any other preconditions. - unsafe extern "C" fn signal_handler( - signum: libc::c_int, - info: *mut libc::siginfo_t, - _data: *mut libc::c_void, - ) { - // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. - let fault_addr = unsafe { (*info).si_addr().addr() }; - - // `with_current_info` expects that the process aborts after it is - // called. If the signal was not caused by a memory access, this might - // not be true. We detect this by noticing that the `si_addr` field is - // zero if the signal is synthetic. - if fault_addr != 0 { - with_current_info(|thread_info| { - // If the faulting address is within the guard page, then we print a - // message saying so and abort. - if let Some(thread_info) = thread_info - && thread_info.guard_page_range.contains(&fault_addr) - { - // Hey you! Yes, you modifying the stack overflow message! - // Please make sure that all functions called here are - // actually async-signal-safe. If they're not, try retrieving - // the information beforehand and storing it in `ThreadInfo`. - // Thank you! - // - says Jonas after having had to watch his carefully - // written code get made unsound again. - let tid = thread_info.tid; - let name = thread_info.name.as_deref().unwrap_or(""); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - rtabort!("stack overflow"); - } - }) - } - - // Unregister ourselves by reverting back to the default behavior. - // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" - let mut action: sigaction = unsafe { mem::zeroed() }; - action.sa_sigaction = SIG_DFL; - // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction - unsafe { sigaction(signum, &action, ptr::null_mut()) }; - - // See comment above for why this function returns. - } - - static PAGE_SIZE: Atomic = AtomicUsize::new(0); - // Store a pointer to the allocation for the main thread's altstack so that - // tools like valgrind don't complain about a leaked unreachable allocation. - // - // If the main thread exits, the process will terminate so there's no use in - // freeing resources. It also means that the altstack is still installed - // while TLS destructors are run on the main thread (c.f. #111272). - static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); - static NEED_ALTSTACK: Atomic = AtomicBool::new(false); - - /// # Safety - /// Must be called only once, on the main thread, during program startup. - pub unsafe fn init() { - PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); - - // SAFETY: - // This is only called on the main thread, and since it is still early - // in the programs lifetime there is (almost) certainly enough stack - // space left to install the guard page. - let mut guard_page_range = unsafe { install_main_guard() }; - - // Even for panic=immediate-abort, installing the guard pages is important for soundness. - // That said, we do not care about giving nice stackoverflow messages via our custom - // signal handler, just exit early and let the user enjoy the segfault. - if cfg!(panic = "immediate-abort") { - return; - } - - // SAFETY: C structures are always zero-initializable. - let mut action: sigaction = unsafe { mem::zeroed() }; - for &signal in &[SIGSEGV, SIGBUS] { - // SAFETY: just fetches the current signal handler into action - unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; - // We assume that overriding the signal handler is always safe, - // which might conflict with certain libraries that rely on a - // specific signal behaviour. To prevent problems, we only - // override the handler if it has not been set yet. - if action.sa_sigaction == SIG_DFL { - if !NEED_ALTSTACK.load(Ordering::Relaxed) { - // haven't set up our sigaltstack yet - NEED_ALTSTACK.store(true, Ordering::Release); - let handler = make_handler(true); - MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); - mem::forget(handler); - - if let Some(guard_page_range) = guard_page_range.take() { - set_current_info(guard_page_range); - } - } - - action.sa_flags = SA_SIGINFO | SA_ONSTACK; - action.sa_sigaction = signal_handler - as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) - as sighandler_t; - // SAFETY: - // `&action` describes a valid `sigaction` and `signal_handler` - // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. - unsafe { sigaction(signal, &action, ptr::null_mut()) }; - } - } - } - - fn get_stack() -> libc::stack_t { - // OpenBSD requires this flag for stack mapping - // otherwise the said mapping will fail as a no-op on most systems - // and has a different meaning on FreeBSD - #[cfg(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - ))] - let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; - #[cfg(not(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - )))] - let flags = MAP_PRIVATE | MAP_ANON; - - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // SAFETY: this does not unmap any existing pages. - let stackp = unsafe { - mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) - }; - if stackp == MAP_FAILED { - panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); - } - // SAFETY: this only affects the memory we just allocated. - let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; - if guard_result != 0 { - panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); - } - // SAFETY: - // The region was allocated with a larger size than `page_size`, so this - // addition is within bounds. - let stackp = unsafe { stackp.add(page_size) }; - - libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } - } - - pub fn make_handler(main_thread: bool) -> Handler { - if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { - return Handler::null(); - } - - if !main_thread { - if let Some(guard_page_range) = current_guard() { - set_current_info(guard_page_range); - } - } - - // Load the current alternate signal stack to see if we need to install - // our own. - // - // SAFETY: C structures are always zero-initializable. - let mut stack = unsafe { mem::zeroed() }; - // SAFETY: `&mut stack` is valid for writing a `stack_t`. - unsafe { sigaltstack(ptr::null(), &mut stack) }; - - // Configure alternate signal stack, if one is not already set. - if stack.ss_flags & SS_DISABLE != 0 { - let stack = get_stack(); - // SAFETY: - // `stack_t` is a freshly allocated stack that's not used anywhere - // else. It contains a guard page, so stack overflows in signal - // handlers will not cause undefined behaviour. We must make the - // fundamental runtime assumption that it is safe to install an - // alternate signal stack if there is none currently installed. - // This might conflict with foreign libraries that use the existence - // of an alternate signal stack as indication that certain runtime - // initialisation by the library has been performed (e.g. old - // versions of `std` assumed that certain thread-locals were already - // accessed and thus initialized in the thread if the stack overflow - // signal was successfully delivered). Such assumptions in other - // libraries are fundamentally flawed, so we pay no regard to them. - unsafe { sigaltstack(&stack, ptr::null_mut()) }; - Handler { data: stack.ss_sp as *mut libc::c_void } - } else { - Handler::null() - } - } - - /// # Safety - /// Must only be called with a pointer returned by `make_handler`, and only - /// once per `Handler`. - pub unsafe fn drop_handler(data: *mut libc::c_void) { - if !data.is_null() { - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - let disabling_stack = libc::stack_t { - ss_sp: ptr::null_mut(), - ss_flags: SS_DISABLE, - // Workaround for bug in macOS implementation of sigaltstack - // UNIX2003 which returns ENOMEM when disabling a stack while - // passing ss_size smaller than MINSIGSTKSZ. According to POSIX - // both ss_sp and ss_size should be ignored in this case. - ss_size: sigstack_size, - }; - // SAFETY: - // We assume that disabling the alternate signal stack is always - // sound, even if the current alternate signal stack is not the one - // we installed in `make_handler`. Any stack overflows from this - // point on will abort the program when the kernel tries to write - // the signal information to the guard page. - // - // FIXME: detect if the stack has changed, and only uninstall if it hasn't. - unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; - // The stack returned by `get_stack` is part of a mapping that - // started one page earlier, so walk back a page and unmap from - // there. - // - // SAFETY: - // This allocation was created by us in `get_stack` and, as the - // alternate signal stack is now disabled, is no longer in use. - unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; - } - - delete_current_info(); - } - - /// Modern kernels on modern hardware can have dynamic signal stack sizes. - #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] - fn sigstack_size() -> usize { - // SAFETY: `getauxval` is always safe to call. - let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; - // If getauxval couldn't find the entry, it returns 0, - // so take the higher of the "constant" and auxval. - // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ - libc::SIGSTKSZ.max(dynamic_sigstksz as _) - } - - /// Not all OS support hardware where this is needed. - #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] - fn sigstack_size() -> usize { - libc::SIGSTKSZ - } - - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; - // SAFETY: - // The pointer is valid for writing a `stack_t`. - assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); - Some(current_stack.ss_sp) - } - - #[cfg(target_os = "macos")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: always safe to call. - let th = unsafe { libc::pthread_self() }; - // SAFETY: `th` is a valid `pthread_t`. - unsafe { - let stackptr = libc::pthread_get_stackaddr_np(th); - let stacksize = libc::pthread_get_stacksize_np(th); - Some(stackptr.map_addr(|addr| addr - stacksize)) - } - } - - #[cfg(target_os = "openbsd")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t`. - // * `&mut current_stack` is coerced to a pointer that is valid for writing - // a `stack_t`. - assert_eq!( - unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, - 0 - ); - - let stack_ptr = current_stack.ss_sp; - // SAFETY: this is always safe to call. - let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { - // main thread - stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) - } else { - // new thread - stack_ptr.addr() - current_stack.ss_size - }; - Some(stack_ptr.with_addr(stackaddr)) - } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", - target_os = "hurd", - target_os = "linux", - target_os = "l4re" - ))] - fn get_stack_start() -> Option<*mut libc::c_void> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut stackaddr = crate::ptr::null_mut(); - let mut stacksize = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, - 0 - ); - ret = Some(stackaddr); - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret - } - - fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - - // Ensure stackaddr is page aligned! A parent process might - // have reset RLIMIT_STACK to be non-page aligned. The - // pthread_attr_getstack() reports the usable stack area - // stackaddr < stackaddr + stacksize, so if stackaddr is not - // page-aligned, calculate the fix such that stackaddr < - // new_page_aligned_stackaddr < stackaddr + stacksize - let remainder = stackaddr % page_size; - Some(if remainder == 0 { - stackptr - } else { - stackptr.with_addr(stackaddr + page_size - remainder) - }) - } - - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard() -> Option> { - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // this way someone on any unix-y OS can check that all these compile - if cfg!(all(target_os = "linux", not(target_env = "musl"))) { - install_main_guard_linux(page_size) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - install_main_guard_linux_musl(page_size) - } else if cfg!(target_os = "freebsd") { - #[cfg(not(target_os = "freebsd"))] - return None; - // The FreeBSD code cannot be checked on non-BSDs. - #[cfg(target_os = "freebsd")] - install_main_guard_freebsd(page_size) - } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { - install_main_guard_bsds(page_size) - } else { - // SAFETY: guaranteed by caller. - unsafe { install_main_guard_default(page_size) } - } - } - - fn install_main_guard_linux(page_size: usize) -> Option> { - // See the corresponding conditional in init(). - // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps - if cfg!(panic = "immediate-abort") { - return None; - } - // Linux doesn't allocate the whole stack right away, and - // the kernel has its own stack-guard mechanism to fault - // when growing too close to an existing mapping. If we map - // our own guard, then the kernel starts enforcing a rather - // large gap above that, rendering much of the possible - // stack space useless. See #43052. - // - // Instead, we'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } - - fn install_main_guard_linux_musl(_page_size: usize) -> Option> { - // For the main thread, the musl's pthread_attr_getstack - // returns the current stack size, rather than maximum size - // it can eventually grow to. It cannot be used to determine - // the position of kernel's stack guard. - None - } - - #[cfg(target_os = "freebsd")] - fn install_main_guard_freebsd(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // FreeBSD's stack autogrows, and optionally includes a guard page - // at the bottom. If we try to remap the bottom of the stack - // ourselves, FreeBSD's guard page moves upwards. So we'll just use - // the builtin guard page. - let stackptr = stack_start_aligned(page_size)?; - let guardaddr = stackptr.addr(); - // Technically the number of guard pages is tunable and controlled - // by the security.bsd.stack_guard_page sysctl. - // By default it is 1, checking once is enough since it is - // a boot time config value. - // FIXME(joboet): this function is only called once, remove the caching. - static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); - - let pages = PAGES.get_or_init(|| { - let mut guard: usize = 0; - let mut size = size_of_val(&guard); - let oid = c"security.bsd.stack_guard_page"; - - let r = unsafe { - libc::sysctlbyname( - oid.as_ptr(), - (&raw mut guard).cast(), - &raw mut size, - ptr::null_mut(), - 0, - ) - }; - if r == 0 { guard } else { 1 } - }); - Some(guardaddr..guardaddr + pages * page_size) - } - - fn install_main_guard_bsds(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // OpenBSD stack already includes a guard page, and stack is - // immutable. - // NetBSD stack includes the guard page. - // - // We'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } - - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard_default(page_size: usize) -> Option> { - // Reallocate the last page of the stack. - // This ensures SIGBUS will be raised on - // stack overflow. - // Systems which enforce strict PAX MPROTECT do not allow - // to mprotect() a mapping with less restrictive permissions - // than the initial mmap() used, so we mmap() here with - // read/write permissions and only then mprotect() it to - // no permissions at all. See issue #50313. - let stackptr = stack_start_aligned(page_size)?; - // SAFETY: - // The memory region from `stackptr..stackptr + page_size` belongs to - // the current thread's stack, and the caller has asserted that there - // is sufficient stack space, which means that this will not overwrite - // any existing allocations. - let result = unsafe { - mmap64( - stackptr, - page_size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON | MAP_FIXED, - -1, - 0, - ) - }; - if result != stackptr || result == MAP_FAILED { - panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); - } - - // SAFETY: - // Since this function is only called on the main thread, the stack will - // not be reused until program exit, so the runtime will never observe - // that part of the stack has been made unusable in this way. - let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; - if result != 0 { - panic!("failed to protect the guard page: {}", io::Error::last_os_error()); - } - - let guardaddr = stackptr.addr(); - - Some(guardaddr..guardaddr + page_size) - } - - #[cfg(any( - target_os = "macos", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ))] - fn current_guard() -> Option> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) - } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "hurd", - target_os = "linux", - target_os = "netbsd", - target_os = "l4re" - ))] - fn current_guard() -> Option> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut guardsize = 0; - // SAFETY: - // `attr` is an initialized attribute object and the pointer is valid - // for writing. - assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); - if guardsize == 0 { - if cfg!(all(target_os = "linux", target_env = "musl")) { - // musl versions before 1.1.19 always reported guard - // size obtained from pthread_attr_get_np as zero. - // Use page size as a fallback. - guardsize = PAGE_SIZE.load(Ordering::Relaxed); - } else { - panic!("there is no guard page"); - } - } - let mut stackptr = crate::ptr::null_mut::(); - let mut size = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, - 0 - ); - - let stackaddr = stackptr.addr(); - ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) - { - // glibc used to include the guard area within the stack, as noted in the BUGS - // section of `man pthread_attr_getguardsize`. This has been corrected starting - // with glibc 2.27, and in some distro backports, so the guard is now placed at the - // end (below) the stack. There's no easy way for us to know which we have at - // runtime, so we'll just match any fault in the range right above or below the - // stack base to call that fault a stack overflow. - Some(stackaddr - guardsize..stackaddr + guardsize) - } else { - Some(stackaddr..stackaddr + guardsize) - }; - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret - } -} - -// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses -// several symbols that might lead to rejections from the App Store, namely -// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. -// -// This might be overly cautious, though it is also what Swift does (and they -// usually have fewer qualms about forwards compatibility, since the runtime -// is shipped with the OS): -// -#[cfg(any( - miri, - not(any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - target_os = "cygwin", - )) -))] -mod imp { - pub unsafe fn init() {} - - pub fn make_handler(_main_thread: bool) -> super::Handler { - super::Handler::null() - } - - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} - -#[cfg(target_os = "cygwin")] -mod imp { mod c { pub type PVECTORED_EXCEPTION_HANDLER = Option i32>; @@ -841,4 +79,3 @@ mod imp { } pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs index 5604e3e6dbf42..f765090ffff7d 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs @@ -1,754 +1,3 @@ -#![cfg_attr(test, allow(dead_code))] -#![forbid(unsafe_op_in_unsafe_fn)] - -pub use self::imp::init; -use self::imp::{drop_handler, make_handler}; - -pub struct Handler { - data: *mut libc::c_void, -} - -impl Handler { - pub unsafe fn new() -> Handler { - make_handler(false) - } - - fn null() -> Handler { - Handler { data: crate::ptr::null_mut() } - } -} - -impl Drop for Handler { - fn drop(&mut self) { - unsafe { - drop_handler(self.data); - } - } -} - -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ), -))] -mod thread_info; - -// miri doesn't model signals nor stack overflows and this code has some -// synchronization properties that we don't want to expose to user code, -// hence we disable it on miri. -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ) -))] -mod imp { - use libc::{ - MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, - SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, - }; - #[cfg(not(all(target_os = "linux", target_env = "gnu")))] - use libc::{mmap as mmap64, mprotect, munmap}; - #[cfg(all(target_os = "linux", target_env = "gnu"))] - use libc::{mmap64, mprotect, munmap}; - - use super::Handler; - use super::thread_info::{delete_current_info, set_current_info, with_current_info}; - use crate::ops::Range; - use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; - use crate::sys::pal::unix::conf; - use crate::{io, mem, ptr}; - - /// Signal handler for the SIGSEGV and SIGBUS handlers. - /// - /// We've got guard pages (unmapped pages) at the end of every thread's - /// stack, so if a thread ends up running into the guard page it'll trigger - /// this handler. We want to detect these cases and print out a helpful error - /// saying that the stack has overflowed. All other signals, however, should - /// go back to what they were originally supposed to do. - /// - /// This handler currently exists purely to print an informative message - /// whenever a thread overflows its stack. We then abort to exit and - /// indicate a crash, but to avoid a misleading SIGSEGV that might lead - /// users to believe that unsafe code has accessed an invalid pointer; the - /// SIGSEGV encountered when overflowing the stack is expected and - /// well-defined. - /// - /// If this is not a stack overflow, the handler un-registers itself and - /// then returns (to allow the original signal to be delivered again). - /// Returning from this kind of signal handler is technically not defined - /// to work when reading the POSIX spec strictly, but in practice it turns - /// out many large systems and all implementations allow returning from a - /// signal handler to work. For a more detailed explanation see the - /// comments on #26458. - /// - /// # Safety - /// Rust doesn't call this, it *gets called* by the kernel, which we expect - /// to provide valid parameters. Apart from that, this function does not - /// have any other preconditions. - unsafe extern "C" fn signal_handler( - signum: libc::c_int, - info: *mut libc::siginfo_t, - _data: *mut libc::c_void, - ) { - // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. - let fault_addr = unsafe { (*info).si_addr().addr() }; - - // `with_current_info` expects that the process aborts after it is - // called. If the signal was not caused by a memory access, this might - // not be true. We detect this by noticing that the `si_addr` field is - // zero if the signal is synthetic. - if fault_addr != 0 { - with_current_info(|thread_info| { - // If the faulting address is within the guard page, then we print a - // message saying so and abort. - if let Some(thread_info) = thread_info - && thread_info.guard_page_range.contains(&fault_addr) - { - // Hey you! Yes, you modifying the stack overflow message! - // Please make sure that all functions called here are - // actually async-signal-safe. If they're not, try retrieving - // the information beforehand and storing it in `ThreadInfo`. - // Thank you! - // - says Jonas after having had to watch his carefully - // written code get made unsound again. - let tid = thread_info.tid; - let name = thread_info.name.as_deref().unwrap_or(""); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - rtabort!("stack overflow"); - } - }) - } - - // Unregister ourselves by reverting back to the default behavior. - // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" - let mut action: sigaction = unsafe { mem::zeroed() }; - action.sa_sigaction = SIG_DFL; - // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction - unsafe { sigaction(signum, &action, ptr::null_mut()) }; - - // See comment above for why this function returns. - } - - static PAGE_SIZE: Atomic = AtomicUsize::new(0); - // Store a pointer to the allocation for the main thread's altstack so that - // tools like valgrind don't complain about a leaked unreachable allocation. - // - // If the main thread exits, the process will terminate so there's no use in - // freeing resources. It also means that the altstack is still installed - // while TLS destructors are run on the main thread (c.f. #111272). - static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); - static NEED_ALTSTACK: Atomic = AtomicBool::new(false); - - /// # Safety - /// Must be called only once, on the main thread, during program startup. - pub unsafe fn init() { - PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); - - // SAFETY: - // This is only called on the main thread, and since it is still early - // in the programs lifetime there is (almost) certainly enough stack - // space left to install the guard page. - let mut guard_page_range = unsafe { install_main_guard() }; - - // Even for panic=immediate-abort, installing the guard pages is important for soundness. - // That said, we do not care about giving nice stackoverflow messages via our custom - // signal handler, just exit early and let the user enjoy the segfault. - if cfg!(panic = "immediate-abort") { - return; - } - - // SAFETY: C structures are always zero-initializable. - let mut action: sigaction = unsafe { mem::zeroed() }; - for &signal in &[SIGSEGV, SIGBUS] { - // SAFETY: just fetches the current signal handler into action - unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; - // We assume that overriding the signal handler is always safe, - // which might conflict with certain libraries that rely on a - // specific signal behaviour. To prevent problems, we only - // override the handler if it has not been set yet. - if action.sa_sigaction == SIG_DFL { - if !NEED_ALTSTACK.load(Ordering::Relaxed) { - // haven't set up our sigaltstack yet - NEED_ALTSTACK.store(true, Ordering::Release); - let handler = make_handler(true); - MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); - mem::forget(handler); - - if let Some(guard_page_range) = guard_page_range.take() { - set_current_info(guard_page_range); - } - } - - action.sa_flags = SA_SIGINFO | SA_ONSTACK; - action.sa_sigaction = signal_handler - as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) - as sighandler_t; - // SAFETY: - // `&action` describes a valid `sigaction` and `signal_handler` - // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. - unsafe { sigaction(signal, &action, ptr::null_mut()) }; - } - } - } - - fn get_stack() -> libc::stack_t { - // OpenBSD requires this flag for stack mapping - // otherwise the said mapping will fail as a no-op on most systems - // and has a different meaning on FreeBSD - #[cfg(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - ))] - let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; - #[cfg(not(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - )))] - let flags = MAP_PRIVATE | MAP_ANON; - - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // SAFETY: this does not unmap any existing pages. - let stackp = unsafe { - mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) - }; - if stackp == MAP_FAILED { - panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); - } - // SAFETY: this only affects the memory we just allocated. - let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; - if guard_result != 0 { - panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); - } - // SAFETY: - // The region was allocated with a larger size than `page_size`, so this - // addition is within bounds. - let stackp = unsafe { stackp.add(page_size) }; - - libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } - } - - pub fn make_handler(main_thread: bool) -> Handler { - if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { - return Handler::null(); - } - - if !main_thread { - if let Some(guard_page_range) = current_guard() { - set_current_info(guard_page_range); - } - } - - // Load the current alternate signal stack to see if we need to install - // our own. - // - // SAFETY: C structures are always zero-initializable. - let mut stack = unsafe { mem::zeroed() }; - // SAFETY: `&mut stack` is valid for writing a `stack_t`. - unsafe { sigaltstack(ptr::null(), &mut stack) }; - - // Configure alternate signal stack, if one is not already set. - if stack.ss_flags & SS_DISABLE != 0 { - let stack = get_stack(); - // SAFETY: - // `stack_t` is a freshly allocated stack that's not used anywhere - // else. It contains a guard page, so stack overflows in signal - // handlers will not cause undefined behaviour. We must make the - // fundamental runtime assumption that it is safe to install an - // alternate signal stack if there is none currently installed. - // This might conflict with foreign libraries that use the existence - // of an alternate signal stack as indication that certain runtime - // initialisation by the library has been performed (e.g. old - // versions of `std` assumed that certain thread-locals were already - // accessed and thus initialized in the thread if the stack overflow - // signal was successfully delivered). Such assumptions in other - // libraries are fundamentally flawed, so we pay no regard to them. - unsafe { sigaltstack(&stack, ptr::null_mut()) }; - Handler { data: stack.ss_sp as *mut libc::c_void } - } else { - Handler::null() - } - } - - /// # Safety - /// Must only be called with a pointer returned by `make_handler`, and only - /// once per `Handler`. - pub unsafe fn drop_handler(data: *mut libc::c_void) { - if !data.is_null() { - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - let disabling_stack = libc::stack_t { - ss_sp: ptr::null_mut(), - ss_flags: SS_DISABLE, - // Workaround for bug in macOS implementation of sigaltstack - // UNIX2003 which returns ENOMEM when disabling a stack while - // passing ss_size smaller than MINSIGSTKSZ. According to POSIX - // both ss_sp and ss_size should be ignored in this case. - ss_size: sigstack_size, - }; - // SAFETY: - // We assume that disabling the alternate signal stack is always - // sound, even if the current alternate signal stack is not the one - // we installed in `make_handler`. Any stack overflows from this - // point on will abort the program when the kernel tries to write - // the signal information to the guard page. - // - // FIXME: detect if the stack has changed, and only uninstall if it hasn't. - unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; - // The stack returned by `get_stack` is part of a mapping that - // started one page earlier, so walk back a page and unmap from - // there. - // - // SAFETY: - // This allocation was created by us in `get_stack` and, as the - // alternate signal stack is now disabled, is no longer in use. - unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; - } - - delete_current_info(); - } - - /// Modern kernels on modern hardware can have dynamic signal stack sizes. - #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] - fn sigstack_size() -> usize { - // SAFETY: `getauxval` is always safe to call. - let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; - // If getauxval couldn't find the entry, it returns 0, - // so take the higher of the "constant" and auxval. - // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ - libc::SIGSTKSZ.max(dynamic_sigstksz as _) - } - - /// Not all OS support hardware where this is needed. - #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] - fn sigstack_size() -> usize { - libc::SIGSTKSZ - } - - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; - // SAFETY: - // The pointer is valid for writing a `stack_t`. - assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); - Some(current_stack.ss_sp) - } - - #[cfg(target_os = "macos")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: always safe to call. - let th = unsafe { libc::pthread_self() }; - // SAFETY: `th` is a valid `pthread_t`. - unsafe { - let stackptr = libc::pthread_get_stackaddr_np(th); - let stacksize = libc::pthread_get_stacksize_np(th); - Some(stackptr.map_addr(|addr| addr - stacksize)) - } - } - - #[cfg(target_os = "openbsd")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t`. - // * `&mut current_stack` is coerced to a pointer that is valid for writing - // a `stack_t`. - assert_eq!( - unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, - 0 - ); - - let stack_ptr = current_stack.ss_sp; - // SAFETY: this is always safe to call. - let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { - // main thread - stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) - } else { - // new thread - stack_ptr.addr() - current_stack.ss_size - }; - Some(stack_ptr.with_addr(stackaddr)) - } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", - target_os = "hurd", - target_os = "linux", - target_os = "l4re" - ))] - fn get_stack_start() -> Option<*mut libc::c_void> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut stackaddr = crate::ptr::null_mut(); - let mut stacksize = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, - 0 - ); - ret = Some(stackaddr); - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret - } - - fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - - // Ensure stackaddr is page aligned! A parent process might - // have reset RLIMIT_STACK to be non-page aligned. The - // pthread_attr_getstack() reports the usable stack area - // stackaddr < stackaddr + stacksize, so if stackaddr is not - // page-aligned, calculate the fix such that stackaddr < - // new_page_aligned_stackaddr < stackaddr + stacksize - let remainder = stackaddr % page_size; - Some(if remainder == 0 { - stackptr - } else { - stackptr.with_addr(stackaddr + page_size - remainder) - }) - } - - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard() -> Option> { - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // this way someone on any unix-y OS can check that all these compile - if cfg!(all(target_os = "linux", not(target_env = "musl"))) { - install_main_guard_linux(page_size) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - install_main_guard_linux_musl(page_size) - } else if cfg!(target_os = "freebsd") { - #[cfg(not(target_os = "freebsd"))] - return None; - // The FreeBSD code cannot be checked on non-BSDs. - #[cfg(target_os = "freebsd")] - install_main_guard_freebsd(page_size) - } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { - install_main_guard_bsds(page_size) - } else { - // SAFETY: guaranteed by caller. - unsafe { install_main_guard_default(page_size) } - } - } - - fn install_main_guard_linux(page_size: usize) -> Option> { - // See the corresponding conditional in init(). - // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps - if cfg!(panic = "immediate-abort") { - return None; - } - // Linux doesn't allocate the whole stack right away, and - // the kernel has its own stack-guard mechanism to fault - // when growing too close to an existing mapping. If we map - // our own guard, then the kernel starts enforcing a rather - // large gap above that, rendering much of the possible - // stack space useless. See #43052. - // - // Instead, we'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } - - fn install_main_guard_linux_musl(_page_size: usize) -> Option> { - // For the main thread, the musl's pthread_attr_getstack - // returns the current stack size, rather than maximum size - // it can eventually grow to. It cannot be used to determine - // the position of kernel's stack guard. - None - } - - #[cfg(target_os = "freebsd")] - fn install_main_guard_freebsd(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // FreeBSD's stack autogrows, and optionally includes a guard page - // at the bottom. If we try to remap the bottom of the stack - // ourselves, FreeBSD's guard page moves upwards. So we'll just use - // the builtin guard page. - let stackptr = stack_start_aligned(page_size)?; - let guardaddr = stackptr.addr(); - // Technically the number of guard pages is tunable and controlled - // by the security.bsd.stack_guard_page sysctl. - // By default it is 1, checking once is enough since it is - // a boot time config value. - // FIXME(joboet): this function is only called once, remove the caching. - static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); - - let pages = PAGES.get_or_init(|| { - let mut guard: usize = 0; - let mut size = size_of_val(&guard); - let oid = c"security.bsd.stack_guard_page"; - - let r = unsafe { - libc::sysctlbyname( - oid.as_ptr(), - (&raw mut guard).cast(), - &raw mut size, - ptr::null_mut(), - 0, - ) - }; - if r == 0 { guard } else { 1 } - }); - Some(guardaddr..guardaddr + pages * page_size) - } - - fn install_main_guard_bsds(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // OpenBSD stack already includes a guard page, and stack is - // immutable. - // NetBSD stack includes the guard page. - // - // We'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } - - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard_default(page_size: usize) -> Option> { - // Reallocate the last page of the stack. - // This ensures SIGBUS will be raised on - // stack overflow. - // Systems which enforce strict PAX MPROTECT do not allow - // to mprotect() a mapping with less restrictive permissions - // than the initial mmap() used, so we mmap() here with - // read/write permissions and only then mprotect() it to - // no permissions at all. See issue #50313. - let stackptr = stack_start_aligned(page_size)?; - // SAFETY: - // The memory region from `stackptr..stackptr + page_size` belongs to - // the current thread's stack, and the caller has asserted that there - // is sufficient stack space, which means that this will not overwrite - // any existing allocations. - let result = unsafe { - mmap64( - stackptr, - page_size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON | MAP_FIXED, - -1, - 0, - ) - }; - if result != stackptr || result == MAP_FAILED { - panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); - } - - // SAFETY: - // Since this function is only called on the main thread, the stack will - // not be reused until program exit, so the runtime will never observe - // that part of the stack has been made unusable in this way. - let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; - if result != 0 { - panic!("failed to protect the guard page: {}", io::Error::last_os_error()); - } - - let guardaddr = stackptr.addr(); - - Some(guardaddr..guardaddr + page_size) - } - - #[cfg(any( - target_os = "macos", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ))] - fn current_guard() -> Option> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) - } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "hurd", - target_os = "linux", - target_os = "netbsd", - target_os = "l4re" - ))] - fn current_guard() -> Option> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut guardsize = 0; - // SAFETY: - // `attr` is an initialized attribute object and the pointer is valid - // for writing. - assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); - if guardsize == 0 { - if cfg!(all(target_os = "linux", target_env = "musl")) { - // musl versions before 1.1.19 always reported guard - // size obtained from pthread_attr_get_np as zero. - // Use page size as a fallback. - guardsize = PAGE_SIZE.load(Ordering::Relaxed); - } else { - panic!("there is no guard page"); - } - } - let mut stackptr = crate::ptr::null_mut::(); - let mut size = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, - 0 - ); - - let stackaddr = stackptr.addr(); - ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) - { - // glibc used to include the guard area within the stack, as noted in the BUGS - // section of `man pthread_attr_getguardsize`. This has been corrected starting - // with glibc 2.27, and in some distro backports, so the guard is now placed at the - // end (below) the stack. There's no easy way for us to know which we have at - // runtime, so we'll just match any fault in the range right above or below the - // stack base to call that fault a stack overflow. - Some(stackaddr - guardsize..stackaddr + guardsize) - } else { - Some(stackaddr..stackaddr + guardsize) - }; - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret - } -} - -// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses -// several symbols that might lead to rejections from the App Store, namely -// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. -// -// This might be overly cautious, though it is also what Swift does (and they -// usually have fewer qualms about forwards compatibility, since the runtime -// is shipped with the OS): -// -#[cfg(any( - miri, - not(any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - target_os = "cygwin", - )) -))] -mod imp { pub unsafe fn init() {} pub fn make_handler(_main_thread: bool) -> super::Handler { @@ -756,89 +5,3 @@ mod imp { } pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} - -#[cfg(target_os = "cygwin")] -mod imp { - mod c { - pub type PVECTORED_EXCEPTION_HANDLER = - Option i32>; - pub type NTSTATUS = i32; - pub type BOOL = i32; - - unsafe extern "system" { - pub fn AddVectoredExceptionHandler( - first: u32, - handler: PVECTORED_EXCEPTION_HANDLER, - ) -> *mut core::ffi::c_void; - pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; - } - - pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; - pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; - - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_POINTERS { - pub ExceptionRecord: *mut EXCEPTION_RECORD, - // We don't need this field here - // pub Context: *mut CONTEXT, - } - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_RECORD { - pub ExceptionCode: NTSTATUS, - pub ExceptionFlags: u32, - pub ExceptionRecord: *mut EXCEPTION_RECORD, - pub ExceptionAddress: *mut core::ffi::c_void, - pub NumberParameters: u32, - pub ExceptionInformation: [usize; 15], - } - } - - /// Reserve stack space for use in stack overflow exceptions. - fn reserve_stack() { - let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; - // Reserving stack space is not critical so we allow it to fail in the released build of libstd. - // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. - debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); - } - - unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { - // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. - unsafe { - let rec = &(*(*ExceptionInfo).ExceptionRecord); - let code = rec.ExceptionCode; - - if code == c::EXCEPTION_STACK_OVERFLOW { - crate::thread::with_current_name(|name| { - let name = name.unwrap_or(""); - let tid = crate::thread::current_os_id(); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - }); - } - c::EXCEPTION_CONTINUE_SEARCH - } - } - - pub unsafe fn init() { - // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. - unsafe { - let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); - // Similar to the above, adding the stack overflow handler is allowed to fail - // but a debug assert is used so CI will still test that it normally works. - debug_assert!(!result.is_null(), "failed to install exception handler"); - } - // Set the thread stack guarantee for the main thread. - reserve_stack(); - } - - pub fn make_handler(main_thread: bool) -> super::Handler { - if !main_thread { - reserve_stack(); - } - super::Handler::null() - } - - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs index 5604e3e6dbf42..ee4de76121097 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs @@ -1,63 +1,3 @@ -#![cfg_attr(test, allow(dead_code))] -#![forbid(unsafe_op_in_unsafe_fn)] - -pub use self::imp::init; -use self::imp::{drop_handler, make_handler}; - -pub struct Handler { - data: *mut libc::c_void, -} - -impl Handler { - pub unsafe fn new() -> Handler { - make_handler(false) - } - - fn null() -> Handler { - Handler { data: crate::ptr::null_mut() } - } -} - -impl Drop for Handler { - fn drop(&mut self) { - unsafe { - drop_handler(self.data); - } - } -} - -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ), -))] -mod thread_info; - -// miri doesn't model signals nor stack overflows and this code has some -// synchronization properties that we don't want to expose to user code, -// hence we disable it on miri. -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ) -))] -mod imp { use libc::{ MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, @@ -724,121 +664,3 @@ mod imp { } ret } -} - -// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses -// several symbols that might lead to rejections from the App Store, namely -// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. -// -// This might be overly cautious, though it is also what Swift does (and they -// usually have fewer qualms about forwards compatibility, since the runtime -// is shipped with the OS): -// -#[cfg(any( - miri, - not(any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - target_os = "cygwin", - )) -))] -mod imp { - pub unsafe fn init() {} - - pub fn make_handler(_main_thread: bool) -> super::Handler { - super::Handler::null() - } - - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} - -#[cfg(target_os = "cygwin")] -mod imp { - mod c { - pub type PVECTORED_EXCEPTION_HANDLER = - Option i32>; - pub type NTSTATUS = i32; - pub type BOOL = i32; - - unsafe extern "system" { - pub fn AddVectoredExceptionHandler( - first: u32, - handler: PVECTORED_EXCEPTION_HANDLER, - ) -> *mut core::ffi::c_void; - pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; - } - - pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; - pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; - - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_POINTERS { - pub ExceptionRecord: *mut EXCEPTION_RECORD, - // We don't need this field here - // pub Context: *mut CONTEXT, - } - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_RECORD { - pub ExceptionCode: NTSTATUS, - pub ExceptionFlags: u32, - pub ExceptionRecord: *mut EXCEPTION_RECORD, - pub ExceptionAddress: *mut core::ffi::c_void, - pub NumberParameters: u32, - pub ExceptionInformation: [usize; 15], - } - } - - /// Reserve stack space for use in stack overflow exceptions. - fn reserve_stack() { - let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; - // Reserving stack space is not critical so we allow it to fail in the released build of libstd. - // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. - debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); - } - - unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { - // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. - unsafe { - let rec = &(*(*ExceptionInfo).ExceptionRecord); - let code = rec.ExceptionCode; - - if code == c::EXCEPTION_STACK_OVERFLOW { - crate::thread::with_current_name(|name| { - let name = name.unwrap_or(""); - let tid = crate::thread::current_os_id(); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - }); - } - c::EXCEPTION_CONTINUE_SEARCH - } - } - - pub unsafe fn init() { - // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. - unsafe { - let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); - // Similar to the above, adding the stack overflow handler is allowed to fail - // but a debug assert is used so CI will still test that it normally works. - debug_assert!(!result.is_null(), "failed to install exception handler"); - } - // Set the thread stack guarantee for the main thread. - reserve_stack(); - } - - pub fn make_handler(main_thread: bool) -> super::Handler { - if !main_thread { - reserve_stack(); - } - super::Handler::null() - } - - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} diff --git a/library/std/src/sys/pal/unix/stack_overflow/mod.rs b/library/std/src/sys/pal/unix/stack_overflow/mod.rs index 5604e3e6dbf42..f9541e182156c 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/mod.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/mod.rs @@ -58,672 +58,6 @@ mod thread_info; ) ))] mod imp { - use libc::{ - MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, - SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, - }; - #[cfg(not(all(target_os = "linux", target_env = "gnu")))] - use libc::{mmap as mmap64, mprotect, munmap}; - #[cfg(all(target_os = "linux", target_env = "gnu"))] - use libc::{mmap64, mprotect, munmap}; - - use super::Handler; - use super::thread_info::{delete_current_info, set_current_info, with_current_info}; - use crate::ops::Range; - use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; - use crate::sys::pal::unix::conf; - use crate::{io, mem, ptr}; - - /// Signal handler for the SIGSEGV and SIGBUS handlers. - /// - /// We've got guard pages (unmapped pages) at the end of every thread's - /// stack, so if a thread ends up running into the guard page it'll trigger - /// this handler. We want to detect these cases and print out a helpful error - /// saying that the stack has overflowed. All other signals, however, should - /// go back to what they were originally supposed to do. - /// - /// This handler currently exists purely to print an informative message - /// whenever a thread overflows its stack. We then abort to exit and - /// indicate a crash, but to avoid a misleading SIGSEGV that might lead - /// users to believe that unsafe code has accessed an invalid pointer; the - /// SIGSEGV encountered when overflowing the stack is expected and - /// well-defined. - /// - /// If this is not a stack overflow, the handler un-registers itself and - /// then returns (to allow the original signal to be delivered again). - /// Returning from this kind of signal handler is technically not defined - /// to work when reading the POSIX spec strictly, but in practice it turns - /// out many large systems and all implementations allow returning from a - /// signal handler to work. For a more detailed explanation see the - /// comments on #26458. - /// - /// # Safety - /// Rust doesn't call this, it *gets called* by the kernel, which we expect - /// to provide valid parameters. Apart from that, this function does not - /// have any other preconditions. - unsafe extern "C" fn signal_handler( - signum: libc::c_int, - info: *mut libc::siginfo_t, - _data: *mut libc::c_void, - ) { - // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. - let fault_addr = unsafe { (*info).si_addr().addr() }; - - // `with_current_info` expects that the process aborts after it is - // called. If the signal was not caused by a memory access, this might - // not be true. We detect this by noticing that the `si_addr` field is - // zero if the signal is synthetic. - if fault_addr != 0 { - with_current_info(|thread_info| { - // If the faulting address is within the guard page, then we print a - // message saying so and abort. - if let Some(thread_info) = thread_info - && thread_info.guard_page_range.contains(&fault_addr) - { - // Hey you! Yes, you modifying the stack overflow message! - // Please make sure that all functions called here are - // actually async-signal-safe. If they're not, try retrieving - // the information beforehand and storing it in `ThreadInfo`. - // Thank you! - // - says Jonas after having had to watch his carefully - // written code get made unsound again. - let tid = thread_info.tid; - let name = thread_info.name.as_deref().unwrap_or(""); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - rtabort!("stack overflow"); - } - }) - } - - // Unregister ourselves by reverting back to the default behavior. - // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" - let mut action: sigaction = unsafe { mem::zeroed() }; - action.sa_sigaction = SIG_DFL; - // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction - unsafe { sigaction(signum, &action, ptr::null_mut()) }; - - // See comment above for why this function returns. - } - - static PAGE_SIZE: Atomic = AtomicUsize::new(0); - // Store a pointer to the allocation for the main thread's altstack so that - // tools like valgrind don't complain about a leaked unreachable allocation. - // - // If the main thread exits, the process will terminate so there's no use in - // freeing resources. It also means that the altstack is still installed - // while TLS destructors are run on the main thread (c.f. #111272). - static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); - static NEED_ALTSTACK: Atomic = AtomicBool::new(false); - - /// # Safety - /// Must be called only once, on the main thread, during program startup. - pub unsafe fn init() { - PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); - - // SAFETY: - // This is only called on the main thread, and since it is still early - // in the programs lifetime there is (almost) certainly enough stack - // space left to install the guard page. - let mut guard_page_range = unsafe { install_main_guard() }; - - // Even for panic=immediate-abort, installing the guard pages is important for soundness. - // That said, we do not care about giving nice stackoverflow messages via our custom - // signal handler, just exit early and let the user enjoy the segfault. - if cfg!(panic = "immediate-abort") { - return; - } - - // SAFETY: C structures are always zero-initializable. - let mut action: sigaction = unsafe { mem::zeroed() }; - for &signal in &[SIGSEGV, SIGBUS] { - // SAFETY: just fetches the current signal handler into action - unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; - // We assume that overriding the signal handler is always safe, - // which might conflict with certain libraries that rely on a - // specific signal behaviour. To prevent problems, we only - // override the handler if it has not been set yet. - if action.sa_sigaction == SIG_DFL { - if !NEED_ALTSTACK.load(Ordering::Relaxed) { - // haven't set up our sigaltstack yet - NEED_ALTSTACK.store(true, Ordering::Release); - let handler = make_handler(true); - MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); - mem::forget(handler); - - if let Some(guard_page_range) = guard_page_range.take() { - set_current_info(guard_page_range); - } - } - - action.sa_flags = SA_SIGINFO | SA_ONSTACK; - action.sa_sigaction = signal_handler - as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) - as sighandler_t; - // SAFETY: - // `&action` describes a valid `sigaction` and `signal_handler` - // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. - unsafe { sigaction(signal, &action, ptr::null_mut()) }; - } - } - } - - fn get_stack() -> libc::stack_t { - // OpenBSD requires this flag for stack mapping - // otherwise the said mapping will fail as a no-op on most systems - // and has a different meaning on FreeBSD - #[cfg(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - ))] - let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; - #[cfg(not(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - )))] - let flags = MAP_PRIVATE | MAP_ANON; - - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // SAFETY: this does not unmap any existing pages. - let stackp = unsafe { - mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) - }; - if stackp == MAP_FAILED { - panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); - } - // SAFETY: this only affects the memory we just allocated. - let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; - if guard_result != 0 { - panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); - } - // SAFETY: - // The region was allocated with a larger size than `page_size`, so this - // addition is within bounds. - let stackp = unsafe { stackp.add(page_size) }; - - libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } - } - - pub fn make_handler(main_thread: bool) -> Handler { - if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { - return Handler::null(); - } - - if !main_thread { - if let Some(guard_page_range) = current_guard() { - set_current_info(guard_page_range); - } - } - - // Load the current alternate signal stack to see if we need to install - // our own. - // - // SAFETY: C structures are always zero-initializable. - let mut stack = unsafe { mem::zeroed() }; - // SAFETY: `&mut stack` is valid for writing a `stack_t`. - unsafe { sigaltstack(ptr::null(), &mut stack) }; - - // Configure alternate signal stack, if one is not already set. - if stack.ss_flags & SS_DISABLE != 0 { - let stack = get_stack(); - // SAFETY: - // `stack_t` is a freshly allocated stack that's not used anywhere - // else. It contains a guard page, so stack overflows in signal - // handlers will not cause undefined behaviour. We must make the - // fundamental runtime assumption that it is safe to install an - // alternate signal stack if there is none currently installed. - // This might conflict with foreign libraries that use the existence - // of an alternate signal stack as indication that certain runtime - // initialisation by the library has been performed (e.g. old - // versions of `std` assumed that certain thread-locals were already - // accessed and thus initialized in the thread if the stack overflow - // signal was successfully delivered). Such assumptions in other - // libraries are fundamentally flawed, so we pay no regard to them. - unsafe { sigaltstack(&stack, ptr::null_mut()) }; - Handler { data: stack.ss_sp as *mut libc::c_void } - } else { - Handler::null() - } - } - - /// # Safety - /// Must only be called with a pointer returned by `make_handler`, and only - /// once per `Handler`. - pub unsafe fn drop_handler(data: *mut libc::c_void) { - if !data.is_null() { - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - let disabling_stack = libc::stack_t { - ss_sp: ptr::null_mut(), - ss_flags: SS_DISABLE, - // Workaround for bug in macOS implementation of sigaltstack - // UNIX2003 which returns ENOMEM when disabling a stack while - // passing ss_size smaller than MINSIGSTKSZ. According to POSIX - // both ss_sp and ss_size should be ignored in this case. - ss_size: sigstack_size, - }; - // SAFETY: - // We assume that disabling the alternate signal stack is always - // sound, even if the current alternate signal stack is not the one - // we installed in `make_handler`. Any stack overflows from this - // point on will abort the program when the kernel tries to write - // the signal information to the guard page. - // - // FIXME: detect if the stack has changed, and only uninstall if it hasn't. - unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; - // The stack returned by `get_stack` is part of a mapping that - // started one page earlier, so walk back a page and unmap from - // there. - // - // SAFETY: - // This allocation was created by us in `get_stack` and, as the - // alternate signal stack is now disabled, is no longer in use. - unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; - } - - delete_current_info(); - } - - /// Modern kernels on modern hardware can have dynamic signal stack sizes. - #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] - fn sigstack_size() -> usize { - // SAFETY: `getauxval` is always safe to call. - let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; - // If getauxval couldn't find the entry, it returns 0, - // so take the higher of the "constant" and auxval. - // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ - libc::SIGSTKSZ.max(dynamic_sigstksz as _) - } - - /// Not all OS support hardware where this is needed. - #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] - fn sigstack_size() -> usize { - libc::SIGSTKSZ - } - - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; - // SAFETY: - // The pointer is valid for writing a `stack_t`. - assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); - Some(current_stack.ss_sp) - } - - #[cfg(target_os = "macos")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: always safe to call. - let th = unsafe { libc::pthread_self() }; - // SAFETY: `th` is a valid `pthread_t`. - unsafe { - let stackptr = libc::pthread_get_stackaddr_np(th); - let stacksize = libc::pthread_get_stacksize_np(th); - Some(stackptr.map_addr(|addr| addr - stacksize)) - } - } - - #[cfg(target_os = "openbsd")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t`. - // * `&mut current_stack` is coerced to a pointer that is valid for writing - // a `stack_t`. - assert_eq!( - unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, - 0 - ); - - let stack_ptr = current_stack.ss_sp; - // SAFETY: this is always safe to call. - let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { - // main thread - stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) - } else { - // new thread - stack_ptr.addr() - current_stack.ss_size - }; - Some(stack_ptr.with_addr(stackaddr)) - } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", - target_os = "hurd", - target_os = "linux", - target_os = "l4re" - ))] - fn get_stack_start() -> Option<*mut libc::c_void> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut stackaddr = crate::ptr::null_mut(); - let mut stacksize = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, - 0 - ); - ret = Some(stackaddr); - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret - } - - fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - - // Ensure stackaddr is page aligned! A parent process might - // have reset RLIMIT_STACK to be non-page aligned. The - // pthread_attr_getstack() reports the usable stack area - // stackaddr < stackaddr + stacksize, so if stackaddr is not - // page-aligned, calculate the fix such that stackaddr < - // new_page_aligned_stackaddr < stackaddr + stacksize - let remainder = stackaddr % page_size; - Some(if remainder == 0 { - stackptr - } else { - stackptr.with_addr(stackaddr + page_size - remainder) - }) - } - - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard() -> Option> { - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // this way someone on any unix-y OS can check that all these compile - if cfg!(all(target_os = "linux", not(target_env = "musl"))) { - install_main_guard_linux(page_size) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - install_main_guard_linux_musl(page_size) - } else if cfg!(target_os = "freebsd") { - #[cfg(not(target_os = "freebsd"))] - return None; - // The FreeBSD code cannot be checked on non-BSDs. - #[cfg(target_os = "freebsd")] - install_main_guard_freebsd(page_size) - } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { - install_main_guard_bsds(page_size) - } else { - // SAFETY: guaranteed by caller. - unsafe { install_main_guard_default(page_size) } - } - } - - fn install_main_guard_linux(page_size: usize) -> Option> { - // See the corresponding conditional in init(). - // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps - if cfg!(panic = "immediate-abort") { - return None; - } - // Linux doesn't allocate the whole stack right away, and - // the kernel has its own stack-guard mechanism to fault - // when growing too close to an existing mapping. If we map - // our own guard, then the kernel starts enforcing a rather - // large gap above that, rendering much of the possible - // stack space useless. See #43052. - // - // Instead, we'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } - - fn install_main_guard_linux_musl(_page_size: usize) -> Option> { - // For the main thread, the musl's pthread_attr_getstack - // returns the current stack size, rather than maximum size - // it can eventually grow to. It cannot be used to determine - // the position of kernel's stack guard. - None - } - - #[cfg(target_os = "freebsd")] - fn install_main_guard_freebsd(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // FreeBSD's stack autogrows, and optionally includes a guard page - // at the bottom. If we try to remap the bottom of the stack - // ourselves, FreeBSD's guard page moves upwards. So we'll just use - // the builtin guard page. - let stackptr = stack_start_aligned(page_size)?; - let guardaddr = stackptr.addr(); - // Technically the number of guard pages is tunable and controlled - // by the security.bsd.stack_guard_page sysctl. - // By default it is 1, checking once is enough since it is - // a boot time config value. - // FIXME(joboet): this function is only called once, remove the caching. - static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); - - let pages = PAGES.get_or_init(|| { - let mut guard: usize = 0; - let mut size = size_of_val(&guard); - let oid = c"security.bsd.stack_guard_page"; - - let r = unsafe { - libc::sysctlbyname( - oid.as_ptr(), - (&raw mut guard).cast(), - &raw mut size, - ptr::null_mut(), - 0, - ) - }; - if r == 0 { guard } else { 1 } - }); - Some(guardaddr..guardaddr + pages * page_size) - } - - fn install_main_guard_bsds(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // OpenBSD stack already includes a guard page, and stack is - // immutable. - // NetBSD stack includes the guard page. - // - // We'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } - - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard_default(page_size: usize) -> Option> { - // Reallocate the last page of the stack. - // This ensures SIGBUS will be raised on - // stack overflow. - // Systems which enforce strict PAX MPROTECT do not allow - // to mprotect() a mapping with less restrictive permissions - // than the initial mmap() used, so we mmap() here with - // read/write permissions and only then mprotect() it to - // no permissions at all. See issue #50313. - let stackptr = stack_start_aligned(page_size)?; - // SAFETY: - // The memory region from `stackptr..stackptr + page_size` belongs to - // the current thread's stack, and the caller has asserted that there - // is sufficient stack space, which means that this will not overwrite - // any existing allocations. - let result = unsafe { - mmap64( - stackptr, - page_size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON | MAP_FIXED, - -1, - 0, - ) - }; - if result != stackptr || result == MAP_FAILED { - panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); - } - - // SAFETY: - // Since this function is only called on the main thread, the stack will - // not be reused until program exit, so the runtime will never observe - // that part of the stack has been made unusable in this way. - let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; - if result != 0 { - panic!("failed to protect the guard page: {}", io::Error::last_os_error()); - } - - let guardaddr = stackptr.addr(); - - Some(guardaddr..guardaddr + page_size) - } - - #[cfg(any( - target_os = "macos", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ))] - fn current_guard() -> Option> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) - } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "hurd", - target_os = "linux", - target_os = "netbsd", - target_os = "l4re" - ))] - fn current_guard() -> Option> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut guardsize = 0; - // SAFETY: - // `attr` is an initialized attribute object and the pointer is valid - // for writing. - assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); - if guardsize == 0 { - if cfg!(all(target_os = "linux", target_env = "musl")) { - // musl versions before 1.1.19 always reported guard - // size obtained from pthread_attr_get_np as zero. - // Use page size as a fallback. - guardsize = PAGE_SIZE.load(Ordering::Relaxed); - } else { - panic!("there is no guard page"); - } - } - let mut stackptr = crate::ptr::null_mut::(); - let mut size = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, - 0 - ); - - let stackaddr = stackptr.addr(); - ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) - { - // glibc used to include the guard area within the stack, as noted in the BUGS - // section of `man pthread_attr_getguardsize`. This has been corrected starting - // with glibc 2.27, and in some distro backports, so the guard is now placed at the - // end (below) the stack. There's no easy way for us to know which we have at - // runtime, so we'll just match any fault in the range right above or below the - // stack base to call that fault a stack overflow. - Some(stackaddr - guardsize..stackaddr + guardsize) - } else { - Some(stackaddr..stackaddr + guardsize) - }; - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret - } } // This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses @@ -749,96 +83,8 @@ mod imp { )) ))] mod imp { - pub unsafe fn init() {} - - pub fn make_handler(_main_thread: bool) -> super::Handler { - super::Handler::null() - } - - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} } #[cfg(target_os = "cygwin")] mod imp { - mod c { - pub type PVECTORED_EXCEPTION_HANDLER = - Option i32>; - pub type NTSTATUS = i32; - pub type BOOL = i32; - - unsafe extern "system" { - pub fn AddVectoredExceptionHandler( - first: u32, - handler: PVECTORED_EXCEPTION_HANDLER, - ) -> *mut core::ffi::c_void; - pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; - } - - pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; - pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; - - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_POINTERS { - pub ExceptionRecord: *mut EXCEPTION_RECORD, - // We don't need this field here - // pub Context: *mut CONTEXT, - } - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_RECORD { - pub ExceptionCode: NTSTATUS, - pub ExceptionFlags: u32, - pub ExceptionRecord: *mut EXCEPTION_RECORD, - pub ExceptionAddress: *mut core::ffi::c_void, - pub NumberParameters: u32, - pub ExceptionInformation: [usize; 15], - } - } - - /// Reserve stack space for use in stack overflow exceptions. - fn reserve_stack() { - let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; - // Reserving stack space is not critical so we allow it to fail in the released build of libstd. - // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. - debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); - } - - unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { - // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. - unsafe { - let rec = &(*(*ExceptionInfo).ExceptionRecord); - let code = rec.ExceptionCode; - - if code == c::EXCEPTION_STACK_OVERFLOW { - crate::thread::with_current_name(|name| { - let name = name.unwrap_or(""); - let tid = crate::thread::current_os_id(); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - }); - } - c::EXCEPTION_CONTINUE_SEARCH - } - } - - pub unsafe fn init() { - // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. - unsafe { - let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); - // Similar to the above, adding the stack overflow handler is allowed to fail - // but a debug assert is used so CI will still test that it normally works. - debug_assert!(!result.is_null(), "failed to install exception handler"); - } - // Set the thread stack guarantee for the main thread. - reserve_stack(); - } - - pub fn make_handler(main_thread: bool) -> super::Handler { - if !main_thread { - reserve_stack(); - } - super::Handler::null() - } - - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} } From e449348b8a02e876834a2f152aef1634f22d2904 Mon Sep 17 00:00:00 2001 From: joboet Date: Wed, 23 Sep 2026 13:48:51 +0200 Subject: [PATCH 08/18] std: split stack overflow module (format) --- .../pal/unix/stack_overflow/handler_cygwin.rs | 134 +- .../pal/unix/stack_overflow/handler_none.rs | 10 +- .../pal/unix/stack_overflow/handler_signal.rs | 1223 ++++++++--------- .../src/sys/pal/unix/stack_overflow/mod.rs | 9 +- 4 files changed, 683 insertions(+), 693 deletions(-) diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs index 089bd2c95073d..3289efe05b068 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs @@ -1,81 +1,81 @@ - mod c { - pub type PVECTORED_EXCEPTION_HANDLER = - Option i32>; - pub type NTSTATUS = i32; - pub type BOOL = i32; +mod c { + pub type PVECTORED_EXCEPTION_HANDLER = + Option i32>; + pub type NTSTATUS = i32; + pub type BOOL = i32; - unsafe extern "system" { - pub fn AddVectoredExceptionHandler( - first: u32, - handler: PVECTORED_EXCEPTION_HANDLER, - ) -> *mut core::ffi::c_void; - pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; - } + unsafe extern "system" { + pub fn AddVectoredExceptionHandler( + first: u32, + handler: PVECTORED_EXCEPTION_HANDLER, + ) -> *mut core::ffi::c_void; + pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; + } - pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; - pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; + pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; + pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_POINTERS { - pub ExceptionRecord: *mut EXCEPTION_RECORD, - // We don't need this field here - // pub Context: *mut CONTEXT, - } - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_RECORD { - pub ExceptionCode: NTSTATUS, - pub ExceptionFlags: u32, - pub ExceptionRecord: *mut EXCEPTION_RECORD, - pub ExceptionAddress: *mut core::ffi::c_void, - pub NumberParameters: u32, - pub ExceptionInformation: [usize; 15], - } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_POINTERS { + pub ExceptionRecord: *mut EXCEPTION_RECORD, + // We don't need this field here + // pub Context: *mut CONTEXT, } - - /// Reserve stack space for use in stack overflow exceptions. - fn reserve_stack() { - let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; - // Reserving stack space is not critical so we allow it to fail in the released build of libstd. - // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. - debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_RECORD { + pub ExceptionCode: NTSTATUS, + pub ExceptionFlags: u32, + pub ExceptionRecord: *mut EXCEPTION_RECORD, + pub ExceptionAddress: *mut core::ffi::c_void, + pub NumberParameters: u32, + pub ExceptionInformation: [usize; 15], } +} - unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { - // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. - unsafe { - let rec = &(*(*ExceptionInfo).ExceptionRecord); - let code = rec.ExceptionCode; +/// Reserve stack space for use in stack overflow exceptions. +fn reserve_stack() { + let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; + // Reserving stack space is not critical so we allow it to fail in the released build of libstd. + // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. + debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); +} - if code == c::EXCEPTION_STACK_OVERFLOW { - crate::thread::with_current_name(|name| { - let name = name.unwrap_or(""); - let tid = crate::thread::current_os_id(); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - }); - } - c::EXCEPTION_CONTINUE_SEARCH +unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { + // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. + unsafe { + let rec = &(*(*ExceptionInfo).ExceptionRecord); + let code = rec.ExceptionCode; + + if code == c::EXCEPTION_STACK_OVERFLOW { + crate::thread::with_current_name(|name| { + let name = name.unwrap_or(""); + let tid = crate::thread::current_os_id(); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + }); } + c::EXCEPTION_CONTINUE_SEARCH } +} - pub unsafe fn init() { - // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. - unsafe { - let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); - // Similar to the above, adding the stack overflow handler is allowed to fail - // but a debug assert is used so CI will still test that it normally works. - debug_assert!(!result.is_null(), "failed to install exception handler"); - } - // Set the thread stack guarantee for the main thread. - reserve_stack(); +pub unsafe fn init() { + // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. + unsafe { + let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); + // Similar to the above, adding the stack overflow handler is allowed to fail + // but a debug assert is used so CI will still test that it normally works. + debug_assert!(!result.is_null(), "failed to install exception handler"); } + // Set the thread stack guarantee for the main thread. + reserve_stack(); +} - pub fn make_handler(main_thread: bool) -> super::Handler { - if !main_thread { - reserve_stack(); - } - super::Handler::null() +pub fn make_handler(main_thread: bool) -> super::Handler { + if !main_thread { + reserve_stack(); } + super::Handler::null() +} - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +pub unsafe fn drop_handler(_data: *mut libc::c_void) {} diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs index f765090ffff7d..a0e2dfa941867 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs @@ -1,7 +1,7 @@ - pub unsafe fn init() {} +pub unsafe fn init() {} - pub fn make_handler(_main_thread: bool) -> super::Handler { - super::Handler::null() - } +pub fn make_handler(_main_thread: bool) -> super::Handler { + super::Handler::null() +} - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +pub unsafe fn drop_handler(_data: *mut libc::c_void) {} diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs index ee4de76121097..54e94e0f22578 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs @@ -1,666 +1,659 @@ - use libc::{ - MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, - SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, - }; - #[cfg(not(all(target_os = "linux", target_env = "gnu")))] - use libc::{mmap as mmap64, mprotect, munmap}; - #[cfg(all(target_os = "linux", target_env = "gnu"))] - use libc::{mmap64, mprotect, munmap}; - - use super::Handler; - use super::thread_info::{delete_current_info, set_current_info, with_current_info}; - use crate::ops::Range; - use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; - use crate::sys::pal::unix::conf; - use crate::{io, mem, ptr}; - - /// Signal handler for the SIGSEGV and SIGBUS handlers. - /// - /// We've got guard pages (unmapped pages) at the end of every thread's - /// stack, so if a thread ends up running into the guard page it'll trigger - /// this handler. We want to detect these cases and print out a helpful error - /// saying that the stack has overflowed. All other signals, however, should - /// go back to what they were originally supposed to do. - /// - /// This handler currently exists purely to print an informative message - /// whenever a thread overflows its stack. We then abort to exit and - /// indicate a crash, but to avoid a misleading SIGSEGV that might lead - /// users to believe that unsafe code has accessed an invalid pointer; the - /// SIGSEGV encountered when overflowing the stack is expected and - /// well-defined. - /// - /// If this is not a stack overflow, the handler un-registers itself and - /// then returns (to allow the original signal to be delivered again). - /// Returning from this kind of signal handler is technically not defined - /// to work when reading the POSIX spec strictly, but in practice it turns - /// out many large systems and all implementations allow returning from a - /// signal handler to work. For a more detailed explanation see the - /// comments on #26458. - /// - /// # Safety - /// Rust doesn't call this, it *gets called* by the kernel, which we expect - /// to provide valid parameters. Apart from that, this function does not - /// have any other preconditions. - unsafe extern "C" fn signal_handler( - signum: libc::c_int, - info: *mut libc::siginfo_t, - _data: *mut libc::c_void, - ) { - // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. - let fault_addr = unsafe { (*info).si_addr().addr() }; - - // `with_current_info` expects that the process aborts after it is - // called. If the signal was not caused by a memory access, this might - // not be true. We detect this by noticing that the `si_addr` field is - // zero if the signal is synthetic. - if fault_addr != 0 { - with_current_info(|thread_info| { - // If the faulting address is within the guard page, then we print a - // message saying so and abort. - if let Some(thread_info) = thread_info - && thread_info.guard_page_range.contains(&fault_addr) - { - // Hey you! Yes, you modifying the stack overflow message! - // Please make sure that all functions called here are - // actually async-signal-safe. If they're not, try retrieving - // the information beforehand and storing it in `ThreadInfo`. - // Thank you! - // - says Jonas after having had to watch his carefully - // written code get made unsound again. - let tid = thread_info.tid; - let name = thread_info.name.as_deref().unwrap_or(""); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - rtabort!("stack overflow"); - } - }) - } - - // Unregister ourselves by reverting back to the default behavior. - // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" - let mut action: sigaction = unsafe { mem::zeroed() }; - action.sa_sigaction = SIG_DFL; - // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction - unsafe { sigaction(signum, &action, ptr::null_mut()) }; - - // See comment above for why this function returns. +use libc::{ + MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, + SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, +}; +#[cfg(not(all(target_os = "linux", target_env = "gnu")))] +use libc::{mmap as mmap64, mprotect, munmap}; +#[cfg(all(target_os = "linux", target_env = "gnu"))] +use libc::{mmap64, mprotect, munmap}; + +use super::Handler; +use super::thread_info::{delete_current_info, set_current_info, with_current_info}; +use crate::ops::Range; +use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; +use crate::sys::pal::unix::conf; +use crate::{io, mem, ptr}; + +/// Signal handler for the SIGSEGV and SIGBUS handlers. +/// +/// We've got guard pages (unmapped pages) at the end of every thread's +/// stack, so if a thread ends up running into the guard page it'll trigger +/// this handler. We want to detect these cases and print out a helpful error +/// saying that the stack has overflowed. All other signals, however, should +/// go back to what they were originally supposed to do. +/// +/// This handler currently exists purely to print an informative message +/// whenever a thread overflows its stack. We then abort to exit and +/// indicate a crash, but to avoid a misleading SIGSEGV that might lead +/// users to believe that unsafe code has accessed an invalid pointer; the +/// SIGSEGV encountered when overflowing the stack is expected and +/// well-defined. +/// +/// If this is not a stack overflow, the handler un-registers itself and +/// then returns (to allow the original signal to be delivered again). +/// Returning from this kind of signal handler is technically not defined +/// to work when reading the POSIX spec strictly, but in practice it turns +/// out many large systems and all implementations allow returning from a +/// signal handler to work. For a more detailed explanation see the +/// comments on #26458. +/// +/// # Safety +/// Rust doesn't call this, it *gets called* by the kernel, which we expect +/// to provide valid parameters. Apart from that, this function does not +/// have any other preconditions. +unsafe extern "C" fn signal_handler( + signum: libc::c_int, + info: *mut libc::siginfo_t, + _data: *mut libc::c_void, +) { + // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. + let fault_addr = unsafe { (*info).si_addr().addr() }; + + // `with_current_info` expects that the process aborts after it is + // called. If the signal was not caused by a memory access, this might + // not be true. We detect this by noticing that the `si_addr` field is + // zero if the signal is synthetic. + if fault_addr != 0 { + with_current_info(|thread_info| { + // If the faulting address is within the guard page, then we print a + // message saying so and abort. + if let Some(thread_info) = thread_info + && thread_info.guard_page_range.contains(&fault_addr) + { + // Hey you! Yes, you modifying the stack overflow message! + // Please make sure that all functions called here are + // actually async-signal-safe. If they're not, try retrieving + // the information beforehand and storing it in `ThreadInfo`. + // Thank you! + // - says Jonas after having had to watch his carefully + // written code get made unsound again. + let tid = thread_info.tid; + let name = thread_info.name.as_deref().unwrap_or(""); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + rtabort!("stack overflow"); + } + }) } - static PAGE_SIZE: Atomic = AtomicUsize::new(0); - // Store a pointer to the allocation for the main thread's altstack so that - // tools like valgrind don't complain about a leaked unreachable allocation. - // - // If the main thread exits, the process will terminate so there's no use in - // freeing resources. It also means that the altstack is still installed - // while TLS destructors are run on the main thread (c.f. #111272). - static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); - static NEED_ALTSTACK: Atomic = AtomicBool::new(false); - - /// # Safety - /// Must be called only once, on the main thread, during program startup. - pub unsafe fn init() { - PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); - - // SAFETY: - // This is only called on the main thread, and since it is still early - // in the programs lifetime there is (almost) certainly enough stack - // space left to install the guard page. - let mut guard_page_range = unsafe { install_main_guard() }; - - // Even for panic=immediate-abort, installing the guard pages is important for soundness. - // That said, we do not care about giving nice stackoverflow messages via our custom - // signal handler, just exit early and let the user enjoy the segfault. - if cfg!(panic = "immediate-abort") { - return; - } + // Unregister ourselves by reverting back to the default behavior. + // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" + let mut action: sigaction = unsafe { mem::zeroed() }; + action.sa_sigaction = SIG_DFL; + // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction + unsafe { sigaction(signum, &action, ptr::null_mut()) }; + + // See comment above for why this function returns. +} + +static PAGE_SIZE: Atomic = AtomicUsize::new(0); +// Store a pointer to the allocation for the main thread's altstack so that +// tools like valgrind don't complain about a leaked unreachable allocation. +// +// If the main thread exits, the process will terminate so there's no use in +// freeing resources. It also means that the altstack is still installed +// while TLS destructors are run on the main thread (c.f. #111272). +static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); +static NEED_ALTSTACK: Atomic = AtomicBool::new(false); + +/// # Safety +/// Must be called only once, on the main thread, during program startup. +pub unsafe fn init() { + PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); + + // SAFETY: + // This is only called on the main thread, and since it is still early + // in the programs lifetime there is (almost) certainly enough stack + // space left to install the guard page. + let mut guard_page_range = unsafe { install_main_guard() }; + + // Even for panic=immediate-abort, installing the guard pages is important for soundness. + // That said, we do not care about giving nice stackoverflow messages via our custom + // signal handler, just exit early and let the user enjoy the segfault. + if cfg!(panic = "immediate-abort") { + return; + } - // SAFETY: C structures are always zero-initializable. - let mut action: sigaction = unsafe { mem::zeroed() }; - for &signal in &[SIGSEGV, SIGBUS] { - // SAFETY: just fetches the current signal handler into action - unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; - // We assume that overriding the signal handler is always safe, - // which might conflict with certain libraries that rely on a - // specific signal behaviour. To prevent problems, we only - // override the handler if it has not been set yet. - if action.sa_sigaction == SIG_DFL { - if !NEED_ALTSTACK.load(Ordering::Relaxed) { - // haven't set up our sigaltstack yet - NEED_ALTSTACK.store(true, Ordering::Release); - let handler = make_handler(true); - MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); - mem::forget(handler); - - if let Some(guard_page_range) = guard_page_range.take() { - set_current_info(guard_page_range); - } + // SAFETY: C structures are always zero-initializable. + let mut action: sigaction = unsafe { mem::zeroed() }; + for &signal in &[SIGSEGV, SIGBUS] { + // SAFETY: just fetches the current signal handler into action + unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; + // We assume that overriding the signal handler is always safe, + // which might conflict with certain libraries that rely on a + // specific signal behaviour. To prevent problems, we only + // override the handler if it has not been set yet. + if action.sa_sigaction == SIG_DFL { + if !NEED_ALTSTACK.load(Ordering::Relaxed) { + // haven't set up our sigaltstack yet + NEED_ALTSTACK.store(true, Ordering::Release); + let handler = make_handler(true); + MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); + mem::forget(handler); + + if let Some(guard_page_range) = guard_page_range.take() { + set_current_info(guard_page_range); } - - action.sa_flags = SA_SIGINFO | SA_ONSTACK; - action.sa_sigaction = signal_handler - as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) - as sighandler_t; - // SAFETY: - // `&action` describes a valid `sigaction` and `signal_handler` - // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. - unsafe { sigaction(signal, &action, ptr::null_mut()) }; } - } - } - fn get_stack() -> libc::stack_t { - // OpenBSD requires this flag for stack mapping - // otherwise the said mapping will fail as a no-op on most systems - // and has a different meaning on FreeBSD - #[cfg(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - ))] - let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; - #[cfg(not(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - )))] - let flags = MAP_PRIVATE | MAP_ANON; - - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // SAFETY: this does not unmap any existing pages. - let stackp = unsafe { - mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) - }; - if stackp == MAP_FAILED { - panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); - } - // SAFETY: this only affects the memory we just allocated. - let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; - if guard_result != 0 { - panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + action.sa_sigaction = signal_handler + as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) + as sighandler_t; + // SAFETY: + // `&action` describes a valid `sigaction` and `signal_handler` + // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. + unsafe { sigaction(signal, &action, ptr::null_mut()) }; } - // SAFETY: - // The region was allocated with a larger size than `page_size`, so this - // addition is within bounds. - let stackp = unsafe { stackp.add(page_size) }; - - libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } } +} - pub fn make_handler(main_thread: bool) -> Handler { - if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { - return Handler::null(); - } +fn get_stack() -> libc::stack_t { + // OpenBSD requires this flag for stack mapping + // otherwise the said mapping will fail as a no-op on most systems + // and has a different meaning on FreeBSD + #[cfg(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + ))] + let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; + #[cfg(not(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + )))] + let flags = MAP_PRIVATE | MAP_ANON; - if !main_thread { - if let Some(guard_page_range) = current_guard() { - set_current_info(guard_page_range); - } - } + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); - // Load the current alternate signal stack to see if we need to install - // our own. - // - // SAFETY: C structures are always zero-initializable. - let mut stack = unsafe { mem::zeroed() }; - // SAFETY: `&mut stack` is valid for writing a `stack_t`. - unsafe { sigaltstack(ptr::null(), &mut stack) }; - - // Configure alternate signal stack, if one is not already set. - if stack.ss_flags & SS_DISABLE != 0 { - let stack = get_stack(); - // SAFETY: - // `stack_t` is a freshly allocated stack that's not used anywhere - // else. It contains a guard page, so stack overflows in signal - // handlers will not cause undefined behaviour. We must make the - // fundamental runtime assumption that it is safe to install an - // alternate signal stack if there is none currently installed. - // This might conflict with foreign libraries that use the existence - // of an alternate signal stack as indication that certain runtime - // initialisation by the library has been performed (e.g. old - // versions of `std` assumed that certain thread-locals were already - // accessed and thus initialized in the thread if the stack overflow - // signal was successfully delivered). Such assumptions in other - // libraries are fundamentally flawed, so we pay no regard to them. - unsafe { sigaltstack(&stack, ptr::null_mut()) }; - Handler { data: stack.ss_sp as *mut libc::c_void } - } else { - Handler::null() - } + // SAFETY: this does not unmap any existing pages. + let stackp = unsafe { + mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) + }; + if stackp == MAP_FAILED { + panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); } + // SAFETY: this only affects the memory we just allocated. + let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; + if guard_result != 0 { + panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); + } + // SAFETY: + // The region was allocated with a larger size than `page_size`, so this + // addition is within bounds. + let stackp = unsafe { stackp.add(page_size) }; - /// # Safety - /// Must only be called with a pointer returned by `make_handler`, and only - /// once per `Handler`. - pub unsafe fn drop_handler(data: *mut libc::c_void) { - if !data.is_null() { - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - let disabling_stack = libc::stack_t { - ss_sp: ptr::null_mut(), - ss_flags: SS_DISABLE, - // Workaround for bug in macOS implementation of sigaltstack - // UNIX2003 which returns ENOMEM when disabling a stack while - // passing ss_size smaller than MINSIGSTKSZ. According to POSIX - // both ss_sp and ss_size should be ignored in this case. - ss_size: sigstack_size, - }; - // SAFETY: - // We assume that disabling the alternate signal stack is always - // sound, even if the current alternate signal stack is not the one - // we installed in `make_handler`. Any stack overflows from this - // point on will abort the program when the kernel tries to write - // the signal information to the guard page. - // - // FIXME: detect if the stack has changed, and only uninstall if it hasn't. - unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; - // The stack returned by `get_stack` is part of a mapping that - // started one page earlier, so walk back a page and unmap from - // there. - // - // SAFETY: - // This allocation was created by us in `get_stack` and, as the - // alternate signal stack is now disabled, is no longer in use. - unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; - } + libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } +} - delete_current_info(); +pub fn make_handler(main_thread: bool) -> Handler { + if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { + return Handler::null(); } - /// Modern kernels on modern hardware can have dynamic signal stack sizes. - #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] - fn sigstack_size() -> usize { - // SAFETY: `getauxval` is always safe to call. - let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; - // If getauxval couldn't find the entry, it returns 0, - // so take the higher of the "constant" and auxval. - // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ - libc::SIGSTKSZ.max(dynamic_sigstksz as _) + if !main_thread { + if let Some(guard_page_range) = current_guard() { + set_current_info(guard_page_range); + } } - /// Not all OS support hardware where this is needed. - #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] - fn sigstack_size() -> usize { - libc::SIGSTKSZ + // Load the current alternate signal stack to see if we need to install + // our own. + // + // SAFETY: C structures are always zero-initializable. + let mut stack = unsafe { mem::zeroed() }; + // SAFETY: `&mut stack` is valid for writing a `stack_t`. + unsafe { sigaltstack(ptr::null(), &mut stack) }; + + // Configure alternate signal stack, if one is not already set. + if stack.ss_flags & SS_DISABLE != 0 { + let stack = get_stack(); + // SAFETY: + // `stack_t` is a freshly allocated stack that's not used anywhere + // else. It contains a guard page, so stack overflows in signal + // handlers will not cause undefined behaviour. We must make the + // fundamental runtime assumption that it is safe to install an + // alternate signal stack if there is none currently installed. + // This might conflict with foreign libraries that use the existence + // of an alternate signal stack as indication that certain runtime + // initialisation by the library has been performed (e.g. old + // versions of `std` assumed that certain thread-locals were already + // accessed and thus initialized in the thread if the stack overflow + // signal was successfully delivered). Such assumptions in other + // libraries are fundamentally flawed, so we pay no regard to them. + unsafe { sigaltstack(&stack, ptr::null_mut()) }; + Handler { data: stack.ss_sp as *mut libc::c_void } + } else { + Handler::null() } +} - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; +/// # Safety +/// Must only be called with a pointer returned by `make_handler`, and only +/// once per `Handler`. +pub unsafe fn drop_handler(data: *mut libc::c_void) { + if !data.is_null() { + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + let disabling_stack = libc::stack_t { + ss_sp: ptr::null_mut(), + ss_flags: SS_DISABLE, + // Workaround for bug in macOS implementation of sigaltstack + // UNIX2003 which returns ENOMEM when disabling a stack while + // passing ss_size smaller than MINSIGSTKSZ. According to POSIX + // both ss_sp and ss_size should be ignored in this case. + ss_size: sigstack_size, + }; // SAFETY: - // The pointer is valid for writing a `stack_t`. - assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); - Some(current_stack.ss_sp) + // We assume that disabling the alternate signal stack is always + // sound, even if the current alternate signal stack is not the one + // we installed in `make_handler`. Any stack overflows from this + // point on will abort the program when the kernel tries to write + // the signal information to the guard page. + // + // FIXME: detect if the stack has changed, and only uninstall if it hasn't. + unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; + // The stack returned by `get_stack` is part of a mapping that + // started one page earlier, so walk back a page and unmap from + // there. + // + // SAFETY: + // This allocation was created by us in `get_stack` and, as the + // alternate signal stack is now disabled, is no longer in use. + unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; } - #[cfg(target_os = "macos")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: always safe to call. - let th = unsafe { libc::pthread_self() }; - // SAFETY: `th` is a valid `pthread_t`. - unsafe { - let stackptr = libc::pthread_get_stackaddr_np(th); - let stacksize = libc::pthread_get_stacksize_np(th); - Some(stackptr.map_addr(|addr| addr - stacksize)) - } + delete_current_info(); +} + +/// Modern kernels on modern hardware can have dynamic signal stack sizes. +#[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] +fn sigstack_size() -> usize { + // SAFETY: `getauxval` is always safe to call. + let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; + // If getauxval couldn't find the entry, it returns 0, + // so take the higher of the "constant" and auxval. + // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ + libc::SIGSTKSZ.max(dynamic_sigstksz as _) +} + +/// Not all OS support hardware where this is needed. +#[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] +fn sigstack_size() -> usize { + libc::SIGSTKSZ +} + +#[cfg(any(target_os = "solaris", target_os = "illumos"))] +fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // The pointer is valid for writing a `stack_t`. + assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); + Some(current_stack.ss_sp) +} + +#[cfg(target_os = "macos")] +fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: always safe to call. + let th = unsafe { libc::pthread_self() }; + // SAFETY: `th` is a valid `pthread_t`. + unsafe { + let stackptr = libc::pthread_get_stackaddr_np(th); + let stacksize = libc::pthread_get_stacksize_np(th); + Some(stackptr.map_addr(|addr| addr - stacksize)) } +} + +#[cfg(target_os = "openbsd")] +fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t`. + // * `&mut current_stack` is coerced to a pointer that is valid for writing + // a `stack_t`. + assert_eq!(unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, 0); + + let stack_ptr = current_stack.ss_sp; + // SAFETY: this is always safe to call. + let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { + // main thread + stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) + } else { + // new thread + stack_ptr.addr() - current_stack.ss_size + }; + Some(stack_ptr.with_addr(stackaddr)) +} + +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "hurd", + target_os = "linux", + target_os = "l4re" +))] +fn get_stack_start() -> Option<*mut libc::c_void> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); - #[cfg(target_os = "openbsd")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut stackaddr = crate::ptr::null_mut(); + let mut stacksize = 0; // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t`. - // * `&mut current_stack` is coerced to a pointer that is valid for writing - // a `stack_t`. + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. assert_eq!( - unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, 0 ); - - let stack_ptr = current_stack.ss_sp; - // SAFETY: this is always safe to call. - let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { - // main thread - stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) - } else { - // new thread - stack_ptr.addr() - current_stack.ss_size - }; - Some(stack_ptr.with_addr(stackaddr)) + ret = Some(stackaddr); } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", - target_os = "hurd", - target_os = "linux", - target_os = "l4re" - ))] - fn get_stack_start() -> Option<*mut libc::c_void> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + if e == 0 || cfg!(target_os = "freebsd") { // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + } + ret +} + +fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + + // Ensure stackaddr is page aligned! A parent process might + // have reset RLIMIT_STACK to be non-page aligned. The + // pthread_attr_getstack() reports the usable stack area + // stackaddr < stackaddr + stacksize, so if stackaddr is not + // page-aligned, calculate the fix such that stackaddr < + // new_page_aligned_stackaddr < stackaddr + stacksize + let remainder = stackaddr % page_size; + Some(if remainder == 0 { + stackptr + } else { + stackptr.with_addr(stackaddr + page_size - remainder) + }) +} + +/// # Safety +/// This function must only be called from the main thread, and there must +/// be sufficient stack space remaining to place a stack guard. +unsafe fn install_main_guard() -> Option> { + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + + // this way someone on any unix-y OS can check that all these compile + if cfg!(all(target_os = "linux", not(target_env = "musl"))) { + install_main_guard_linux(page_size) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + install_main_guard_linux_musl(page_size) + } else if cfg!(target_os = "freebsd") { #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut stackaddr = crate::ptr::null_mut(); - let mut stacksize = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, - 0 - ); - ret = Some(stackaddr); - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret + return None; + // The FreeBSD code cannot be checked on non-BSDs. + #[cfg(target_os = "freebsd")] + install_main_guard_freebsd(page_size) + } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { + install_main_guard_bsds(page_size) + } else { + // SAFETY: guaranteed by caller. + unsafe { install_main_guard_default(page_size) } } +} - fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - - // Ensure stackaddr is page aligned! A parent process might - // have reset RLIMIT_STACK to be non-page aligned. The - // pthread_attr_getstack() reports the usable stack area - // stackaddr < stackaddr + stacksize, so if stackaddr is not - // page-aligned, calculate the fix such that stackaddr < - // new_page_aligned_stackaddr < stackaddr + stacksize - let remainder = stackaddr % page_size; - Some(if remainder == 0 { - stackptr - } else { - stackptr.with_addr(stackaddr + page_size - remainder) - }) +fn install_main_guard_linux(page_size: usize) -> Option> { + // See the corresponding conditional in init(). + // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps + if cfg!(panic = "immediate-abort") { + return None; } - - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard() -> Option> { - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // this way someone on any unix-y OS can check that all these compile - if cfg!(all(target_os = "linux", not(target_env = "musl"))) { - install_main_guard_linux(page_size) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - install_main_guard_linux_musl(page_size) - } else if cfg!(target_os = "freebsd") { - #[cfg(not(target_os = "freebsd"))] - return None; - // The FreeBSD code cannot be checked on non-BSDs. - #[cfg(target_os = "freebsd")] - install_main_guard_freebsd(page_size) - } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { - install_main_guard_bsds(page_size) - } else { - // SAFETY: guaranteed by caller. - unsafe { install_main_guard_default(page_size) } - } + // Linux doesn't allocate the whole stack right away, and + // the kernel has its own stack-guard mechanism to fault + // when growing too close to an existing mapping. If we map + // our own guard, then the kernel starts enforcing a rather + // large gap above that, rendering much of the possible + // stack space useless. See #43052. + // + // Instead, we'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) +} + +fn install_main_guard_linux_musl(_page_size: usize) -> Option> { + // For the main thread, the musl's pthread_attr_getstack + // returns the current stack size, rather than maximum size + // it can eventually grow to. It cannot be used to determine + // the position of kernel's stack guard. + None +} + +#[cfg(target_os = "freebsd")] +fn install_main_guard_freebsd(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; } - - fn install_main_guard_linux(page_size: usize) -> Option> { - // See the corresponding conditional in init(). - // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps - if cfg!(panic = "immediate-abort") { - return None; - } - // Linux doesn't allocate the whole stack right away, and - // the kernel has its own stack-guard mechanism to fault - // when growing too close to an existing mapping. If we map - // our own guard, then the kernel starts enforcing a rather - // large gap above that, rendering much of the possible - // stack space useless. See #43052. - // - // Instead, we'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) + // FreeBSD's stack autogrows, and optionally includes a guard page + // at the bottom. If we try to remap the bottom of the stack + // ourselves, FreeBSD's guard page moves upwards. So we'll just use + // the builtin guard page. + let stackptr = stack_start_aligned(page_size)?; + let guardaddr = stackptr.addr(); + // Technically the number of guard pages is tunable and controlled + // by the security.bsd.stack_guard_page sysctl. + // By default it is 1, checking once is enough since it is + // a boot time config value. + // FIXME(joboet): this function is only called once, remove the caching. + static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); + + let pages = PAGES.get_or_init(|| { + let mut guard: usize = 0; + let mut size = size_of_val(&guard); + let oid = c"security.bsd.stack_guard_page"; + + let r = unsafe { + libc::sysctlbyname( + oid.as_ptr(), + (&raw mut guard).cast(), + &raw mut size, + ptr::null_mut(), + 0, + ) + }; + if r == 0 { guard } else { 1 } + }); + Some(guardaddr..guardaddr + pages * page_size) +} + +fn install_main_guard_bsds(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; } - - fn install_main_guard_linux_musl(_page_size: usize) -> Option> { - // For the main thread, the musl's pthread_attr_getstack - // returns the current stack size, rather than maximum size - // it can eventually grow to. It cannot be used to determine - // the position of kernel's stack guard. - None + // OpenBSD stack already includes a guard page, and stack is + // immutable. + // NetBSD stack includes the guard page. + // + // We'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) +} + +/// # Safety +/// This function must only be called from the main thread, and there must +/// be sufficient stack space remaining to place a stack guard. +unsafe fn install_main_guard_default(page_size: usize) -> Option> { + // Reallocate the last page of the stack. + // This ensures SIGBUS will be raised on + // stack overflow. + // Systems which enforce strict PAX MPROTECT do not allow + // to mprotect() a mapping with less restrictive permissions + // than the initial mmap() used, so we mmap() here with + // read/write permissions and only then mprotect() it to + // no permissions at all. See issue #50313. + let stackptr = stack_start_aligned(page_size)?; + // SAFETY: + // The memory region from `stackptr..stackptr + page_size` belongs to + // the current thread's stack, and the caller has asserted that there + // is sufficient stack space, which means that this will not overwrite + // any existing allocations. + let result = unsafe { + mmap64( + stackptr, + page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + -1, + 0, + ) + }; + if result != stackptr || result == MAP_FAILED { + panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); } - #[cfg(target_os = "freebsd")] - fn install_main_guard_freebsd(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // FreeBSD's stack autogrows, and optionally includes a guard page - // at the bottom. If we try to remap the bottom of the stack - // ourselves, FreeBSD's guard page moves upwards. So we'll just use - // the builtin guard page. - let stackptr = stack_start_aligned(page_size)?; - let guardaddr = stackptr.addr(); - // Technically the number of guard pages is tunable and controlled - // by the security.bsd.stack_guard_page sysctl. - // By default it is 1, checking once is enough since it is - // a boot time config value. - // FIXME(joboet): this function is only called once, remove the caching. - static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); - - let pages = PAGES.get_or_init(|| { - let mut guard: usize = 0; - let mut size = size_of_val(&guard); - let oid = c"security.bsd.stack_guard_page"; - - let r = unsafe { - libc::sysctlbyname( - oid.as_ptr(), - (&raw mut guard).cast(), - &raw mut size, - ptr::null_mut(), - 0, - ) - }; - if r == 0 { guard } else { 1 } - }); - Some(guardaddr..guardaddr + pages * page_size) + // SAFETY: + // Since this function is only called on the main thread, the stack will + // not be reused until program exit, so the runtime will never observe + // that part of the stack has been made unusable in this way. + let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; + if result != 0 { + panic!("failed to protect the guard page: {}", io::Error::last_os_error()); } - fn install_main_guard_bsds(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // OpenBSD stack already includes a guard page, and stack is - // immutable. - // NetBSD stack includes the guard page. - // - // We'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) + let guardaddr = stackptr.addr(); + + Some(guardaddr..guardaddr + page_size) +} + +#[cfg(any( + target_os = "macos", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", +))] +fn current_guard() -> Option> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) +} + +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "hurd", + target_os = "linux", + target_os = "netbsd", + target_os = "l4re" +))] +fn current_guard() -> Option> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard_default(page_size: usize) -> Option> { - // Reallocate the last page of the stack. - // This ensures SIGBUS will be raised on - // stack overflow. - // Systems which enforce strict PAX MPROTECT do not allow - // to mprotect() a mapping with less restrictive permissions - // than the initial mmap() used, so we mmap() here with - // read/write permissions and only then mprotect() it to - // no permissions at all. See issue #50313. - let stackptr = stack_start_aligned(page_size)?; + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut guardsize = 0; // SAFETY: - // The memory region from `stackptr..stackptr + page_size` belongs to - // the current thread's stack, and the caller has asserted that there - // is sufficient stack space, which means that this will not overwrite - // any existing allocations. - let result = unsafe { - mmap64( - stackptr, - page_size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON | MAP_FIXED, - -1, - 0, - ) - }; - if result != stackptr || result == MAP_FAILED { - panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); + // `attr` is an initialized attribute object and the pointer is valid + // for writing. + assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); + if guardsize == 0 { + if cfg!(all(target_os = "linux", target_env = "musl")) { + // musl versions before 1.1.19 always reported guard + // size obtained from pthread_attr_get_np as zero. + // Use page size as a fallback. + guardsize = PAGE_SIZE.load(Ordering::Relaxed); + } else { + panic!("there is no guard page"); + } } - + let mut stackptr = crate::ptr::null_mut::(); + let mut size = 0; // SAFETY: - // Since this function is only called on the main thread, the stack will - // not be reused until program exit, so the runtime will never observe - // that part of the stack has been made unusable in this way. - let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; - if result != 0 { - panic!("failed to protect the guard page: {}", io::Error::last_os_error()); - } - - let guardaddr = stackptr.addr(); + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!(unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, 0); - Some(guardaddr..guardaddr + page_size) - } - - #[cfg(any( - target_os = "macos", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ))] - fn current_guard() -> Option> { - let stackptr = get_stack_start()?; let stackaddr = stackptr.addr(); - Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) + ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) { + // glibc used to include the guard area within the stack, as noted in the BUGS + // section of `man pthread_attr_getguardsize`. This has been corrected starting + // with glibc 2.27, and in some distro backports, so the guard is now placed at the + // end (below) the stack. There's no easy way for us to know which we have at + // runtime, so we'll just match any fault in the range right above or below the + // stack base to call that fault a stack overflow. + Some(stackaddr - guardsize..stackaddr + guardsize) + } else { + Some(stackaddr..stackaddr + guardsize) + }; } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "hurd", - target_os = "linux", - target_os = "netbsd", - target_os = "l4re" - ))] - fn current_guard() -> Option> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - + if e == 0 || cfg!(target_os = "freebsd") { // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut guardsize = 0; - // SAFETY: - // `attr` is an initialized attribute object and the pointer is valid - // for writing. - assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); - if guardsize == 0 { - if cfg!(all(target_os = "linux", target_env = "musl")) { - // musl versions before 1.1.19 always reported guard - // size obtained from pthread_attr_get_np as zero. - // Use page size as a fallback. - guardsize = PAGE_SIZE.load(Ordering::Relaxed); - } else { - panic!("there is no guard page"); - } - } - let mut stackptr = crate::ptr::null_mut::(); - let mut size = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, - 0 - ); - - let stackaddr = stackptr.addr(); - ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) - { - // glibc used to include the guard area within the stack, as noted in the BUGS - // section of `man pthread_attr_getguardsize`. This has been corrected starting - // with glibc 2.27, and in some distro backports, so the guard is now placed at the - // end (below) the stack. There's no easy way for us to know which we have at - // runtime, so we'll just match any fault in the range right above or below the - // stack base to call that fault a stack overflow. - Some(stackaddr - guardsize..stackaddr + guardsize) - } else { - Some(stackaddr..stackaddr + guardsize) - }; - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); } + ret +} diff --git a/library/std/src/sys/pal/unix/stack_overflow/mod.rs b/library/std/src/sys/pal/unix/stack_overflow/mod.rs index f9541e182156c..0a070a20d432f 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/mod.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/mod.rs @@ -57,8 +57,7 @@ mod thread_info; target_os = "illumos", ) ))] -mod imp { -} +mod imp {} // This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses // several symbols that might lead to rejections from the App Store, namely @@ -82,9 +81,7 @@ mod imp { target_os = "cygwin", )) ))] -mod imp { -} +mod imp {} #[cfg(target_os = "cygwin")] -mod imp { -} +mod imp {} From 6387f4b9bcde5a76f05411c9b441396f2458690c Mon Sep 17 00:00:00 2001 From: joboet Date: Wed, 23 Sep 2026 13:56:19 +0200 Subject: [PATCH 09/18] std: split stack overflow module --- .../src/sys/pal/unix/stack_overflow/mod.rs | 100 +++++++----------- 1 file changed, 40 insertions(+), 60 deletions(-) diff --git a/library/std/src/sys/pal/unix/stack_overflow/mod.rs b/library/std/src/sys/pal/unix/stack_overflow/mod.rs index 0a070a20d432f..d60fe4b5bbec8 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/mod.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/mod.rs @@ -1,6 +1,46 @@ #![cfg_attr(test, allow(dead_code))] #![forbid(unsafe_op_in_unsafe_fn)] +cfg_select! { + // This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses + // several symbols that might lead to rejections from the App Store, namely + // `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. + // + // This might be overly cautious, though it is also what Swift does (and they + // usually have fewer qualms about forwards compatibility, since the runtime + // is shipped with the OS): + // + // + // miri doesn't model signals nor stack overflows and this code has some + // synchronization properties that we don't want to expose to user code, + // hence we disable it on miri. + all( + not(miri), + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ) + ) => { + mod handler_signal; + mod thread_info; + use handler_signal as imp; + } + target_os = "cygwin" => { + mod handler_cygwin; + use handler_cygwin as imp; + } + _ => { + mod handler_none; + use handler_none as imp; + } +} + pub use self::imp::init; use self::imp::{drop_handler, make_handler}; @@ -25,63 +65,3 @@ impl Drop for Handler { } } } - -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ), -))] -mod thread_info; - -// miri doesn't model signals nor stack overflows and this code has some -// synchronization properties that we don't want to expose to user code, -// hence we disable it on miri. -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ) -))] -mod imp {} - -// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses -// several symbols that might lead to rejections from the App Store, namely -// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. -// -// This might be overly cautious, though it is also what Swift does (and they -// usually have fewer qualms about forwards compatibility, since the runtime -// is shipped with the OS): -// -#[cfg(any( - miri, - not(any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - target_os = "cygwin", - )) -))] -mod imp {} - -#[cfg(target_os = "cygwin")] -mod imp {} From 26b5f90dd7f716ffbba11ffc7b04415b5a72b342 Mon Sep 17 00:00:00 2001 From: EFanZh Date: Mon, 28 Sep 2026 16:41:56 +0800 Subject: [PATCH 10/18] Move `alloc::rc` into `alloc::rcs` --- library/alloc/src/lib.rs | 12 ++++++++++-- library/alloc/src/{ => rcs}/rc.rs | 0 src/etc/natvis/liballoc.natvis | 8 ++++---- src/tools/miri/tests/fail/memleak_rc.stderr | 4 ++-- tests/debuginfo/rc_arc.rs | 12 ++++++------ tests/debuginfo/strings-and-strs.rs | 6 +++--- .../methods/shadowed-intrinsic-method-deref.stderr | 2 +- 7 files changed, 26 insertions(+), 18 deletions(-) rename library/alloc/src/{ => rcs}/rc.rs (100%) diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index de6830a514656..60ada2ba866bb 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -230,12 +230,22 @@ // from other crates, but since this can only appear for lang items, it doesn't seem worth fixing. #![feature(intra_doc_pointers)] +#[cfg(not(no_rc))] +#[stable(feature = "rust1", since = "1.0.0")] +pub use rcs::rc; + // Module with internal macros used by other modules (needs to be included before other modules). #[macro_use] mod macros; mod raw_vec; +/// Implementations of reference-counted pointers. +#[cfg(not(no_rc))] +mod rcs { + pub mod rc; +} + // Heaps provided for low-level allocation strategies pub mod alloc; @@ -256,8 +266,6 @@ pub mod intrinsics; #[unstable(feature = "alloc_io", issue = "154046")] pub mod io; pub mod panicking; -#[cfg(not(no_rc))] -pub mod rc; pub mod slice; pub mod str; pub mod string; diff --git a/library/alloc/src/rc.rs b/library/alloc/src/rcs/rc.rs similarity index 100% rename from library/alloc/src/rc.rs rename to library/alloc/src/rcs/rc.rs diff --git a/src/etc/natvis/liballoc.natvis b/src/etc/natvis/liballoc.natvis index b56f6f5800eee..cfcca9014e70c 100644 --- a/src/etc/natvis/liballoc.natvis +++ b/src/etc/natvis/liballoc.natvis @@ -72,7 +72,7 @@ it is necessary for them. --> - + {ptr.pointer->value} @@ -87,7 +87,7 @@ - + {{ len={ptr.pointer.length} }} ptr.pointer.length @@ -102,7 +102,7 @@ - + {ptr.pointer->value} @@ -117,7 +117,7 @@ - + {{ len={ptr.pointer.length} }} ptr.pointer.length diff --git a/src/tools/miri/tests/fail/memleak_rc.stderr b/src/tools/miri/tests/fail/memleak_rc.stderr index a77cf55b0c317..26156050ae5d3 100644 --- a/src/tools/miri/tests/fail/memleak_rc.stderr +++ b/src/tools/miri/tests/fail/memleak_rc.stderr @@ -1,5 +1,5 @@ error: memory leaked: ALLOC (Rust heap, SIZE, ALIGN), allocated here: - --> RUSTLIB/alloc/src/rc.rs:LL:CC + --> RUSTLIB/alloc/src/rcs/rc.rs:LL:CC | LL | Self::from_inner(Box::into_non_null(Box::new(RcInner { | _________________________________________________^ @@ -11,7 +11,7 @@ LL | | }))) | = note: stack backtrace: 0: std::rc::Rc::>>::new - at RUSTLIB/alloc/src/rc.rs:LL:CC + at RUSTLIB/alloc/src/rcs/rc.rs:LL:CC 1: main at tests/fail/memleak_rc.rs:LL:CC diff --git a/tests/debuginfo/rc_arc.rs b/tests/debuginfo/rc_arc.rs index 4399dfe54e7a5..39b5dc0d84408 100644 --- a/tests/debuginfo/rc_arc.rs +++ b/tests/debuginfo/rc_arc.rs @@ -27,12 +27,12 @@ //@ cdb-command:g //@ cdb-command:dx rc,d -//@ cdb-check:rc,d : 111 [Type: alloc::rc::Rc] +//@ cdb-check:rc,d : 111 [Type: alloc::rcs::rc::Rc] //@ cdb-check: [Reference count] : 11 [Type: core::cell::Cell] //@ cdb-check: [Weak reference count] : 2 [Type: core::cell::Cell] //@ cdb-command:dx weak_rc,d -//@ cdb-check:weak_rc,d : 111 [Type: alloc::rc::Weak] +//@ cdb-check:weak_rc,d : 111 [Type: alloc::rcs::rc::Weak] //@ cdb-check: [Reference count] : 11 [Type: core::cell::Cell] //@ cdb-check: [Weak reference count] : 2 [Type: core::cell::Cell] @@ -47,17 +47,17 @@ //@ cdb-check: [Weak reference count] : 2 [Type: core::sync::atomic::Atomic] //@ cdb-command:dx dyn_rc,d -//@ cdb-check:dyn_rc,d [Type: alloc::rc::Rc,alloc::alloc::Global>] +//@ cdb-check:dyn_rc,d [Type: alloc::rcs::rc::Rc,alloc::alloc::Global>] //@ cdb-check: [Reference count] : 31 [Type: core::cell::Cell] //@ cdb-check: [Weak reference count] : 2 [Type: core::cell::Cell] //@ cdb-command:dx dyn_rc_weak,d -//@ cdb-check:dyn_rc_weak,d [Type: alloc::rc::Weak,alloc::alloc::Global>] +//@ cdb-check:dyn_rc_weak,d [Type: alloc::rcs::rc::Weak,alloc::alloc::Global>] //@ cdb-check: [Reference count] : 31 [Type: core::cell::Cell] //@ cdb-check: [Weak reference count] : 2 [Type: core::cell::Cell] //@ cdb-command:dx slice_rc,d -//@ cdb-check:slice_rc,d : { len=3 } [Type: alloc::rc::Rc,alloc::alloc::Global>] +//@ cdb-check:slice_rc,d : { len=3 } [Type: alloc::rcs::rc::Rc,alloc::alloc::Global>] //@ cdb-check: [Length] : 3 [Type: [...]] //@ cdb-check: [Reference count] : 41 [Type: core::cell::Cell] //@ cdb-check: [Weak reference count] : 2 [Type: core::cell::Cell] @@ -66,7 +66,7 @@ //@ cdb-check: [2] : 3 [Type: u32] //@ cdb-command:dx slice_rc_weak,d -//@ cdb-check:slice_rc_weak,d : { len=3 } [Type: alloc::rc::Weak,alloc::alloc::Global>] +//@ cdb-check:slice_rc_weak,d : { len=3 } [Type: alloc::rcs::rc::Weak,alloc::alloc::Global>] //@ cdb-check: [Length] : 3 [Type: [...]] //@ cdb-check: [Reference count] : 41 [Type: core::cell::Cell] //@ cdb-check: [Weak reference count] : 2 [Type: core::cell::Cell] diff --git a/tests/debuginfo/strings-and-strs.rs b/tests/debuginfo/strings-and-strs.rs index 81e3037f1e566..56e9f6a5b9773 100644 --- a/tests/debuginfo/strings-and-strs.rs +++ b/tests/debuginfo/strings-and-strs.rs @@ -20,13 +20,13 @@ //@ gdb-check:$4 = ("Hello", "World") //@ gdb-command:print str_in_rc -//@ gdb-check:$5 = alloc::rc::Rc<&str, alloc::alloc::Global> {ptr: core::ptr::non_null::NonNull> {pointer: 0x[...]}, phantom: core::marker::PhantomData>, alloc: alloc::alloc::Global} +//@ gdb-check:$5 = alloc::rcs::rc::Rc<&str, alloc::alloc::Global> {ptr: core::ptr::non_null::NonNull> {pointer: 0x[...]}, phantom: core::marker::PhantomData>, alloc: alloc::alloc::Global} //@ gdb-command:print box_str //@ gdb-check:$6 = alloc::boxed::Box [87, 111, 114, 108, 100] //@ gdb-command:print rc_str -//@ gdb-check:$7 = alloc::rc::Rc {ptr: core::ptr::non_null::NonNull> {pointer: alloc::rc::RcInner {strong: core::cell::Cell {value: core::cell::UnsafeCell {value: 1}}, weak: core::cell::Cell {value: core::cell::UnsafeCell {value: 1}}, value: 0x[...]}}, phantom: core::marker::PhantomData>, alloc: alloc::alloc::Global} +//@ gdb-check:$7 = alloc::rcs::rc::Rc {ptr: core::ptr::non_null::NonNull> {pointer: alloc::rcs::rc::RcInner {strong: core::cell::Cell {value: core::cell::UnsafeCell {value: 1}}, weak: core::cell::Cell {value: core::cell::UnsafeCell {value: 1}}, value: 0x[...]}}, phantom: core::marker::PhantomData>, alloc: alloc::alloc::Global} // === LLDB TESTS ================================================================================== //@ lldb-command:run @@ -56,7 +56,7 @@ // lldb-command:v rc_str // ignore-tidy-linelength -// lldb-check:(alloc::rc::Rc) rc_str = strong=1, weak=1 { value = "World" } +// lldb-check:(alloc::rcs::rc::Rc) rc_str = strong=1, weak=1 { value = "World" } #![allow(unused_variables)] diff --git a/tests/ui/methods/shadowed-intrinsic-method-deref.stderr b/tests/ui/methods/shadowed-intrinsic-method-deref.stderr index 4e0c5dfc55f09..d4dccd6dc5fdb 100644 --- a/tests/ui/methods/shadowed-intrinsic-method-deref.stderr +++ b/tests/ui/methods/shadowed-intrinsic-method-deref.stderr @@ -6,7 +6,7 @@ LL | let sb : &S = &s.borrow(); | help: the trait `Borrow` is not implemented for `Rc>` but trait `Borrow>` is implemented for it - --> $SRC_DIR/alloc/src/rc.rs:LL:COL + --> $SRC_DIR/alloc/src/rcs/rc.rs:LL:COL = help: for that trait implementation, expected `RefCell`, found `S` = note: there's an inherent method on `RefCell` of the same name, which can be auto-dereferenced from `&RefCell` help: to access the inherent method on `RefCell`, use the fully-qualified path From f74f6ea68cb07dc75af48fffc1efc41b80c411d1 Mon Sep 17 00:00:00 2001 From: EFanZh Date: Mon, 28 Sep 2026 16:41:59 +0800 Subject: [PATCH 11/18] Rename `library/alloc/src/sync.rs` to `library/alloc/src/rcs/arc.rs` temporarily to keep file history --- library/alloc/src/{sync.rs => rcs/arc.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename library/alloc/src/{sync.rs => rcs/arc.rs} (100%) diff --git a/library/alloc/src/sync.rs b/library/alloc/src/rcs/arc.rs similarity index 100% rename from library/alloc/src/sync.rs rename to library/alloc/src/rcs/arc.rs From cba32372c7b057d58d8d9f4d3fd17848d83d2b93 Mon Sep 17 00:00:00 2001 From: EFanZh Date: Mon, 28 Sep 2026 16:41:59 +0800 Subject: [PATCH 12/18] Re-export `alloc::rcs::arc::*` in `alloc::sync` --- library/alloc/src/lib.rs | 3 +++ library/alloc/src/rcs/arc.rs | 10 ---------- library/alloc/src/sync.rs | 14 ++++++++++++++ src/etc/natvis/liballoc.natvis | 8 ++++---- tests/codegen-llvm/debuginfo-cyclic-structure.rs | 2 +- tests/codegen-llvm/issues/issue-111603.rs | 8 ++++---- tests/debuginfo/rc_arc.rs | 12 ++++++------ tests/debuginfo/thread.rs | 2 +- 8 files changed, 33 insertions(+), 26 deletions(-) create mode 100644 library/alloc/src/sync.rs diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 60ada2ba866bb..b1aa1a9a4f525 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -244,6 +244,9 @@ mod raw_vec; #[cfg(not(no_rc))] mod rcs { pub mod rc; + + #[cfg(all(not(no_sync), target_has_atomic = "ptr"))] + pub(crate) mod arc; } // Heaps provided for low-level allocation strategies diff --git a/library/alloc/src/rcs/arc.rs b/library/alloc/src/rcs/arc.rs index e412cee62d8f6..e61ea5193eccb 100644 --- a/library/alloc/src/rcs/arc.rs +++ b/library/alloc/src/rcs/arc.rs @@ -1,13 +1,3 @@ -#![stable(feature = "rust1", since = "1.0.0")] - -//! Thread-safe reference-counting pointers. -//! -//! See the [`Arc`][Arc] documentation for more details. -//! -//! **Note**: This module is only available on platforms that support atomic -//! loads and stores of pointers. This may be detected at compile time using -//! `#[cfg(target_has_atomic = "ptr")]`. - use core::any::Any; use core::cell::CloneFromCell; #[cfg(not(no_global_oom_handling))] diff --git a/library/alloc/src/sync.rs b/library/alloc/src/sync.rs new file mode 100644 index 0000000000000..afb1848d21a24 --- /dev/null +++ b/library/alloc/src/sync.rs @@ -0,0 +1,14 @@ +#![stable(feature = "rust1", since = "1.0.0")] + +//! Thread-safe reference-counting pointers. +//! +//! See the [`Arc`][Arc] documentation for more details. +//! +//! **Note**: This module is only available on platforms that support atomic +//! loads and stores of pointers. This may be detected at compile time using +//! `#[cfg(target_has_atomic = "ptr")]`. + +#[unstable(feature = "unique_rc_arc", issue = "112566")] +pub use crate::rcs::arc::UniqueArc; +#[stable(feature = "rust1", since = "1.0.0")] +pub use crate::rcs::arc::{Arc, Weak}; diff --git a/src/etc/natvis/liballoc.natvis b/src/etc/natvis/liballoc.natvis index cfcca9014e70c..c8e143b49911b 100644 --- a/src/etc/natvis/liballoc.natvis +++ b/src/etc/natvis/liballoc.natvis @@ -131,7 +131,7 @@ - + {ptr.pointer->data} @@ -146,7 +146,7 @@ - + {{ len={ptr.pointer.length} }} ptr.pointer.length @@ -160,7 +160,7 @@ - + {ptr.pointer->data} @@ -175,7 +175,7 @@ - + {{ len={ptr.pointer.length} }} ptr.pointer.length diff --git a/tests/codegen-llvm/debuginfo-cyclic-structure.rs b/tests/codegen-llvm/debuginfo-cyclic-structure.rs index b8cc544774158..dc7193dd08ed2 100644 --- a/tests/codegen-llvm/debuginfo-cyclic-structure.rs +++ b/tests/codegen-llvm/debuginfo-cyclic-structure.rs @@ -3,7 +3,7 @@ // Check that debug information exists for structures containing loops (cyclic references). // Previously it may incorrectly prune member information during recursive type inference check. -// CHECK: !DICompositeType(tag: DW_TAG_structure_type, name: "Arc Arc<[u64]> { // CHECK-LABEL: @new_uninit #[no_mangle] pub fn new_uninit(x: u64) -> Arc<[u64; 1000]> { - // CHECK: call alloc::sync::arcinner_layout_for_value_layout - // CHECK-NOT: call alloc::sync::arcinner_layout_for_value_layout + // CHECK: call alloc::rcs::arc::arcinner_layout_for_value_layout + // CHECK-NOT: call alloc::rcs::arc::arcinner_layout_for_value_layout let mut arc = Arc::new_uninit(); unsafe { Arc::get_mut_unchecked(&mut arc) }.write([x; 1000]); unsafe { arc.assume_init() } @@ -30,8 +30,8 @@ pub fn new_uninit(x: u64) -> Arc<[u64; 1000]> { // CHECK-LABEL: @new_uninit_slice #[no_mangle] pub fn new_uninit_slice(x: u64) -> Arc<[u64]> { - // CHECK: call alloc::sync::arcinner_layout_for_value_layout - // CHECK-NOT: call alloc::sync::arcinner_layout_for_value_layout + // CHECK: call alloc::rcs::arc::arcinner_layout_for_value_layout + // CHECK-NOT: call alloc::rcs::arc::arcinner_layout_for_value_layout let mut arc = Arc::new_uninit_slice(1000); for elem in unsafe { Arc::get_mut_unchecked(&mut arc) } { elem.write(x); diff --git a/tests/debuginfo/rc_arc.rs b/tests/debuginfo/rc_arc.rs index 39b5dc0d84408..adeb96dba1b80 100644 --- a/tests/debuginfo/rc_arc.rs +++ b/tests/debuginfo/rc_arc.rs @@ -37,12 +37,12 @@ //@ cdb-check: [Weak reference count] : 2 [Type: core::cell::Cell] //@ cdb-command:dx arc,d -//@ cdb-check:arc,d : 222 [Type: alloc::sync::Arc] +//@ cdb-check:arc,d : 222 [Type: alloc::rcs::arc::Arc] //@ cdb-check: [Reference count] : 21 [Type: core::sync::atomic::Atomic] //@ cdb-check: [Weak reference count] : 2 [Type: core::sync::atomic::Atomic] //@ cdb-command:dx weak_arc,d -//@ cdb-check:weak_arc,d : 222 [Type: alloc::sync::Weak] +//@ cdb-check:weak_arc,d : 222 [Type: alloc::rcs::arc::Weak] //@ cdb-check: [Reference count] : 21 [Type: core::sync::atomic::Atomic] //@ cdb-check: [Weak reference count] : 2 [Type: core::sync::atomic::Atomic] @@ -75,17 +75,17 @@ //@ cdb-check: [2] : 3 [Type: u32] //@ cdb-command:dx dyn_arc,d -//@ cdb-check:dyn_arc,d [Type: alloc::sync::Arc,alloc::alloc::Global>] +//@ cdb-check:dyn_arc,d [Type: alloc::rcs::arc::Arc,alloc::alloc::Global>] //@ cdb-check: [Reference count] : 51 [Type: core::sync::atomic::Atomic] //@ cdb-check: [Weak reference count] : 2 [Type: core::sync::atomic::Atomic] //@ cdb-command:dx dyn_arc_weak,d -//@ cdb-check:dyn_arc_weak,d [Type: alloc::sync::Weak,alloc::alloc::Global>] +//@ cdb-check:dyn_arc_weak,d [Type: alloc::rcs::arc::Weak,alloc::alloc::Global>] //@ cdb-check: [Reference count] : 51 [Type: core::sync::atomic::Atomic] //@ cdb-check: [Weak reference count] : 2 [Type: core::sync::atomic::Atomic] //@ cdb-command:dx slice_arc,d -//@ cdb-check:slice_arc,d : { len=3 } [Type: alloc::sync::Arc,alloc::alloc::Global>] +//@ cdb-check:slice_arc,d : { len=3 } [Type: alloc::rcs::arc::Arc,alloc::alloc::Global>] //@ cdb-check: [Length] : 3 [Type: [...]] //@ cdb-check: [Reference count] : 61 [Type: core::sync::atomic::Atomic] //@ cdb-check: [Weak reference count] : 2 [Type: core::sync::atomic::Atomic] @@ -94,7 +94,7 @@ //@ cdb-check: [2] : 6 [Type: u32] //@ cdb-command:dx slice_arc_weak,d -//@ cdb-check:slice_arc_weak,d : { len=3 } [Type: alloc::sync::Weak,alloc::alloc::Global>] +//@ cdb-check:slice_arc_weak,d : { len=3 } [Type: alloc::rcs::arc::Weak,alloc::alloc::Global>] //@ cdb-check: [Length] : 3 [Type: [...]] //@ cdb-check: [Reference count] : 61 [Type: core::sync::atomic::Atomic] //@ cdb-check: [Weak reference count] : 2 [Type: core::sync::atomic::Atomic] diff --git a/tests/debuginfo/thread.rs b/tests/debuginfo/thread.rs index d78dad406ac6e..ee33f921cc033 100644 --- a/tests/debuginfo/thread.rs +++ b/tests/debuginfo/thread.rs @@ -14,7 +14,7 @@ // //@ cdb-command:dx t,d //@ cdb-check:t,d : [...] [Type: std::thread::thread::Thread *] -//@ cdb-check:[...] inner [...][Type: core::pin::Pin >] +//@ cdb-check:[...] inner [...][Type: core::pin::Pin >] use std::thread; From 2e58a77da46625e9233cfac7b5cc758c4db29a18 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 16 Sep 2026 20:34:55 +0200 Subject: [PATCH 13/18] Fix intra doc link resolution when a doc comment is composed of both inner and outer doc comment --- compiler/rustc_resolve/src/late.rs | 34 +++++++++--- compiler/rustc_resolve/src/rustdoc.rs | 53 +++++++++++-------- compiler/rustc_resolve/src/rustdoc/tests.rs | 3 ++ src/librustdoc/clean/types.rs | 12 +++-- src/librustdoc/clean/types/tests.rs | 1 + .../passes/collect_intra_doc_links.rs | 48 ++++++++++++----- .../passes/lint/redundant_explicit_links.rs | 9 ++-- .../intra-doc/mixed-inner-outer.rs | 22 ++++++++ 8 files changed, 132 insertions(+), 50 deletions(-) create mode 100644 tests/rustdoc-html/intra-doc/mixed-inner-outer.rs diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index e1787eadf5fb9..4050ec8eee5d5 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -12,6 +12,7 @@ use std::debug_assert_matches; use std::mem::{replace, swap, take}; use std::ops::{ControlFlow, Range}; +use rustc_ast::attr::AttributeExt; use rustc_ast::visit::{ AssocCtxt, BoundKind, FnCtxt, FnKind, Visitor, try_visit, visit_opt, walk_list, }; @@ -2860,10 +2861,22 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } fn resolve_item(&mut self, item: &'ast Item) { - let mod_inner_docs = - matches!(item.kind, ItemKind::Mod(..)) && rustdoc::inner_docs(&item.attrs); - if !mod_inner_docs && !matches!(item.kind, ItemKind::Impl(..) | ItemKind::Use(..)) { - self.resolve_doc_links(&item.attrs, MaybeExported::Ok(item.id)); + match item.kind { + ItemKind::Mod(..) => { + // We only handle outer doc comments for modules here. + let attrs = if let Some(pos) = item.attrs.iter().position(|a| { + a.doc_resolution_scope().is_some_and(|style| style == AttrStyle::Inner) + }) { + &item.attrs[..pos] + } else { + &item.attrs + }; + self.resolve_doc_links(attrs, MaybeExported::Ok(item.id)); + } + ItemKind::Impl(..) | ItemKind::Use(..) => {} + _ => { + self.resolve_doc_links(&item.attrs, MaybeExported::Ok(item.id)); + } } debug!("(resolving item) resolving {:?} ({:?})", item.kind.ident(), item.kind); @@ -2957,9 +2970,16 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { let orig_module = replace(&mut self.parent_scope.module, module); self.with_rib(ValueNS, RibKind::Module(module.expect_local()), |this| { this.with_rib(TypeNS, RibKind::Module(module.expect_local()), |this| { - if mod_inner_docs { - this.resolve_doc_links(&item.attrs, MaybeExported::Ok(item.id)); - } + // Outer doc comments were already handled above, now we handle + // inner doc comments. + let attrs = if let Some(pos) = item.attrs.iter().position(|a| { + a.doc_resolution_scope().is_some_and(|style| style == AttrStyle::Inner) + }) { + &item.attrs[pos..] + } else { + &[] + }; + this.resolve_doc_links(attrs, MaybeExported::Ok(item.id)); let old_macro_rules = this.parent_scope.macro_rules; visit::walk_item(this, item); // Maintain macro_rules scopes in the same way as during early resolution diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index ddf5600f76659..ac463297ad84d 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -9,9 +9,9 @@ use pulldown_cmark::{ }; use rustc_ast as ast; use rustc_ast::attr::AttributeExt; -use rustc_ast::join_path_syms; use rustc_ast::token::DocFragmentKind; use rustc_ast::util::comments::beautify_doc_string; +use rustc_ast::{AttrStyle, join_path_syms}; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::unord::UnordSet; use rustc_middle::ty::TyCtxt; @@ -48,6 +48,7 @@ pub struct DocFragment { /// Because we tamper with the spans context, this information cannot be correctly retrieved /// later on. So instead, we compute it and store it here. pub from_expansion: bool, + pub style: AttrStyle, } #[derive(Clone, Copy, Debug)] @@ -215,8 +216,15 @@ pub fn attrs_to_doc_fragments<'a, A: AttributeExt + Clone + 'a>( (value_span.with_ctxt(attr_span.ctxt()), value_span.from_expansion()) } }; - let fragment = - DocFragment { span, doc, kind: fragment_kind, item_id, indent: 0, from_expansion }; + let fragment = DocFragment { + span, + doc, + kind: fragment_kind, + item_id, + indent: 0, + from_expansion, + style: attr.doc_resolution_scope().unwrap(), + }; doc_fragments.push(fragment); } else if !doc_only { other_attrs.push(attr.clone()); @@ -231,6 +239,14 @@ pub fn attrs_to_doc_fragments<'a, A: AttributeExt + Clone + 'a>( (doc_fragments, other_attrs) } +/// Represents a doc comment, split in two parts, outer and inner attributes. It's needed for +/// the intra-doc link resolution context. +#[derive(Default)] +pub struct DocStrings { + pub outer: String, + pub inner: String, +} + /// Return the doc-comments on this item, grouped by the module they came from. /// The module can be different if this is a re-export with added documentation. /// @@ -238,11 +254,16 @@ pub fn attrs_to_doc_fragments<'a, A: AttributeExt + Clone + 'a>( /// early and late doc link resolution regardless of their position. pub fn prepare_to_doc_link_resolution( doc_fragments: &[DocFragment], -) -> FxIndexMap, String> { - let mut res = FxIndexMap::default(); +) -> FxIndexMap, DocStrings> { + let mut res: FxIndexMap, DocStrings> = FxIndexMap::default(); for fragment in doc_fragments { - let out_str = res.entry(fragment.item_id).or_default(); - add_doc_fragment(out_str, fragment); + let out_strs = res.entry(fragment.item_id).or_default(); + let out = if fragment.style == AttrStyle::Inner { + &mut out_strs.inner + } else { + &mut out_strs.outer + }; + add_doc_fragment(out, fragment); } res } @@ -351,19 +372,6 @@ pub fn strip_generics_from_path(path_str: &str) -> Result, MalformedGen } } -/// Returns whether the first doc-comment is an inner attribute. -/// -/// If there are no doc-comments, return true. -/// FIXME(#78591): Support both inner and outer attributes on the same item. -pub fn inner_docs(attrs: &[impl AttributeExt]) -> bool { - for attr in attrs { - if let Some(attr_style) = attr.doc_resolution_scope() { - return attr_style == ast::AttrStyle::Inner; - } - } - true -} - /// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]` or `#[doc(attribute)]`. pub fn has_primitive_or_keyword_or_attribute_docs(attrs: &[impl AttributeExt]) -> bool { for attr in attrs { @@ -413,7 +421,10 @@ pub(crate) fn attrs_to_preprocessed_links(attrs: &[ast::Attribute]) -> Vec) -> bool { + use rustc_ast::attr::AttributeExt; + self.item_id .as_def_id() .map(|did| { - inner_docs( - #[allow(deprecated)] - tcx.get_all_attrs(did), - ) + #[allow(deprecated)] + tcx.get_all_attrs(did).iter().any(|attr| { + attr.doc_resolution_scope().is_some_and(|style| style == ast::AttrStyle::Inner) + }) }) .unwrap_or(false) } diff --git a/src/librustdoc/clean/types/tests.rs b/src/librustdoc/clean/types/tests.rs index a0fc623c20c48..29a8fd5e2678e 100644 --- a/src/librustdoc/clean/types/tests.rs +++ b/src/librustdoc/clean/types/tests.rs @@ -12,6 +12,7 @@ fn create_doc_fragment(s: &str) -> Vec { kind: DocFragmentKind::Sugared(CommentKind::Line), indent: 0, from_expansion: false, + style: rustc_ast::AttrStyle::Outer, }] } diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index d7815e64178a7..ac7cf2a42bd3b 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -21,8 +21,8 @@ use rustc_middle::ty; use rustc_middle::ty::{Ty, TyCtxt}; use rustc_resolve::rustdoc::pulldown_cmark::LinkType; use rustc_resolve::rustdoc::{ - MalformedGenerics, has_primitive_or_keyword_or_attribute_docs, prepare_to_doc_link_resolution, - source_span_for_markdown_range, strip_generics_from_path, + DocStrings, MalformedGenerics, has_primitive_or_keyword_or_attribute_docs, + prepare_to_doc_link_resolution, source_span_for_markdown_range, strip_generics_from_path, }; use rustc_span::def_id::ModId; use rustc_span::edit_distance::find_best_match_for_name; @@ -1091,14 +1091,7 @@ impl LinkCollector<'_, '_> { return; } - let mut try_insert_links = |item_id, doc: &str| { - if should_skip_link_resolution(item_id) { - return; - } - let module_id = match tcx.def_kind(item_id) { - DefKind::Mod if item.inner_docs(tcx) => ModId::new_unchecked(item_id), - _ => find_nearest_parent_module(tcx, item_id).unwrap(), - }; + let mut try_insert_links_inner = |item_id, module_id, doc: &str| { for md_link in preprocessed_markdown_links(&doc) { let link = self.resolve_link(&doc, item, item_id, module_id, &md_link); if let Some(link) = link { @@ -1112,16 +1105,45 @@ impl LinkCollector<'_, '_> { } }; + let mut try_insert_links = |item_id, docs: &DocStrings| { + if should_skip_link_resolution(item_id) { + return; + } + match tcx.def_kind(item_id) { + DefKind::Mod => { + if !docs.outer.is_empty() { + try_insert_links_inner( + item_id, + find_nearest_parent_module(tcx, item_id).unwrap(), + &docs.outer, + ); + } + if !docs.inner.is_empty() { + try_insert_links_inner(item_id, ModId::new_unchecked(item_id), &docs.inner); + } + } + _ => { + // It's the same handling for non-module items. + let module_id = find_nearest_parent_module(tcx, item_id).unwrap(); + try_insert_links_inner( + item_id, + module_id, + &format!("{}{}", docs.outer, docs.inner), + ); + } + } + }; + // We want to resolve in the lexical scope of the documentation. // In the presence of re-exports, this is not the same as the module of the item. // Rather than merging all documentation into one, resolve it one attribute at a time // so we know which module it came from. for (item_id, doc) in prepare_to_doc_link_resolution(&item.attrs.doc_strings) { - if !may_have_doc_links(&doc) { + if !may_have_doc_links(&doc.inner) && !may_have_doc_links(&doc.outer) { continue; } - debug!("combined_docs={doc}"); + debug!("combined_docs=outer: {} inner: {}", doc.outer, doc.inner); // NOTE: if there are links that start in one crate and end in another, this will not resolve them. // This is a degenerate case and it's not supported by rustdoc. let item_id = item_id.unwrap_or_else(|| item.item_id.expect_def_id()); @@ -1163,7 +1185,7 @@ impl LinkCollector<'_, '_> { } else { item.item_id.expect_def_id() }; - try_insert_links(item_id, note) + try_insert_links(item_id, &DocStrings { outer: note.to_owned(), inner: String::new() }); } } diff --git a/src/librustdoc/passes/lint/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs index 902054c917699..e02fbae3e0568 100644 --- a/src/librustdoc/passes/lint/redundant_explicit_links.rs +++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs @@ -30,10 +30,11 @@ struct LinkData { pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId) { let hunks = prepare_to_doc_link_resolution(&item.attrs.doc_strings); for (item_id, doc) in hunks { - if let Some(item_id) = item_id.or(item.def_id()) - && !doc.is_empty() - { - check_redundant_explicit_link_for_did(cx, item, item_id, hir_id, &doc); + if let Some(item_id) = item_id.or(item.def_id()) { + if !doc.outer.is_empty() || !doc.inner.is_empty() { + let doc = format!("{}{}", doc.outer, doc.inner); + check_redundant_explicit_link_for_did(cx, item, item_id, hir_id, &doc); + } } } } diff --git a/tests/rustdoc-html/intra-doc/mixed-inner-outer.rs b/tests/rustdoc-html/intra-doc/mixed-inner-outer.rs new file mode 100644 index 0000000000000..5a0f92522662d --- /dev/null +++ b/tests/rustdoc-html/intra-doc/mixed-inner-outer.rs @@ -0,0 +1,22 @@ +// This test ensures that when a documentation is composed of both inner and outer +// doc comments, the intra-doc link resolution still works as expected. +// Regression test for . +// Regression test for . +// Regression test for . + +#![crate_name = "foo"] +#![deny(rustdoc::broken_intra_doc_links)] + +//@ has foo/demo/index.html +//@ has - '//a[@href="../struct.Foo.html"]' 'Foo' +//@ has - '//a[@href="struct.DemoStruct.html"]' 'DemoStruct' + +/// Outer doc-comment [`Foo`]. +pub mod demo { + //! + //! Inner doc-comment with link: [`DemoStruct`] + + pub struct DemoStruct; +} + +pub struct Foo; From ceee44bc9df314e1b522e6e479764b96ad5db82d Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Mon, 28 Sep 2026 16:01:46 +0200 Subject: [PATCH 14/18] Force the correct type variable to never for method resolution on an adjusted never type --- compiler/rustc_hir_typeck/src/method/probe.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index 178585984cf27..aee246a61fb1e 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -524,8 +524,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span, MethodCallOnDivergingInferenceVariable, ); - let root_ty = Ty::new_var(self.tcx, ty_id); - self.demand_eqtype(span, root_ty, self.tcx.types.never); + self.demand_eqtype(span, ty, self.tcx.types.never); } else { let guar = match *ty.kind() { _ if let Some(guar) = self.tainted_by_errors() => guar, From f3b65028f7a6f4cf250929317375d06437b445a2 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Mon, 28 Sep 2026 16:02:04 +0200 Subject: [PATCH 15/18] Run the `method-on-never` type on the next trait solver --- ....stderr => method-on-never.current.stderr} | 20 ++-- .../basic/method-on-never.next.stderr | 113 ++++++++++++++++++ tests/ui/never_type/basic/method-on-never.rs | 2 + 3 files changed, 125 insertions(+), 10 deletions(-) rename tests/ui/never_type/basic/{method-on-never.stderr => method-on-never.current.stderr} (93%) create mode 100644 tests/ui/never_type/basic/method-on-never.next.stderr diff --git a/tests/ui/never_type/basic/method-on-never.stderr b/tests/ui/never_type/basic/method-on-never.current.stderr similarity index 93% rename from tests/ui/never_type/basic/method-on-never.stderr rename to tests/ui/never_type/basic/method-on-never.current.stderr index f5903616be6f4..ec89b37bc403e 100644 --- a/tests/ui/never_type/basic/method-on-never.stderr +++ b/tests/ui/never_type/basic/method-on-never.current.stderr @@ -1,5 +1,5 @@ warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:42:7 + --> $DIR/method-on-never.rs:44:7 | LL | x.method(); | ^^^^^^ @@ -10,7 +10,7 @@ LL | x.method(); = note: `#[warn(method_call_on_diverging_infer_var)]` (part of `#[warn(future_incompatible)]`) on by default warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:46:17 + --> $DIR/method-on-never.rs:48:17 | LL | { loop {} }.method(); | ^^^^^^ @@ -20,7 +20,7 @@ LL | { loop {} }.method(); = note: for more information, see issue #156047 warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:51:16 + --> $DIR/method-on-never.rs:53:16 | LL | y => y.method(), | ^^^^^^ @@ -30,7 +30,7 @@ LL | y => y.method(), = note: for more information, see issue #156047 warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:57:27 + --> $DIR/method-on-never.rs:59:27 | LL | error => (&error).anyhow_kind().new(error), | ^^^^^^^^^^^ @@ -40,7 +40,7 @@ LL | error => (&error).anyhow_kind().new(error), = note: for more information, see issue #156047 warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:63:9 + --> $DIR/method-on-never.rs:65:9 | LL | res.method(); | ^^^^^^ @@ -53,7 +53,7 @@ warning: 5 warnings emitted Future incompatibility report: Future breakage diagnostic: warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:42:7 + --> $DIR/method-on-never.rs:44:7 | LL | x.method(); | ^^^^^^ @@ -65,7 +65,7 @@ LL | x.method(); Future breakage diagnostic: warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:46:17 + --> $DIR/method-on-never.rs:48:17 | LL | { loop {} }.method(); | ^^^^^^ @@ -77,7 +77,7 @@ LL | { loop {} }.method(); Future breakage diagnostic: warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:51:16 + --> $DIR/method-on-never.rs:53:16 | LL | y => y.method(), | ^^^^^^ @@ -89,7 +89,7 @@ LL | y => y.method(), Future breakage diagnostic: warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:57:27 + --> $DIR/method-on-never.rs:59:27 | LL | error => (&error).anyhow_kind().new(error), | ^^^^^^^^^^^ @@ -101,7 +101,7 @@ LL | error => (&error).anyhow_kind().new(error), Future breakage diagnostic: warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:63:9 + --> $DIR/method-on-never.rs:65:9 | LL | res.method(); | ^^^^^^ diff --git a/tests/ui/never_type/basic/method-on-never.next.stderr b/tests/ui/never_type/basic/method-on-never.next.stderr new file mode 100644 index 0000000000000..ec89b37bc403e --- /dev/null +++ b/tests/ui/never_type/basic/method-on-never.next.stderr @@ -0,0 +1,113 @@ +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:44:7 + | +LL | x.method(); + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + = note: `#[warn(method_call_on_diverging_infer_var)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:48:17 + | +LL | { loop {} }.method(); + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:53:16 + | +LL | y => y.method(), + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:59:27 + | +LL | error => (&error).anyhow_kind().new(error), + | ^^^^^^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:65:9 + | +LL | res.method(); + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + +warning: 5 warnings emitted + +Future incompatibility report: Future breakage diagnostic: +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:44:7 + | +LL | x.method(); + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + = note: `#[warn(method_call_on_diverging_infer_var)]` (part of `#[warn(future_incompatible)]`) on by default + +Future breakage diagnostic: +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:48:17 + | +LL | { loop {} }.method(); + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + = note: `#[warn(method_call_on_diverging_infer_var)]` (part of `#[warn(future_incompatible)]`) on by default + +Future breakage diagnostic: +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:53:16 + | +LL | y => y.method(), + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + = note: `#[warn(method_call_on_diverging_infer_var)]` (part of `#[warn(future_incompatible)]`) on by default + +Future breakage diagnostic: +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:59:27 + | +LL | error => (&error).anyhow_kind().new(error), + | ^^^^^^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + = note: `#[warn(method_call_on_diverging_infer_var)]` (part of `#[warn(future_incompatible)]`) on by default + +Future breakage diagnostic: +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:65:9 + | +LL | res.method(); + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + = note: `#[warn(method_call_on_diverging_infer_var)]` (part of `#[warn(future_incompatible)]`) on by default + diff --git a/tests/ui/never_type/basic/method-on-never.rs b/tests/ui/never_type/basic/method-on-never.rs index 4194884ade9ea..ac928a0e9d071 100644 --- a/tests/ui/never_type/basic/method-on-never.rs +++ b/tests/ui/never_type/basic/method-on-never.rs @@ -1,3 +1,5 @@ +//@ revisions: current next +//@[next] compile-flags: -Znext-solver //@ check-pass // Regression test for https://github.com/rust-lang/rust/issues/143349 From b6d0fb14576c72e11f0754c6f457ab0341817a8b Mon Sep 17 00:00:00 2001 From: dianne Date: Fri, 11 Sep 2026 09:39:50 -0700 Subject: [PATCH 16/18] select: prefer impl candidates over where clauses --- .../rustc_trait_selection/src/solve/select.rs | 15 ++++++++-- ...tin-impl-over-where-clause-in-const-pat.rs | 26 +++++++++++++++++ ...ser-impl-over-where-clause-in-const-pat.rs | 29 +++++++++++++++++++ 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 tests/ui/trait-bounds/prefer-builtin-impl-over-where-clause-in-const-pat.rs create mode 100644 tests/ui/trait-bounds/prefer-user-impl-over-where-clause-in-const-pat.rs diff --git a/compiler/rustc_trait_selection/src/solve/select.rs b/compiler/rustc_trait_selection/src/solve/select.rs index 53b999e4e5244..f41808df8a5b2 100644 --- a/compiler/rustc_trait_selection/src/solve/select.rs +++ b/compiler/rustc_trait_selection/src/solve/select.rs @@ -2,7 +2,7 @@ use std::ops::ControlFlow; use rustc_infer::infer::InferCtxt; use rustc_infer::traits::solve::inspect::ProbeKind; -use rustc_infer::traits::solve::{CandidateSource, Certainty, Goal}; +use rustc_infer::traits::solve::{CandidateSource, Certainty, Goal, ParamEnvSource}; use rustc_infer::traits::{ BuiltinImplSource, ImplSource, ImplSourceUserDefinedData, Obligation, ObligationCause, PolyTraitObligation, Selection, SelectionError, SelectionResult, @@ -93,8 +93,8 @@ fn candidate_should_be_dropped_in_favor_of<'tcx>( victim: &inspect::InspectCandidate<'_, 'tcx>, other: &inspect::InspectCandidate<'_, 'tcx>, ) -> bool { - // Don't winnow until `Certainty::Yes` -- we don't need to winnow until - // codegen, and only on the good path. + // Don't winnow until `Certainty::Yes` -- we don't need to winnow until constant evaluation or + // codegen. if matches!(other.result().unwrap(), Certainty::Maybe(_)) { return false; } @@ -137,6 +137,15 @@ fn candidate_should_be_dropped_in_favor_of<'tcx>( victim.goal().infcx().tcx.specializes((other_def_id, victim_def_id)) } + // Prefer impl candidates over global where clause candidates. Unless `generic_const_args` + // is enabled, we currently don't use an empty environment when resolving and evaluating + // constants to lower them to patterns. If we don't drop where clause candidates here, we + // can fail to select impl candidates (#162331). + ( + CandidateSource::ParamEnv(ParamEnvSource::Global), + CandidateSource::Impl(_) | CandidateSource::BuiltinImpl(_), + ) => true, + _ => false, } } diff --git a/tests/ui/trait-bounds/prefer-builtin-impl-over-where-clause-in-const-pat.rs b/tests/ui/trait-bounds/prefer-builtin-impl-over-where-clause-in-const-pat.rs new file mode 100644 index 0000000000000..e774bef50c123 --- /dev/null +++ b/tests/ui/trait-bounds/prefer-builtin-impl-over-where-clause-in-const-pat.rs @@ -0,0 +1,26 @@ +//! Regression test adjacent to +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +//@ check-pass + +#![feature(const_trait_impl)] +#![feature(const_clone)] + +// At the time of writing, `ZERO` is evaluated in an environment containing `g`'s `(u8,): Clone` +// clause. In the new solver, this wasn't dropped in favor of the built-in `(u8,): Clone` impl when +// resolving an instance for `<(u8,) as Clone>::clone`, which resulted in ambiguity. + +const ZERO: (u8,) = (0,).clone(); + +fn g() +where + (u8,): Clone, +{ + match (0,) { + ZERO => {} + _ => {} + } +} + +fn main() {} diff --git a/tests/ui/trait-bounds/prefer-user-impl-over-where-clause-in-const-pat.rs b/tests/ui/trait-bounds/prefer-user-impl-over-where-clause-in-const-pat.rs new file mode 100644 index 0000000000000..e2c27d4f90e11 --- /dev/null +++ b/tests/ui/trait-bounds/prefer-user-impl-over-where-clause-in-const-pat.rs @@ -0,0 +1,29 @@ +//! Regression test for +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +//@ check-pass + +// At the time of writing, when resolving an instance for `::N`, the environment +// contains `f`'s `u8: Trait` clause. The old solver dropped the where clause candidate in favor of +// the `impl Trait for u8` candidate, but the new solver didn't, which resulted in ambiguity. + +pub trait Trait { + const N: usize; +} + +impl Trait for u8 { + const N: usize = 0; +} + +pub fn f() +where + u8: Trait, +{ + match 0 { + ::N => {} + _ => {} + } +} + +fn main() {} From 67cb4ce2794995b6d1561766251e03153230aef9 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 28 Sep 2026 09:47:35 -0600 Subject: [PATCH 17/18] Add .seek_read_buf_exact() to std::os::windows::fs::FileExt --- library/std/src/fs/tests.rs | 37 +++++++++++++++++++++ library/std/src/os/windows/fs.rs | 56 ++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 250109f277dab..db9a9f3f20b88 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -1075,6 +1075,43 @@ fn test_seek_read_buf() { check!(fs::remove_file(&filename)); } +#[test] +#[cfg(windows)] +fn test_seek_read_buf_exact() { + use crate::os::windows::fs::FileExt; + + let tmpdir = tmpdir(); + let filename = tmpdir.join("file_rt_io_file_test_seek_read_buf_exact.txt"); + { + let oo = OpenOptions::new().create_new(true).write(true).read(true).clone(); + let mut file = check!(oo.open(&filename)); + check!(file.write_all(b"0123456789")); + } + { + let mut file = check!(File::open(&filename)); + let mut buf: [MaybeUninit; 5] = [MaybeUninit::uninit(); 5]; + let mut buf = BorrowedBuf::from(buf.as_mut_slice()); + + // Exact read + check!(file.seek_read_buf_exact(buf.unfilled(), 2)); + assert_eq!(buf.filled(), b"23456"); + assert_eq!(check!(file.stream_position()), 7); + + // Already full + check!(file.seek_read_buf_exact(buf.unfilled(), 3)); + assert_eq!(check!(file.stream_position()), 7); + check!(file.seek_read_buf_exact(buf.unfilled(), 10)); // No call to seek_read() + assert_eq!(buf.filled(), b"23456"); + assert_eq!(check!(file.stream_position()), 7); + + // Non-empty exact read past eof fails + let err = file.seek_read_buf_exact(buf.clear().unfilled(), 6).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::UnexpectedEof); + assert_eq!(check!(file.stream_position()), 10); + } + check!(fs::remove_file(&filename)); +} + #[test] fn file_test_read_buf() { let tmpdir = tmpdir(); diff --git a/library/std/src/os/windows/fs.rs b/library/std/src/os/windows/fs.rs index 3e6a934f318b2..bc3dae156ede4 100644 --- a/library/std/src/os/windows/fs.rs +++ b/library/std/src/os/windows/fs.rs @@ -152,6 +152,62 @@ pub trait FileExt { io::default_read_buf(|b| self.seek_read(b, offset), buf) } + /// Seeks to a given position and reads the exact number of bytes required to fill `buf`. + /// + /// This is equivalent to the [`seek_read_exact`](FileExt::seek_read_exact) method, except + /// that it is passed a [`BorrowedCursor`] rather than `&mut [u8]` to allow use with + /// uninitialized buffers. The new data will be appended to any existing contents of `buf`. + /// + /// Reading beyond the end of the file will always succeed without reading any bytes. + /// + /// # Examples + /// + #[cfg_attr(windows, doc = "```no_run")] + #[cfg_attr(not(windows), doc = "```ignore (needs windows)")] + /// #![feature(core_io_borrowed_buf)] + /// #![feature(seek_read_exact_seek_write_all)] + /// + /// use std::io; + /// use std::io::BorrowedBuf; + /// use std::fs::File; + /// use std::mem::MaybeUninit; + /// use std::os::windows::prelude::*; + /// + /// fn main() -> io::Result<()> { + /// let mut file = File::open("pi.txt")?; + /// + /// // Read some bytes starting from offset 2 + /// let mut buf: [MaybeUninit; 10] = [MaybeUninit::uninit(); 10]; + /// let mut buf = BorrowedBuf::from(buf.as_mut_slice()); + /// file.seek_read_buf_exact(buf.unfilled(), 2)?; + /// + /// assert!(buf.filled().starts_with(b"1")); + /// + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "seek_read_exact_seek_write_all", issue = "162868")] + fn seek_read_buf_exact( + &self, + mut buf: BorrowedCursor<'_, u8>, + mut offset: u64, + ) -> io::Result<()> { + while buf.capacity() > 0 { + let prev_written = buf.written(); + match self.seek_read_buf(buf.reborrow(), offset) { + Ok(()) => {} + Err(e) if e.is_interrupted() => {} + Err(e) => return Err(e), + } + let n = buf.written() - prev_written; + offset += n as u64; + if n == 0 { + return Err(io::Error::READ_EXACT_EOF); + } + } + Ok(()) + } + /// Seeks to a given position and writes a number of bytes. /// /// Returns the number of bytes written. From 5931727a13e08bdb8a7b60b65f37f283285a2d8c Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 28 Aug 2026 12:58:33 +0200 Subject: [PATCH 18/18] add `Div` and `Mul` for `Complex<{float}>` --- ...oot_tests-Disable-complex-type-tests.patch | 32 ++++++++++ library/core/src/num/complex.rs | 54 ++++++++++++++++- library/core/src/num/imp/libm.rs | 21 +++++++ library/coretests/tests/num/complex.rs | 60 +++++++++++++++++++ 4 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 compiler/rustc_codegen_cranelift/patches/0029-sysroot_tests-Disable-complex-type-tests.patch diff --git a/compiler/rustc_codegen_cranelift/patches/0029-sysroot_tests-Disable-complex-type-tests.patch b/compiler/rustc_codegen_cranelift/patches/0029-sysroot_tests-Disable-complex-type-tests.patch new file mode 100644 index 0000000000000..d27810a988492 --- /dev/null +++ b/compiler/rustc_codegen_cranelift/patches/0029-sysroot_tests-Disable-complex-type-tests.patch @@ -0,0 +1,32 @@ +From 20d296e9a8b837e586fcc8fc648d4ab9687a2a47 Mon Sep 17 00:00:00 2001 +From: Folkert de Vries +Date: Mon, 28 Sep 2026 19:14:22 +0200 +Subject: [PATCH] sysroot_tests: Disable complex type tests + +--- + coretests/tests/num/complex.rs | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/coretests/tests/num/complex.rs b/coretests/tests/num/complex.rs +index d73d04b1afc..289c0baec17 100644 +--- a/coretests/tests/num/complex.rs ++++ b/coretests/tests/num/complex.rs +@@ -75,6 +75,7 @@ fn complex_negation() { + } + + #[test] ++#[cfg_attr(target_os = "windows", ignore = "hits an ABI issue with cranelift on windows")] + fn complex_multiplication() { + #[cfg(target_has_reliable_f16)] + assert_eq!(Complex::new(1.0f16, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0)); +@@ -105,6 +106,7 @@ fn complex_multiplication() { + } + + #[test] ++#[cfg_attr(target_os = "windows", ignore = "hits an ABI issue with cranelift on windows")] + fn complex_div() { + #[cfg(target_has_reliable_f16)] + assert_eq!(Complex::new(2.0f16, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0)); +-- +2.43.0 + diff --git a/library/core/src/num/complex.rs b/library/core/src/num/complex.rs index 66126c52fadad..c6a56285cdfb1 100644 --- a/library/core/src/num/complex.rs +++ b/library/core/src/num/complex.rs @@ -1,4 +1,5 @@ -use crate::ops::{Add, Neg, Sub}; +use crate::num::imp::libm::complex::*; +use crate::ops::{Add, Div, Mul, Neg, Sub}; /// A complex number. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -91,3 +92,54 @@ impl> Sub for Complex { Complex::new(self.re - rhs, self.im) } } + +macro_rules! impl_complex_mul_div { + ($ty:ty, $mul:ident, $div:ident) => { + #[unstable(feature = "complex_numbers", issue = "154023")] + impl Mul for Complex<$ty> { + type Output = Self; + + #[inline] + fn mul(self, rhs: Self) -> Self::Output { + let Complex { re: a, im: b } = self; + let Complex { re: c, im: d } = rhs; + + let ac = a * c; + let bd = b * d; + let ad = a * d; + let bc = b * c; + + let z = Complex::new(ac - bd, ad + bc); + + // Only call the libcall when both components are NaN. + // + // The naive algorithm would return NaN + NaNi for an input like + // (1 + 0i) * (inf + infi). The libcall instead returns inf + infi. + // + // We duplicate the fast path here so that it can be inlined. We use a libcall + // for the NaN correction to reduce the size of `core`. + if z.re.is_nan() && z.im.is_nan() { + crate::hint::cold_path(); + $mul(a, b, c, d) + } else { + z + } + } + } + + #[unstable(feature = "complex_numbers", issue = "154023")] + impl Div for Complex<$ty> { + type Output = Self; + + #[inline] + fn div(self, rhs: Self) -> Self::Output { + $div(self.re, self.im, rhs.re, rhs.im) + } + } + }; +} + +impl_complex_mul_div!(f16, __rust_mulhc3, __rust_divhc3); +impl_complex_mul_div!(f32, __mulsc3, __divsc3); +impl_complex_mul_div!(f64, __muldc3, __divdc3); +impl_complex_mul_div!(f128, __rust_multc3, __rust_divtc3); diff --git a/library/core/src/num/imp/libm.rs b/library/core/src/num/imp/libm.rs index a8d6bdc0b5d7c..6ce02ad60a5df 100644 --- a/library/core/src/num/imp/libm.rs +++ b/library/core/src/num/imp/libm.rs @@ -76,6 +76,27 @@ unsafe extern "C" { pub(crate) safe fn truncf16(x: f16) -> f16; } +/// These symbols are always provided by compiler-builtins. +pub(crate) mod complex { + use crate::num::Complex; + + unsafe extern "C" { + pub(crate) safe fn __mulsc3(a: f32, b: f32, c: f32, d: f32) -> Complex; + pub(crate) safe fn __muldc3(a: f64, b: f64, c: f64, d: f64) -> Complex; + + pub(crate) safe fn __divsc3(a: f32, b: f32, c: f32, d: f32) -> Complex; + pub(crate) safe fn __divdc3(a: f64, b: f64, c: f64, d: f64) -> Complex; + } + + unsafe extern "Rust" { + pub(crate) safe fn __rust_mulhc3(a: f16, b: f16, c: f16, d: f16) -> Complex; + pub(crate) safe fn __rust_multc3(a: f128, b: f128, c: f128, d: f128) -> Complex; + + pub(crate) safe fn __rust_divhc3(a: f16, b: f16, c: f16, d: f16) -> Complex; + pub(crate) safe fn __rust_divtc3(a: f128, b: f128, c: f128, d: f128) -> Complex; + } +} + /// These symbols will be available when `std` is available, and on many no-std platforms. However, /// since this isn't a guarantee, we cannot rely on them for stable implementations. pub(crate) mod likely_available { diff --git a/library/coretests/tests/num/complex.rs b/library/coretests/tests/num/complex.rs index c22c5b9575b3d..d73d04b1afc98 100644 --- a/library/coretests/tests/num/complex.rs +++ b/library/coretests/tests/num/complex.rs @@ -73,3 +73,63 @@ fn complex_negation() { assert_eq!(-Complex::new(1.0, -2.0), Complex::new(-1.0, 2.0)); assert_eq!(-Complex::new(1.0, f32::INFINITY), Complex::new(-1.0, f32::NEG_INFINITY),); } + +#[test] +fn complex_multiplication() { + #[cfg(target_has_reliable_f16)] + assert_eq!(Complex::new(1.0f16, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0)); + assert_eq!(Complex::new(1.0f32, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0)); + assert_eq!(Complex::new(1.0f64, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0)); + #[cfg(target_has_reliable_f128)] + assert_eq!(Complex::new(1.0f128, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0)); + + // The naive algorithm would return NaN + NaNi for these inputs, but the libcall handles it. + #[cfg(target_has_reliable_f16)] + assert_eq!( + Complex::new(1.0, 0.0) * Complex::new(f16::INFINITY, f16::INFINITY), + Complex::new(f16::INFINITY, f16::INFINITY) + ); + assert_eq!( + Complex::new(1.0, 0.0) * Complex::new(f32::INFINITY, f32::INFINITY), + Complex::new(f32::INFINITY, f32::INFINITY) + ); + assert_eq!( + Complex::new(1.0, 0.0) * Complex::new(f64::INFINITY, f64::INFINITY), + Complex::new(f64::INFINITY, f64::INFINITY) + ); + #[cfg(target_has_reliable_f128)] + assert_eq!( + Complex::new(1.0, 0.0) * Complex::new(f128::INFINITY, f128::INFINITY), + Complex::new(f128::INFINITY, f128::INFINITY) + ); +} + +#[test] +fn complex_div() { + #[cfg(target_has_reliable_f16)] + assert_eq!(Complex::new(2.0f16, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0)); + assert_eq!(Complex::new(2.0f32, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0)); + assert_eq!(Complex::new(2.0f64, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0)); + #[cfg(target_has_reliable_f128)] + assert_eq!(Complex::new(2.0f128, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0)); + + // The naive algorithm would return NaN + NaNi for these inputs, but the libcall handles it. + #[cfg(target_has_reliable_f16)] + assert_eq!( + Complex::new(f16::INFINITY, 0.0) / Complex::new(1.0, 1.0), + Complex::new(f16::INFINITY, f16::NEG_INFINITY) + ); + assert_eq!( + Complex::new(f32::INFINITY, 0.0) / Complex::new(1.0, 1.0), + Complex::new(f32::INFINITY, f32::NEG_INFINITY) + ); + assert_eq!( + Complex::new(f64::INFINITY, 0.0) / Complex::new(1.0, 1.0), + Complex::new(f64::INFINITY, f64::NEG_INFINITY) + ); + #[cfg(target_has_reliable_f128)] + assert_eq!( + Complex::new(f128::INFINITY, 0.0) / Complex::new(1.0, 1.0), + Complex::new(f128::INFINITY, f128::NEG_INFINITY) + ); +}