diff --git a/.github/renovate.json5 b/.github/renovate.json5 index e0ddb3670fa83..0ab5f5eeb57a3 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -48,8 +48,8 @@ "groupName": "Cargo lock file maintenance", "commitMessageAction": "Compiler and tools lock file update", // Renovate merges all matching rules, so the lockfiles rules below - // also inherits this note and asks Triagebot for a dep-bumps reviewer. - "prBodyNotes": ["r? dep-bumps"] + // also inherits these notes and asks Triagebot for a dep-bumps reviewer. + "prBodyNotes": ["r? dep-bumps", "⚠️ Supply chain security safety: wait 3 days until the last commit to this PR before running `bors try` or `bors r+`"] }, { // Update library/Cargo.lock in a dedicated PR. diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index 0528fee35031c..8d9b26cd18423 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -621,14 +621,10 @@ pub trait Machine<'tcx>: Sized { interp_ok(ReturnAction::Normal) } - /// Called immediately after an "immediate" local variable is read in a given frame + /// Called immediately after an "immediate" local variable is read /// (i.e., this is called for reads that do not end up accessing addressable memory). #[inline(always)] - fn after_local_read( - _ecx: &InterpCx<'tcx, Self>, - _frame: &Frame<'tcx, Self::Provenance, Self::FrameExtra>, - _local: mir::Local, - ) -> InterpResult<'tcx> { + fn after_local_read(_ecx: &InterpCx<'tcx, Self>, _local: mir::Local) -> InterpResult<'tcx> { interp_ok(()) } diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 96152737d3d1e..03a621812eb57 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -135,10 +135,10 @@ pub struct Memory<'tcx, M: Machine<'tcx>> { // FIXME: this should not be public, but interning currently needs access to it pub(super) dead_alloc_map: FxIndexMap, - /// This stores whether we are currently doing reads purely for the purpose of validation. - /// Those reads do not trigger the machine's hooks for memory reads. + /// This stores whether we are currently doing reads/writes that aren't "real". + /// Those accesses do not trigger the machine's hooks. /// Needless to say, this must only be set with great care! - validation_in_progress: Cell, + ghost_mode: Cell, } /// A reference to some allocation that was already bounds-checked for the given region @@ -166,7 +166,7 @@ impl<'tcx, M: Machine<'tcx>> Memory<'tcx, M> { extra_fn_ptr_map: FxIndexMap::default(), va_list_map: FxIndexMap::default(), dead_alloc_map: FxIndexMap::default(), - validation_in_progress: Cell::new(false), + ghost_mode: Cell::new(false), } } @@ -768,7 +768,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // We want to call the hook on *all* accesses that involve an AllocId, including zero-sized // accesses. That means we cannot rely on the closure above or the `Some` branch below. We // do this after `check_and_deref_ptr` to ensure some basic sanity has already been checked. - if !self.memory.validation_in_progress.get() { + if !self.memory.ghost_mode.get() { if let Ok((alloc_id, ..)) = self.ptr_try_get_alloc_id(ptr, size_i64) { M::before_alloc_access(self.tcx, &self.machine, alloc_id)?; } @@ -776,7 +776,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { if let Some((alloc_id, offset, prov, alloc)) = ptr_and_alloc { let range = alloc_range(offset, size); - if !self.memory.validation_in_progress.get() { + if !self.memory.ghost_mode.get() { M::before_memory_read( self.tcx, &self.machine, @@ -856,7 +856,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ) -> InterpResult<'tcx, Option>> { let tcx = self.tcx; - let validation_in_progress = self.memory.validation_in_progress.get(); + let validation_in_progress = self.memory.ghost_mode.get(); let size_i64 = i64::try_from(size.bytes()).unwrap(); // it would be an error to even ask for more than isize::MAX bytes let ptr_and_alloc = Self::check_and_deref_ptr( @@ -1204,48 +1204,44 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { result } - /// Runs the closure in "validation" mode, which means the machine's memory read hooks will be + /// Runs the closure in "ghost" mode, which means the machine's memory read hooks will be /// suppressed. Needless to say, this must only be set with great care! Cannot be nested. /// /// We do this so Miri's allocation access tracking does not show the validation - /// reads as spurious accesses. - pub fn run_for_validation_mut(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { + /// reads as spurious accesses as those aren't "real" reads. Also useful for debuggers + /// that want to just display the Miri machine state. + pub fn ghost_run_mut(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { // This deliberately uses `==` on `bool` to follow the pattern // `assert!(val.replace(new) == old)`. - assert!( - self.memory.validation_in_progress.replace(true) == false, - "`validation_in_progress` was already set" - ); + assert!(self.memory.ghost_mode.replace(true) == false, "`ghost_mode` was already set"); let res = f(self); assert!( - self.memory.validation_in_progress.replace(false) == true, - "`validation_in_progress` was unset by someone else" + self.memory.ghost_mode.replace(false) == true, + "`ghost_mode` was unset by someone else" ); res } - /// Runs the closure in "validation" mode, which means the machine's memory read hooks will be + /// Runs the closure in "ghost" mode, which means the machine's memory read hooks will be /// suppressed. Needless to say, this must only be set with great care! Cannot be nested. /// /// We do this so Miri's allocation access tracking does not show the validation - /// reads as spurious accesses. - pub fn run_for_validation_ref(&self, f: impl FnOnce(&Self) -> R) -> R { + /// reads as spurious accesses as those aren't "real" reads. Also useful for debuggers + /// that want to just display the Miri machine state. + pub fn ghost_run(&self, f: impl FnOnce(&Self) -> R) -> R { // This deliberately uses `==` on `bool` to follow the pattern // `assert!(val.replace(new) == old)`. - assert!( - self.memory.validation_in_progress.replace(true) == false, - "`validation_in_progress` was already set" - ); + assert!(self.memory.ghost_mode.replace(true) == false, "`ghost_mode` was already set"); let res = f(self); assert!( - self.memory.validation_in_progress.replace(false) == true, - "`validation_in_progress` was unset by someone else" + self.memory.ghost_mode.replace(false) == true, + "`ghost_mode` was unset by someone else" ); res } pub(super) fn validation_in_progress(&self) -> bool { - self.memory.validation_in_progress.get() + self.memory.ghost_mode.get() } } @@ -1516,7 +1512,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { }; let src_alloc = self.get_alloc_raw(src_alloc_id)?; let src_range = alloc_range(src_offset, size); - assert!(!self.memory.validation_in_progress.get(), "we can't be copying during validation"); + assert!(!self.memory.ghost_mode.get(), "we can't be copying during validation"); // Trigger read hook. // For the overlapping case, it is crucial that we trigger the read hook diff --git a/compiler/rustc_const_eval/src/interpret/operand.rs b/compiler/rustc_const_eval/src/interpret/operand.rs index 4bafea98cd569..81f26cd7e5479 100644 --- a/compiler/rustc_const_eval/src/interpret/operand.rs +++ b/compiler/rustc_const_eval/src/interpret/operand.rs @@ -722,32 +722,38 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { interp_ok(s) } - /// Read from a local of the current frame. Convenience method for [`InterpCx::local_at_frame_to_op`]. + /// Read from a local of a current frame. + /// Will not access memory, instead an indirect `Operand` is returned. pub fn local_to_op( &self, local: mir::Local, layout: Option>, ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - self.local_at_frame_to_op(self.frame(), local, layout) + let frame = self.frame(); + let layout = self.layout_of_local(frame, local, layout)?; + let op = *frame.locals[local].access()?; + if matches!(op, Operand::Immediate(_)) { + assert!(!layout.is_unsized()); + if !self.validation_in_progress() { + M::after_local_read(self, local)?; + } + } + interp_ok(OpTy { op, layout }) } - /// Read from a local of a given frame. - /// Will not access memory, instead an indirect `Operand` is returned. + /// Tools like Priroda and [Aquascope](https://github.com/cognitive-engineering-lab/aquascope/) + /// need to access any local without triggering any access hook, since these are not actual + /// AM-level accesses. Do not call this from inside the interpreter! /// - /// This is public because it is used by [Aquascope](https://github.com/cognitive-engineering-lab/aquascope/) - /// to get an OpTy from a local. - pub fn local_at_frame_to_op( + /// Remember to use `ghost_run` when accessing memory for such purposes, to suppress + /// the access hooks for that as well. + pub fn ghost_local_in_frame_to_op( &self, frame: &Frame<'tcx, M::Provenance, M::FrameExtra>, local: mir::Local, - layout: Option>, ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - let layout = self.layout_of_local(frame, local, layout)?; + let layout = self.layout_of_local(frame, local, None)?; let op = *frame.locals[local].access()?; - if matches!(op, Operand::Immediate(_)) { - assert!(!layout.is_unsized()); - } - M::after_local_read(self, frame, local)?; interp_ok(OpTy { op, layout }) } diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs index d291f1f6fdcbc..5686413915ca5 100644 --- a/compiler/rustc_const_eval/src/interpret/stack.rs +++ b/compiler/rustc_const_eval/src/interpret/stack.rs @@ -90,7 +90,7 @@ pub struct Frame<'tcx, Prov: Provenance = CtfeProvenance, Extra = ()> { /// can either directly contain `Scalar` or refer to some part of an `Allocation`. /// /// Do *not* access this directly; always go through the machine hook! - pub locals: IndexVec>, + pub(super) locals: IndexVec>, /// The complete variable argument list of this frame. Its elements must be dropped when the /// frame is popped. @@ -168,8 +168,9 @@ impl<'tcx, Prov: Provenance> LocalState<'tcx, Prov> { /// This is a hack because Miri needs a way to visit all the provenance in a `LocalState` /// without having a layout or `TyCtxt` available, and we want to keep the `Operand` type - /// private. - pub fn as_mplace_or_imm( + /// private. Does not count as a read of the local for the AM! It's a "ghost" read, like for + /// validation or similar purposes. + pub fn as_mplace_or_imm_ghost( &self, ) -> Option>, MemPlaceMeta), Immediate>> { match self.value { @@ -293,6 +294,10 @@ impl<'tcx, Prov: Provenance, Extra> Frame<'tcx, Prov, Extra> { self.return_cont } + pub fn locals(&self) -> &IndexVec> { + &self.locals + } + /// Return the `SourceInfo` of the current instruction. pub fn current_source_info(&self) -> Option<&mir::SourceInfo> { self.loc.left().map(|loc| self.body.source_info(loc)) diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index 5f0799c70aa0b..2cd4caf5ba250 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -1611,7 +1611,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { trace!("validate_place_internal: {:?}, {:?}", *val, val.layout.ty); // Run the visitor. - self.run_for_validation_mut(|ecx| { + self.ghost_run_mut(|ecx| { let reset_padding = reset_provenance_and_padding && { // Check if `val` is actually stored in memory. If not, padding is not even // represented and we need not reset it. diff --git a/library/Cargo.lock b/library/Cargo.lock index a58d52d040b6c..0f1c9933d5475 100644 --- a/library/Cargo.lock +++ b/library/Cargo.lock @@ -182,9 +182,9 @@ dependencies = [ [[package]] name = "moto-rt" -version = "0.16.3" +version = "0.17.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcd8eb21b606833bc3a8c1c343bc75eb1999b288b2c2d515ae872e9e2f153187" +checksum = "9f3c01b588c6d37e3f4f065712c4f33e12fad17ad4152a70e8346991e2bbe92e" dependencies = [ "rustc-std-workspace-alloc", "rustc-std-workspace-core", diff --git a/library/core/src/num/nonzero.rs b/library/core/src/num/nonzero.rs index e747ac591a3e2..d2acaf837c761 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -1561,7 +1561,10 @@ macro_rules! nonzero_integer_signedness_dependent_impls { without modifying the original"] #[inline] pub const fn div_ceil(self, rhs: Self) -> Self { - let v = self.get().div_ceil(rhs.get()); + // An implementation of the function without calculating the remainder. + // It is better than the implementation for normal integers, but it can only + // be used here because of the possibility to subtract by one without overflow. + let v = (self.get() - 1) / rhs.get() + 1; // SAFETY: ceiled division of two positive integers can never be zero. unsafe { Self::new_unchecked(v) } } diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 51f307e2e279e..a98ef99b9d7bb 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -515,8 +515,13 @@ macro_rules! uint_impl { /// /// # Panics /// - /// This function will panic if `n` is greater than or equal to the number of - /// bits in `self`. + /// ## Overflow behavior + /// + /// If overflow checks are enabled (default in debug mode), this function will panic if `n` + /// is greater than or equal to the number of bits in `self`. If overflow checks are + /// disabled (default in release mode), there is no panic; instead, the value is shifted + /// by `n % Self::BITS`. + // FIXME(wrapping_funnel_shifts): link to `wrapping_funnel_shl` when stable. /// /// # Examples /// @@ -543,21 +548,31 @@ macro_rules! uint_impl { /// /// ```should_panic /// #![feature(funnel_shifts)] + /// # #![feature(cfg_overflow_checks)] + /// # #[cfg(overflow_checks)] { /// #[doc = concat!("let a = ", stringify!($SelfT), "::MAX;")] /// // Okay #[doc = concat!("let _ = a.rotate_left(", stringify!($SelfT), "::BITS);")] - /// // Panics + /// // Panics (only when overflow checks are enabled) #[doc = concat!("let _ = a.funnel_shl(a, ", stringify!($SelfT), "::BITS);")] + /// # } + /// # #[cfg(not(overflow_checks))] panic!("fulfill should_panic"); /// ``` #[rustc_const_unstable(feature = "funnel_shifts", issue = "145686")] #[unstable(feature = "funnel_shifts", issue = "145686")] #[must_use = "this returns the result of the operation, without modifying the original"] #[inline(always)] + #[rustc_inherit_overflow_checks] pub const fn funnel_shl(self, right: Self, n: u32) -> Self { - assert!(n < Self::BITS, "attempt to funnel shift left with overflow"); - // SAFETY: just checked that `shift` is in-range - unsafe { self.unchecked_funnel_shl(right, n) } + if intrinsics::overflow_checks() { + assert!(n < Self::BITS, "attempt to funnel shift left with overflow"); + } + // SAFETY: `n` is wrapped to within range + unsafe { + let n = n & (Self::BITS - 1); + self.unchecked_funnel_shl(right, n) + } } /// Performs a right funnel shift. @@ -571,8 +586,13 @@ macro_rules! uint_impl { /// /// # Panics /// - /// This function will panic if `n` is greater than or equal to the number of - /// bits in `self`. + /// ## Overflow behavior + /// + /// If overflow checks are enabled (default in debug mode), this function will panic if `n` + /// is greater than or equal to the number of bits in `self`. If overflow checks are + /// disabled (default in release mode), there is no panic; instead, the value is shifted + /// by `n % Self::BITS`. + // FIXME(wrapping_funnel_shifts): link to `wrapping_funnel_shr` when stable. /// /// # Examples /// @@ -599,21 +619,31 @@ macro_rules! uint_impl { /// /// ```should_panic /// #![feature(funnel_shifts)] + /// # #![feature(cfg_overflow_checks)] + /// # #[cfg(overflow_checks)] { /// #[doc = concat!("let a = ", stringify!($SelfT), "::MAX;")] /// // Okay #[doc = concat!("let _ = a.rotate_right(", stringify!($SelfT), "::BITS);")] - /// // Panics + /// // Panics (only when overflow checks are enabled) #[doc = concat!("let _ = a.funnel_shr(a, ", stringify!($SelfT), "::BITS);")] + /// # } + /// # #[cfg(not(overflow_checks))] panic!("fulfill should_panic"); /// ``` #[rustc_const_unstable(feature = "funnel_shifts", issue = "145686")] #[unstable(feature = "funnel_shifts", issue = "145686")] #[must_use = "this returns the result of the operation, without modifying the original"] #[inline(always)] + #[rustc_inherit_overflow_checks] pub const fn funnel_shr(self, right: Self, n: u32) -> Self { - assert!(n < Self::BITS, "attempt to funnel shift right with overflow"); - // SAFETY: just checked that `shift` is in-range - unsafe { self.unchecked_funnel_shr(right, n) } + if intrinsics::overflow_checks() { + assert!(n < Self::BITS, "attempt to funnel shift right with overflow"); + } + // SAFETY: `n` is wrapped to within range + unsafe { + let n = n & (Self::BITS - 1); + self.unchecked_funnel_shr(right, n) + } } /// Unchecked funnel shift left. diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index de90ce42c1529..c993c947929cd 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -11,6 +11,7 @@ #![feature(borrowed_buf_init)] #![feature(bstr)] #![feature(casefold)] +#![feature(cfg_overflow_checks)] #![feature(cfg_target_has_reliable_f16_f128)] #![feature(char_internals)] #![feature(clone_to_uninit)] diff --git a/library/coretests/tests/num/uint_macros.rs b/library/coretests/tests/num/uint_macros.rs index 8189776807915..4dfcb1b8b4688 100644 --- a/library/coretests/tests/num/uint_macros.rs +++ b/library/coretests/tests/num/uint_macros.rs @@ -216,17 +216,31 @@ macro_rules! uint_module { } #[test] + #[cfg(overflow_checks)] #[should_panic = "attempt to funnel shift left with overflow"] fn test_funnel_shl_overflow() { let _ = <$T>::funnel_shl(A, B, $T::BITS); } #[test] + #[cfg(overflow_checks)] #[should_panic = "attempt to funnel shift right with overflow"] fn test_funnel_shr_overflow() { let _ = <$T>::funnel_shr(A, B, $T::BITS); } + #[test] + #[cfg(not(overflow_checks))] + fn test_funnel_shl_overflow() { + let _ = <$T>::funnel_shl(A, B, $T::BITS); + } + + #[test] + #[cfg(not(overflow_checks))] + fn test_funnel_shr_overflow() { + let _ = <$T>::funnel_shr(A, B, $T::BITS); + } + #[test] fn test_funnel_shifts_runtime() { for i in 0..$T::BITS - 1 { diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index f216d8a1d2874..770563212b7ec 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -71,7 +71,7 @@ fortanix-sgx-abi = { version = "0.6.1", features = [ ], public = true } [target.'cfg(target_os = "motor")'.dependencies] -moto-rt = { version = "0.16", features = ['rustc-dep-of-std'], public = true } +moto-rt = { version = "0.17", features = ['rustc-dep-of-std'], public = true } [target.'cfg(target_os = "hermit")'.dependencies] hermit-abi = { version = "0.5.0", features = [ diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 1e46ddb99e010..3cc375b7290da 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -1578,6 +1578,63 @@ impl Dir { .map(|inner| Self { inner }) } + /// Attempts to open a directory at `path` according to `opts`. + /// + /// This function opens a directory. To open a file instead, see [`File::open`]. + /// + /// # Errors + /// + /// This function will return an error if `path` does not point to an existing directory. + /// Other errors may also be returned according to [`OpenOptions::open`]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::{fs::{Dir, OpenOptions}, io}; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::open_with("foo", &OpenOptions::new().read(true))?; + /// let mut f = dir.open_file("bar.txt")?; + /// let contents = io::read_to_string(f)?; + /// assert_eq!(contents, "Hello, world!"); + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn open_with>(path: P, opts: &OpenOptions) -> io::Result { + fs_imp::Dir::open(path.as_ref(), &opts.0).map(|inner| Self { inner }) + } + + /// Attempts to open a directory at `path` with the minimum permissions for traversal. + /// + /// The permissions requested by this function are guaranteed to be sufficient to open a child + /// file or folder, but not necessarily to list all children. + /// + /// # Errors + /// + /// This function may return an error according to [`OpenOptions::open`]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::{fs::Dir, io}; + /// + /// fn main() -> std::io::Result<()> { + /// let foo = Dir::open_for_traversal("foo")?; + /// let foobar = foo.open_dir("bar")?; + /// let mut foobarbaz = foobar.open_file("baz")?; + /// let contents = io::read_to_string(foobarbaz)?; + /// assert_eq!(contents, "Hello, world!"); + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn open_for_traversal>(path: P) -> io::Result { + fs_imp::Dir::open_for_traversal(path.as_ref()).map(|inner| Self { inner }) + } + /// Queries metadata about the underlying directory. /// /// # Examples @@ -1719,6 +1776,99 @@ impl Dir { ) -> io::Result<()> { self.inner.rename(from.as_ref(), &to_dir.inner, to.as_ref()) } + + /// Attempts to create a directory relative to this directory. + /// + /// This function interprets `path` relative to the directory provided by `self`. To create a directory + /// relative to the current working directory, or at an absolute path, see + /// [`fs::create_dir`][crate::fs::create_dir]. + #[unstable(feature = "dirfd", issue = "120426")] + pub fn create_dir>(&self, path: P) -> io::Result<()> { + self.inner.create_dir(path.as_ref()) + } + + /// Attempts to open a directory in read-only mode relative to this directory. + /// + /// This function interprets `path` relative to the directory provided by `self`. To open a directory + /// relative to the current working directory, or at an absolute path, see [`Dir::open`]. + /// + /// # Errors + /// + /// This function will return an error if `path` does not point to an existing directory. + /// Other errors may also be returned according to [`OpenOptions::open`]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::{fs::Dir}; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::open("foo")?; + /// let foobar = dir.open_dir("bar")?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn open_dir>(&self, path: P) -> io::Result { + self.inner + .open_dir(path.as_ref(), &OpenOptions::new().read(true).0) + .map(|inner| Self { inner }) + } + + /// Attempts to open a directory relative to this directory according to `opts`. + /// + /// This function interprets `path` relative to the directory provided by `self`. To open a directory + /// relative to the current working directory, or at an absolute path, see [`Dir::open`]. + /// + /// # Errors + /// + /// This function will return errors according to [`OpenOptions::open`]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::fs::{Dir, OpenOptions}; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::open("foo")?; + /// let foobar_w = dir.open_dir_with("bar", &OpenOptions::new().write(true))?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn open_dir_with>(&self, path: P, opts: &OpenOptions) -> io::Result { + self.inner.open_dir(path.as_ref(), &opts.0).map(|inner| Self { inner }) + } + + /// Attempts to remove a directory relative to this directory. + /// + /// This function interprets `path` relative to the directory provided by `self`. To remove a directory + /// relative to the current working directory, or at an absolute path, see + /// [`fs::remove_dir`][crate::fs::remove_dir]. + /// + /// # Errors + /// + /// This function will return an error if `path` does not point to an existing directory. + /// Other errors may also be returned according to [`OpenOptions::open`]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::{fs::Dir}; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::open("foo")?; + /// dir.remove_dir("bar")?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn remove_dir>(&self, path: P) -> io::Result<()> { + self.inner.remove_dir(path.as_ref()) + } } impl AsInner for Dir { diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 3a6c04146922a..075814b379027 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -2756,3 +2756,38 @@ fn test_dir_rename_file() { check!(f.read_exact(&mut buf)); assert_eq!(b"bar", &buf); } + +#[test] +fn test_dir_remove_dir() { + let tmpdir = tmpdir(); + check!(fs::create_dir(tmpdir.join("foo"))); + let dir = check!(Dir::open(tmpdir.path())); + check!(dir.remove_dir("foo")); + assert!(!matches!(exists(tmpdir.join("foo")), Ok(true))); +} + +#[test] +fn test_dir_create_dir() { + let tmpdir = tmpdir(); + let dir = check!(Dir::open(tmpdir.path())); + check!(dir.create_dir("foo")); + check!(Dir::open(tmpdir.join("foo"))); +} + +#[test] +fn test_dir_open_dir() { + let tmpdir = tmpdir(); + let dir1 = check!(Dir::open(tmpdir.path())); + check!(dir1.create_dir("foo")); + let dir2 = check!(Dir::open(tmpdir.path().join("foo"))); + let mut f = + check!(dir2.open_file_with("bar.txt", &OpenOptions::new().create(true).write(true))); + check!(f.write(b"baz")); + check!(f.flush()); + drop(f); + let dir3 = check!(dir1.open_dir("foo")); + let mut f = check!(dir3.open_file("bar.txt")); + let mut buf = [0u8; 3]; + check!(f.read_exact(&mut buf)); + assert_eq!(b"baz", &buf); +} diff --git a/library/std/src/process/tests.rs b/library/std/src/process/tests.rs index 9fe14b2e468a5..35ce30f1146e4 100644 --- a/library/std/src/process/tests.rs +++ b/library/std/src/process/tests.rs @@ -16,14 +16,12 @@ fn known_command() -> Command { } } -#[cfg(target_os = "android")] -fn shell_cmd() -> Command { - Command::new("/system/bin/sh") -} - -#[cfg(not(target_os = "android"))] fn shell_cmd() -> Command { - Command::new("/bin/sh") + if cfg!(target_os = "android") || cfg!(target_os = "motor") { + Command::new("/system/bin/sh") + } else { + Command::new("/bin/sh") + } } #[test] diff --git a/library/std/src/sys/fd/motor.rs b/library/std/src/sys/fd/motor.rs index b81072bcb50c7..e41f176ca982e 100644 --- a/library/std/src/sys/fd/motor.rs +++ b/library/std/src/sys/fd/motor.rs @@ -2,7 +2,7 @@ use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read}; use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; -use crate::sys::{AsInner, FromInner, IntoInner, map_motor_error}; +use crate::sys::{AsInner, FromInner, IntoInner, io_slices, io_slices_mut, map_motor_error}; #[derive(Debug)] pub struct FileDesc(OwnedFd); @@ -17,7 +17,8 @@ impl FileDesc { } pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { - io::default_read_vectored(|b| self.read(b), bufs) + moto_rt::fs::read_vectored(self.as_raw_fd(), &mut io_slices_mut(bufs)) + .map_err(map_motor_error) } pub fn read_to_end(&self, buf: &mut Vec) -> io::Result { @@ -30,16 +31,16 @@ impl FileDesc { } pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { - crate::io::default_write_vectored(|b| self.write(b), bufs) + moto_rt::fs::write_vectored(self.as_raw_fd(), &io_slices(bufs)).map_err(map_motor_error) } pub fn is_write_vectored(&self) -> bool { - false + true } #[inline] pub fn is_read_vectored(&self) -> bool { - false + true } pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { diff --git a/library/std/src/sys/fs/common.rs b/library/std/src/sys/fs/common.rs index edc31d21ca1fa..17b98a4506544 100644 --- a/library/std/src/sys/fs/common.rs +++ b/library/std/src/sys/fs/common.rs @@ -1,6 +1,6 @@ #![allow(dead_code)] // not used on all platforms -use crate::fs::{remove_file, rename}; +use crate::fs::{create_dir, remove_dir, remove_file, rename}; use crate::io::{self, Error, ErrorKind}; use crate::path::{Path, PathBuf}; use crate::sys::IntoInner; @@ -71,6 +71,12 @@ impl Dir { path.canonicalize().map(|path| Self { path }) } + pub fn open_for_traversal(path: &Path) -> io::Result { + let mut opts = OpenOptions::new(); + opts.read(true); + Self::open(path, &opts) + } + pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result { File::open(&self.path.join(path), opts) } @@ -86,6 +92,18 @@ impl Dir { pub fn rename(&self, from: &Path, to_dir: &Self, to: &Path) -> io::Result<()> { rename(self.path.join(from), to_dir.path.join(to)) } + + pub fn create_dir(&self, path: &Path) -> io::Result<()> { + create_dir(self.path.join(path)) + } + + pub fn open_dir(&self, path: &Path, opts: &OpenOptions) -> io::Result { + Self::open(&self.path.join(path), opts) + } + + pub fn remove_dir(&self, path: &Path) -> io::Result<()> { + remove_dir(path) + } } impl fmt::Debug for Dir { diff --git a/library/std/src/sys/fs/motor.rs b/library/std/src/sys/fs/motor.rs index a76f64a47c24a..938d1537790a1 100644 --- a/library/std/src/sys/fs/motor.rs +++ b/library/std/src/sys/fs/motor.rs @@ -6,7 +6,21 @@ use crate::path::{Path, PathBuf}; use crate::sys::fd::FileDesc; pub use crate::sys::fs::common::{Dir, exists}; use crate::sys::time::SystemTime; -use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner, map_motor_error, unsupported}; +use crate::sys::{ + AsInner, AsInnerMut, FromInner, IntoInner, io_slices, io_slices_mut, map_motor_error, + unsupported, +}; + +fn try_lock(fd: RawFd, operation: u8) -> Result<(), crate::fs::TryLockError> { + moto_rt::fs::file_lock(fd, operation).map_err(|err| { + let err = map_motor_error(err); + if err.kind() == io::ErrorKind::WouldBlock { + crate::fs::TryLockError::WouldBlock + } else { + crate::fs::TryLockError::Error(err) + } + }) +} #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct FileType { @@ -196,11 +210,12 @@ impl File { } pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { - crate::io::default_read_vectored(|b| self.read(b), bufs) + moto_rt::fs::read_vectored(self.as_raw_fd(), &mut io_slices_mut(bufs)) + .map_err(map_motor_error) } pub fn is_read_vectored(&self) -> bool { - false + true } pub fn read_buf(&self, cursor: BorrowedCursor<'_, u8>) -> io::Result<()> { @@ -212,11 +227,11 @@ impl File { } pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { - crate::io::default_write_vectored(|b| self.write(b), bufs) + moto_rt::fs::write_vectored(self.as_raw_fd(), &io_slices(bufs)).map_err(map_motor_error) } pub fn is_write_vectored(&self) -> bool { - false + true } pub fn flush(&self) -> io::Result<()> { @@ -259,23 +274,24 @@ impl File { } pub fn lock(&self) -> io::Result<()> { - unsupported() + moto_rt::fs::file_lock(self.as_raw_fd(), moto_rt::fs::LOCK_EXCLUSIVE) + .map_err(map_motor_error) } pub fn lock_shared(&self) -> io::Result<()> { - unsupported() + moto_rt::fs::file_lock(self.as_raw_fd(), moto_rt::fs::LOCK_SHARED).map_err(map_motor_error) } pub fn try_lock(&self) -> Result<(), crate::fs::TryLockError> { - Err(crate::fs::TryLockError::Error(io::Error::from(io::ErrorKind::Unsupported))) + try_lock(self.as_raw_fd(), moto_rt::fs::TRY_LOCK_EXCLUSIVE) } pub fn try_lock_shared(&self) -> Result<(), crate::fs::TryLockError> { - Err(crate::fs::TryLockError::Error(io::Error::from(io::ErrorKind::Unsupported))) + try_lock(self.as_raw_fd(), moto_rt::fs::TRY_LOCK_SHARED) } pub fn unlock(&self) -> io::Result<()> { - unsupported() + moto_rt::fs::file_lock(self.as_raw_fd(), moto_rt::fs::UNLOCK).map_err(map_motor_error) } pub fn size(&self) -> Option> { diff --git a/library/std/src/sys/fs/unix/dir.rs b/library/std/src/sys/fs/unix/dir.rs index 84c5c24668bff..aad309362127b 100644 --- a/library/std/src/sys/fs/unix/dir.rs +++ b/library/std/src/sys/fs/unix/dir.rs @@ -1,4 +1,4 @@ -use libc::{c_int, renameat, unlinkat}; +use libc::{c_int, mkdirat, renameat, unlinkat}; cfg_select! { not(any( @@ -28,6 +28,14 @@ use crate::sys::helpers::run_path_with_cstr; use crate::sys::{AsInner, FromInner, IntoInner, cvt, cvt_r}; use crate::{fmt, fs, io}; +const TRAVERSE_DIRECTORY: i32 = + cfg_select! { + any(target_os = "freebsd", target_os = "aix") => libc::O_EXEC, + any(target_os = "linux", target_os = "android", target_os = "l4re") => libc::O_PATH, + target_os = "illumos" => libc::O_SEARCH, + _ => libc::O_RDONLY, + }; + pub struct Dir(OwnedFd); impl Dir { @@ -35,8 +43,14 @@ impl Dir { run_path_with_cstr(path, &|path| Self::open_with_c(path, opts)) } + pub fn open_for_traversal(path: &Path) -> io::Result { + run_path_with_cstr(path, &|path| Self::open_traversal_c(path)) + } + pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result { - run_path_with_cstr(path.as_ref(), &|path| self.open_file_c(path, opts)) + run_path_with_cstr(path.as_ref(), &|path| self.open_file_c(path, opts, 0)) + .map(|fd| FileDesc::from_inner(fd)) + .map(File) } pub fn metadata(&self) -> io::Result { @@ -59,7 +73,19 @@ impl Dir { }) } - pub fn open_with_c(path: &CStr, opts: &OpenOptions) -> io::Result { + pub fn open_dir(&self, path: &Path, opts: &OpenOptions) -> io::Result { + run_path_with_cstr(path, &|path| self.open_file_c(path, opts, libc::O_DIRECTORY)).map(Self) + } + + pub fn create_dir(&self, path: &Path) -> io::Result<()> { + run_path_with_cstr(path.as_ref(), &|path| self.create_dir_c(path)) + } + + pub fn remove_dir(&self, path: &Path) -> io::Result<()> { + run_path_with_cstr(path, &|path| self.remove_c(path, true)) + } + + fn open_with_c(path: &CStr, opts: &OpenOptions) -> io::Result { let flags = libc::O_CLOEXEC | libc::O_DIRECTORY | opts.get_access_mode()? @@ -69,15 +95,27 @@ impl Dir { Ok(Self(unsafe { OwnedFd::from_raw_fd(fd) })) } - fn open_file_c(&self, path: &CStr, opts: &OpenOptions) -> io::Result { + fn open_traversal_c(path: &CStr) -> io::Result { + let flags = libc::O_CLOEXEC | libc::O_DIRECTORY | TRAVERSE_DIRECTORY; + let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, 0) })?; + Ok(Self(unsafe { OwnedFd::from_raw_fd(fd) })) + } + + fn open_file_c( + &self, + path: &CStr, + opts: &OpenOptions, + extra_flags: c_int, + ) -> io::Result { let flags = libc::O_CLOEXEC | opts.get_access_mode()? | opts.get_creation_mode()? - | (opts.custom_flags as c_int & !libc::O_ACCMODE); + | (opts.custom_flags as c_int & !libc::O_ACCMODE) + | extra_flags; let fd = cvt_r(|| unsafe { openat64(self.0.as_raw_fd(), path.as_ptr(), flags, opts.mode as c_int) })?; - Ok(File(unsafe { FileDesc::from_raw_fd(fd) })) + Ok(unsafe { OwnedFd::from_raw_fd(fd) }) } fn remove_c(&self, path: &CStr, remove_dir: bool) -> io::Result<()> { @@ -97,6 +135,10 @@ impl Dir { }) .map(|_| ()) } + + fn create_dir_c(&self, path: &CStr) -> io::Result<()> { + cvt(unsafe { mkdirat(self.0.as_raw_fd(), path.as_ptr(), 0o777) }).map(|_| ()) + } } impl fmt::Debug for Dir { diff --git a/library/std/src/sys/fs/windows/dir.rs b/library/std/src/sys/fs/windows/dir.rs index 5e69515b66599..4fe0062af821b 100644 --- a/library/std/src/sys/fs/windows/dir.rs +++ b/library/std/src/sys/fs/windows/dir.rs @@ -66,6 +66,12 @@ impl Dir { with_native_path(path, &|path| Self::open_with_native(path, opts)) } + pub fn open_for_traversal(path: &Path) -> io::Result { + let mut opts = OpenOptions::new(); + opts.access_mode(c::FILE_TRAVERSE); + with_native_path(path, &|path| Self::open_with_native(path, &opts)) + } + pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result { // NtCreateFile will fail if given an absolute path and a non-null RootDirectory if path.is_absolute() { @@ -87,6 +93,24 @@ impl Dir { self.rename_native(&from, to_dir, &to, is_dir) } + pub fn create_dir(&self, path: &Path) -> io::Result<()> { + let mut opts = OpenOptions::new(); + opts.read(true); + opts.write(true); + opts.create_new(true); + self.open_dir(path, &opts).map(|_| ()) + } + + pub fn open_dir(&self, path: &Path, opts: &OpenOptions) -> io::Result { + let path = to_u16s_without_nul(&path)?; + self.open_file_native(&path, &opts, true).map(|handle| Self { handle }) + } + + pub fn remove_dir(&self, path: &Path) -> io::Result<()> { + let path = to_u16s_without_nul(&path)?; + self.remove_native(&path, true) + } + fn open_with_native(path: &WCStr, opts: &OpenOptions) -> io::Result { let creation = opts.get_creation_mode()?; let sa = c::SECURITY_ATTRIBUTES { diff --git a/library/std/src/sys/io/error/motor.rs b/library/std/src/sys/io/error/motor.rs index 3c22d5fcb7b06..06417417e8554 100644 --- a/library/std/src/sys/io/error/motor.rs +++ b/library/std/src/sys/io/error/motor.rs @@ -50,6 +50,7 @@ pub fn decode_error_kind(code: io::RawOsError) -> io::ErrorKind { moto_rt::Error::BadHandle => io::ErrorKind::InvalidInput, moto_rt::Error::FileTooLarge => io::ErrorKind::FileTooLarge, moto_rt::Error::NotConnected => io::ErrorKind::NotConnected, + moto_rt::Error::ConnectionReset => io::ErrorKind::ConnectionReset, moto_rt::Error::StorageFull => io::ErrorKind::StorageFull, moto_rt::Error::InvalidData => io::ErrorKind::InvalidData, _ => io::ErrorKind::Uncategorized, diff --git a/library/std/src/sys/pal/motor/mod.rs b/library/std/src/sys/pal/motor/mod.rs index 5bf217db9013a..6ff8a4798fbdb 100644 --- a/library/std/src/sys/pal/motor/mod.rs +++ b/library/std/src/sys/pal/motor/mod.rs @@ -1,14 +1,38 @@ #![allow(unsafe_op_in_unsafe_fn)] use crate::io; +use crate::vec::Vec; pub(crate) fn map_motor_error(err: moto_rt::Error) -> io::Error { let error_code: moto_rt::ErrorCode = err.into(); io::Error::from_raw_os_error(error_code.into()) } +/// The buffer list `moto_rt::fs::write_vectored` takes. +/// +/// Unix hands `writev` an `[IoSlice]` directly, but only because its `IoSlice` +/// is documented to be ABI-compatible with `iovec`. Motor uses the generic +/// representation, whose layout is deliberately unspecified, so the list is +/// rebuilt through the public `Deref` rather than reinterpreted. That costs one +/// small allocation against a filesystem or IPC round trip, and needs no +/// `unsafe` and no assumption about a type shared with every other target. +pub(crate) fn io_slices<'a>(bufs: &'a [io::IoSlice<'_>]) -> Vec<&'a [u8]> { + bufs.iter().map(|buf| &**buf).collect() +} + +/// The buffer list `moto_rt::fs::read_vectored` takes. See [`io_slices`]. +pub(crate) fn io_slices_mut<'a>(bufs: &'a mut [io::IoSliceMut<'_>]) -> Vec<&'a mut [u8]> { + bufs.iter_mut().map(|buf| &mut **buf).collect() +} + +// Weak: when a program is linked with mlibc (e.g. via the Motor clang +// driver, which always links mlibc's crt1.o), crt1.o's strong motor_start +// must win. mlibc's entry initializes the VDSO vtable and the C runtime +// (TCB, stdio, .init_array constructors) and then calls the C `main` +// that rustc generates, so Rust std works identically in both flows. #[cfg(not(test))] #[unsafe(no_mangle)] +#[linkage = "weak"] pub extern "C" fn motor_start() -> ! { // Initialize the runtime. moto_rt::start(); diff --git a/library/std/src/sys/paths/mod.rs b/library/std/src/sys/paths/mod.rs index 57f894249ae74..69c814ef06c06 100644 --- a/library/std/src/sys/paths/mod.rs +++ b/library/std/src/sys/paths/mod.rs @@ -15,9 +15,9 @@ cfg_select! { #[expect(dead_code)] mod unsupported; mod imp { - pub use super::motor::{chdir, current_exe, getcwd, temp_dir}; - pub use super::unsupported::{ - JoinPathsError, SplitPaths, home_dir, join_paths, split_paths, + pub use super::motor::{ + JoinPathsError, SplitPaths, chdir, current_exe, getcwd, home_dir, join_paths, + split_paths, temp_dir, }; } } diff --git a/library/std/src/sys/paths/motor.rs b/library/std/src/sys/paths/motor.rs index 33d746b13d592..82f8a35e50db6 100644 --- a/library/std/src/sys/paths/motor.rs +++ b/library/std/src/sys/paths/motor.rs @@ -1,7 +1,49 @@ -use crate::io; +use crate::ffi::{OsStr, OsString}; use crate::os::motor::ffi::OsStrExt; use crate::path::{self, PathBuf}; use crate::sys::pal::map_motor_error; +use crate::{fmt, io, iter, str}; + +const PATH_SEPARATOR: char = ':'; + +pub type SplitPaths<'a> = iter::Map, fn(&str) -> PathBuf>; + +pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> { + fn into_pathbuf(part: &str) -> PathBuf { + PathBuf::from(part) + } + unparsed.as_str().split(PATH_SEPARATOR).map(into_pathbuf as fn(&str) -> PathBuf) +} + +#[derive(Debug)] +pub struct JoinPathsError; + +pub fn join_paths(paths: I) -> Result +where + I: Iterator, + T: AsRef, +{ + let mut joined = String::new(); + for (i, path) in paths.enumerate() { + let path = path.as_ref().as_str(); + if i > 0 { + joined.push(PATH_SEPARATOR); + } + if path.contains(PATH_SEPARATOR) { + return Err(JoinPathsError); + } + joined.push_str(path); + } + Ok(OsString::from(joined)) +} + +impl fmt::Display for JoinPathsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "path segment contains separator `{PATH_SEPARATOR}`") + } +} + +impl crate::error::Error for JoinPathsError {} pub fn getcwd() -> io::Result { moto_rt::fs::getcwd().map(PathBuf::from).map_err(map_motor_error) @@ -11,10 +53,14 @@ pub fn chdir(path: &path::Path) -> io::Result<()> { moto_rt::fs::chdir(path.as_os_str().as_str()).map_err(map_motor_error) } +pub fn home_dir() -> Option { + Some(PathBuf::from("/user")) +} + pub fn current_exe() -> io::Result { moto_rt::process::current_exe().map(PathBuf::from).map_err(map_motor_error) } pub fn temp_dir() -> PathBuf { - PathBuf::from(moto_rt::fs::TEMP_DIR) + crate::env::var_os("TMPDIR").map(PathBuf::from).unwrap_or_else(|| PathBuf::from("/user/tmp")) } diff --git a/library/std/src/sys/process/mod.rs b/library/std/src/sys/process/mod.rs index f46870e0c4042..e0d52a6c5c295 100644 --- a/library/std/src/sys/process/mod.rs +++ b/library/std/src/sys/process/mod.rs @@ -49,8 +49,7 @@ pub use imp::{ target_os = "l4re" )) ), - target_os = "windows", - target_os = "motor" + target_os = "windows" ))] pub fn output(cmd: &mut Command) -> crate::io::Result<(ExitStatus, Vec, Vec)> { let (mut process, mut pipes) = cmd.spawn(Stdio::MakePipe, false)?; @@ -88,7 +87,6 @@ pub fn output(cmd: &mut Command) -> crate::io::Result<(ExitStatus, Vec, Vec< target_os = "l4re" )) ), - target_os = "windows", - target_os = "motor" + target_os = "windows" )))] pub use imp::output; diff --git a/library/std/src/sys/process/motor.rs b/library/std/src/sys/process/motor.rs index 080da9be3af92..5cedbf9792b3d 100644 --- a/library/std/src/sys/process/motor.rs +++ b/library/std/src/sys/process/motor.rs @@ -3,39 +3,36 @@ use super::env::{CommandEnv, CommandResolvedEnvs}; use crate::ffi::OsStr; pub use crate::ffi::OsString as EnvKey; use crate::num::NonZeroI32; -use crate::os::fd::{FromRawFd, IntoRawFd}; +use crate::os::fd::{AsRawFd, FromRawFd}; use crate::os::motor::ffi::OsStrExt; use crate::path::Path; use crate::process::StdioPipes; use crate::sys::fs::File; -use crate::sys::{AsInner, FromInner, map_motor_error}; +use crate::sys::{IntoInner, map_motor_error}; use crate::{fmt, io}; pub enum Stdio { Inherit, Null, MakePipe, + // There is no public `From` conversion yet. + #[expect(dead_code)] + ParentStdin, + ParentStdout, + ParentStderr, Fd(crate::sys::fd::FileDesc), } impl Stdio { - fn into_rt(self) -> moto_rt::RtFd { + fn into_rt(&self) -> moto_rt::RtFd { match self { Stdio::Inherit => moto_rt::process::STDIO_INHERIT, Stdio::Null => moto_rt::process::STDIO_NULL, Stdio::MakePipe => moto_rt::process::STDIO_MAKE_PIPE, - Stdio::Fd(fd) => fd.into_raw_fd(), - } - } - - fn try_clone(&self) -> io::Result { - match self { - Self::Fd(fd) => { - Ok(Self::Fd(crate::sys::fd::FileDesc::from_inner(fd.as_inner().try_clone()?))) - } - Self::Inherit => Ok(Self::Inherit), - Self::Null => Ok(Self::Null), - Self::MakePipe => Ok(Self::MakePipe), + Stdio::ParentStdin => moto_rt::process::STDIO_PARENT_STDIN, + Stdio::ParentStdout => moto_rt::process::STDIO_PARENT_STDOUT, + Stdio::ParentStderr => moto_rt::process::STDIO_PARENT_STDERR, + Stdio::Fd(fd) => fd.as_raw_fd(), } } } @@ -53,10 +50,7 @@ pub struct Command { impl Command { pub fn new(program: &OsStr) -> Command { - let mut env = CommandEnv::default(); - env.remove(OsStr::new(moto_rt::process::STDIO_IS_TERMINAL_ENV_KEY)); - - Command { program: program.as_str().to_owned(), env, ..Default::default() } + Command { program: program.as_str().to_owned(), ..Default::default() } } pub fn arg(&mut self, arg: &OsStr) { @@ -114,21 +108,21 @@ impl Command { needs_stdin: bool, ) -> io::Result<(Process, StdioPipes)> { let stdin = if let Some(stdin) = self.stdin.as_ref() { - stdin.try_clone()?.into_rt() + stdin.into_rt() } else if needs_stdin { - default.try_clone()?.into_rt() + default.into_rt() } else { Stdio::Null.into_rt() }; let stdout = if let Some(stdout) = self.stdout.as_ref() { - stdout.try_clone()?.into_rt() + stdout.into_rt() } else { - default.try_clone()?.into_rt() + default.into_rt() }; - let stderr = if let Some(stderr) = self.stdout.as_ref() { - stderr.try_clone()?.into_rt() + let stderr = if let Some(stderr) = self.stderr.as_ref() { + stderr.into_rt() } else { - default.try_clone()?.into_rt() + default.into_rt() }; let mut env = Vec::<(String, String)>::new(); @@ -146,11 +140,11 @@ impl Command { stderr, }; - let (handle, stdin, stdout, stderr) = - moto_rt::process::spawn(args).map_err(map_motor_error)?; + let res = moto_rt::process::spawn(args).map_err(map_motor_error)?; + let (handle, stdin, stdout, stderr) = (res.handle, res.stdin, res.stdout, res.stderr); Ok(( - Process { handle }, + Process { handle, pid: res.pid as u32 }, StdioPipes { stdin: if stdin >= 0 { Some(unsafe { ChildPipe::from_raw_fd(stdin) }) @@ -172,6 +166,29 @@ impl Command { } } +pub fn output(cmd: &mut Command) -> io::Result<(ExitStatus, Vec, Vec)> { + let (mut process, mut pipes) = cmd.spawn(Stdio::MakePipe, false)?; + + drop(pipes.stdin.take()); + let (mut stdout, mut stderr) = (Vec::new(), Vec::new()); + crate::thread::scope(|scope| { + let waiter = scope.spawn(move || { + let status = process.wait(); + drop(process); + status + }); + let read_result = match (pipes.stdout.take(), pipes.stderr.take()) { + (None, None) => Ok(()), + (Some(out), None) => out.read_to_end(&mut stdout).map(|_| ()), + (None, Some(err)) => err.read_to_end(&mut stderr).map(|_| ()), + (Some(out), Some(err)) => read_output(out, &mut stdout, err, &mut stderr), + }; + let status = waiter.join().expect("child wait thread panicked"); + read_result?; + Ok((status?, stdout, stderr)) + }) +} + impl From for Stdio { fn from(fd: crate::sys::fd::FileDesc) -> Stdio { Stdio::Fd(fd) @@ -179,20 +196,20 @@ impl From for Stdio { } impl From for Stdio { - fn from(_file: File) -> Stdio { - panic!("Not implemented") + fn from(file: File) -> Stdio { + Stdio::Fd(file.into_inner()) } } impl From for Stdio { fn from(_: io::Stdout) -> Stdio { - panic!("Not implemented") + Stdio::ParentStdout } } impl From for Stdio { fn from(_: io::Stderr) -> Stdio { - panic!("Not implemented") + Stdio::ParentStderr } } @@ -255,6 +272,7 @@ impl From for ExitCode { pub struct Process { handle: u64, + pid: u32, } impl Drop for Process { @@ -265,7 +283,9 @@ impl Drop for Process { impl Process { pub fn id(&self) -> u32 { - 0 + // The kernel bounds pids to the i32-positive range, so the pid the + // runtime reported at spawn is exact (pid-refactoring-design.md). + self.pid } pub fn kill(&mut self) -> io::Result<()> { @@ -324,14 +344,25 @@ impl<'a> fmt::Debug for CommandArgs<'a> { pub type ChildPipe = crate::sys::pipe::Pipe; pub fn read_output( - _out: ChildPipe, - _stdout: &mut Vec, - _err: ChildPipe, - _stderr: &mut Vec, + out: ChildPipe, + stdout: &mut Vec, + err: ChildPipe, + stderr: &mut Vec, ) -> io::Result<()> { - Err(io::Error::from_raw_os_error(moto_rt::E_NOT_IMPLEMENTED.into())) + // Drain both pipes concurrently so the child can't deadlock filling one + // pipe while we block reading the other. + crate::thread::scope(|s| { + let err_reader = s.spawn(move || err.read_to_end(stderr)); + let out_res = out.read_to_end(stdout); + let err_res = err_reader.join().expect("stderr reader thread panicked"); + out_res?; + err_res?; + Ok(()) + }) } pub fn getpid() -> u32 { - panic!("Pids on Motor OS are u64.") + // Motor OS pids are u64 in the ABI, but the kernel bounds them to i32 + // to be compatible with the wider ecosystem. + moto_rt::process::current_pid().try_into().expect("current_pid() too large") } diff --git a/library/std/src/sys/stdio/motor.rs b/library/std/src/sys/stdio/motor.rs index acc30b134d828..24a9dd830437f 100644 --- a/library/std/src/sys/stdio/motor.rs +++ b/library/std/src/sys/stdio/motor.rs @@ -1,5 +1,5 @@ use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; -use crate::sys::{AsInner, FromInner, IntoInner, map_motor_error}; +use crate::sys::{AsInner, FromInner, IntoInner, io_slices, io_slices_mut, map_motor_error}; use crate::{io, process, sys}; pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; @@ -38,6 +38,15 @@ impl io::Read for Stdin { fn read(&mut self, buf: &mut [u8]) -> io::Result { moto_rt::fs::read(moto_rt::FD_STDIN, buf).map_err(map_motor_error) } + + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + moto_rt::fs::read_vectored(moto_rt::FD_STDIN, &mut io_slices_mut(bufs)) + .map_err(map_motor_error) + } + + fn is_read_vectored(&self) -> bool { + true + } } impl io::Write for Stdout { @@ -48,6 +57,14 @@ impl io::Write for Stdout { fn flush(&mut self) -> io::Result<()> { moto_rt::fs::flush(moto_rt::FD_STDOUT).map_err(map_motor_error) } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + moto_rt::fs::write_vectored(moto_rt::FD_STDOUT, &io_slices(bufs)).map_err(map_motor_error) + } + + fn is_write_vectored(&self) -> bool { + true + } } impl io::Write for Stderr { @@ -58,6 +75,14 @@ impl io::Write for Stderr { fn flush(&mut self) -> io::Result<()> { moto_rt::fs::flush(moto_rt::FD_STDERR).map_err(map_motor_error) } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + moto_rt::fs::write_vectored(moto_rt::FD_STDERR, &io_slices(bufs)).map_err(map_motor_error) + } + + fn is_write_vectored(&self) -> bool { + true + } } pub fn panic_output() -> Option { diff --git a/src/doc/reference b/src/doc/reference index eda708334abad..3b38834b39f73 160000 --- a/src/doc/reference +++ b/src/doc/reference @@ -1 +1 @@ -Subproject commit eda708334abad285d63bc1f3558a51e8b790346e +Subproject commit 3b38834b39f732c64686f7c64aa29dcf3cd83ba5 diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs index 4c4c811c01358..a4766e53314cb 100644 --- a/src/tools/miri/priroda/src/debugger.rs +++ b/src/tools/miri/priroda/src/debugger.rs @@ -890,13 +890,14 @@ impl<'tcx> PrirodaContext<'tcx> { value: "".to_string(), }; - match &frame.locals[local].as_mplace_or_imm() { + match &frame.locals()[local].as_mplace_or_imm_ghost() { None => { local_desc.value = "".to_string(); } Some(Either::Right(Uninit)) => local_desc.value = "".to_string(), Some(Either::Left(_) | Either::Right(_)) => { + // FIXME: This seems wrong, it ignore the frame. let op = self .ecx .local_to_op(local, None) diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs index a1102a9642439..c086f71c2dcaa 100644 --- a/src/tools/miri/src/concurrency/data_race.rs +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -779,7 +779,7 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> { // Only metadata on the location itself is used. if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() { - let old_val = this.run_for_validation_ref(|this| this.read_scalar(place)).discard_err(); + let old_val = this.ghost_run(|this| this.read_scalar(place)).discard_err(); return genmc_ctx.atomic_load( this, place.ptr().addr(), @@ -811,7 +811,7 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> { // Read the previous value so we can put it in the store buffer later. // Both GenMC and Miri need this. This value is nonsense if there are concurrent writes // but the code consuming the value is aware of that. - let old_val = this.run_for_validation_ref(|this| this.read_scalar(dest)).discard_err(); + let old_val = this.ghost_run(|this| this.read_scalar(dest)).discard_err(); // Inform GenMC about the atomic store. if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() { diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 5b3b56760a6c4..f5cfc62ad659a 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -345,8 +345,8 @@ impl VisitProvenance for Thread<'_> { impl VisitProvenance for Frame<'_, Provenance, FrameExtra<'_>> { fn visit_provenance(&self, visit: &mut VisitWith<'_>) { let return_place = self.return_place(); + let locals = self.locals(); let Frame { - locals, extra, // There are some private fields we cannot access; they contain no tags. .. @@ -356,7 +356,8 @@ impl VisitProvenance for Frame<'_, Provenance, FrameExtra<'_>> { return_place.visit_provenance(visit); // Locals. for local in locals.iter() { - match local.as_mplace_or_imm() { + // We only need the provenance so it's good for this to not be a real read. + match local.as_mplace_or_imm_ghost() { None => {} Some(Either::Left((ptr, meta))) => { ptr.visit_provenance(visit); diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 4a0869da63bff..1d88a792734f9 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -500,7 +500,7 @@ pub fn report_result<'tcx>( trace!("-------------------"); trace!("Frame {}", i); trace!(" return: {:?}", frame.return_place()); - for (i, local) in frame.locals.iter().enumerate() { + for (i, local) in frame.locals().iter().enumerate() { trace!(" local {}: {:?}", i, local); } } diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 361ebef73b357..076738a5db08e 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -2000,12 +2000,8 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { res } - fn after_local_read( - ecx: &InterpCx<'tcx, Self>, - frame: &Frame<'tcx, Provenance, FrameExtra<'tcx>>, - local: mir::Local, - ) -> InterpResult<'tcx> { - if let Some(data_race) = &frame.extra.data_race { + fn after_local_read(ecx: &InterpCx<'tcx, Self>, local: mir::Local) -> InterpResult<'tcx> { + if let Some(data_race) = &ecx.frame().extra.data_race { let _trace = enter_trace_span!(data_race::after_local_read); data_race.local_read(local, &ecx.machine); } diff --git a/src/tools/rustfmt/src/items.rs b/src/tools/rustfmt/src/items.rs index 6a68e2ec8c502..a4719334fd5e6 100644 --- a/src/tools/rustfmt/src/items.rs +++ b/src/tools/rustfmt/src/items.rs @@ -2361,7 +2361,14 @@ impl Rewrite for ast::Param { Ok(result) } else { - self.ty.rewrite_result(context, shape) + combine_strs_with_missing_comments( + context, + ¶m_attrs_result, + &self.ty.rewrite_result(context, shape)?, + span, + shape, + !has_multiple_attr_lines && !has_doc_comments, + ) } } } diff --git a/src/tools/rustfmt/tests/source/issue-6561/trait-fn.rs b/src/tools/rustfmt/tests/source/issue-6561/trait-fn.rs new file mode 100644 index 0000000000000..f88c69daddafb --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-6561/trait-fn.rs @@ -0,0 +1,6 @@ +// rustfmt-edition: 2015 + +trait A { + fn f1(#[allow()] u32); + fn f2(#[allow()] u32, #[allow()] u32); +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/issue-6561/variadic.rs b/src/tools/rustfmt/tests/source/issue-6561/variadic.rs new file mode 100644 index 0000000000000..6108d584d30d8 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-6561/variadic.rs @@ -0,0 +1,5 @@ +#[allow()] +unsafe extern "C" { + #[allow()] + pub fn foo(#[allow()] arg: *mut u8, #[allow()]...); +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/source/issue-6607/fn-type.rs b/src/tools/rustfmt/tests/source/issue-6607/fn-type.rs new file mode 100644 index 0000000000000..e85d04d6041e1 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-6607/fn-type.rs @@ -0,0 +1,3 @@ +struct Foo { + v: fn(#[cfg(false)] i32), +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/target/issue-6561/trait-fn.rs b/src/tools/rustfmt/tests/target/issue-6561/trait-fn.rs new file mode 100644 index 0000000000000..a30396be56126 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-6561/trait-fn.rs @@ -0,0 +1,6 @@ +// rustfmt-edition: 2015 + +trait A { + fn f1(#[allow()] u32); + fn f2(#[allow()] u32, #[allow()] u32); +} diff --git a/src/tools/rustfmt/tests/target/issue-6561/variadic.rs b/src/tools/rustfmt/tests/target/issue-6561/variadic.rs new file mode 100644 index 0000000000000..bf5274f31e1da --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-6561/variadic.rs @@ -0,0 +1,5 @@ +#[allow()] +unsafe extern "C" { + #[allow()] + pub fn foo(#[allow()] arg: *mut u8, #[allow()] ...); +} diff --git a/src/tools/rustfmt/tests/target/issue-6607/fn-type.rs b/src/tools/rustfmt/tests/target/issue-6607/fn-type.rs new file mode 100644 index 0000000000000..7f89d12be8d63 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-6607/fn-type.rs @@ -0,0 +1,3 @@ +struct Foo { + v: fn(#[cfg(false)] i32), +} diff --git a/tests/crashes/108428.rs b/tests/crashes/108248.rs similarity index 84% rename from tests/crashes/108428.rs rename to tests/crashes/108248.rs index b18123b6a7c40..36252e29d33f0 100644 --- a/tests/crashes/108428.rs +++ b/tests/crashes/108248.rs @@ -1,4 +1,4 @@ -//@ known-bug: #108428 +//@ known-bug: #108248 //@ needs-rustc-debug-assertions //@ compile-flags: -Wunused-lifetimes fn main() { diff --git a/tests/crashes/138262.rs b/tests/crashes/138262.rs new file mode 100644 index 0000000000000..ce5b3bb257e5d --- /dev/null +++ b/tests/crashes/138262.rs @@ -0,0 +1,12 @@ +//@ known-bug: #138262 +//@ compile-flags: -Zsanitizer=cfi -Ccodegen-units=1 -Clto -Clink-dead-code=true -Cunsafe-allow-abi-mismatch=sanitizer -Ctarget-feature=-crt-static +//@ ignore-backends: gcc +//@ needs-sanitizer-cfi +fn foo() {} + +core::arch::global_asm!("/* {} */", sym foo::<{ + || {}; + 0 +}>); + +fn main() {} diff --git a/tests/crashes/142155.rs b/tests/crashes/142155.rs new file mode 100644 index 0000000000000..8c0769bf2b586 --- /dev/null +++ b/tests/crashes/142155.rs @@ -0,0 +1,12 @@ +//@ known-bug: #142155 +//@ needs-rustc-debug-assertions +//@ edition: 2021 + +#![warn(tail_expr_drop_order)] +use core::future::Future; + +fn f() -> impl Future> { + async { Some("nope".into()) } +} + +fn main() {} diff --git a/tests/crashes/144241.rs b/tests/crashes/144241.rs new file mode 100644 index 0000000000000..3f91fcc7c6275 --- /dev/null +++ b/tests/crashes/144241.rs @@ -0,0 +1,4 @@ +//@ known-bug: #144241 +fn main() { + |_: dyn ?Sized + !Send| {} +} diff --git a/tests/crashes/149562.rs b/tests/crashes/149562.rs new file mode 100644 index 0000000000000..4d032a0af5c3e --- /dev/null +++ b/tests/crashes/149562.rs @@ -0,0 +1,10 @@ +//@ known-bug: #149562 +//@ needs-rustc-debug-assertions +fn a() -> T +where + T: ?Sized, + T: ?Sized, +{ +} + +fn main() {} diff --git a/tests/crashes/152414.rs b/tests/crashes/152414.rs new file mode 100644 index 0000000000000..226f9e29faad6 --- /dev/null +++ b/tests/crashes/152414.rs @@ -0,0 +1,6 @@ +//@ known-bug: #152414 +//@ needs-rustc-debug-assertions +#![feature(generic_assert)] +fn main() { + assert!(size_of(val, 1) >= 1); +} diff --git a/tests/crashes/152416.rs b/tests/crashes/152416.rs new file mode 100644 index 0000000000000..9ca418cce3628 --- /dev/null +++ b/tests/crashes/152416.rs @@ -0,0 +1,17 @@ +//@ known-bug: #152416 +//@ needs-rustc-debug-assertions +//@ compile-flags: -Zunstable-options + +trait AssetID {} +trait Archive { + fn name(&self); +} +struct NorthlightAssetID; +impl AssetID for NorthlightAssetID {} +fn get() -> Box> { + let x: Box> = todo!(); + x +} +fn main() { + get().name(); +} diff --git a/tests/crashes/152626.rs b/tests/crashes/152626.rs new file mode 100644 index 0000000000000..eafb714c2f5c2 --- /dev/null +++ b/tests/crashes/152626.rs @@ -0,0 +1,7 @@ +//@ known-bug: #152626 +//@ needs-rustc-debug-assertions +struct A>(T); +fn f() -> A<&'static ()> { + todo!() +} +fn main() {} diff --git a/tests/crashes/154903.rs b/tests/crashes/154903.rs new file mode 100644 index 0000000000000..63e80d8f9e251 --- /dev/null +++ b/tests/crashes/154903.rs @@ -0,0 +1,7 @@ +//@ known-bug: #154903 +//@ compile-flags: -Zlint-mir +#![feature(guard_patterns)] + +fn a(((x if true, _) | (_, x)): (i32, i32)) {} + +fn main() {} diff --git a/tests/crashes/154963.rs b/tests/crashes/154963.rs new file mode 100644 index 0000000000000..8fafc29c48342 --- /dev/null +++ b/tests/crashes/154963.rs @@ -0,0 +1,10 @@ +//@ known-bug: #154963 +#![feature(extern_types, negative_impls)] + +unsafe extern "C" { + type ExternType; +} + +impl !Unpin for ExternType {} + +fn main() {} diff --git a/tests/crashes/155053.rs b/tests/crashes/155053.rs new file mode 100644 index 0000000000000..31b9ccaf20540 --- /dev/null +++ b/tests/crashes/155053.rs @@ -0,0 +1,11 @@ +//@ known-bug: #155053 +#![feature(pin_ergonomics)] +#![feature(extern_types)] + +unsafe extern "C" { + type ExternType; +} + +impl Unpin for ExternType {} + +fn main() {} diff --git a/tests/crashes/156101.rs b/tests/crashes/156101.rs new file mode 100644 index 0000000000000..c95361fab2ecc --- /dev/null +++ b/tests/crashes/156101.rs @@ -0,0 +1,4 @@ +//@ known-bug: #156101 +fn main() { + format_args!(concat!("𐏿", "{f:?#}")); +} diff --git a/tests/crashes/156288.rs b/tests/crashes/156288.rs new file mode 100644 index 0000000000000..b745cfe063dda --- /dev/null +++ b/tests/crashes/156288.rs @@ -0,0 +1,3 @@ +//@ known-bug: #156288 +#[warn(rust_2021_incompatible_closure_captures)] +const _: () = |b| move || b; diff --git a/tests/ui/float/classify-runtime-const.rs b/tests/ui/float/classify-runtime-const.rs index e3b97386a3124..7a06594eb7d42 100644 --- a/tests/ui/float/classify-runtime-const.rs +++ b/tests/ui/float/classify-runtime-const.rs @@ -2,54 +2,43 @@ //@ revisions: opt noopt ctfe //@[opt] compile-flags: -O //@[noopt] compile-flags: -Zmir-opt-level=0 -//@ min-llvm-version: 22 -//@ compile-flags: --check-cfg=cfg(target_has_reliable_f16) +//@ min-llvm-version: 23 +//@ compile-flags: --check-cfg=cfg(target_has_reliable_f16,target_has_reliable_f128) // ignore-tidy-file-linelength #![feature(cfg_target_has_reliable_f16_f128)] #![cfg_attr(target_has_reliable_f16, feature(f16))] +#![cfg_attr(target_has_reliable_f128, feature(f128))] // This tests the float classification functions, for regular runtime code and for const evaluation. - -use std::num::FpCategory::*; - -#[cfg(not(ctfe))] use std::hint::black_box; -#[cfg(ctfe)] -#[allow(unused)] -const fn black_box(x: T) -> T { x } +use std::num::FpCategory::*; #[cfg(not(ctfe))] macro_rules! assert_test { - ($a:expr, NonDet) => { - { - // Compute `a`, but do not compare with anything as the result is non-deterministic. - let _val = $a; - } - }; - ($a:expr, $b:ident) => { - { - // Let-bind to avoid promotion. - // No black_box here! That can mask x87 failures. - let a = $a; - let b = $b; - assert_eq!(a, b, "{} produces wrong result", stringify!($a)); - } - }; + ($a:expr, NonDet) => {{ + // Compute `a`, but do not compare with anything as the result is non-deterministic. + let _val = $a; + }}; + ($a:expr, $b:ident) => {{ + // Let-bind to avoid promotion. + // No black_box here! That can mask x87 failures. + let a = $a; + let b = $b; + assert_eq!(a, b, "{} produces wrong result", stringify!($a)); + }}; } #[cfg(ctfe)] macro_rules! assert_test { - ($a:expr, NonDet) => { - { - // Compute `a`, but do not compare with anything as the result is non-deterministic. - const _: () = { let _val = $a; }; - } - }; - ($a:expr, $b:ident) => { - { - const _: () = assert!(matches!($a, $b)); - } - }; + ($a:expr, NonDet) => {{ + // Compute `a`, but do not compare with anything as the result is non-deterministic. + const _: () = { + let _val = $a; + }; + }}; + ($a:expr, $b:ident) => {{ + const _: () = assert!(matches!($a, $b)); + }}; } macro_rules! suite { @@ -72,6 +61,13 @@ macro_rules! suite { type $tyname = f64; suite_inner!(f64 => $($tt)*); } + + #[cfg(target_has_reliable_f128)] + fn f128() { + #[allow(unused)] + type $tyname = f128; + suite_inner!(f128 => $($tt)*); + } } } @@ -136,5 +132,6 @@ fn main() { f16(); f32(); f64(); - // FIXME(f128): also test f128 + #[cfg(target_has_reliable_f128)] + f128(); } diff --git a/tests/ui/std/overflow-check-ops.rs b/tests/ui/std/overflow-check-ops.rs new file mode 100644 index 0000000000000..458c1b121664e --- /dev/null +++ b/tests/ui/std/overflow-check-ops.rs @@ -0,0 +1,45 @@ +//! Verify the behavior differences between enabling and disabling overflow checks. + +//@ run-pass +//@ needs-unwind +//@ revisions: ERROR WRAP +//@[ERROR] compile-flags: -C overflow-checks=true +//@[WRAP] compile-flags: -C overflow-checks=false + +#![feature(cfg_overflow_checks)] +#![feature(funnel_shifts)] + +use std::hint::black_box as bb; +use std::{assert_matches, fmt, panic}; + +#[track_caller] +fn check(func: fn() -> T, wrapping_res: T, name: &str) { + let type_name = std::any::type_name::(); + let res = panic::catch_unwind(func); + if cfg!(overflow_checks) { + assert_matches!(res, Err(_), "{type_name} {name}"); + } else { + assert_eq!(res.unwrap(), wrapping_res, "{type_name} {name}"); + } +} + +fn main() { + check(|| bb(u32::MAX) + bb(1), 0, "add"); + check(|| bb(0u32) - bb(1), u32::MAX, "sub"); + check(|| bb(u32::MAX) * bb(2), u32::MAX << 1, "mul"); + check(|| bb(1u32) << bb(32), 1, "shl"); + check(|| bb(u32::MAX) >> bb(32), u32::MAX, "shr"); + check(|| bb(1234u32).funnel_shl(4567, bb(32)), 1234, "funnel_shl"); + check(|| bb(1234u32).funnel_shr(4567, bb(32)), 4567, "funnel_shr"); + check(|| bb(u32::MAX).pow(bb(2)), 1, "pow"); + check(|| bb(u32::MAX).next_power_of_two(), 0, "next_power_of_two"); + + check(|| bb(i32::MAX) + bb(1), i32::MIN, "add"); + check(|| bb(i32::MIN) - bb(1), i32::MAX, "sub"); + check(|| bb(i32::MAX) * bb(2), i32::MAX << 1, "mul"); + check(|| -bb(i32::MIN), i32::MIN, "neg"); + check(|| bb(i32::MIN).abs(), i32::MIN, "abs"); + check(|| bb(1) << bb(32), 1, "shl"); + check(|| bb(i32::MAX) >> bb(32), i32::MAX, "shr"); + check(|| bb(i32::MAX).pow(bb(2)), 1, "pow"); +}