diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 2735c800c4a38..f7d35f3ff3b4b 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -156,14 +156,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { .collect(); generic_args.push((kw::SelfUpper, this.clone())); - let args = FormatArgs { - this, - // Unused - this_sugared: String::new(), - // Unused - item_context: "", - generic_args, - }; + let args = FormatArgs { this, generic_args, .. }; let CustomDiagnostic { message, label, notes, parent_label: _ } = directive.eval(None, &args); diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 7d01868645a09..8e055c855c4f1 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -173,6 +173,16 @@ fn make_format_args( style: fmt_style, uncooked_symbol: uncooked_fmt_str, } = { + // Extract snippet so that we can check cases `{}`, `{:?}` and `{:#?}` and emit help for + // them later. + let snippet = if let ExprKind::Block(b, None) = &efmt.kind + && b.stmts.len() <= 1 + { + Some(ecx.sess.source_map().span_to_snippet(unexpanded_fmt_span)) + } else { + None + }; + let ExpandResult::Ready(mac) = expr_to_spanned_string(ecx, efmt.clone(), msg) else { return ExpandResult::Retry(()); }; @@ -222,12 +232,26 @@ fn make_format_args( }); } sugg_fmt = sugg_fmt.trim_end().to_string(); - err.span_suggestion( + err.span_suggestion_verbose( unexpanded_fmt_span.shrink_to_lo(), "you might be missing a string literal to format with", format!("\"{sugg_fmt}\", "), Applicability::MaybeIncorrect, ); + + if let Some(Ok(snippet)) = snippet.as_ref() { + match snippet.as_str() { + "{}" | "{:?}" | "{:#?}" => { + err.span_suggestion_verbose( + unexpanded_fmt_span, + format!("you might want to enclose `{snippet}` with `\"\"`"), + format!("\"{snippet}\""), + Applicability::MaybeIncorrect, + ); + } + _ => {} + }; + } } } err.emit() diff --git a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs index 780550fc4cc74..23263284d57a6 100644 --- a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs +++ b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs @@ -1529,6 +1529,12 @@ fn codegen_regular_intrinsic_call<'tcx>( fx.bcx.set_cold_block(fx.bcx.current_block().unwrap()); } + sym::return_address => { + let val = fx.bcx.ins().get_return_address(fx.pointer_type); + let val = CValue::by_val(val, ret.layout()); + ret.write_cvalue(fx, val); + } + // Unimplemented intrinsics must have a fallback body. The fallback body is obtained // by converting the `InstanceKind::Intrinsic` to an `InstanceKind::Item`. _ => { diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 94bd4a6ef76ef..5d5f515968e7c 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -838,6 +838,22 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { } } + sym::return_address => { + match self.sess().target.arch { + // Expand this list as needed + | Arch::Wasm32 + | Arch::Wasm64 => { + let ty = self.type_ptr(); + self.const_null(ty) + } + _ => { + let ty = self.type_ix(32); + let val = self.const_int(ty, 0); + self.call_intrinsic("llvm.returnaddress", &[], &[val]) + } + } + } + _ => { debug!("unknown intrinsic '{}' -- falling back to default body", name); // Call the fallback body instead of generating the intrinsic code diff --git a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs index f4a5e8baa2a5f..38d76e1aad369 100644 --- a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs +++ b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs @@ -121,7 +121,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { | sym::contract_checks | sym::atomic_fence | sym::atomic_singlethreadfence - | sym::caller_location => {} + | sym::caller_location + | sym::return_address => {} _ => { span_bug!( span, diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs index d2ac7103bccb8..5068501a0e2d1 100644 --- a/compiler/rustc_expand/src/lib.rs +++ b/compiler/rustc_expand/src/lib.rs @@ -1,6 +1,7 @@ // tidy-alphabetical-start #![allow(internal_features)] #![feature(associated_type_defaults)] +#![feature(default_field_values)] #![feature(macro_metavar_expr)] #![feature(proc_macro_diagnostic)] #![feature(proc_macro_internals)] diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 4e7e51c0a43cb..16b21b062cce7 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -77,19 +77,8 @@ pub(super) fn failed_to_match_macro( let CustomDiagnostic { message: custom_message, label: custom_label, notes: custom_notes, .. } = { - let macro_name = name.to_string(); on_unmatch_args - .map(|directive| { - directive.eval( - None, - &FormatArgs { - this: macro_name.clone(), - this_sugared: macro_name, - item_context: "macro invocation", - generic_args: Vec::new(), - }, - ) - }) + .map(|directive| directive.eval(None, &FormatArgs { this: name.to_string(), .. })) .unwrap_or_default() }; diff --git a/compiler/rustc_hir/src/attrs/diagnostic.rs b/compiler/rustc_hir/src/attrs/diagnostic.rs index 66cdf2be8fc4d..2beafee54541f 100644 --- a/compiler/rustc_hir/src/attrs/diagnostic.rs +++ b/compiler/rustc_hir/src/attrs/diagnostic.rs @@ -218,10 +218,11 @@ impl FormatString { /// ``` #[derive(Debug)] pub struct FormatArgs { + /// The name of the item the attribute is on. pub this: String, - pub this_sugared: String, - pub item_context: &'static str, - pub generic_args: Vec<(Symbol, String)>, + pub this_sugared: String = String::new(), + pub item_context: &'static str = "", + pub generic_args: Vec<(Symbol, String)> = Vec::new(), } #[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] diff --git a/compiler/rustc_hir/src/lib.rs b/compiler/rustc_hir/src/lib.rs index c2d9f879cd601..7c2bf3c5b2797 100644 --- a/compiler/rustc_hir/src/lib.rs +++ b/compiler/rustc_hir/src/lib.rs @@ -7,6 +7,7 @@ #![feature(closure_track_caller)] #![feature(const_default)] #![feature(const_trait_impl)] +#![feature(default_field_values)] #![feature(derive_const)] #![feature(exhaustive_patterns)] #![feature(never_type)] diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 7d606439cedc3..77cf46c71931c 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -180,6 +180,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::ptr_guaranteed_cmp | sym::ptr_mask | sym::ptr_metadata + | sym::return_address | sym::rotate_left | sym::rotate_right | sym::round_ties_even_f16 @@ -803,6 +804,8 @@ pub(crate) fn check_intrinsic_type( | sym::atomic_xor => (2, 1, vec![Ty::new_mut_ptr(tcx, param(0)), param(1)], param(0)), sym::atomic_fence | sym::atomic_singlethreadfence => (0, 1, Vec::new(), tcx.types.unit), + sym::return_address => (0, 0, vec![], Ty::new_imm_ptr(tcx, tcx.types.unit)), + other => { tcx.dcx().emit_err(UnrecognizedIntrinsicFunction { span, name: other }); return; diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index b027872dd99cc..68a220a7224d4 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -5639,7 +5639,12 @@ declare_lint! { /// LLVM periodically updates its list of intrinsics. Deprecated intrinsics are unlikely /// to be removed, but they may optimize less well than their new versions, so it's /// best to use the new version. Also, some deprecated intrinsics might have buggy - /// behavior + /// behavior. + /// + /// This `link_llvm_intrinsics` lint is intended to be used internally only, and requires the + /// `#![feature(link_llvm_intrinsics)]` internal feature gate. For more information, see [its chapter in + /// the Unstable Book](https://doc.rust-lang.org/unstable-book/language-features/link-llvm-intrinsics.html) + /// and [its tracking issue](https://github.com/rust-lang/rust/issues/29602). pub DEPRECATED_LLVM_INTRINSIC, Allow, "detects uses of deprecated LLVM intrinsics", diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 198ec4080816b..b6ba35f0f3db8 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -886,12 +886,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let args = FormatArgs { this, - // Unused - this_sugared: String::new(), - // Unused - item_context: "", - // Unused - generic_args: Vec::new(), + .. }; let CustomDiagnostic { message, label, notes, .. } = directive.eval(None, &args); diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 4cacdbd3408a5..6fd6d0351df0e 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1660,6 +1660,7 @@ symbols! { residual, result, result_ffi_guarantees, + return_address, return_position_impl_trait_in_trait, return_type_notation, riscv32, diff --git a/library/core/src/arch.rs b/library/core/src/arch.rs index e5078a45c6d9c..916e99b338640 100644 --- a/library/core/src/arch.rs +++ b/library/core/src/arch.rs @@ -76,3 +76,30 @@ pub macro global_asm("assembly template", $(operands,)* $(options($(option),*))? pub fn breakpoint() { core::intrinsics::breakpoint(); } + +/// The `core::arch::return_address!()` macro returns a pointer with an address that corresponds to the caller of the function that invoked the `return_address!()` macro. +/// The pointer has no provenance, as if created by `core::ptr::without_provenance`. It cannot be used to read memory (other than ZSTs). +/// +/// The value returned by the macro depends highly on the architecture and compiler (including any options set). +/// In particular, it is allowed to be wrong (particularly if inlining is involved), or even contain a nonsense value. +/// The result of this macro must not be relied upon for soundness or correctness, only for debugging purposes. +/// +/// As a best effort, if a useful value cannot be determined (for example, due to limitations on the current codegen), +/// this macro tries to return a null pointer instead of nonsense (this cannot be relied upon for correctness, however). +/// +/// Formally, this function returns a pointer with a non-deterministic address and no provenance. +/// +/// This is equivalent to the gcc `__builtin_return_address(0)` intrinsic (other forms of the intrinsic are not supported). +/// Because the operation can be always performed by the compiler without crashing or causing undefined behaviour, invoking the macro is a safe operation. +/// +/// ## Example +/// ``` +/// # #![cfg(not(miri))] // FIXME: Figure out how to make miri work before stabilizing this macro +/// #![feature(return_address)] +/// +/// let addr = core::arch::return_address!(); +/// println!("Caller is {addr:p}"); +/// ``` +#[unstable(feature = "return_address", issue = "154966")] +#[allow_internal_unstable(core_intrinsics)] +pub macro return_address() {{ core::intrinsics::return_address() }} diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index c71085ef9f97d..06e8e19a4ef44 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -3589,3 +3589,16 @@ pub const fn va_copy<'f>(src: &VaList<'f>) -> VaList<'f> { pub const unsafe fn va_end(ap: &mut VaList<'_>) { /* deliberately does nothing */ } + +/// Returns the return address of the caller function (after inlining) in a best-effort manner or a null pointer if it is not supported on the current backend. +/// Returning an accurate value is a quality-of-implementation concern, but no hard guarantees are +/// made about the return value: formally, the intrinsic non-deterministically returns +/// an arbitrary pointer without provenance. +/// +/// Note that unlike most intrinsics, this is safe to call. This is because it only finds the return address of the immediate caller, which is guaranteed to be possible. +/// Other forms of the corresponding gcc or llvm intrinsic (which can have wildly unpredictable results or even crash at runtime) are not exposed. +#[rustc_intrinsic] +#[rustc_nounwind] +pub fn return_address() -> *const () { + core::ptr::null() +} diff --git a/library/std/src/os/windows/fs.rs b/library/std/src/os/windows/fs.rs index 7fd46b31f7d83..54d5cafe15ec6 100644 --- a/library/std/src/os/windows/fs.rs +++ b/library/std/src/os/windows/fs.rs @@ -4,11 +4,11 @@ #![stable(feature = "rust1", since = "1.0.0")] -use crate::fs::{self, Metadata, OpenOptions}; +use crate::fs::{self, Metadata, OpenOptions, Permissions}; use crate::io::BorrowedCursor; use crate::path::Path; use crate::sealed::Sealed; -use crate::sys::{AsInner, AsInnerMut, IntoInner}; +use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner}; use crate::time::SystemTime; use crate::{io, sys}; @@ -368,6 +368,68 @@ impl OpenOptionsExt2 for OpenOptions { } } +/// Windows-specific extensions to [`fs::Permissions`]. This extension trait +/// provides extra utilities to shows what Windows file attributes are enabled +/// in [`Permissions`] and to manually set file attributes on [`Permissions`]. +/// +/// See Microsoft's [`File Attribute Constants`] page to know what file +/// attribute metadata are defined and stored on Windows files. +/// +/// [`Permissions`]: fs::Permissions +/// [`File Attribute Constants`]: +/// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants +/// +/// # Example +/// +/// ```no_run +/// #![feature(windows_permissions_ext)] +/// use std::fs::Permissions; +/// use std::os::windows::fs::PermissionsExt; +/// +/// const FILE_ATTRIBUTE_SYSTEM: u32 = 0x4; +/// const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x20; +/// let my_file_attr = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE; +/// let mut permissions = Permissions::from_file_attributes(my_file_attr); +/// assert_eq!(permissions.file_attributes(), my_file_attr); +/// +/// const FILE_ATTRIBUTE_HIDDEN: u32 = 0x2; +/// let new_file_attr = permissions.file_attributes() | FILE_ATTRIBUTE_HIDDEN; +/// permissions.set_file_attributes(new_file_attr); +/// assert_eq!(permissions.file_attributes(), new_file_attr); +/// ``` +#[unstable(feature = "windows_permissions_ext", issue = "152956")] +pub trait PermissionsExt: Sealed { + /// Returns the file attribute bits. + #[unstable(feature = "windows_permissions_ext", issue = "152956")] + fn file_attributes(&self) -> u32; + + /// Sets the file attribute bits. + #[unstable(feature = "windows_permissions_ext", issue = "152956")] + fn set_file_attributes(&mut self, mask: u32); + + /// Creates a new instance from the given file attribute bits. + #[unstable(feature = "windows_permissions_ext", issue = "152956")] + fn from_file_attributes(mask: u32) -> Self; +} + +#[unstable(feature = "windows_permissions_ext", issue = "152956")] +impl Sealed for fs::Permissions {} + +#[unstable(feature = "windows_permissions_ext", issue = "152956")] +impl PermissionsExt for fs::Permissions { + fn file_attributes(&self) -> u32 { + self.as_inner().file_attributes() + } + + fn set_file_attributes(&mut self, mask: u32) { + *self = Permissions::from_inner(FromInner::from_inner(mask)); + } + + fn from_file_attributes(mask: u32) -> Self { + Permissions::from_inner(FromInner::from_inner(mask)) + } +} + /// Windows-specific extensions to [`fs::Metadata`]. /// /// The data members that this trait exposes correspond to the members diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index 74854cdeb498d..e0b02670264d9 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -1167,6 +1167,16 @@ impl FilePermissions { self.attrs &= !c::FILE_ATTRIBUTE_READONLY; } } + + pub fn file_attributes(&self) -> u32 { + self.attrs as u32 + } +} + +impl FromInner for FilePermissions { + fn from_inner(attrs: u32) -> FilePermissions { + FilePermissions { attrs } + } } impl FileTimes { diff --git a/library/std/src/sys/process/windows/child_pipe.rs b/library/std/src/sys/process/windows/child_pipe.rs index b848435ac275f..8d71e1c61f82d 100644 --- a/library/std/src/sys/process/windows/child_pipe.rs +++ b/library/std/src/sys/process/windows/child_pipe.rs @@ -1,6 +1,8 @@ use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; use crate::ops::Neg; use crate::os::windows::prelude::*; +use crate::sync::atomic::Atomic; +use crate::sync::atomic::Ordering::Relaxed; use crate::sys::handle::Handle; use crate::sys::{FromInner, IntoInner, api, c}; use crate::{mem, ptr}; @@ -70,10 +72,19 @@ pub(super) fn child_pipe(ours_readable: bool, their_handle_inheritable: bool) -> let mut object_attributes = c::OBJECT_ATTRIBUTES::default(); object_attributes.Length = size_of::() as u32; - // Open a handle to the pipe filesystem (`\??\PIPE\`). - // This will be used when creating a new annon pipe. - let pipe_fs = { - let path = api::unicode_str!(r"\??\PIPE\"); + // Open a handle to the pipe filesystem (`\Device\NamedPipe\`). + // This will be used when creating a new anonymous pipe. + // + // We cache the handle once so we can reuse it without needing to reopen it each time. + // NOTE: this means the handle may appear to be leaked but that's fine because + // it's only one handle and the OS will clean it up when the process exits. + static PIPE_FS: Atomic = Atomic::::new(ptr::null_mut()); + let pipe_fs = if let handle = PIPE_FS.load(Relaxed) + && !handle.is_null() + { + handle + } else { + let path = api::unicode_str!(r"\Device\NamedPipe\"); object_attributes.ObjectName = path.as_ptr(); let mut pipe_fs = ptr::null_mut(); let status = c::NtOpenFile( @@ -85,7 +96,13 @@ pub(super) fn child_pipe(ours_readable: bool, their_handle_inheritable: bool) -> c::FILE_SYNCHRONOUS_IO_NONALERT, // synchronous access ); if c::nt_success(status) { - Handle::from_raw_handle(pipe_fs) + match PIPE_FS.compare_exchange(ptr::null_mut(), pipe_fs, Relaxed, Relaxed) { + Ok(_) => pipe_fs, + Err(existing) => { + c::CloseHandle(pipe_fs); + existing + } + } } else { return Err(io::Error::from_raw_os_error(c::RtlNtStatusToDosError(status) as i32)); } @@ -104,7 +121,7 @@ pub(super) fn child_pipe(ours_readable: bool, their_handle_inheritable: bool) -> let ours = { // Use the pipe filesystem as the root directory. // With no name provided, an anonymous pipe will be created. - object_attributes.RootDirectory = pipe_fs.as_raw_handle(); + object_attributes.RootDirectory = pipe_fs; // A negative timeout value is a relative time (rather than an absolute time). // The time is given in 100's of nanoseconds so this is 50 milliseconds. diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock index 25005693117ea..3a810a6415d15 100644 --- a/src/tools/miri/Cargo.lock +++ b/src/tools/miri/Cargo.lock @@ -1013,9 +1013,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" dependencies = [ "cc", "libc", diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index e9fc6c4cd023e..5ab8e6c9d9ba7 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -e22c616e4e87914135c1db261a03e0437255335e +9836b06b55f5389f605ee7766eeecd9f17a86cb5 diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index e9458cc3f4568..f72bfe031d36b 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -111,8 +111,8 @@ pub enum BlockReason { Epoll, /// Blocked on eventfd. Eventfd, - /// Blocked on unnamed_socket. - UnnamedSocket, + /// Blocked on virtual socket. + VirtualSocket, /// Blocked on an IO operation. IO, /// Blocked for any reason related to GenMC, such as `assume` statements (GenMC mode only). diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index f41e3c20a7d54..5f3bf4a9e9e3e 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -105,8 +105,9 @@ pub use rustc_const_eval::interpret::*; // Resolve ambiguity. #[doc(no_inline)] pub use rustc_const_eval::interpret::{self, AllocMap, Provenance as _}; -use rustc_log::tracing::{self, info, trace}; -use rustc_middle::{bug, span_bug}; +pub use rustc_data_structures::either::Either; +pub use rustc_log::tracing::{self, info, trace}; +pub use rustc_middle::{bug, span_bug}; #[cfg(all(feature = "native-lib", unix))] pub mod native_lib { diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index 5468fd3037425..04b84e6f3e67c 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -1,6 +1,6 @@ use std::any::Any; use std::collections::BTreeMap; -use std::fs::{File, Metadata}; +use std::fs::File; use std::io::{ErrorKind, IsTerminal, Read, Seek, SeekFrom, Write}; use std::marker::CoercePointee; use std::ops::Deref; @@ -209,7 +209,11 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt { throw_unsup_format!("cannot close {}", self.name()); } - fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { + /// Returns the metadata for this FD, if available. + /// This is either host metadata, or a non-file-backed-FD type. + /// The latter is for new represented as a string storing a `libc` name so we only + /// support that kind of metadata on Unix targets. + fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either, &'static str>> { throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors"); } @@ -432,8 +436,8 @@ impl FileDescription for FileHandle { } } - fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { - interp_ok(self.file.metadata()) + fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either, &'static str>> { + interp_ok(Either::Left(self.file.metadata())) } fn is_tty(&self, communicate_allowed: bool) -> bool { diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 5adc5932883ef..0988edaef2b0f 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -229,17 +229,22 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0)); let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0)); let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0)); - let mode = metadata.mode.to_uint(this.libc_ty_layout("mode_t").size)?; // We do *not* use `deref_pointer_as` here since determining the right pointee type // is highly non-trivial: it depends on which exact alias of the function was invoked // (e.g. `fstat` vs `fstat64`), and then on FreeBSD it also depends on the ABI level // which can be different between the libc used by std and the libc used by everyone else. let buf = this.deref_pointer(buf_op)?; + + // `libc::S_IF*` constants are of type `mode_t`, which varies in width across targets + // (`u16` on macOS, `u32` on Linux). Read the scalar using `mode_t`'s size on the target. + let mode_t_size = this.libc_ty_layout("mode_t").size; + let mode: u32 = metadata.mode.to_uint(mode_t_size)?.try_into().unwrap(); + this.write_int_fields_named( &[ ("st_dev", metadata.dev.into()), - ("st_mode", mode.try_into().unwrap()), + ("st_mode", mode.into()), ("st_nlink", 0), ("st_ino", 0), ("st_uid", metadata.uid.into()), @@ -747,13 +752,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Err(err) => return this.set_last_error_and_return_i32(err), }; - // The `mode` field specifies the type of the file and the permissions over the file for - // the owner, its group and other users. Given that we can only provide the file type - // without using platform specific methods, we only set the bits corresponding to the file - // type. This should be an `__u16` but `libc` provides its values as `u32`. + // `statx.stx_mode` is `__u16`. `libc::S_IF*` are of type `mode_t`, which varies in + // width across targets (`u16` on macOS, `u32` on Linux). Read using `mode_t`'s size. + let mode_t_size = this.libc_ty_layout("mode_t").size; let mode: u16 = metadata .mode - .to_u32()? + .to_uint(mode_t_size)? .try_into() .unwrap_or_else(|_| bug!("libc contains bad value for constant")); @@ -1630,6 +1634,34 @@ fn extract_sec_and_nsec<'tcx>( } } +fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str { + #[cfg(unix)] + use std::os::unix::fs::FileTypeExt; + + if file_type.is_file() { + "S_IFREG" + } else if file_type.is_dir() { + "S_IFDIR" + } else if file_type.is_symlink() { + "S_IFLNK" + } else { + // Certain file types are only available when the host is a Unix system. + #[cfg(unix)] + { + if file_type.is_socket() { + return "S_IFSOCK"; + } else if file_type.is_fifo() { + return "S_IFIFO"; + } else if file_type.is_char_device() { + return "S_IFCHR"; + } else if file_type.is_block_device() { + return "S_IFBLK"; + } + } + "S_IFREG" + } +} + /// Stores a file's metadata in order to avoid code duplication in the different metadata related /// shims. struct FileMetadata { @@ -1662,10 +1694,27 @@ impl FileMetadata { let Some(fd) = ecx.machine.fds.get(fd_num) else { return interp_ok(Err(LibcError("EBADF"))); }; + match fd.metadata()? { + Either::Left(host) => Self::from_meta(ecx, host), + Either::Right(name) => Self::synthetic(ecx, name), + } + } - let metadata = fd.metadata()?; - drop(fd); - FileMetadata::from_meta(ecx, metadata) + fn synthetic<'tcx>( + ecx: &mut MiriInterpCx<'tcx>, + mode_name: &str, + ) -> InterpResult<'tcx, Result> { + let mode = ecx.eval_libc(mode_name); + interp_ok(Ok(FileMetadata { + mode, + size: 0, + created: None, + accessed: None, + modified: None, + dev: 0, + uid: 0, + gid: 0, + })) } fn from_meta<'tcx>( @@ -1680,16 +1729,7 @@ impl FileMetadata { }; let file_type = metadata.file_type(); - - let mode_name = if file_type.is_file() { - "S_IFREG" - } else if file_type.is_dir() { - "S_IFDIR" - } else { - "S_IFLNK" - }; - - let mode = ecx.eval_libc(mode_name); + let mode = ecx.eval_libc(file_type_to_mode_name(file_type)); let size = metadata.len(); diff --git a/src/tools/miri/src/shims/unix/linux_like/epoll.rs b/src/tools/miri/src/shims/unix/linux_like/epoll.rs index 7480db00d6ed3..bd07e13d47fbb 100644 --- a/src/tools/miri/src/shims/unix/linux_like/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux_like/epoll.rs @@ -119,6 +119,13 @@ impl FileDescription for Epoll { "epoll" } + fn metadata<'tcx>( + &self, + ) -> InterpResult<'tcx, Either, &'static str>> { + // On Linux, epoll is an "anonymous inode" reported as S_IFREG. + interp_ok(Either::Right("S_IFREG")) + } + fn destroy<'tcx>( mut self, self_id: FdId, diff --git a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs index d374a1e75f72e..7cccbd0e275c5 100644 --- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs +++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs @@ -37,6 +37,13 @@ impl FileDescription for EventFd { "event" } + fn metadata<'tcx>( + &self, + ) -> InterpResult<'tcx, Either, &'static str>> { + // On Linux, eventfd is an "anonymous inode" reported as S_IFREG. + interp_ok(Either::Right("S_IFREG")) + } + fn destroy<'tcx>( self, _self_id: FdId, diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs index 5ea49926fb9fd..c55a28bfa7b2a 100644 --- a/src/tools/miri/src/shims/unix/mod.rs +++ b/src/tools/miri/src/shims/unix/mod.rs @@ -7,7 +7,7 @@ mod mem; mod socket; mod sync; mod thread; -mod unnamed_socket; +mod virtual_socket; mod android; mod freebsd; @@ -25,7 +25,7 @@ pub use self::mem::EvalContextExt as _; pub use self::socket::EvalContextExt as _; pub use self::sync::EvalContextExt as _; pub use self::thread::{EvalContextExt as _, ThreadNameResult}; -pub use self::unnamed_socket::EvalContextExt as _; +pub use self::virtual_socket::EvalContextExt as _; // Make up some constants. const UID: u32 = 1000; diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index 9d7d5a32f127b..c553cd1f70e8b 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -7,7 +7,6 @@ use std::{io, iter}; use mio::Interest; use mio::event::Source; use mio::net::{TcpListener, TcpStream}; -use rand::Rng; use rustc_abi::Size; use rustc_const_eval::interpret::{InterpResult, interp_ok}; use rustc_middle::throw_unsup_format; @@ -168,8 +167,13 @@ impl FileDescription for Socket { } fn short_fd_operations(&self) -> bool { - // Short accesses on TCP sockets are realistic and expected to happen. - true + // Linux de-facto guarantees (or at least, applications like tokio assume [1, 2]) that + // when a read/write on a streaming socket comes back short, the kernel buffer is + // empty/full. SO we can't do short reads/writes here. + // + // [1]: https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L182 + // [2]: https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L240 + false } fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription { @@ -652,18 +656,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest); }; - // Non-deterministically decide to further reduce the length, simulating a partial send. - // We avoid reducing the write size to 0: the docs seem to be entirely fine with that, - // but the standard library is not (https://github.com/rust-lang/rust/issues/145959). - let length = if this.machine.short_fd_operations - && length >= 2 - && this.machine.rng.get_mut().random() - { - length / 2 - } else { - length - }; - let mut is_op_non_block = false; // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so @@ -774,21 +766,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest); }; - // Non-deterministically decide to further reduce the length, simulating a partial receive. - // We don't simulate partial receives for lengths < 2 because the man page states that a - // return value of zero can only be returned in some special cases: - // "When a stream socket peer has performed an orderly shutdown, the return value will be 0 - // (the traditional "end-of-file" return). [...] The value 0 may also be returned if the - // requested number of bytes to receive from a stream socket was 0." - let length = if this.machine.short_fd_operations - && length >= 2 - && this.machine.rng.get_mut().random() - { - length / 2 // since `length` is at least 2, the result is still at least 1 - } else { - length - }; - let mut should_peek = false; let mut is_op_non_block = false; @@ -1502,6 +1479,8 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // This is a *non-blocking* write. let result = this.write_to_host(stream, length, buffer_ptr)?; + // FIXME: When the host does a short write, we should emit an epoll edge -- at least for targets for which tokio assumes no short writes: + // match result { Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::NotConnected => { // On Windows hosts, `send` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK @@ -1578,6 +1557,8 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { length, buffer_ptr, )?; + // FIXME: When the host does a short read, we should emit an epoll edge -- at least for targets for which tokio assumes no short reads: + // match result { Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::NotConnected => { // On Windows hosts, `recv` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK diff --git a/src/tools/miri/src/shims/unix/unnamed_socket.rs b/src/tools/miri/src/shims/unix/virtual_socket.rs similarity index 87% rename from src/tools/miri/src/shims/unix/unnamed_socket.rs rename to src/tools/miri/src/shims/unix/virtual_socket.rs index 74d6abf7b63d4..51bd30840ffbd 100644 --- a/src/tools/miri/src/shims/unix/unnamed_socket.rs +++ b/src/tools/miri/src/shims/unix/virtual_socket.rs @@ -1,6 +1,6 @@ -//! This implements "anonymous" sockets, that do not correspond to anything on the host system and +//! This implements "virtual" sockets, that do not correspond to anything on the host system and //! are entirely implemented inside Miri. -//! We also use the same infrastructure to implement unnamed pipes. +//! This is used to implement `socketpair` and `pipe`. use std::cell::{Cell, OnceCell, RefCell}; use std::collections::VecDeque; @@ -22,7 +22,7 @@ use crate::*; const MAX_SOCKETPAIR_BUFFER_CAPACITY: usize = 0x34000; #[derive(Debug, PartialEq)] -enum AnonSocketType { +enum VirtualSocketType { // Either end of the socketpair fd. Socketpair, // Read end of the pipe. @@ -31,16 +31,16 @@ enum AnonSocketType { PipeWrite, } -/// One end of a pair of connected unnamed sockets. +/// One end of a pair of connected virtual sockets. #[derive(Debug)] -struct AnonSocket { +struct VirtualSocket { /// The buffer we are reading from, or `None` if this is the writing end of a pipe. /// (In that case, the peer FD will be the reading end of that pipe.) readbuf: Option>, - /// The `AnonSocket` file descriptor that is our "peer", and that holds the buffer we are + /// The `VirtualSocket` file descriptor that is our "peer", and that holds the buffer we are /// writing to. This is a weak reference because the other side may be closed before us; all /// future writes will then trigger EPIPE. - peer_fd: OnceCell>, + peer_fd: OnceCell>, /// Indicates whether the peer has lost data when the file description is closed. /// This flag is set to `true` if the peer's `readbuf` is non-empty at the time /// of closure. @@ -53,8 +53,8 @@ struct AnonSocket { blocked_write_tid: RefCell>, /// Whether this fd is non-blocking or not. is_nonblock: Cell, - // Differentiate between different AnonSocket fd types. - fd_type: AnonSocketType, + // Differentiate between different virtual socket fd types. + fd_type: VirtualSocketType, } #[derive(Debug)] @@ -69,20 +69,30 @@ impl Buffer { } } -impl AnonSocket { - fn peer_fd(&self) -> &WeakFileDescriptionRef { +impl VirtualSocket { + fn peer_fd(&self) -> &WeakFileDescriptionRef { self.peer_fd.get().unwrap() } } -impl FileDescription for AnonSocket { +impl FileDescription for VirtualSocket { fn name(&self) -> &'static str { match self.fd_type { - AnonSocketType::Socketpair => "socketpair", - AnonSocketType::PipeRead | AnonSocketType::PipeWrite => "pipe", + VirtualSocketType::Socketpair => "socketpair", + VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => "pipe", } } + fn metadata<'tcx>( + &self, + ) -> InterpResult<'tcx, Either, &'static str>> { + let mode_name = match self.fd_type { + VirtualSocketType::Socketpair => "S_IFSOCK", + VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => "S_IFIFO", + }; + interp_ok(Either::Right(mode_name)) + } + fn destroy<'tcx>( self, _self_id: FdId, @@ -111,7 +121,7 @@ impl FileDescription for AnonSocket { ecx: &mut MiriInterpCx<'tcx>, finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { - anonsocket_read(self, ptr, len, ecx, finish) + virtual_socket_read(self, ptr, len, ecx, finish) } fn write<'tcx>( @@ -122,15 +132,17 @@ impl FileDescription for AnonSocket { ecx: &mut MiriInterpCx<'tcx>, finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { - anonsocket_write(self, ptr, len, ecx, finish) + virtual_socket_write(self, ptr, len, ecx, finish) } fn short_fd_operations(&self) -> bool { - // Pipes guarantee that sufficiently small accesses are not broken apart: - // . - // For now, we don't bother checking for the size, and just entirely disable - // short accesses on pipes. - matches!(self.fd_type, AnonSocketType::Socketpair) + // Linux de-facto guarantees (or at least, applications like tokio assume [1, 2]) that + // when a read/write on a streaming socket comes back short, the kernel buffer is + // empty/full. SO we can't do short reads/writes here. + // + // [1]: https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L182 + // [2]: https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L240 + false } fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription { @@ -145,13 +157,13 @@ impl FileDescription for AnonSocket { // fd is closed, so we need to look at the original type of this socket, not at whether // the peer socket still exists. match self.fd_type { - AnonSocketType::Socketpair => { + VirtualSocketType::Socketpair => { flags |= ecx.eval_libc_i32("O_RDWR"); } - AnonSocketType::PipeRead => { + VirtualSocketType::PipeRead => { flags |= ecx.eval_libc_i32("O_RDONLY"); } - AnonSocketType::PipeWrite => { + VirtualSocketType::PipeWrite => { flags |= ecx.eval_libc_i32("O_WRONLY"); } } @@ -190,9 +202,9 @@ impl FileDescription for AnonSocket { } } -/// Write to AnonSocket based on the space available and return the written byte size. -fn anonsocket_write<'tcx>( - self_ref: FileDescriptionRef, +/// Write to VirtualSocket based on the space available and return the written byte size. +fn virtual_socket_write<'tcx>( + self_ref: FileDescriptionRef, ptr: Pointer, len: usize, ecx: &mut MiriInterpCx<'tcx>, @@ -228,11 +240,11 @@ fn anonsocket_write<'tcx>( // Block the current thread; only keep a weak ref for this. let weak_self_ref = FileDescriptionRef::downgrade(&self_ref); ecx.block_thread( - BlockReason::UnnamedSocket, + BlockReason::VirtualSocket, None, callback!( @capture<'tcx> { - weak_self_ref: WeakFileDescriptionRef, + weak_self_ref: WeakFileDescriptionRef, ptr: Pointer, len: usize, finish: DynMachineCallback<'tcx, Result>, @@ -242,7 +254,7 @@ fn anonsocket_write<'tcx>( // If we got unblocked, then our peer successfully upgraded its weak // ref to us. That means we can also upgrade our weak ref. let self_ref = weak_self_ref.upgrade().unwrap(); - anonsocket_write(self_ref, ptr, len, this, finish) + virtual_socket_write(self_ref, ptr, len, this, finish) } ), ); @@ -266,7 +278,7 @@ fn anonsocket_write<'tcx>( let waiting_threads = std::mem::take(&mut *peer_fd.blocked_read_tid.borrow_mut()); // FIXME: We can randomize the order of unblocking. for thread_id in waiting_threads { - ecx.unblock_thread(thread_id, BlockReason::UnnamedSocket)?; + ecx.unblock_thread(thread_id, BlockReason::VirtualSocket)?; } // Notify epoll waiters: we might be no longer writable, peer might now be readable. // The notification to the peer seems to be always sent on Linux, even if the @@ -279,9 +291,9 @@ fn anonsocket_write<'tcx>( interp_ok(()) } -/// Read from AnonSocket and return the number of bytes read. -fn anonsocket_read<'tcx>( - self_ref: FileDescriptionRef, +/// Read from VirtualSocket and return the number of bytes read. +fn virtual_socket_read<'tcx>( + self_ref: FileDescriptionRef, ptr: Pointer, len: usize, ecx: &mut MiriInterpCx<'tcx>, @@ -316,11 +328,11 @@ fn anonsocket_read<'tcx>( // Block the current thread; only keep a weak ref for this. let weak_self_ref = FileDescriptionRef::downgrade(&self_ref); ecx.block_thread( - BlockReason::UnnamedSocket, + BlockReason::VirtualSocket, None, callback!( @capture<'tcx> { - weak_self_ref: WeakFileDescriptionRef, + weak_self_ref: WeakFileDescriptionRef, ptr: Pointer, len: usize, finish: DynMachineCallback<'tcx, Result>, @@ -330,7 +342,7 @@ fn anonsocket_read<'tcx>( // If we got unblocked, then our peer successfully upgraded its weak // ref to us. That means we can also upgrade our weak ref. let self_ref = weak_self_ref.upgrade().unwrap(); - anonsocket_read(self_ref, ptr, len, this, finish) + virtual_socket_read(self_ref, ptr, len, this, finish) } ), ); @@ -363,7 +375,7 @@ fn anonsocket_read<'tcx>( let waiting_threads = std::mem::take(&mut *peer_fd.blocked_write_tid.borrow_mut()); // FIXME: We can randomize the order of unblocking. for thread_id in waiting_threads { - ecx.unblock_thread(thread_id, BlockReason::UnnamedSocket)?; + ecx.unblock_thread(thread_id, BlockReason::VirtualSocket)?; } // Notify epoll waiters: peer is now writable. // Linux seems to always notify the peer if the read buffer is now empty. @@ -379,7 +391,7 @@ fn anonsocket_read<'tcx>( interp_ok(()) } -impl UnixFileDescription for AnonSocket { +impl UnixFileDescription for VirtualSocket { fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollEvents> { // We only check the status of EPOLLIN, EPOLLOUT, EPOLLHUP and EPOLLRDHUP flags. // If other event flags need to be supported in the future, the check should be added here. @@ -487,23 +499,23 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Generate file descriptions. let fds = &mut this.machine.fds; - let fd0 = fds.new_ref(AnonSocket { + let fd0 = fds.new_ref(VirtualSocket { readbuf: Some(RefCell::new(Buffer::new())), peer_fd: OnceCell::new(), peer_lost_data: Cell::new(false), blocked_read_tid: RefCell::new(Vec::new()), blocked_write_tid: RefCell::new(Vec::new()), is_nonblock: Cell::new(is_sock_nonblock), - fd_type: AnonSocketType::Socketpair, + fd_type: VirtualSocketType::Socketpair, }); - let fd1 = fds.new_ref(AnonSocket { + let fd1 = fds.new_ref(VirtualSocket { readbuf: Some(RefCell::new(Buffer::new())), peer_fd: OnceCell::new(), peer_lost_data: Cell::new(false), blocked_read_tid: RefCell::new(Vec::new()), blocked_write_tid: RefCell::new(Vec::new()), is_nonblock: Cell::new(is_sock_nonblock), - fd_type: AnonSocketType::Socketpair, + fd_type: VirtualSocketType::Socketpair, }); // Make the file descriptions point to each other. @@ -557,23 +569,23 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Generate file descriptions. // pipefd[0] refers to the read end of the pipe. let fds = &mut this.machine.fds; - let fd0 = fds.new_ref(AnonSocket { + let fd0 = fds.new_ref(VirtualSocket { readbuf: Some(RefCell::new(Buffer::new())), peer_fd: OnceCell::new(), peer_lost_data: Cell::new(false), blocked_read_tid: RefCell::new(Vec::new()), blocked_write_tid: RefCell::new(Vec::new()), is_nonblock: Cell::new(is_nonblock), - fd_type: AnonSocketType::PipeRead, + fd_type: VirtualSocketType::PipeRead, }); - let fd1 = fds.new_ref(AnonSocket { + let fd1 = fds.new_ref(VirtualSocket { readbuf: None, peer_fd: OnceCell::new(), peer_lost_data: Cell::new(false), blocked_read_tid: RefCell::new(Vec::new()), blocked_write_tid: RefCell::new(Vec::new()), is_nonblock: Cell::new(is_nonblock), - fd_type: AnonSocketType::PipeWrite, + fd_type: VirtualSocketType::PipeWrite, }); // Make the file descriptions point to each other. diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs index 1ee93cf911c5a..a6efc36d75f6c 100644 --- a/src/tools/miri/src/shims/windows/fs.rs +++ b/src/tools/miri/src/shims/windows/fs.rs @@ -22,8 +22,10 @@ impl FileDescription for DirHandle { "directory" } - fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { - interp_ok(self.path.metadata()) + fn metadata<'tcx>( + &self, + ) -> InterpResult<'tcx, Either, &'static str>> { + interp_ok(Either::Left(self.path.metadata())) } fn destroy<'tcx>( @@ -49,8 +51,10 @@ impl FileDescription for MetadataHandle { "metadata-only" } - fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { - interp_ok(Ok(self.meta.clone())) + fn metadata<'tcx>( + &self, + ) -> InterpResult<'tcx, Either, &'static str>> { + interp_ok(Either::Left(Ok(self.meta.clone()))) } fn destroy<'tcx>( @@ -329,11 +333,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; let metadata = match desc.metadata()? { - Ok(meta) => meta, - Err(e) => { + Either::Left(Ok(meta)) => meta, + Either::Left(Err(e)) => { this.set_last_error(e)?; return interp_ok(this.eval_windows("c", "FALSE")); } + Either::Right(_mode) => + throw_unsup_format!( + "`GetFileInformationByHandle` is not supported on non-file-backed handles" + ), }; let size = metadata.len(); diff --git a/src/tools/miri/src/shims/x86/avx512.rs b/src/tools/miri/src/shims/x86/avx512.rs index 8e1d22d723e79..fe4adf971c0d7 100644 --- a/src/tools/miri/src/shims/x86/avx512.rs +++ b/src/tools/miri/src/shims/x86/avx512.rs @@ -104,8 +104,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { pmaddbw(this, left, right, dest)?; } - // Used to implement the _mm512_permutexvar_epi32 function. - "permvar.si.512" => { + // Used to implement the _mm512_permutexvar_epi32/_mm512_permutexvar_epi64 functions. + "permvar.si.512" | "permvar.di.512" => { let [left, right] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; diff --git a/src/tools/miri/src/shims/x86/mod.rs b/src/tools/miri/src/shims/x86/mod.rs index ce6538c8ca273..e6e7f4b6f09f9 100644 --- a/src/tools/miri/src/shims/x86/mod.rs +++ b/src/tools/miri/src/shims/x86/mod.rs @@ -1056,12 +1056,22 @@ fn pmaddbw<'tcx>( interp_ok(()) } -/// Shuffle 32-bit integers in `values` across lanes using the corresponding -/// index in `indices`, and store the results in dst. +/// Shuffle elements in `values` across lanes using the corresponding index in +/// `indices`, and store the results in `dest`. +/// +/// This helper is shared by both the 32-bit-lane and 64-bit-lane AVX +/// permute-by-index intrinsics. The element type is taken from `values` and +/// `dest`, while the index lanes are interpreted at their full width (`i32` or +/// `i64`, depending on the intrinsic). +/// +/// For a vector with `N` lanes, only the low `log2(N)` bits of each index are +/// used. Equivalently, lane `i` of the result is copied from +/// `values[indices[i] & (N - 1)]`. /// /// /// /// +/// fn permute<'tcx>( ecx: &mut crate::MiriInterpCx<'tcx>, values: &OpTy<'tcx>, @@ -1075,18 +1085,21 @@ fn permute<'tcx>( // fn permd(a: u32x8, b: u32x8) -> u32x8; // fn permps(a: __m256, b: i32x8) -> __m256; // fn vpermd(a: i32x16, idx: i32x16) -> i32x16; + // fn vpermq(a: i64x8, b: i64x8) -> i64x8; assert_eq!(dest_len, values_len); assert_eq!(dest_len, indices_len); // Only use the lower 3 bits to index into a vector with 8 lanes, // or the lower 4 bits when indexing into a 16-lane vector. assert!(dest_len.is_power_of_two()); - let mask = u32::try_from(dest_len).unwrap().strict_sub(1); + let mask = u128::from(dest_len).strict_sub(1); for i in 0..dest_len { let dest = ecx.project_index(&dest, i)?; - let index = ecx.read_scalar(&ecx.project_index(&indices, i)?)?.to_u32()?; - let element = ecx.project_index(&values, (index & mask).into())?; + let index_place = ecx.project_index(&indices, i)?; + let index = ecx.read_scalar(&index_place)?.to_uint(index_place.layout.size)?; + // `mask` is at most `dest_len - 1` which fits in a `u64`, so this cannot fail. + let element = ecx.project_index(&values, u64::try_from(index & mask).unwrap())?; ecx.copy_op(&element, &dest)?; } diff --git a/src/tools/miri/tests/deps/Cargo.lock b/src/tools/miri/tests/deps/Cargo.lock index 2a1468d55ad0d..1c31cc3f6120e 100644 --- a/src/tools/miri/tests/deps/Cargo.lock +++ b/src/tools/miri/tests/deps/Cargo.lock @@ -45,7 +45,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -281,7 +281,7 @@ checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -298,7 +298,7 @@ dependencies = [ "page_size", "tempfile", "tokio", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -383,7 +383,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -463,7 +463,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -487,7 +487,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -503,7 +503,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -666,15 +666,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -684,71 +675,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/src/tools/miri/tests/deps/Cargo.toml b/src/tools/miri/tests/deps/Cargo.toml index fd301fc5cf321..73b3025212ec5 100644 --- a/src/tools/miri/tests/deps/Cargo.toml +++ b/src/tools/miri/tests/deps/Cargo.toml @@ -26,7 +26,7 @@ tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "net", futures = { version = "0.3.0", default-features = false, features = ["alloc", "async-await"] } [target.'cfg(windows)'.dependencies] -windows-sys = { version = "0.60", features = [ +windows-sys = { version = "0.61", features = [ "Win32_Foundation", "Win32_System_Threading", "Win32_Storage_FileSystem", diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs b/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs new file mode 100644 index 0000000000000..c29c8ceaa1dfc --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs @@ -0,0 +1,108 @@ +//@ignore-target: windows # No libc fstat on non-file FDs on Windows +//@compile-flags: -Zmiri-disable-isolation + +use std::mem::MaybeUninit; + +#[path = "../../utils/libc.rs"] +mod libc_utils; +use libc_utils::errno_check; + +fn main() { + test_fstat_socketpair(); + test_fstat_pipe(); + #[cfg(target_os = "linux")] + test_fstat_eventfd(); + #[cfg(target_os = "linux")] + test_fstat_epoll(); +} + +/// Calls fstat and returns a reference to the result. +/// We use `assume_init_ref` rather than `assume_init` because not all fields +/// of `libc::stat` may be written by fstat (e.g. `st_lspare` on macOS). +fn do_fstat(fd: i32, buf: &mut MaybeUninit) -> &libc::stat { + let res = unsafe { libc::fstat(fd, buf.as_mut_ptr()) }; + assert_eq!(res, 0, "fstat failed on fd {}", fd); + unsafe { buf.assume_init_ref() } +} + +fn assert_stat_fields_are_accessible(stat: &libc::stat) { + let _st_nlink = stat.st_nlink; + let _st_blksize = stat.st_blksize; + let _st_blocks = stat.st_blocks; + let _st_ino = stat.st_ino; + let _st_dev = stat.st_dev; + let _st_uid = stat.st_uid; + let _st_gid = stat.st_gid; + let _st_rdev = stat.st_rdev; + let _st_atime = stat.st_atime; + let _st_mtime = stat.st_mtime; + let _st_ctime = stat.st_ctime; + let _st_atime_nsec = stat.st_atime_nsec; + let _st_mtime_nsec = stat.st_mtime_nsec; + let _st_ctime_nsec = stat.st_ctime_nsec; +} + +/// Test fstat on socketpair file descriptors. +fn test_fstat_socketpair() { + let mut fds = [0i32; 2]; + errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); + + for fd in fds.iter() { + let mut buf = MaybeUninit::uninit(); + let stat = do_fstat(*fd, &mut buf); + assert_eq!( + stat.st_mode & libc::S_IFMT, + libc::S_IFSOCK, + "socketpair should have S_IFSOCK mode" + ); + assert_eq!(stat.st_size, 0, "socketpair should have size 0"); + assert_stat_fields_are_accessible(stat); + } + + errno_check(unsafe { libc::close(fds[0]) }); + errno_check(unsafe { libc::close(fds[1]) }); +} + +/// Test fstat on pipe file descriptors. +fn test_fstat_pipe() { + let mut fds = [0i32; 2]; + errno_check(unsafe { libc::pipe(fds.as_mut_ptr()) }); + + for fd in fds.iter() { + let mut buf = MaybeUninit::uninit(); + let stat = do_fstat(*fd, &mut buf); + assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFIFO, "pipe should have S_IFIFO mode"); + assert_eq!(stat.st_size, 0, "pipe should have size 0"); + assert_stat_fields_are_accessible(stat); + } + + errno_check(unsafe { libc::close(fds[0]) }); + errno_check(unsafe { libc::close(fds[1]) }); +} + +/// Test fstat on eventfd file descriptors (Linux only). +#[cfg(target_os = "linux")] +fn test_fstat_eventfd() { + let flags = libc::EFD_CLOEXEC | libc::EFD_NONBLOCK; + let fd = libc_utils::errno_result(unsafe { libc::eventfd(0, flags) }).unwrap(); + + let mut buf = MaybeUninit::uninit(); + let stat = do_fstat(fd, &mut buf); + assert_eq!(stat.st_size, 0, "eventfd should have size 0"); + assert_stat_fields_are_accessible(stat); + + errno_check(unsafe { libc::close(fd) }); +} + +/// Test fstat on epoll file descriptors (Linux only). +#[cfg(target_os = "linux")] +fn test_fstat_epoll() { + let fd = libc_utils::errno_result(unsafe { libc::epoll_create1(libc::EPOLL_CLOEXEC) }).unwrap(); + + let mut buf = MaybeUninit::uninit(); + let stat = do_fstat(fd, &mut buf); + assert_eq!(stat.st_size, 0, "epoll should have size 0"); + assert_stat_fields_are_accessible(stat); + + errno_check(unsafe { libc::close(fd) }); +} diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs index 64c1e8d4c3a6e..c87677a756a00 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs @@ -11,7 +11,6 @@ use std::thread; use std::time::Duration; use libc_utils::*; -use utils::check_nondet; const TEST_BYTES: &[u8] = b"these are some test bytes!"; @@ -36,7 +35,6 @@ fn main() { test_accept_connect(); test_send_peek_recv(); - test_partial_send_recv(); test_write_read(); test_getsockname_ipv4(); @@ -295,50 +293,6 @@ fn test_send_peek_recv() { server_thread.join().unwrap(); } -/// Test that we actually do partial sends and partial receives for sockets. -fn test_partial_send_recv() { - let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); - let client_sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - - // Spawn the server thread. - let server_thread = thread::spawn(move || { - let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap(); - - // Yield back to client to test that we do incomplete writes. - thread::sleep(Duration::from_millis(10)); - - // We know the buffer contains enough bytes to test incomplete reads. - - // Ensure we sometimes do incomplete reads. - check_nondet(|| { - let mut buffer = [0u8; 4]; - let bytes_read = - unsafe { errno_result(libc::read(peerfd, buffer.as_mut_ptr().cast(), 4)).unwrap() }; - bytes_read == 4 - }); - }); - - net::connect_ipv4(client_sockfd, addr).unwrap(); - - // Ensure we sometimes do incomplete writes. - check_nondet(|| { - let bytes_written = - unsafe { errno_result(libc::write(client_sockfd, [0; 4].as_ptr().cast(), 4)).unwrap() }; - bytes_written == 4 - }); - - let buffer = [0u8; 100_000]; - // Write a lot of bytes into the socket such that we can test - // incomplete reads. - unsafe { - errno_result(libc_utils::write_all(client_sockfd, buffer.as_ptr().cast(), buffer.len())) - .unwrap() - }; - - server_thread.join().unwrap(); -} - /// Test writing bytes into a connected stream and then reading them /// from the other end. /// We want to test this because `write` and `read` should be the same as diff --git a/src/tools/miri/tests/pass/atomic.rs b/src/tools/miri/tests/pass/atomic.rs index d8ac5114f27fa..2231affc0c618 100644 --- a/src/tools/miri/tests/pass/atomic.rs +++ b/src/tools/miri/tests/pass/atomic.rs @@ -185,12 +185,12 @@ fn atomic_ptr() { } fn weak_sometimes_fails() { - let atomic = AtomicBool::new(false); + let atomic = AtomicUsize::new(0); let tries = 100; for _ in 0..tries { let cur = atomic.load(Relaxed); - // Try (weakly) to flip the flag. - if atomic.compare_exchange_weak(cur, !cur, Relaxed, Relaxed).is_err() { + // Try (weakly) to modify the flag. + if atomic.compare_exchange_weak(cur, cur + 1, Relaxed, Relaxed).is_err() { // We failed, so return and skip the panic. return; } diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs index e1e23eda84281..0417a4cbc6791 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs @@ -219,6 +219,30 @@ unsafe fn test_avx512() { } test_mm512_permutexvar_epi32(); + #[target_feature(enable = "avx512f")] + unsafe fn test_mm512_permutexvar_epi64() { + let a = _mm512_setr_epi64(100, 200, 300, 400, 500, 600, 700, 800); + + // Mirrors stdarch's basic sanity check. + let idx = _mm512_set1_epi64(1); + let r = _mm512_permutexvar_epi64(idx, a); + let e = _mm512_set1_epi64(200); + assert_eq_m512i(r, e); + + // This must permute across the full 512-bit register, not within 128-bit lanes. + let idx = _mm512_setr_epi64(7, 0, 5, 2, 6, 1, 4, 3); + let r = _mm512_permutexvar_epi64(idx, a); + let e = _mm512_setr_epi64(800, 100, 600, 300, 700, 200, 500, 400); + assert_eq_m512i(r, e); + + // Only the low 3 bits of each 64-bit index are used. + let idx = _mm512_setr_epi64(8, 15, -1, i64::MIN, 0, 1, 2, 3); + let r = _mm512_permutexvar_epi64(idx, a); + let e = _mm512_setr_epi64(100, 800, 800, 100, 100, 200, 300, 400); + assert_eq_m512i(r, e); + } + test_mm512_permutexvar_epi64(); + #[target_feature(enable = "avx512bw")] unsafe fn test_mm512_shuffle_epi8() { #[rustfmt::skip] diff --git a/tests/codegen-llvm/intrinsics/return_address.rs b/tests/codegen-llvm/intrinsics/return_address.rs new file mode 100644 index 0000000000000..5aa731d6383f5 --- /dev/null +++ b/tests/codegen-llvm/intrinsics/return_address.rs @@ -0,0 +1,12 @@ +//@ ignore-wasm + +#![crate_type = "lib"] +#![feature(core_intrinsics, return_address)] + +// CHECK-LABEL: @call_return_address_intrinsic +#[no_mangle] +#[inline(never)] +pub fn call_return_address_intrinsic() -> *const () { + // CHECK: call ptr @llvm.returnaddress(i32 0) + core::intrinsics::return_address() +} diff --git a/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.fixed b/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.fixed index 1ca5125fe8bc9..e7b443470b6ca 100644 --- a/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.fixed +++ b/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.fixed @@ -2,9 +2,9 @@ fn main() { let s = "123"; - println!("{:?} {} {}", {}, "sss", s); + println!("{:?} {} {}", "{}", "sss", s); //~^ ERROR format argument must be a string literal - println!("{:?}", {}); + println!("{:?}", "{}"); //~^ ERROR format argument must be a string literal println!("{} {} {} {:?}", s, "sss", s, {}); //~^ ERROR format argument must be a string literal diff --git a/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.stderr b/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.stderr index 81fca8c03cc1f..8a64db9218f39 100644 --- a/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.stderr +++ b/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.stderr @@ -8,6 +8,11 @@ help: you might be missing a string literal to format with | LL | println!("{:?} {} {}", {}, "sss", s); | +++++++++++++ +help: you might want to enclose `{}` with `""` + | +LL - println!({}, "sss", s); +LL + println!("{}", "sss", s); + | error: format argument must be a string literal --> $DIR/format-empty-block-unit-tuple-suggestion-130170.rs:7:14 @@ -19,6 +24,11 @@ help: you might be missing a string literal to format with | LL | println!("{:?}", {}); | +++++++ +help: you might want to enclose `{}` with `""` + | +LL - println!({}); +LL + println!("{}"); + | error: format argument must be a string literal --> $DIR/format-empty-block-unit-tuple-suggestion-130170.rs:9:14 diff --git a/tests/ui/macros/suggest-enclosing-format-string.rs b/tests/ui/macros/suggest-enclosing-format-string.rs new file mode 100644 index 0000000000000..ab1c6d2a1a19c --- /dev/null +++ b/tests/ui/macros/suggest-enclosing-format-string.rs @@ -0,0 +1,27 @@ +// Suggest enclosing the format string with `""` when it is one of `{}`, `{:?}`, and `{:#?}`. + +#[derive(Debug)] +enum UwU { + QwQ, + AwA, + QAQ, +} + +fn main() { + println!({}, UwU::QwQ); + //~^ ERROR format argument must be a string literal + //~| HELP you might be missing a string literal to format with + //~| HELP you might want to enclose `{}` with `""` + println!({:?}, UwU::QwQ); + //~^ ERROR expected expression, found `:` + //~| ERROR format argument must be a string literal + //~| HELP you might be missing a string literal to format with + //~| HELP maybe write a path separator here + //~| HELP you might want to enclose `{:?}` with `""` + println!({:#?}, UwU::QwQ); + //~^ ERROR expected expression, found `:` + //~| ERROR format argument must be a string literal + //~| HELP you might be missing a string literal to format with + //~| HELP maybe write a path separator here + //~| HELP you might want to enclose `{:#?}` with `""` +} diff --git a/tests/ui/macros/suggest-enclosing-format-string.stderr b/tests/ui/macros/suggest-enclosing-format-string.stderr new file mode 100644 index 0000000000000..64f46d39af343 --- /dev/null +++ b/tests/ui/macros/suggest-enclosing-format-string.stderr @@ -0,0 +1,72 @@ +error: format argument must be a string literal + --> $DIR/suggest-enclosing-format-string.rs:11:14 + | +LL | println!({}, UwU::QwQ); + | ^^ + | +help: you might be missing a string literal to format with + | +LL | println!("{:?} {}", {}, UwU::QwQ); + | ++++++++++ +help: you might want to enclose `{}` with `""` + | +LL - println!({}, UwU::QwQ); +LL + println!("{}", UwU::QwQ); + | + +error: expected expression, found `:` + --> $DIR/suggest-enclosing-format-string.rs:15:15 + | +LL | println!({:?}, UwU::QwQ); + | ^ expected expression + | +help: maybe write a path separator here + | +LL | println!({::?}, UwU::QwQ); + | + + +error: format argument must be a string literal + --> $DIR/suggest-enclosing-format-string.rs:15:14 + | +LL | println!({:?}, UwU::QwQ); + | ^^^^ + | +help: you might be missing a string literal to format with + | +LL | println!("{} {}", {:?}, UwU::QwQ); + | ++++++++ +help: you might want to enclose `{:?}` with `""` + | +LL - println!({:?}, UwU::QwQ); +LL + println!("{:?}", UwU::QwQ); + | + +error: expected expression, found `:` + --> $DIR/suggest-enclosing-format-string.rs:21:15 + | +LL | println!({:#?}, UwU::QwQ); + | ^ expected expression + | +help: maybe write a path separator here + | +LL | println!({::#?}, UwU::QwQ); + | + + +error: format argument must be a string literal + --> $DIR/suggest-enclosing-format-string.rs:21:14 + | +LL | println!({:#?}, UwU::QwQ); + | ^^^^^ + | +help: you might be missing a string literal to format with + | +LL | println!("{} {}", {:#?}, UwU::QwQ); + | ++++++++ +help: you might want to enclose `{:#?}` with `""` + | +LL - println!({:#?}, UwU::QwQ); +LL + println!("{:#?}", UwU::QwQ); + | + +error: aborting due to 5 previous errors +