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 3a1ffcb47844e..b7c7b39bcb48b 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 { 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 fdff976527231..6350f79ed007f 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] diff --git a/library/Cargo.lock b/library/Cargo.lock index 610ff493cab04..d029173e807a4 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", diff --git a/library/alloc/src/io/copy.rs b/library/alloc/src/io/copy.rs new file mode 100644 index 0000000000000..798bd2c1ec0a0 --- /dev/null +++ b/library/alloc/src/io/copy.rs @@ -0,0 +1,90 @@ +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::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")] +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 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 new file mode 100644 index 0000000000000..d75f97dbed2b8 --- /dev/null +++ b/library/alloc/src/io/copy/generic.rs @@ -0,0 +1,256 @@ +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. +/// +/// 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_priority = BufferedReaderSpec::buffer_priority(reader); + let write_priority = BufferedWriterSpec::buffer_priority(writer); + + if read_priority >= DEFAULT_BUF_SIZE && read_priority >= write_priority { + return BufferedReaderSpec::copy_to(reader, writer); + } + + BufferedWriterSpec::copy_from(writer, reader) +} + +/// 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_priority(&self) -> BufferPriority; + + fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result; +} + +impl BufferedReaderSpec for T +where + Self: Read, + T: ?Sized, +{ + #[inline] + default fn buffer_priority(&self) -> BufferPriority { + UNBUFFERED + } + + default fn copy_to(&mut self, _to: &mut (impl Write + ?Sized)) -> Result { + unreachable!("only called from specializations") + } +} + +impl BufferedReaderSpec for &[u8] { + fn buffer_priority(&self) -> BufferPriority { + IN_MEMORY + } + + 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_priority(&self) -> BufferPriority { + IN_MEMORY + } + + 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_priority(&self) -> BufferPriority { + 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 in `generic_copy` that either uses a +/// stack buffer or reuses the internal buffer of a [`BufWriter`]. +trait BufferedWriterSpec: Write { + fn buffer_priority(&self) -> BufferPriority; + + fn copy_from(&mut self, reader: &mut R) -> Result; +} + +impl BufferedWriterSpec for W { + #[inline] + 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_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); + } + + 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_priority(&self) -> BufferPriority { + 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")) + } +} + +/// Copies from `reader` to `writer` using a stack-allocated buffer (a fixed sized array). +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..738f3af6beaae --- /dev/null +++ b/library/alloc/src/io/copy/specialization.rs @@ -0,0 +1,74 @@ +//! Provides specialization for `io::copy`. + +use super::CopyState; +use crate::io::{BufReader, Read, Result, Take, Write}; + +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 R { + default fn copy(&mut self, _writer: &mut W) -> Result { + Ok(CopyState::Fallback(0)) + } +} + +impl SpecCopyInner for R { + fn copy(&mut self, writer: &mut W) -> Result { + ::copy(self, writer) + } +} + +#[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. + /// + /// 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, + ) -> 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/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) +} 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(); 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}; 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"); } } 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 + 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); +}