From 0e336684fd92ec8343d76085646dffe576b9b015 Mon Sep 17 00:00:00 2001 From: tison Date: Sun, 30 Nov 2025 10:42:41 +0800 Subject: [PATCH 1/8] Leverage &mut in OnceLock when possible Signed-off-by: tison --- library/std/src/sync/once_lock.rs | 45 ++++++++++++++++++------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/library/std/src/sync/once_lock.rs b/library/std/src/sync/once_lock.rs index d1bbe0ff843c..dc7df5baf863 100644 --- a/library/std/src/sync/once_lock.rs +++ b/library/std/src/sync/once_lock.rs @@ -1,3 +1,4 @@ +use super::once::OnceExclusiveState; use crate::cell::UnsafeCell; use crate::fmt; use crate::marker::PhantomData; @@ -152,8 +153,8 @@ impl OnceLock { #[stable(feature = "once_cell", since = "1.70.0")] #[rustc_should_not_be_called_on_const_items] pub fn get(&self) -> Option<&T> { - if self.is_initialized() { - // Safe b/c checked is_initialized + if self.initialized() { + // Safe b/c checked initialized Some(unsafe { self.get_unchecked() }) } else { None @@ -170,8 +171,8 @@ impl OnceLock { #[inline] #[stable(feature = "once_cell", since = "1.70.0")] pub fn get_mut(&mut self) -> Option<&mut T> { - if self.is_initialized() { - // Safe b/c checked is_initialized and we have a unique access + if self.initialized_mut() { + // Safe b/c checked initialized and we have a unique access Some(unsafe { self.get_unchecked_mut() }) } else { None @@ -402,14 +403,12 @@ impl OnceLock { // NOTE: We need to perform an acquire on the state in this method // in order to correctly synchronize `LazyLock::force`. This is // currently done by calling `self.get()`, which in turn calls - // `self.is_initialized()`, which in turn performs the acquire. + // `self.initialized()`, which in turn performs the acquire. if let Some(value) = self.get() { return Ok(value); } self.initialize(f)?; - debug_assert!(self.is_initialized()); - // SAFETY: The inner value has been initialized Ok(unsafe { self.get_unchecked() }) } @@ -451,10 +450,10 @@ impl OnceLock { where F: FnOnce() -> Result, { - if self.get().is_none() { + if self.get_mut().is_none() { self.initialize(f)?; } - debug_assert!(self.is_initialized()); + // SAFETY: The inner value has been initialized Ok(unsafe { self.get_unchecked_mut() }) } @@ -503,22 +502,32 @@ impl OnceLock { #[inline] #[stable(feature = "once_cell", since = "1.70.0")] pub fn take(&mut self) -> Option { - if self.is_initialized() { + if self.initialized_mut() { self.once = Once::new(); // SAFETY: `self.value` is initialized and contains a valid `T`. - // `self.once` is reset, so `is_initialized()` will be false again + // `self.once` is reset, so `initialized()` will be false again // which prevents the value from being read twice. - unsafe { Some((&mut *self.value.get()).assume_init_read()) } + unsafe { Some(self.value.get_mut().assume_init_read()) } } else { None } } #[inline] - fn is_initialized(&self) -> bool { + fn initialized(&self) -> bool { self.once.is_completed() } + #[inline] + fn initialized_mut(&mut self) -> bool { + // `state()` does not perform an atomic load, so prefer it over `is_complete()`. + let state = self.once.state(); + match state { + OnceExclusiveState::Complete => true, + _ => false, + } + } + #[cold] #[optimize(size)] fn initialize(&self, f: F) -> Result<(), E> @@ -552,7 +561,7 @@ impl OnceLock { /// The cell must be initialized #[inline] unsafe fn get_unchecked(&self) -> &T { - debug_assert!(self.is_initialized()); + debug_assert!(self.initialized()); unsafe { (&*self.value.get()).assume_init_ref() } } @@ -561,8 +570,8 @@ impl OnceLock { /// The cell must be initialized #[inline] unsafe fn get_unchecked_mut(&mut self) -> &mut T { - debug_assert!(self.is_initialized()); - unsafe { (&mut *self.value.get()).assume_init_mut() } + debug_assert!(self.initialized_mut()); + unsafe { self.value.get_mut().assume_init_mut() } } } @@ -689,11 +698,11 @@ impl Eq for OnceLock {} unsafe impl<#[may_dangle] T> Drop for OnceLock { #[inline] fn drop(&mut self) { - if self.is_initialized() { + if self.initialized_mut() { // SAFETY: The cell is initialized and being dropped, so it can't // be accessed again. We also don't touch the `T` other than // dropping it, which validates our usage of #[may_dangle]. - unsafe { (&mut *self.value.get()).assume_init_drop() }; + unsafe { self.value.get_mut().assume_init_drop() }; } } } From 5f5286beb2d09b16c4c0bdbc7a1ab61898a64029 Mon Sep 17 00:00:00 2001 From: Theemathas Chirananthavat Date: Sat, 29 Nov 2025 14:42:43 +0700 Subject: [PATCH 2/8] Rewrite `String::replace_range` This simplifies the code, provides better panic messages, and avoids an integer overflow. --- library/alloc/src/string.rs | 35 ++++++++++-------------------- library/alloctests/tests/string.rs | 23 +++++++++++++++----- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 4a2689e01ff1..7e35c43de519 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -50,8 +50,6 @@ use core::iter::from_fn; use core::ops::Add; #[cfg(not(no_global_oom_handling))] use core::ops::AddAssign; -#[cfg(not(no_global_oom_handling))] -use core::ops::Bound::{Excluded, Included, Unbounded}; use core::ops::{self, Range, RangeBounds}; use core::str::pattern::{Pattern, Utf8Pattern}; use core::{fmt, hash, ptr, slice}; @@ -2049,30 +2047,19 @@ impl String { where R: RangeBounds, { - // Memory safety - // - // Replace_range does not have the memory safety issues of a vector Splice. - // of the vector version. The data is just plain bytes. + // We avoid #81138 (nondeterministic RangeBounds impls) because we only use `range` once, here. + let checked_range = slice::range(range, ..self.len()); - // WARNING: Inlining this variable would be unsound (#81138) - let start = range.start_bound(); - match start { - Included(&n) => assert!(self.is_char_boundary(n)), - Excluded(&n) => assert!(self.is_char_boundary(n + 1)), - Unbounded => {} - }; - // WARNING: Inlining this variable would be unsound (#81138) - let end = range.end_bound(); - match end { - Included(&n) => assert!(self.is_char_boundary(n + 1)), - Excluded(&n) => assert!(self.is_char_boundary(n)), - Unbounded => {} - }; + assert!( + self.is_char_boundary(checked_range.start), + "start of range should be a character boundary" + ); + assert!( + self.is_char_boundary(checked_range.end), + "end of range should be a character boundary" + ); - // Using `range` again would be unsound (#81138) - // We assume the bounds reported by `range` remain the same, but - // an adversarial implementation could change between calls - unsafe { self.as_mut_vec() }.splice((start, end), replace_with.bytes()); + unsafe { self.as_mut_vec() }.splice(checked_range, replace_with.bytes()); } /// Replaces the leftmost occurrence of a pattern with another string, in-place. diff --git a/library/alloctests/tests/string.rs b/library/alloctests/tests/string.rs index ecc5b9dc82ed..71557f284f47 100644 --- a/library/alloctests/tests/string.rs +++ b/library/alloctests/tests/string.rs @@ -616,8 +616,15 @@ fn test_replace_range() { } #[test] -#[should_panic] -fn test_replace_range_char_boundary() { +#[should_panic = "start of range should be a character boundary"] +fn test_replace_range_start_char_boundary() { + let mut s = "Hello, 世界!".to_owned(); + s.replace_range(8.., ""); +} + +#[test] +#[should_panic = "end of range should be a character boundary"] +fn test_replace_range_end_char_boundary() { let mut s = "Hello, 世界!".to_owned(); s.replace_range(..8, ""); } @@ -632,28 +639,32 @@ fn test_replace_range_inclusive_range() { } #[test] -#[should_panic] +#[should_panic = "range end index 6 out of range for slice of length 5"] fn test_replace_range_out_of_bounds() { let mut s = String::from("12345"); s.replace_range(5..6, "789"); } #[test] -#[should_panic] +#[should_panic = "range end index 5 out of range for slice of length 5"] fn test_replace_range_inclusive_out_of_bounds() { let mut s = String::from("12345"); s.replace_range(5..=5, "789"); } +// The overflowed index value is target-dependent, +// so we don't check for its exact value in the panic message #[test] -#[should_panic] +#[should_panic = "out of range for slice of length 3"] fn test_replace_range_start_overflow() { let mut s = String::from("123"); s.replace_range((Excluded(usize::MAX), Included(0)), ""); } +// The overflowed index value is target-dependent, +// so we don't check for its exact value in the panic message #[test] -#[should_panic] +#[should_panic = "out of range for slice of length 3"] fn test_replace_range_end_overflow() { let mut s = String::from("456"); s.replace_range((Included(0), Included(usize::MAX)), ""); From a2cff48048b5090557fbd53931431c73681d0cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 12 Dec 2025 16:13:58 +0100 Subject: [PATCH 3/8] Refactor and document `PlainSourceTarball` --- src/bootstrap/src/core/build_steps/dist.rs | 205 ++++++++++----------- 1 file changed, 102 insertions(+), 103 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index 40149ee09427..24781083bfab 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -1210,6 +1210,8 @@ impl Step for Src { } } +/// Tarball for people who want to build rustc and other components from the source. +/// Does not contain GPL code for licensing reasons. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct PlainSourceTarball; @@ -1232,51 +1234,18 @@ impl Step for PlainSourceTarball { /// Creates the plain source tarball fn run(self, builder: &Builder<'_>) -> GeneratedTarball { - // NOTE: This is a strange component in a lot of ways. It uses `src` as the target, which - // means neither rustup nor rustup-toolchain-install-master know how to download it. - // It also contains symbolic links, unlike other any other dist tarball. - // It's used for distros building rustc from source in a pre-vendored environment. - let mut tarball = Tarball::new(builder, "rustc", "src"); - tarball.permit_symlinks(true); - let plain_dst_src = tarball.image_dir(); - - // This is the set of root paths which will become part of the source package - let src_files = [ - // tidy-alphabetical-start - ".gitmodules", - "CONTRIBUTING.md", - "COPYRIGHT", - "Cargo.lock", - "Cargo.toml", - "LICENSE-APACHE", - "LICENSE-MIT", - "README.md", - "RELEASES.md", - "REUSE.toml", - "bootstrap.example.toml", - "configure", - "license-metadata.json", - "package.json", - "x", - "x.ps1", - "x.py", - "yarn.lock", - // tidy-alphabetical-end - ]; - let src_dirs = ["src", "compiler", "library", "tests", "LICENSES"]; - - copy_src_dirs( + let tarball = prepare_source_tarball( builder, - &builder.src, - &src_dirs, + "src", &[ // We don't currently use the GCC source code for building any official components, // it is very big, and has unclear licensing implications due to being GPL licensed. // We thus exclude it from the source tarball from now. "src/gcc", ], - plain_dst_src, ); + + let plain_dst_src = tarball.image_dir(); // We keep something in src/gcc because it is a registered submodule, // and if it misses completely it can cause issues elsewhere // (see https://github.com/rust-lang/rust/issues/137332). @@ -1288,76 +1257,106 @@ impl Step for PlainSourceTarball { "The GCC source code is not included due to unclear licensing implications\n" )); } - - // Copy the files normally - for item in &src_files { - builder.copy_link( - &builder.src.join(item), - &plain_dst_src.join(item), - FileType::Regular, - ); - } - - // Create the version file - builder.create(&plain_dst_src.join("version"), &builder.rust_version()); - - // Create the files containing git info, to ensure --version outputs the same. - let write_git_info = |info: Option<&Info>, path: &Path| { - if let Some(info) = info { - t!(std::fs::create_dir_all(path)); - channel::write_commit_hash_file(path, &info.sha); - channel::write_commit_info_file(path, info); - } - }; - write_git_info(builder.rust_info().info(), plain_dst_src); - write_git_info(builder.cargo_info.info(), &plain_dst_src.join("./src/tools/cargo")); - - if builder.config.dist_vendor { - builder.require_and_update_all_submodules(); - - // Vendor packages that are required by opt-dist to collect PGO profiles. - let pkgs_for_pgo_training = build_helper::LLVM_PGO_CRATES - .iter() - .chain(build_helper::RUSTC_PGO_CRATES) - .map(|pkg| { - let mut manifest_path = - builder.src.join("./src/tools/rustc-perf/collector/compile-benchmarks"); - manifest_path.push(pkg); - manifest_path.push("Cargo.toml"); - manifest_path - }); - - // Vendor all Cargo dependencies - let vendor = builder.ensure(Vendor { - sync_args: pkgs_for_pgo_training.collect(), - versioned_dirs: true, - root_dir: plain_dst_src.into(), - output_dir: VENDOR_DIR.into(), - }); - - let cargo_config_dir = plain_dst_src.join(".cargo"); - builder.create_dir(&cargo_config_dir); - builder.create(&cargo_config_dir.join("config.toml"), &vendor.config); - } - - // Delete extraneous directories - // FIXME: if we're managed by git, we should probably instead ask git if the given path - // is managed by it? - for entry in walkdir::WalkDir::new(tarball.image_dir()) - .follow_links(true) - .into_iter() - .filter_map(|e| e.ok()) - { - if entry.path().is_dir() && entry.path().file_name() == Some(OsStr::new("__pycache__")) - { - t!(fs::remove_dir_all(entry.path())); - } - } - tarball.bare() } } +fn prepare_source_tarball(builder: &Builder<'_>, name: &str, exclude_dirs: &[&str]) -> Tarball { + // NOTE: This is a strange component in a lot of ways. It uses `src` as the target, which + // means neither rustup nor rustup-toolchain-install-master know how to download it. + // It also contains symbolic links, unlike other any other dist tarball. + // It's used for distros building rustc from source in a pre-vendored environment. + let mut tarball = Tarball::new(builder, "rustc", name); + tarball.permit_symlinks(true); + let plain_dst_src = tarball.image_dir(); + + // This is the set of root paths which will become part of the source package + let src_files = [ + // tidy-alphabetical-start + ".gitmodules", + "CONTRIBUTING.md", + "COPYRIGHT", + "Cargo.lock", + "Cargo.toml", + "LICENSE-APACHE", + "LICENSE-MIT", + "README.md", + "RELEASES.md", + "REUSE.toml", + "bootstrap.example.toml", + "configure", + "license-metadata.json", + "package.json", + "x", + "x.ps1", + "x.py", + "yarn.lock", + // tidy-alphabetical-end + ]; + let src_dirs = ["src", "compiler", "library", "tests", "LICENSES"]; + + copy_src_dirs(builder, &builder.src, &src_dirs, exclude_dirs, plain_dst_src); + + // Copy the files normally + for item in &src_files { + builder.copy_link(&builder.src.join(item), &plain_dst_src.join(item), FileType::Regular); + } + + // Create the version file + builder.create(&plain_dst_src.join("version"), &builder.rust_version()); + + // Create the files containing git info, to ensure --version outputs the same. + let write_git_info = |info: Option<&Info>, path: &Path| { + if let Some(info) = info { + t!(std::fs::create_dir_all(path)); + channel::write_commit_hash_file(path, &info.sha); + channel::write_commit_info_file(path, info); + } + }; + write_git_info(builder.rust_info().info(), plain_dst_src); + write_git_info(builder.cargo_info.info(), &plain_dst_src.join("./src/tools/cargo")); + + if builder.config.dist_vendor { + builder.require_and_update_all_submodules(); + + // Vendor packages that are required by opt-dist to collect PGO profiles. + let pkgs_for_pgo_training = + build_helper::LLVM_PGO_CRATES.iter().chain(build_helper::RUSTC_PGO_CRATES).map(|pkg| { + let mut manifest_path = + builder.src.join("./src/tools/rustc-perf/collector/compile-benchmarks"); + manifest_path.push(pkg); + manifest_path.push("Cargo.toml"); + manifest_path + }); + + // Vendor all Cargo dependencies + let vendor = builder.ensure(Vendor { + sync_args: pkgs_for_pgo_training.collect(), + versioned_dirs: true, + root_dir: plain_dst_src.into(), + output_dir: VENDOR_DIR.into(), + }); + + let cargo_config_dir = plain_dst_src.join(".cargo"); + builder.create_dir(&cargo_config_dir); + builder.create(&cargo_config_dir.join("config.toml"), &vendor.config); + } + + // Delete extraneous directories + // FIXME: if we're managed by git, we should probably instead ask git if the given path + // is managed by it? + for entry in walkdir::WalkDir::new(tarball.image_dir()) + .follow_links(true) + .into_iter() + .filter_map(|e| e.ok()) + { + if entry.path().is_dir() && entry.path().file_name() == Some(OsStr::new("__pycache__")) { + t!(fs::remove_dir_all(entry.path())); + } + } + tarball +} + #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Cargo { pub build_compiler: Compiler, From 0d2a418798a030ebf72115e6c4624609f690b249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 12 Dec 2025 16:35:41 +0100 Subject: [PATCH 4/8] Add `PlainSourceTarballGpl` component that includes also GPL code --- src/bootstrap/src/core/build_steps/dist.rs | 37 ++++++++++++++++++- .../builder/cli_paths/snapshots/x_dist.snap | 3 ++ src/bootstrap/src/core/builder/mod.rs | 1 + 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index 24781083bfab..b9bf5ba61eed 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -1211,7 +1211,8 @@ impl Step for Src { } /// Tarball for people who want to build rustc and other components from the source. -/// Does not contain GPL code for licensing reasons. +/// Does not contain GPL code, which is separated into `PlainSourceTarballGpl` +/// for licensing reasons. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct PlainSourceTarball; @@ -1261,7 +1262,39 @@ impl Step for PlainSourceTarball { } } -fn prepare_source_tarball(builder: &Builder<'_>, name: &str, exclude_dirs: &[&str]) -> Tarball { +/// Tarball with *all* source code for source builds, including GPL-licensed code. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct PlainSourceTarballGpl; + +impl Step for PlainSourceTarballGpl { + /// Produces the location of the tarball generated + type Output = GeneratedTarball; + const IS_HOST: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.alias("rustc-src-gpl") + } + + fn is_default_step(builder: &Builder<'_>) -> bool { + builder.config.rust_dist_src + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure(PlainSourceTarballGpl); + } + + /// Creates the plain source tarball + fn run(self, builder: &Builder<'_>) -> GeneratedTarball { + let tarball = prepare_source_tarball(builder, "src-gpl", &[]); + tarball.bare() + } +} + +fn prepare_source_tarball<'a>( + builder: &'a Builder<'a>, + name: &str, + exclude_dirs: &[&str], +) -> Tarball<'a> { // NOTE: This is a strange component in a lot of ways. It uses `src` as the target, which // means neither rustup nor rustup-toolchain-install-master know how to download it. // It also contains symbolic links, unlike other any other dist tarball. diff --git a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_dist.snap b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_dist.snap index 2fc8ca143dd0..7fae0b24df94 100644 --- a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_dist.snap +++ b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_dist.snap @@ -29,6 +29,9 @@ expression: dist [Dist] dist::PlainSourceTarball targets: [x86_64-unknown-linux-gnu] - Set({dist::rustc-src}) +[Dist] dist::PlainSourceTarballGpl + targets: [x86_64-unknown-linux-gnu] + - Set({dist::rustc-src-gpl}) [Dist] dist::ReproducibleArtifacts targets: [x86_64-unknown-linux-gnu] - Set({dist::reproducible-artifacts}) diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 961d0cd855ae..a5d339511267 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -986,6 +986,7 @@ impl<'a> Builder<'a> { // and force us to rebuild tools after vendoring dependencies. // To work around this, create the Tarball after building all the tools. dist::PlainSourceTarball, + dist::PlainSourceTarballGpl, dist::BuildManifest, dist::ReproducibleArtifacts, dist::Gcc From f5aec79668a1c37bd75575eda5755e049f4e6f1b Mon Sep 17 00:00:00 2001 From: Josh Kaplan Date: Wed, 8 Oct 2025 17:53:52 -0400 Subject: [PATCH 5/8] Implement round-ties-to-even for Duration Debug for consistency with f64 --- library/core/src/time.rs | 28 +++++++++++++++++++++++++++- library/coretests/tests/time.rs | 10 +++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index 940129d68816..0f2160338db5 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -1316,7 +1316,33 @@ impl fmt::Debug for Duration { // normal floating point numbers. However, we only need to do work // when rounding up. This happens if the first digit of the // remaining ones is >= 5. - let integer_part = if fractional_part > 0 && fractional_part >= divisor * 5 { + let integer_part = if fractional_part > 0 && fractional_part == divisor * 5 { + let last_digit_is_odd = if pos > 0 { + (buf[pos - 1] - b'0') % 2 == 1 + } else { + // No fractional digits - check the integer part + (integer_part % 2) == 1 + }; + + if last_digit_is_odd { + let mut rev_pos = pos; + let mut carry = true; + while carry && rev_pos > 0 { + rev_pos -= 1; + + if buf[rev_pos] < b'9' { + buf[rev_pos] += 1; + carry = false; + } else { + buf[rev_pos] = b'0'; + } + } + + if carry { integer_part.checked_add(1) } else { Some(integer_part) } + } else { + Some(integer_part) + } + } else if fractional_part > 0 && fractional_part > divisor * 5 { // Round up the number contained in the buffer. We go through // the buffer backwards and keep track of the carry. let mut rev_pos = pos; diff --git a/library/coretests/tests/time.rs b/library/coretests/tests/time.rs index fb3c50f9bde9..ff80ff680943 100644 --- a/library/coretests/tests/time.rs +++ b/library/coretests/tests/time.rs @@ -439,7 +439,6 @@ fn debug_formatting_precision_two() { assert_eq!(format!("{:.2?}", Duration::new(4, 001_000_000)), "4.00s"); assert_eq!(format!("{:.2?}", Duration::new(2, 100_000_000)), "2.10s"); assert_eq!(format!("{:.2?}", Duration::new(2, 104_990_000)), "2.10s"); - assert_eq!(format!("{:.2?}", Duration::new(2, 105_000_000)), "2.11s"); assert_eq!(format!("{:.2?}", Duration::new(8, 999_999_999)), "9.00s"); } @@ -480,6 +479,15 @@ fn debug_formatting_precision_high() { assert_eq!(format!("{:.20?}", Duration::new(4, 001_000_000)), "4.00100000000000000000s"); } +#[test] +fn debug_formatting_round_to_even() { + assert_eq!(format!("{:.0?}", Duration::new(1, 500_000_000)), "2s"); + assert_eq!(format!("{:.0?}", Duration::new(2, 500_000_000)), "2s"); + assert_eq!(format!("{:.0?}", Duration::new(0, 1_500_000)), "2ms"); + assert_eq!(format!("{:.0?}", Duration::new(0, 2_500_000)), "2ms"); + assert_eq!(format!("{:.2?}", Duration::new(2, 105_000_000)), "2.10s"); +} + #[test] fn duration_const() { // test that the methods of `Duration` are usable in a const context From 95302b25377bf678d8db964eff20f7acd2044991 Mon Sep 17 00:00:00 2001 From: Josh Kaplan Date: Sun, 12 Oct 2025 19:09:32 -0400 Subject: [PATCH 6/8] Update comment on duration rounding behavior --- library/core/src/time.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index 0f2160338db5..8266a2a63bc0 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -1315,7 +1315,8 @@ impl fmt::Debug for Duration { // need to perform rounding to match the semantics of printing // normal floating point numbers. However, we only need to do work // when rounding up. This happens if the first digit of the - // remaining ones is >= 5. + // remaining ones is > 5. When the first digit is exactly 5, rounding + // follows IEEE-754 round-ties-to-even semantics. let integer_part = if fractional_part > 0 && fractional_part == divisor * 5 { let last_digit_is_odd = if pos > 0 { (buf[pos - 1] - b'0') % 2 == 1 From c74aa910ca9ab0f4b6662600a031d57689f11fae Mon Sep 17 00:00:00 2001 From: Josh Kaplan Date: Mon, 22 Dec 2025 09:20:56 -0500 Subject: [PATCH 7/8] Merged Duration Debug tie rounding with existing rounding logic --- library/core/src/time.rs | 64 ++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index 8266a2a63bc0..18792659d974 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -1315,9 +1315,12 @@ impl fmt::Debug for Duration { // need to perform rounding to match the semantics of printing // normal floating point numbers. However, we only need to do work // when rounding up. This happens if the first digit of the - // remaining ones is > 5. When the first digit is exactly 5, rounding - // follows IEEE-754 round-ties-to-even semantics. - let integer_part = if fractional_part > 0 && fractional_part == divisor * 5 { + // remaining ones is >= 5. When the first digit is exactly 5, rounding + // follows IEEE-754 round-ties-to-even semantics: we only round up + // if the last written digit is odd. + let integer_part = if fractional_part > 0 && fractional_part >= divisor * 5 { + // For ties (fractional_part == divisor * 5), only round up if last digit is odd + let is_tie = fractional_part == divisor * 5; let last_digit_is_odd = if pos > 0 { (buf[pos - 1] - b'0') % 2 == 1 } else { @@ -1325,12 +1328,20 @@ impl fmt::Debug for Duration { (integer_part % 2) == 1 }; - if last_digit_is_odd { + if is_tie && !last_digit_is_odd { + Some(integer_part) + } else { + // Round up the number contained in the buffer. We go through + // the buffer backwards and keep track of the carry. let mut rev_pos = pos; let mut carry = true; while carry && rev_pos > 0 { rev_pos -= 1; + // If the digit in the buffer is not '9', we just need to + // increment it and can stop then (since we don't have a + // carry anymore). Otherwise, we set it to '0' (overflow) + // and continue. if buf[rev_pos] < b'9' { buf[rev_pos] += 1; carry = false; @@ -1339,43 +1350,20 @@ impl fmt::Debug for Duration { } } - if carry { integer_part.checked_add(1) } else { Some(integer_part) } - } else { - Some(integer_part) - } - } else if fractional_part > 0 && fractional_part > divisor * 5 { - // Round up the number contained in the buffer. We go through - // the buffer backwards and keep track of the carry. - let mut rev_pos = pos; - let mut carry = true; - while carry && rev_pos > 0 { - rev_pos -= 1; - - // If the digit in the buffer is not '9', we just need to - // increment it and can stop then (since we don't have a - // carry anymore). Otherwise, we set it to '0' (overflow) - // and continue. - if buf[rev_pos] < b'9' { - buf[rev_pos] += 1; - carry = false; + // If we still have the carry bit set, that means that we set + // the whole buffer to '0's and need to increment the integer + // part. + if carry { + // If `integer_part == u64::MAX` and precision < 9, any + // carry of the overflow during rounding of the + // `fractional_part` into the `integer_part` will cause the + // `integer_part` itself to overflow. Avoid this by using an + // `Option`, with `None` representing `u64::MAX + 1`. + integer_part.checked_add(1) } else { - buf[rev_pos] = b'0'; + Some(integer_part) } } - - // If we still have the carry bit set, that means that we set - // the whole buffer to '0's and need to increment the integer - // part. - if carry { - // If `integer_part == u64::MAX` and precision < 9, any - // carry of the overflow during rounding of the - // `fractional_part` into the `integer_part` will cause the - // `integer_part` itself to overflow. Avoid this by using an - // `Option`, with `None` representing `u64::MAX + 1`. - integer_part.checked_add(1) - } else { - Some(integer_part) - } } else { Some(integer_part) }; From d2290c425314a391d4b8eef846d0d0633d6b296d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 28 Dec 2025 16:24:55 +0100 Subject: [PATCH 8/8] fix ManuallyDrop::into_inner aliasing (Miri) issues --- library/core/src/mem/manually_drop.rs | 4 +++- src/tools/miri/tests/pass/issues/issue-miri-4793.rs | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 src/tools/miri/tests/pass/issues/issue-miri-4793.rs diff --git a/library/core/src/mem/manually_drop.rs b/library/core/src/mem/manually_drop.rs index 74f917bcf1cc..ca008a82ee6f 100644 --- a/library/core/src/mem/manually_drop.rs +++ b/library/core/src/mem/manually_drop.rs @@ -200,7 +200,9 @@ impl ManuallyDrop { #[rustc_const_stable(feature = "const_manually_drop", since = "1.32.0")] #[inline(always)] pub const fn into_inner(slot: ManuallyDrop) -> T { - slot.value.into_inner() + // Cannot use `MaybeDangling::into_inner` as that does not yet have the desired semantics. + // SAFETY: We know this is a valid `T`. `slot` will not be dropped. + unsafe { (&raw const slot).cast::().read() } } /// Takes the value from the `ManuallyDrop` container out. diff --git a/src/tools/miri/tests/pass/issues/issue-miri-4793.rs b/src/tools/miri/tests/pass/issues/issue-miri-4793.rs new file mode 100644 index 000000000000..ccbff462f995 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-miri-4793.rs @@ -0,0 +1,3 @@ +fn main() { + let _ = std::panic::catch_unwind(|| Box::::from("...")); +}