From 73e5f54ae4efee9b9df1c430792ae38f8a7030fb Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 12 May 2026 15:30:58 +1000 Subject: [PATCH 01/10] Move `std::io::copy` to `alloc::io` Rely on specialization to allow `std` to provide optimized copy implementations. --- library/alloc/src/io/copy.rs | 79 ++++++ library/alloc/src/io/copy/generic.rs | 236 ++++++++++++++++ library/alloc/src/io/copy/specialization.rs | 75 +++++ library/alloc/src/io/mod.rs | 3 + library/std/src/io/copy.rs | 294 -------------------- library/std/src/io/mod.rs | 20 +- library/std/src/sys/io/kernel_copy/linux.rs | 103 ++++++- library/std/src/sys/io/kernel_copy/mod.rs | 19 +- library/std/src/sys/io/mod.rs | 1 - 9 files changed, 497 insertions(+), 333 deletions(-) create mode 100644 library/alloc/src/io/copy.rs create mode 100644 library/alloc/src/io/copy/generic.rs create mode 100644 library/alloc/src/io/copy/specialization.rs diff --git a/library/alloc/src/io/copy.rs b/library/alloc/src/io/copy.rs new file mode 100644 index 0000000000000..4afcf9ed828ed --- /dev/null +++ b/library/alloc/src/io/copy.rs @@ -0,0 +1,79 @@ +mod generic; +mod specialization; + +use self::generic::generic_copy; +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub use self::specialization::SpecCopy; +use self::specialization::specialized_copy; +use crate::io::{Read, Result, Write}; + +#[derive(Debug)] +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub enum CopyState { + Ended(u64), + Fallback(u64), +} + +/// Copies the entire contents of a reader into a writer. +/// +/// This function will continuously read data from `reader` and then +/// write it into `writer` in a streaming fashion until `reader` +/// returns EOF. +/// +/// On success, the total number of bytes that were copied from +/// `reader` to `writer` is returned. +/// +/// If you want to copy the contents of one file to another and you’re +/// working with filesystem paths, see the [`fs::copy`] function. +/// +// FIXME(#74481): Hard-links required to link from `alloc` to `std` +/// [`fs::copy`]: ../../std/fs/fn.copy.html +/// +/// # Errors +/// +/// This function will return an error immediately if any call to [`read`] or +/// [`write`] returns an error. All instances of [`ErrorKind::Interrupted`] are +/// handled by this function and the underlying operation is retried. +/// +/// [`read`]: Read::read +/// [`write`]: Write::write +/// [`ErrorKind::Interrupted`]: crate::io::ErrorKind::Interrupted +/// +/// # Examples +/// +/// ``` +/// use std::io; +/// +/// fn main() -> io::Result<()> { +/// let mut reader: &[u8] = b"hello"; +/// let mut writer: Vec = vec![]; +/// +/// io::copy(&mut reader, &mut writer)?; +/// +/// assert_eq!(&b"hello"[..], &writer[..]); +/// Ok(()) +/// } +/// ``` +/// +/// # Platform-specific behavior +/// +/// On Linux (including Android), this function uses `copy_file_range(2)`, +/// `sendfile(2)` or `splice(2)` syscalls to move data directly between file +/// descriptors if possible. +/// +/// Note that platform-specific behavior may change in the future. +#[stable(feature = "rust1", since = "1.0.0")] +pub fn copy(reader: &mut R, writer: &mut W) -> Result +where + R: Read, + W: Write, +{ + match specialized_copy(reader, writer)? { + CopyState::Ended(copied) => Ok(copied), + CopyState::Fallback(copied) => { + generic_copy(reader, writer).map(|additional| copied + additional) + } + } +} diff --git a/library/alloc/src/io/copy/generic.rs b/library/alloc/src/io/copy/generic.rs new file mode 100644 index 0000000000000..7dbc56e464732 --- /dev/null +++ b/library/alloc/src/io/copy/generic.rs @@ -0,0 +1,236 @@ +use core::cmp; +use core::mem::MaybeUninit; + +#[cfg(not(no_global_oom_handling))] +use crate::collections::VecDeque; +use crate::io::{BorrowedBuf, BufReader, BufWriter, DEFAULT_BUF_SIZE, Read, Result, Write}; +use crate::vec::Vec; +#[cfg_attr( + no_global_oom_handling, + expect(unused_imports, reason = "only required for VecDeque specialization") +)] +use crate::{alloc::Allocator, io::IoSlice}; + +/// The userspace read-write-loop implementation of `io::copy` that is used when +/// OS-specific specializations for copy offloading are not available or not applicable. +pub(super) fn generic_copy(reader: &mut R, writer: &mut W) -> Result +where + R: Read, + W: Write, +{ + let read_buf = BufferedReaderSpec::buffer_size(reader); + let write_buf = BufferedWriterSpec::buffer_size(writer); + + if read_buf >= DEFAULT_BUF_SIZE && read_buf >= write_buf { + return BufferedReaderSpec::copy_to(reader, writer); + } + + BufferedWriterSpec::copy_from(writer, reader) +} + +/// Specialization of the read-write loop that reuses the internal +/// buffer of a BufReader. If there's no buffer then the writer side +/// should be used instead. +trait BufferedReaderSpec { + fn buffer_size(&self) -> usize; + + fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result; +} + +impl BufferedReaderSpec for T +where + Self: Read, + T: ?Sized, +{ + #[inline] + default fn buffer_size(&self) -> usize { + 0 + } + + default fn copy_to(&mut self, _to: &mut (impl Write + ?Sized)) -> Result { + unreachable!("only called from specializations") + } +} + +impl BufferedReaderSpec for &[u8] { + fn buffer_size(&self) -> usize { + // prefer this specialization since the source "buffer" is all we'll ever need, + // even if it's small + usize::MAX + } + + fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result { + let len = self.len(); + to.write_all(self)?; + *self = &self[len..]; + Ok(len as u64) + } +} + +#[cfg(not(no_global_oom_handling))] +impl BufferedReaderSpec for VecDeque { + fn buffer_size(&self) -> usize { + // prefer this specialization since the source "buffer" is all we'll ever need, + // even if it's small + usize::MAX + } + + fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result { + let len = self.len(); + let (front, back) = self.as_slices(); + let bufs = &mut [IoSlice::new(front), IoSlice::new(back)]; + to.write_all_vectored(bufs)?; + self.clear(); + Ok(len as u64) + } +} + +impl BufferedReaderSpec for BufReader +where + Self: Read, + I: ?Sized, +{ + fn buffer_size(&self) -> usize { + self.capacity() + } + + fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result { + let mut len = 0; + + loop { + // Hack: this relies on `impl Read for BufReader` always calling fill_buf + // if the buffer is empty, even for empty slices. + // It can't be called directly here since specialization prevents us + // from adding I: Read + match self.read(&mut []) { + Ok(_) => {} + Err(e) if e.is_interrupted() => continue, + Err(e) => return Err(e), + } + let buf = self.buffer(); + if self.buffer().len() == 0 { + return Ok(len); + } + + // In case the writer side is a BufWriter then its write_all + // implements an optimization that passes through large + // buffers to the underlying writer. That code path is #[cold] + // but we're still avoiding redundant memcopies when doing + // a copy between buffered inputs and outputs. + to.write_all(buf)?; + len += buf.len() as u64; + self.discard_buffer(); + } + } +} + +/// Specialization of the read-write loop that either uses a stack buffer +/// or reuses the internal buffer of a BufWriter +trait BufferedWriterSpec: Write { + fn buffer_size(&self) -> usize; + + fn copy_from(&mut self, reader: &mut R) -> Result; +} + +impl BufferedWriterSpec for W { + #[inline] + default fn buffer_size(&self) -> usize { + 0 + } + + default fn copy_from(&mut self, reader: &mut R) -> Result { + stack_buffer_copy(reader, self) + } +} + +impl BufferedWriterSpec for BufWriter { + fn buffer_size(&self) -> usize { + self.capacity() + } + + fn copy_from(&mut self, reader: &mut R) -> Result { + if self.capacity() < DEFAULT_BUF_SIZE { + return stack_buffer_copy(reader, self); + } + + let mut len = 0; + let mut init = false; + + loop { + let buf = self.buffer_mut(); + let mut read_buf: BorrowedBuf<'_, u8> = buf.spare_capacity_mut().into(); + + if init { + // SAFETY: `init` is only true after `reader` initializes + // `read_buf`. See the comment about `flush_buf` below. + unsafe { read_buf.set_init() }; + } + + if read_buf.capacity() >= DEFAULT_BUF_SIZE { + let mut cursor = read_buf.unfilled(); + match reader.read_buf(cursor.reborrow()) { + Ok(()) => { + let bytes_read = cursor.written(); + + if bytes_read == 0 { + return Ok(len); + } + + init = read_buf.is_init(); + len += bytes_read as u64; + + // SAFETY: BorrowedBuf guarantees all of its filled bytes are init + unsafe { buf.set_len(buf.len() + bytes_read) }; + + // Read again if the buffer still has enough capacity, as BufWriter itself would do + // This will occur if the reader returns short reads + } + Err(ref e) if e.is_interrupted() => {} + Err(e) => return Err(e), + } + } else { + // SAFETY: `flush_buf` will not de-initialize any elements of + // the spare capacity so we can remember `init` across this. + self.flush_buf()?; + } + } + } +} + +impl BufferedWriterSpec for Vec { + fn buffer_size(&self) -> usize { + cmp::max(DEFAULT_BUF_SIZE, self.capacity() - self.len()) + } + + fn copy_from(&mut self, reader: &mut R) -> Result { + reader.read_to_end(self).map(|bytes| u64::try_from(bytes).expect("usize overflowed u64")) + } +} + +fn stack_buffer_copy( + reader: &mut R, + writer: &mut W, +) -> Result { + let buf: &mut [_] = &mut [MaybeUninit::uninit(); DEFAULT_BUF_SIZE]; + let mut buf: BorrowedBuf<'_, u8> = buf.into(); + + let mut len = 0; + + loop { + match reader.read_buf(buf.unfilled()) { + Ok(()) => {} + Err(e) if e.is_interrupted() => continue, + Err(e) => return Err(e), + }; + + if buf.filled().is_empty() { + break; + } + + len += buf.filled().len() as u64; + writer.write_all(buf.filled())?; + buf.clear(); + } + + Ok(len) +} diff --git a/library/alloc/src/io/copy/specialization.rs b/library/alloc/src/io/copy/specialization.rs new file mode 100644 index 0000000000000..83ad36b2b3b04 --- /dev/null +++ b/library/alloc/src/io/copy/specialization.rs @@ -0,0 +1,75 @@ +//! Provides specialization for `io::copy`. + +use super::CopyState; +use crate::io::{BufReader, Read, Result, Take, Write}; + +/// The implementation of `io::copy` that can rely on platform specific specialization +/// provided by `libstd`. +pub(super) fn specialized_copy( + reader: &mut R, + writer: &mut W, +) -> Result +where + R: Read, + W: Write, +{ + SpecCopyInner::copy((reader, writer)) +} + +trait SpecCopyInner { + fn copy(self) -> Result; +} + +impl SpecCopyInner for (&mut R, &mut W) { + default fn copy(self) -> Result { + Ok(CopyState::Fallback(0)) + } +} + +impl SpecCopyInner for (&mut R, &mut W) { + fn copy(self) -> Result { + ::copy(self.0, self.1) + } +} + +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +#[rustc_specialization_trait] +pub trait SpecCopy: Read { + /// Attempt to copy from this reader to the provided writer using a specialized + /// process. + fn copy( + _reader: &mut R, + _writer: &mut W, + ) -> Result; +} + +impl SpecCopy for &mut T +where + T: SpecCopy, +{ + fn copy( + reader: &mut R, + writer: &mut W, + ) -> Result { + ::copy(reader, writer) + } +} + +impl SpecCopy for Take { + fn copy( + reader: &mut R, + writer: &mut W, + ) -> Result { + ::copy(reader, writer) + } +} + +impl SpecCopy for BufReader { + fn copy( + reader: &mut R, + writer: &mut W, + ) -> Result { + ::copy(reader, writer) + } +} diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index ef09d13cc6247..5c043240daba8 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -2,6 +2,7 @@ mod buf_read; mod buffered; +mod copy; mod cursor; mod error; mod impls; @@ -35,12 +36,14 @@ use self::util::{bytes, lines, split, uninlined_slow_read_byte}; pub use self::{ buf_read::BufRead, buffered::{BufReader, BufWriter, IntoInnerError, LineWriter, WriterPanicked}, + copy::copy, read::{Read, read_to_string}, util::{Bytes, Lines, Split}, }; #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub use self::{ + copy::{CopyState, SpecCopy}, read::{ DEFAULT_BUF_SIZE, default_read_buf, default_read_to_end, default_read_to_string, default_read_vectored, diff --git a/library/std/src/io/copy.rs b/library/std/src/io/copy.rs index 43c6f71357e61..87c2771955a9d 100644 --- a/library/std/src/io/copy.rs +++ b/library/std/src/io/copy.rs @@ -1,296 +1,2 @@ -use super::{BorrowedBuf, BufReader, BufWriter, DEFAULT_BUF_SIZE, Read, Result, Write}; -use crate::alloc::Allocator; -use crate::cmp; -use crate::collections::VecDeque; -use crate::io::IoSlice; -use crate::mem::MaybeUninit; -use crate::sys::io::{CopyState, kernel_copy}; - #[cfg(test)] mod tests; - -/// Copies the entire contents of a reader into a writer. -/// -/// This function will continuously read data from `reader` and then -/// write it into `writer` in a streaming fashion until `reader` -/// returns EOF. -/// -/// On success, the total number of bytes that were copied from -/// `reader` to `writer` is returned. -/// -/// If you want to copy the contents of one file to another and you’re -/// working with filesystem paths, see the [`fs::copy`] function. -/// -/// [`fs::copy`]: crate::fs::copy -/// -/// # Errors -/// -/// This function will return an error immediately if any call to [`read`] or -/// [`write`] returns an error. All instances of [`ErrorKind::Interrupted`] are -/// handled by this function and the underlying operation is retried. -/// -/// [`read`]: Read::read -/// [`write`]: Write::write -/// [`ErrorKind::Interrupted`]: crate::io::ErrorKind::Interrupted -/// -/// # Examples -/// -/// ``` -/// use std::io; -/// -/// fn main() -> io::Result<()> { -/// let mut reader: &[u8] = b"hello"; -/// let mut writer: Vec = vec![]; -/// -/// io::copy(&mut reader, &mut writer)?; -/// -/// assert_eq!(&b"hello"[..], &writer[..]); -/// Ok(()) -/// } -/// ``` -/// -/// # Platform-specific behavior -/// -/// On Linux (including Android), this function uses `copy_file_range(2)`, -/// `sendfile(2)` or `splice(2)` syscalls to move data directly between file -/// descriptors if possible. -/// -/// Note that platform-specific behavior [may change in the future][changes]. -/// -/// [changes]: crate::io#platform-specific-behavior -#[stable(feature = "rust1", since = "1.0.0")] -pub fn copy(reader: &mut R, writer: &mut W) -> Result -where - R: Read, - W: Write, -{ - match kernel_copy(reader, writer)? { - CopyState::Ended(copied) => Ok(copied), - CopyState::Fallback(copied) => { - generic_copy(reader, writer).map(|additional| copied + additional) - } - } -} - -/// The userspace read-write-loop implementation of `io::copy` that is used when -/// OS-specific specializations for copy offloading are not available or not applicable. -fn generic_copy(reader: &mut R, writer: &mut W) -> Result -where - R: Read, - W: Write, -{ - let read_buf = BufferedReaderSpec::buffer_size(reader); - let write_buf = BufferedWriterSpec::buffer_size(writer); - - if read_buf >= DEFAULT_BUF_SIZE && read_buf >= write_buf { - return BufferedReaderSpec::copy_to(reader, writer); - } - - BufferedWriterSpec::copy_from(writer, reader) -} - -/// Specialization of the read-write loop that reuses the internal -/// buffer of a BufReader. If there's no buffer then the writer side -/// should be used instead. -trait BufferedReaderSpec { - fn buffer_size(&self) -> usize; - - fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result; -} - -impl BufferedReaderSpec for T -where - Self: Read, - T: ?Sized, -{ - #[inline] - default fn buffer_size(&self) -> usize { - 0 - } - - default fn copy_to(&mut self, _to: &mut (impl Write + ?Sized)) -> Result { - unreachable!("only called from specializations") - } -} - -impl BufferedReaderSpec for &[u8] { - fn buffer_size(&self) -> usize { - // prefer this specialization since the source "buffer" is all we'll ever need, - // even if it's small - usize::MAX - } - - fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result { - let len = self.len(); - to.write_all(self)?; - *self = &self[len..]; - Ok(len as u64) - } -} - -impl BufferedReaderSpec for VecDeque { - fn buffer_size(&self) -> usize { - // prefer this specialization since the source "buffer" is all we'll ever need, - // even if it's small - usize::MAX - } - - fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result { - let len = self.len(); - let (front, back) = self.as_slices(); - let bufs = &mut [IoSlice::new(front), IoSlice::new(back)]; - to.write_all_vectored(bufs)?; - self.clear(); - Ok(len as u64) - } -} - -impl BufferedReaderSpec for BufReader -where - Self: Read, - I: ?Sized, -{ - fn buffer_size(&self) -> usize { - self.capacity() - } - - fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result { - let mut len = 0; - - loop { - // Hack: this relies on `impl Read for BufReader` always calling fill_buf - // if the buffer is empty, even for empty slices. - // It can't be called directly here since specialization prevents us - // from adding I: Read - match self.read(&mut []) { - Ok(_) => {} - Err(e) if e.is_interrupted() => continue, - Err(e) => return Err(e), - } - let buf = self.buffer(); - if self.buffer().len() == 0 { - return Ok(len); - } - - // In case the writer side is a BufWriter then its write_all - // implements an optimization that passes through large - // buffers to the underlying writer. That code path is #[cold] - // but we're still avoiding redundant memcopies when doing - // a copy between buffered inputs and outputs. - to.write_all(buf)?; - len += buf.len() as u64; - self.discard_buffer(); - } - } -} - -/// Specialization of the read-write loop that either uses a stack buffer -/// or reuses the internal buffer of a BufWriter -trait BufferedWriterSpec: Write { - fn buffer_size(&self) -> usize; - - fn copy_from(&mut self, reader: &mut R) -> Result; -} - -impl BufferedWriterSpec for W { - #[inline] - default fn buffer_size(&self) -> usize { - 0 - } - - default fn copy_from(&mut self, reader: &mut R) -> Result { - stack_buffer_copy(reader, self) - } -} - -impl BufferedWriterSpec for BufWriter { - fn buffer_size(&self) -> usize { - self.capacity() - } - - fn copy_from(&mut self, reader: &mut R) -> Result { - if self.capacity() < DEFAULT_BUF_SIZE { - return stack_buffer_copy(reader, self); - } - - let mut len = 0; - let mut init = false; - - loop { - let buf = self.buffer_mut(); - let mut read_buf: BorrowedBuf<'_, u8> = buf.spare_capacity_mut().into(); - - if init { - // SAFETY: `init` is only true after `reader` initializes - // `read_buf`. See the comment about `flush_buf` below. - unsafe { read_buf.set_init() }; - } - - if read_buf.capacity() >= DEFAULT_BUF_SIZE { - let mut cursor = read_buf.unfilled(); - match reader.read_buf(cursor.reborrow()) { - Ok(()) => { - let bytes_read = cursor.written(); - - if bytes_read == 0 { - return Ok(len); - } - - init = read_buf.is_init(); - len += bytes_read as u64; - - // SAFETY: BorrowedBuf guarantees all of its filled bytes are init - unsafe { buf.set_len(buf.len() + bytes_read) }; - - // Read again if the buffer still has enough capacity, as BufWriter itself would do - // This will occur if the reader returns short reads - } - Err(ref e) if e.is_interrupted() => {} - Err(e) => return Err(e), - } - } else { - // SAFETY: `flush_buf` will not de-initialize any elements of - // the spare capacity so we can remember `init` across this. - self.flush_buf()?; - } - } - } -} - -impl BufferedWriterSpec for Vec { - fn buffer_size(&self) -> usize { - cmp::max(DEFAULT_BUF_SIZE, self.capacity() - self.len()) - } - - fn copy_from(&mut self, reader: &mut R) -> Result { - reader.read_to_end(self).map(|bytes| u64::try_from(bytes).expect("usize overflowed u64")) - } -} - -fn stack_buffer_copy( - reader: &mut R, - writer: &mut W, -) -> Result { - let buf: &mut [_] = &mut [MaybeUninit::uninit(); DEFAULT_BUF_SIZE]; - let mut buf: BorrowedBuf<'_, u8> = buf.into(); - - let mut len = 0; - - loop { - match reader.read_buf(buf.unfilled()) { - Ok(()) => {} - Err(e) if e.is_interrupted() => continue, - Err(e) => return Err(e), - }; - - if buf.filled().is_empty() { - break; - } - - len += buf.filled().len() as u64; - writer.write_all(buf.filled())?; - buf.clear(); - } - - Ok(len) -} diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 572f0fb3e4611..203572c9067b6 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -314,12 +314,13 @@ pub use alloc_crate::io::{BorrowedBuf, BorrowedCursor}; #[stable(feature = "rust1", since = "1.0.0")] pub use alloc_crate::io::{ BufRead, BufReader, BufWriter, Bytes, Chain, Cursor, Empty, Error, ErrorKind, IntoInnerError, - LineWriter, Lines, Read, Repeat, Result, Seek, SeekFrom, Sink, Split, Take, Write, empty, + LineWriter, Lines, Read, Repeat, Result, Seek, SeekFrom, Sink, Split, Take, Write, copy, empty, repeat, sink, }; #[allow(unused_imports, reason = "only used by certain platforms")] pub(crate) use alloc_crate::io::{ - DEFAULT_BUF_SIZE, default_read_buf, default_read_vectored, default_write_vectored, + CopyState, DEFAULT_BUF_SIZE, SpecCopy, default_read_buf, default_read_vectored, + default_write_vectored, }; pub(crate) use alloc_crate::io::{ IoHandle, SpecReadByte, default_read_to_end, default_read_to_string, stream_len_default, @@ -331,21 +332,20 @@ pub use alloc_crate::io::{IoSlice, IoSliceMut}; pub use self::pipe::{PipeReader, PipeWriter, pipe}; #[stable(feature = "is_terminal", since = "1.70.0")] pub use self::stdio::IsTerminal; -pub(crate) use self::stdio::attempt_print_to_stderr; #[unstable(feature = "print_internals", issue = "none")] #[doc(hidden)] pub use self::stdio::{_eprint, _print}; +#[stable(feature = "rust1", since = "1.0.0")] +pub use self::stdio::{ + Stderr, StderrLock, Stdin, StdinLock, Stdout, StdoutLock, stderr, stdin, stdout, +}; +pub(crate) use self::stdio::{attempt_print_to_stderr, cleanup}; #[unstable(feature = "internal_output_capture", issue = "none")] #[doc(no_inline, hidden)] pub use self::stdio::{set_output_capture, try_set_output_capture}; -#[stable(feature = "rust1", since = "1.0.0")] -pub use self::{ - copy::copy, - stdio::{Stderr, StderrLock, Stdin, StdinLock, Stdout, StdoutLock, stderr, stdin, stdout}, -}; mod buffered; -pub(crate) mod copy; +mod copy; mod cursor; mod error; mod impls; @@ -353,5 +353,3 @@ mod pipe; pub mod prelude; mod stdio; mod util; - -pub(crate) use stdio::cleanup; diff --git a/library/std/src/sys/io/kernel_copy/linux.rs b/library/std/src/sys/io/kernel_copy/linux.rs index 1c00d317f2a52..433fabc3f9733 100644 --- a/library/std/src/sys/io/kernel_copy/linux.rs +++ b/library/std/src/sys/io/kernel_copy/linux.rs @@ -48,12 +48,11 @@ use libc::sendfile as sendfile64; use libc::sendfile64; use libc::{EBADF, EINVAL, ENOSYS, EOPNOTSUPP, EOVERFLOW, EPERM, EXDEV}; -use super::CopyState; use crate::cmp::min; use crate::fs::{File, Metadata}; use crate::io::{ - BufRead, BufReader, BufWriter, Error, PipeReader, PipeWriter, Read, Result, StderrLock, - StdinLock, StdoutLock, Take, Write, + self, BufRead, BufReader, BufWriter, CopyState, Error, PipeReader, PipeWriter, Read, Result, + StderrLock, StdinLock, StdoutLock, Take, Write, }; use crate::mem::ManuallyDrop; use crate::net::TcpStream; @@ -70,12 +69,98 @@ use crate::sys::weak::syscall; #[cfg(test)] mod tests; -pub fn kernel_copy( - read: &mut R, - write: &mut W, -) -> Result { - let copier = Copier { read, write }; - SpecCopy::copy(copier) +#[doc(hidden)] +#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")] +impl io::SpecCopy for File { + fn copy(read: &mut R, write: &mut W) -> Result { + SpecCopy::copy(Copier { read, write }) + } +} + +#[doc(hidden)] +#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")] +impl io::SpecCopy for &File { + fn copy(read: &mut R, write: &mut W) -> Result { + SpecCopy::copy(Copier { read, write }) + } +} + +#[doc(hidden)] +#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")] +impl io::SpecCopy for TcpStream { + fn copy(read: &mut R, write: &mut W) -> Result { + SpecCopy::copy(Copier { read, write }) + } +} + +#[doc(hidden)] +#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")] +impl io::SpecCopy for &TcpStream { + fn copy(read: &mut R, write: &mut W) -> Result { + SpecCopy::copy(Copier { read, write }) + } +} + +#[doc(hidden)] +#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")] +impl io::SpecCopy for UnixStream { + fn copy(read: &mut R, write: &mut W) -> Result { + SpecCopy::copy(Copier { read, write }) + } +} + +#[doc(hidden)] +#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")] +impl io::SpecCopy for &UnixStream { + fn copy(read: &mut R, write: &mut W) -> Result { + SpecCopy::copy(Copier { read, write }) + } +} + +#[doc(hidden)] +#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")] +impl io::SpecCopy for PipeReader { + fn copy(read: &mut R, write: &mut W) -> Result { + SpecCopy::copy(Copier { read, write }) + } +} + +#[doc(hidden)] +#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")] +impl io::SpecCopy for &PipeReader { + fn copy(read: &mut R, write: &mut W) -> Result { + SpecCopy::copy(Copier { read, write }) + } +} + +#[doc(hidden)] +#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")] +impl io::SpecCopy for ChildStdout { + fn copy(read: &mut R, write: &mut W) -> Result { + SpecCopy::copy(Copier { read, write }) + } +} + +#[doc(hidden)] +#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")] +impl io::SpecCopy for ChildStderr { + fn copy(read: &mut R, write: &mut W) -> Result { + SpecCopy::copy(Copier { read, write }) + } +} + +#[doc(hidden)] +#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")] +impl io::SpecCopy for StdinLock<'_> { + fn copy(read: &mut R, write: &mut W) -> Result { + SpecCopy::copy(Copier { read, write }) + } +} + +impl io::SpecCopy for CachedFileMetadata { + fn copy(read: &mut R, write: &mut W) -> Result { + SpecCopy::copy(Copier { read, write }) + } } /// This type represents either the inferred `FileType` of a `RawFd` based on the source diff --git a/library/std/src/sys/io/kernel_copy/mod.rs b/library/std/src/sys/io/kernel_copy/mod.rs index a89279412cf7f..bb71fde631cce 100644 --- a/library/std/src/sys/io/kernel_copy/mod.rs +++ b/library/std/src/sys/io/kernel_copy/mod.rs @@ -1,23 +1,6 @@ -pub enum CopyState { - #[cfg_attr(not(any(target_os = "linux", target_os = "android")), expect(dead_code))] - Ended(u64), - Fallback(u64), -} - cfg_select! { any(target_os = "linux", target_os = "android") => { mod linux; - pub use linux::kernel_copy; - } - _ => { - use crate::io::{Result, Read, Write}; - - pub fn kernel_copy(_reader: &mut R, _writer: &mut W) -> Result - where - R: Read, - W: Write, - { - Ok(CopyState::Fallback(0)) - } } + _ => { } } diff --git a/library/std/src/sys/io/mod.rs b/library/std/src/sys/io/mod.rs index 02a180f4bc295..efca981eb813a 100644 --- a/library/std/src/sys/io/mod.rs +++ b/library/std/src/sys/io/mod.rs @@ -45,4 +45,3 @@ pub use error::errno_location; pub use error::set_errno; pub use error::{decode_error_kind, errno, error_string, is_interrupted}; pub use is_terminal::is_terminal; -pub use kernel_copy::{CopyState, kernel_copy}; From a8ef638c6932ff692513cd0ae15d695a9c0e9575 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 28 Jul 2026 13:18:34 +1000 Subject: [PATCH 02/10] Add documentation based on feedback Co-Authored-By: Clar Fon <15850505+clarfonthey@users.noreply.github.com> --- library/alloc/src/io/copy.rs | 15 ++++- library/alloc/src/io/copy/generic.rs | 72 +++++++++++++-------- library/alloc/src/io/copy/specialization.rs | 39 ++++++----- 3 files changed, 78 insertions(+), 48 deletions(-) diff --git a/library/alloc/src/io/copy.rs b/library/alloc/src/io/copy.rs index 4afcf9ed828ed..798bd2c1ec0a0 100644 --- a/library/alloc/src/io/copy.rs +++ b/library/alloc/src/io/copy.rs @@ -5,9 +5,20 @@ use self::generic::generic_copy; #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub use self::specialization::SpecCopy; -use self::specialization::specialized_copy; +use self::specialization::SpecCopyInner; use crate::io::{Read, Result, Write}; +/// Used as a part of [copy specialization](SpecCopy) to communicate how many bytes +/// were copied, and whether copying is done. +/// +/// * [`Ended(n)`](CopyState::Ended) indicates copying completed, moving a total +/// of `n` bytes. +/// * [`Fallback(n)`](CopyState::Fallback) indicates copying is _might_ not be +/// complete, and so far `n` bytes have been copied using specialization. +/// The remaining must be copied using a fallback implementation. +/// +/// If a particular `Read` and `Write` combination do not implement a specialized +/// copy routine, the specialized function will return `Fallback(0)`. #[derive(Debug)] #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] @@ -70,7 +81,7 @@ where R: Read, W: Write, { - match specialized_copy(reader, writer)? { + match SpecCopyInner::copy(reader, writer)? { CopyState::Ended(copied) => Ok(copied), CopyState::Fallback(copied) => { generic_copy(reader, writer).map(|additional| copied + additional) diff --git a/library/alloc/src/io/copy/generic.rs b/library/alloc/src/io/copy/generic.rs index 7dbc56e464732..d75f97dbed2b8 100644 --- a/library/alloc/src/io/copy/generic.rs +++ b/library/alloc/src/io/copy/generic.rs @@ -1,4 +1,3 @@ -use core::cmp; use core::mem::MaybeUninit; #[cfg(not(no_global_oom_handling))] @@ -13,26 +12,46 @@ use crate::{alloc::Allocator, io::IoSlice}; /// The userspace read-write-loop implementation of `io::copy` that is used when /// OS-specific specializations for copy offloading are not available or not applicable. +/// +/// This function is able to perform a mild amount of specialization based on +/// the size of the `reader` and `writer` buffers, if they have any. +/// +/// * If `reader`'s buffer is large enough ([`>=DEFAULT_BUF_SIZE`](DEFAULT_BUF_SIZE)), +/// _and_ it is larger than `writer`'s buffer, copying will be controlled by `R`. +/// * Otherwise, copying will be controlled by `writer`. +/// +/// Currently, `[u8]`, `Vec`, `VecDeque`, `BufReader` and `BufWriter` +/// are specialized with. pub(super) fn generic_copy(reader: &mut R, writer: &mut W) -> Result where R: Read, W: Write, { - let read_buf = BufferedReaderSpec::buffer_size(reader); - let write_buf = BufferedWriterSpec::buffer_size(writer); + let read_priority = BufferedReaderSpec::buffer_priority(reader); + let write_priority = BufferedWriterSpec::buffer_priority(writer); - if read_buf >= DEFAULT_BUF_SIZE && read_buf >= write_buf { + if read_priority >= DEFAULT_BUF_SIZE && read_priority >= write_priority { return BufferedReaderSpec::copy_to(reader, writer); } BufferedWriterSpec::copy_from(writer, reader) } -/// Specialization of the read-write loop that reuses the internal -/// buffer of a BufReader. If there's no buffer then the writer side +/// This is used by [`generic_copy`] to decide whether to use [`BufferedReaderSpec::copy_to`] +/// or [`BufferedWriterSpec::copy_from`]. +type BufferPriority = usize; + +/// Unbuffered readers and writers have the lowest priority. +const UNBUFFERED: BufferPriority = BufferPriority::MIN; + +/// Readers and writers with their entire contents have the highest priority. +const IN_MEMORY: BufferPriority = BufferPriority::MAX; + +/// Specialization of the read-write loop in [`generic_copy`] that reuses the +/// internal buffer of a [`BufReader`]. If there's no buffer then the writer side /// should be used instead. trait BufferedReaderSpec { - fn buffer_size(&self) -> usize; + fn buffer_priority(&self) -> BufferPriority; fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result; } @@ -43,8 +62,8 @@ where T: ?Sized, { #[inline] - default fn buffer_size(&self) -> usize { - 0 + default fn buffer_priority(&self) -> BufferPriority { + UNBUFFERED } default fn copy_to(&mut self, _to: &mut (impl Write + ?Sized)) -> Result { @@ -53,10 +72,8 @@ where } impl BufferedReaderSpec for &[u8] { - fn buffer_size(&self) -> usize { - // prefer this specialization since the source "buffer" is all we'll ever need, - // even if it's small - usize::MAX + fn buffer_priority(&self) -> BufferPriority { + IN_MEMORY } fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result { @@ -69,10 +86,8 @@ impl BufferedReaderSpec for &[u8] { #[cfg(not(no_global_oom_handling))] impl BufferedReaderSpec for VecDeque { - fn buffer_size(&self) -> usize { - // prefer this specialization since the source "buffer" is all we'll ever need, - // even if it's small - usize::MAX + fn buffer_priority(&self) -> BufferPriority { + IN_MEMORY } fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result { @@ -90,7 +105,7 @@ where Self: Read, I: ?Sized, { - fn buffer_size(&self) -> usize { + fn buffer_priority(&self) -> BufferPriority { self.capacity() } @@ -124,32 +139,36 @@ where } } -/// Specialization of the read-write loop that either uses a stack buffer -/// or reuses the internal buffer of a BufWriter +/// Specialization of the read-write loop in `generic_copy` that either uses a +/// stack buffer or reuses the internal buffer of a [`BufWriter`]. trait BufferedWriterSpec: Write { - fn buffer_size(&self) -> usize; + fn buffer_priority(&self) -> BufferPriority; fn copy_from(&mut self, reader: &mut R) -> Result; } impl BufferedWriterSpec for W { #[inline] - default fn buffer_size(&self) -> usize { - 0 + default fn buffer_priority(&self) -> BufferPriority { + UNBUFFERED } default fn copy_from(&mut self, reader: &mut R) -> Result { + // Unlike `BufferedReaderSpec::copy_to`, this _will_ be called as the fallback + // when both the reader and writer provide no specialization. stack_buffer_copy(reader, self) } } impl BufferedWriterSpec for BufWriter { - fn buffer_size(&self) -> usize { + fn buffer_priority(&self) -> BufferPriority { self.capacity() } fn copy_from(&mut self, reader: &mut R) -> Result { if self.capacity() < DEFAULT_BUF_SIZE { + // Since neither this buffer nor the reader's buffer are large enough, + // fall back to the unspecialized implementation. return stack_buffer_copy(reader, self); } @@ -198,8 +217,8 @@ impl BufferedWriterSpec for BufWriter { } impl BufferedWriterSpec for Vec { - fn buffer_size(&self) -> usize { - cmp::max(DEFAULT_BUF_SIZE, self.capacity() - self.len()) + fn buffer_priority(&self) -> BufferPriority { + self.capacity() - self.len() } fn copy_from(&mut self, reader: &mut R) -> Result { @@ -207,6 +226,7 @@ impl BufferedWriterSpec for Vec { } } +/// Copies from `reader` to `writer` using a stack-allocated buffer (a fixed sized array). fn stack_buffer_copy( reader: &mut R, writer: &mut W, diff --git a/library/alloc/src/io/copy/specialization.rs b/library/alloc/src/io/copy/specialization.rs index 83ad36b2b3b04..738f3af6beaae 100644 --- a/library/alloc/src/io/copy/specialization.rs +++ b/library/alloc/src/io/copy/specialization.rs @@ -3,32 +3,21 @@ use super::CopyState; use crate::io::{BufReader, Read, Result, Take, Write}; -/// The implementation of `io::copy` that can rely on platform specific specialization -/// provided by `libstd`. -pub(super) fn specialized_copy( - reader: &mut R, - writer: &mut W, -) -> Result -where - R: Read, - W: Write, -{ - SpecCopyInner::copy((reader, writer)) -} - -trait SpecCopyInner { - fn copy(self) -> Result; +pub(super) trait SpecCopyInner { + /// The implementation of `io::copy` that can rely on platform specific specialization + /// provided by `libstd`. + fn copy(&mut self, writer: &mut W) -> Result; } -impl SpecCopyInner for (&mut R, &mut W) { - default fn copy(self) -> Result { +impl SpecCopyInner for R { + default fn copy(&mut self, _writer: &mut W) -> Result { Ok(CopyState::Fallback(0)) } } -impl SpecCopyInner for (&mut R, &mut W) { - fn copy(self) -> Result { - ::copy(self.0, self.1) +impl SpecCopyInner for R { + fn copy(&mut self, writer: &mut W) -> Result { + ::copy(self, writer) } } @@ -38,6 +27,16 @@ impl SpecCopyInner for (&mut R, &mut W) { pub trait SpecCopy: Read { /// Attempt to copy from this reader to the provided writer using a specialized /// process. + /// + /// Note that this function does _not_ take `self` as a parameter, and instead + /// is passed a generic `Read` type `R`. + /// This allows the `Self` type to provide specialized implementations for + /// any combination of `Read` and `Write` types. + /// However, in practice `Self` and types wrapping `Self` will be passed as + /// the `reader` argument. + /// + /// As of time of writing, `&mut R`, `Take`, and `BufReader` will + /// forward to `R` for a specialized copy implementation. fn copy( _reader: &mut R, _writer: &mut W, From 366ebd6a8eeb2d7fed149578b83175c4586215de Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Mon, 27 Jul 2026 13:39:40 -0400 Subject: [PATCH 03/10] If the first_take is set to true and our first item returned None, return None always. Additionally, if any self.iter.nth() calls return a None, early return None as well. This fixes an edge case with non fused iterators to not advance the iterator beyond the first None item it observed in accordance with nth documentation saying that nth() will return None if n is greater than or equal to the length of the iterator. --- library/core/src/iter/adapters/step_by.rs | 9 ++- .../coretests/tests/iter/adapters/step_by.rs | 81 +++++++++++++++++++ 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/library/core/src/iter/adapters/step_by.rs b/library/core/src/iter/adapters/step_by.rs index 4c70c9dc84b3b..3a1ff98ec3343 100644 --- a/library/core/src/iter/adapters/step_by.rs +++ b/library/core/src/iter/adapters/step_by.rs @@ -254,9 +254,9 @@ unsafe impl StepByImpl for StepBy { default fn spec_nth(&mut self, mut n: usize) -> Option { if self.first_take { self.first_take = false; - let first = self.iter.next(); + let first = self.iter.next()?; if n == 0 { - return first; + return Some(first); } n -= 1; } @@ -266,7 +266,7 @@ unsafe impl StepByImpl for StepBy { // n + 1 could overflow // thus, if n is usize::MAX, instead of adding one, we call .nth(step) if n == usize::MAX { - self.iter.nth(step - 1); + self.iter.nth(step - 1)?; } else { n += 1; } @@ -290,7 +290,8 @@ unsafe impl StepByImpl for StepBy { n -= div_step; nth_step }; - self.iter.nth(nth - 1); + + self.iter.nth(nth - 1)?; } } diff --git a/library/coretests/tests/iter/adapters/step_by.rs b/library/coretests/tests/iter/adapters/step_by.rs index 1ebebb9691933..810c014fdc8d9 100644 --- a/library/coretests/tests/iter/adapters/step_by.rs +++ b/library/coretests/tests/iter/adapters/step_by.rs @@ -93,6 +93,46 @@ fn test_iterator_step_by_nth_overflow() { assert_eq!(it.0, (usize::MAX as Bigger) * 1); } +#[test] +#[allow(non_local_definitions)] +fn test_iterator_step_by_nth_overflow_on_none() { + #[cfg(target_pointer_width = "16")] + type Bigger = u32; + #[cfg(target_pointer_width = "32")] + type Bigger = u64; + #[cfg(target_pointer_width = "64")] + type Bigger = u128; + + #[derive(Clone)] + struct Test(Bigger); + impl Iterator for &mut Test { + type Item = i32; + fn next(&mut self) -> Option { + None + } + fn nth(&mut self, n: usize) -> Option { + self.0 += n as Bigger + 1; + None + } + } + + // usize::MAX * usize::MAX overflow + let mut it = Test(0); + let mut step_by = (&mut it).step_by(usize::MAX); + // first next() call sets StepBy's first_take to false + assert_eq!(step_by.next(), None); + assert_eq!(step_by.nth(usize::MAX), None); + assert_eq!(it.0, usize::MAX as Bigger + 1); + + // usize::MAX * (usize::MAX - 1) overflow + let mut it = Test(0); + let mut step_by = (&mut it).step_by(usize::MAX); + // first next() call sets StepBy's first_take to false + assert_eq!(step_by.next(), None); + assert_eq!(step_by.nth(usize::MAX - 1), None); + assert_eq!(it.0, usize::MAX as Bigger + 1); +} + #[test] fn test_iterator_step_by_nth_try_fold() { let mut it = (0..).step_by(10); @@ -337,3 +377,44 @@ fn test_step_by_new_range_iter() { assert_eq!(it.next_back(), Some(10)); assert_eq!(it.next(), None); } + +#[test] +fn test_step_by_nth_non_fused() { + struct StepByNthOne { + exhausted: bool, + } + impl Iterator for StepByNthOne { + type Item = i32; + fn next(&mut self) -> Option { + if self.exhausted { + Some(0) + } else { + self.exhausted = true; + None + } + } + } + + let mut iter = StepByNthOne { exhausted: false }.step_by(1); + assert_eq!(iter.nth(1), None) +} + +#[test] +fn test_step_by_nth_non_fused_on_non_first_take() { + struct StepByOneNthMax(i32); + impl Iterator for StepByOneNthMax { + type Item = i32; + fn next(&mut self) -> Option { + let prev = self.0; + self.0 += 1; + if prev == 1 { None } else { Some(prev) } + } + } + + let mut iter = StepByOneNthMax(0).step_by(1); + // sets StepBy iterator first_take field to false + assert_eq!(iter.next(), Some(0)); + // Our underlying iterator should be pointing at a `None` item + // so we should expect `StepBy::nth` to return `None` + assert_eq!(iter.nth(usize::MAX), None) +} From 8f6a09a711b6b8cec746587ce9f7a9e30fd54633 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sat, 1 Aug 2026 10:56:08 +0000 Subject: [PATCH 04/10] Library lock file maintenance --- library/Cargo.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/library/Cargo.lock b/library/Cargo.lock index b7cc8590ae711..4b2ede0b35997 100644 --- a/library/Cargo.lock +++ b/library/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "addr2line" -version = "0.27.0" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efe1709241908a54ef1925c6018f41d3f523d0cfe174719761eb39e7b7bf086a" +checksum = "e567177890eb1617b1f774005b66b26b2377afd138a2ca37aae7d8f0c81429d4" dependencies = [ "gimli", "rustc-std-workspace-alloc", @@ -78,9 +78,9 @@ dependencies = [ [[package]] name = "dlmalloc" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f5b01c17f85ee988d832c40e549a64bd89ab2c9f8d8a613bdf5122ae507e294" +checksum = "ad5208a115eaba24916f7456929832e310a81518c641f93fee4f89aa93aa3675" dependencies = [ "cfg-if", "libc", @@ -155,9 +155,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" dependencies = [ "rustc-std-workspace-core", ] @@ -175,9 +175,9 @@ dependencies = [ [[package]] name = "moto-rt" -version = "0.16.0" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29aea9f7dfeb258e030a84e0ec38a9c2ec2063d4f45eb2db31445cfc40b3dba1" +checksum = "0aadbab5a5ca5a01ec1ff4bc03c2c3a7643f0e1d97ad5a4a5b58ce78504e17da" dependencies = [ "rustc-std-workspace-alloc", "rustc-std-workspace-core", @@ -250,18 +250,18 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_core", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" [[package]] name = "rand_xorshift" @@ -419,9 +419,9 @@ dependencies = [ [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "rustc-std-workspace-alloc", "rustc-std-workspace-core", From 2eac8f3d16e03822e24727f5ce98553a6067d714 Mon Sep 17 00:00:00 2001 From: Yukang Date: Sat, 1 Aug 2026 20:55:07 +0800 Subject: [PATCH 05/10] Add regression test for unused_allocation on boxed comparison --- .../unused-allocation-box-cmp-issue-134186.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/ui/lint/unused/unused-allocation-box-cmp-issue-134186.rs diff --git a/tests/ui/lint/unused/unused-allocation-box-cmp-issue-134186.rs b/tests/ui/lint/unused/unused-allocation-box-cmp-issue-134186.rs new file mode 100644 index 0000000000000..62f57a11dba4e --- /dev/null +++ b/tests/ui/lint/unused/unused-allocation-box-cmp-issue-134186.rs @@ -0,0 +1,15 @@ +//@ check-pass +// Regression test for . +// Comparing two `Box` values with `==` autorefs each `Box::new(...)` to `&Box`, so the +// allocation is necessary and `unused_allocation` must not fire + +#![deny(unused_allocation)] + +pub fn main() { + let a = Box::new(42); + + // `PartialEq for Box` takes the operands by reference, so these allocations are used. + let _ = a == Box::new(99); + let _ = Box::new(99) == a; + let _ = Box::new(1) == Box::new(2); +} From 9890f666db49fac42cb154470ae8525b31dd03f9 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Fri, 31 Jul 2026 11:23:26 +0200 Subject: [PATCH 06/10] implement `CmRef` for `CmRefCell`, having 2 different borrow types of the underlying `RefCell` --- compiler/rustc_resolve/src/check_unused.rs | 2 +- .../rustc_resolve/src/diagnostics/impls.rs | 32 +++++----- .../src/effective_visibilities.rs | 4 +- compiler/rustc_resolve/src/imports.rs | 4 +- .../rustc_resolve/src/late/diagnostics.rs | 8 +-- compiler/rustc_resolve/src/lib.rs | 64 +++++++++++++------ 6 files changed, 67 insertions(+), 47 deletions(-) diff --git a/compiler/rustc_resolve/src/check_unused.rs b/compiler/rustc_resolve/src/check_unused.rs index 4c5c591ad94e3..41573749abbe7 100644 --- a/compiler/rustc_resolve/src/check_unused.rs +++ b/compiler/rustc_resolve/src/check_unused.rs @@ -558,7 +558,7 @@ impl Resolver<'_, '_> { let unused_imports = visitor.unused_imports; let mut check_redundant_imports = FxIndexSet::default(); for module in &self.local_modules { - for (_key, resolution) in self.resolutions(module.to_module()).borrow().iter() { + for (_key, resolution) in self.resolutions(module.to_module()).iter() { if let Some(decl) = resolution.borrow().best_decl() && let DeclKind::Import { import, .. } = decl.kind && let ImportKind::Single { id, .. } = import.kind diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index 212629395c98b..cc2c72ad59906 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -1870,24 +1870,22 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // If so, we have to disambiguate the potential import suggestions by making // the paths *global* (i.e., by prefixing them with `::`). let needs_disambiguation = - self.resolutions(parent_scope.module).borrow().iter().any( - |(key, name_resolution)| { - if key.ns == TypeNS - && key.ident == *ident - && let Some(decl) = name_resolution.borrow().best_decl() - { - match decl.res() { - // No disambiguation needed if the identically named item we - // found in scope actually refers to the crate in question. - Res::Def(_, def_id) => def_id != crate_def_id, - Res::PrimTy(_) => true, - _ => false, - } - } else { - false + self.resolutions(parent_scope.module).iter().any(|(key, name_resolution)| { + if key.ns == TypeNS + && key.ident == *ident + && let Some(decl) = name_resolution.borrow().best_decl() + { + match decl.res() { + // No disambiguation needed if the identically named item we + // found in scope actually refers to the crate in question. + Res::Def(_, def_id) => def_id != crate_def_id, + Res::PrimTy(_) => true, + _ => false, } - }, - ); + } else { + false + } + }); let mut crate_path = ThinVec::new(); if needs_disambiguation { crate_path.push(ast::PathSegment::path_root(rustc_span::DUMMY_SP)); diff --git a/compiler/rustc_resolve/src/effective_visibilities.rs b/compiler/rustc_resolve/src/effective_visibilities.rs index 90927492195b0..ff976b080d40d 100644 --- a/compiler/rustc_resolve/src/effective_visibilities.rs +++ b/compiler/rustc_resolve/src/effective_visibilities.rs @@ -125,7 +125,7 @@ impl<'a, 'ra, 'tcx> EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> { /// including their whole reexport chains. fn set_bindings_effective_visibilities(&mut self, module_id: LocalDefId) { let module = self.r.expect_module(module_id.to_def_id()); - for (_, name_resolution) in self.r.resolutions(module).borrow().iter() { + for (_, name_resolution) in self.r.resolutions(module).iter() { let Some(decl) = name_resolution.borrow().best_decl() else { continue; }; @@ -309,7 +309,7 @@ impl<'a, 'ra, 'tcx> EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> { ) { if self.macro_reachable.insert((module_def_id, defining_mod)) { let module = self.r.expect_module(module_def_id.to_def_id()); - for (_, name_resolution) in self.r.resolutions(module).borrow().iter() { + for (_, name_resolution) in self.r.resolutions(module).iter() { let Some(decl) = name_resolution.borrow().best_decl() else { continue; }; diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index c00eb97f4c3d6..6e2ea9abf2de8 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -1002,7 +1002,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { pub(crate) fn lint_reexports(&mut self, exported_ambiguities: FxHashSet>) { for module in &self.local_modules { - for (key, resolution) in self.resolutions(module.to_module()).borrow().iter() { + for (key, resolution) in self.resolutions(module.to_module()).iter() { let resolution = resolution.borrow(); let Some(binding) = resolution.best_decl() else { continue }; @@ -1481,7 +1481,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let names = match module { ModuleOrUniformRoot::Module(module) => { self.resolutions(module) - .borrow() .iter() .filter_map(|(BindingKey { ident: i, .. }, resolution)| { if i.name == ident.name { @@ -1799,7 +1798,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let import_bindings = match imported_module { ModuleOrUniformRoot::Module(module) if module != import.parent_scope.module => self .resolutions(module) - .borrow() .iter() .filter_map(|(key, resolution)| { let res = resolution.borrow(); diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index f2ce21377acce..9550ac1446ec7 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -190,7 +190,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { assoc_name: Symbol, ) -> Option { let module = self.r.get_module(trait_def_id)?; - self.r.resolutions(module).borrow().iter().find_map(|(key, resolution)| { + self.r.resolutions(module).iter().find_map(|(key, resolution)| { if key.ident.name != assoc_name { return None; } @@ -648,7 +648,6 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { && self .r .resolutions(module) - .borrow() .iter() .any(|(key, _r)| key.ident.name == following_seg.ident.name) } else { @@ -1164,7 +1163,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { fn lookup_doc_alias_name(&mut self, path: &[Segment], ns: Namespace) -> Option<(DefId, Ident)> { let find_doc_alias_name = |r: &mut Resolver<'ra, '_>, m: Module<'ra>, item_name: Symbol| { - for resolution in r.resolutions(m).borrow().values() { + for resolution in r.resolutions(m).values() { let Some(did) = resolution.borrow().best_decl().and_then(|binding| binding.res().opt_def_id()) else { @@ -1904,7 +1903,6 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { let targets: Vec<_> = self .r .resolutions(module) - .borrow() .iter() .filter_map(|(key, resolution)| { let resolution = resolution.borrow(); @@ -2767,7 +2765,6 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { let targets = self .r .resolutions(*module) - .borrow() .iter() .filter_map(|(key, res)| res.borrow().best_decl().map(|binding| (key, binding.res()))) .filter(|(_, res)| match (kind, res) { @@ -2970,7 +2967,6 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { let module = self.r.expect_module(def_id); self.r .resolutions(module) - .borrow() .iter() .any(|(key, _)| key.ident.name == following_seg.ident.name) } diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index af62b136f60c5..a3c804e56ee22 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -21,7 +21,7 @@ #![recursion_limit = "256"] // tidy-alphabetical-end -use std::cell::Ref; +use std::cell::{Ref, RefMut}; use std::collections::BTreeSet; use std::ops::ControlFlow; use std::sync::{Arc, OnceLock}; @@ -81,7 +81,7 @@ use crate::diagnostics::impls::{ ImportSuggestion, LabelSuggestion, OnUnknownData, StructCtor, Suggestion, }; use crate::imports::{ImportResolution, NameResolutionRef}; -use crate::ref_mut::{CmCell, CmRefCell}; +use crate::ref_mut::{CmCell, CmRef, CmRefCell}; mod build_reduced_graph; mod check_unused; @@ -637,7 +637,7 @@ type ResolutionTable<'ra> = FxIndexMap>; enum Resolutions<'ra> { Local(CmRefCell>), - Extern(OnceLock>>), + Extern(OnceLock>), } impl<'ra> Resolutions<'ra> { @@ -792,7 +792,7 @@ impl<'ra> Module<'ra> { resolver: &R, mut f: impl FnMut(&R, IdentKey, Span, Namespace, Decl<'ra>), ) { - for (key, name_resolution) in resolver.as_ref().resolutions(self).borrow().iter() { + for (key, name_resolution) in resolver.as_ref().resolutions(self).iter() { let name_resolution = name_resolution.borrow(); if let Some(decl) = name_resolution.best_decl() { f(resolver, key.ident, name_resolution.orig_ident_span, key.ns, decl); @@ -805,7 +805,7 @@ impl<'ra> Module<'ra> { resolver: &mut R, mut f: impl FnMut(&mut R, IdentKey, Span, Namespace, Decl<'ra>), ) { - for (key, name_resolution) in resolver.as_mut().resolutions(self).borrow().iter() { + for (key, name_resolution) in resolver.as_mut().resolutions(self).iter() { let name_resolution = name_resolution.borrow(); if let Some(decl) = name_resolution.best_decl() { f(resolver, key.ident, name_resolution.orig_ident_span, key.ns, decl); @@ -2152,7 +2152,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { match (trait_module, assoc_item) { (Some(trait_module), Some((name, ns))) => self .resolutions(trait_module) - .borrow() .iter() .any(|(key, _name_resolution)| key.ns == ns && key.ident.name == name), _ => true, @@ -2177,14 +2176,28 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { self.tcx.hir_arena.alloc_slice(&import_ids) } - fn resolutions(&self, module: Module<'ra>) -> &'ra CmRefCell> { + fn resolutions(&self, module: Module<'ra>) -> CmRef<'ra, ResolutionTable<'ra>> { match &module.0.0.lazy_resolutions { - Resolutions::Local(local_res) => local_res, + Resolutions::Local(local_res) => CmRef::Tracked(local_res.borrow()), Resolutions::Extern(extern_res) => { - // as long as 1 thread is building this external table, all other threads will wait - extern_res.get_or_init(|| { - CmRefCell::new(self.build_reduced_graph_external(module.expect_extern())) - }) + // It is fine to return a `CmRef::Untracked`, we never give out a `&mut` + // to an external table. + CmRef::Untracked( + // As long as 1 thread is building this external table, all other threads will wait. + extern_res + .get_or_init(|| self.build_reduced_graph_external(module.expect_extern())), + ) + } + } + } + + fn resolutions_mut(&self, module: Module<'ra>) -> RefMut<'ra, ResolutionTable<'ra>> { + match &module.0.0.lazy_resolutions { + Resolutions::Local(local_res) => local_res.borrow_mut(self), + Resolutions::Extern(_) => { + // We do not allow in place mutations of the external resolution table. In fact, + // we never attempt it. + unreachable!("Attempted to mutably borrow an extenral resolution table") } } } @@ -2194,7 +2207,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { module: Module<'ra>, key: BindingKey, ) -> Option>> { - self.resolutions(module).borrow().get(&key).map(|resolution| resolution.0.borrow()) + self.resolutions(module).get(&key).map(|resolution| resolution.0.borrow()) } #[track_caller] @@ -2204,7 +2217,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { key: BindingKey, orig_ident_span: Span, ) -> NameResolutionRef<'ra> { - *self.resolutions(module).borrow_mut(self).entry(key).or_insert_with(|| { + *self.resolutions_mut(module).entry(key).or_insert_with(|| { self.arenas.alloc_name_resolution(NameResolution::new(orig_ident_span)) }) } @@ -2915,6 +2928,24 @@ mod ref_mut { } } + pub(crate) enum CmRef<'b, T> { + /// A tracked borrow of a [`CmRefCell`] + Tracked(Ref<'b, T>), + /// An untracked or normal reference (not dynamically borrow-checked by `RefCell`) + Untracked(&'b T), + } + + impl<'b, T> Deref for CmRef<'b, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + match self { + CmRef::Tracked(r) => r, + CmRef::Untracked(r) => r, + } + } + } + /// A wrapper around a [`RefCell`] that only allows writes (mutable borrows) based on a condition in the resolver. #[derive(Default)] pub(crate) struct CmRefCell(RefCell); @@ -2926,10 +2957,7 @@ mod ref_mut { #[track_caller] pub(crate) fn borrow_mut<'ra, 'tcx>(&self, r: &Resolver<'ra, 'tcx>) -> RefMut<'_, T> { - if r.assert_speculative { - panic!("not allowed to mutably borrow a `CmRefCell` during speculative resolution"); - } - self.0.borrow_mut() + self.try_borrow_mut(r).unwrap() } #[track_caller] From 5653258172b51610baa34257acbdbda85a1fd906 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 1 Aug 2026 17:53:10 +1000 Subject: [PATCH 07/10] Check `proc_macro_deps.rs` by reading it, not by including it --- src/tools/tidy/src/deps.rs | 90 +++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index 342743d21340f..043fe7a72c50f 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -1,9 +1,9 @@ //! Checks the licenses of third-party dependencies. -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::{Display, Formatter}; -use std::fs::{File, read_dir}; -use std::io::Write; +use std::fs::{self, read_dir}; +use std::io; use std::path::Path; use cargo_metadata::semver::Version; @@ -11,9 +11,6 @@ use cargo_metadata::{Metadata, Package, PackageId}; use crate::diagnostics::{RunningCheck, TidyCtx}; -#[path = "../../../bootstrap/src/utils/proc_macro_deps.rs"] -mod proc_macro_deps; - #[derive(Clone, Copy)] struct ListLocation { path: &'static str, @@ -722,53 +719,58 @@ fn check_proc_macro_dep_list(root: &Path, cargo: &Path, bless: bool, check: &mut } // Remove the proc-macro crates themselves proc_macro_deps.retain(|pkg| !is_proc_macro_pkg(&metadata[pkg])); + // Sort and deduplicate the crate names. + let proc_macro_deps = + proc_macro_deps.into_iter().map(|dep| metadata[dep].name.as_ref()).collect::>(); - let proc_macro_deps: HashSet<_> = - proc_macro_deps.into_iter().map(|dep| metadata[dep].name.as_ref()).collect(); - let expected = proc_macro_deps::CRATES.iter().copied().collect::>(); - - let needs_blessing = proc_macro_deps.difference(&expected).next().is_some() - || expected.difference(&proc_macro_deps).next().is_some(); + let expected = { + use std::fmt::Write; - if needs_blessing && bless { - let mut proc_macro_deps: Vec<_> = proc_macro_deps.into_iter().collect(); - proc_macro_deps.sort(); - let mut file = File::create(root.join("src/bootstrap/src/utils/proc_macro_deps.rs")) - .expect("`proc_macro_deps` should exist"); - writeln!( - &mut file, - "/// Do not update manually - use `./x.py test tidy --bless` + const HEADER: &str = "\ +/// Do not update manually - use `./x.py test tidy --bless` /// Holds all direct and indirect dependencies of proc-macro crates in tree. /// See pub static CRATES: &[&str] = &[ - // tidy-alphabetical-start" - ) - .unwrap(); + // tidy-alphabetical-start +"; + const FOOTER: &str = " // tidy-alphabetical-end +]; +"; + + let mut buf = String::with_capacity(4096); + buf.push_str(HEADER); for dep in proc_macro_deps { - writeln!(&mut file, " {dep:?},").unwrap(); + writeln!(buf, " {dep:?},").unwrap(); } - writeln!( - &mut file, - " // tidy-alphabetical-end -];" - ) - .unwrap(); - } else { - let mut error_found = false; + buf.push_str(FOOTER); + buf + }; - for missing in proc_macro_deps.difference(&expected) { - error_found = true; - check.error(format!( - "proc-macro crate dependency `{missing}` is not registered in `src/bootstrap/src/utils/proc_macro_deps.rs`", - )); - } - for extra in expected.difference(&proc_macro_deps) { - error_found = true; - check.error(format!( - "`{extra}` is registered in `src/bootstrap/src/utils/proc_macro_deps.rs`, but is not a proc-macro crate dependency", - )); + const PROC_MACRO_DEPS_RS: &str = "src/bootstrap/src/utils/proc_macro_deps.rs"; + let proc_macro_deps_rs_path = &root.join(PROC_MACRO_DEPS_RS); + let actual = match fs::read_to_string(proc_macro_deps_rs_path) { + Ok(actual) => actual, + Err(e) => { + if e.kind() == io::ErrorKind::NotFound { + check.error(format!( + "`{PROC_MACRO_DEPS_RS}` not found; has it been moved or renamed?" + )); + } else { + check.error(format!("`{PROC_MACRO_DEPS_RS}` could not be read: {e:?}")); + } + return; } - if error_found { + }; + + if actual != expected { + if bless { + fs::write(proc_macro_deps_rs_path, &expected).unwrap(); + } else { + let diff = similar::TextDiff::from_lines(&actual, &expected); + let mut unified = diff.unified_diff(); + unified.header(PROC_MACRO_DEPS_RS, "(expected)"); + + check.error(format!("`{PROC_MACRO_DEPS_RS}` is not up-to-date:\n{unified}")); check.message("Run `./x.py test tidy --bless` to regenerate the list"); } } From bcc5501fa53eb7fd6ae7ba718349702a4d89feeb Mon Sep 17 00:00:00 2001 From: Paul Mabileau Date: Fri, 31 Jul 2026 12:50:03 +0200 Subject: [PATCH 08/10] Fix(lib/fs/tests): Avoid permission denials when cleaning up TempDirs in `set_get_permissions_nofollows*` At least under Windows 7, the `set_get_permissions_nofollows` and `set_get_permissions_nofollows_symlink` FS tests currently fail on: ``` ---- fs::tests::set_get_permissions_nofollows stdout ---- thread 'fs::tests::set_get_permissions_nofollows' (2308) panicked at library/std/src/test_helpers.rs:53:20: called `Result::unwrap()` on an `Err` value: Os { code: 5, kind: PermissionDenied, message: "Access is denied." } ---- fs::tests::set_get_permissions_nofollows stdout end ---- ---- fs::tests::set_get_permissions_nofollows_symlink stdout ---- thread 'fs::tests::set_get_permissions_nofollows_symlink' (1108) panicked at library/std/src/test_helpers.rs:53:20: called `Result::unwrap()` on an `Err` value: Os { code: 5, kind: PermissionDenied, message: "Access is denied." } ---- fs::tests::set_get_permissions_nofollows_symlink stdout end ---- ``` The panic clearly occurs in `TempDir::drop` that calls `fs::remove_dir_all`. This is consistent with the fact that `FILE_ATTRIBUTE_READONLY` is set on the file: > Applications can read the file, but cannot write to it or delete it. from [the attribute's documentation]. This therefore fixes these tests by resetting the attribute before letting the drop guard run. [the attribute's documentation]: https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants Signed-off-by: Paul Mabileau --- library/std/src/fs/tests.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index cf73d3aedfb9a..f0dbe3e76984a 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -629,6 +629,16 @@ fn set_get_permissions_nofollows() { assert_eq!(result.unwrap(), ()); let metadata0 = check!(fs::metadata(&filename)); assert!(metadata0.permissions().readonly()); + + // Reset the read-only bit under Windows 7: avoids the + // `TempDir::drop` from crashing on a permission denial when + // trying to delete the file that has it. + #[cfg(all(windows, target_vendor = "win7"))] + { + let mut permission_bits = metadata0.permissions(); + permission_bits.set_readonly(false); + check!(fs::set_permissions_nofollow(&filename, permission_bits)); + } }, _ => { let error_kind = result.unwrap_err().kind(); @@ -669,6 +679,16 @@ fn set_get_permissions_nofollows_symlink() { assert!(metadata0.permissions().readonly()); #[cfg(not(windows))] assert!(!metadata0.permissions().readonly()); + + // Reset the read-only bit under Windows 7: avoids the + // `TempDir::drop` from crashing on a permission denial when + // trying to delete the file that has it. + #[cfg(all(windows, target_vendor = "win7"))] + { + let mut permission_bits = metadata0.permissions(); + permission_bits.set_readonly(false); + check!(fs::set_permissions_nofollow(&symlink_name, permission_bits)); + } }, _ => { let error_kind = result.unwrap_err().kind(); From 5c2400bc33d46e6e0bf4da265939ef84cafb1e6c Mon Sep 17 00:00:00 2001 From: Taha Dostifam Date: Sat, 1 Aug 2026 18:49:21 +0330 Subject: [PATCH 09/10] feat: add finalize_check to RustcForceInline attribute parser to check if it conflicts with Inline attribute --- .../src/attributes/inline.rs | 18 ++++++++++++++---- .../src/session_diagnostics.rs | 9 +++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/inline.rs b/compiler/rustc_attr_parsing/src/attributes/inline.rs index 45d93e215c781..52960ae220a59 100644 --- a/compiler/rustc_attr_parsing/src/attributes/inline.rs +++ b/compiler/rustc_attr_parsing/src/attributes/inline.rs @@ -1,12 +1,10 @@ -// FIXME(jdonszelmann): merge these two parsers and error when both attributes are present here. -// note: need to model better how duplicate attr errors work when not using -// SingleAttributeParser which is what we have two of here. - use rustc_feature::AttributeStability; use rustc_hir::attrs::{AttributeKind, InlineAttr}; +use rustc_hir::find_attr; use rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT; use super::prelude::*; +use crate::session_diagnostics::InlineForceInlineConflict; pub(crate) struct InlineParser; @@ -94,4 +92,16 @@ impl SingleAttributeParser for RustcForceInlineParser { cx.attr_span, )) } + + fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, attr_span: Span) { + let Some(inline_span) = find_attr!(cx.parsed_attrs, Inline(attr, span) if !matches!(attr, InlineAttr::Force { .. }) => span) + else { + return; + }; + + cx.emit_err(InlineForceInlineConflict { + inline_span: *inline_span, + force_inline_span: attr_span, + }); + } } diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 53fa0a2fb9293..4bedecf030825 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -13,6 +13,15 @@ use rustc_target::spec::TargetTuple; use crate::AttributeTemplate; use crate::context::Suggestion; +#[derive(Diagnostic)] +#[diag("`#[rustc_force_inline]` and `#[inline]` cannot be used together")] +pub(crate) struct InlineForceInlineConflict { + #[primary_span] + pub force_inline_span: Span, + #[label("the inline attribute is specified here")] + pub inline_span: Span, +} + #[derive(Diagnostic)] #[diag("`#[ffi_const]` function cannot be `#[ffi_pure]`", code = E0757)] pub(crate) struct BothFfiConstAndPure { From 98cc5dc960854adbdadb387d157d2a6cab77d5cd Mon Sep 17 00:00:00 2001 From: Taha Dostifam Date: Sat, 1 Aug 2026 19:03:18 +0330 Subject: [PATCH 10/10] test: added testcase ui/attributes/inline/rustc-force-inline-conflict-with-inline --- .../rustc-force-inline-conflict-with-inline.rs | 11 +++++++++++ ...tc-force-inline-conflict-with-inline.stderr | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/ui/attributes/inline/rustc-force-inline-conflict-with-inline.rs create mode 100644 tests/ui/attributes/inline/rustc-force-inline-conflict-with-inline.stderr diff --git a/tests/ui/attributes/inline/rustc-force-inline-conflict-with-inline.rs b/tests/ui/attributes/inline/rustc-force-inline-conflict-with-inline.rs new file mode 100644 index 0000000000000..1f7ceda739a8d --- /dev/null +++ b/tests/ui/attributes/inline/rustc-force-inline-conflict-with-inline.rs @@ -0,0 +1,11 @@ +#![feature(rustc_attrs)] + +#[inline] //~ NOTE: the inline attribute is specified here +#[rustc_force_inline] //~ ERROR: cannot be used together +fn foo() {} + +#[rustc_force_inline] //~ ERROR: cannot be used together +#[inline] //~ NOTE: the inline attribute is specified here +fn bar() {} + +fn main() {} diff --git a/tests/ui/attributes/inline/rustc-force-inline-conflict-with-inline.stderr b/tests/ui/attributes/inline/rustc-force-inline-conflict-with-inline.stderr new file mode 100644 index 0000000000000..dc8e2cd5939f3 --- /dev/null +++ b/tests/ui/attributes/inline/rustc-force-inline-conflict-with-inline.stderr @@ -0,0 +1,18 @@ +error: `#[rustc_force_inline]` and `#[inline]` cannot be used together + --> $DIR/rustc-force-inline-conflict-with-inline.rs:4:1 + | +LL | #[inline] + | --------- the inline attribute is specified here +LL | #[rustc_force_inline] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: `#[rustc_force_inline]` and `#[inline]` cannot be used together + --> $DIR/rustc-force-inline-conflict-with-inline.rs:7:1 + | +LL | #[rustc_force_inline] + | ^^^^^^^^^^^^^^^^^^^^^ +LL | #[inline] + | --------- the inline attribute is specified here + +error: aborting due to 2 previous errors +