From 4b5d5449d215a9786c63c9936895039db0b20ffc Mon Sep 17 00:00:00 2001 From: Connor Horman Date: Sun, 26 Apr 2026 11:56:36 +0000 Subject: [PATCH 01/37] Implement `core::arch::return_address` and tests * Implement core::arch::return_address and tests Fix typo Apply suggestions from code review Wording/docs changes. Co-authored-by: Ralf Jung Change signature according to Ralf's comment Fix call to `core::intrinsics::return_address()` according to the new signature Add cranelift implementation for intrinsic Change wording on `return_address!()` to be clear that returning a null pointer is best-effort. Fix formatting of doc comment Fix mistake in cranelift codegen * Do not generate llvm.returnaddress on wasm * Supress return_address test on miri --- .../src/intrinsics/mod.rs | 6 +++++ compiler/rustc_codegen_llvm/src/intrinsic.rs | 16 +++++++++++ .../rustc_codegen_ssa/src/mir/intrinsic.rs | 3 ++- .../rustc_hir_analysis/src/check/intrinsic.rs | 3 +++ compiler/rustc_span/src/symbol.rs | 1 + library/core/src/arch.rs | 27 +++++++++++++++++++ library/core/src/intrinsics/mod.rs | 13 +++++++++ .../codegen-llvm/intrinsics/return_address.rs | 12 +++++++++ 8 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 tests/codegen-llvm/intrinsics/return_address.rs 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 9742f9fb3e42e..7d58ac4d46dba 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -789,6 +789,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 fd0c7c656ac21..209116bab01ce 100644 --- a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs +++ b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs @@ -120,7 +120,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_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 58454cfc489c6..efcd8b354bd36 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 @@ -812,6 +813,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_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 981bfed363dcc..e47ec956e8de7 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1657,6 +1657,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 94d0c7eab9227..69fdb93dcf93f 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/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() +} From 706d098dad54913bed9da5138951fe01079e8121 Mon Sep 17 00:00:00 2001 From: Connor Horman Date: Tue, 28 Apr 2026 18:02:49 -0400 Subject: [PATCH 02/37] Fix cfg use for disabling test on miri --- library/core/src/arch.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/core/src/arch.rs b/library/core/src/arch.rs index 916e99b338640..569a4e8460fc1 100644 --- a/library/core/src/arch.rs +++ b/library/core/src/arch.rs @@ -94,11 +94,12 @@ pub fn breakpoint() { /// /// ## Example /// ``` -/// # #![cfg(not(miri))] // FIXME: Figure out how to make miri work before stabilizing this macro /// #![feature(return_address)] -/// +/// # #[cfg(not(miri))] // FIXME: Figure out how to make miri work before stabilizing this macro +/// # { /// let addr = core::arch::return_address!(); /// println!("Caller is {addr:p}"); +/// # } /// ``` #[unstable(feature = "return_address", issue = "154966")] #[allow_internal_unstable(core_intrinsics)] From 06cb86d952d7bd17378f195dc844ba18a916445a Mon Sep 17 00:00:00 2001 From: cijiugechu Date: Thu, 30 Apr 2026 14:19:21 +0800 Subject: [PATCH 03/37] fix closure HIR span context mismatch --- compiler/rustc_middle/src/hir/map.rs | 4 ++-- .../proc-macro/auxiliary/closure-hir-span.rs | 23 +++++++++++++++++++ tests/ui/proc-macro/closure-hir-span.rs | 10 ++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 tests/ui/proc-macro/auxiliary/closure-hir-span.rs create mode 100644 tests/ui/proc-macro/closure-hir-span.rs diff --git a/compiler/rustc_middle/src/hir/map.rs b/compiler/rustc_middle/src/hir/map.rs index 5a28dde4c9ba0..74f07911975d1 100644 --- a/compiler/rustc_middle/src/hir/map.rs +++ b/compiler/rustc_middle/src/hir/map.rs @@ -982,8 +982,8 @@ impl<'tcx> TyCtxt<'tcx> { span, .. }) => { - // Ensure that the returned span has the item's SyntaxContext. - fn_decl_span.find_ancestor_inside(*span).unwrap_or(*span) + // Ensure that the returned span has the closure expression's SyntaxContext. + fn_decl_span.find_ancestor_inside_same_ctxt(*span).unwrap_or(*span) } _ => self.hir_span_with_body(hir_id), }; diff --git a/tests/ui/proc-macro/auxiliary/closure-hir-span.rs b/tests/ui/proc-macro/auxiliary/closure-hir-span.rs new file mode 100644 index 0000000000000..6c29f0c35aff5 --- /dev/null +++ b/tests/ui/proc-macro/auxiliary/closure-hir-span.rs @@ -0,0 +1,23 @@ +//@ edition:2024 + +extern crate proc_macro; + +#[proc_macro] +pub fn m(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let mut iter = input.into_iter(); + let move_token = iter.next().unwrap(); + let pipe1 = iter.next().unwrap(); + let pipe2 = iter.next().unwrap(); + let body = iter.next().unwrap(); + + let mut inner = proc_macro::TokenStream::new(); + inner.extend([body]); + let new_body = proc_macro::TokenTree::Group(proc_macro::Group::new( + proc_macro::Delimiter::Brace, + inner, + )); + + let mut out = proc_macro::TokenStream::new(); + out.extend([move_token, pipe1, pipe2, new_body]); + out +} diff --git a/tests/ui/proc-macro/closure-hir-span.rs b/tests/ui/proc-macro/closure-hir-span.rs new file mode 100644 index 0000000000000..ff865587e26a2 --- /dev/null +++ b/tests/ui/proc-macro/closure-hir-span.rs @@ -0,0 +1,10 @@ +//! Regression test for +//@ check-pass +//@ edition:2024 +//@ proc-macro: closure-hir-span.rs + +extern crate closure_hir_span; + +fn main() { + closure_hir_span::m!(move || {}); +} From c876de40015720aa51b01abcdab725493a51e0e2 Mon Sep 17 00:00:00 2001 From: Connor Horman Date: Sun, 3 May 2026 12:06:26 -0400 Subject: [PATCH 04/37] Final attempt at making miri work --- library/core/src/arch.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/core/src/arch.rs b/library/core/src/arch.rs index 569a4e8460fc1..737a643ef8659 100644 --- a/library/core/src/arch.rs +++ b/library/core/src/arch.rs @@ -95,11 +95,13 @@ pub fn breakpoint() { /// ## Example /// ``` /// #![feature(return_address)] -/// # #[cfg(not(miri))] // FIXME: Figure out how to make miri work before stabilizing this macro -/// # { +/// +/// # fn run_test() { /// let addr = core::arch::return_address!(); /// println!("Caller is {addr:p}"); /// # } +/// # #[cfg(not(miri))] // FIXME: Figure out how to make miri work before stabilizing this macro +/// # run_test() /// ``` #[unstable(feature = "return_address", issue = "154966")] #[allow_internal_unstable(core_intrinsics)] From dfc5fd5e673e0bfde6217d79fa71f510d466ac68 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Wed, 29 Apr 2026 11:33:05 +0200 Subject: [PATCH 05/37] Add `getsockname` shim for connecting and connected sockets Previously it only worked for bound and listening sockets and returned an unspecified address otherwise. --- src/tools/miri/src/diagnostics.rs | 13 +++++ src/tools/miri/src/shims/unix/socket.rs | 21 +++++++- .../pass-dep/libc/libc-socket-no-blocking.rs | 48 +++++++++++++++++++ ...ibc-socket-no-blocking.windows_host.stderr | 21 ++++++++ .../miri/tests/pass-dep/libc/libc-socket.rs | 29 +++++++++++ 5 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.windows_host.stderr diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 14beafc6a34ba..95b56706367d7 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -144,6 +144,7 @@ pub enum NonHaltingDiagnostic { effective_failure_ordering: AtomicReadOrd, }, FileInProcOpened, + ConnectingSocketGetsockname, } /// Level of Miri specific diagnostics @@ -650,6 +651,8 @@ impl<'tcx> MiriMachine<'tcx> { | WeakMemoryOutdatedLoad { .. } => ("tracking was triggered here".to_string(), DiagLevel::Note), FileInProcOpened => ("open a file in `/proc`".to_string(), DiagLevel::Warning), + ConnectingSocketGetsockname => + ("Called `getsockname` on connecting socket".to_string(), DiagLevel::Warning), }; let title = match &e { @@ -698,12 +701,22 @@ impl<'tcx> MiriMachine<'tcx> { format!("GenMC currently does not model the failure ordering for `compare_exchange`. {was_upgraded_msg}. Miri with GenMC might miss bugs related to this memory access.") } FileInProcOpened => format!("files in `/proc` can bypass the Abstract Machine and might not work properly in Miri"), + ConnectingSocketGetsockname => format!("connecting sockets return unspecified socket addresses on Windows hosts") }; let notes = match &e { ProgressReport { block_count } => { vec![note!("so far, {block_count} basic blocks have been executed")] } + ConnectingSocketGetsockname => + vec![ + note!( + "Windows hosts do not provide `local_addr` information while the socket is still connecting, which might break the assumptions of code compiled for Unix targets" + ), + note!( + "an unspecified socket address (e.g. `0.0.0.0:0`) will be returned instead" + ), + ], _ => vec![], }; diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index 99a6378704af3..7f947ca0858a6 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -1,6 +1,7 @@ use std::cell::{Cell, RefCell}; use std::io::Read; use std::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6}; +use std::sync::atomic::AtomicBool; use std::time::Duration; use std::{io, iter}; @@ -975,9 +976,27 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Ok(address) => address, Err(e) => return this.set_last_error_and_return_i32(e), }, + SocketState::Connecting(stream) | SocketState::Connected(stream) => { + if cfg!(windows) && matches!(&*state, SocketState::Connecting(_)) { + // FIXME: On Windows hosts `TcpStream::local_addr` returns `0.0.0.0:0` whilst + // the socket is connecting: + // + // This is problematic because UNIX targets could expect a real local address even + // for a connecting non-blocking socket. + + static DEDUP: AtomicBool = AtomicBool::new(false); + if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) { + this.emit_diagnostic(NonHaltingDiagnostic::ConnectingSocketGetsockname); + } + } + match stream.local_addr() { + Ok(address) => address, + Err(e) => return this.set_last_error_and_return_i32(e), + } + } // For non-bound sockets the POSIX manual says the returned address is unspecified. // Often this is 0.0.0.0:0 and thus we set it to this value. - _ => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)), + SocketState::Initial => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)), }; match this.write_socket_address(&address, address_ptr, address_len_ptr, "getsockname")? { diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs index f43847733bc70..40c821e858cd3 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs @@ -51,6 +51,8 @@ fn main() { test_send_recv_dontwait(); test_write_read_nonblock(); + test_getsockname_ipv4_connect_nonblock(); + test_getpeername_ipv4_nonblock(); test_getpeername_ipv4_nonblock_no_peer(); } @@ -574,6 +576,52 @@ fn test_write_read_nonblock() { server_thread.join().unwrap(); } +/// Test the `getsockname` syscall on a connecting IPv4 socket +/// which is not connected. +fn test_getsockname_ipv4_connect_nonblock() { + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + + unsafe { + // Change client socket to be non-blocking. + errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + // We cannot attempt to connect to a localhost address because + // it could be the case that a socket from another test is + // currently listening on `localhost:12321` because we bind to + // random ports everywhere. For `192.0.2.1` we know that nothing is + // listening because it's a blackhole address: + // + // The port `12321` is just a random non-zero port because Windows + // and Apple hosts return EADDRNOTAVAIL when attempting to connect to + // a zero port. + let addr = net::sock_addr_ipv4([192, 0, 2, 1], 12321); + + // Non-blocking connect should fail with EINPROGRESS. + let err = net::connect_ipv4(client_sockfd, addr).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InProgress); + + let (_, sock_addr) = net::sockname_ipv4(|storage, len| unsafe { + libc::getsockname(client_sockfd, storage, len) + }) + .unwrap(); + + // The unspecified IPv4 address. + let addr = net::sock_addr_ipv4([0, 0, 0, 0], 0); + + assert_eq!(addr.sin_family, sock_addr.sin_family); + if cfg!(windows_host) { + // On Windows hosts a connecting socket is bound to the unspecified address. + assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); + } else { + // On UNIX hosts a connecting socket is bound to any local interface address + // but not the unspecified address. + assert_ne!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); + } + assert!(sock_addr.sin_port > 0); +} + /// Test that the `getpeername` syscall successfully returns the peer address /// for a non-blocking IPv4 socket whose connection has been successfully /// established before calling the syscall. diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.windows_host.stderr b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.windows_host.stderr new file mode 100644 index 0000000000000..8173c121067a6 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.windows_host.stderr @@ -0,0 +1,21 @@ +warning: connecting sockets return unspecified socket addresses on Windows hosts + --> tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC + | +LL | libc::getsockname(client_sockfd, storage, len) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Called `getsockname` on connecting socket + | + = note: Windows hosts do not provide `local_addr` information while the socket is still connecting, which might break the assumptions of code compiled for Unix targets + = note: an unspecified socket address (e.g. `0.0.0.0:0`) will be returned instead + = note: this is on thread `main` + = note: stack backtrace: + 0: test_getsockname_ipv4_connect_nonblock::{closure#0} + at tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC + 1: libc_utils::net::sockname + at tests/pass-dep/libc/../../utils/libc.rs:LL:CC + 2: libc_utils::net::sockname_ipv4 + at tests/pass-dep/libc/../../utils/libc.rs:LL:CC + 3: test_getsockname_ipv4_connect_nonblock + at tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC + 4: main + at tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC + 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 173edbc822a79..33f99371eee69 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs @@ -41,6 +41,7 @@ fn main() { test_getsockname_ipv4(); test_getsockname_ipv4_random_port(); test_getsockname_ipv4_unbound(); + test_getsockname_ipv4_connect(); test_getsockname_ipv6(); test_getpeername_ipv4(); @@ -427,6 +428,34 @@ fn test_getsockname_ipv4_unbound() { assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); } +/// Test the `getsockname` syscall on a connected IPv4 socket. +fn test_getsockname_ipv4_connect() { + 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 || net::accept_ipv4(server_sockfd).unwrap()); + + net::connect_ipv4(client_sockfd, addr).unwrap(); + + let (_, sock_addr) = net::sockname_ipv4(|storage, len| unsafe { + libc::getsockname(client_sockfd, storage, len) + }) + .unwrap(); + + // We want to ensure that the local address is not the unspecified address. + // Because the bound address could be of any local interface, we cannot + // test for localhost. + let addr = net::sock_addr_ipv4([0, 0, 0, 0], 0); + + assert_eq!(addr.sin_family, sock_addr.sin_family); + assert_ne!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); + assert!(sock_addr.sin_port > 0); + + server_thread.join().unwrap(); +} + /// Test the `getsockname` syscall on an IPv6 socket which is bound. /// The `getsockname` syscall should return the same address as to /// which the socket was bound to. From fdab736e671b97ab5b16240027dfa172d8f9b6c0 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Mon, 4 May 2026 05:55:07 +0000 Subject: [PATCH 06/37] Prepare for merging from rust-lang/rust This updates the rust-version file to 045b17737dab5fcc28e4cbee0cfe2ce4ed363b32. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index a5106c872503d..59e9e5a0e6ee9 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -44860d3e9ef700cac0b4a61d924f41f46bf1b447 +045b17737dab5fcc28e4cbee0cfe2ce4ed363b32 From c79b2c44b9792d6637f0465e97251bcec530930d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 4 May 2026 12:23:20 +0200 Subject: [PATCH 07/37] avoid underflow in std::Instant when computing times before program startup --- src/tools/miri/src/shims/time.rs | 7 +++++++ .../tests/pass/shims/time-with-isolation.rs | 18 ++++++++++++++++++ src/tools/miri/tests/pass/shims/time.rs | 10 +++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 2c2b029a1232f..b2b6e57637d1e 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -247,6 +247,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let qpc = i64::try_from(duration.as_nanos()).map_err(|_| { err_unsup_format!("programs running longer than 2^63 nanoseconds are not supported") })?; + // We are allowed to offset this by an arbitrary constant, which will correspond to the + // value of this clock at program start time. We use that freedom to work around + // , caused by std casting the result of + // this function to `u64`. We pick an offset of `i64::MAX/2` instead of `i64::MAX` to avoid + // being too close to overflow for callers that actually do use signed integers. + let qpc = qpc.strict_add(i64::MAX / 2); + this.write_scalar( Scalar::from_i64(qpc), &this.deref_pointer_as(lpPerformanceCount_op, this.machine.layouts.i64)?, diff --git a/src/tools/miri/tests/pass/shims/time-with-isolation.rs b/src/tools/miri/tests/pass/shims/time-with-isolation.rs index e7b1624412358..524745c362993 100644 --- a/src/tools/miri/tests/pass/shims/time-with-isolation.rs +++ b/src/tools/miri/tests/pass/shims/time-with-isolation.rs @@ -1,5 +1,13 @@ +#![feature(duration_constructors, thread_sleep_until)] use std::time::{Duration, Instant}; +fn test_underflow() { + // The time 1 day before the program started should be representable. + // (This used to underflow on Windows.) + let now = Instant::now(); + let _earlier = now - Duration::from_days(1); +} + fn test_sleep() { // We sleep a *long* time here -- but the clock is virtual so the test should still pass quickly. let before = Instant::now(); @@ -8,6 +16,14 @@ fn test_sleep() { assert!((after - before).as_secs() >= 3600); } +fn test_sleep_until() { + let before = Instant::now(); + let hunderd_millis_after_start = before + Duration::from_millis(100); + std::thread::sleep_until(hunderd_millis_after_start); + let after = Instant::now(); + assert!((after - before).as_millis() >= 100); +} + /// Ensure that time passes even if we don't sleep (but just work). fn test_time_passes() { // Check `Instant`. @@ -48,8 +64,10 @@ fn test_deterministic() { } fn main() { + test_underflow(); test_time_passes(); test_block_for_one_second(); test_sleep(); + test_sleep_until(); test_deterministic(); } diff --git a/src/tools/miri/tests/pass/shims/time.rs b/src/tools/miri/tests/pass/shims/time.rs index ef0b400f1a716..627ba3af8a344 100644 --- a/src/tools/miri/tests/pass/shims/time.rs +++ b/src/tools/miri/tests/pass/shims/time.rs @@ -1,5 +1,5 @@ //@compile-flags: -Zmiri-disable-isolation -#![feature(thread_sleep_until)] +#![feature(duration_constructors, thread_sleep_until)] use std::time::{Duration, Instant, SystemTime}; @@ -9,6 +9,13 @@ fn duration_sanity(diff: Duration) { assert!(diff.as_millis() < 1000); // macOS is very slow sometimes } +fn test_underflow() { + // The time 1 day before the program started should be representable. + // (This used to underflow on Windows.) + let now = Instant::now(); + let _earlier = now - Duration::from_days(1); +} + fn test_sleep() { let before = Instant::now(); std::thread::sleep(Duration::from_millis(100)); @@ -57,6 +64,7 @@ fn main() { assert_eq!(now2 - diff, now1); duration_sanity(diff); + test_underflow(); test_sleep(); test_sleep_until(); } From 8e91dd72d1a7b01d1a72759d68bd147c1d19f47e Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Mon, 4 May 2026 19:45:23 +0200 Subject: [PATCH 08/37] fix: remove `WSAESHUTDOWN` workaround for socket writes --- src/tools/miri/src/shims/unix/socket.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index 7f947ca0858a6..747a64a9b8e8f 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -1548,15 +1548,6 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK. interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into()))) } - Err(IoError::HostError(e)) - if cfg!(windows) - && matches!(e.raw_os_error(), Some(/* WSAESHUTDOWN error code */ 10058)) => - { - // FIXME: This is a temporary workaround for handling WSAESHUTDOWN errors - // on Windows. A discussion on how those errors should be handled can be found here: - // - interp_ok(Err(IoError::HostError(io::ErrorKind::BrokenPipe.into()))) - } result => interp_ok(result), } } From df26762c838c9fd523f6c780551adb96e1de757c Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Tue, 5 May 2026 14:30:42 +0100 Subject: [PATCH 09/37] core: Replace `ptr::slice_from_raw_parts` with `slice::from_raw_parts` --- library/core/src/slice/sort/shared/smallsort.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/library/core/src/slice/sort/shared/smallsort.rs b/library/core/src/slice/sort/shared/smallsort.rs index e555fce440872..0017feb75b641 100644 --- a/library/core/src/slice/sort/shared/smallsort.rs +++ b/library/core/src/slice/sort/shared/smallsort.rs @@ -284,7 +284,7 @@ fn small_sort_general_with_scratch bool>( // permutation of the input through drop_guard. This technique is similar // to ping-pong merging. bidirectional_merge( - &*ptr::slice_from_raw_parts(drop_guard.src, drop_guard.len), + slice::from_raw_parts(drop_guard.src, drop_guard.len), drop_guard.dst, is_less, ); @@ -332,7 +332,7 @@ where let v_base = v.as_mut_ptr(); let initial_region_len = if no_merge { len } else { len_div_2 }; // SAFETY: Both possible values of `initial_region_len` are in-bounds. - let mut region = unsafe { &mut *ptr::slice_from_raw_parts_mut(v_base, initial_region_len) }; + let mut region = unsafe { slice::from_raw_parts_mut(v_base, initial_region_len) }; // Avoid compiler unrolling, we *really* don't want that to happen here for binary-size reasons. loop { @@ -357,9 +357,7 @@ where } // SAFETY: The right side of `v` based on `len_div_2` is guaranteed in-bounds. - unsafe { - region = &mut *ptr::slice_from_raw_parts_mut(v_base.add(len_div_2), len - len_div_2) - }; + unsafe { region = slice::from_raw_parts_mut(v_base.add(len_div_2), len - len_div_2) }; } // SAFETY: We checked that T is Freeze and thus observation safe. @@ -367,11 +365,7 @@ where // scratch and v must not alias and scratch has v.len() space. unsafe { let scratch_base = stack_array.as_mut_ptr() as *mut T; - bidirectional_merge( - &mut *ptr::slice_from_raw_parts_mut(v_base, len), - scratch_base, - is_less, - ); + bidirectional_merge(slice::from_raw_parts_mut(v_base, len), scratch_base, is_less); ptr::copy_nonoverlapping(scratch_base, v_base, len); } } @@ -675,7 +669,7 @@ unsafe fn sort8_stable bool>( // SAFETY: scratch_base[0..8] is now initialized, allowing us to merge back // into dst. unsafe { - bidirectional_merge(&*ptr::slice_from_raw_parts(scratch_base, 8), dst, is_less); + bidirectional_merge(slice::from_raw_parts(scratch_base, 8), dst, is_less); } } From 38f567f85e2623d6b74219cf31e6788d77da8be5 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Tue, 21 Apr 2026 01:02:00 +0200 Subject: [PATCH 10/37] Add epoll integration for network sockets --- src/tools/miri/src/concurrency/blocking_io.rs | 378 ++++++++++++------ src/tools/miri/src/concurrency/thread.rs | 57 +-- src/tools/miri/src/lib.rs | 5 +- src/tools/miri/src/shims/files.rs | 8 + src/tools/miri/src/shims/mod.rs | 4 +- src/tools/miri/src/shims/unix/fd.rs | 4 +- .../src/shims/unix/linux/foreign_items.rs | 1 - .../miri/src/shims/unix/linux_like/epoll.rs | 48 ++- .../miri/src/shims/unix/linux_like/eventfd.rs | 8 +- src/tools/miri/src/shims/unix/mod.rs | 4 +- src/tools/miri/src/shims/unix/socket.rs | 147 ++++--- .../miri/src/shims/unix/virtual_socket.rs | 36 +- .../libc/libc-socket-no-blocking-epoll.rs | 297 ++++++++++++++ src/tools/miri/tests/utils/libc.rs | 26 +- 14 files changed, 796 insertions(+), 227 deletions(-) create mode 100644 src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs diff --git a/src/tools/miri/src/concurrency/blocking_io.rs b/src/tools/miri/src/concurrency/blocking_io.rs index 35d7474347ab5..9dc9bbfd1f1d2 100644 --- a/src/tools/miri/src/concurrency/blocking_io.rs +++ b/src/tools/miri/src/concurrency/blocking_io.rs @@ -1,12 +1,15 @@ +use std::cell::RefMut; use std::collections::BTreeMap; use std::io; +use std::ops::BitOrAssign; use std::time::Duration; use mio::event::Source; use mio::{Events, Interest, Poll, Token}; -use rustc_data_structures::fx::FxHashMap; -use crate::shims::{FdId, FileDescriptionRef}; +use crate::shims::{ + EpollEvalContextExt, FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef, +}; use crate::*; /// Capacity of the event queue which can be polled at a time. @@ -14,24 +17,113 @@ use crate::*; /// this value can be set rather low. const IO_EVENT_CAPACITY: usize = 16; -/// Trait for values that contain a mio [`Source`]. -pub trait WithSource { +/// Trait for file descriptions that contain a mio [`Source`]. +pub trait SourceFileDescription: FileDescription { /// Invoke `f` on the source inside `self`. fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()>; + + /// Get a mutable reference to the readiness of the source. + fn get_readiness_mut(&self) -> RefMut<'_, BlockingIoSourceReadiness>; +} + +/// An I/O interest for a blocked thread. Note that all threads are always considered +/// to be interested in "error" events. +#[derive(Debug, Clone, Copy)] +pub enum BlockingIoInterest { + /// The blocked thread is interested in [`Interest::READABLE`]. + Read, + /// The blocked thread is interested in [`Interest::WRITABLE`]. + Write, + /// The blocked thread is interested in [`Interest::READABLE`] and + /// [`Interest::WRITABLE`]. + ReadWrite, +} + +/// Struct reflecting the readiness of a source file description. +#[derive(Debug)] +pub struct BlockingIoSourceReadiness { + /// Boolean whether the source is currently readable. + pub readable: bool, + /// Boolean whether the source is currently writable. + pub writable: bool, + /// Boolean whether the read end of the source has been + /// closed. + pub read_closed: bool, + /// Boolean whether the write end of the source has been + /// closed. + pub write_closed: bool, + /// Boolean whether the source currently has an error. + pub error: bool, +} + +impl BlockingIoSourceReadiness { + pub fn empty() -> Self { + Self { + readable: false, + writable: false, + read_closed: false, + write_closed: false, + error: false, + } + } + + /// Check whether the current readiness fulfills the blocking I/O interest of + /// `interest`. + /// This function also returns `true` if the error readiness is set + /// even when the requested interest might not be fulfilled. + fn fulfills_interest(&self, interest: &BlockingIoInterest) -> bool { + match interest { + BlockingIoInterest::Read => self.readable || self.error, + BlockingIoInterest::Write => self.writable || self.error, + BlockingIoInterest::ReadWrite => self.readable || self.writable || self.error, + } + } } -/// An interest receiver defines the action that should be taken when -/// the associated [`Interest`] is fulfilled. -#[derive(Debug, Hash, PartialEq, Clone, Copy, Eq, PartialOrd, Ord)] -pub enum InterestReceiver { - /// The specified thread should be unblocked. - UnblockThread(ThreadId), +impl BitOrAssign for BlockingIoSourceReadiness { + fn bitor_assign(&mut self, rhs: Self) { + self.readable |= rhs.readable; + self.writable |= rhs.writable; + self.read_closed |= rhs.read_closed; + self.write_closed |= rhs.write_closed; + self.error |= rhs.error; + } +} + +impl From<&mio::event::Event> for BlockingIoSourceReadiness { + fn from(event: &mio::event::Event) -> Self { + Self { + readable: event.is_readable(), + writable: event.is_writable(), + read_closed: event.is_read_closed(), + write_closed: event.is_write_closed(), + error: event.is_error(), + } + } +} + +struct BlockingIoSource { + /// The source file description which is registered into the poll. + /// We only store weak references such that source file descriptions + /// can be destroyed whilst they are registered. However, they are required + /// to deregister themselves when [`FileDescription::destroy`] is called. + fd: WeakFileDescriptionRef, + /// The threads which are blocked on the I/O source, and the interest indicating + /// when they should be unblocked. + blocked_threads: BTreeMap, } /// Manager for managing blocking host I/O in a non-blocking manner. /// We use [`Poll`] to poll for new I/O events from the OS for sources /// registered using this manager. /// +/// The semantics of this manager are that host I/O sources are registered +/// to a [`Poll`] for their entire lifespan. Once host readiness events happen +/// on a registered source, its internal epoll readiness gets updated -- even +/// when the source isn't part of an active epoll instance. Also, for the entire +/// lifespan of the source, threads can be added which should be unblocked +/// once a certain [`BlockingIoSourceReadiness`] for an I/O source is satisfied. +/// /// Since blocking host I/O is inherently non-deterministic, no method on this /// manager should be called when isolation is enabled. The only exception is /// the [`BlockingIoManager::new`] function to create the manager. Everywhere else, @@ -44,10 +136,9 @@ pub struct BlockingIoManager { /// This is not part of the state and only stored to avoid allocating a /// new buffer for every poll. events: Events, - /// Map from source ids to the actual sources and their registered receivers - /// together with their associated interests. - sources: - BTreeMap, FxHashMap)>, + /// Map from source file description ids to the actual sources and their + /// blocked threads. + sources: BTreeMap, } impl BlockingIoManager { @@ -70,133 +161,176 @@ impl BlockingIoManager { /// specified duration. /// - If the timeout is [`None`] the poll blocks indefinitely until an event occurs. /// - /// Returns the interest receivers for all file descriptions which received an I/O event together - /// with the file description they were registered for. - pub fn poll( - &mut self, + /// The events also immediately get processed: threads get unblocked, and epoll readiness gets updated. + pub fn poll<'tcx>( + ecx: &mut MiriInterpCx<'tcx>, timeout: Option, - ) -> Result)>, io::Error> { - let poll = - self.poll.as_mut().expect("Blocking I/O should not be called with isolation enabled"); + ) -> InterpResult<'tcx, Result<(), io::Error>> { + let poll = ecx + .machine + .blocking_io + .poll + .as_mut() + .expect("Blocking I/O should not be called with isolation enabled"); // Poll for new I/O events from OS and store them in the events buffer. - poll.poll(&mut self.events, timeout)?; + if let Err(err) = poll.poll(&mut ecx.machine.blocking_io.events, timeout) { + return interp_ok(Err(err)); + }; - let ready = self + let event_fds = ecx + .machine + .blocking_io .events .iter() - .flat_map(|event| { + .map(|event| { let token = event.token(); // We know all tokens are valid `FdId`. let fd_id = FdId::new_unchecked(token.0); - let (source, interests) = - self.sources.get(&fd_id).expect("Source should be registered"); - assert_eq!(source.id(), fd_id); - // Because we allow spurious wake-ups, we mark all interests as ready even - // though some may not have been fulfilled. - interests.keys().map(move |receiver| (*receiver, source.clone())) + let source = ecx + .machine + .blocking_io + .sources + .get(&fd_id) + .expect("Source should be registered"); + let fd = source.fd.upgrade().expect( + "Source file description shouldn't be destroyed whilst being registered", + ); + + assert_eq!(fd.id(), fd_id); + // Update the readiness of the source. + *fd.get_readiness_mut() |= BlockingIoSourceReadiness::from(event); + // Put FD into `event_fds` list. + fd }) .collect::>(); - // Deregister all ready sources as we only want to receive one event per receiver. - ready.iter().for_each(|(receiver, source)| self.deregister(source.id(), *receiver)); + // Update the epoll readiness for all source file descriptions which received an event. Also, + // unblock the threads which are blocked on such a source and whose interests are now fulfilled. + for fd in event_fds.into_iter() { + // Update epoll readiness for the `fd` source. + ecx.update_epoll_active_events(fd.clone(), false)?; + + let source = + ecx.machine.blocking_io.sources.get(&fd.id()).expect( + "Source file description shouldn't be destroyed whilst being registered", + ); + + // List of all thread id's whose interests are currently fulfilled + // and which are blocked on the `fd` source. This also includes + // threads whose interests were already fulfilled before the + // `poll` invocation. + let threads = source + .blocked_threads + .iter() + .filter_map(|(thread_id, interest)| { + fd.get_readiness_mut().fulfills_interest(interest).then_some(*thread_id) + }) + .collect::>(); + + // Unblock all threads whose interests are currently fulfilled and + // which are blocked on the `fd` source. + threads + .into_iter() + .try_for_each(|thread_id| ecx.unblock_thread(thread_id, BlockReason::IO))?; + } + + interp_ok(Ok(())) + } - Ok(ready) + /// Return whether a source file description is currently registered in the + /// blocking I/O poll. + /// This can also be used to check whether a file description is a host + /// I/O source. + pub fn contains_source(&self, source_id: &FdId) -> bool { + self.sources.contains_key(source_id) } - /// Register an interest for a blocking I/O source. - /// - /// As the OS can always produce spurious wake-ups, it's the callers responsibility to - /// verify the requested I/O interests are really ready and to register again if they're not. - /// - /// It's assumed that no interest is already registered for this source with the same reason! - pub fn register( - &mut self, - source_fd: FileDescriptionRef, - receiver: InterestReceiver, - interest: Interest, - ) { + /// Register a source file description to the blocking I/O poll. + pub fn register(&mut self, source_fd: FileDescriptionRef) { let poll = self.poll.as_ref().expect("Blocking I/O should not be called with isolation enabled"); let id = source_fd.id(); let token = Token(id.to_usize()); - let Some((_, current_interests)) = self.sources.get_mut(&id) else { - // The source is not yet registered. + // All possible interests. + // We only care about the readable and writable interests because those are the only + // interests which are available on all platforms. Internally, mio also + // registers an error interest. + let interest = Interest::READABLE | Interest::WRITABLE; - // Treat errors from registering as fatal. On UNIX hosts this can only - // fail due to system resource errors (e.g. ENOMEM or ENOSPC). - source_fd - .with_source(&mut |source| poll.registry().register(source, token, interest)) - .unwrap(); + // Treat errors from registering as fatal. On UNIX hosts this can only + // fail due to system resource errors (e.g. ENOMEM or ENOSPC) or when the source is already registered. + source_fd + .with_source(&mut |source| poll.registry().register(source, token, interest)) + .unwrap(); - self.sources.insert(id, (source_fd, FxHashMap::from_iter([(receiver, interest)]))); - return; + let source = BlockingIoSource { + fd: FileDescriptionRef::downgrade(&source_fd), + blocked_threads: BTreeMap::default(), }; - // The source is already registered. We need to check whether we need to - // reregister because the provided interest contains new interests for the source. - - let old_interest = - interest_union(current_interests).expect("Source should contain at least one interest"); - - current_interests - .try_insert(receiver, interest) - .unwrap_or_else(|_| panic!("Receiver should be unique")); - - let new_interest = old_interest.add(interest); - - // Reregister the source since the overall interests might have changed. - - // Treat errors from reregistering as fatal. On UNIX hosts this can only - // fail due to system resource errors (e.g. ENOMEM or ENOSPC). - source_fd - .with_source(&mut |source| poll.registry().reregister(source, token, new_interest)) - .unwrap(); + self.sources + .try_insert(id, source) + .unwrap_or_else(|_| panic!("Source should not already be registered")); } - /// Deregister an interest from a blocking I/O source. + /// Deregister a source file description from the blocking I/O poll. /// - /// The receiver is assumed to be registered for the provided source! - pub fn deregister(&mut self, source_id: FdId, receiver: InterestReceiver) { + /// It's assumed that the file description with id `source_id` is already + /// removed from the file description table. + pub fn deregister(&mut self, source_id: FdId, source: impl SourceFileDescription) { let poll = self.poll.as_ref().expect("Blocking I/O should not be called with isolation enabled"); - let token = Token(source_id.to_usize()); - let (fd, current_interests) = - self.sources.get_mut(&source_id).expect("Source should be registered"); - - current_interests - .remove(&receiver) - .unwrap_or_else(|| panic!("Receiver should be registered for source")); + let stored_source = self.sources.remove(&source_id).expect("Source should be registered"); + // Ensure that the source file description is already removed from the file + // description table. + assert!( + stored_source.fd.upgrade().is_none(), + "Sources must only be deregistered when they are destroyed" + ); - let Some(new_interest) = interest_union(current_interests) else { - // There are no longer any interests in this source. - // We can thus deregister the source from the poll. + // Because we only store `WeakFileDescriptionRef`s and the `stored_source` file description + // is already destroyed, the weak reference can no longer be upgraded. Thus, we cannot use + // it to deregister the source from the poll and instead use the `source` argument to deregister. - // Treat errors from deregistering as fatal. On UNIX hosts this can only - // fail due to system resource errors (e.g. ENOMEM or ENOSPC). - fd.with_source(&mut |source| poll.registry().deregister(source)).unwrap(); - self.sources.remove(&source_id); - return; - }; + // Treat errors from deregistering as fatal. On UNIX hosts this can only + // fail due to system resource errors (e.g. ENOMEM or ENOSPC). + source.with_source(&mut |source| poll.registry().deregister(source)).unwrap(); + } - // Reregister the source since the overall interests might have changed. + /// Add a new blocked thread to a registered source. The thread gets unblocked + /// once its [`BlockingIoInterest`] is fulfilled when calling + /// [`BlockingIoManager::poll`]. + /// + /// It's assumed that the thread of `thread_id` isn't already blocked on + /// the source with id `source_id` and that this source is currently + /// registered. + fn add_blocked_thread( + &mut self, + source_id: FdId, + thread_id: ThreadId, + interest: BlockingIoInterest, + ) { + let source = self.sources.get_mut(&source_id).expect("Source should be registered"); - // Treat errors from reregistering as fatal. On UNIX hosts this can only - // fail due to system resource errors (e.g. ENOMEM or ENOSPC). - fd.with_source(&mut |source| poll.registry().reregister(source, token, new_interest)) - .unwrap(); + source + .blocked_threads + .try_insert(thread_id, interest) + .expect("Thread cannot be blocked multiple times on the same source"); } -} -/// Get the union of all interests for a source. Returns `None` if the map is empty. -fn interest_union(interests: &FxHashMap) -> Option { - interests - .values() - .copied() - .fold(None, |acc, interest| acc.map(|acc: Interest| acc.add(interest)).or(Some(interest))) + /// Remove a blocked thread from a registered source. + /// + /// It's assumed that the thread of `thread_id` is blocked on the + /// source with id `source_id` and that this source is currently + /// registered. + pub fn remove_blocked_thread(&mut self, source_id: FdId, thread_id: ThreadId) { + let source = self.sources.get_mut(&source_id).expect("Source should be registered"); + source.blocked_threads.remove(&thread_id).expect("Thread should be blocked on source"); + } } impl<'tcx> EvalContextExt<'tcx> for MiriInterpCx<'tcx> {} @@ -205,23 +339,39 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> { /// are fulfilled or the optional timeout exceeded. /// The callback will be invoked when the thread gets unblocked. /// - /// There can be spurious wake-ups by the OS and thus it's the callers + /// Note that an error interest is implicitly added to `interest`. + /// This means that the thread will also be unblocked when the error + /// readiness gets set for the source even when the requested interest + /// might not be fulfilled. + /// + /// There can also be spurious wake-ups by the OS and thus it's the callers /// responsibility to verify that the requested I/O interests are /// really ready and to block again if they're not. + /// + /// It's the callers responsibility to remove the [`BlockingIoInterest`] + /// from the blocking I/O manager in the provided callback function. #[inline] fn block_thread_for_io( &mut self, - source_fd: FileDescriptionRef, - interests: Interest, + source_fd: FileDescriptionRef, + interest: BlockingIoInterest, timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>, callback: DynUnblockCallback<'tcx>, - ) { + ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - this.machine.blocking_io.register( - source_fd, - InterestReceiver::UnblockThread(this.active_thread()), - interests, - ); - this.block_thread(BlockReason::IO, timeout, callback); + + // We always have to do this since the thread will de-register itself. + this.machine.blocking_io.add_blocked_thread(source_fd.id(), this.active_thread(), interest); + + if source_fd.get_readiness_mut().fulfills_interest(&interest) { + // The requested readiness is currently already fulfilled for the provided source. + // Instead of actually blocking the thread, we just run the callback function. + callback.call(this, UnblockKind::Ready) + } else { + // The I/O readiness is currently not fulfilled. We block the thread + // until the readiness is fulfilled and execute the callback then. + this.block_thread(BlockReason::IO, timeout, callback); + interp_ok(()) + } } } diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 3b0b2b26c1922..3c2c2c0252149 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -18,8 +18,7 @@ use rustc_span::{DUMMY_SP, Span}; use rustc_target::spec::Os; use crate::concurrency::GlobalDataRaceHandler; -use crate::concurrency::blocking_io::InterestReceiver; -use crate::shims::tls; +use crate::shims::{Epoll, EpollEvalContextExt, FileDescriptionRef, tls}; use crate::*; #[derive(Clone, Copy, Debug, PartialEq)] @@ -108,7 +107,7 @@ pub enum BlockReason { /// Blocked on an InitOnce. InitOnce, /// Blocked on epoll. - Epoll, + Epoll { epfd: FileDescriptionRef }, /// Blocked on eventfd. Eventfd, /// Blocked on virtual socket. @@ -779,7 +778,9 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { .filter(|(_id, thread)| thread.state.is_enabled()); // Pick a new thread, and switch to it. let new_thread = if thread_manager.fixed_scheduling { - threads_iter.next() + let next = threads_iter.next(); + drop(threads_iter); + next } else { threads_iter.choose(rng) }; @@ -800,48 +801,56 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { if thread_manager.threads[thread_manager.active_thread].state.is_enabled() { return interp_ok(SchedulingAction::ExecuteStep); } + // We have not found a thread to execute. - if thread_manager.threads.iter().all(|thread| thread.state.is_terminated()) { + let threads = &this.machine.threads.threads; + + if threads.iter().all(|thread| thread.state.is_terminated()) { unreachable!("all threads terminated without the main thread terminating?!"); } else if let Some(sleep_time) = potential_sleep_time { // All threads are currently blocked, but we have unexecuted // timeout_callbacks, which may unblock some of the threads. Hence, // sleep until the first callback. interp_ok(SchedulingAction::SleepAndWaitForIo(Some(sleep_time))) - } else if thread_manager - .threads - .iter() - .any(|thread| thread.state.is_blocked_on(&BlockReason::IO)) - { - // At least one thread is blocked on host I/O but doesn't - // have a timeout set. Hence, we sleep indefinitely in the - // hope that eventually an I/O event for this thread happens. + } else if threads.iter().any(|thread| this.is_thread_blocked_on_host(thread)) { + // At least one thread doesn't have a timeout set, and is blocked on host I/O or is waiting on an + // epoll instance which contains a host source interest. Hence, we sleep indefinitely in the + // hope that eventually an I/O event happens. interp_ok(SchedulingAction::SleepAndWaitForIo(None)) } else { throw_machine_stop!(TerminationInfo::GlobalDeadlock); } } + /// Check whether the provided thread is currently blocked on host I/O. + /// This means, it's either blocked on an I/O operation directly or it's + /// blocked on an epoll instance which contains a host source interest. + fn is_thread_blocked_on_host(&self, thread: &Thread<'tcx>) -> bool { + let this = self.eval_context_ref(); + match &thread.state { + ThreadState::Blocked { reason: BlockReason::IO, .. } => true, + ThreadState::Blocked { reason: BlockReason::Epoll { epfd }, .. } => + this.has_epoll_host_interests(epfd), + _ => false, + } + } + /// Poll for I/O events until either an I/O event happened or the timeout expired. /// The different timeout values are described in [`BlockingIoManager::poll`]. + /// + /// Unblocks all threads which are blocked on I/O and whose I/O interests + /// are currently fulfilled. fn poll_and_unblock(&mut self, timeout: Option) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let ready = match this.machine.blocking_io.poll(timeout) { - Ok(ready) => ready, + match BlockingIoManager::poll(this, timeout)? { + Ok(_) => interp_ok(()), // We can ignore errors originating from interrupts; that's just a spurious wakeup. - Err(e) if e.kind() == io::ErrorKind::Interrupted => return interp_ok(()), + Err(e) if e.kind() == io::ErrorKind::Interrupted => interp_ok(()), // For other errors we panic. On Linux and BSD hosts this should only be // reachable when a system resource error (e.g. ENOMEM or ENOSPC) occurred. Err(e) => panic!("unexpected error while polling: {e}"), - }; - - ready.into_iter().try_for_each(|(receiver, _source)| { - match receiver { - InterestReceiver::UnblockThread(thread_id) => - this.unblock_thread(thread_id, BlockReason::IO), - } - }) + } } /// Find all threads with expired timeouts, unblock them and execute their timeout callbacks. diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 86816e5f789b4..944ff933ac9ba 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -134,7 +134,10 @@ pub use crate::borrow_tracker::{ BorTag, BorrowTrackerMethod, EvalContextExt as _, TreeBorrowsParams, }; pub use crate::clock::{Instant, MonotonicClock}; -pub use crate::concurrency::blocking_io::{BlockingIoManager, EvalContextExt as _, WithSource}; +pub use crate::concurrency::blocking_io::{ + BlockingIoInterest, BlockingIoManager, BlockingIoSourceReadiness, EvalContextExt as _, + SourceFileDescription, +}; pub use crate::concurrency::cpu_affinity::MAX_CPUS; pub use crate::concurrency::data_race::{ AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, EvalContextExt as _, diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index 04b84e6f3e67c..26f98a5f2b196 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -61,6 +61,14 @@ impl FileDescriptionRef { } } +impl PartialEq for FileDescriptionRef { + fn eq(&self, other: &Self) -> bool { + self.0.id == other.0.id + } +} + +impl Eq for FileDescriptionRef {} + /// Holds a weak reference to the actual file description. #[derive(Debug)] pub struct WeakFileDescriptionRef(Weak>); diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs index e15134fa01bb0..551f7b2eff34e 100644 --- a/src/tools/miri/src/shims/mod.rs +++ b/src/tools/miri/src/shims/mod.rs @@ -23,10 +23,10 @@ pub mod time; pub mod tls; pub mod unwind; -pub use self::files::{FdId, FdTable, FileDescriptionRef}; +pub use self::files::{FdId, FdTable, FileDescription, FileDescriptionRef, WeakFileDescriptionRef}; #[cfg(all(feature = "native-lib", unix))] pub use self::native_lib::trace::{init_sv, register_retcode_sv}; -pub use self::unix::{DirTable, EpollInterestTable}; +pub use self::unix::{DirTable, Epoll, EpollEvalContextExt, EpollInterestTable}; /// What needs to be done after emulating an item (a shim or an intrinsic) is done. pub enum EmulateItemResult { diff --git a/src/tools/miri/src/shims/unix/fd.rs b/src/tools/miri/src/shims/unix/fd.rs index 5e5f7d6bc3b84..15b13d3ba57ac 100644 --- a/src/tools/miri/src/shims/unix/fd.rs +++ b/src/tools/miri/src/shims/unix/fd.rs @@ -10,7 +10,7 @@ use rustc_target::spec::Os; use crate::shims::files::FileDescription; use crate::shims::sig::check_min_vararg_count; -use crate::shims::unix::linux_like::epoll::EpollEvents; +use crate::shims::unix::linux_like::epoll::EpollReadiness; use crate::shims::unix::*; use crate::*; @@ -77,7 +77,7 @@ pub trait UnixFileDescription: FileDescription { } /// Return which epoll events are currently active. - fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollEvents> { + fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadiness> { throw_unsup_format!("{}: epoll does not support this file description", self.name()); } } diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs index f679f27aed236..502e4e9307a8e 100644 --- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -4,7 +4,6 @@ use rustc_span::Symbol; use rustc_target::callconv::FnAbi; use self::shims::unix::linux::mem::EvalContextExt as _; -use self::shims::unix::linux_like::epoll::EvalContextExt as _; use self::shims::unix::linux_like::eventfd::EvalContextExt as _; use self::shims::unix::linux_like::syscall::syscall; use crate::machine::{SIGRTMAX, SIGRTMIN}; 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 bd07e13d47fbb..1758c04c8981e 100644 --- a/src/tools/miri/src/shims/unix/linux_like/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux_like/epoll.rs @@ -17,7 +17,7 @@ type EpollEventKey = (FdId, FdNum); /// An `Epoll` file descriptor connects file handles and epoll events #[derive(Debug, Default)] -struct Epoll { +pub struct Epoll { /// A map of EpollEventInterests registered under this epoll instance. Each entry is /// differentiated using FdId and file descriptor value. interest_list: RefCell>, @@ -55,9 +55,9 @@ pub struct EpollEventInterest { data: u64, } -/// EpollReadyEvents reflects the readiness of a file description. +/// Struct reflecting the readiness of a file description. #[derive(Debug)] -pub struct EpollEvents { +pub struct EpollReadiness { /// The associated file is available for read(2) operations, in the sense that a read will not block. /// (I.e., returning EOF is considered "ready".) pub epollin: bool, @@ -76,9 +76,9 @@ pub struct EpollEvents { pub epollerr: bool, } -impl EpollEvents { - pub fn new() -> Self { - EpollEvents { +impl EpollReadiness { + pub fn empty() -> Self { + EpollReadiness { epollin: false, epollout: false, epollrdhup: false, @@ -114,6 +114,19 @@ impl EpollEvents { } } +// Best-effort mapping from cross platform readiness to epoll readiness. +impl From<&BlockingIoSourceReadiness> for EpollReadiness { + fn from(readiness: &BlockingIoSourceReadiness) -> Self { + Self { + epollin: readiness.readable, + epollout: readiness.writable, + epollrdhup: readiness.read_closed, + epollhup: readiness.write_closed, + epollerr: readiness.error, + } + } +} + impl FileDescription for Epoll { fn name(&self) -> &'static str { "epoll" @@ -354,11 +367,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interest.data = data; } + let active_events = fd_ref.as_unix(this).epoll_active_events()?.get_event_bitmask(this); + // Deliver events for the new interest. update_readiness( this, &epfd, - fd_ref.as_unix(this).epoll_active_events()?.get_event_bitmask(this), + active_events, /* force_edge */ true, move |callback| { // Need to release the RefCell when this closure returns, so we have to move @@ -479,7 +494,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // This means there'll be a leak if we never wake up, but that anyway would imply // a thread is permanently blocked so this is fine. this.block_thread( - BlockReason::Epoll, + BlockReason::Epoll { epfd: epfd.clone() }, timeout, callback!( @capture<'tcx> { @@ -547,6 +562,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) } + + /// Recursively check whether the [`Epoll`] file description contains + /// interests which are host I/O source file descriptions. + fn has_epoll_host_interests(&self, epfd: &FileDescriptionRef) -> bool { + let this = self.eval_context_ref(); + epfd.interest_list.borrow().iter().any(|((fd_id, _fd_num), _)| { + // By looking up whether the file description is currently registered, + // we get whether it's a host I/O source file description. + this.machine.blocking_io.contains_source(fd_id) + }) + } } /// Call this when the interests denoted by `for_each_interest` have their active event set changed @@ -557,7 +583,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// be waking up threads which might require access to those `RefCell`. fn update_readiness<'tcx>( ecx: &mut MiriInterpCx<'tcx>, - epoll: &Epoll, + epoll: &FileDescriptionRef, active_events: u32, force_edge: bool, for_each_interest: impl FnOnce( @@ -587,7 +613,7 @@ fn update_readiness<'tcx>( && let Some(thread_id) = epoll.queue.borrow_mut().pop_front() { drop(ready_set); // release the "lock" so the unblocked thread can have it - ecx.unblock_thread(thread_id, BlockReason::Epoll)?; + ecx.unblock_thread(thread_id, BlockReason::Epoll { epfd: epoll.clone() })?; ready_set = epoll.ready_set.borrow_mut(); } @@ -612,7 +638,7 @@ fn return_ready_list<'tcx>( for (key, interest) in interest_list.iter() { // Ensure this matches the latest readiness of this FD. // We have to do an FD lookup by ID for this. The FdNum might be already closed. - let fd = &ecx.machine.fds.fds.values().find(|fd| fd.id() == key.0).unwrap(); + let fd = ecx.machine.fds.fds.values().find(|fd| fd.id() == key.0).unwrap(); let current_active = fd.as_unix(ecx).epoll_active_events()?.get_event_bitmask(ecx); assert_eq!(interest.active_events, current_active & interest.relevant_events); } 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 7cccbd0e275c5..76374ca24d0da 100644 --- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs +++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs @@ -6,7 +6,7 @@ use std::io::ErrorKind; use crate::concurrency::VClock; use crate::shims::files::{FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef}; use crate::shims::unix::UnixFileDescription; -use crate::shims::unix::linux_like::epoll::{EpollEvents, EvalContextExt as _}; +use crate::shims::unix::linux_like::epoll::{EpollReadiness, EvalContextExt as _}; use crate::*; /// Maximum value that the eventfd counter can hold. @@ -114,14 +114,14 @@ impl FileDescription for EventFd { } impl UnixFileDescription for EventFd { - fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollEvents> { + fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadiness> { // We only check the status of EPOLLIN and EPOLLOUT flags for eventfd. If other event flags // need to be supported in the future, the check should be added here. - interp_ok(EpollEvents { + interp_ok(EpollReadiness { epollin: self.counter.get() != 0, epollout: self.counter.get() != MAX_COUNTER, - ..EpollEvents::new() + ..EpollReadiness::empty() }) } } diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs index c55a28bfa7b2a..730c46eb2702c 100644 --- a/src/tools/miri/src/shims/unix/mod.rs +++ b/src/tools/miri/src/shims/unix/mod.rs @@ -20,7 +20,9 @@ mod solarish; pub use self::env::{EvalContextExt as _, UnixEnvVars}; pub use self::fd::{EvalContextExt as _, UnixFileDescription}; pub use self::fs::{DirTable, EvalContextExt as _}; -pub use self::linux_like::epoll::EpollInterestTable; +pub use self::linux_like::epoll::{ + Epoll, EpollInterestTable, EvalContextExt as EpollEvalContextExt, +}; pub use self::mem::EvalContextExt as _; pub use self::socket::EvalContextExt as _; pub use self::sync::EvalContextExt as _; diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index 99a6378704af3..eee3d6fde5c66 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -1,10 +1,9 @@ -use std::cell::{Cell, RefCell}; +use std::cell::{Cell, RefCell, RefMut}; use std::io::Read; use std::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::time::Duration; use std::{io, iter}; -use mio::Interest; use mio::event::Source; use mio::net::{TcpListener, TcpStream}; use rustc_abi::Size; @@ -12,9 +11,9 @@ use rustc_const_eval::interpret::{InterpResult, interp_ok}; use rustc_middle::throw_unsup_format; use rustc_target::spec::Os; -use crate::concurrency::blocking_io::InterestReceiver; use crate::shims::files::{EvalContextExt as _, FdId, FileDescription, FileDescriptionRef}; use crate::shims::unix::UnixFileDescription; +use crate::shims::unix::linux_like::epoll::{EpollReadiness, EvalContextExt as _}; use crate::*; #[derive(Debug, PartialEq)] @@ -56,6 +55,8 @@ struct Socket { state: RefCell, /// Whether this fd is non-blocking or not. is_non_block: Cell, + /// The current blocking I/O readiness of the file description. + io_readiness: RefCell, } impl FileDescription for Socket { @@ -65,12 +66,21 @@ impl FileDescription for Socket { fn destroy<'tcx>( self, - _self_id: FdId, + self_id: FdId, communicate_allowed: bool, - _ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, std::io::Result<()>> { + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, io::Result<()>> { assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!"); + if matches!( + &*self.state.borrow(), + SocketState::Listening(_) | SocketState::Connecting(_) | SocketState::Connected(_) + ) { + // There exists an associated host socket so we need to deregister it + // from the blocking I/O manager. + ecx.machine.blocking_io.deregister(self_id, self) + }; + interp_ok(Ok(())) } @@ -112,8 +122,7 @@ impl FileDescription for Socket { } else { // The socket is in blocking mode and thus the read call should block // until we can read some bytes from the socket. - this.block_for_recv(socket, ptr, len, /* should_peek */ false, finish); - interp_ok(()) + this.block_for_recv(socket, ptr, len, /* should_peek */ false, finish) } } ), @@ -158,8 +167,7 @@ impl FileDescription for Socket { } else { // The socket is in blocking mode and thus the write call should block // until we can write some bytes into the socket. - this.block_for_send(socket, ptr, len, finish); - interp_ok(()) + this.block_for_send(socket, ptr, len, finish) } } ), @@ -167,12 +175,10 @@ impl FileDescription for Socket { } fn short_fd_operations(&self) -> bool { - // 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 + // Linux guarantees that when a read/write on a streaming socket comes back short, + // the kernel buffer is empty/full: + // See in Q&A section. + // So we can't do short reads/writes here. false } @@ -251,6 +257,10 @@ impl UnixFileDescription for Socket { throw_unsup_format!("ioctl: unsupported operation {op:#x} on socket"); } + + fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadiness> { + interp_ok(EpollReadiness::from(&*self.io_readiness.borrow())) + } } impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} @@ -328,6 +338,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { family, state: RefCell::new(SocketState::Initial), is_non_block: Cell::new(is_sock_nonblock), + io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()), }); interp_ok(Scalar::from_i32(fds.insert(fd))) @@ -425,7 +436,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match *state { SocketState::Bound(socket_addr) => match TcpListener::bind(socket_addr) { - Ok(listener) => *state = SocketState::Listening(listener), + Ok(listener) => { + *state = SocketState::Listening(listener); + drop(state); + // Register the socket to the blocking I/O manager because + // we now have an associated host socket. + this.machine.blocking_io.register(socket); + } Err(e) => return this.set_last_error_and_return_i32(e), }, SocketState::Initial => { @@ -537,8 +554,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { address_len_ptr, is_client_sock_nonblock, dest.clone(), - ); - interp_ok(()) + ) } } @@ -591,7 +607,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // don't return an error after receiving an [`Interest::WRITEABLE`] // event on the stream. match TcpStream::connect(address) { - Ok(stream) => *socket.state.borrow_mut() = SocketState::Connecting(stream), + Ok(stream) => { + *socket.state.borrow_mut() = SocketState::Connecting(stream); + // Register the socket to the blocking I/O manager because + // we now have an associated host socket. + this.machine.blocking_io.register(socket.clone()); + } Err(e) => return this.set_last_error_and_return(e, dest), }; @@ -730,8 +751,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Err(e) => this.set_last_error_and_return(e, &dest) } }), - ); - interp_ok(()) + ) } } ), @@ -853,8 +873,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Err(e) => this.set_last_error_and_return(e, &dest) } }), - ); - interp_ok(()) + ) } } ), @@ -1380,11 +1399,11 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { address_len_ptr: Pointer, is_client_sock_nonblock: bool, dest: MPlaceTy<'tcx>, - ) { + ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); this.block_thread_for_io( socket.clone(), - Interest::READABLE, + BlockingIoInterest::Read, None, callback!(@capture<'tcx> { address_ptr: Pointer, @@ -1395,6 +1414,9 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } |this, kind: UnblockKind| { assert_eq!(kind, UnblockKind::Ready); + // Remove the blocking I/O interest for unblocking this thread. + this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread()); + match this.try_non_block_accept(&socket, address_ptr, address_len_ptr, is_client_sock_nonblock)? { Ok(sockfd) => { // We need to create the scalar using the destination size since @@ -1405,13 +1427,12 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }, Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => { // We need to block the thread again as it would still block. - this.block_for_accept(socket, address_ptr, address_len_ptr, is_client_sock_nonblock, dest); - interp_ok(()) + this.block_for_accept(socket, address_ptr, address_len_ptr, is_client_sock_nonblock, dest) } Err(e) => this.set_last_error_and_return(e, &dest), } }), - ); + ) } /// Attempt to accept an incoming connection on the listening socket in a @@ -1437,6 +1458,13 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let (stream, addr) = match listener.accept() { Ok(peer) => peer, + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + // We know that the source is not readable so we need to update its readiness. + socket.io_readiness.borrow_mut().readable = false; + this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?; + + return interp_ok(Err(IoError::HostError(e))); + } Err(e) => return interp_ok(Err(IoError::HostError(e))), }; @@ -1460,7 +1488,11 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { family, state: RefCell::new(SocketState::Connected(stream)), is_non_block: Cell::new(is_client_sock_nonblock), + io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()), }); + // Register the socket to the blocking I/O manager because + // there is an associated host socket. + this.machine.blocking_io.register(fd.clone()); let sockfd = this.machine.fds.insert(fd); interp_ok(Ok(sockfd)) } @@ -1478,11 +1510,11 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { buffer_ptr: Pointer, length: usize, finish: DynMachineCallback<'tcx, Result>, - ) { + ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); this.block_thread_for_io( socket.clone(), - Interest::WRITABLE, + BlockingIoInterest::Write, None, callback!(@capture<'tcx> { socket: FileDescriptionRef, @@ -1492,15 +1524,18 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } |this, kind: UnblockKind| { assert_eq!(kind, UnblockKind::Ready); + // Remove the blocking I/O interest for unblocking this thread. + this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread()); + match this.try_non_block_send(&socket, buffer_ptr, length)? { Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => { - this.block_for_send(socket, buffer_ptr, length, finish); - interp_ok(()) + // We need to block the thread again as it would still block. + this.block_for_send(socket, buffer_ptr, length, finish) }, result => finish.call(this, result) } }), - ); + ) } /// Attempt to send bytes into the connected socket in a non-blocking manner. @@ -1524,7 +1559,13 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // 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 => { + Err(IoError::HostError(e)) + if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) => + { + // We know that the source is not writable so we need to update it's readiness. + socket.io_readiness.borrow_mut().writable = false; + this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?; + // On Windows hosts, `send` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK. interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into()))) @@ -1556,11 +1597,11 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { length: usize, should_peek: bool, finish: DynMachineCallback<'tcx, Result>, - ) { + ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); this.block_thread_for_io( socket.clone(), - Interest::READABLE, + BlockingIoInterest::Read, None, callback!(@capture<'tcx> { socket: FileDescriptionRef, @@ -1571,16 +1612,18 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } |this, kind: UnblockKind| { assert_eq!(kind, UnblockKind::Ready); + // Remove the blocking I/O interest for unblocking this thread. + this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread()); + match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? { Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => { // We need to block the thread again as it would still block. - this.block_for_recv(socket, buffer_ptr, length, should_peek, finish); - interp_ok(()) + this.block_for_recv(socket, buffer_ptr, length, should_peek, finish) }, result => finish.call(this, result) } }), - ); + ) } /// Attempt to receive bytes from the connected socket in a non-blocking manner. @@ -1611,7 +1654,13 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // 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 => { + Err(IoError::HostError(e)) + if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) => + { + // We know that the source is not readable so we need to update it's readiness. + socket.io_readiness.borrow_mut().readable = false; + this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?; + // On Windows hosts, `recv` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK. interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into()))) @@ -1666,7 +1715,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.block_thread_for_io( socket.clone(), - Interest::WRITABLE, + BlockingIoInterest::Write, timeout, callback!( @capture<'tcx> { @@ -1675,11 +1724,13 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { foreign_name: &'static str, action: DynMachineCallback<'tcx, Result<(), ()>>, } |this, kind: UnblockKind| { + // Remove the blocking I/O interest for unblocking this thread. + this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread()); + if UnblockKind::TimedOut == kind { // We can only time out when `should_wait` is false. // This then means that the socket is not yet connected. assert!(!should_wait); - this.machine.blocking_io.deregister(socket.id(), InterestReceiver::UnblockThread(this.active_thread())); return action.call(this, Err(())) } @@ -1752,9 +1803,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { action.call(this, Ok(())) } ), - ); - - interp_ok(()) + ) } } @@ -1764,7 +1813,7 @@ impl VisitProvenance for FileDescriptionRef { fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {} } -impl WithSource for Socket { +impl SourceFileDescription for Socket { fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()> { let mut state = self.state.borrow_mut(); match &mut *state { @@ -1774,4 +1823,8 @@ impl WithSource for Socket { _ => unreachable!(), } } + + fn get_readiness_mut(&self) -> RefMut<'_, BlockingIoSourceReadiness> { + self.io_readiness.borrow_mut() + } } diff --git a/src/tools/miri/src/shims/unix/virtual_socket.rs b/src/tools/miri/src/shims/unix/virtual_socket.rs index 51bd30840ffbd..7fc8556406dbf 100644 --- a/src/tools/miri/src/shims/unix/virtual_socket.rs +++ b/src/tools/miri/src/shims/unix/virtual_socket.rs @@ -13,7 +13,7 @@ use crate::shims::files::{ EvalContextExt as _, FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef, }; use crate::shims::unix::UnixFileDescription; -use crate::shims::unix::linux_like::epoll::{EpollEvents, EvalContextExt as _}; +use crate::shims::unix::linux_like::epoll::{EpollReadiness, EvalContextExt as _}; use crate::*; /// The maximum capacity of the socketpair buffer in bytes. @@ -136,12 +136,10 @@ impl FileDescription for VirtualSocket { } fn short_fd_operations(&self) -> bool { - // 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 + // Linux guarantees that when a read/write on a streaming socket comes back short, + // the kernel buffer is empty/full: + // See in Q&A section. + // So we can't do short reads/writes here. false } @@ -392,20 +390,20 @@ fn virtual_socket_read<'tcx>( } impl UnixFileDescription for VirtualSocket { - fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollEvents> { + fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadiness> { // 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. - let mut epoll_ready_events = EpollEvents::new(); + let mut epoll_readiness = EpollReadiness::empty(); // Check if it is readable. if let Some(readbuf) = &self.readbuf { if !readbuf.borrow().buf.is_empty() { - epoll_ready_events.epollin = true; + epoll_readiness.epollin = true; } } else { // Without a read buffer, reading never blocks, so we are always ready. - epoll_ready_events.epollin = true; + epoll_readiness.epollin = true; } // Check if is writable. @@ -414,28 +412,28 @@ impl UnixFileDescription for VirtualSocket { let data_size = writebuf.borrow().buf.len(); let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(data_size); if available_space != 0 { - epoll_ready_events.epollout = true; + epoll_readiness.epollout = true; } } else { // Without a write buffer, writing never blocks. - epoll_ready_events.epollout = true; + epoll_readiness.epollout = true; } } else { // Peer FD has been closed. This always sets both the RDHUP and HUP flags // as we do not support `shutdown` that could be used to partially close the stream. - epoll_ready_events.epollrdhup = true; - epoll_ready_events.epollhup = true; + epoll_readiness.epollrdhup = true; + epoll_readiness.epollhup = true; // Since the peer is closed, even if no data is available reads will return EOF and // writes will return EPIPE. In other words, they won't block, so we mark this as ready // for read and write. - epoll_ready_events.epollin = true; - epoll_ready_events.epollout = true; + epoll_readiness.epollin = true; + epoll_readiness.epollout = true; // If there is data lost in peer_fd, set EPOLLERR. if self.peer_lost_data.get() { - epoll_ready_events.epollerr = true; + epoll_readiness.epollerr = true; } } - interp_ok(epoll_ready_events) + interp_ok(epoll_readiness) } } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs new file mode 100644 index 0000000000000..b162319d75574 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs @@ -0,0 +1,297 @@ +//@only-target: linux android illumos +//@compile-flags: -Zmiri-disable-isolation +//@revisions: windows_host unix_host +//@[unix_host] ignore-host: windows +//@[windows_host] only-host: windows + +#![feature(io_error_inprogress)] + +#[path = "../../utils/libc.rs"] +mod libc_utils; + +use std::io::ErrorKind; +use std::thread; +use std::time::Duration; + +use libc_utils::epoll::*; +use libc_utils::*; + +const TEST_BYTES: &[u8] = b"these are some test bytes!"; + +fn main() { + test_connect_nonblock(); + test_accept_nonblock(); + test_recv_nonblock(); + #[cfg(not(windows_hosts))] + test_send_nonblock(); +} + +/// Test that connecting to a server socket works when the client +/// socket is non-blocking before the `connect` call. +/// Instead of busy waiting until we no longer get ENOTCONN, we register +/// the client socket to epoll and wait for a WRITABLE event. +fn test_connect_nonblock() { + 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() }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + + unsafe { + // Change client socket to be non-blocking. + errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + // Spawn the server thread. + let server_thread = thread::spawn(move || { + // Yield to client thread to ensure that it actually needs + // to wait until the connection gets accepted. + thread::sleep(Duration::from_millis(10)); + + net::accept_ipv4(server_sockfd).unwrap(); + }); + + // Non-blocking connects always "fail" with EINPROGRESS. + let err = net::connect_ipv4(client_sockfd, addr).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InProgress); + + // Add client socket with WRITABLE interest to epoll. + epoll_ctl_add(epfd, client_sockfd, EPOLLOUT | EPOLLET | EPOLLERR).unwrap(); + + // Wait until we are done connecting. + check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLOUT, data: client_sockfd }], -1); + + // FIXME: Check SO_ERROR here once we implemented `getsockopt`. + + // We should now be connected and thus getting the peer name should work. + net::sockname_ipv4(|storage, len| unsafe { libc::getpeername(client_sockfd, storage, len) }) + .unwrap(); + + server_thread.join().unwrap(); +} + +/// Test that accepting incoming connections works with non-blocking server sockets. +/// Instead of busy waiting until we no longer get EWOULDBLOCK, we add the +/// server socket to an epoll instance and wait for a READABLE event. +fn test_accept_nonblock() { + 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() }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + + unsafe { + // Change server socket to be non-blocking. + errno_check(libc::fcntl(server_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + // Spawn the server thread. + let server_thread = thread::spawn(move || { + // Add client socket with READABLE interest to epoll. + epoll_ctl_add(epfd, server_sockfd, EPOLLIN | EPOLLET | EPOLLERR).unwrap(); + + // Wait until we get a readable event on the server socket. + check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLIN, data: server_sockfd }], -1); + + // Accepting should now be possible. + net::accept_ipv4(server_sockfd).unwrap(); + + // Accepting should now trigger an EWOULDBLOCK. + let err = net::accept_ipv4(server_sockfd).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::WouldBlock); + + // Ensure that the server is no longer readable because we just + // attempted to accept without an incoming connection. + assert_eq!(current_epoll_readiness::<8>(server_sockfd, EPOLLIN | EPOLLET), 0); + }); + + // Yield to server thread such that it actually needs to wait for an + // incoming client connection. + thread::sleep(Duration::from_millis(10)); + + net::connect_ipv4(client_sockfd, addr).unwrap(); + + server_thread.join().unwrap(); +} + +/// Test receiving bytes from a connected stream without blocking. +/// Instead of busy waiting until we no longer receive EWOULDBLOCK when trying to +/// read from the client, we register the client socket to epoll and wait for +/// READABLE events. +fn test_recv_nonblock() { + 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() }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + + // Spawn the server thread. + let server_thread = thread::spawn(move || { + let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap(); + // `peerfd` is a blocking socket now. But that's okay, the client still does non-blocking + // reads/writes. + + // Yield back to client so that it starts receiving before we start sending. + thread::sleep(Duration::from_millis(10)); + + unsafe { + errno_result(libc_utils::write_all( + peerfd, + TEST_BYTES.as_ptr().cast(), + TEST_BYTES.len(), + )) + .unwrap() + }; + }); + + net::connect_ipv4(client_sockfd, addr).unwrap(); + + unsafe { + // Change client socket to be non-blocking. + errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + // We are connected and the server socket is not writing. + + let mut buffer = [0; TEST_BYTES.len()]; + // Receiving from a socket when the peer is not writing is + // not possible without blocking. + let err = unsafe { + errno_result(libc::recv(client_sockfd, buffer.as_mut_ptr().cast(), buffer.len(), 0)) + .unwrap_err() + }; + assert_eq!(err.kind(), ErrorKind::WouldBlock); + + // Try to receive bytes from the peer socket without blocking. + // Since the peer socket might do partial writes, we might need to + // call `epoll_wait` multiple times until we received everything. + + // Add client socket with READABLE interest to epoll. + epoll_ctl_add(epfd, client_sockfd, EPOLLIN | EPOLLET | EPOLLERR).unwrap(); + + let mut bytes_received = 0; + + while bytes_received != buffer.len() { + let read_result = unsafe { + errno_result(libc::recv( + client_sockfd, + buffer.as_mut_ptr().byte_add(bytes_received).cast(), + buffer.len() - bytes_received, + 0, + )) + }; + + match read_result { + Ok(received) => bytes_received += received as usize, + Err(err) if err.kind() == ErrorKind::WouldBlock => { + // Use epoll to block until there's data available again. + check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLIN, data: client_sockfd }], -1); + } + Err(err) => panic!("unexpected error whilst receiving: {err}"), + } + } + + assert_eq!(&buffer, TEST_BYTES); + + let mut buffer = [0u8; 1]; + let err = unsafe { + errno_result(libc::recv(client_sockfd, buffer.as_mut_ptr().cast(), buffer.len(), 0)) + .unwrap_err() + }; + assert_eq!(err.kind(), ErrorKind::WouldBlock); + + // Ensure that the client is no longer readable because + // we just got an EWOULDBLOCK from a receive call. + assert_eq!(current_epoll_readiness::<8>(client_sockfd, EPOLLIN | EPOLLET), 0); + + server_thread.join().unwrap(); +} + +/// Test sending bytes into a connected stream without blocking. +/// Once the buffer is filled we wait for the epoll WRITABLE +/// readiness instead of busy waiting until we can +/// write again. +/// +/// **Note**: This can only be tested on UNIX hosts because +/// the socket write buffers dynamically grow for localhost +/// connections on Windows. +#[cfg(not(windows_hosts))] +fn test_send_nonblock() { + 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() }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + + // Spawn the server thread. + let server_thread = thread::spawn(move || { + let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap(); + peerfd + }); + + net::connect_ipv4(client_sockfd, addr).unwrap(); + + unsafe { + // Change client socket to be non-blocking. + errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + // Try to send bytes to the peer socket without blocking. + // We first fill up the buffer and ensure that we keep the + // writable readiness during the fill up process. + + // Add client socket with READABLE interest to epoll. + epoll_ctl_add(epfd, client_sockfd, EPOLLOUT | EPOLLET | EPOLLERR).unwrap(); + + let fill_buf = [1u8; 32_000]; + let mut total_written = 0usize; + loop { + // Ensure the socket is still writable. + assert_eq!(current_epoll_readiness::<8>(client_sockfd, EPOLLOUT | EPOLLET), EPOLLOUT); + + let result = unsafe { + errno_result(libc::send(client_sockfd, fill_buf.as_ptr().cast(), fill_buf.len(), 0)) + }; + + match result { + Ok(written) => total_written += written as usize, + Err(err) if err.kind() == ErrorKind::WouldBlock => break, + Err(err) => panic!("unexpected error whilst filling up buffer: {err}"), + } + } + + // The buffer should be filled up because we received an EWOULDBLOCK. + // This also means that the client socket should no longer be writable. + assert_eq!(current_epoll_readiness::<8>(client_sockfd, EPOLLOUT | EPOLLET), 0); + + let peerfd = server_thread.join().unwrap(); + + // Spawn the reader thread. + let reader_thread = thread::spawn(move || { + // Read half of the bytes needed to fill the buffer. + // This should make the client buffer writable again. + // We need to do it this way because different hosts + // have different read thresholds after which they + // trigger a new writable event. + let mut buffer = Vec::with_capacity(total_written / 2); + buffer.fill(0u8); + unsafe { + errno_result(libc_utils::read_all_generic( + buffer.as_mut_ptr().cast(), + total_written / 2, + libc_utils::NoRetry, + |buf, count| libc::recv(peerfd, buf, count, 0), + )) + .unwrap() + }; + }); + + // Wait until the socket is again writable. + check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLOUT, data: client_sockfd }], -1); + + let fill_buf = [1u8; 100]; + // We should be able to write again without blocking because we just received + // a writable event. + unsafe { + errno_result(libc::send(client_sockfd, fill_buf.as_ptr().cast(), fill_buf.len(), 0)) + .unwrap() + }; + + reader_thread.join().unwrap(); +} diff --git a/src/tools/miri/tests/utils/libc.rs b/src/tools/miri/tests/utils/libc.rs index 26797ee4c3cba..89a610194ccce 100644 --- a/src/tools/miri/tests/utils/libc.rs +++ b/src/tools/miri/tests/utils/libc.rs @@ -155,7 +155,7 @@ pub mod epoll { use libc::c_int; pub use libc::{EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD}; // Re-export some constants we need a lot for this. - pub use libc::{EPOLLET, EPOLLHUP, EPOLLIN, EPOLLOUT, EPOLLRDHUP}; + pub use libc::{EPOLLERR, EPOLLET, EPOLLHUP, EPOLLIN, EPOLLOUT, EPOLLRDHUP}; use super::*; @@ -204,6 +204,30 @@ pub mod epoll { pub fn check_epoll_wait_noblock(epfd: i32, expected: &[Ev]) { check_epoll_wait::(epfd, expected, 0); } + + /// Query the current epoll readiness of a file descriptor. + /// This is done by creating a new epoll instance, adding the + /// fd to the epoll interests and then performing a zero-timeout + /// wait. + pub fn current_epoll_readiness(fd: i32, interests: i32) -> c_int { + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + // Add fd with all possible interests to epoll instance. + epoll_ctl_add(epfd, fd, interests).unwrap(); + + let mut array: [libc::epoll_event; N] = [libc::epoll_event { events: 0, u64: 0 }; N]; + let num = errno_result(unsafe { + // Use zero-timeout to just query without waiting. + libc::epoll_wait(epfd, array.as_mut_ptr(), N.try_into().unwrap(), 0) + }) + .expect("epoll_wait returned an error"); + + let mut readiness = 0; + let events = &mut array[..num.try_into().unwrap()]; + events.iter().for_each(|e| { + readiness |= e.events.cast_signed(); + }); + readiness + } } pub mod net { From b7548cb1e058b32f1cac51178ced948a034ca185 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 2 May 2026 21:48:40 +1000 Subject: [PATCH 11/37] Reformat `string_enum!` --- src/tools/compiletest/src/util.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/tools/compiletest/src/util.rs b/src/tools/compiletest/src/util.rs index 26d2edaedbbe2..cded36b3698a5 100644 --- a/src/tools/compiletest/src/util.rs +++ b/src/tools/compiletest/src/util.rs @@ -95,21 +95,34 @@ macro_rules! static_regex { pub(crate) use static_regex; macro_rules! string_enum { - ($(#[$meta:meta])* $vis:vis enum $name:ident { $($variant:ident => $repr:expr,)* }) => { + ( + $(#[$meta:meta])* + $vis:vis enum $name:ident { + $( + $variant:ident => $repr:expr, + )* + } + ) => { $(#[$meta])* $vis enum $name { - $($variant,)* + $( + $variant, + )* } impl $name { #[allow(dead_code)] - $vis const VARIANTS: &'static [Self] = &[$(Self::$variant,)*]; + $vis const VARIANTS: &'static [Self] = &[ + $( Self::$variant, )* + ]; #[allow(dead_code)] - $vis const STR_VARIANTS: &'static [&'static str] = &[$(Self::$variant.to_str(),)*]; + $vis const STR_VARIANTS: &'static [&'static str] = &[ + $( Self::$variant.to_str(), )* + ]; $vis const fn to_str(&self) -> &'static str { match self { - $(Self::$variant => $repr,)* + $( Self::$variant => $repr, )* } } } @@ -125,7 +138,7 @@ macro_rules! string_enum { fn from_str(s: &str) -> Result { match s { - $($repr => Ok(Self::$variant),)* + $( $repr => Ok(Self::$variant), )* _ => Err(format!(concat!("unknown `", stringify!($name), "` variant: `{}`"), s)), } } From bd0533d83fe381ba53c45a3114302732fce52d03 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 30 Apr 2026 16:45:00 +1000 Subject: [PATCH 12/37] Replace `pass_mode` and `fail_mode` fields with `pass_fail_mode` This commit introduces `PassFailMode` as an internal detail of `TestProps`, but the full migration is left to a subsequent commit. --- src/tools/compiletest/src/common.rs | 42 ++++++++ src/tools/compiletest/src/directives.rs | 97 ++++++------------- .../compiletest/src/directives/handlers.rs | 12 +-- src/tools/compiletest/src/runtest.rs | 7 +- src/tools/compiletest/src/runtest/ui.rs | 12 +-- 5 files changed, 85 insertions(+), 85 deletions(-) diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index f73d7c96f7e85..8b81c4e25d5b2 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -81,6 +81,48 @@ string_enum! { } } +string_enum! { + #[derive(Clone, Copy, PartialEq, Eq, Debug)] + pub(crate) enum PassFailMode { + CheckFail => "check-fail", + CheckPass => "check-pass", + BuildFail => "build-fail", + BuildPass => "build-pass", + RunFail => "run-fail", + RunCrash => "run-crash", + RunFailOrCrash => "run-fail-or-crash", + RunPass => "run-pass", + } +} + +impl PassFailMode { + pub(crate) fn fail_mode(&self) -> Option { + match self { + PassFailMode::CheckFail => Some(FailMode::Check), + PassFailMode::BuildFail => Some(FailMode::Build), + PassFailMode::RunFail => Some(FailMode::Run(RunFailMode::Fail)), + PassFailMode::RunCrash => Some(FailMode::Run(RunFailMode::Crash)), + PassFailMode::RunFailOrCrash => Some(FailMode::Run(RunFailMode::FailOrCrash)), + + PassFailMode::CheckPass | PassFailMode::BuildPass | PassFailMode::RunPass => None, + } + } + + pub(crate) fn pass_mode(&self) -> Option { + match self { + PassFailMode::CheckPass => Some(PassMode::Check), + PassFailMode::BuildPass => Some(PassMode::Build), + PassFailMode::RunPass => Some(PassMode::Run), + + PassFailMode::CheckFail + | PassFailMode::BuildFail + | PassFailMode::RunFail + | PassFailMode::RunCrash + | PassFailMode::RunFailOrCrash => None, + } + } +} + string_enum! { #[derive(Clone, Copy, PartialEq, Debug, Hash)] pub(crate) enum PassMode { diff --git a/src/tools/compiletest/src/directives.rs b/src/tools/compiletest/src/directives.rs index 17f89f1dd9a00..5e347193e991e 100644 --- a/src/tools/compiletest/src/directives.rs +++ b/src/tools/compiletest/src/directives.rs @@ -6,7 +6,7 @@ use camino::{Utf8Path, Utf8PathBuf}; use semver::Version; use tracing::*; -use crate::common::{CodegenBackend, Config, Debugger, FailMode, PassMode, RunFailMode, TestMode}; +use crate::common::{CodegenBackend, Config, Debugger, FailMode, PassFailMode, PassMode, TestMode}; use crate::debuggers::{extract_cdb_version, extract_gdb_version}; use crate::directives::auxiliary::parse_and_update_aux; pub(crate) use crate::directives::auxiliary::{AuxCrate, AuxProps}; @@ -165,12 +165,13 @@ pub(crate) struct TestProps { // error annotations are needed, but this may be updated in the future to // include other relaxations. pub(crate) known_bug: bool, - // How far should the test proceed while still passing. - pass_mode: Option, + /// Whether this is a check, build, or build-and-run test, and whether the + /// final step should succeed or fail. + /// + /// None for non-UI tests, and for auxiliary crates used by UI tests. + pub(crate) pass_fail_mode: Option, // Ignore `--pass` overrides from the command line for this test. ignore_pass: bool, - // How far this test should proceed to start failing. - pub(crate) fail_mode: Option, // rustdoc will test the output of the `--test` option pub(crate) check_test_line_numbers_match: bool, // customized normalization rules @@ -296,8 +297,7 @@ impl TestProps { incremental_dir: None, incremental: false, known_bug: false, - pass_mode: None, - fail_mode: None, + pass_fail_mode: None, ignore_pass: false, check_test_line_numbers_match: false, normalize_stdout: vec![], @@ -343,10 +343,9 @@ impl TestProps { props.load_from(testfile, revision, config); props.exec_env.push(("RUSTC".to_string(), config.rustc_path.to_string())); - match (props.pass_mode, props.fail_mode) { - (None, None) if config.mode == TestMode::Ui => props.fail_mode = Some(FailMode::Check), - (Some(_), Some(_)) => panic!("cannot use a *-fail and *-pass mode together"), - _ => {} + // UI tests default to `//@ check-fail` if unspecified. + if config.mode == TestMode::Ui && props.pass_fail_mode.is_none() { + props.pass_fail_mode = Some(PassFailMode::CheckFail); } props @@ -406,73 +405,37 @@ impl TestProps { } } - fn update_fail_mode(&mut self, ln: &DirectiveLine<'_>, config: &Config) { - let check_ui = |mode: &str| { - if config.mode != TestMode::Ui { - panic!("`{}-fail` directive is only supported in UI tests", mode); - } - }; - let fail_mode = if config.parse_name_directive(ln, "check-fail") { - check_ui("check"); - Some(FailMode::Check) - } else if config.parse_name_directive(ln, "build-fail") { - check_ui("build"); - Some(FailMode::Build) - } else if config.parse_name_directive(ln, "run-fail") { - check_ui("run"); - Some(FailMode::Run(RunFailMode::Fail)) - } else if config.parse_name_directive(ln, "run-crash") { - check_ui("run"); - Some(FailMode::Run(RunFailMode::Crash)) - } else if config.parse_name_directive(ln, "run-fail-or-crash") { - check_ui("run"); - Some(FailMode::Run(RunFailMode::FailOrCrash)) - } else { - None - }; - match (self.fail_mode, fail_mode) { - (None, Some(_)) => self.fail_mode = fail_mode, - (Some(_), Some(_)) => panic!("multiple `*-fail` directives in a single test"), - (_, None) => {} + fn update_pass_fail_mode(&mut self, ln: &DirectiveLine<'_>, config: &Config) { + let name = ln.name; + if config.mode != TestMode::Ui { + panic!("`{name}` directive is only supported in UI tests"); } + if self.pass_fail_mode.is_some() { + panic!("multiple `*-fail` or `*-pass` directives in a single test"); + } + + let mode = ln.name.parse::().unwrap(); + self.pass_fail_mode = Some(mode); } - fn update_pass_mode(&mut self, ln: &DirectiveLine<'_>, config: &Config) { - let check_no_run = |s| match (config.mode, s) { - (TestMode::Ui, _) => (), - (mode, _) => panic!("`{s}` directive is not supported in `{mode}` tests"), - }; - let pass_mode = if config.parse_name_directive(ln, "check-pass") { - check_no_run("check-pass"); - Some(PassMode::Check) - } else if config.parse_name_directive(ln, "build-pass") { - check_no_run("build-pass"); - Some(PassMode::Build) - } else if config.parse_name_directive(ln, "run-pass") { - check_no_run("run-pass"); - Some(PassMode::Run) - } else { - None - }; - match (self.pass_mode, pass_mode) { - (None, Some(_)) => self.pass_mode = pass_mode, - (Some(_), Some(_)) => panic!("multiple `*-pass` directives in a single test"), - (_, None) => {} - } + pub(crate) fn fail_mode(&self) -> Option { + self.pass_fail_mode?.fail_mode() } pub(crate) fn pass_mode(&self, config: &Config) -> Option { - if !self.ignore_pass && self.fail_mode.is_none() { - if let mode @ Some(_) = config.force_pass_mode { - return mode; - } + let declared = self.local_pass_mode()?; + if let Some(force_pass_mode) = config.force_pass_mode + && !self.ignore_pass + { + Some(force_pass_mode) + } else { + Some(declared) } - self.pass_mode } // does not consider CLI override for pass mode pub(crate) fn local_pass_mode(&self) -> Option { - self.pass_mode + self.pass_fail_mode?.pass_mode() } fn update_add_minicore(&mut self, ln: &DirectiveLine<'_>, config: &Config) { diff --git a/src/tools/compiletest/src/directives/handlers.rs b/src/tools/compiletest/src/directives/handlers.rs index a1d6bd3088239..fa36c86bde259 100644 --- a/src/tools/compiletest/src/directives/handlers.rs +++ b/src/tools/compiletest/src/directives/handlers.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::sync::{Arc, LazyLock}; -use crate::common::{Config, TestMode}; +use crate::common::{Config, PassFailMode, TestMode}; use crate::directives::{ DirectiveLine, NormalizeKind, NormalizeRule, TestProps, parse_and_update_aux, parse_edition_range, split_flags, @@ -196,15 +196,9 @@ fn make_directive_handlers_map() -> HashMap<&'static str, Handler> { &mut props.check_test_line_numbers_match, ); }), - multi_handler(&["check-pass", "build-pass", "run-pass"], |config, ln, props| { - props.update_pass_mode(ln, config); + multi_handler(PassFailMode::STR_VARIANTS, |config, ln, props| { + props.update_pass_fail_mode(ln, config); }), - multi_handler( - &["check-fail", "build-fail", "run-fail", "run-crash", "run-fail-or-crash"], - |config, ln, props| { - props.update_fail_mode(ln, config); - }, - ), handler(IGNORE_PASS, |config, ln, props| { config.set_name_directive(ln, IGNORE_PASS, &mut props.ignore_pass); }), diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index ab268f944816f..5dbe869f4075d 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -296,7 +296,8 @@ impl<'test> TestCx<'test> { fn should_run(&self, pm: Option) -> WillExecute { let test_should_run = match self.config.mode { TestMode::Ui => { - pm == Some(PassMode::Run) || matches!(self.props.fail_mode, Some(FailMode::Run(_))) + pm == Some(PassMode::Run) + || matches!(self.props.fail_mode(), Some(FailMode::Run(_))) } mode => panic!("unimplemented for mode {:?}", mode), }; @@ -317,7 +318,7 @@ impl<'test> TestCx<'test> { fn should_compile_successfully(&self, pm: Option) -> bool { match self.config.mode { TestMode::RustdocJs => true, - TestMode::Ui => pm.is_some() || self.props.fail_mode > Some(FailMode::Build), + TestMode::Ui => pm.is_some() || self.props.fail_mode() > Some(FailMode::Build), TestMode::Crashes => false, mode => panic!("unimplemented for mode {:?}", mode), } @@ -907,7 +908,7 @@ impl<'test> TestCx<'test> { } fn should_emit_metadata(&self, pm: Option) -> Emit { - match (pm, self.props.fail_mode, self.config.mode) { + match (pm, self.props.fail_mode(), self.config.mode) { (Some(PassMode::Check), ..) | (_, Some(FailMode::Check), TestMode::Ui) => { Emit::Metadata } diff --git a/src/tools/compiletest/src/runtest/ui.rs b/src/tools/compiletest/src/runtest/ui.rs index 2d8b8c057d802..61fb2643c8b4e 100644 --- a/src/tools/compiletest/src/runtest/ui.rs +++ b/src/tools/compiletest/src/runtest/ui.rs @@ -13,11 +13,11 @@ use crate::runtest::{ impl TestCx<'_> { pub(super) fn run_ui_test(&self) { - if let Some(FailMode::Build) = self.props.fail_mode { + if let Some(FailMode::Build) = self.props.fail_mode() { // Make sure a build-fail test cannot fail due to failing analysis (e.g. typeck). let proc_res = self.compile_test(WillExecute::No, Emit::Metadata); self.check_if_test_should_compile( - self.props.fail_mode, + self.props.fail_mode(), Some(PassMode::Check), &proc_res, ); @@ -27,7 +27,7 @@ impl TestCx<'_> { let should_run = self.should_run(pm); let emit_metadata = self.should_emit_metadata(pm); let proc_res = self.compile_test(should_run, emit_metadata); - self.check_if_test_should_compile(self.props.fail_mode, pm, &proc_res); + self.check_if_test_should_compile(self.props.fail_mode(), pm, &proc_res); if matches!(proc_res.truncated, Truncated::Yes) && !self.props.dont_check_compiler_stdout && !self.props.dont_check_compiler_stderr @@ -167,7 +167,7 @@ impl TestCx<'_> { &proc_res, ); } - } else if self.props.fail_mode == Some(FailMode::Run(RunFailMode::Fail)) { + } else if self.props.fail_mode() == Some(FailMode::Run(RunFailMode::Fail)) { // If the test is marked as `run-fail` but do not support // unwinding we allow it to crash, since a panic will trigger an // abort (crash) instead of unwind (exit with code 101). @@ -183,11 +183,11 @@ impl TestCx<'_> { }; self.fatal_proc_rec(&err, &proc_res); } - } else if self.props.fail_mode == Some(FailMode::Run(RunFailMode::Crash)) { + } else if self.props.fail_mode() == Some(FailMode::Run(RunFailMode::Crash)) { if run_result != RunResult::Crash { self.fatal_proc_rec(&format!("test did not crash! {pass_hint}"), &proc_res); } - } else if self.props.fail_mode == Some(FailMode::Run(RunFailMode::FailOrCrash)) { + } else if self.props.fail_mode() == Some(FailMode::Run(RunFailMode::FailOrCrash)) { if run_result != RunResult::Fail && run_result != RunResult::Crash { self.fatal_proc_rec( &format!("test did not exit with failure or crash! {pass_hint}"), From 78c27e9a6f69eaba949cc49e0a2328d83603dc43 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 30 Apr 2026 21:34:16 +1000 Subject: [PATCH 13/37] Migrate from `PassMode`/`FailMode` to `PassFailMode` --- src/tools/compiletest/src/common.rs | 79 ++++++++-------- src/tools/compiletest/src/directives.rs | 26 +----- src/tools/compiletest/src/lib.rs | 6 +- src/tools/compiletest/src/runtest.rs | 117 +++++++++++------------- src/tools/compiletest/src/runtest/ui.rs | 36 ++++---- src/tools/compiletest/src/util.rs | 2 + 6 files changed, 120 insertions(+), 146 deletions(-) diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index 8b81c4e25d5b2..1cf9c94a11721 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -88,44 +88,67 @@ string_enum! { CheckPass => "check-pass", BuildFail => "build-fail", BuildPass => "build-pass", + /// Running the program must make it exit with a regular failure exit code + /// in the range `1..=127`. If the program is terminated by e.g. a signal + /// the test will fail. RunFail => "run-fail", + /// Running the program must result in a crash, e.g. by `SIGABRT` or + /// `SIGSEGV` on Unix or on Windows by having an appropriate NTSTATUS high + /// bit in the exit code. RunCrash => "run-crash", + /// Running the program must either fail or crash. Useful for e.g. sanitizer + /// tests since some sanitizer implementations exit the process with code 1 + /// to in the face of memory errors while others abort (crash) the process + /// in the face of memory errors. RunFailOrCrash => "run-fail-or-crash", RunPass => "run-pass", } } impl PassFailMode { - pub(crate) fn fail_mode(&self) -> Option { + pub(crate) fn is_pass(&self) -> bool { match self { - PassFailMode::CheckFail => Some(FailMode::Check), - PassFailMode::BuildFail => Some(FailMode::Build), - PassFailMode::RunFail => Some(FailMode::Run(RunFailMode::Fail)), - PassFailMode::RunCrash => Some(FailMode::Run(RunFailMode::Crash)), - PassFailMode::RunFailOrCrash => Some(FailMode::Run(RunFailMode::FailOrCrash)), + PassFailMode::CheckPass | PassFailMode::BuildPass | PassFailMode::RunPass => true, - PassFailMode::CheckPass | PassFailMode::BuildPass | PassFailMode::RunPass => None, + PassFailMode::CheckFail + | PassFailMode::BuildFail + | PassFailMode::RunFail + | PassFailMode::RunCrash + | PassFailMode::RunFailOrCrash => false, } } - pub(crate) fn pass_mode(&self) -> Option { + pub(crate) fn is_check(&self) -> bool { match self { - PassFailMode::CheckPass => Some(PassMode::Check), - PassFailMode::BuildPass => Some(PassMode::Build), - PassFailMode::RunPass => Some(PassMode::Run), + PassFailMode::CheckFail | PassFailMode::CheckPass => true, + + PassFailMode::BuildFail + | PassFailMode::BuildPass + | PassFailMode::RunFail + | PassFailMode::RunCrash + | PassFailMode::RunFailOrCrash + | PassFailMode::RunPass => false, + } + } + pub(crate) fn is_run(&self) -> bool { + match self { PassFailMode::CheckFail + | PassFailMode::CheckPass | PassFailMode::BuildFail - | PassFailMode::RunFail + | PassFailMode::BuildPass => false, + + PassFailMode::RunFail | PassFailMode::RunCrash - | PassFailMode::RunFailOrCrash => None, + | PassFailMode::RunFailOrCrash + | PassFailMode::RunPass => true, } } } string_enum! { #[derive(Clone, Copy, PartialEq, Debug, Hash)] - pub(crate) enum PassMode { + pub(crate) enum ForcePassMode { Check => "check", Build => "build", Run => "run", @@ -141,30 +164,6 @@ string_enum! { } } -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] -pub(crate) enum RunFailMode { - /// Running the program must make it exit with a regular failure exit code - /// in the range `1..=127`. If the program is terminated by e.g. a signal - /// the test will fail. - Fail, - /// Running the program must result in a crash, e.g. by `SIGABRT` or - /// `SIGSEGV` on Unix or on Windows by having an appropriate NTSTATUS high - /// bit in the exit code. - Crash, - /// Running the program must either fail or crash. Useful for e.g. sanitizer - /// tests since some sanitizer implementations exit the process with code 1 - /// to in the face of memory errors while others abort (crash) the process - /// in the face of memory errors. - FailOrCrash, -} - -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] -pub(crate) enum FailMode { - Check, - Build, - Run(RunFailMode), -} - string_enum! { #[derive(Clone, Debug, PartialEq)] pub(crate) enum CompareMode { @@ -512,7 +511,7 @@ pub(crate) struct Config { /// FIXME: make it even more obvious (especially in PR CI where `--pass=check` is used) when a /// pass mode is forced when the test fails, because it can be very non-obvious when e.g. an /// error is emitted only when `//@ build-pass` but not `//@ check-pass`. - pub(crate) force_pass_mode: Option, + pub(crate) force_pass_mode: Option, /// Explicitly enable or disable running of the target test binary. /// @@ -549,7 +548,7 @@ pub(crate) struct Config { /// may override this setting. /// /// FIXME: this flag / config option is somewhat misleading. For instance, in ui tests, it's - /// *only* applied to the [`PassMode::Run`] test crate and not its auxiliaries. + /// *only* applied to the [`PassFailMode::RunPass`] test crate and not its auxiliaries. pub(crate) optimize_tests: bool, /// Target platform tuple. diff --git a/src/tools/compiletest/src/directives.rs b/src/tools/compiletest/src/directives.rs index 5e347193e991e..51a9e638fd7a4 100644 --- a/src/tools/compiletest/src/directives.rs +++ b/src/tools/compiletest/src/directives.rs @@ -6,7 +6,7 @@ use camino::{Utf8Path, Utf8PathBuf}; use semver::Version; use tracing::*; -use crate::common::{CodegenBackend, Config, Debugger, FailMode, PassFailMode, PassMode, TestMode}; +use crate::common::{CodegenBackend, Config, Debugger, PassFailMode, TestMode}; use crate::debuggers::{extract_cdb_version, extract_gdb_version}; use crate::directives::auxiliary::parse_and_update_aux; pub(crate) use crate::directives::auxiliary::{AuxCrate, AuxProps}; @@ -171,7 +171,7 @@ pub(crate) struct TestProps { /// None for non-UI tests, and for auxiliary crates used by UI tests. pub(crate) pass_fail_mode: Option, // Ignore `--pass` overrides from the command line for this test. - ignore_pass: bool, + pub(crate) ignore_pass: bool, // rustdoc will test the output of the `--test` option pub(crate) check_test_line_numbers_match: bool, // customized normalization rules @@ -418,26 +418,6 @@ impl TestProps { self.pass_fail_mode = Some(mode); } - pub(crate) fn fail_mode(&self) -> Option { - self.pass_fail_mode?.fail_mode() - } - - pub(crate) fn pass_mode(&self, config: &Config) -> Option { - let declared = self.local_pass_mode()?; - if let Some(force_pass_mode) = config.force_pass_mode - && !self.ignore_pass - { - Some(force_pass_mode) - } else { - Some(declared) - } - } - - // does not consider CLI override for pass mode - pub(crate) fn local_pass_mode(&self) -> Option { - self.pass_fail_mode?.pass_mode() - } - fn update_add_minicore(&mut self, ln: &DirectiveLine<'_>, config: &Config) { let add_minicore = config.parse_name_directive(ln, directives::ADD_MINICORE); if add_minicore { @@ -452,7 +432,7 @@ impl TestProps { // FIXME(jieyouxu): this check is currently order-dependent, but we should probably // collect all directives in one go then perform a validation pass after that. - if self.local_pass_mode().is_some_and(|pm| pm == PassMode::Run) { + if self.pass_fail_mode == Some(PassFailMode::RunPass) { // `minicore` can only be used with non-run modes, because it's `core` prelude stubs // and can't run. panic!("`add-minicore` cannot be used to run the test binary"); diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index dcea79b18e6a8..5494d3f26eb76 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -40,8 +40,8 @@ use walkdir::WalkDir; use self::directives::{EarlyProps, make_test_description}; use crate::common::{ - CodegenBackend, CompareMode, Config, Debugger, PassMode, TestMode, TestPaths, UI_EXTENSIONS, - expected_output_path, output_base_dir, output_relative_path, + CodegenBackend, CompareMode, Config, Debugger, ForcePassMode, TestMode, TestPaths, + UI_EXTENSIONS, expected_output_path, output_base_dir, output_relative_path, }; use crate::directives::{AuxProps, DirectivesCache, FileDirectives}; use crate::edition::parse_edition; @@ -446,7 +446,7 @@ fn parse_config(args: Vec) -> Config { skip: matches.opt_strs("skip"), filter_exact: matches.opt_present("exact"), force_pass_mode: matches.opt_str("pass").map(|mode| { - mode.parse::() + mode.parse::() .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode)) }), // FIXME: this run scheme is... confusing. diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 5dbe869f4075d..514c060e414e4 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -14,7 +14,7 @@ use regex::{Captures, Regex}; use tracing::*; use crate::common::{ - CompareMode, Config, Debugger, FailMode, PassMode, RunFailMode, RunResult, TestMode, TestPaths, + CompareMode, Config, Debugger, ForcePassMode, PassFailMode, RunResult, TestMode, TestPaths, TestSuite, UI_EXTENSIONS, UI_FIXED, UI_RUN_STDERR, UI_RUN_STDOUT, UI_STDERR, UI_STDOUT, UI_SVG, UI_WINDOWS_SVG, expected_output_path, incremental_dir, output_base_dir, output_base_name, }; @@ -289,70 +289,68 @@ impl<'test> TestCx<'test> { } } - fn pass_mode(&self) -> Option { - self.props.pass_mode(self.config) - } + /// Returns the pass/fail expectation of this UI test + /// (e.g. `//@ check-pass` or `//@ build-fail`), + /// possibly modified by an explicit `--pass=check` on the command-line. + fn effective_pass_fail_mode(&self) -> Option { + assert_eq!(self.config.mode, TestMode::Ui); + // UI tests always have a pass/fail mode, but their auxiliary crates never have one. + let declared = self.props.pass_fail_mode?; - fn should_run(&self, pm: Option) -> WillExecute { - let test_should_run = match self.config.mode { - TestMode::Ui => { - pm == Some(PassMode::Run) - || matches!(self.props.fail_mode(), Some(FailMode::Run(_))) + // Specifying `--pass` only overrides `//@ pass-*` modes, and only if + // the test doesn't opt out with `//@ ignore-pass`. + if let Some(force_pass_mode) = self.config.force_pass_mode + && !self.props.ignore_pass + && declared.is_pass() + { + match force_pass_mode { + ForcePassMode::Check => Some(PassFailMode::CheckPass), + ForcePassMode::Build => Some(PassFailMode::BuildPass), + ForcePassMode::Run => Some(PassFailMode::RunPass), } - mode => panic!("unimplemented for mode {:?}", mode), - }; - if test_should_run { self.run_if_enabled() } else { WillExecute::No } + } else { + Some(declared) + } } fn run_if_enabled(&self) -> WillExecute { if self.config.run_enabled() { WillExecute::Yes } else { WillExecute::Disabled } } - fn should_run_successfully(&self, pm: Option) -> bool { - match self.config.mode { - TestMode::Ui => pm == Some(PassMode::Run), - mode => panic!("unimplemented for mode {:?}", mode), - } - } + fn check_if_test_should_compile(&self, pass_fail: PassFailMode, proc_res: &ProcRes) { + assert_eq!(self.config.mode, TestMode::Ui); - fn should_compile_successfully(&self, pm: Option) -> bool { - match self.config.mode { - TestMode::RustdocJs => true, - TestMode::Ui => pm.is_some() || self.props.fail_mode() > Some(FailMode::Build), - TestMode::Crashes => false, - mode => panic!("unimplemented for mode {:?}", mode), - } - } + let should_compile_successfully = match pass_fail { + PassFailMode::CheckFail | PassFailMode::BuildFail => false, - fn check_if_test_should_compile( - &self, - fail_mode: Option, - pass_mode: Option, - proc_res: &ProcRes, - ) { - if self.should_compile_successfully(pass_mode) { + PassFailMode::CheckPass + | PassFailMode::BuildPass + | PassFailMode::RunFail + | PassFailMode::RunCrash + | PassFailMode::RunFailOrCrash + | PassFailMode::RunPass => true, + }; + + if should_compile_successfully { if !proc_res.status.success() { - match (fail_mode, pass_mode) { - (Some(FailMode::Build), Some(PassMode::Check)) => { - // A `build-fail` test needs to `check-pass`. - self.fatal_proc_rec( - "`build-fail` test is required to pass check build, but check build failed", - proc_res, - ); - } - _ => { - self.fatal_proc_rec( - "test compilation failed although it shouldn't!", - proc_res, - ); - } + if pass_fail == PassFailMode::CheckPass + && self.effective_pass_fail_mode() == Some(PassFailMode::BuildFail) + { + // A `build-fail` test needs to `check-pass`. + self.fatal_proc_rec( + "`build-fail` test is required to pass check build, but check build failed", + proc_res, + ); + } else { + self.fatal_proc_rec("test compilation failed although it shouldn't!", proc_res); } } } else { if proc_res.status.success() { let err = &format!("{} test did not emit an error", self.config.mode); - let extra_note = (self.config.mode == crate::common::TestMode::Ui) - .then_some("note: by default, ui tests are expected not to compile.\nhint: use check-pass, build-pass, or run-pass directive to change this behavior."); + let extra_note = Some( + "note: by default, ui tests are expected not to compile.\nhint: use check-pass, build-pass, or run-pass directive to change this behavior.", + ); self.fatal_proc_rec_general(err, extra_note, proc_res, || ()); } @@ -907,15 +905,6 @@ impl<'test> TestCx<'test> { } } - fn should_emit_metadata(&self, pm: Option) -> Emit { - match (pm, self.props.fail_mode(), self.config.mode) { - (Some(PassMode::Check), ..) | (_, Some(FailMode::Check), TestMode::Ui) => { - Emit::Metadata - } - _ => Emit::None, - } - } - fn compile_test(&self, will_execute: WillExecute, emit: Emit) -> ProcRes { self.compile_test_general(will_execute, emit, Vec::new()) } @@ -944,10 +933,12 @@ impl<'test> TestCx<'test> { // let's just ignore unused code warnings by defaults and tests // can turn it back on if needed. if compiler_kind == CompilerKind::Rustc - // Note that we use the local pass mode here as we don't want + // Note that we use the declared pass mode here as we don't want // to set unused to allow if we've overridden the pass mode // via command line flags. - && self.props.local_pass_mode() != Some(PassMode::Run) + // FIXME(Zalathar): We should probably also warn in run-fail/crash + // tests, but that requires changes to some existing tests. + && self.props.pass_fail_mode != Some(PassFailMode::RunPass) { AllowUnused::Yes } else { @@ -1656,9 +1647,11 @@ impl<'test> TestCx<'test> { TestMode::Ui => { // If optimize-tests is true we still only want to optimize tests that actually get // executed and that don't specify their own optimization levels. - // Note: aux libs don't have a pass-mode, so they won't get optimized + // Note: aux libs don't have a pass/fail mode, so they won't get optimized // unless compile-flags are set in the aux file. - if self.props.pass_mode(&self.config) == Some(PassMode::Run) + // FIXME(Zalathar): We could also optimize run-fail/run-crash tests, + // but it's unclear whether that would be helpful or a waste of time. + if self.effective_pass_fail_mode() == Some(PassFailMode::RunPass) && !self .props .compile_flags diff --git a/src/tools/compiletest/src/runtest/ui.rs b/src/tools/compiletest/src/runtest/ui.rs index 61fb2643c8b4e..45ba73a75eeec 100644 --- a/src/tools/compiletest/src/runtest/ui.rs +++ b/src/tools/compiletest/src/runtest/ui.rs @@ -5,29 +5,29 @@ use std::io::Write; use rustfix::{Filter, apply_suggestions, get_suggestions_from_json}; use tracing::debug; +use crate::common::PassFailMode; use crate::json; use crate::runtest::{ - AllowUnused, Emit, FailMode, LinkToAux, PassMode, ProcRes, RunFailMode, RunResult, - TargetLocation, TestCx, TestOutput, Truncated, UI_FIXED, WillExecute, + AllowUnused, Emit, LinkToAux, ProcRes, RunResult, TargetLocation, TestCx, TestOutput, + Truncated, UI_FIXED, WillExecute, }; impl TestCx<'_> { pub(super) fn run_ui_test(&self) { - if let Some(FailMode::Build) = self.props.fail_mode() { + let pass_fail = + self.effective_pass_fail_mode().expect("UI tests always have a pass/fail mode"); + + if pass_fail == PassFailMode::BuildFail { // Make sure a build-fail test cannot fail due to failing analysis (e.g. typeck). let proc_res = self.compile_test(WillExecute::No, Emit::Metadata); - self.check_if_test_should_compile( - self.props.fail_mode(), - Some(PassMode::Check), - &proc_res, - ); + self.check_if_test_should_compile(PassFailMode::CheckPass, &proc_res); } - let pm = self.pass_mode(); - let should_run = self.should_run(pm); - let emit_metadata = self.should_emit_metadata(pm); - let proc_res = self.compile_test(should_run, emit_metadata); - self.check_if_test_should_compile(self.props.fail_mode(), pm, &proc_res); + let will_execute = if pass_fail.is_run() { self.run_if_enabled() } else { WillExecute::No }; + let emit_metadata = if pass_fail.is_check() { Emit::Metadata } else { Emit::None }; + let proc_res = self.compile_test(will_execute, emit_metadata); + self.check_if_test_should_compile(pass_fail, &proc_res); + if matches!(proc_res.truncated, Truncated::Yes) && !self.props.dont_check_compiler_stdout && !self.props.dont_check_compiler_stderr @@ -136,7 +136,7 @@ impl TestCx<'_> { // If the test is executed, capture its ProcRes separately so that // pattern/forbid checks can report the *runtime* stdout/stderr when they fail. let mut run_proc_res: Option = None; - let output_to_check = if let WillExecute::Yes = should_run { + let output_to_check = if will_execute == WillExecute::Yes { let proc_res = self.exec_compiled_test(); let run_output_errors = if self.props.check_run_results { self.load_compare_outputs(&proc_res, TestOutput::Run, explicit) @@ -160,14 +160,14 @@ impl TestCx<'_> { // Help users understand why the test failed by including the actual // exit code and actual run result in the failure message. let pass_hint = format!("code={code:?} so test would pass with `{run_result}`"); - if self.should_run_successfully(pm) { + if pass_fail == PassFailMode::RunPass { if run_result != RunResult::Pass { self.fatal_proc_rec( &format!("test did not exit with success! {pass_hint}"), &proc_res, ); } - } else if self.props.fail_mode() == Some(FailMode::Run(RunFailMode::Fail)) { + } else if pass_fail == PassFailMode::RunFail { // If the test is marked as `run-fail` but do not support // unwinding we allow it to crash, since a panic will trigger an // abort (crash) instead of unwind (exit with code 101). @@ -183,11 +183,11 @@ impl TestCx<'_> { }; self.fatal_proc_rec(&err, &proc_res); } - } else if self.props.fail_mode() == Some(FailMode::Run(RunFailMode::Crash)) { + } else if pass_fail == PassFailMode::RunCrash { if run_result != RunResult::Crash { self.fatal_proc_rec(&format!("test did not crash! {pass_hint}"), &proc_res); } - } else if self.props.fail_mode() == Some(FailMode::Run(RunFailMode::FailOrCrash)) { + } else if pass_fail == PassFailMode::RunFailOrCrash { if run_result != RunResult::Fail && run_result != RunResult::Crash { self.fatal_proc_rec( &format!("test did not exit with failure or crash! {pass_hint}"), diff --git a/src/tools/compiletest/src/util.rs b/src/tools/compiletest/src/util.rs index cded36b3698a5..2bfdd8889399d 100644 --- a/src/tools/compiletest/src/util.rs +++ b/src/tools/compiletest/src/util.rs @@ -99,6 +99,7 @@ macro_rules! string_enum { $(#[$meta:meta])* $vis:vis enum $name:ident { $( + $(#[$variant_meta:meta])* $variant:ident => $repr:expr, )* } @@ -106,6 +107,7 @@ macro_rules! string_enum { $(#[$meta])* $vis enum $name { $( + $(#[$variant_meta])* $variant, )* } From 529a4ecf335a35e55f433e94af6c808baa49ef44 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 6 May 2026 17:43:00 +1000 Subject: [PATCH 14/37] Use a `match` for checking UI test run results --- src/tools/compiletest/src/runtest/ui.rs | 80 +++++++++++++++---------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/src/tools/compiletest/src/runtest/ui.rs b/src/tools/compiletest/src/runtest/ui.rs index 45ba73a75eeec..f8a0c00083aec 100644 --- a/src/tools/compiletest/src/runtest/ui.rs +++ b/src/tools/compiletest/src/runtest/ui.rs @@ -157,45 +157,61 @@ impl TestCx<'_> { } else { RunResult::Crash }; + // Help users understand why the test failed by including the actual // exit code and actual run result in the failure message. let pass_hint = format!("code={code:?} so test would pass with `{run_result}`"); - if pass_fail == PassFailMode::RunPass { - if run_result != RunResult::Pass { - self.fatal_proc_rec( - &format!("test did not exit with success! {pass_hint}"), - &proc_res, - ); + match pass_fail { + PassFailMode::CheckFail + | PassFailMode::CheckPass + | PassFailMode::BuildFail + | PassFailMode::BuildPass => { + unreachable!("test program should not have run in mode {pass_fail:?}") } - } else if pass_fail == PassFailMode::RunFail { - // If the test is marked as `run-fail` but do not support - // unwinding we allow it to crash, since a panic will trigger an - // abort (crash) instead of unwind (exit with code 101). - let crash_ok = !self.config.can_unwind(); - if run_result != RunResult::Fail && !(crash_ok && run_result == RunResult::Crash) { - let err = if crash_ok { - format!( - "test did not exit with failure or crash (`{}` can't unwind)! {pass_hint}", - self.config.target - ) - } else { - format!("test did not exit with failure! {pass_hint}") - }; - self.fatal_proc_rec(&err, &proc_res); + + PassFailMode::RunPass => { + if run_result != RunResult::Pass { + self.fatal_proc_rec( + &format!("test did not exit with success! {pass_hint}"), + &proc_res, + ); + } } - } else if pass_fail == PassFailMode::RunCrash { - if run_result != RunResult::Crash { - self.fatal_proc_rec(&format!("test did not crash! {pass_hint}"), &proc_res); + + PassFailMode::RunFail => { + // If the test is marked as `run-fail` but do not support + // unwinding we allow it to crash, since a panic will trigger an + // abort (crash) instead of unwind (exit with code 101). + let crash_ok = !self.config.can_unwind(); + if run_result != RunResult::Fail + && !(crash_ok && run_result == RunResult::Crash) + { + let err = if crash_ok { + format!( + "test did not exit with failure or crash (`{}` can't unwind)! {pass_hint}", + self.config.target + ) + } else { + format!("test did not exit with failure! {pass_hint}") + }; + self.fatal_proc_rec(&err, &proc_res); + } } - } else if pass_fail == PassFailMode::RunFailOrCrash { - if run_result != RunResult::Fail && run_result != RunResult::Crash { - self.fatal_proc_rec( - &format!("test did not exit with failure or crash! {pass_hint}"), - &proc_res, - ); + + PassFailMode::RunCrash => { + if run_result != RunResult::Crash { + self.fatal_proc_rec(&format!("test did not crash! {pass_hint}"), &proc_res); + } + } + + PassFailMode::RunFailOrCrash => { + if run_result != RunResult::Fail && run_result != RunResult::Crash { + self.fatal_proc_rec( + &format!("test did not exit with failure or crash! {pass_hint}"), + &proc_res, + ); + } } - } else { - unreachable!("run_ui_test() must not be called if the test should not run"); } let output = self.get_output(&proc_res); From 81d92de34a9022b4051a9a04cd62301c444fa4bb Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Tue, 28 Apr 2026 02:21:39 +0200 Subject: [PATCH 15/37] Add `getaddrinfo` and `freeaddrinfo` shims Using those shims socket addresses can be created directly from hostnames (e.g. using `ToSocketAddrs` from the standard library) --- src/tools/miri/src/concurrency/data_race.rs | 3 +- src/tools/miri/src/diagnostics.rs | 12 +- src/tools/miri/src/machine.rs | 9 +- .../miri/src/shims/unix/foreign_items.rs | 19 + src/tools/miri/src/shims/unix/mod.rs | 2 + src/tools/miri/src/shims/unix/socket.rs | 298 +---------- .../miri/src/shims/unix/socket_address.rs | 499 ++++++++++++++++++ .../src/shims/unix/solarish/foreign_items.rs | 11 +- .../pass-dep/libc/libc-socket-address.rs | 88 +++ .../pass-dep/libc/libc-socket-invalid-addr.rs | 31 ++ .../libc/libc-socket-invalid-addr.stderr | 8 + .../miri/tests/pass/shims/socket-address.rs | 33 ++ src/tools/miri/tests/pass/shims/socket.rs | 2 +- 13 files changed, 721 insertions(+), 294 deletions(-) create mode 100644 src/tools/miri/src/shims/unix/socket_address.rs create mode 100644 src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs create mode 100644 src/tools/miri/tests/pass-dep/libc/libc-socket-invalid-addr.rs create mode 100644 src/tools/miri/tests/pass-dep/libc/libc-socket-invalid-addr.stderr create mode 100644 src/tools/miri/tests/pass/shims/socket-address.rs diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs index 76f735d457cf5..06a1510feabfd 100644 --- a/src/tools/miri/src/concurrency/data_race.rs +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -1029,7 +1029,8 @@ impl VClockAlloc { | MiriMemoryKind::C | MiriMemoryKind::WinHeap | MiriMemoryKind::WinLocal - | MiriMemoryKind::Mmap, + | MiriMemoryKind::Mmap + | MiriMemoryKind::SocketAddress, ) | MemoryKind::Stack => { let (alloc_index, clocks) = global.active_thread_state(thread_mgr); diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 8e7223a0372e5..25bbe36b3fe6d 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -155,6 +155,9 @@ pub enum NonHaltingDiagnostic { }, FileInProcOpened, ConnectingSocketGetsockname, + SocketAddressResolution { + error: std::io::Error, + }, } /// Level of Miri specific diagnostics @@ -663,6 +666,8 @@ impl<'tcx> MiriMachine<'tcx> { FileInProcOpened => ("open a file in `/proc`".to_string(), DiagLevel::Warning), ConnectingSocketGetsockname => ("Called `getsockname` on connecting socket".to_string(), DiagLevel::Warning), + SocketAddressResolution { .. } => + ("error during address resolution".to_string(), DiagLevel::Warning), }; let title = match &e { @@ -711,7 +716,8 @@ impl<'tcx> MiriMachine<'tcx> { format!("GenMC currently does not model the failure ordering for `compare_exchange`. {was_upgraded_msg}. Miri with GenMC might miss bugs related to this memory access.") } FileInProcOpened => format!("files in `/proc` can bypass the Abstract Machine and might not work properly in Miri"), - ConnectingSocketGetsockname => format!("connecting sockets return unspecified socket addresses on Windows hosts") + ConnectingSocketGetsockname => format!("connecting sockets return unspecified socket addresses on Windows hosts"), + SocketAddressResolution { error } => format!("address resolution failed: {error}"), }; let notes = match &e { @@ -727,6 +733,10 @@ impl<'tcx> MiriMachine<'tcx> { "an unspecified socket address (e.g. `0.0.0.0:0`) will be returned instead" ), ], + SocketAddressResolution { .. } => + vec![note!( + "Miri cannot return proper error information from this call; only a generic error code is being returned" + )], _ => vec![], }; diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 6c4c3fa141848..21f505719270b 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -202,8 +202,10 @@ pub enum MiriMemoryKind { /// Memory for thread-local statics. /// This memory may leak. Tls, - /// Memory mapped directly by the program + /// Memory mapped directly by the program. Mmap, + /// Memory allocated for `getaddrinfo` result. + SocketAddress, } impl From for MemoryKind { @@ -219,7 +221,7 @@ impl MayLeak for MiriMemoryKind { use self::MiriMemoryKind::*; match self { Rust | Miri | C | WinHeap | WinLocal | Runtime => false, - Machine | Global | ExternStatic | Tls | Mmap => true, + Machine | Global | ExternStatic | Tls | Mmap | SocketAddress => true, } } } @@ -232,7 +234,7 @@ impl MiriMemoryKind { // Heap allocations are fine since the `Allocation` is created immediately. Rust | Miri | C | WinHeap | WinLocal | Mmap => true, // Everything else is unclear, let's not show potentially confusing spans. - Machine | Global | ExternStatic | Tls | Runtime => false, + Machine | Global | ExternStatic | Tls | Runtime | SocketAddress => false, } } } @@ -252,6 +254,7 @@ impl fmt::Display for MiriMemoryKind { ExternStatic => write!(f, "extern static"), Tls => write!(f, "thread-local static"), Mmap => write!(f, "mmap"), + SocketAddress => write!(f, "socket address"), } } } diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index dc2ce5da28288..3a47c9552788d 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -690,6 +690,25 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.shutdown(sockfd, how)?; this.write_scalar(result, dest)?; } + "getaddrinfo" => { + let [node, service, hints, res] = this.check_shim_sig( + shim_sig!(extern "C" fn(*const _, *const _, *const _, *mut _) -> i32), + link_name, + abi, + args, + )?; + let result = this.getaddrinfo(node, service, hints, res)?; + this.write_scalar(result, dest)?; + } + "freeaddrinfo" => { + let [res] = this.check_shim_sig( + shim_sig!(extern "C" fn(*mut _) -> ()), + link_name, + abi, + args, + )?; + this.freeaddrinfo(res)?; + } // Time "gettimeofday" => { diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs index 730c46eb2702c..cae31d47c1b5a 100644 --- a/src/tools/miri/src/shims/unix/mod.rs +++ b/src/tools/miri/src/shims/unix/mod.rs @@ -5,6 +5,7 @@ mod fd; mod fs; mod mem; mod socket; +mod socket_address; mod sync; mod thread; mod virtual_socket; @@ -25,6 +26,7 @@ pub use self::linux_like::epoll::{ }; pub use self::mem::EvalContextExt as _; pub use self::socket::EvalContextExt as _; +pub use self::socket_address::EvalContextExt as _; pub use self::sync::EvalContextExt as _; pub use self::thread::{EvalContextExt as _, ThreadNameResult}; pub use self::virtual_socket::EvalContextExt as _; diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index 213a9d1f260cf..ca0ddfd4726ae 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -1,13 +1,12 @@ use std::cell::{Cell, RefCell, RefMut}; +use std::io; use std::io::Read; -use std::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6}; +use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4}; use std::sync::atomic::AtomicBool; use std::time::Duration; -use std::{io, iter}; use mio::event::Source; use mio::net::{TcpListener, TcpStream}; -use rustc_abi::Size; use rustc_const_eval::interpret::{InterpResult, interp_ok}; use rustc_middle::throw_unsup_format; use rustc_target::spec::Os; @@ -15,6 +14,7 @@ use rustc_target::spec::Os; use crate::shims::files::{EvalContextExt as _, FdId, FileDescription, FileDescriptionRef}; use crate::shims::unix::UnixFileDescription; use crate::shims::unix::linux_like::epoll::{EpollReadiness, EvalContextExt as _}; +use crate::shims::unix::socket_address::EvalContextExt as _; use crate::*; #[derive(Debug, PartialEq)] @@ -354,7 +354,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); let socket = this.read_scalar(socket)?.to_i32()?; - let address = match this.socket_address(address, address_len, "bind")? { + let address = match this.read_socket_address(address, address_len, "bind")? { Ok(addr) => addr, Err(e) => return this.set_last_error_and_return_i32(e), }; @@ -570,7 +570,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); let socket = this.read_scalar(socket)?.to_i32()?; - let address = match this.socket_address(address, address_len, "connect")? { + let address = match this.read_socket_address(address, address_len, "connect")? { Ok(address) => address, Err(e) => return this.set_last_error_and_return(e, dest), }; @@ -1018,10 +1018,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { SocketState::Initial => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)), }; - match this.write_socket_address(&address, address_ptr, address_len_ptr, "getsockname")? { - Ok(_) => interp_ok(Scalar::from_i32(0)), - Err(e) => this.set_last_error_and_return_i32(e), - } + this.write_socket_address(&address, address_ptr, address_len_ptr, "getsockname") + .map(|_| Scalar::from_i32(0)) } fn getpeername( @@ -1078,15 +1076,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Err(e) => return this.set_last_error_and_return(e, &dest), }; - match this.write_socket_address( + this.write_socket_address( &address, address_ptr, address_len_ptr, "getpeername", - )? { - Ok(_) => this.write_scalar(Scalar::from_i32(0), &dest), - Err(e) => this.set_last_error_and_return(e, &dest), - } + )?; + this.write_scalar(Scalar::from_i32(0), &dest) } ), ) @@ -1137,274 +1133,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {} trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { - /// Attempt to turn an address and length operand into a standard library socket address. - /// - /// Returns an IO error should the address length not match the address family length. - fn socket_address( - &self, - address: &OpTy<'tcx>, - address_len: &OpTy<'tcx>, - foreign_name: &'static str, - ) -> InterpResult<'tcx, Result> { - let this = self.eval_context_ref(); - - let socklen_layout = this.libc_ty_layout("socklen_t"); - // We only support address lengths which can be stored in a u64 since the - // size of a layout is also stored in a u64. - let address_len: u64 = - this.read_scalar(address_len)?.to_int(socklen_layout.size)?.try_into().unwrap(); - - // Initially, treat address as generic sockaddr just to extract the family field. - let sockaddr_layout = this.libc_ty_layout("sockaddr"); - if address_len < sockaddr_layout.size.bytes() { - // Address length should be at least as big as the generic sockaddr - return interp_ok(Err(LibcError("EINVAL"))); - } - let address = this.deref_pointer_as(address, sockaddr_layout)?; - - let family_field = this.project_field_named(&address, "sa_family")?; - let family_layout = this.libc_ty_layout("sa_family_t"); - let family = this.read_scalar(&family_field)?.to_int(family_layout.size)?; - - // Depending on the family, decide whether it's IPv4 or IPv6 and use specialized layout - // to extract address and port. - let socket_addr = if family == this.eval_libc_i32("AF_INET").into() { - let sockaddr_in_layout = this.libc_ty_layout("sockaddr_in"); - if address_len != sockaddr_in_layout.size.bytes() { - // Address length should be exactly the length of an IPv4 address. - return interp_ok(Err(LibcError("EINVAL"))); - } - let address = address.transmute(sockaddr_in_layout, this)?; - - let port_field = this.project_field_named(&address, "sin_port")?; - // Read bytes and treat them as big endian since port is stored in network byte order. - let port_bytes: [u8; 2] = this - .read_bytes_ptr_strip_provenance(port_field.ptr(), Size::from_bytes(2))? - .try_into() - .unwrap(); - let port = u16::from_be_bytes(port_bytes); - - let addr_field = this.project_field_named(&address, "sin_addr")?; - let s_addr_field = this.project_field_named(&addr_field, "s_addr")?; - // Read bytes and treat them as big endian since address is stored in network byte order. - let addr_bytes: [u8; 4] = this - .read_bytes_ptr_strip_provenance(s_addr_field.ptr(), Size::from_bytes(4))? - .try_into() - .unwrap(); - let addr_bits = u32::from_be_bytes(addr_bytes); - - SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from_bits(addr_bits), port)) - } else if family == this.eval_libc_i32("AF_INET6").into() { - let sockaddr_in6_layout = this.libc_ty_layout("sockaddr_in6"); - if address_len != sockaddr_in6_layout.size.bytes() { - // Address length should be exactly the length of an IPv6 address. - return interp_ok(Err(LibcError("EINVAL"))); - } - // We cannot transmute since the `sockaddr_in6` layout is bigger than the `sockaddr` layout. - let address = address.offset(Size::ZERO, sockaddr_in6_layout, this)?; - - let port_field = this.project_field_named(&address, "sin6_port")?; - // Read bytes and treat them as big endian since port is stored in network byte order. - let port_bytes: [u8; 2] = this - .read_bytes_ptr_strip_provenance(port_field.ptr(), Size::from_bytes(2))? - .try_into() - .unwrap(); - let port = u16::from_be_bytes(port_bytes); - - let addr_field = this.project_field_named(&address, "sin6_addr")?; - let s_addr_field = this - .project_field_named(&addr_field, "s6_addr")? - .transmute(this.machine.layouts.u128, this)?; - // Read bytes and treat them as big endian since address is stored in network byte order. - let addr_bytes: [u8; 16] = this - .read_bytes_ptr_strip_provenance(s_addr_field.ptr(), Size::from_bytes(16))? - .try_into() - .unwrap(); - let addr_bits = u128::from_be_bytes(addr_bytes); - - let flowinfo_field = this.project_field_named(&address, "sin6_flowinfo")?; - // flowinfo doesn't get the big endian treatment as this field is stored in native byte order - // and not in network byte order. - let flowinfo = this.read_scalar(&flowinfo_field)?.to_u32()?; - - let scope_id_field = this.project_field_named(&address, "sin6_scope_id")?; - // scope_id doesn't get the big endian treatment as this field is stored in native byte order - // and not in network byte order. - let scope_id = this.read_scalar(&scope_id_field)?.to_u32()?; - - SocketAddr::V6(SocketAddrV6::new( - Ipv6Addr::from_bits(addr_bits), - port, - flowinfo, - scope_id, - )) - } else { - // Socket of other types shouldn't be created in a first place and - // thus also no address family of another type should be supported. - throw_unsup_format!( - "{foreign_name}: address family {family:#x} is unsupported, \ - only AF_INET and AF_INET6 are allowed" - ); - }; - - interp_ok(Ok(socket_addr)) - } - - /// Attempt to write a standard library socket address into a pointer. - /// - /// The `address_len_ptr` parameter serves both as input and output parameter. - /// On input, it points to the size of the buffer `address_ptr` points to, and - /// on output it points to the non-truncated size of the written address in the - /// buffer pointed to by `address_ptr`. - /// - /// If the address buffer doesn't fit the whole address, the address is truncated to not - /// overflow the buffer. - fn write_socket_address( - &mut self, - address: &SocketAddr, - address_ptr: Pointer, - address_len_ptr: Pointer, - foreign_name: &'static str, - ) -> InterpResult<'tcx, Result<(), IoError>> { - let this = self.eval_context_mut(); - - if address_ptr == Pointer::null() || address_len_ptr == Pointer::null() { - // The POSIX man page doesn't account for the cases where the `address_ptr` or - // `address_len_ptr` could be null pointers. Thus, this behavior is undefined! - throw_ub_format!( - "{foreign_name}: writing a socket address but the address or the length pointer is a null pointer" - ) - } - - let socklen_layout = this.libc_ty_layout("socklen_t"); - let address_buffer_len_place = this.ptr_to_mplace(address_len_ptr, socklen_layout); - // We only support buffer lengths which can be stored in a u64 since the - // size of a layout in bytes is also stored in a u64. - let address_buffer_len: u64 = this - .read_scalar(&address_buffer_len_place)? - .to_int(socklen_layout.size)? - .try_into() - .unwrap(); - - let (address_buffer, address_layout) = match address { - SocketAddr::V4(address) => { - // IPv4 address bytes; already stored in network byte order. - let address_bytes = address.ip().octets(); - // Port needs to be manually turned into network byte order. - let port = address.port().to_be(); - - let sockaddr_in_layout = this.libc_ty_layout("sockaddr_in"); - // Allocate new buffer on the stack with the `sockaddr_in` layout. - // We need a temporary buffer as `address_ptr` might not point to a large enough - // buffer, in which case we have to truncate. - let address_buffer = this.allocate(sockaddr_in_layout, MemoryKind::Stack)?; - // Zero the whole buffer as some libc targets have additional fields which we fill - // with zero bytes (just like the standard library does it). - this.write_bytes_ptr( - address_buffer.ptr(), - iter::repeat_n(0, address_buffer.layout.size.bytes_usize()), - )?; - - let sin_family_field = this.project_field_named(&address_buffer, "sin_family")?; - // We cannot simply write the `AF_INET` scalar into the `sin_family_field` because on most - // systems the field has a layout of 16-bit whilst the scalar has a size of 32-bit. - // Since the `AF_INET` constant is chosen such that it can safely be converted into - // a 16-bit integer, we use the following logic to get a scalar of the right size. - let af_inet = this.eval_libc("AF_INET"); - let address_family = - Scalar::from_int(af_inet.to_int(af_inet.size())?, sin_family_field.layout.size); - this.write_scalar(address_family, &sin_family_field)?; - - let sin_port_field = this.project_field_named(&address_buffer, "sin_port")?; - // Write the port in target native endianness bytes as we already converted it - // to big endian above. - this.write_bytes_ptr(sin_port_field.ptr(), port.to_ne_bytes())?; - - let sin_addr_field = this.project_field_named(&address_buffer, "sin_addr")?; - let s_addr_field = this.project_field_named(&sin_addr_field, "s_addr")?; - this.write_bytes_ptr(s_addr_field.ptr(), address_bytes)?; - - (address_buffer, sockaddr_in_layout) - } - SocketAddr::V6(address) => { - // IPv6 address bytes; already stored in network byte order. - let address_bytes = address.ip().octets(); - // Port needs to be manually turned into network byte order. - let port = address.port().to_be(); - // Flowinfo is stored in native byte order. - let flowinfo = address.flowinfo(); - // Scope id is stored in native byte order. - let scope_id = address.scope_id(); - - let sockaddr_in6_layout = this.libc_ty_layout("sockaddr_in6"); - // Allocate new buffer on the stack with the `sockaddr_in6` layout. - // We need a temporary buffer as `address_ptr` might not point to a large enough - // buffer, in which case we have to truncate. - let address_buffer = this.allocate(sockaddr_in6_layout, MemoryKind::Stack)?; - // Zero the whole buffer as some libc targets have additional fields which we fill - // with zero bytes (just like the standard library does it). - this.write_bytes_ptr( - address_buffer.ptr(), - iter::repeat_n(0, address_buffer.layout.size.bytes_usize()), - )?; - - let sin6_family_field = this.project_field_named(&address_buffer, "sin6_family")?; - // We cannot simply write the `AF_INET6` scalar into the `sin6_family_field` because on most - // systems the field has a layout of 16-bit whilst the scalar has a size of 32-bit. - // Since the `AF_INET6` constant is chosen such that it can safely be converted into - // a 16-bit integer, we use the following logic to get a scalar of the right size. - let af_inet6 = this.eval_libc("AF_INET6"); - let address_family = Scalar::from_int( - af_inet6.to_int(af_inet6.size())?, - sin6_family_field.layout.size, - ); - this.write_scalar(address_family, &sin6_family_field)?; - - let sin6_port_field = this.project_field_named(&address_buffer, "sin6_port")?; - // Write the port in target native endianness bytes as we already converted it - // to big endian above. - this.write_bytes_ptr(sin6_port_field.ptr(), port.to_ne_bytes())?; - - let sin6_flowinfo_field = - this.project_field_named(&address_buffer, "sin6_flowinfo")?; - this.write_scalar(Scalar::from_u32(flowinfo), &sin6_flowinfo_field)?; - - let sin6_scope_id_field = - this.project_field_named(&address_buffer, "sin6_scope_id")?; - this.write_scalar(Scalar::from_u32(scope_id), &sin6_scope_id_field)?; - - let sin6_addr_field = this.project_field_named(&address_buffer, "sin6_addr")?; - let s6_addr_field = this.project_field_named(&sin6_addr_field, "s6_addr")?; - this.write_bytes_ptr(s6_addr_field.ptr(), address_bytes)?; - - (address_buffer, sockaddr_in6_layout) - } - }; - - // Copy the truncated address into the pointer pointed to by `address_ptr`. - this.mem_copy( - address_buffer.ptr(), - address_ptr, - // Truncate the address to fit the provided buffer. - address_layout.size.min(Size::from_bytes(address_buffer_len)), - // The buffers are guaranteed to not overlap since the `address_buffer` - // was just newly allocated on the stack. - true, - )?; - // Deallocate the address buffer as it was only needed to construct the address and - // copy it into the buffer pointed to by `address_ptr`. - this.deallocate_ptr(address_buffer.ptr(), None, MemoryKind::Stack)?; - // Size of the non-truncated address. - let address_len = address_layout.size.bytes(); - - this.write_scalar( - Scalar::from_uint(address_len, socklen_layout.size), - &address_buffer_len_place, - )?; - - interp_ok(Ok(())) - } - /// Block the thread until there's an incoming connection or an error occurred. /// /// This recursively calls itself should the operation still block for some reason. @@ -1496,11 +1224,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // We only attempt a write if the address pointer is not a null pointer. // If the address pointer is a null pointer the user isn't interested in the // address and we don't need to write anything. - if let Err(e) = - this.write_socket_address(&addr, address_ptr, address_len_ptr, "accept4")? - { - return interp_ok(Err(e)); - }; + this.write_socket_address(&addr, address_ptr, address_len_ptr, "accept4")?; } let fd = this.machine.fds.new_ref(Socket { diff --git a/src/tools/miri/src/shims/unix/socket_address.rs b/src/tools/miri/src/shims/unix/socket_address.rs new file mode 100644 index 0000000000000..c0f7e8e1720f8 --- /dev/null +++ b/src/tools/miri/src/shims/unix/socket_address.rs @@ -0,0 +1,499 @@ +use std::iter; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; + +use rustc_abi::Size; +use rustc_target::spec::Env; + +use crate::*; + +impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} +pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + fn getaddrinfo( + &mut self, + node: &OpTy<'tcx>, + service: &OpTy<'tcx>, + hints: &OpTy<'tcx>, + res: &OpTy<'tcx>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let node_ptr = this.read_pointer(node)?; + let service_ptr = this.read_pointer(service)?; + let hints_ptr = this.read_pointer(hints)?; + let res_mplace = this.deref_pointer(res)?; + + if node_ptr == Pointer::null() { + // We cannot get an address without the `node` part because + // the [`ToSocketAddrs`] trait requires an address. + throw_unsup_format!( + "getaddrinfo: getting the address info without a `node` is unsupported" + ); + } + + let mut port = 0; + if service_ptr != Pointer::null() { + // The C-string at `service_ptr` is either a port number or a name of a + // well-known service. + let service_c_str = this.read_c_str(service_ptr)?; + + // Try to parse `service_c_str` as a number -- the only case we support. + match str::from_utf8(service_c_str).ok().and_then(|s| s.parse::().ok()) { + Some(service_port) => port = service_port, + None => { + // The string is not a valid port number; this is unsupported + // because the standard library's [`ToSocketAddrs`] only supports + // numeric ports. + throw_unsup_format!( + "getaddrinfo: non-numeric `service` arguments aren't supported" + ) + } + } + } + + let node_c_str = this.read_c_str(node_ptr)?; + let Some(node_str) = str::from_utf8(node_c_str).ok() else { + throw_unsup_format!("getaddrinfo: node is not a valid UTF-8 string") + }; + + if hints_ptr == Pointer::null() { + // The standard library only supports getting TCP address information. The + // empty hints pointer would allow any socket type so we cannot support it. + throw_unsup_format!( + "getaddrinfo: getting address info without providing socket type hint is unsupported" + ) + } + + let hints_layout = this.libc_ty_layout("addrinfo"); + let hints_mplace = this.ptr_to_mplace(hints_ptr, hints_layout); + + let family_field = this.project_field_named(&hints_mplace, "ai_family")?; + let family = this.read_scalar(&family_field)?; + if family != Scalar::from_i32(0) { + // We cannot provide a family hint to the standard library implementation. + throw_unsup_format!("getaddrinfo: family hints are not supported") + } + + let socktype_field = this.project_field_named(&hints_mplace, "ai_socktype")?; + let socktype = this.read_scalar(&socktype_field)?; + if socktype != this.eval_libc("SOCK_STREAM") { + // The standard library only supports getting TCP address information. + throw_unsup_format!( + "getaddrinfo: only queries with socket type SOCK_STREAM are supported" + ) + } + + let protocol_field = this.project_field_named(&hints_mplace, "ai_protocol")?; + let protocol = this.read_scalar(&protocol_field)?; + if protocol != Scalar::from_i32(0) { + // We cannot provide a protocol hint to the standard library implementation. + throw_unsup_format!("getaddrinfo: protocol hints are not supported") + } + + let flags_field = this.project_field_named(&hints_mplace, "ai_flags")?; + let flags = this.read_scalar(&flags_field)?; + if flags != Scalar::from_i32(0) { + // We cannot provide any flag hints to the standard library implementation. + throw_unsup_format!("getaddrinfo: flag hints are not supported") + } + + let socket_addrs = match (node_str, port).to_socket_addrs() { + Ok(addrs) => addrs, + Err(e) => { + // `getaddrinfo` returns negative integer values when there was an error during socket + // address resolution. Because the standard library doesn't expose those integer values + // directly, we just return a generic protocol error. + // The actual error is emitted as part of a warning diagnostic. + this.emit_diagnostic(NonHaltingDiagnostic::SocketAddressResolution { error: e }); + this.set_last_error(LibcError("EPROTO"))?; + return interp_ok(this.eval_libc("EAI_SYSTEM")); + } + }; + + let res_ptr = this.allocate_address_infos(socket_addrs)?; + + this.write_pointer(res_ptr, &res_mplace)?; + interp_ok(Scalar::from_i32(0)) + } + + fn freeaddrinfo(&mut self, res: &OpTy<'tcx>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let res_ptr = this.read_pointer(res)?; + + this.free_address_infos(res_ptr) + } + + /// Attempt to turn an address and length operand into a standard library socket address. + /// + /// Returns an IO error should the address length not match the address family length. + fn read_socket_address( + &self, + address: &OpTy<'tcx>, + address_len: &OpTy<'tcx>, + foreign_name: &'static str, + ) -> InterpResult<'tcx, Result> { + let this = self.eval_context_ref(); + + let socklen_layout = this.libc_ty_layout("socklen_t"); + // We only support address lengths which can be stored in a u64 since the + // size of a layout is also stored in a u64. + let address_len: u64 = + this.read_scalar(address_len)?.to_int(socklen_layout.size)?.try_into().unwrap(); + + // Initially, treat address as generic sockaddr just to extract the family field. + let sockaddr_layout = this.libc_ty_layout("sockaddr"); + if address_len < sockaddr_layout.size.bytes() { + // Address length should be at least as big as the generic sockaddr + return interp_ok(Err(LibcError("EINVAL"))); + } + let address = this.deref_pointer_as(address, sockaddr_layout)?; + + let family_field = this.project_field_named(&address, "sa_family")?; + let family_layout = this.libc_ty_layout("sa_family_t"); + let family = this.read_scalar(&family_field)?.to_int(family_layout.size)?; + + // Depending on the family, decide whether it's IPv4 or IPv6 and use specialized layout + // to extract address and port. + let socket_addr = if family == this.eval_libc_i32("AF_INET").into() { + let sockaddr_in_layout = this.libc_ty_layout("sockaddr_in"); + if address_len != sockaddr_in_layout.size.bytes() { + // Address length should be exactly the length of an IPv4 address. + return interp_ok(Err(LibcError("EINVAL"))); + } + let address = address.transmute(sockaddr_in_layout, this)?; + + let port_field = this.project_field_named(&address, "sin_port")?; + // Read bytes and treat them as big endian since port is stored in network byte order. + let port_bytes: [u8; 2] = this + .read_bytes_ptr_strip_provenance(port_field.ptr(), Size::from_bytes(2))? + .try_into() + .unwrap(); + let port = u16::from_be_bytes(port_bytes); + + let addr_field = this.project_field_named(&address, "sin_addr")?; + let s_addr_field = this.project_field_named(&addr_field, "s_addr")?; + // Read bytes and treat them as big endian since address is stored in network byte order. + let addr_bytes: [u8; 4] = this + .read_bytes_ptr_strip_provenance(s_addr_field.ptr(), Size::from_bytes(4))? + .try_into() + .unwrap(); + let addr_bits = u32::from_be_bytes(addr_bytes); + + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from_bits(addr_bits), port)) + } else if family == this.eval_libc_i32("AF_INET6").into() { + let sockaddr_in6_layout = this.libc_ty_layout("sockaddr_in6"); + if address_len != sockaddr_in6_layout.size.bytes() { + // Address length should be exactly the length of an IPv6 address. + return interp_ok(Err(LibcError("EINVAL"))); + } + // We cannot transmute since the `sockaddr_in6` layout is bigger than the `sockaddr` layout. + let address = address.offset(Size::ZERO, sockaddr_in6_layout, this)?; + + let port_field = this.project_field_named(&address, "sin6_port")?; + // Read bytes and treat them as big endian since port is stored in network byte order. + let port_bytes: [u8; 2] = this + .read_bytes_ptr_strip_provenance(port_field.ptr(), Size::from_bytes(2))? + .try_into() + .unwrap(); + let port = u16::from_be_bytes(port_bytes); + + let addr_field = this.project_field_named(&address, "sin6_addr")?; + let s_addr_field = this + .project_field_named(&addr_field, "s6_addr")? + .transmute(this.machine.layouts.u128, this)?; + // Read bytes and treat them as big endian since address is stored in network byte order. + let addr_bytes: [u8; 16] = this + .read_bytes_ptr_strip_provenance(s_addr_field.ptr(), Size::from_bytes(16))? + .try_into() + .unwrap(); + let addr_bits = u128::from_be_bytes(addr_bytes); + + let flowinfo_field = this.project_field_named(&address, "sin6_flowinfo")?; + // flowinfo doesn't get the big endian treatment as this field is stored in native byte order + // and not in network byte order. + let flowinfo = this.read_scalar(&flowinfo_field)?.to_u32()?; + + let scope_id_field = this.project_field_named(&address, "sin6_scope_id")?; + // scope_id doesn't get the big endian treatment as this field is stored in native byte order + // and not in network byte order. + let scope_id = this.read_scalar(&scope_id_field)?.to_u32()?; + + SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::from_bits(addr_bits), + port, + flowinfo, + scope_id, + )) + } else { + // Socket of other types shouldn't be created in a first place and + // thus also no address family of another type should be supported. + throw_unsup_format!( + "{foreign_name}: address family {family:#x} is unsupported, \ + only AF_INET and AF_INET6 are allowed" + ); + }; + + interp_ok(Ok(socket_addr)) + } + + /// Attempt to write a standard library socket address into a pointer. + /// + /// The `address_len_ptr` parameter serves both as input and output parameter. + /// On input, it points to the size of the buffer `address_ptr` points to, and + /// on output it points to the non-truncated size of the written address in the + /// buffer pointed to by `address_ptr`. + /// + /// If the address buffer doesn't fit the whole address, the address is truncated to not + /// overflow the buffer. + fn write_socket_address( + &mut self, + address: &SocketAddr, + address_ptr: Pointer, + address_len_ptr: Pointer, + foreign_name: &'static str, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + if address_ptr == Pointer::null() || address_len_ptr == Pointer::null() { + // The POSIX man page doesn't account for the cases where the `address_ptr` or + // `address_len_ptr` could be null pointers. Thus, this behavior is undefined! + throw_ub_format!( + "{foreign_name}: writing a socket address but the address or the length pointer is a null pointer" + ) + } + + let socklen_layout = this.libc_ty_layout("socklen_t"); + let address_buffer_len_place = this.ptr_to_mplace(address_len_ptr, socklen_layout); + // We only support buffer lengths which can be stored in a u64 since the + // size of a layout in bytes is also stored in a u64. + let address_buffer_len: u64 = this + .read_scalar(&address_buffer_len_place)? + .to_int(socklen_layout.size)? + .try_into() + .unwrap(); + + let (address_buffer, address_layout) = match address { + SocketAddr::V4(address) => { + // IPv4 address bytes; already stored in network byte order. + let address_bytes = address.ip().octets(); + // Port needs to be manually turned into network byte order. + let port = address.port().to_be(); + + let sockaddr_in_layout = this.libc_ty_layout("sockaddr_in"); + // Allocate new buffer on the stack with the `sockaddr_in` layout. + // We need a temporary buffer as `address_ptr` might not point to a large enough + // buffer, in which case we have to truncate. + let address_buffer = this.allocate(sockaddr_in_layout, MemoryKind::Stack)?; + // Zero the whole buffer as some libc targets have additional fields which we fill + // with zero bytes (just like the standard library does it). + this.write_bytes_ptr( + address_buffer.ptr(), + iter::repeat_n(0, address_buffer.layout.size.bytes_usize()), + )?; + + let sin_family_field = this.project_field_named(&address_buffer, "sin_family")?; + // We cannot simply write the `AF_INET` scalar into the `sin_family_field` because on most + // systems the field has a layout of 16-bit whilst the scalar has a size of 32-bit. + // Since the `AF_INET` constant is chosen such that it can safely be converted into + // a 16-bit integer, we use the following logic to get a scalar of the right size. + let af_inet = this.eval_libc("AF_INET"); + let address_family = + Scalar::from_int(af_inet.to_int(af_inet.size())?, sin_family_field.layout.size); + this.write_scalar(address_family, &sin_family_field)?; + + let sin_port_field = this.project_field_named(&address_buffer, "sin_port")?; + // Write the port in target native endianness bytes as we already converted it + // to big endian above. + this.write_bytes_ptr(sin_port_field.ptr(), port.to_ne_bytes())?; + + let sin_addr_field = this.project_field_named(&address_buffer, "sin_addr")?; + let s_addr_field = this.project_field_named(&sin_addr_field, "s_addr")?; + this.write_bytes_ptr(s_addr_field.ptr(), address_bytes)?; + + (address_buffer, sockaddr_in_layout) + } + SocketAddr::V6(address) => { + // IPv6 address bytes; already stored in network byte order. + let address_bytes = address.ip().octets(); + // Port needs to be manually turned into network byte order. + let port = address.port().to_be(); + // Flowinfo is stored in native byte order. + let flowinfo = address.flowinfo(); + // Scope id is stored in native byte order. + let scope_id = address.scope_id(); + + let sockaddr_in6_layout = this.libc_ty_layout("sockaddr_in6"); + // Allocate new buffer on the stack with the `sockaddr_in6` layout. + // We need a temporary buffer as `address_ptr` might not point to a large enough + // buffer, in which case we have to truncate. + let address_buffer = this.allocate(sockaddr_in6_layout, MemoryKind::Stack)?; + // Zero the whole buffer as some libc targets have additional fields which we fill + // with zero bytes (just like the standard library does it). + this.write_bytes_ptr( + address_buffer.ptr(), + iter::repeat_n(0, address_buffer.layout.size.bytes_usize()), + )?; + + let sin6_family_field = this.project_field_named(&address_buffer, "sin6_family")?; + // We cannot simply write the `AF_INET6` scalar into the `sin6_family_field` because on most + // systems the field has a layout of 16-bit whilst the scalar has a size of 32-bit. + // Since the `AF_INET6` constant is chosen such that it can safely be converted into + // a 16-bit integer, we use the following logic to get a scalar of the right size. + let af_inet6 = this.eval_libc("AF_INET6"); + let address_family = Scalar::from_int( + af_inet6.to_int(af_inet6.size())?, + sin6_family_field.layout.size, + ); + this.write_scalar(address_family, &sin6_family_field)?; + + let sin6_port_field = this.project_field_named(&address_buffer, "sin6_port")?; + // Write the port in target native endianness bytes as we already converted it + // to big endian above. + this.write_bytes_ptr(sin6_port_field.ptr(), port.to_ne_bytes())?; + + let sin6_flowinfo_field = + this.project_field_named(&address_buffer, "sin6_flowinfo")?; + this.write_scalar(Scalar::from_u32(flowinfo), &sin6_flowinfo_field)?; + + let sin6_scope_id_field = + this.project_field_named(&address_buffer, "sin6_scope_id")?; + this.write_scalar(Scalar::from_u32(scope_id), &sin6_scope_id_field)?; + + let sin6_addr_field = this.project_field_named(&address_buffer, "sin6_addr")?; + let s6_addr_field = this.project_field_named(&sin6_addr_field, "s6_addr")?; + this.write_bytes_ptr(s6_addr_field.ptr(), address_bytes)?; + + (address_buffer, sockaddr_in6_layout) + } + }; + + // Copy the truncated address into the pointer pointed to by `address_ptr`. + this.mem_copy( + address_buffer.ptr(), + address_ptr, + // Truncate the address to fit the provided buffer. + address_layout.size.min(Size::from_bytes(address_buffer_len)), + // The buffers are guaranteed to not overlap since the `address_buffer` + // was just newly allocated on the stack. + true, + )?; + // Deallocate the address buffer as it was only needed to construct the address and + // copy it into the buffer pointed to by `address_ptr`. + this.deallocate_ptr(address_buffer.ptr(), None, MemoryKind::Stack)?; + // Size of the non-truncated address. + let address_len = address_layout.size.bytes(); + + this.write_scalar( + Scalar::from_uint(address_len, socklen_layout.size), + &address_buffer_len_place, + )?; + + interp_ok(()) + } +} + +impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {} +trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + /// Allocate a linked list of address info structs from an iterator of [`SocketAddr`]s. + /// Returns a pointer pointing to the head of the linked list. + fn allocate_address_infos( + &mut self, + mut addresses: impl Iterator, + ) -> InterpResult<'tcx, Pointer> { + let this = self.eval_context_mut(); + + let Some(address) = addresses.next() else { + // Iterator is empty; we return a null pointer. + return interp_ok(Pointer::null()); + }; + + let addrinfo_layout = this.libc_ty_layout("addrinfo"); + + let addrinfo_mplace = + this.allocate(addrinfo_layout, MiriMemoryKind::SocketAddress.into())?; + + let flags_mplace = this.project_field_named(&addrinfo_mplace, "ai_flags")?; + // We don't support flag hints and depending on the target libc we have different default values: + // "According to POSIX.1, specifying hints as NULL should cause `ai_flags` to be assumed as 0. + // The GNU C library instead assumes a value of (AI_V4MAPPED | AI_ADDRCONFIG) for this case, + // since this value is considered an improvement on the specification." + let flags = if matches!(this.tcx.sess.target.env, Env::Gnu) { + this.eval_libc_i32("AI_V4MAPPED") | this.eval_libc_i32("AI_ADDRCONFIG") + } else { + 0 + }; + this.write_int(flags, &flags_mplace)?; + + let family_mplace = this.project_field_named(&addrinfo_mplace, "ai_family")?; + let family = match &address { + SocketAddr::V4(_) => this.eval_libc("AF_INET"), + SocketAddr::V6(_) => this.eval_libc("AF_INET6"), + }; + this.write_scalar(family, &family_mplace)?; + + let socktype_mplace = this.project_field_named(&addrinfo_mplace, "ai_socktype")?; + this.write_scalar(this.eval_libc("SOCK_STREAM"), &socktype_mplace)?; + + let protocol_mplace = this.project_field_named(&addrinfo_mplace, "ai_protocol")?; + // We don't support protocol hints and thus we just return zero which falls back + // to the default protocol for the provided socket type. + this.write_int(0, &protocol_mplace)?; + + // `sockaddr_storage` is guaranteed to fit any `sockaddr_*` address structure. + let sockaddr_layout = this.libc_ty_layout("sockaddr_storage"); + + let addrlen_mplace = this.project_field_named(&addrinfo_mplace, "ai_addrlen")?; + let addr_mplace = this.project_field_named(&addrinfo_mplace, "ai_addr")?; + this.write_int(sockaddr_layout.size.bytes(), &addrlen_mplace)?; + + let sockaddr_mplace = this.allocate(sockaddr_layout, MiriMemoryKind::Machine.into())?; + // Zero the newly allocated socket address struct. + this.write_bytes_ptr( + sockaddr_mplace.ptr(), + iter::repeat_n(0, sockaddr_mplace.layout.size.bytes_usize()), + )?; + this.write_socket_address( + &address, + sockaddr_mplace.ptr(), + addrlen_mplace.ptr(), + "getaddrinfo", + )?; + this.write_pointer(sockaddr_mplace.ptr(), &addr_mplace)?; + + let canonname_mplace = this.project_field_named(&addrinfo_mplace, "ai_canonname")?; + this.write_pointer(Pointer::null(), &canonname_mplace)?; + + // Allocate remaining list and store a pointer to it. + let next_mplace = this.project_field_named(&addrinfo_mplace, "ai_next")?; + let next_ptr = this.allocate_address_infos(addresses)?; + this.write_pointer(next_ptr, &next_mplace)?; + + interp_ok(addrinfo_mplace.ptr()) + } + + /// Deallocate the linked list of address info structs. + /// `address_ptr` points to the start from where we deallocate recursively. + fn free_address_infos(&mut self, address_ptr: Pointer) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + if address_ptr == Pointer::null() { + // We're at the end of the linked list. + return interp_ok(()); + } + + let addrinfo_layout = this.libc_ty_layout("addrinfo"); + let addrinfo_mplace = this.ptr_to_mplace(address_ptr, addrinfo_layout); + + let addr_field = this.project_field_named(&addrinfo_mplace, "ai_addr")?; + let addr_ptr = this.read_pointer(&addr_field)?; + this.deallocate_ptr(addr_ptr, None, MiriMemoryKind::Machine.into())?; + + let next_field = this.project_field_named(&addrinfo_mplace, "ai_next")?; + let next_ptr = this.read_pointer(&next_field)?; + this.free_address_infos(next_ptr)?; + + this.deallocate_ptr(address_ptr, None, MiriMemoryKind::SocketAddress.into())?; + + interp_ok(()) + } +} diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs index d4ebdeab86f16..7f2af20a5a7ea 100644 --- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs @@ -122,7 +122,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.socket(domain, type_, protocol)?; this.write_scalar(result, dest)?; } - "__xnet_bind" => { let [socket, address, address_len] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, *const _, libc::socklen_t) -> i32), @@ -142,6 +141,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; this.connect(socket, address, address_len, dest)?; } + "__xnet_getaddrinfo" => { + let [node, service, hints, res] = this.check_shim_sig( + shim_sig!(extern "C" fn(*const _, *const _, *const _, *mut _) -> i32), + link_name, + abi, + args, + )?; + let result = this.getaddrinfo(node, service, hints, res)?; + this.write_scalar(result, dest)?; + } // Miscellaneous "___errno" => { diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs new file mode 100644 index 0000000000000..30ba4414be0c8 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs @@ -0,0 +1,88 @@ +//@ignore-target: windows # No libc socket on Windows +//@compile-flags: -Zmiri-disable-isolation + +#[path = "../../utils/libc.rs"] +mod libc_utils; +#[path = "../../utils/mod.rs"] +mod utils; + +use std::ffi::CString; +use std::ptr; + +use libc_utils::*; + +fn main() { + test_getaddrinfo_freeaddrinfo(); +} + +/// Test doing address resolution using the `getaddrinfo` syscall. +/// This also tests freeing the address linked list using `freeaddrinfo`. +fn test_getaddrinfo_freeaddrinfo() { + let node_c_str = CString::new("localhost").unwrap(); + let service_c_str = CString::new("8080").unwrap(); + + let mut hints: libc::addrinfo = unsafe { std::mem::zeroed() }; + hints.ai_socktype = libc::SOCK_STREAM; + let mut res: *mut libc::addrinfo = ptr::null_mut(); + unsafe { + errno_check(libc::getaddrinfo( + node_c_str.as_ptr(), + service_c_str.as_ptr(), + &hints, + &mut res, + )); + } + let start = res; + let mut addr_count = 0; + + loop { + unsafe { + let Some(cur) = res.as_ref() else { + // It's a null pointer so we're at the end of the linked list. + break; + }; + + addr_count += 1; + match (*cur).ai_family as libc::c_int { + libc::AF_INET => { + let (_, addr) = net::sockname_ipv4(|storage, len| { + *(storage as *mut libc::sockaddr_in) = *cur.ai_addr.cast(); + *len = (*res).ai_addrlen; + 0 + }) + .unwrap(); + + let localhost_ipv4 = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 8080); + assert_eq!(localhost_ipv4.sin_family, addr.sin_family); + assert_eq!(localhost_ipv4.sin_port, addr.sin_port); + assert_eq!(localhost_ipv4.sin_addr.s_addr, addr.sin_addr.s_addr); + } + libc::AF_INET6 => { + let (_, addr) = net::sockname_ipv6(|storage, len| { + *(storage as *mut libc::sockaddr_in6) = *cur.ai_addr.cast(); + *len = (*res).ai_addrlen; + 0 + }) + .unwrap(); + + let localhost_ipv6 = net::sock_addr_ipv6(net::IPV6_LOCALHOST, 8080); + assert_eq!(localhost_ipv6.sin6_family, addr.sin6_family); + assert_eq!(localhost_ipv6.sin6_port, addr.sin6_port); + assert_eq!(localhost_ipv6.sin6_flowinfo, addr.sin6_flowinfo); + assert_eq!(localhost_ipv6.sin6_scope_id, addr.sin6_scope_id); + assert_eq!(localhost_ipv6.sin6_addr.s6_addr, addr.sin6_addr.s6_addr); + } + family => panic!("unexpected address family: {family}"), + } + + res = cur.ai_next; + } + } + + // We expect an IPv4 and an IPv6 address. + assert!(addr_count == 2); + + unsafe { + libc::freeaddrinfo(start.cast()); + } +} diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-invalid-addr.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-invalid-addr.rs new file mode 100644 index 0000000000000..4e46870da204b --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-invalid-addr.rs @@ -0,0 +1,31 @@ +//@ignore-target: windows # No libc socket on Windows +//@compile-flags: -Zmiri-disable-isolation +//@normalize-stderr-test: "address resolution failed: .*" -> "address resolution failed: $$MSG" + +#[path = "../../utils/libc.rs"] +mod libc_utils; +#[path = "../../utils/mod.rs"] +mod utils; + +use std::ffi::CString; +use std::ptr; + +use libc_utils::*; + +/// Test doing address resolution using the `getaddrinfo` syscall. +/// This also tests freeing the address linked list using `freeaddrinfo`. +fn main() { + let node_c_str = CString::new("this-is-not-a-valid-address").unwrap(); + let service_c_str = CString::new("8080").unwrap(); + + let mut hints: libc::addrinfo = unsafe { std::mem::zeroed() }; + hints.ai_socktype = libc::SOCK_STREAM; + let mut res: *mut libc::addrinfo = ptr::null_mut(); + let retval = + unsafe { libc::getaddrinfo(node_c_str.as_ptr(), service_c_str.as_ptr(), &hints, &mut res) }; + + // We return a system error. + assert_eq!(retval, libc::EAI_SYSTEM); + // Last error should be a generic protocol error. + assert_eq!(errno(), libc::EPROTO); +} diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-invalid-addr.stderr b/src/tools/miri/tests/pass-dep/libc/libc-socket-invalid-addr.stderr new file mode 100644 index 0000000000000..4eea2b6d24fb3 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-invalid-addr.stderr @@ -0,0 +1,8 @@ +warning: address resolution failed: $MSG + --> tests/pass-dep/libc/libc-socket-invalid-addr.rs:LL:CC + | +LL | unsafe { libc::getaddrinfo(node_c_str.as_ptr(), service_c_str.as_ptr(), &hints, &mut res) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error during address resolution + | + = note: Miri cannot return proper error information from this call; only a generic error code is being returned + diff --git a/src/tools/miri/tests/pass/shims/socket-address.rs b/src/tools/miri/tests/pass/shims/socket-address.rs new file mode 100644 index 0000000000000..af1283739df54 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/socket-address.rs @@ -0,0 +1,33 @@ +//@ignore-target: windows # No socket address support on Windows +//@compile-flags: -Zmiri-disable-isolation + +use std::net::{ + IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, TcpListener, ToSocketAddrs, +}; + +fn main() { + test_address_resolution(); +} + +/// Test getting a socket address from a hostname and a port. +fn test_address_resolution() { + let listener = TcpListener::bind("localhost:0").unwrap(); + let address = listener.local_addr().unwrap(); + match address.ip() { + IpAddr::V4(addr) => assert_eq!(addr, Ipv4Addr::LOCALHOST), + IpAddr::V6(addr) => assert_eq!(addr, Ipv6Addr::LOCALHOST), + } + + let addr_str = "localhost:8888"; + let mut addr_count = 0; + for addr in addr_str.to_socket_addrs().unwrap() { + addr_count += 1; + match addr { + SocketAddr::V4(addr) => assert_eq!(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8888), addr), + SocketAddr::V6(addr) => + assert_eq!(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 8888, 0, 0), addr), + } + } + // We expect an IPv4 and an IPv6 address. + assert!(addr_count == 2) +} diff --git a/src/tools/miri/tests/pass/shims/socket.rs b/src/tools/miri/tests/pass/shims/socket.rs index 584ce8f60ce92..957eefc628a3a 100644 --- a/src/tools/miri/tests/pass/shims/socket.rs +++ b/src/tools/miri/tests/pass/shims/socket.rs @@ -1,4 +1,4 @@ -//@ignore-target: windows # No libc socket on Windows +//@ignore-target: windows # No socket support on Windows //@compile-flags: -Zmiri-disable-isolation use std::io::{ErrorKind, Read, Write}; From 27def62f14d94031fa514fe88e59239954735572 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Thu, 7 May 2026 05:52:52 +0000 Subject: [PATCH 16/37] Prepare for merging from rust-lang/rust This updates the rust-version file to 4ddd4538a881317c622ed674b08300b8fc8dabdd. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 59e9e5a0e6ee9..ea9beeae52c5d 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -045b17737dab5fcc28e4cbee0cfe2ce4ed363b32 +4ddd4538a881317c622ed674b08300b8fc8dabdd From cdc427aa3a58026bdb06d63100552e1389374184 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Thu, 7 May 2026 06:02:00 +0000 Subject: [PATCH 17/37] fmt --- src/tools/miri/tests/pass/function_calls/abi_compat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/tests/pass/function_calls/abi_compat.rs b/src/tools/miri/tests/pass/function_calls/abi_compat.rs index ca76897faea86..37795daa55c84 100644 --- a/src/tools/miri/tests/pass/function_calls/abi_compat.rs +++ b/src/tools/miri/tests/pass/function_calls/abi_compat.rs @@ -66,7 +66,7 @@ fn test_abi_newtype() { #[repr(transparent)] #[derive(Copy, Clone)] enum Wrapper4 { - V(Zst, T, [u8; 0]) + V(Zst, T, [u8; 0]), } let t = T::default(); From 172929a81174a11fccb061ec34a142123a3dda82 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 7 May 2026 08:29:11 +0200 Subject: [PATCH 18/37] bless genmc tests --- .../tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr | 4 ++-- .../miri/tests/genmc/fail/shims/mutex_double_unlock.stderr | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr b/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr index 272a52ba7fefe..3ce5618fca93a 100644 --- a/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr +++ b/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr @@ -11,9 +11,9 @@ LL | self.lock.inner.unlock(); = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/std/src/sync/poison/mutex.rs:LL:CC - 1: std::ptr::drop_in_place)) + 1: std::ptr::drop_glue)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::ptr::drop_in_place)) + 2: std::ptr::drop_glue)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC 3: std::mem::drop at RUSTLIB/core/src/mem/mod.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr b/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr index d668706f76475..6d3fc92c531ff 100644 --- a/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr +++ b/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr @@ -10,7 +10,7 @@ LL | self.lock.inner.unlock(); = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/std/src/sync/poison/mutex.rs:LL:CC - 1: std::ptr::drop_in_place)) + 1: std::ptr::drop_glue)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC 2: std::mem::drop at RUSTLIB/core/src/mem/mod.rs:LL:CC From f9bc18dc62f44a2b575f847e00d44948fe3f7900 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 7 May 2026 08:40:00 +0200 Subject: [PATCH 19/37] try to remove generic normalization from backtraces --- .../libc_pthread_mutex_deadlock.stderr | 4 ++-- ...ibc_pthread_mutex_free_while_queued.stderr | 4 ++-- ..._pthread_rwlock_write_read_deadlock.stderr | 4 ++-- ...pthread_rwlock_write_write_deadlock.stderr | 4 ++-- .../concurrency/windows_join_detached.stderr | 4 ++-- .../concurrency/windows_join_main.stderr | 4 ++-- .../concurrency/windows_join_self.stderr | 4 ++-- .../libc/eventfd_block_read_twice.stderr | 4 ++-- .../libc/eventfd_block_write_twice.stderr | 4 ++-- .../libc/libc_epoll_block_two_thread.stderr | 4 ++-- .../socketpair-close-while-blocked.stderr | 4 ++-- .../libc/socketpair_block_read_twice.stderr | 4 ++-- .../libc/socketpair_block_write_twice.stderr | 4 ++-- .../fail/alloc/alloc_error_handler.stderr | 2 +- .../fail/alloc/global_system_mixup.stderr | 2 +- .../miri/tests/fail/alloc/stack_free.stderr | 4 ++-- .../both_borrows/aliasing_mut4.tree.stderr | 6 ++--- .../buggy_split_at_mut.stack.stderr | 2 +- .../drop_in_place_protector.stack.stderr | 6 ++--- .../drop_in_place_protector.tree.stderr | 6 ++--- .../newtype_pair_retagging.stack.stderr | 6 ++--- .../newtype_pair_retagging.tree.stderr | 6 ++--- .../newtype_retagging.stack.stderr | 6 ++--- .../newtype_retagging.tree.stderr | 6 ++--- .../tests/fail/coroutine-pinned-moved.stderr | 2 +- src/tools/miri/tests/fail/memleak_rc.stderr | 2 +- .../miri/tests/fail/panic/bad_unwind.stderr | 6 ++--- .../miri/tests/fail/panic/panic_abort1.stderr | 2 +- .../miri/tests/fail/panic/panic_abort2.stderr | 2 +- .../miri/tests/fail/panic/panic_abort3.stderr | 2 +- .../miri/tests/fail/panic/panic_abort4.stderr | 2 +- .../tests/fail/shims/fs/isolated_file.stderr | 12 +++++----- .../deallocate_against_protector1.stderr | 4 ++-- .../drop_in_place_retag.stderr | 2 +- .../tests/fail/tail_calls/cc-mismatch.stderr | 20 ++++++++--------- .../miri/tests/fail/tls_macro_leak.stderr | 4 ++-- .../implicit_writes/ptr_write.stderr | 2 +- .../implicit_writes/ptr_write_box.stderr | 2 +- .../ptr_write_unsafe_cell.stderr | 2 +- .../tree_borrows/strongly-protected.stderr | 4 ++-- .../strongly_protected_wildcard.stderr | 4 ++-- .../unaligned_pointers/drop_in_place.stderr | 2 +- .../reference_to_packed.stderr | 2 +- .../uninit/uninit_alloc_diagnostic.stderr | 2 +- ...it_alloc_diagnostic_with_provenance.stderr | 2 +- .../atomics/atomic_ptr_double_free.stderr | 2 +- .../atomic_ptr_alloc_race.dealloc.stderr | 2 +- .../atomic_ptr_alloc_race.write.stderr | 2 +- .../atomic_ptr_dealloc_write_race.stderr | 2 +- .../atomic_ptr_write_dealloc_race.stderr | 2 +- .../genmc/fail/data_race/mpu2_rels_rlx.stderr | 2 +- .../data_race/weak_orderings.rel_rlx.stderr | 2 +- .../data_race/weak_orderings.rlx_acq.stderr | 2 +- .../data_race/weak_orderings.rlx_rlx.stderr | 2 +- .../shims/mutex_diff_thread_unlock.stderr | 6 ++--- .../fail/shims/mutex_double_unlock.stderr | 4 ++-- .../fail/simple/alloc_large.multiple.stderr | 6 ++--- .../fail/simple/alloc_large.single.stderr | 6 ++--- .../cas_failure_ord_racy_key_init.stderr | 2 +- ...ibc-socket-no-blocking.windows_host.stderr | 4 ++-- .../tests/pass/alloc-access-tracking.stderr | 2 +- .../backtrace/backtrace-global-alloc.stderr | 20 ++++++++--------- .../tests/pass/backtrace/backtrace-std.stderr | 22 +++++++++---------- .../tests/pass/open_a_file_in_proc.stderr | 12 +++++----- src/tools/miri/tests/ui.rs | 2 -- 65 files changed, 144 insertions(+), 146 deletions(-) diff --git a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.stderr b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.stderr index 468c0e63cc34c..31a25b17546c6 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_free_while_queued.stderr b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_free_while_queued.stderr index b120a447d8b7e..ae37c89406bb3 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_free_while_queued.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_free_while_queued.stderr @@ -10,9 +10,9 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout); = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: main::{closure#0}::{closure#2} at tests/fail-dep/concurrency/libc_pthread_mutex_free_while_queued.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.stderr b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.stderr index e00c297a9b298..1cfbbb46a1c3b 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.stderr b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.stderr index 21fa8dcbd18de..2354bdcdbe1d7 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/concurrency/windows_join_detached.stderr b/src/tools/miri/tests/fail-dep/concurrency/windows_join_detached.stderr index 73e79852d63a1..77e3bb78ef106 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/windows_join_detached.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/windows_join_detached.stderr @@ -10,9 +10,9 @@ LL | let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle( = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/concurrency/windows_join_detached.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.stderr b/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.stderr index 2f3e41b405bc3..a8015e18eb73e 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.stderr @@ -8,9 +8,9 @@ LL | let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle( = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/concurrency/windows_join_main.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.stderr b/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.stderr index 8f28324363b33..16290624ca766 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.stderr @@ -8,9 +8,9 @@ LL | let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle( = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/concurrency/windows_join_self.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.stderr b/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.stderr index f5cfd35847af4..62e8eb422006b 100644 --- a/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.stderr +++ b/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/libc/eventfd_block_read_twice.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.stderr b/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.stderr index 92c3fc47c4fd6..e87eabad398ac 100644 --- a/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.stderr +++ b/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/libc/eventfd_block_write_twice.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr index af6578357a591..4f0f9daa3e0e6 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr +++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/libc/libc_epoll_block_two_thread.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair-close-while-blocked.stderr b/src/tools/miri/tests/fail-dep/libc/socketpair-close-while-blocked.stderr index b40c35d6d2667..886183be74139 100644 --- a/src/tools/miri/tests/fail-dep/libc/socketpair-close-while-blocked.stderr +++ b/src/tools/miri/tests/fail-dep/libc/socketpair-close-while-blocked.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/libc/socketpair-close-while-blocked.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr b/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr index d649076374f57..9c047878d4012 100644 --- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr +++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/libc/socketpair_block_read_twice.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr index 66f3359a1292c..b39addc3ce7bd 100644 --- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr +++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/libc/socketpair_block_write_twice.rs:LL:CC diff --git a/src/tools/miri/tests/fail/alloc/alloc_error_handler.stderr b/src/tools/miri/tests/fail/alloc/alloc_error_handler.stderr index ed9b7ad18b0fa..e99c0fc99bd9c 100644 --- a/src/tools/miri/tests/fail/alloc/alloc_error_handler.stderr +++ b/src/tools/miri/tests/fail/alloc/alloc_error_handler.stderr @@ -10,7 +10,7 @@ LL | crate::process::abort() = note: stack backtrace: 0: std::alloc::rust_oom::{closure#0} at RUSTLIB/std/src/alloc.rs:LL:CC - 1: std::sys::backtrace::__rust_end_short_backtrace + 1: std::sys::backtrace::__rust_end_short_backtrace::<{closure@std::alloc::rust_oom::{closure#0}}, !> at RUSTLIB/std/src/sys/backtrace.rs:LL:CC 2: std::alloc::rust_oom at RUSTLIB/std/src/alloc.rs:LL:CC diff --git a/src/tools/miri/tests/fail/alloc/global_system_mixup.stderr b/src/tools/miri/tests/fail/alloc/global_system_mixup.stderr index 4214e3e7675ba..1e9d859b5cf81 100644 --- a/src/tools/miri/tests/fail/alloc/global_system_mixup.stderr +++ b/src/tools/miri/tests/fail/alloc/global_system_mixup.stderr @@ -7,7 +7,7 @@ LL | FREE(); = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: stack backtrace: - 0: std::sys::alloc::PLATFORM::dealloc + 0: std::sys::alloc::PLATFORM::::dealloc at RUSTLIB/std/src/sys/alloc/PLATFORM.rs:LL:CC 1: ::deallocate at RUSTLIB/std/src/alloc.rs:LL:CC diff --git a/src/tools/miri/tests/fail/alloc/stack_free.stderr b/src/tools/miri/tests/fail/alloc/stack_free.stderr index 043c4a680f277..70fc61964df5c 100644 --- a/src/tools/miri/tests/fail/alloc/stack_free.stderr +++ b/src/tools/miri/tests/fail/alloc/stack_free.stderr @@ -9,9 +9,9 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout); = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: main at tests/fail/alloc/stack_free.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr index 70a96197b00a0..a033065613fc6 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr @@ -20,11 +20,11 @@ help: the protected tag was created here, in the initial state Frozen LL | fn safe(x: &i32, y: &mut Cell) { | ^ = note: stack backtrace: - 0: std::mem::replace + 0: std::mem::replace:: at RUSTLIB/core/src/mem/mod.rs:LL:CC - 1: std::cell::Cell::replace + 1: std::cell::Cell::::replace at RUSTLIB/core/src/cell.rs:LL:CC - 2: std::cell::Cell::set + 2: std::cell::Cell::::set at RUSTLIB/core/src/cell.rs:LL:CC 3: safe at tests/fail/both_borrows/aliasing_mut4.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.stack.stderr b/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.stack.stderr index 7a34e7d4e991e..b3de50d94f552 100644 --- a/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.stack.stderr @@ -23,7 +23,7 @@ help: was later invalidated at offsets [0x0..0x10] by a Unique retag LL | from_raw_parts_mut(ptr.offset(mid as isize), len - mid), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: stack backtrace: - 0: safe::split_at_mut + 0: safe::split_at_mut:: at tests/fail/both_borrows/buggy_split_at_mut.rs:LL:CC 1: main at tests/fail/both_borrows/buggy_split_at_mut.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.stack.stderr b/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.stack.stderr index 17b704ce80384..586dcdf8515dc 100644 --- a/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.stack.stderr @@ -19,11 +19,11 @@ LL | core::ptr::drop_in_place(x); = note: stack backtrace: 0: ::drop at tests/fail/both_borrows/drop_in_place_protector.rs:LL:CC - 1: std::ptr::drop_glue - shim(Some(HasDrop)) + 1: std::ptr::drop_glue:: - shim(Some(HasDrop)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::ptr::drop_glue - shim(Some((HasDrop, u8))) + 2: std::ptr::drop_glue::<(HasDrop, u8)> - shim(Some((HasDrop, u8))) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 3: std::ptr::drop_in_place + 3: std::ptr::drop_in_place::<(HasDrop, u8)> at RUSTLIB/core/src/ptr/mod.rs:LL:CC 4: main at tests/fail/both_borrows/drop_in_place_protector.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.tree.stderr b/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.tree.stderr index 61413c8551a82..867711bc67411 100644 --- a/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.tree.stderr @@ -22,11 +22,11 @@ LL | core::ptr::drop_in_place(x); = note: stack backtrace: 0: ::drop at tests/fail/both_borrows/drop_in_place_protector.rs:LL:CC - 1: std::ptr::drop_glue - shim(Some(HasDrop)) + 1: std::ptr::drop_glue:: - shim(Some(HasDrop)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::ptr::drop_glue - shim(Some((HasDrop, u8))) + 2: std::ptr::drop_glue::<(HasDrop, u8)> - shim(Some((HasDrop, u8))) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 3: std::ptr::drop_in_place + 3: std::ptr::drop_in_place::<(HasDrop, u8)> at RUSTLIB/core/src/ptr/mod.rs:LL:CC 4: main at tests/fail/both_borrows/drop_in_place_protector.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr index d7688680e7da2..2f10e7df418f4 100644 --- a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr @@ -17,13 +17,13 @@ help: is this argument LL | fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { | ^^ = note: stack backtrace: - 0: std::boxed::Box::from_raw_in + 0: std::boxed::Box::::from_raw_in at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::boxed::Box::from_raw + 1: std::boxed::Box::::from_raw at RUSTLIB/alloc/src/boxed.rs:LL:CC 2: main::{closure#0} at tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC - 3: dealloc_while_running + 3: dealloc_while_running::<{closure@tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC}> at tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC 4: main at tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr index e84fa4281d13b..58c43ebe4aeea 100644 --- a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr @@ -28,13 +28,13 @@ LL | || drop(Box::from_raw(ptr)), = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: main::{closure#0} at tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC - 4: dealloc_while_running + 4: dealloc_while_running::<{closure@tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC}> at tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC 5: main at tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.stack.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.stack.stderr index b7b0f28126698..a173cf25e9e78 100644 --- a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.stack.stderr @@ -17,13 +17,13 @@ help: is this argument LL | fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { | ^^ = note: stack backtrace: - 0: std::boxed::Box::from_raw_in + 0: std::boxed::Box::::from_raw_in at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::boxed::Box::from_raw + 1: std::boxed::Box::::from_raw at RUSTLIB/alloc/src/boxed.rs:LL:CC 2: main::{closure#0} at tests/fail/both_borrows/newtype_retagging.rs:LL:CC - 3: dealloc_while_running + 3: dealloc_while_running::<{closure@tests/fail/both_borrows/newtype_retagging.rs:LL:CC}> at tests/fail/both_borrows/newtype_retagging.rs:LL:CC 4: main at tests/fail/both_borrows/newtype_retagging.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr index 1151a44410cc3..a5b66643144ec 100644 --- a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr @@ -28,13 +28,13 @@ LL | || drop(Box::from_raw(ptr)), = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: main::{closure#0} at tests/fail/both_borrows/newtype_retagging.rs:LL:CC - 4: dealloc_while_running + 4: dealloc_while_running::<{closure@tests/fail/both_borrows/newtype_retagging.rs:LL:CC}> at tests/fail/both_borrows/newtype_retagging.rs:LL:CC 5: main at tests/fail/both_borrows/newtype_retagging.rs:LL:CC diff --git a/src/tools/miri/tests/fail/coroutine-pinned-moved.stderr b/src/tools/miri/tests/fail/coroutine-pinned-moved.stderr index 0c57c6894a085..7998db2424b36 100644 --- a/src/tools/miri/tests/fail/coroutine-pinned-moved.stderr +++ b/src/tools/miri/tests/fail/coroutine-pinned-moved.stderr @@ -21,7 +21,7 @@ LL | }; // *deallocate* coroutine_iterator at tests/fail/coroutine-pinned-moved.rs:LL:CC 1: as std::iter::Iterator>::next at tests/fail/coroutine-pinned-moved.rs:LL:CC - 2: std::boxed::iter::next + 2: std::boxed::iter::>>::next at RUSTLIB/alloc/src/boxed/iter.rs:LL:CC 3: main at tests/fail/coroutine-pinned-moved.rs:LL:CC diff --git a/src/tools/miri/tests/fail/memleak_rc.stderr b/src/tools/miri/tests/fail/memleak_rc.stderr index 91097a99117f2..2c22367f52462 100644 --- a/src/tools/miri/tests/fail/memleak_rc.stderr +++ b/src/tools/miri/tests/fail/memleak_rc.stderr @@ -5,7 +5,7 @@ LL | Box::leak(Box::new(RcInner { strong: Cell::new(1), weak: Ce | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: stack backtrace: - 0: std::rc::Rc::new + 0: std::rc::Rc::>>::new at RUSTLIB/alloc/src/rc.rs:LL:CC 1: main at tests/fail/memleak_rc.rs:LL:CC diff --git a/src/tools/miri/tests/fail/panic/bad_unwind.stderr b/src/tools/miri/tests/fail/panic/bad_unwind.stderr index d47f7b5b10c1f..cde80858d0fd2 100644 --- a/src/tools/miri/tests/fail/panic/bad_unwind.stderr +++ b/src/tools/miri/tests/fail/panic/bad_unwind.stderr @@ -14,11 +14,11 @@ LL | std::panic::catch_unwind(|| unwind()).unwrap_err(); = note: stack backtrace: 0: main::{closure#0} at tests/fail/panic/bad_unwind.rs:LL:CC - 1: std::panicking::catch_unwind::do_call + 1: std::panicking::catch_unwind::do_call::<{closure@tests/fail/panic/bad_unwind.rs:LL:CC}, ()> at RUSTLIB/std/src/panicking.rs:LL:CC - 2: std::panicking::catch_unwind + 2: std::panicking::catch_unwind::<(), {closure@tests/fail/panic/bad_unwind.rs:LL:CC}> at RUSTLIB/std/src/panicking.rs:LL:CC - 3: std::panic::catch_unwind + 3: std::panic::catch_unwind::<{closure@tests/fail/panic/bad_unwind.rs:LL:CC}, ()> at RUSTLIB/std/src/panic.rs:LL:CC 4: main at tests/fail/panic/bad_unwind.rs:LL:CC diff --git a/src/tools/miri/tests/fail/panic/panic_abort1.stderr b/src/tools/miri/tests/fail/panic/panic_abort1.stderr index 4135ceadc5397..079eb4117b062 100644 --- a/src/tools/miri/tests/fail/panic/panic_abort1.stderr +++ b/src/tools/miri/tests/fail/panic/panic_abort1.stderr @@ -20,7 +20,7 @@ LL | crate::process::abort(); at RUSTLIB/std/src/panicking.rs:LL:CC 4: std::panicking::panic_handler::{closure#0} at RUSTLIB/std/src/panicking.rs:LL:CC - 5: std::sys::backtrace::__rust_end_short_backtrace + 5: std::sys::backtrace::__rust_end_short_backtrace::<{closure@std::panicking::panic_handler::{closure#0}}, !> at RUSTLIB/std/src/sys/backtrace.rs:LL:CC 6: std::panicking::panic_handler at RUSTLIB/std/src/panicking.rs:LL:CC diff --git a/src/tools/miri/tests/fail/panic/panic_abort2.stderr b/src/tools/miri/tests/fail/panic/panic_abort2.stderr index 5b485604037ce..5437fa077a0a4 100644 --- a/src/tools/miri/tests/fail/panic/panic_abort2.stderr +++ b/src/tools/miri/tests/fail/panic/panic_abort2.stderr @@ -20,7 +20,7 @@ LL | crate::process::abort(); at RUSTLIB/std/src/panicking.rs:LL:CC 4: std::panicking::panic_handler::{closure#0} at RUSTLIB/std/src/panicking.rs:LL:CC - 5: std::sys::backtrace::__rust_end_short_backtrace + 5: std::sys::backtrace::__rust_end_short_backtrace::<{closure@std::panicking::panic_handler::{closure#0}}, !> at RUSTLIB/std/src/sys/backtrace.rs:LL:CC 6: std::panicking::panic_handler at RUSTLIB/std/src/panicking.rs:LL:CC diff --git a/src/tools/miri/tests/fail/panic/panic_abort3.stderr b/src/tools/miri/tests/fail/panic/panic_abort3.stderr index e4179f93f931c..022515ebecfd1 100644 --- a/src/tools/miri/tests/fail/panic/panic_abort3.stderr +++ b/src/tools/miri/tests/fail/panic/panic_abort3.stderr @@ -20,7 +20,7 @@ LL | crate::process::abort(); at RUSTLIB/std/src/panicking.rs:LL:CC 4: std::panicking::panic_handler::{closure#0} at RUSTLIB/std/src/panicking.rs:LL:CC - 5: std::sys::backtrace::__rust_end_short_backtrace + 5: std::sys::backtrace::__rust_end_short_backtrace::<{closure@std::panicking::panic_handler::{closure#0}}, !> at RUSTLIB/std/src/sys/backtrace.rs:LL:CC 6: std::panicking::panic_handler at RUSTLIB/std/src/panicking.rs:LL:CC diff --git a/src/tools/miri/tests/fail/panic/panic_abort4.stderr b/src/tools/miri/tests/fail/panic/panic_abort4.stderr index d2e50b87068f6..a39037d528558 100644 --- a/src/tools/miri/tests/fail/panic/panic_abort4.stderr +++ b/src/tools/miri/tests/fail/panic/panic_abort4.stderr @@ -20,7 +20,7 @@ LL | crate::process::abort(); at RUSTLIB/std/src/panicking.rs:LL:CC 4: std::panicking::panic_handler::{closure#0} at RUSTLIB/std/src/panicking.rs:LL:CC - 5: std::sys::backtrace::__rust_end_short_backtrace + 5: std::sys::backtrace::__rust_end_short_backtrace::<{closure@std::panicking::panic_handler::{closure#0}}, !> at RUSTLIB/std/src/sys/backtrace.rs:LL:CC 6: std::panicking::panic_handler at RUSTLIB/std/src/panicking.rs:LL:CC diff --git a/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr b/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr index 09501f98a9aa0..5012e4c7ca8d7 100644 --- a/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr +++ b/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr @@ -9,25 +9,25 @@ LL | let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode a = note: stack backtrace: 0: std::sys::fs::PLATFORM::File::open_c::{closure#0} at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC - 1: std::sys::pal::PLATFORM::cvt_r + 1: std::sys::pal::PLATFORM::cvt_r:: at RUSTLIB/std/src/sys/pal/PLATFORM/mod.rs:LL:CC 2: std::sys::fs::PLATFORM::File::open_c at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC 3: std::sys::fs::PLATFORM::File::open::{closure#0} at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC - 4: std::sys::helpers::small_c_string::run_with_cstr_stack + 4: std::sys::helpers::small_c_string::run_with_cstr_stack:: at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC - 5: std::sys::helpers::small_c_string::run_with_cstr + 5: std::sys::helpers::small_c_string::run_with_cstr:: at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC - 6: std::sys::helpers::small_c_string::run_path_with_cstr + 6: std::sys::helpers::small_c_string::run_path_with_cstr:: at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC 7: std::sys::fs::PLATFORM::File::open at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC 8: std::fs::OpenOptions::_open at RUSTLIB/std/src/fs.rs:LL:CC - 9: std::fs::OpenOptions::open + 9: std::fs::OpenOptions::open::<&std::path::Path> at RUSTLIB/std/src/fs.rs:LL:CC - 10: std::fs::File::open + 10: std::fs::File::open::<&str> at RUSTLIB/std/src/fs.rs:LL:CC 11: main at tests/fail/shims/fs/isolated_file.rs:LL:CC diff --git a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr index 1d9bfe5440685..9f0df14ee4ddd 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr +++ b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr @@ -9,9 +9,9 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout); = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: main::{closure#0} at tests/fail/stacked_borrows/deallocate_against_protector1.rs:LL:CC diff --git a/src/tools/miri/tests/fail/stacked_borrows/drop_in_place_retag.stderr b/src/tools/miri/tests/fail/stacked_borrows/drop_in_place_retag.stderr index 96e31615c20ed..c9a3c93916fde 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/drop_in_place_retag.stderr +++ b/src/tools/miri/tests/fail/stacked_borrows/drop_in_place_retag.stderr @@ -12,7 +12,7 @@ help: was created by a SharedReadOnly retag at offsets [0x0..0x1] LL | let x = core::ptr::addr_of!(x); | ^^^^^^^^^^^^^^^^^^^^^^ = note: stack backtrace: - 0: std::ptr::drop_in_place + 0: std::ptr::drop_in_place:: at RUSTLIB/core/src/ptr/mod.rs:LL:CC 1: main at tests/fail/stacked_borrows/drop_in_place_retag.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr index c7e471f570c85..a9440caa3e3b1 100644 --- a/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr +++ b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr @@ -9,29 +9,29 @@ LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output; = note: stack backtrace: 0: >::call_once - shim(fn()) at RUSTLIB/core/src/ops/function.rs:LL:CC - 1: std::sys::backtrace::__rust_begin_short_backtrace + 1: std::sys::backtrace::__rust_begin_short_backtrace:: at RUSTLIB/std/src/sys/backtrace.rs:LL:CC - 2: std::rt::lang_start::{closure#0} + 2: std::rt::lang_start::<()>::{closure#0} at RUSTLIB/std/src/rt.rs:LL:CC - 3: std::ops::function::impls::call_once + 3: std::ops::function::impls:: for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once at RUSTLIB/core/src/ops/function.rs:LL:CC - 4: std::panicking::catch_unwind::do_call + 4: std::panicking::catch_unwind::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32> at RUSTLIB/std/src/panicking.rs:LL:CC - 5: std::panicking::catch_unwind + 5: std::panicking::catch_unwind:: i32 + std::marker::Sync + std::panic::RefUnwindSafe> at RUSTLIB/std/src/panicking.rs:LL:CC - 6: std::panic::catch_unwind + 6: std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32> at RUSTLIB/std/src/panic.rs:LL:CC 7: std::rt::lang_start_internal::{closure#0} at RUSTLIB/std/src/rt.rs:LL:CC - 8: std::panicking::catch_unwind::do_call + 8: std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize> at RUSTLIB/std/src/panicking.rs:LL:CC - 9: std::panicking::catch_unwind + 9: std::panicking::catch_unwind:: at RUSTLIB/std/src/panicking.rs:LL:CC - 10: std::panic::catch_unwind + 10: std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize> at RUSTLIB/std/src/panic.rs:LL:CC 11: std::rt::lang_start_internal at RUSTLIB/std/src/rt.rs:LL:CC - 12: std::rt::lang_start + 12: std::rt::lang_start::<()> at RUSTLIB/std/src/rt.rs:LL:CC error: aborting due to 1 previous error diff --git a/src/tools/miri/tests/fail/tls_macro_leak.stderr b/src/tools/miri/tests/fail/tls_macro_leak.stderr index 3f8240f44547f..135dde879b9e1 100644 --- a/src/tools/miri/tests/fail/tls_macro_leak.stderr +++ b/src/tools/miri/tests/fail/tls_macro_leak.stderr @@ -7,9 +7,9 @@ LL | cell.set(Some(Box::leak(Box::new(123)))); = note: stack backtrace: 0: main::{closure#0}::{closure#0} at tests/fail/tls_macro_leak.rs:LL:CC - 1: std::thread::LocalKey::>>::try_with + 1: std::thread::LocalKey::>>::try_with::<{closure@tests/fail/tls_macro_leak.rs:LL:CC}, ()> at RUSTLIB/std/src/thread/local.rs:LL:CC - 2: std::thread::LocalKey::>>::with + 2: std::thread::LocalKey::>>::with::<{closure@tests/fail/tls_macro_leak.rs:LL:CC}, ()> at RUSTLIB/std/src/thread/local.rs:LL:CC 3: main::{closure#0} at tests/fail/tls_macro_leak.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write.stderr b/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write.stderr index e88e13104815e..baec37b17f026 100644 --- a/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write.stderr @@ -20,7 +20,7 @@ help: the protected tag was created here, in the initial state Reserved LL | fn dereference(x: T, y: *mut u8) -> T { | ^ = note: stack backtrace: - 0: dereference + 0: dereference::<&mut u8> at tests/fail/tree_borrows/implicit_writes/ptr_write.rs:LL:CC 1: main at tests/fail/tree_borrows/implicit_writes/ptr_write.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_box.stderr b/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_box.stderr index 9951fd56ada05..9e0bc972dce4f 100644 --- a/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_box.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_box.stderr @@ -20,7 +20,7 @@ help: the protected tag was created here, in the initial state Reserved LL | fn dereference(x: T, y: *mut u8) -> T { | ^ = note: stack backtrace: - 0: dereference + 0: dereference::> at tests/fail/tree_borrows/implicit_writes/ptr_write_box.rs:LL:CC 1: main at tests/fail/tree_borrows/implicit_writes/ptr_write_box.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_unsafe_cell.stderr b/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_unsafe_cell.stderr index 87cf0c8ba2de7..bb65e846ebe3c 100644 --- a/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_unsafe_cell.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_unsafe_cell.stderr @@ -20,7 +20,7 @@ help: the protected tag was created here, in the initial state Reserved LL | fn dereference(x: T, y: *mut u8) -> T { | ^ = note: stack backtrace: - 0: dereference + 0: dereference::<&mut std::cell::UnsafeCell> at tests/fail/tree_borrows/implicit_writes/ptr_write_unsafe_cell.rs:LL:CC 1: main at tests/fail/tree_borrows/implicit_writes/ptr_write_unsafe_cell.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr b/src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr index d2a11659460ef..9af0414ce6061 100644 --- a/src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr @@ -21,9 +21,9 @@ LL | fn inner(x: &mut i32, f: fn(*mut i32)) { = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: main::{closure#0} at tests/fail/tree_borrows/strongly-protected.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr index c192c2d00d088..e6c68dd3de92f 100644 --- a/src/tools/miri/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr @@ -21,9 +21,9 @@ LL | fn inner(x: &mut i32, f: fn(usize)) { = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: main::{closure#0} at tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC diff --git a/src/tools/miri/tests/fail/unaligned_pointers/drop_in_place.stderr b/src/tools/miri/tests/fail/unaligned_pointers/drop_in_place.stderr index 603d582cde804..52096d5c7ecdf 100644 --- a/src/tools/miri/tests/fail/unaligned_pointers/drop_in_place.stderr +++ b/src/tools/miri/tests/fail/unaligned_pointers/drop_in_place.stderr @@ -7,7 +7,7 @@ LL | unsafe { drop_glue(&mut *to_drop) } = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: stack backtrace: - 0: std::ptr::drop_in_place + 0: std::ptr::drop_in_place:: at RUSTLIB/core/src/ptr/mod.rs:LL:CC 1: main at tests/fail/unaligned_pointers/drop_in_place.rs:LL:CC diff --git a/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.stderr b/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.stderr index d8252e51ae84c..28ed982a0864a 100644 --- a/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.stderr +++ b/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.stderr @@ -7,7 +7,7 @@ LL | mem::transmute(x) = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: stack backtrace: - 0: raw_to_ref + 0: raw_to_ref::<'_, i32> at tests/fail/unaligned_pointers/reference_to_packed.rs:LL:CC 1: main at tests/fail/unaligned_pointers/reference_to_packed.rs:LL:CC diff --git a/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic.stderr b/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic.stderr index e0caff6a21a9d..26d5ffa62812d 100644 --- a/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic.stderr +++ b/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic.stderr @@ -9,7 +9,7 @@ LL | let mut order = unsafe { compare_bytes(left, right, len) as isize } = note: stack backtrace: 0: ::compare at RUSTLIB/core/src/slice/cmp.rs:LL:CC - 1: core::slice::cmp::cmp + 1: core::slice::cmp::::cmp at RUSTLIB/core/src/slice/cmp.rs:LL:CC 2: main at tests/fail/uninit/uninit_alloc_diagnostic.rs:LL:CC diff --git a/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr b/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr index 28609ddec6a11..4e532293410e3 100644 --- a/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr +++ b/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr @@ -9,7 +9,7 @@ LL | let mut order = unsafe { compare_bytes(left, right, len) as isize } = note: stack backtrace: 0: ::compare at RUSTLIB/core/src/slice/cmp.rs:LL:CC - 1: core::slice::cmp::cmp + 1: core::slice::cmp::::cmp at RUSTLIB/core/src/slice/cmp.rs:LL:CC 2: main at tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.stderr b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.stderr index 2ba64ac032a8d..9eb9183611bc3 100644 --- a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.stderr +++ b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.stderr @@ -25,7 +25,7 @@ LL | dealloc(ptr as *mut u8, Layout::new::()) at tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC 2: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 3: genmc::spawn_pthread_closure::thread_func + 3: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC}> at tests/genmc/fail/atomics/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/atomics/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.dealloc.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.dealloc.stderr index aa51e1213d0c9..c7f8cba09f6ef 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.dealloc.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.dealloc.stderr @@ -13,7 +13,7 @@ LL | dealloc(b as *mut u8, Layout::new::()); at tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.write.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.write.stderr index 77e9817a55c74..da50ff8612345 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.write.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.write.stderr @@ -13,7 +13,7 @@ LL | *b = 42; at tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.stderr index 47df94404efa3..de3b554d4ac11 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.stderr @@ -23,7 +23,7 @@ LL | }), at tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.stderr index e2c87b2d25e19..248009cb1ed0c 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.stderr @@ -13,7 +13,7 @@ LL | }), at tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/mpu2_rels_rlx.stderr b/src/tools/miri/tests/genmc/fail/data_race/mpu2_rels_rlx.stderr index a3a15a71ce156..1ccbf7683f20c 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/mpu2_rels_rlx.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/mpu2_rels_rlx.stderr @@ -13,7 +13,7 @@ LL | X = 2; at tests/genmc/fail/data_race/mpu2_rels_rlx.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/mpu2_rels_rlx.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rel_rlx.stderr b/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rel_rlx.stderr index 1220c0c09cbe1..e589153e1e819 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rel_rlx.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rel_rlx.stderr @@ -13,7 +13,7 @@ LL | X = 2; at tests/genmc/fail/data_race/weak_orderings.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/weak_orderings.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_acq.stderr b/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_acq.stderr index 1220c0c09cbe1..e589153e1e819 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_acq.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_acq.stderr @@ -13,7 +13,7 @@ LL | X = 2; at tests/genmc/fail/data_race/weak_orderings.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/weak_orderings.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_rlx.stderr b/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_rlx.stderr index 1220c0c09cbe1..e589153e1e819 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_rlx.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_rlx.stderr @@ -13,7 +13,7 @@ LL | X = 2; at tests/genmc/fail/data_race/weak_orderings.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/weak_orderings.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr b/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr index 3ce5618fca93a..9171c07eebcd4 100644 --- a/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr +++ b/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr @@ -11,11 +11,11 @@ LL | self.lock.inner.unlock(); = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/std/src/sync/poison/mutex.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::sync::MutexGuard<'_, u64>)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::ptr::drop_glue)) + 2: std::ptr::drop_glue::>> - shim(Some(EvilSend>)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 3: std::mem::drop + 3: std::mem::drop::>> at RUSTLIB/core/src/mem/mod.rs:LL:CC 4: miri_start::{closure#0} at tests/genmc/fail/shims/mutex_diff_thread_unlock.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr b/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr index 6d3fc92c531ff..e75eed834cb72 100644 --- a/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr +++ b/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr @@ -10,9 +10,9 @@ LL | self.lock.inner.unlock(); = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/std/src/sync/poison/mutex.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::sync::MutexGuard<'_, u64>)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: miri_start at tests/genmc/fail/shims/mutex_double_unlock.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/simple/alloc_large.multiple.stderr b/src/tools/miri/tests/genmc/fail/simple/alloc_large.multiple.stderr index 5ddcce1fa30c3..f9d217acf42e7 100644 --- a/src/tools/miri/tests/genmc/fail/simple/alloc_large.multiple.stderr +++ b/src/tools/miri/tests/genmc/fail/simple/alloc_large.multiple.stderr @@ -11,11 +11,11 @@ LL | AllocInit::Uninitialized => alloc.allocate(layout), at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC 1: alloc::raw_vec::RawVecInner::with_capacity_in at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC - 2: alloc::raw_vec::RawVec::with_capacity_in + 2: alloc::raw_vec::RawVec::::with_capacity_in at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC - 3: std::vec::Vec::with_capacity_in + 3: std::vec::Vec::::with_capacity_in at RUSTLIB/alloc/src/vec/mod.rs:LL:CC - 4: std::vec::Vec::with_capacity + 4: std::vec::Vec::::with_capacity at RUSTLIB/alloc/src/vec/mod.rs:LL:CC 5: miri_start at tests/genmc/fail/simple/alloc_large.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/simple/alloc_large.single.stderr b/src/tools/miri/tests/genmc/fail/simple/alloc_large.single.stderr index 5ddcce1fa30c3..f9d217acf42e7 100644 --- a/src/tools/miri/tests/genmc/fail/simple/alloc_large.single.stderr +++ b/src/tools/miri/tests/genmc/fail/simple/alloc_large.single.stderr @@ -11,11 +11,11 @@ LL | AllocInit::Uninitialized => alloc.allocate(layout), at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC 1: alloc::raw_vec::RawVecInner::with_capacity_in at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC - 2: alloc::raw_vec::RawVec::with_capacity_in + 2: alloc::raw_vec::RawVec::::with_capacity_in at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC - 3: std::vec::Vec::with_capacity_in + 3: std::vec::Vec::::with_capacity_in at RUSTLIB/alloc/src/vec/mod.rs:LL:CC - 4: std::vec::Vec::with_capacity + 4: std::vec::Vec::::with_capacity at RUSTLIB/alloc/src/vec/mod.rs:LL:CC 5: miri_start at tests/genmc/fail/simple/alloc_large.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr index 24bde07924d1f..720879217679a 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr @@ -13,7 +13,7 @@ LL | match KEY.compare_exchange(KEY_SENTVAL, key, Release, Acquire) { at tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs:LL:CC 2: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 3: genmc::spawn_pthread_closure::thread_func + 3: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs:LL:CC}> at tests/genmc/pass/atomics/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/pass/atomics/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.windows_host.stderr b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.windows_host.stderr index 8173c121067a6..006e911499a86 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.windows_host.stderr +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.windows_host.stderr @@ -10,9 +10,9 @@ LL | libc::getsockname(client_sockfd, storage, len) = note: stack backtrace: 0: test_getsockname_ipv4_connect_nonblock::{closure#0} at tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC - 1: libc_utils::net::sockname + 1: libc_utils::net::sockname::<{closure@tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC}> at tests/pass-dep/libc/../../utils/libc.rs:LL:CC - 2: libc_utils::net::sockname_ipv4 + 2: libc_utils::net::sockname_ipv4::<{closure@tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC}> at tests/pass-dep/libc/../../utils/libc.rs:LL:CC 3: test_getsockname_ipv4_connect_nonblock at tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC diff --git a/src/tools/miri/tests/pass/alloc-access-tracking.stderr b/src/tools/miri/tests/pass/alloc-access-tracking.stderr index 0e25be460cbba..bcc1d987330ab 100644 --- a/src/tools/miri/tests/pass/alloc-access-tracking.stderr +++ b/src/tools/miri/tests/pass/alloc-access-tracking.stderr @@ -25,7 +25,7 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout); = note: stack backtrace: 0: > as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::>> - shim(Some(std::boxed::Box>)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC 2: main at tests/pass/alloc-access-tracking.rs:LL:CC diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.stderr b/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.stderr index b4ef3f76c20f7..bf443274a958f 100644 --- a/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.stderr +++ b/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.stderr @@ -2,27 +2,27 @@ at tests/pass/backtrace/backtrace-global-alloc.rs:LL:CC 1: >::call_once - shim(fn()) at RUSTLIB/core/src/ops/function.rs:LL:CC - 2: std::sys::backtrace::__rust_begin_short_backtrace + 2: std::sys::backtrace::__rust_begin_short_backtrace:: at RUSTLIB/std/src/sys/backtrace.rs:LL:CC - 3: std::rt::lang_start::{closure#0} + 3: std::rt::lang_start::<()>::{closure#0} at RUSTLIB/std/src/rt.rs:LL:CC - 4: std::ops::function::impls::call_once + 4: std::ops::function::impls:: for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once at RUSTLIB/core/src/ops/function.rs:LL:CC - 5: std::panicking::catch_unwind::do_call + 5: std::panicking::catch_unwind::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32> at RUSTLIB/std/src/panicking.rs:LL:CC - 6: std::panicking::catch_unwind + 6: std::panicking::catch_unwind:: i32 + std::marker::Sync + std::panic::RefUnwindSafe> at RUSTLIB/std/src/panicking.rs:LL:CC - 7: std::panic::catch_unwind + 7: std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32> at RUSTLIB/std/src/panic.rs:LL:CC 8: std::rt::lang_start_internal::{closure#0} at RUSTLIB/std/src/rt.rs:LL:CC - 9: std::panicking::catch_unwind::do_call + 9: std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize> at RUSTLIB/std/src/panicking.rs:LL:CC - 10: std::panicking::catch_unwind + 10: std::panicking::catch_unwind:: at RUSTLIB/std/src/panicking.rs:LL:CC - 11: std::panic::catch_unwind + 11: std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize> at RUSTLIB/std/src/panic.rs:LL:CC 12: std::rt::lang_start_internal at RUSTLIB/std/src/rt.rs:LL:CC - 13: std::rt::lang_start + 13: std::rt::lang_start::<()> at RUSTLIB/std/src/rt.rs:LL:CC diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-std.stderr b/src/tools/miri/tests/pass/backtrace/backtrace-std.stderr index 706eacc70fd8d..ee0c65fa6eb67 100644 --- a/src/tools/miri/tests/pass/backtrace/backtrace-std.stderr +++ b/src/tools/miri/tests/pass/backtrace/backtrace-std.stderr @@ -2,7 +2,7 @@ at tests/pass/backtrace/backtrace-std.rs:LL:CC 1: func_c at tests/pass/backtrace/backtrace-std.rs:LL:CC - 2: func_b + 2: func_b:: at tests/pass/backtrace/backtrace-std.rs:LL:CC 3: func_a at tests/pass/backtrace/backtrace-std.rs:LL:CC @@ -10,27 +10,27 @@ at tests/pass/backtrace/backtrace-std.rs:LL:CC 5: >::call_once - shim(fn()) at RUSTLIB/core/src/ops/function.rs:LL:CC - 6: std::sys::backtrace::__rust_begin_short_backtrace + 6: std::sys::backtrace::__rust_begin_short_backtrace:: at RUSTLIB/std/src/sys/backtrace.rs:LL:CC - 7: std::rt::lang_start::{closure#0} + 7: std::rt::lang_start::<()>::{closure#0} at RUSTLIB/std/src/rt.rs:LL:CC - 8: std::ops::function::impls::call_once + 8: std::ops::function::impls:: for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once at RUSTLIB/core/src/ops/function.rs:LL:CC - 9: std::panicking::catch_unwind::do_call + 9: std::panicking::catch_unwind::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32> at RUSTLIB/std/src/panicking.rs:LL:CC - 10: std::panicking::catch_unwind + 10: std::panicking::catch_unwind:: i32 + std::marker::Sync + std::panic::RefUnwindSafe> at RUSTLIB/std/src/panicking.rs:LL:CC - 11: std::panic::catch_unwind + 11: std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32> at RUSTLIB/std/src/panic.rs:LL:CC 12: std::rt::lang_start_internal::{closure#0} at RUSTLIB/std/src/rt.rs:LL:CC - 13: std::panicking::catch_unwind::do_call + 13: std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize> at RUSTLIB/std/src/panicking.rs:LL:CC - 14: std::panicking::catch_unwind + 14: std::panicking::catch_unwind:: at RUSTLIB/std/src/panicking.rs:LL:CC - 15: std::panic::catch_unwind + 15: std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize> at RUSTLIB/std/src/panic.rs:LL:CC 16: std::rt::lang_start_internal at RUSTLIB/std/src/rt.rs:LL:CC - 17: std::rt::lang_start + 17: std::rt::lang_start::<()> at RUSTLIB/std/src/rt.rs:LL:CC diff --git a/src/tools/miri/tests/pass/open_a_file_in_proc.stderr b/src/tools/miri/tests/pass/open_a_file_in_proc.stderr index 0ba31ed5adac0..c80b11ecb37bb 100644 --- a/src/tools/miri/tests/pass/open_a_file_in_proc.stderr +++ b/src/tools/miri/tests/pass/open_a_file_in_proc.stderr @@ -7,25 +7,25 @@ LL | let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode a = note: stack backtrace: 0: std::sys::fs::PLATFORM::File::open_c::{closure#0} at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC - 1: std::sys::pal::PLATFORM::cvt_r + 1: std::sys::pal::PLATFORM::cvt_r:: at RUSTLIB/std/src/sys/pal/PLATFORM/mod.rs:LL:CC 2: std::sys::fs::PLATFORM::File::open_c at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC 3: std::sys::fs::PLATFORM::File::open::{closure#0} at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC - 4: std::sys::helpers::small_c_string::run_with_cstr_stack + 4: std::sys::helpers::small_c_string::run_with_cstr_stack:: at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC - 5: std::sys::helpers::small_c_string::run_with_cstr + 5: std::sys::helpers::small_c_string::run_with_cstr:: at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC - 6: std::sys::helpers::small_c_string::run_path_with_cstr + 6: std::sys::helpers::small_c_string::run_path_with_cstr:: at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC 7: std::sys::fs::PLATFORM::File::open at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC 8: std::fs::OpenOptions::_open at RUSTLIB/std/src/fs.rs:LL:CC - 9: std::fs::OpenOptions::open + 9: std::fs::OpenOptions::open::<&std::path::Path> at RUSTLIB/std/src/fs.rs:LL:CC - 10: std::fs::File::open + 10: std::fs::File::open::<&str> at RUSTLIB/std/src/fs.rs:LL:CC 11: main at tests/pass/open_a_file_in_proc.rs:LL:CC diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs index 10064bc2bbb3f..23125d116cf19 100644 --- a/src/tools/miri/tests/ui.rs +++ b/src/tools/miri/tests/ui.rs @@ -267,8 +267,6 @@ regexes! { "<[0-9]+=" => " "$1", - // erase generics in backtraces - "([0-9]+: .*)::<.*>" => "$1", // erase long hexadecimals r"0x[0-9a-fA-F]+[0-9a-fA-F]{2,2}" => "$$HEX", // erase specific alignments From 289b5af02c6e3a359d31805e716ddb89732c8f0f Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 7 May 2026 18:20:55 +0200 Subject: [PATCH 20/37] Prepare for merging from rust-lang/rust This updates the rust-version file to 32bd660612bf1c61bdf290a3ec643c8538b8357d. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index ea9beeae52c5d..471ca935dca8f 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -4ddd4538a881317c622ed674b08300b8fc8dabdd +32bd660612bf1c61bdf290a3ec643c8538b8357d From 4a771e4f59cc879dcde9406d5bb2251eef14bffd Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 7 May 2026 18:21:51 +0200 Subject: [PATCH 21/37] remove QueryPerformanceCounter work-around --- src/tools/miri/src/shims/time.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index b2b6e57637d1e..66bf50e5f18d2 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -247,12 +247,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let qpc = i64::try_from(duration.as_nanos()).map_err(|_| { err_unsup_format!("programs running longer than 2^63 nanoseconds are not supported") })?; - // We are allowed to offset this by an arbitrary constant, which will correspond to the - // value of this clock at program start time. We use that freedom to work around - // , caused by std casting the result of - // this function to `u64`. We pick an offset of `i64::MAX/2` instead of `i64::MAX` to avoid - // being too close to overflow for callers that actually do use signed integers. - let qpc = qpc.strict_add(i64::MAX / 2); this.write_scalar( Scalar::from_i64(qpc), From e9862322bd4ffb932d8cb8c89a58a8a214e92a5f Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Fri, 8 May 2026 05:39:43 +0000 Subject: [PATCH 22/37] Prepare for merging from rust-lang/rust This updates the rust-version file to 63b1dfc0e00fd6f8ad7cd8817fc712e7d9b7be59. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 471ca935dca8f..4bb9513424511 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -32bd660612bf1c61bdf290a3ec643c8538b8357d +63b1dfc0e00fd6f8ad7cd8817fc712e7d9b7be59 From bba4079fed1e961f7dc4dd66e4dbc73b6372ab55 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Tue, 21 Apr 2026 22:32:47 +0200 Subject: [PATCH 23/37] Add simple `getsockopt` shim for TTL and SO_ERROR --- .../miri/src/shims/unix/foreign_items.rs | 11 ++ src/tools/miri/src/shims/unix/socket.rs | 162 +++++++++++++++++- .../miri/src/shims/unix/socket_address.rs | 10 +- .../src/shims/unix/solarish/foreign_items.rs | 11 ++ .../libc/libc-socket-no-blocking-epoll.rs | 54 +++++- .../pass-dep/libc/libc-socket-no-blocking.rs | 10 ++ .../miri/tests/pass-dep/libc/libc-socket.rs | 52 ++++++ src/tools/miri/tests/pass-dep/tokio/socket.rs | 56 ++++++ src/tools/miri/tests/pass/shims/socket.rs | 9 + src/tools/miri/tests/utils/libc.rs | 29 ++++ 10 files changed, 393 insertions(+), 11 deletions(-) create mode 100644 src/tools/miri/tests/pass-dep/tokio/socket.rs diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 3a47c9552788d..0766352bab208 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -661,6 +661,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.setsockopt(socket, level, option_name, option_value, option_len)?; this.write_scalar(result, dest)?; } + "getsockopt" => { + let [socket, level, option_name, option_value, option_len] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, i32, i32, *mut _, *mut _) -> i32), + link_name, + abi, + args, + )?; + let result = + this.getsockopt(socket, level, option_name, option_value, option_len)?; + this.write_scalar(result, dest)?; + } "getsockname" => { let [socket, address, address_len] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, *mut _, *mut _) -> i32), diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index ca0ddfd4726ae..853e69c23411d 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -7,6 +7,7 @@ use std::time::Duration; use mio::event::Source; use mio::net::{TcpListener, TcpStream}; +use rustc_abi::Size; use rustc_const_eval::interpret::{InterpResult, interp_ok}; use rustc_middle::throw_unsup_format; use rustc_target::spec::Os; @@ -58,6 +59,8 @@ struct Socket { is_non_block: Cell, /// The current blocking I/O readiness of the file description. io_readiness: RefCell, + /// [`Some`] when the socket had an async error which has not yet been fetched via `SO_ERROR`. + error: RefCell>, } impl FileDescription for Socket { @@ -340,6 +343,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { state: RefCell::new(SocketState::Initial), is_non_block: Cell::new(is_sock_nonblock), io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()), + error: RefCell::new(None), }); interp_ok(Scalar::from_i32(fds.insert(fd))) @@ -950,6 +954,152 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ); } + fn getsockopt( + &mut self, + socket: &OpTy<'tcx>, + level: &OpTy<'tcx>, + option_name: &OpTy<'tcx>, + option_value: &OpTy<'tcx>, + option_len: &OpTy<'tcx>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let socket = this.read_scalar(socket)?.to_i32()?; + let level = this.read_scalar(level)?.to_i32()?; + let option_name = this.read_scalar(option_name)?.to_i32()?; + // These two pointers are used to return the value: `len_ptr` initially stores how much space + // is available. If the actual value fits into that space, it is written to + // `value_ptr` and `len_ptr` is updated to represent how many bytes + // were actually written. If the value does not fit, it is silently truncated. + // Also see . + let option_value_ptr = this.read_pointer(option_value)?; + let option_len_ptr = this.read_pointer(option_len)?; + + // Get the file handle + let Some(fd) = this.machine.fds.get(socket) else { + return this.set_last_error_and_return_i32(LibcError("EBADF")); + }; + + let Some(socket) = fd.downcast::() else { + // Man page specifies to return ENOTSOCK if `fd` is not a socket. + return this.set_last_error_and_return_i32(LibcError("ENOTSOCK")); + }; + + if option_value_ptr == Pointer::null() || option_len_ptr == Pointer::null() { + // This socket option returns a value and thus we need to return EFAULT + // when either the value or the length pointers are null pointers. + return this.set_last_error_and_return_i32(LibcError("EFAULT")); + } + + let socklen_layout = this.libc_ty_layout("socklen_t"); + let option_len_ptr_mplace = this.ptr_to_mplace(option_len_ptr, socklen_layout); + let option_len: usize = this + .read_scalar(&option_len_ptr_mplace)? + .to_int(socklen_layout.size)? + .try_into() + .unwrap(); + + // We need a temporary buffer as `option_value_ptr` might not point to a large enough + // buffer, in which case we have to truncate. + let value_buffer = if level == this.eval_libc_i32("SOL_SOCKET") { + let opt_so_error = this.eval_libc_i32("SO_ERROR"); + + if option_name == opt_so_error { + // Because `TcpStream::take_error()` and `TcpListener::take_error()` consume the latest async + // error, we know that our stored `socket.error` is outdated when `TcpStream::take_error()`/ + // `TcpListener::take_error()` returns `Ok(Some(...))`. + // If they return `Ok(None)`, then we fall back to the stored `socket.error`. + let error = match &*socket.state.borrow() { + SocketState::Initial | SocketState::Bound(_) => socket.error.take(), + SocketState::Listening(listener) => + listener.take_error().unwrap_or(socket.error.take()), + SocketState::Connecting(stream) | SocketState::Connected(stream) => + stream.take_error().unwrap_or(socket.error.take()), + }; + // Clear our own stored error -- it was either `take`n above or it is outdated. + socket.error.replace(None); + + // We know there is no longer an async error and thus we need to update the + // I/O and epoll readiness of the socket. + socket.io_readiness.borrow_mut().error = false; + this.update_epoll_active_events(socket, /* force_edge */ false)?; + + let return_value = match error { + Some(err) => this.io_error_to_errnum(err)?.to_i32()?, + // If there is no error, we write 0 into the option value buffer. + None => 0, + }; + + // Allocate new buffer on the stack with the `i32` layout. + let value_buffer = this.allocate(this.machine.layouts.i32, MemoryKind::Stack)?; + this.write_int(return_value, &value_buffer)?; + value_buffer + } else { + throw_unsup_format!( + "getsockopt: option {option_name:#x} is unsupported for level SOL_SOCKET", + ); + } + } else if level == this.eval_libc_i32("IPPROTO_IP") { + let opt_ip_ttl = this.eval_libc_i32("IP_TTL"); + + if option_name == opt_ip_ttl { + let ttl = match &*socket.state.borrow() { + SocketState::Initial | SocketState::Bound(_) => + throw_unsup_format!( + "getsockopt: reading option IP_TTL on level IPPROTO_IP is only supported \ + on connected and listening sockets" + ), + SocketState::Listening(listener) => listener.ttl(), + SocketState::Connecting(stream) | SocketState::Connected(stream) => + stream.ttl(), + }; + + let ttl = match ttl { + Ok(ttl) => ttl, + Err(e) => return this.set_last_error_and_return_i32(e), + }; + + // Allocate new buffer on the stack with the `u32` layout. + let value_buffer = this.allocate(this.machine.layouts.u32, MemoryKind::Stack)?; + this.write_int(ttl, &value_buffer)?; + value_buffer + } else { + throw_unsup_format!( + "getsockopt: option {option_name:#x} is unsupported for level IPPROTO_IP", + ); + } + } else { + throw_unsup_format!( + "getsockopt: level {level:#x} is unsupported, only SOL_SOCKET is allowed" + ) + }; + + // Truncated size of the output value. + let output_value_len = value_buffer.layout.size.min(Size::from_bytes(option_len)); + // Copy the truncated value into the buffer pointed to by `option_value_ptr`. + this.mem_copy( + value_buffer.ptr(), + option_value_ptr, + // Truncate the value to fit the provided buffer. + output_value_len, + // The buffers are guaranteed to not overlap since the `value_buffer` + // was just newly allocated on the stack. + true, + )?; + // Deallocate the value buffer as it was only needed to store the value and + // copy it into the buffer pointed to by `option_value_ptr`. + this.deallocate_ptr(value_buffer.ptr(), None, MemoryKind::Stack)?; + + // On output, the length pointer contains the amount of bytes written -- not the size + // of the value before truncation. + this.write_scalar( + Scalar::from_uint(output_value_len.bytes(), socklen_layout.size), + &option_len_ptr_mplace, + )?; + + interp_ok(Scalar::from_i32(0)) + } + fn getsockname( &mut self, socket: &OpTy<'tcx>, @@ -1232,6 +1382,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { state: RefCell::new(SocketState::Connected(stream)), is_non_block: Cell::new(is_client_sock_nonblock), io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()), + error: RefCell::new(None), }); // Register the socket to the blocking I/O manager because // there is an associated host socket. @@ -1490,17 +1641,18 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; // Manually check whether there were any errors since calling `connect`. - if let Ok(Some(_)) = stream.take_error() { + if let Ok(Some(err)) = stream.take_error() { // There was an error during connecting and thus we // return ENOTCONN. It's the program's responsibility // to read SO_ERROR itself. - // + + // Store the error such that we can return it when + // `getsockopt(SOL_SOCKET, SO_ERROR, ...)` is called on the socket. + socket.error.replace(Some(err)); + // Go back to initial state since the only way of getting into the // `Connecting` state is from the `Initial` state and at this point // we know that the connection won't be established anymore. - // - // FIXME: We're currently just dropping the error information. Eventually - // we'll have to store it so that it can be recovered by the user. *state = SocketState::Initial; drop(state); return action.call(this, Err(())) diff --git a/src/tools/miri/src/shims/unix/socket_address.rs b/src/tools/miri/src/shims/unix/socket_address.rs index c0f7e8e1720f8..90c316d0d1163 100644 --- a/src/tools/miri/src/shims/unix/socket_address.rs +++ b/src/tools/miri/src/shims/unix/socket_address.rs @@ -272,7 +272,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { .try_into() .unwrap(); - let (address_buffer, address_layout) = match address { + let address_buffer = match address { SocketAddr::V4(address) => { // IPv4 address bytes; already stored in network byte order. let address_bytes = address.ip().octets(); @@ -310,7 +310,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let s_addr_field = this.project_field_named(&sin_addr_field, "s_addr")?; this.write_bytes_ptr(s_addr_field.ptr(), address_bytes)?; - (address_buffer, sockaddr_in_layout) + address_buffer } SocketAddr::V6(address) => { // IPv6 address bytes; already stored in network byte order. @@ -363,7 +363,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let s6_addr_field = this.project_field_named(&sin6_addr_field, "s6_addr")?; this.write_bytes_ptr(s6_addr_field.ptr(), address_bytes)?; - (address_buffer, sockaddr_in6_layout) + address_buffer } }; @@ -372,7 +372,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { address_buffer.ptr(), address_ptr, // Truncate the address to fit the provided buffer. - address_layout.size.min(Size::from_bytes(address_buffer_len)), + address_buffer.layout.size.min(Size::from_bytes(address_buffer_len)), // The buffers are guaranteed to not overlap since the `address_buffer` // was just newly allocated on the stack. true, @@ -381,7 +381,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // copy it into the buffer pointed to by `address_ptr`. this.deallocate_ptr(address_buffer.ptr(), None, MemoryKind::Stack)?; // Size of the non-truncated address. - let address_len = address_layout.size.bytes(); + let address_len = address_buffer.layout.size.bytes(); this.write_scalar( Scalar::from_uint(address_len, socklen_layout.size), diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs index 7f2af20a5a7ea..37b665ceebd1f 100644 --- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs @@ -151,6 +151,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.getaddrinfo(node, service, hints, res)?; this.write_scalar(result, dest)?; } + "__xnet_getsockopt" => { + let [socket, level, option_name, option_value, option_len] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, i32, i32, *mut _, *mut _) -> i32), + link_name, + abi, + args, + )?; + let result = + this.getsockopt(socket, level, option_name, option_value, option_len)?; + this.write_scalar(result, dest)?; + } // Miscellaneous "___errno" => { diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs index b162319d75574..132f1c81d8845 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs @@ -21,6 +21,7 @@ const TEST_BYTES: &[u8] = b"these are some test bytes!"; fn main() { test_connect_nonblock(); test_accept_nonblock(); + test_connect_nonblock_err(); test_recv_nonblock(); #[cfg(not(windows_hosts))] test_send_nonblock(); @@ -60,7 +61,10 @@ fn test_connect_nonblock() { // Wait until we are done connecting. check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLOUT, data: client_sockfd }], -1); - // FIXME: Check SO_ERROR here once we implemented `getsockopt`. + // There should be no error during async connection. + let errno = + net::getsockopt::(client_sockfd, libc::SOL_SOCKET, libc::SO_ERROR).unwrap(); + assert_eq!(errno, 0); // We should now be connected and thus getting the peer name should work. net::sockname_ipv4(|storage, len| unsafe { libc::getpeername(client_sockfd, storage, len) }) @@ -112,6 +116,54 @@ fn test_accept_nonblock() { server_thread.join().unwrap(); } +/// Test that the SO_ERROR socket option is set when attempting to +/// connect to an unbound address without blocking. +fn test_connect_nonblock_err() { + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + + unsafe { + // Change client socket to be non-blocking. + errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + // We cannot attempt to connect to a localhost address because + // it could be the case that a socket from another test is + // currently listening on `localhost:12321` because we bind to + // random ports everywhere. For `127.0.1.1` we know that it's a loopback + // address and thus exists but because it's not the standard loopback + // address we also assume that nothing is bound to it. + // The port `12321` is just a random non-zero port because Windows + // and Apple hosts return EADDRNOTAVAIL when attempting to connect to + // a zero port. + let addr = net::sock_addr_ipv4([127, 0, 1, 1], 12321); + + // Non-blocking connect should fail with EINPROGRESS. + let err = net::connect_ipv4(client_sockfd, addr).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InProgress); + + // Add interest for client socket. + epoll_ctl_add(epfd, client_sockfd, EPOLLOUT | EPOLLET | libc::EPOLLERR).unwrap(); + + // Wait until the socket has an error. + check_epoll_wait::<8>( + epfd, + &[Ev { events: libc::EPOLLERR | EPOLLOUT | EPOLLHUP, data: client_sockfd }], + -1, + ); + + let errno = + net::getsockopt::(client_sockfd, libc::SOL_SOCKET, libc::SO_ERROR).unwrap(); + // Depending on the host we receive different error kinds. Thus, we only check + // that it's a nonzero error code. + assert!(errno != 0); + + // Ensure that error readiness is cleared after reading SO_ERROR. + let readiness = current_epoll_readiness::<8>(client_sockfd, EPOLLET | EPOLLOUT | EPOLLERR); + assert!(readiness & EPOLLERR == 0); +} + /// Test receiving bytes from a connected stream without blocking. /// Instead of busy waiting until we no longer receive EWOULDBLOCK when trying to /// read from the client, we register the client socket to epoll and wait for diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs index 40c821e858cd3..236e72d4b4ece 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs @@ -227,6 +227,11 @@ fn test_connect_nonblock() { assert_eq!(err.kind(), ErrorKind::InProgress); loop { + // There should be no error during async connection. + let errno = net::getsockopt::(client_sockfd, libc::SOL_SOCKET, libc::SO_ERROR) + .unwrap(); + assert_eq!(errno, 0); + let result = net::sockname_ipv4(|storage, len| unsafe { libc::getpeername(client_sockfd, storage, len) }); @@ -699,6 +704,11 @@ fn test_getpeername_ipv4_nonblock_no_peer() { let err = net::connect_ipv4(client_sockfd, addr).unwrap_err(); assert_eq!(err.kind(), ErrorKind::InProgress); + // There should be no error during async connection. + let errno = + net::getsockopt::(client_sockfd, libc::SOL_SOCKET, libc::SO_ERROR).unwrap(); + assert_eq!(errno, 0); + // Since we're never accepting the connection, the socket should never be // successfully connected and thus we should be unable to read the peername. let Err(err) = net::sockname_ipv4(|storage, len| unsafe { 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 33f99371eee69..f05c9c4da0c95 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs @@ -50,6 +50,8 @@ fn main() { test_shutdown(); test_shutdown_readable_after_write_close(); test_shutdown_writable_after_read_close(); + + test_getsockopt_truncate(); } /// Test creating a socket and then closing it afterwards. @@ -640,3 +642,53 @@ fn test_shutdown_writable_after_read_close() { server_thread.join().unwrap(); } + +/// Test that the value gets silently truncated when a too small +/// length is provided and that the length gets reduced when the value +/// is smaller than the provided length. +fn test_getsockopt_truncate() { + let (sockfd, _) = net::make_listener_ipv4().unwrap(); + + // The actual TTL with a correctly sized buffer. + let ttl = net::getsockopt::(sockfd, libc::IPPROTO_IP, libc::IP_TTL).unwrap(); + + let mut option_value = std::mem::MaybeUninit::::zeroed(); + // The actual length is 4 bytes. + let mut short_option_len = 2 as libc::socklen_t; + + errno_result(unsafe { + libc::getsockopt( + sockfd, + libc::IPPROTO_IP, + libc::IP_TTL, + option_value.as_mut_ptr().cast(), + &mut short_option_len, + ) + }) + .unwrap(); + // Ensure that the size wasn't changed. + assert_eq!(short_option_len, 2); + let short_ttl = unsafe { option_value.assume_init() }; + + // Assert that the value was silently truncated. + assert_eq!(short_ttl.to_ne_bytes()[0..2], ttl.to_ne_bytes()[0..2]); + + let mut option_value = std::mem::MaybeUninit::::zeroed(); + // The actual length is 4 bytes. + let mut long_option_len = 6 as libc::socklen_t; + + errno_result(unsafe { + libc::getsockopt( + sockfd, + libc::IPPROTO_IP, + libc::IP_TTL, + option_value.as_mut_ptr().cast(), + &mut long_option_len, + ) + }) + .unwrap(); + // Ensure that the size was shortened to the actual length. + assert_eq!(long_option_len, 4); + let long_ttl = unsafe { option_value.assume_init() }; + assert_eq!(long_ttl, ttl); +} diff --git a/src/tools/miri/tests/pass-dep/tokio/socket.rs b/src/tools/miri/tests/pass-dep/tokio/socket.rs new file mode 100644 index 0000000000000..a8014fa8ce7bb --- /dev/null +++ b/src/tools/miri/tests/pass-dep/tokio/socket.rs @@ -0,0 +1,56 @@ +//@only-target: linux # We only support tokio on Linux +//@compile-flags: -Zmiri-disable-isolation + +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::{TcpListener, TcpStream}; + +const TEST_BYTES: &[u8] = b"these are some test bytes!"; + +#[tokio::main] +async fn main() { + test_accept_and_connect().await; + test_read_write().await; +} + +/// Test connecting and accepting a connection. +async fn test_accept_and_connect() { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + // Get local address with randomized port to know where + // we need to connect to. + let address = listener.local_addr().unwrap(); + + // Start server thread. + tokio::spawn(async move { + let (_stream, _addr) = listener.accept().await.unwrap(); + }); + + let _stream = TcpStream::connect(address).await.unwrap(); +} + +/// Test writing bytes into and reading bytes from a connected stream. +async fn test_read_write() { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + // Get local address with randomized port to know where + // we need to connect to. + let address = listener.local_addr().unwrap(); + + // Start server thread. + tokio::spawn(async move { + let (mut stream, _addr) = listener.accept().await.unwrap(); + + stream.write_all(TEST_BYTES).await.unwrap(); + + let mut buffer = [0; TEST_BYTES.len()]; + stream.read_exact(&mut buffer).await.unwrap(); + + assert_eq!(&buffer, TEST_BYTES); + }); + + let mut stream = TcpStream::connect(address).await.unwrap(); + + let mut buffer = [0; TEST_BYTES.len()]; + stream.read_exact(&mut buffer).await.unwrap(); + assert_eq!(&buffer, TEST_BYTES); + + stream.write_all(TEST_BYTES).await.unwrap(); +} diff --git a/src/tools/miri/tests/pass/shims/socket.rs b/src/tools/miri/tests/pass/shims/socket.rs index 957eefc628a3a..4f448fa44780b 100644 --- a/src/tools/miri/tests/pass/shims/socket.rs +++ b/src/tools/miri/tests/pass/shims/socket.rs @@ -15,6 +15,7 @@ fn main() { test_peek(); test_peer_addr(); test_shutdown(); + test_sockopt_ttl(); } fn test_create_ipv4_listener() { @@ -152,3 +153,11 @@ fn test_shutdown() { let _stream = handle.join().unwrap(); } + +/// Test setting and reading the TTL socket option. +fn test_sockopt_ttl() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + listener.ttl().unwrap(); + + // TODO: Once we support setting the TTL we should also test it here. +} diff --git a/src/tools/miri/tests/utils/libc.rs b/src/tools/miri/tests/utils/libc.rs index 89a610194ccce..8f4c8f4473c50 100644 --- a/src/tools/miri/tests/utils/libc.rs +++ b/src/tools/miri/tests/utils/libc.rs @@ -388,6 +388,35 @@ pub mod net { Ok(()) } + /// Get a socket option. It's the caller's responsibility that `T` is + /// associated with the given socket option. + /// + /// This function is directly copied from the standard library implementation + /// for sockets on UNIX targets. + pub fn getsockopt( + sockfd: libc::c_int, + level: libc::c_int, + option_name: libc::c_int, + ) -> io::Result { + let mut option_value = std::mem::MaybeUninit::::zeroed(); + let mut option_len = size_of::() as libc::socklen_t; + let provided_len = option_len; + + errno_result(unsafe { + libc::getsockopt( + sockfd, + level, + option_name, + option_value.as_mut_ptr().cast(), + &mut option_len, + ) + })?; + // Ensure that there was no truncation. + assert!(option_len == provided_len); + + Ok(unsafe { option_value.assume_init() }) + } + /// Wraps a call to a platform function that returns an IPv4 socket address. /// Returns a tuple containing the actual return value of the performed /// syscall and the written address of it. From c2ab95fb610a124a9980f7ad19bcc0cd02ec66db Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 8 May 2026 16:08:56 +0200 Subject: [PATCH 24/37] Handle --print=backend-has-mnemonic in cg_clif And introduce a has_mnemonic method on CodegenBackend just like --print=backend-has-zstd --- compiler/rustc_codegen_cranelift/src/lib.rs | 5 +++++ compiler/rustc_codegen_llvm/src/lib.rs | 4 ++++ compiler/rustc_codegen_llvm/src/llvm_util.rs | 12 ++++-------- compiler/rustc_codegen_ssa/src/traits/backend.rs | 8 ++++++++ compiler/rustc_driver_impl/src/lib.rs | 4 +++- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/lib.rs b/compiler/rustc_codegen_cranelift/src/lib.rs index 361f143d99913..acfe8188e360e 100644 --- a/compiler/rustc_codegen_cranelift/src/lib.rs +++ b/compiler/rustc_codegen_cranelift/src/lib.rs @@ -200,6 +200,11 @@ impl CodegenBackend for CraneliftCodegenBackend { println!("Cranelift version: {}", cranelift_codegen::VERSION); } + fn has_mnemonic(&self, sess: &Session, mnemonic: &str) -> bool { + // All Cranelift supported targets support ret except for s390x + mnemonic == "ret" && sess.target.arch != Arch::S390x + } + fn target_cpu(&self, sess: &Session) -> String { // FIXME handle `-Ctarget-cpu=native` match sess.opts.cg.target_cpu { diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index b273047b98fc6..e4312ff935c04 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -314,6 +314,10 @@ impl CodegenBackend for LlvmCodegenBackend { llvm::LLVMRustLLVMHasZstdCompression() } + fn has_mnemonic(&self, sess: &Session, mnemonic: &str) -> bool { + llvm_util::target_has_mnemonic(sess, mnemonic) + } + fn target_config(&self, sess: &Session) -> TargetConfig { target_config(sess) } diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index f8b5c99c304b4..10802f8b12a35 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -480,10 +480,6 @@ pub(crate) fn print(req: &PrintRequest, out: &mut String, sess: &Session) { match req.kind { PrintKind::TargetCPUs => print_target_cpus(sess, tm.raw(), out), PrintKind::TargetFeatures => print_target_features(sess, tm.raw(), out), - PrintKind::BackendHasMnemonic => { - let mnemonic = req.arg.as_deref().expect("BackendHasMnemonic requires arg"); - print_target_has_mnemonic(tm.raw(), mnemonic, out) - } _ => bug!("rustc_codegen_llvm can't handle print request: {:?}", req), } } @@ -746,9 +742,9 @@ pub(crate) fn tune_cpu(sess: &Session) -> Option<&str> { Some(handle_native(name)) } -fn print_target_has_mnemonic(tm: &llvm::TargetMachine, mnemonic: &str, out: &mut String) { - use std::fmt::Write; +pub(crate) fn target_has_mnemonic(sess: &Session, mnemonic: &str) -> bool { + require_inited(); + let tm = create_informational_target_machine(sess, false); let cstr = SmallCStr::new(mnemonic); - let has_mnemonic = unsafe { llvm::LLVMRustTargetHasMnemonic(tm, cstr.as_ptr()) }; - writeln!(out, "{}", has_mnemonic).unwrap(); + unsafe { llvm::LLVMRustTargetHasMnemonic(tm.raw(), cstr.as_ptr()) } } diff --git a/compiler/rustc_codegen_ssa/src/traits/backend.rs b/compiler/rustc_codegen_ssa/src/traits/backend.rs index 9898b67b91f78..6ad3447ad69ed 100644 --- a/compiler/rustc_codegen_ssa/src/traits/backend.rs +++ b/compiler/rustc_codegen_ssa/src/traits/backend.rs @@ -92,6 +92,14 @@ pub trait CodegenBackend { false } + /// Value printed by `--print=backend-has-mnemonic:...`. + /// + /// Used by compiletest to determine whether tests involving `asm!()` should + /// be executed or skipped. + fn has_mnemonic(&self, _sess: &Session, _mnemonic: &str) -> bool { + false + } + /// The metadata loader used to load rlib and dylib metadata. /// /// Alternative codegen backends may want to use different rlib or dylib formats than the diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 8d80e742a1b27..aaac65721dfab 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -802,7 +802,9 @@ fn print_crate_info( println_info!("{}", calling_conventions.join("\n")); } BackendHasMnemonic => { - codegen_backend.print(req, &mut crate_info, sess); + let has_mnemonic: bool = + codegen_backend.has_mnemonic(sess, req.arg.as_ref().unwrap()); + println_info!("{has_mnemonic}"); } BackendHasZstd => { let has_zstd: bool = codegen_backend.has_zstd(); From 29852b2c13981e772f4b293ac2778ceed924a335 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Fri, 8 May 2026 16:33:00 +0100 Subject: [PATCH 25/37] cargo: Remove deprecated package authors field --- src/tools/miri/Cargo.toml | 1 - src/tools/miri/bench-cargo-miri/mse/Cargo.toml | 1 - src/tools/miri/bench-cargo-miri/serde1/Cargo.toml | 1 - src/tools/miri/bench-cargo-miri/serde2/Cargo.toml | 1 - src/tools/miri/cargo-miri/Cargo.toml | 1 - src/tools/miri/genmc-sys/Cargo.toml | 1 - src/tools/miri/miri-script/Cargo.toml | 1 - src/tools/miri/test-cargo-miri/Cargo.toml | 1 - src/tools/miri/test-cargo-miri/exported-symbol-dep/Cargo.toml | 1 - src/tools/miri/test-cargo-miri/exported-symbol/Cargo.toml | 1 - src/tools/miri/test-cargo-miri/issue-1567/Cargo.toml | 1 - src/tools/miri/test-cargo-miri/issue-1691/Cargo.toml | 1 - src/tools/miri/test-cargo-miri/issue-1705/Cargo.toml | 1 - src/tools/miri/test-cargo-miri/issue-rust-86261/Cargo.toml | 1 - src/tools/miri/test-cargo-miri/proc-macro-crate/Cargo.toml | 1 - src/tools/miri/test-cargo-miri/subcrate/Cargo.toml | 1 - src/tools/miri/tests/deps/Cargo.toml | 1 - 17 files changed, 17 deletions(-) diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml index 3255885d95be3..6aa93d60e9b6a 100644 --- a/src/tools/miri/Cargo.toml +++ b/src/tools/miri/Cargo.toml @@ -1,5 +1,4 @@ [package] -authors = ["Miri Team"] description = "An experimental interpreter for Rust MIR (core driver)." license = "MIT OR Apache-2.0" name = "miri" diff --git a/src/tools/miri/bench-cargo-miri/mse/Cargo.toml b/src/tools/miri/bench-cargo-miri/mse/Cargo.toml index 7b4c2dc758faa..10f14636092b4 100644 --- a/src/tools/miri/bench-cargo-miri/mse/Cargo.toml +++ b/src/tools/miri/bench-cargo-miri/mse/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "mse" version = "0.1.0" -authors = ["Ralf Jung "] edition = "2018" [dependencies] diff --git a/src/tools/miri/bench-cargo-miri/serde1/Cargo.toml b/src/tools/miri/bench-cargo-miri/serde1/Cargo.toml index 7cb863a7abf33..66429f4974f9c 100644 --- a/src/tools/miri/bench-cargo-miri/serde1/Cargo.toml +++ b/src/tools/miri/bench-cargo-miri/serde1/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "cargo-miri-test" version = "0.1.0" -authors = ["Oliver Schneider "] edition = "2018" [dependencies] diff --git a/src/tools/miri/bench-cargo-miri/serde2/Cargo.toml b/src/tools/miri/bench-cargo-miri/serde2/Cargo.toml index 7cb863a7abf33..66429f4974f9c 100644 --- a/src/tools/miri/bench-cargo-miri/serde2/Cargo.toml +++ b/src/tools/miri/bench-cargo-miri/serde2/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "cargo-miri-test" version = "0.1.0" -authors = ["Oliver Schneider "] edition = "2018" [dependencies] diff --git a/src/tools/miri/cargo-miri/Cargo.toml b/src/tools/miri/cargo-miri/Cargo.toml index dd7e4c12d3a44..d240a5d665f13 100644 --- a/src/tools/miri/cargo-miri/Cargo.toml +++ b/src/tools/miri/cargo-miri/Cargo.toml @@ -1,5 +1,4 @@ [package] -authors = ["Miri Team"] description = "An experimental interpreter for Rust MIR (cargo wrapper)." license = "MIT OR Apache-2.0" name = "cargo-miri" diff --git a/src/tools/miri/genmc-sys/Cargo.toml b/src/tools/miri/genmc-sys/Cargo.toml index 37fcc58070a38..a67d1cc2e77f5 100644 --- a/src/tools/miri/genmc-sys/Cargo.toml +++ b/src/tools/miri/genmc-sys/Cargo.toml @@ -1,5 +1,4 @@ [package] -authors = ["Miri Team"] license = "MIT OR Apache-2.0" name = "genmc-sys" version = "0.1.0" diff --git a/src/tools/miri/miri-script/Cargo.toml b/src/tools/miri/miri-script/Cargo.toml index 39858880e8c60..fd9a563e1fafd 100644 --- a/src/tools/miri/miri-script/Cargo.toml +++ b/src/tools/miri/miri-script/Cargo.toml @@ -1,5 +1,4 @@ [package] -authors = ["Miri Team"] description = "Helpers for miri maintenance" license = "MIT OR Apache-2.0" name = "miri-script" diff --git a/src/tools/miri/test-cargo-miri/Cargo.toml b/src/tools/miri/test-cargo-miri/Cargo.toml index 3f08f802cf422..45168dff155d3 100644 --- a/src/tools/miri/test-cargo-miri/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/Cargo.toml @@ -5,7 +5,6 @@ exclude = ["no-std-smoke"] # it wants to be panic="abort" [package] name = "cargo-miri-test" version = "0.1.0" -authors = ["Miri Team"] edition = "2024" [dependencies] diff --git a/src/tools/miri/test-cargo-miri/exported-symbol-dep/Cargo.toml b/src/tools/miri/test-cargo-miri/exported-symbol-dep/Cargo.toml index 00c41172c3af2..70b7ea29ab873 100644 --- a/src/tools/miri/test-cargo-miri/exported-symbol-dep/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/exported-symbol-dep/Cargo.toml @@ -1,5 +1,4 @@ [package] name = "exported_symbol_dep" version = "0.1.0" -authors = ["Miri Team"] edition = "2018" diff --git a/src/tools/miri/test-cargo-miri/exported-symbol/Cargo.toml b/src/tools/miri/test-cargo-miri/exported-symbol/Cargo.toml index 7c01be1a85f9c..69a74e988b235 100644 --- a/src/tools/miri/test-cargo-miri/exported-symbol/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/exported-symbol/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "exported_symbol" version = "0.1.0" -authors = ["Miri Team"] edition = "2018" [dependencies] diff --git a/src/tools/miri/test-cargo-miri/issue-1567/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-1567/Cargo.toml index 6a6e09036a01d..f502df0e342b1 100644 --- a/src/tools/miri/test-cargo-miri/issue-1567/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/issue-1567/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "issue_1567" version = "0.1.0" -authors = ["Miri Team"] edition = "2018" [lib] diff --git a/src/tools/miri/test-cargo-miri/issue-1691/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-1691/Cargo.toml index 3100cc6a60b58..2ba1cee142739 100644 --- a/src/tools/miri/test-cargo-miri/issue-1691/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/issue-1691/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "issue_1691" version = "0.1.0" -authors = ["Miri Team"] edition = "2018" [lib] diff --git a/src/tools/miri/test-cargo-miri/issue-1705/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-1705/Cargo.toml index ae63647a88819..d11f4433fd49f 100644 --- a/src/tools/miri/test-cargo-miri/issue-1705/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/issue-1705/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "issue_1705" version = "0.1.0" -authors = ["Miri Team"] edition = "2018" [lib] diff --git a/src/tools/miri/test-cargo-miri/issue-rust-86261/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-rust-86261/Cargo.toml index a6b65ebb5318d..39077ac7b6345 100644 --- a/src/tools/miri/test-cargo-miri/issue-rust-86261/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/issue-rust-86261/Cargo.toml @@ -1,5 +1,4 @@ [package] name = "issue_rust_86261" version = "0.1.0" -authors = ["Miri Team"] edition = "2018" diff --git a/src/tools/miri/test-cargo-miri/proc-macro-crate/Cargo.toml b/src/tools/miri/test-cargo-miri/proc-macro-crate/Cargo.toml index 89652f9b04286..f1dc4acb6dff6 100644 --- a/src/tools/miri/test-cargo-miri/proc-macro-crate/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/proc-macro-crate/Cargo.toml @@ -2,7 +2,6 @@ # regression test for issue 1760 name = "proc_macro_crate" version = "0.1.0" -authors = ["Miri Team"] edition = "2018" [lib] diff --git a/src/tools/miri/test-cargo-miri/subcrate/Cargo.toml b/src/tools/miri/test-cargo-miri/subcrate/Cargo.toml index f2f6360f2d219..396e54488489d 100644 --- a/src/tools/miri/test-cargo-miri/subcrate/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/subcrate/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "subcrate" version = "0.1.0" -authors = ["Miri Team"] # This is deliberately *not* on the 2024 edition to ensure doctests keep working # on old editions. edition = "2018" diff --git a/src/tools/miri/tests/deps/Cargo.toml b/src/tools/miri/tests/deps/Cargo.toml index 73b3025212ec5..a4d06b628081c 100644 --- a/src/tools/miri/tests/deps/Cargo.toml +++ b/src/tools/miri/tests/deps/Cargo.toml @@ -1,5 +1,4 @@ [package] -authors = ["Miri Team"] description = "dependencies that unit tests can have" license = "MIT OR Apache-2.0" name = "miri-test-deps" From b2afb173d617d984dabd5e4dc9c8bd7c964f20bb Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 8 May 2026 17:38:49 +0200 Subject: [PATCH 26/37] rename and adjust some libc utils for better consistency with std --- .../fail-dep/libc/libc-epoll-data-race.rs | 8 +- .../libc-read-and-uninit-premature-eof.rs | 9 +- .../libc/libc_epoll_block_two_thread.rs | 2 +- .../libc/socketpair_block_write_twice.rs | 4 +- .../pass-dep/libc/libc-blocking-io-same-fd.rs | 15 ++- .../pass-dep/libc/libc-epoll-blocking.rs | 10 +- .../pass-dep/libc/libc-epoll-no-blocking.rs | 117 ++++++------------ src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 4 +- .../miri/tests/pass-dep/libc/libc-pipe.rs | 28 ++--- .../libc/libc-socket-no-blocking-epoll.rs | 13 +- .../pass-dep/libc/libc-socket-no-blocking.rs | 73 ++++------- .../miri/tests/pass-dep/libc/libc-socket.rs | 23 +--- .../tests/pass-dep/libc/libc-socketpair.rs | 60 ++++----- src/tools/miri/tests/utils/libc.rs | 77 +++++------- 14 files changed, 172 insertions(+), 271 deletions(-) diff --git a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs index 3b1217cda126a..336f39e0512eb 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs @@ -68,14 +68,12 @@ fn main() { let thread1 = spawn(move || { unsafe { VAL_ONE = 41 }; - let data = "abcde".as_bytes().as_ptr(); - let res = unsafe { libc_utils::write_all(fds_a[0], data as *const libc::c_void, 5) }; - assert_eq!(res, 5); + let data = "abcde".as_bytes(); + libc_utils::write_all(fds_a[0], data).unwrap(); unsafe { VAL_TWO = 51 }; - let res = unsafe { libc_utils::write_all(fds_b[0], data as *const libc::c_void, 5) }; - assert_eq!(res, 5); + libc_utils::write_all(fds_b[0], data).unwrap(); }); thread::yield_now(); diff --git a/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs b/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs index e2fd6463a116d..4400d52e59d6c 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs @@ -1,7 +1,8 @@ //! We test that if we requested to read 4 bytes, but actually read 3 bytes, //! then 3 bytes (not 4) will be initialized. //@ignore-target: windows # no file system support on Windows -//@compile-flags: -Zmiri-disable-isolation +// Short FD ops can affect the exact error message here +//@compile-flags: -Zmiri-disable-isolation -Zmiri-no-short-fd-operations use std::ffi::CString; use std::fs::remove_file; @@ -21,9 +22,9 @@ fn main() { let fd = libc::open(cpath.as_ptr(), libc::O_RDONLY); assert_ne!(fd, -1); let mut buf: MaybeUninit<[u8; 4]> = std::mem::MaybeUninit::uninit(); - // Read as much as we can from a 3-byte file. - let res = libc_utils::read_all(fd, buf.as_mut_ptr().cast::(), 4); - assert!(res == 3); + // Do a 4-byte read; this can actually read at most 3 bytes. + let res = libc::read(fd, buf.as_mut_ptr().cast::(), 4); + assert!(res <= 3); buf.assume_init(); //~ERROR: encountered uninitialized memory, but expected an integer assert_eq!(libc::close(fd), 0); } diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs index 8c51416f5aa93..16ac2d643c4e5 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs @@ -79,7 +79,7 @@ fn main() { thread::yield_now(); // Create two events at once. - libc_utils::write_all_from_slice(fd1, &0_u64.to_ne_bytes()).unwrap(); + libc_utils::write_all(fd1, &0_u64.to_ne_bytes()).unwrap(); thread1.join().unwrap(); thread2.join().unwrap(); diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs index 34eab8aaa0462..2f8b5be0c0c57 100644 --- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs +++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs @@ -23,9 +23,7 @@ fn main() { assert_eq!(res, 0); let arr1: [u8; 212992] = [1; 212992]; // Exhaust the space in the buffer so the subsequent write will block. - let res = - unsafe { libc_utils::write_all(fds[0], arr1.as_ptr() as *const libc::c_void, 212992) }; - assert_eq!(res, 212992); + libc_utils::write_all(fds[0], &arr1).unwrap(); let thread1 = thread::spawn(move || { let data = "a".as_bytes(); // The write below will be blocked because the buffer is already full. diff --git a/src/tools/miri/tests/pass-dep/libc/libc-blocking-io-same-fd.rs b/src/tools/miri/tests/pass-dep/libc/libc-blocking-io-same-fd.rs index 01bda8a4f15f6..96af64131f1d1 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-blocking-io-same-fd.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-blocking-io-same-fd.rs @@ -24,16 +24,15 @@ fn main() { thread::yield_now(); let mut buffer = [22u8; 128]; - let bytes_written = unsafe { - errno_result(libc_utils::write_all_generic( + unsafe { + libc_utils::write_all_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::NoRetry, |buf, len| libc::send(peerfd, buf, len, 0), - )) + ) .unwrap() }; - assert_eq!(bytes_written as usize, 128); }); net::connect_ipv4(client_sockfd, addr).unwrap(); @@ -41,12 +40,12 @@ fn main() { let reader_thread = thread::spawn(move || { let mut buffer = [0u8; 8]; unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::NoRetry, |buf, count| libc::recv(client_sockfd, buf, count, 0), - )) + ) .unwrap() }; assert_eq!(&buffer, &[22u8; 8]); @@ -54,12 +53,12 @@ fn main() { let mut buffer = [0u8; 8]; unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::NoRetry, |buf, count| libc::recv(client_sockfd, buf, count, 0), - )) + ) .unwrap() }; assert_eq!(&buffer, &[22u8; 8]); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs index f9615fc6e4146..d9a2be7fc5014 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs @@ -58,7 +58,7 @@ fn test_epoll_block_then_unblock() { // epoll_wait before triggering notification so it will block then get unblocked before timeout. let thread1 = thread::spawn(move || { thread::yield_now(); - write_all_from_slice(fds[1], b"abcde").unwrap(); + write_all(fds[1], b"abcde").unwrap(); }); check_epoll_wait::<1>(epfd, &[Ev { events: libc::EPOLLIN | libc::EPOLLOUT, data: fds[0] }], 10); thread1.join().unwrap(); @@ -83,7 +83,7 @@ fn test_notification_after_timeout() { check_epoll_wait::<1>(epfd, &[], 10); // Trigger epoll notification after timeout. - write_all_from_slice(fds[1], b"abcde").unwrap(); + write_all(fds[1], b"abcde").unwrap(); // Check the result of the notification. check_epoll_wait::<1>(epfd, &[Ev { events: libc::EPOLLIN | libc::EPOLLOUT, data: fds[0] }], 10); @@ -106,7 +106,7 @@ fn test_epoll_race() { // Write to the static mut variable. unsafe { VAL = 1 }; // Write to the eventfd instance. - write_all_from_slice(fd, &1_u64.to_ne_bytes()).unwrap(); + write_all(fd, &1_u64.to_ne_bytes()).unwrap(); }); thread::yield_now(); // epoll_wait for the event to happen. @@ -130,7 +130,7 @@ fn wakeup_on_new_interest() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); // Write to fd[0] - write_all_from_slice(fds[0], b"abcde").unwrap(); + write_all(fds[0], b"abcde").unwrap(); // Block a thread on the epoll instance. let t = std::thread::spawn(move || { @@ -189,7 +189,7 @@ fn multiple_events_wake_multiple_threads() { thread::yield_now(); // Trigger the eventfd. This triggers two events at once! - write_all_from_slice(fd1, &0_u64.to_ne_bytes()).unwrap(); + write_all(fd1, &0_u64.to_ne_bytes()).unwrap(); // Both threads should have been woken up so that both events can be consumed. let e1 = t1.join().unwrap(); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs index 63300c9a433c7..e8f54bf85a2d6 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs @@ -56,10 +56,10 @@ fn test_epoll_socketpair() { let mut fds = [-1, -1]; errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); - // Write to fd[0] - write_all_from_slice(fds[0], b"abcde").unwrap(); + // Write to fds[0] + write_all(fds[0], b"abcde").unwrap(); - // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP + // Register fds[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP epoll_ctl_add(epfd, fds[1], EPOLLIN | EPOLLOUT | EPOLLET | EPOLLRDHUP).unwrap(); // Check result from epoll_wait. @@ -68,10 +68,10 @@ fn test_epoll_socketpair() { // Check that this is indeed using "ET" (edge-trigger) semantics: a second epoll should return nothing. check_epoll_wait_noblock::<8>(epfd, &[]); - // Write some more to fd[0]. - write_all_from_slice(fds[0], b"abcde").unwrap(); + // Write some more to fds[0]. + write_all(fds[0], b"abcde").unwrap(); - // This did not change the readiness of fd[1], so we should get no event. + // This did not change the readiness of fds[1], so we should get no event. // However, Linux seems to always deliver spurious events to the peer on each write, // so we match that. check_epoll_wait_noblock::<8>(epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT }]); @@ -98,7 +98,7 @@ fn test_epoll_ctl_mod() { let mut fds = [-1, -1]; errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); - // Register fd[1] with EPOLLIN|EPOLLET, and data of "0". + // Register fds[1] with EPOLLIN|EPOLLET, and data of "0". epoll_ctl(epfd, EPOLL_CTL_ADD, fds[1], Ev { events: EPOLLIN | EPOLLET, data: 0 }).unwrap(); // Check result from epoll_wait. No notification would be returned. @@ -112,8 +112,8 @@ fn test_epoll_ctl_mod() { // Write to fds[1] and read from fds[0] to make the notification ready again // (relying on there always being an event when the buffer gets emptied). - write_all_from_slice(fds[1], "abc".as_bytes()).unwrap(); - read_all_into_array::<3>(fds[0]).unwrap(); + write_all(fds[1], "abc".as_bytes()).unwrap(); + read_exact_array::<3>(fds[0]).unwrap(); // Now that the event is already ready, change the "data" value. epoll_ctl(epfd, EPOLL_CTL_MOD, fds[1], Ev { events: EPOLLOUT | EPOLLET, data: 2 }).unwrap(); @@ -136,12 +136,10 @@ fn test_epoll_ctl_del() { let mut fds = [-1, -1]; errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); - // Write to fd[0] - let data = b"abcde".as_ptr(); - let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) }; - assert_eq!(res, 5); + // Write to fds[0] + libc_utils::write_all(fds[0], b"abcde").unwrap(); - // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET + // Register fds[1] with EPOLLIN|EPOLLOUT|EPOLLET let mut ev = libc::epoll_event { events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET) as u32, u64: u64::try_from(fds[1]).unwrap(), @@ -168,9 +166,7 @@ fn test_two_epoll_instance() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); // Write to the socketpair. - let data = b"abcde".as_ptr(); - let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) }; - assert_eq!(res, 5); + libc_utils::write_all(fds[0], b"abcde").unwrap(); // Register one side of the socketpair with EPOLLIN | EPOLLOUT | EPOLLET. epoll_ctl_add(epfd1, fds[1], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); @@ -208,9 +204,7 @@ fn test_two_same_fd_in_same_epoll_instance() { assert_eq!(res, 0); // Write to the socketpair. - let data = b"abcde".as_ptr(); - let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) }; - assert_eq!(res, 5); + libc_utils::write_all(fds[0], b"abcde").unwrap(); // Two notification should be received. let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap(); @@ -227,7 +221,7 @@ fn test_epoll_eventfd() { let fd = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap(); // Write 1 to the eventfd instance. - libc_utils::write_all_from_slice(fd, &1_u64.to_ne_bytes()).unwrap(); + libc_utils::write_all(fd, &1_u64.to_ne_bytes()).unwrap(); // Create an epoll instance. let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); @@ -241,14 +235,14 @@ fn test_epoll_eventfd() { check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); // Write 0 to the eventfd. - libc_utils::write_all_from_slice(fd, &0_u64.to_ne_bytes()).unwrap(); + libc_utils::write_all(fd, &0_u64.to_ne_bytes()).unwrap(); // This does not change the status, so we should get no event. // However, Linux performs a spurious wakeup. check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); // Read from the eventfd. - libc_utils::read_all_into_array::<8>(fd).unwrap(); + libc_utils::read_exact_array::<8>(fd).unwrap(); // This consumes the event, so the read status is gone. However, deactivation // does not trigger an event. @@ -257,9 +251,7 @@ fn test_epoll_eventfd() { check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); // Write the maximum possible value. - let sized_8_data: [u8; 8] = (u64::MAX - 1).to_ne_bytes(); - let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) }; - assert_eq!(res, 8); + libc_utils::write_all(fd, &(u64::MAX - 1).to_ne_bytes()).unwrap(); // This reactivates reads, therefore triggering an event. Writing is no longer possible. let expected_event = u32::try_from(libc::EPOLLIN).unwrap(); @@ -282,9 +274,7 @@ fn test_epoll_socketpair_both_sides() { // Write to fds[1]. // (We do the write after the register here, unlike in `test_epoll_socketpair`, to ensure // we cover both orders in which this could be done.) - let data = b"abcde".as_ptr(); - let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) }; - assert_eq!(res, 5); + libc_utils::write_all(fds[1], b"abcde").unwrap(); // Two notification should be received. let expected_event0 = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap(); @@ -297,10 +287,7 @@ fn test_epoll_socketpair_both_sides() { ); // Read from fds[0]. - let mut buf: [u8; 5] = [0; 5]; - let res = - unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) }; - assert_eq!(res, 5); + let buf = libc_utils::read_exact_array::<5>(fds[0]).unwrap(); assert_eq!(buf, *b"abcde"); // The state of fds[1] does not change (was writable, is writable). @@ -323,9 +310,7 @@ fn test_closed_fd() { epoll_ctl_add(epfd, fd, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); // Write to the eventfd instance. - let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes(); - let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) }; - assert_eq!(res, 8); + libc_utils::write_all(fd, &1_u64.to_ne_bytes()).unwrap(); // Close the eventfd. errno_check(unsafe { libc::close(fd) }); @@ -363,10 +348,7 @@ fn test_not_fully_closed_fd() { check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)]); // Write to the eventfd instance to produce notification. - let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes(); - let res = - unsafe { libc_utils::write_all(newfd, sized_8_data.as_ptr() as *const libc::c_void, 8) }; - assert_eq!(res, 8); + libc_utils::write_all(newfd, &1_u64.to_ne_bytes()).unwrap(); // Close the dupped fd. errno_check(unsafe { libc::close(newfd) }); @@ -383,9 +365,7 @@ fn test_event_overwrite() { errno_result(unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) }).unwrap(); // Write to the eventfd instance. - let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes(); - let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) }; - assert_eq!(res, 8); + libc_utils::write_all(fd, &1_u64.to_ne_bytes()).unwrap(); // Create an epoll instance. let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); @@ -435,9 +415,7 @@ fn test_socketpair_read() { // Write a bunch of data bytes to fds[1]. let data = [42u8; 1024]; - let res = - unsafe { libc_utils::write_all(fds[1], data.as_ptr() as *const libc::c_void, data.len()) }; - assert_eq!(res, data.len() as isize); + libc_utils::write_all(fds[1], &data).unwrap(); // Two notification should be received. let expected_event0 = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap(); @@ -451,9 +429,7 @@ fn test_socketpair_read() { // Read some of the data from fds[0]. let mut buf = [0; 512]; - let res = - unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) }; - assert_eq!(res, buf.len() as isize); + libc_utils::read_exact(fds[0], &mut buf).unwrap(); // fds[1] did not change, it is still writable, so we get no event. let expected_event = u32::try_from(libc::EPOLLOUT).unwrap(); @@ -462,9 +438,7 @@ fn test_socketpair_read() { // Read until the buffer is empty. let rest = data.len() - buf.len(); - let res = - unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), rest as libc::size_t) }; - assert_eq!(res, rest as isize); + libc_utils::read_exact(fds[0], &mut buf[..rest]).unwrap(); // Now we get a notification that fds[1] can be written. This is spurious since it // could already be written before, but Linux seems to always emit a notification for @@ -481,7 +455,7 @@ fn test_no_notification_for_unregister_flag() { let mut fds = [-1, -1]; errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); - // Register fd[0] with EPOLLOUT|EPOLLET. + // Register fds[0] with EPOLLOUT|EPOLLET. let mut ev = libc::epoll_event { events: (libc::EPOLLOUT | libc::EPOLLET).cast_unsigned(), u64: u64::try_from(fds[0]).unwrap(), @@ -489,12 +463,8 @@ fn test_no_notification_for_unregister_flag() { let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) }; assert_eq!(res, 0); - // Write to fd[1]. - let data = b"abcde".as_ptr(); - let res: i32 = unsafe { - libc_utils::write_all(fds[1], data as *const libc::c_void, 5).try_into().unwrap() - }; - assert_eq!(res, 5); + // Write to fds[1]. + libc_utils::write_all(fds[1], b"abcde").unwrap(); // Check result from epoll_wait. Since we didn't register EPOLLIN flag, the notification won't // contain EPOLLIN even though fds[0] is now readable. @@ -523,16 +493,14 @@ fn test_socketpair_epollerr() { let mut fds = [-1, -1]; errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); - // Write to fd[0] - let data = b"abcde".as_ptr(); - let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) }; - assert_eq!(res, 5); + // Write to fds[0] + libc_utils::write_all(fds[0], b"abcde").unwrap(); // Close fds[1]. // EPOLLERR will be triggered if we close peer fd that still has data in its read buffer. errno_check(unsafe { libc::close(fds[1]) }); - // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP + // Register fds[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP epoll_ctl_add(epfd, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET | libc::EPOLLRDHUP) .unwrap(); @@ -671,9 +639,7 @@ fn test_issue_3858() { errno_check(unsafe { libc::close(epfd) }); // Write to the eventfd instance. - let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes(); - let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) }; - assert_eq!(res, 8); + libc_utils::write_all(fd, &1_u64.to_ne_bytes()).unwrap(); } /// Ensure that if a socket becomes un-writable, we don't see it any more. @@ -694,10 +660,9 @@ fn test_issue_4374() { // Fill up fds[0] so that it is not writable any more. let zeros = [0u8; 512]; loop { - let res = unsafe { - libc_utils::write_all(fds[0], zeros.as_ptr() as *const libc::c_void, zeros.len()) - }; - if res < 0 { + let res = libc_utils::write_all(fds[0], &zeros); + if let Err(err) = res { + assert_eq!(err.kind(), std::io::ErrorKind::WouldBlock); break; } } @@ -719,19 +684,13 @@ fn test_issue_4374_reads() { assert_eq!(unsafe { libc::fcntl(fds[1], libc::F_SETFL, libc::O_NONBLOCK) }, 0); // Write to fds[1] so that fds[0] becomes readable. - let data = b"abcde".as_ptr(); - let res: i32 = unsafe { - libc_utils::write_all(fds[1], data as *const libc::c_void, 5).try_into().unwrap() - }; - assert_eq!(res, 5); + libc_utils::write_all(fds[1], b"abcde").unwrap(); // Register fds[0] with epoll while it is readable. epoll_ctl_add(epfd0, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); // Read fds[0] so it is no longer readable. - let mut buf = [0u8; 512]; - let res = unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr() as *mut libc::c_void, 5) }; - assert_eq!(res, 5); + libc_utils::read_exact_array::<5>(fds[0]).unwrap(); // We should now still see a notification, but only about it being writable. let expected_event = u32::try_from(libc::EPOLLOUT).unwrap(); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index b2402ac482ffa..23f2bf66f7d3f 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -221,8 +221,8 @@ fn test_dup_stdout_stderr() { unsafe { let new_stdout = libc::fcntl(1, libc::F_DUPFD, 0); let new_stderr = libc::fcntl(2, libc::F_DUPFD, 0); - libc_utils::write_all(new_stdout, bytes.as_ptr() as *const libc::c_void, bytes.len()); - libc_utils::write_all(new_stderr, bytes.as_ptr() as *const libc::c_void, bytes.len()); + libc_utils::write_all(new_stdout, bytes).unwrap(); + libc_utils::write_all(new_stderr, bytes).unwrap(); } } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs b/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs index 8f8d4f85c0109..98d7340fa9db3 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs @@ -31,19 +31,19 @@ fn test_pipe() { // Read size == data available in buffer. let data = b"12345"; - write_all_from_slice(fds[1], data).unwrap(); - let buf3 = read_all_into_array::<5>(fds[0]).unwrap(); + write_all(fds[1], data).unwrap(); + let buf3 = read_exact_array::<5>(fds[0]).unwrap(); assert_eq!(&buf3, data); // Read size > data available in buffer. let data = b"123"; - write_all_from_slice(fds[1], data).unwrap(); + write_all(fds[1], data).unwrap(); let mut buf4: [u8; 5] = [0; 5]; - let (part1, rest) = read_into_slice(fds[0], &mut buf4).unwrap(); + let (part1, rest) = read_split_slice(fds[0], &mut buf4).unwrap(); assert_eq!(part1[..], data[..part1.len()]); // Write 2 more bytes so we can exactly fill the `rest`. - write_all_from_slice(fds[1], b"34").unwrap(); - read_all_into_slice(fds[0], rest).unwrap(); + write_all(fds[1], b"34").unwrap(); + read_exact(fds[0], rest).unwrap(); } fn test_pipe_threaded() { @@ -51,19 +51,19 @@ fn test_pipe_threaded() { errno_check(unsafe { libc::pipe(fds.as_mut_ptr()) }); let thread1 = thread::spawn(move || { - let buf = read_all_into_array::<5>(fds[0]).unwrap(); + let buf = read_exact_array::<5>(fds[0]).unwrap(); assert_eq!(&buf, b"abcde"); }); thread::yield_now(); - write_all_from_slice(fds[1], b"abcde").unwrap(); + write_all(fds[1], b"abcde").unwrap(); thread1.join().unwrap(); // Read and write from different direction let thread2 = thread::spawn(move || { thread::yield_now(); - write_all_from_slice(fds[1], b"12345").unwrap(); + write_all(fds[1], b"12345").unwrap(); }); - let buf = read_all_into_array::<5>(fds[0]).unwrap(); + let buf = read_exact_array::<5>(fds[0]).unwrap(); assert_eq!(&buf, b"12345"); thread2.join().unwrap(); } @@ -77,13 +77,13 @@ fn test_race() { let thread1 = thread::spawn(move || { // write() from the main thread will occur before the read() here // because preemption is disabled and the main thread yields after write(). - let buf = read_all_into_array::<1>(fds[0]).unwrap(); + let buf = read_exact_array::<1>(fds[0]).unwrap(); assert_eq!(&buf, b"a"); // The read above establishes a happens-before so it is now safe to access this global variable. unsafe { assert_eq!(VAL, 1) }; }); unsafe { VAL = 1 }; - write_all_from_slice(fds[1], b"a").unwrap(); + write_all(fds[1], b"a").unwrap(); thread::yield_now(); thread1.join().unwrap(); } @@ -190,10 +190,10 @@ fn test_pipe_fcntl_threaded() { // The write below will unblock the `read` in main thread: even though // the socket is now "non-blocking", the shim needs to deal correctly // with threads that were blocked before the socket was made non-blocking. - write_all_from_slice(fds[1], b"abcde").unwrap(); + write_all(fds[1], b"abcde").unwrap(); }); // The `read` below will block. - let buf = read_all_into_array::<5>(fds[0]).unwrap(); + let buf = read_exact_array::<5>(fds[0]).unwrap(); thread1.join().unwrap(); assert_eq!(&buf, b"abcde"); } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs index b162319d75574..c788aa939e3c3 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs @@ -131,14 +131,7 @@ fn test_recv_nonblock() { // Yield back to client so that it starts receiving before we start sending. thread::sleep(Duration::from_millis(10)); - unsafe { - errno_result(libc_utils::write_all( - peerfd, - TEST_BYTES.as_ptr().cast(), - TEST_BYTES.len(), - )) - .unwrap() - }; + libc_utils::write_all(peerfd, TEST_BYTES).unwrap(); }); net::connect_ipv4(client_sockfd, addr).unwrap(); @@ -272,12 +265,12 @@ fn test_send_nonblock() { let mut buffer = Vec::with_capacity(total_written / 2); buffer.fill(0u8); unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), total_written / 2, libc_utils::NoRetry, |buf, count| libc::recv(peerfd, buf, count, 0), - )) + ) .unwrap() }; }); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs index 40c821e858cd3..88c961863c774 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs @@ -262,12 +262,12 @@ fn test_send_recv_nonblock() { thread::sleep(Duration::from_millis(10)); unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( TEST_BYTES.as_ptr().cast(), TEST_BYTES.len(), libc_utils::NoRetry, |buf, count| libc::send(peerfd, buf, count, 0), - )) + ) .unwrap() }; @@ -275,12 +275,12 @@ fn test_send_recv_nonblock() { // This will block until the client sent us this data. let mut buffer = [0; TEST_BYTES.len()]; unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::NoRetry, |buf, count| libc::recv(peerfd, buf, count, 0), - )) + ) .unwrap() }; assert_eq!(&buffer, TEST_BYTES); @@ -309,12 +309,12 @@ fn test_send_recv_nonblock() { // sleep multiple times until we received everything. unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::RetryAfter(Duration::from_millis(10)), |buf, count| libc::recv(client_sockfd, buf, count, 0), - )) + ) .unwrap() }; assert_eq!(&buffer, TEST_BYTES); @@ -323,12 +323,12 @@ fn test_send_recv_nonblock() { // Sending into the empty buffer should succeed without blocking. unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( TEST_BYTES.as_ptr().cast(), TEST_BYTES.len(), libc_utils::NoRetry, |buf, count| libc::send(client_sockfd, buf, count, 0), - )) + ) .unwrap() }; @@ -340,12 +340,12 @@ fn test_send_recv_nonblock() { let fill_buf = [1u8; 5_000_000]; // This fills the socket receive buffer and thus should start blocking. let err = unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( fill_buf.as_ptr().cast(), fill_buf.len(), libc_utils::NoRetry, |buf, count| libc::send(client_sockfd, buf, count, 0), - )) + ) .unwrap_err() }; assert_eq!(err.kind(), ErrorKind::WouldBlock) @@ -378,12 +378,12 @@ fn test_send_recv_dontwait() { thread::sleep(Duration::from_millis(10)); unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( TEST_BYTES.as_ptr().cast(), TEST_BYTES.len(), libc_utils::NoRetry, |buf, count| libc::send(peerfd, buf, count, 0), - )) + ) .unwrap() }; @@ -391,12 +391,12 @@ fn test_send_recv_dontwait() { // This will block until the client sent us this data. let mut buffer = [0; TEST_BYTES.len()]; unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::NoRetry, |buf, count| libc::recv(peerfd, buf, count, 0), - )) + ) .unwrap() }; assert_eq!(&buffer, TEST_BYTES); @@ -425,12 +425,12 @@ fn test_send_recv_dontwait() { // sleep multiple times until we received everything. unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::RetryAfter(Duration::from_millis(10)), |buf, count| libc::recv(client_sockfd, buf, count, libc::MSG_DONTWAIT), - )) + ) .unwrap() }; assert_eq!(&buffer, TEST_BYTES); @@ -439,12 +439,12 @@ fn test_send_recv_dontwait() { // Sending into the empty buffer should succeed without blocking. unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( TEST_BYTES.as_ptr().cast(), TEST_BYTES.len(), libc_utils::NoRetry, |buf, count| libc::send(client_sockfd, buf, count, libc::MSG_DONTWAIT), - )) + ) .unwrap() }; @@ -456,12 +456,12 @@ fn test_send_recv_dontwait() { let fill_buf = [1u8; 5_000_000]; // This fills the socket receive buffer and thus should start blocking. let err = unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( fill_buf.as_ptr().cast(), fill_buf.len(), libc_utils::NoRetry, |buf, count| libc::send(client_sockfd, buf, count, libc::MSG_DONTWAIT), - )) + ) .unwrap_err() }; assert_eq!(err.kind(), ErrorKind::WouldBlock) @@ -484,23 +484,12 @@ fn test_write_read_nonblock() { // Yield back to client so that it starts receiving before we start sending. thread::sleep(Duration::from_millis(10)); - let bytes_written = unsafe { - errno_result(libc_utils::write_all( - peerfd, - TEST_BYTES.as_ptr().cast(), - TEST_BYTES.len(), - )) - .unwrap() - }; - assert_eq!(bytes_written as usize, TEST_BYTES.len()); + libc_utils::write_all(peerfd, TEST_BYTES).unwrap(); // The buffer should contain `TEST_BYTES` at the beginning. // This will block until the client sent us this data. let mut buffer = [0; TEST_BYTES.len()]; - unsafe { - errno_result(libc_utils::read_all(peerfd, buffer.as_mut_ptr().cast(), buffer.len())) - .unwrap() - }; + libc_utils::read_exact(peerfd, &mut buffer).unwrap(); assert_eq!(&buffer, TEST_BYTES); }); @@ -531,12 +520,12 @@ fn test_write_read_nonblock() { // sleep multiple times until we read everything. unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::RetryAfter(Duration::from_millis(10)), |buf, count| libc::read(client_sockfd, buf, count), - )) + ) .unwrap() }; assert_eq!(&buffer, TEST_BYTES); @@ -544,15 +533,7 @@ fn test_write_read_nonblock() { // Now we test non-blocking writing. // Writing into the empty buffer should succeed without blocking. - let bytes_written = unsafe { - errno_result(libc_utils::write_all( - client_sockfd, - TEST_BYTES.as_ptr().cast(), - TEST_BYTES.len(), - )) - .unwrap() - }; - assert_eq!(bytes_written as usize, TEST_BYTES.len()); + libc_utils::write_all(client_sockfd, TEST_BYTES).unwrap(); if !cfg!(windows_host) { // Keep sending data until the buffer is full and we block. @@ -562,12 +543,12 @@ fn test_write_read_nonblock() { let fill_buf = [1u8; 5_000_000]; // This fills the socket receive buffer and thus should start blocking. let err = unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( fill_buf.as_ptr().cast(), fill_buf.len(), libc_utils::NoRetry, |buf, count| libc::write(client_sockfd, buf, count), - )) + ) .unwrap_err() }; assert_eq!(err.kind(), ErrorKind::WouldBlock) 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 33f99371eee69..2175a21511ebb 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs @@ -269,12 +269,12 @@ fn test_send_peek_recv() { // Write the bytes into the stream. unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( TEST_BYTES.as_ptr().cast(), TEST_BYTES.len(), libc_utils::NoRetry, |buf, count| libc::send(peerfd, buf, count, 0), - )) + ) .unwrap() }; }); @@ -301,12 +301,12 @@ fn test_send_peek_recv() { let mut buffer = [0; TEST_BYTES.len()]; unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::NoRetry, |buf, count| libc::recv(client_sockfd, buf, count, 0), - )) + ) .unwrap() }; assert_eq!(&buffer, TEST_BYTES); @@ -328,24 +328,13 @@ fn test_write_read() { let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap(); // Write some bytes into the stream. - let bytes_written = unsafe { - errno_result(libc_utils::write_all( - peerfd, - TEST_BYTES.as_ptr().cast(), - TEST_BYTES.len(), - )) - .unwrap() - }; - assert_eq!(bytes_written as usize, TEST_BYTES.len()); + libc_utils::write_all(peerfd, TEST_BYTES).unwrap(); }); net::connect_ipv4(client_sockfd, addr).unwrap(); let mut buffer = [0; TEST_BYTES.len()]; - unsafe { - errno_result(libc_utils::read_all(client_sockfd, buffer.as_mut_ptr().cast(), buffer.len())) - .unwrap() - }; + libc_utils::read_exact(client_sockfd, &mut buffer).unwrap(); assert_eq!(&buffer, TEST_BYTES); server_thread.join().unwrap(); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs b/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs index 20424fc86dc2b..da521600d84a6 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs @@ -26,46 +26,50 @@ fn test_socketpair() { // Read size == data available in buffer. let data = b"abcde"; - write_all_from_slice(fds[0], data).unwrap(); - let buf = read_all_into_array::<5>(fds[1]).unwrap(); + write_all(fds[0], data).unwrap(); + let buf = read_exact_array::<5>(fds[1]).unwrap(); assert_eq!(&buf, data); // Read size > data available in buffer. let data = b"abc"; - write_all_from_slice(fds[0], data).unwrap(); + write_all(fds[0], data).unwrap(); let mut buf2: [u8; 5] = [0; 5]; - let (read, rest) = read_into_slice(fds[1], &mut buf2).unwrap(); + let (read, rest) = read_split_slice(fds[1], &mut buf2).unwrap(); assert_eq!(read[..], data[..read.len()]); // Write 2 more bytes so we can exactly fill the `rest`. - write_all_from_slice(fds[0], b"12").unwrap(); - read_all_into_slice(fds[1], rest).unwrap(); + write_all(fds[0], b"12").unwrap(); + read_exact(fds[1], rest).unwrap(); + assert_eq!(&buf2, b"abc12"); // Test read and write from another direction. // Read size == data available in buffer. let data = b"12345"; - write_all_from_slice(fds[1], data).unwrap(); - let buf3 = read_all_into_array::<5>(fds[0]).unwrap(); + write_all(fds[1], data).unwrap(); + let buf3 = read_exact_array::<5>(fds[0]).unwrap(); assert_eq!(&buf3, data); // Read size > data available in buffer. - let data = b"123"; - write_all_from_slice(fds[1], data).unwrap(); + let data = b"abc"; + write_all(fds[1], data).unwrap(); let mut buf4: [u8; 5] = [0; 5]; - let (read, rest) = read_into_slice(fds[0], &mut buf4).unwrap(); + let (read, rest) = read_split_slice(fds[0], &mut buf4).unwrap(); assert_eq!(read[..], data[..read.len()]); // Write 2 more bytes so we can exactly fill the `rest`. - write_all_from_slice(fds[1], b"12").unwrap(); - read_all_into_slice(fds[0], rest).unwrap(); + write_all(fds[1], b"12").unwrap(); + read_exact(fds[0], rest).unwrap(); + assert_eq!(&buf4, b"abc12"); // Test when happens when we close one end, with some data in the buffer. - write_all_from_slice(fds[0], data).unwrap(); + write_all(fds[0], data).unwrap(); errno_check(unsafe { libc::close(fds[0]) }); // Reading the other end should return that data, then EOF. let mut buf: [u8; 5] = [0; 5]; - let (res, _) = read_until_eof_into_slice(fds[1], &mut buf).unwrap(); - assert_eq!(res, data); + let (read, _tail) = read_split_slice(fds[1], &mut buf).unwrap(); + assert_eq!(read, data); + let (read, _tail) = read_split_slice(fds[1], &mut buf).unwrap(); + assert_eq!(read, &[]); // Writing the other end should emit EPIPE. - let err = write_all_from_slice(fds[1], &mut buf).unwrap_err(); + let err = write_all(fds[1], &mut buf).unwrap_err(); assert_eq!(err.raw_os_error(), Some(libc::EPIPE)); } @@ -74,19 +78,19 @@ fn test_socketpair_threaded() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); let thread1 = thread::spawn(move || { - let buf = read_all_into_array::<5>(fds[1]).unwrap(); + let buf = read_exact_array::<5>(fds[1]).unwrap(); assert_eq!(&buf, b"abcde"); }); thread::yield_now(); - write_all_from_slice(fds[0], b"abcde").unwrap(); + write_all(fds[0], b"abcde").unwrap(); thread1.join().unwrap(); // Read and write from different direction let thread2 = thread::spawn(move || { thread::yield_now(); - write_all_from_slice(fds[1], b"12345").unwrap(); + write_all(fds[1], b"12345").unwrap(); }); - let buf = read_all_into_array::<5>(fds[0]).unwrap(); + let buf = read_exact_array::<5>(fds[0]).unwrap(); assert_eq!(&buf, b"12345"); thread2.join().unwrap(); } @@ -98,13 +102,13 @@ fn test_race() { let thread1 = thread::spawn(move || { // write() from the main thread will occur before the read() here // because preemption is disabled and the main thread yields after write(). - let buf = read_all_into_array::<1>(fds[1]).unwrap(); + let buf = read_exact_array::<1>(fds[1]).unwrap(); assert_eq!(&buf, b"a"); // The read above establishes a happens-before so it is now safe to access this global variable. unsafe { assert_eq!(VAL, 1) }; }); unsafe { VAL = 1 }; - write_all_from_slice(fds[0], b"a").unwrap(); + write_all(fds[0], b"a").unwrap(); thread::yield_now(); thread1.join().unwrap(); } @@ -115,12 +119,12 @@ fn test_blocking_read() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); let thread1 = thread::spawn(move || { // Let this thread block on read. - let buf = read_all_into_array::<3>(fds[1]).unwrap(); + let buf = read_exact_array::<3>(fds[1]).unwrap(); assert_eq!(&buf, b"abc"); }); let thread2 = thread::spawn(move || { // Unblock thread1 by doing writing something. - write_all_from_slice(fds[0], b"abc").unwrap(); + write_all(fds[0], b"abc").unwrap(); }); thread1.join().unwrap(); thread2.join().unwrap(); @@ -132,14 +136,14 @@ fn test_blocking_write() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); let arr1: [u8; 0x34000] = [1; 0x34000]; // Exhaust the space in the buffer so the subsequent write will block. - write_all_from_slice(fds[0], &arr1).unwrap(); + write_all(fds[0], &arr1).unwrap(); let thread1 = thread::spawn(move || { // The write below will be blocked because the buffer is already full. - write_all_from_slice(fds[0], b"abc").unwrap(); + write_all(fds[0], b"abc").unwrap(); }); let thread2 = thread::spawn(move || { // Unblock thread1 by freeing up some space. - let buf = read_all_into_array::<3>(fds[1]).unwrap(); + let buf = read_exact_array::<3>(fds[1]).unwrap(); assert_eq!(buf, [1, 1, 1]); }); thread1.join().unwrap(); diff --git a/src/tools/miri/tests/utils/libc.rs b/src/tools/miri/tests/utils/libc.rs index 89a610194ccce..a694e9149e895 100644 --- a/src/tools/miri/tests/utils/libc.rs +++ b/src/tools/miri/tests/utils/libc.rs @@ -32,12 +32,14 @@ pub fn errno_check + Ord + fmt::Debug>(ret: T) { } /// Invoke the `read` function until `buf` is full. `retry` contols the behavior on EAGAIN. -pub unsafe fn read_all_generic( +/// Panics if we get EOF before the buffer is filled. +#[track_caller] +pub unsafe fn read_exact_generic( buf: *mut libc::c_void, count: libc::size_t, retry: Retry, read: impl Fn(*mut libc::c_void, libc::size_t) -> libc::ssize_t, -) -> libc::ssize_t { +) -> io::Result<()> { assert!(count > 0); let mut read_so_far = 0; while read_so_far < count { @@ -50,68 +52,52 @@ pub unsafe fn read_all_generic( continue; } } - return res; + return Err(io::Error::last_os_error()); } if res == 0 { - // EOF - break; + // We expected more data but got EOF. + panic!( + "could not fill buffer with {count} bytes: EOF received after {read_so_far} bytes" + ); } read_so_far += res as libc::size_t; } - return read_so_far as libc::ssize_t; -} - -/// Read from `fd` until `buf` is full. Abort on first error. -pub unsafe fn read_all( - fd: libc::c_int, - buf: *mut libc::c_void, - count: libc::size_t, -) -> libc::ssize_t { - read_all_generic(buf, count, NoRetry, |buf, count| libc::read(fd, buf, count)) + Ok(()) } /// Try to fill the given slice by reading from `fd`. Panic if that many bytes could not be read. #[track_caller] -pub fn read_all_into_slice(fd: libc::c_int, buf: &mut [u8]) -> io::Result<()> { - let res = errno_result(unsafe { read_all(fd, buf.as_mut_ptr().cast(), buf.len()) })?; - assert_eq!(res as usize, buf.len()); - Ok(()) +pub fn read_exact(fd: libc::c_int, buf: &mut [u8]) -> io::Result<()> { + unsafe { + read_exact_generic(buf.as_mut_ptr().cast(), buf.len(), NoRetry, |buf, count| { + libc::read(fd, buf, count) + }) + } } /// Read exactly `N` bytes from `fd`. Error if that many bytes could not be read. #[track_caller] -pub fn read_all_into_array(fd: libc::c_int) -> io::Result<[u8; N]> { +pub fn read_exact_array(fd: libc::c_int) -> io::Result<[u8; N]> { let mut buf = [0; N]; - read_all_into_slice(fd, &mut buf)?; + read_exact(fd, &mut buf)?; Ok(buf) } /// Do a single read from `fd` and return the part of the buffer that was written into, /// and the rest. #[track_caller] -pub fn read_into_slice(fd: libc::c_int, buf: &mut [u8]) -> io::Result<(&mut [u8], &mut [u8])> { +pub fn read_split_slice(fd: libc::c_int, buf: &mut [u8]) -> io::Result<(&mut [u8], &mut [u8])> { let res = errno_result(unsafe { libc::read(fd, buf.as_mut_ptr().cast(), buf.len()) })?; Ok(buf.split_at_mut(res as usize)) } -/// Read from `fd` until we get EOF and return the part of the buffer that was written into, -/// and the rest. -#[track_caller] -pub fn read_until_eof_into_slice( - fd: libc::c_int, - buf: &mut [u8], -) -> io::Result<(&mut [u8], &mut [u8])> { - let res = errno_result(unsafe { read_all(fd, buf.as_mut_ptr().cast(), buf.len()) })?; - Ok(buf.split_at_mut(res as usize)) -} - /// Invoke the `write` function until `buf` is full. `retry` controls the behavior on EAGAIN. pub unsafe fn write_all_generic( buf: *const libc::c_void, count: libc::size_t, retry: Retry, write: impl Fn(*const libc::c_void, libc::size_t) -> libc::ssize_t, -) -> libc::ssize_t { +) -> io::Result<()> { assert!(count > 0); let mut written_so_far = 0; while written_so_far < count { @@ -124,29 +110,22 @@ pub unsafe fn write_all_generic( continue; } } - return res; + return Err(io::Error::last_os_error()); } // Apparently a return value of 0 is just a short write, nothing special (unlike reads). written_so_far += res as libc::size_t; } - return written_so_far as libc::ssize_t; -} - -/// Write to `fd` until `buf` is fully written. Abort on first error. -pub unsafe fn write_all( - fd: libc::c_int, - buf: *const libc::c_void, - count: libc::size_t, -) -> libc::ssize_t { - write_all_generic(buf, count, NoRetry, |buf, count| libc::write(fd, buf, count)) + return Ok(()); } /// Write the entire `buf` to `fd`. Panic if not all bytes could be written. #[track_caller] -pub fn write_all_from_slice(fd: libc::c_int, buf: &[u8]) -> io::Result<()> { - let res = errno_result(unsafe { write_all(fd, buf.as_ptr().cast(), buf.len()) })?; - assert_eq!(res as usize, buf.len()); - Ok(()) +pub fn write_all(fd: libc::c_int, buf: &[u8]) -> io::Result<()> { + unsafe { + write_all_generic(buf.as_ptr().cast(), buf.len(), NoRetry, |buf, count| { + libc::write(fd, buf, count) + }) + } } #[cfg(any(target_os = "linux", target_os = "android", target_os = "illumos"))] From ea6eaab341b66720f868f10b8905b497a1c40762 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 9 May 2026 16:42:37 +0200 Subject: [PATCH 27/37] fstat: expose permissions in 'mode' field --- src/tools/miri/src/shims/unix/fs.rs | 62 +++++++++---------- src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 3 + .../pass-dep/libc/libc-fstat-non-file.rs | 2 + 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index b1d39513065ce..fcd0cb2617421 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -236,15 +236,10 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { // 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.unwrap_or(0).into()), - ("st_mode", mode.into()), + ("st_mode", metadata.mode.into()), ("st_nlink", metadata.nlink.unwrap_or(0).into()), ("st_ino", metadata.ino.unwrap_or(0).into()), ("st_uid", metadata.uid.unwrap_or(0).into()), @@ -766,15 +761,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { mask |= this.eval_libc_u32("STATX_BLOCKS"); } - // `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_uint(mode_t_size)? - .try_into() - .unwrap_or_else(|_| bug!("libc contains bad value for constant")); - // We need to set the corresponding bits of `mask` if the access, creation and modification // times were available. Otherwise we let them be zero. let (access_sec, access_nsec) = metadata @@ -805,12 +791,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_int_fields_named( &[ ("stx_mask", mask.into()), + ("stx_mode", metadata.mode.into()), ("stx_blksize", metadata.blksize.unwrap_or(0).into()), ("stx_attributes", 0), ("stx_nlink", metadata.nlink.unwrap_or(0).into()), ("stx_uid", metadata.uid.unwrap_or(0).into()), ("stx_gid", metadata.gid.unwrap_or(0).into()), - ("stx_mode", mode.into()), ("stx_ino", metadata.ino.unwrap_or(0).into()), ("stx_size", metadata.size.into()), ("stx_blocks", metadata.blocks.unwrap_or(0).into()), @@ -1684,7 +1670,8 @@ fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str { /// expose it. `statx` must only advertise the corresponding `STATX_*` bit when the field is `Some`; /// legacy `stat` writes zero for `None` to preserve the old fallback behavior. struct FileMetadata { - mode: Scalar, + /// This holds both the file type (dir, regular, symlink, ...) and permissions. + mode: u32, size: u64, created: Option<(u64, u32)>, accessed: Option<(u64, u32)>, @@ -1728,6 +1715,9 @@ impl FileMetadata { mode_name: &str, ) -> InterpResult<'tcx, Result> { let mode = ecx.eval_libc(mode_name); + let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap(); + // We observed 0x777 on sockets and 0x600 on pipes... + let mode = mode | 0o666; interp_ok(Ok(FileMetadata { mode, size: 0, @@ -1757,6 +1747,7 @@ impl FileMetadata { let file_type = metadata.file_type(); let mode = ecx.eval_libc(file_type_to_mode_name(file_type)); + let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap(); let size = metadata.len(); @@ -1769,6 +1760,8 @@ impl FileMetadata { cfg_select! { unix => { use std::os::unix::fs::MetadataExt; + use std::os::unix::fs::PermissionsExt; + let dev = metadata.dev(); let ino = metadata.ino(); let nlink = metadata.nlink(); @@ -1777,6 +1770,8 @@ impl FileMetadata { let blksize = metadata.blksize(); let blocks = metadata.blocks(); + mode |= metadata.permissions().mode(); + interp_ok(Ok(FileMetadata { mode, size, @@ -1792,20 +1787,25 @@ impl FileMetadata { blocks: Some(blocks), })) } - _ => interp_ok(Ok(FileMetadata { - mode, - size, - created, - accessed, - modified, - dev: None, - ino: None, - nlink: None, - uid: None, - gid: None, - blksize: None, - blocks: None, - })), + _ => { + // Emulate "everyone can read" or "everyone can read and write". + mode |= if metadata.permissions().readonly() { 0o111 } else { 0o333 }; + + interp_ok(Ok(FileMetadata { + mode, + size, + created, + accessed, + modified, + dev: None, + ino: None, + nlink: None, + uid: None, + gid: None, + blksize: None, + blocks: None, + })) + }, } } } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index 23f2bf66f7d3f..a388943f922ff 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -605,6 +605,7 @@ fn test_fstat() { assert_eq!(stat.st_size, 5); assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFREG); + assert_ne!(stat.st_mode & !libc::S_IFMT, 0, "some permission should be set"); // Check that all fields are initialized. check_stat_fields(stat); @@ -625,6 +626,7 @@ fn test_stat() { assert_eq!(stat.st_size, 5); assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFREG); + assert_ne!(stat.st_mode & !libc::S_IFMT, 0, "some permission should be set"); // Check that all fields are initialized. check_stat_fields(stat); @@ -648,6 +650,7 @@ fn test_lstat() { let stat = unsafe { stat.assume_init_ref() }; assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFLNK); + assert_ne!(stat.st_mode & !libc::S_IFMT, 0, "some permission should be set"); // Check that all fields are initialized. check_stat_fields(stat); 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 index c29c8ceaa1dfc..cf848f1903311 100644 --- 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 @@ -55,6 +55,7 @@ fn test_fstat_socketpair() { libc::S_IFSOCK, "socketpair should have S_IFSOCK mode" ); + assert_ne!(stat.st_mode & !libc::S_IFMT, 0, "socketpair should have permissions"); assert_eq!(stat.st_size, 0, "socketpair should have size 0"); assert_stat_fields_are_accessible(stat); } @@ -72,6 +73,7 @@ fn test_fstat_pipe() { 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_ne!(stat.st_mode & !libc::S_IFMT, 0, "pipe should have permissions"); assert_eq!(stat.st_size, 0, "pipe should have size 0"); assert_stat_fields_are_accessible(stat); } From 773af235051378c81a2af06838cf71da2885f72c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 9 May 2026 17:37:09 +0200 Subject: [PATCH 28/37] stream_send_recv_stress tests: wait for threads to finish --- library/std/tests/sync/mpmc.rs | 58 +++++++++++++++++++++------------- library/std/tests/sync/mpsc.rs | 58 +++++++++++++++++++++------------- 2 files changed, 72 insertions(+), 44 deletions(-) diff --git a/library/std/tests/sync/mpmc.rs b/library/std/tests/sync/mpmc.rs index db221ff15890e..e8ed5ab13d009 100644 --- a/library/std/tests/sync/mpmc.rs +++ b/library/std/tests/sync/mpmc.rs @@ -419,34 +419,48 @@ fn oneshot_multi_thread_send_recv_stress() { #[test] fn stream_send_recv_stress() { - for _ in 0..stress_factor() { - let (tx, rx) = channel(); - - send(tx, 0); - recv(rx, 0); + thread::scope(|s| { + for _ in 0..stress_factor() { + let (tx, rx) = channel(); + + send(tx, 0, s); + recv(rx, 0, s); + + fn send<'scope, 'env>( + tx: Sender>, + i: i32, + s: &'scope thread::Scope<'scope, 'env>, + ) where + 'env: 'scope, + { + if i == 10 { + return; + } - fn send(tx: Sender>, i: i32) { - if i == 10 { - return; + s.spawn(move || { + tx.send(Box::new(i)).unwrap(); + send(tx, i + 1, s); + }); } - thread::spawn(move || { - tx.send(Box::new(i)).unwrap(); - send(tx, i + 1); - }); - } + fn recv<'scope, 'env>( + rx: Receiver>, + i: i32, + s: &'scope thread::Scope<'scope, 'env>, + ) where + 'env: 'scope, + { + if i == 10 { + return; + } - fn recv(rx: Receiver>, i: i32) { - if i == 10 { - return; + s.spawn(move || { + assert!(*rx.recv().unwrap() == i); + recv(rx, i + 1, s); + }); } - - thread::spawn(move || { - assert!(*rx.recv().unwrap() == i); - recv(rx, i + 1); - }); } - } + }) } #[test] diff --git a/library/std/tests/sync/mpsc.rs b/library/std/tests/sync/mpsc.rs index 4dc4b955da7c2..f1364dda713a1 100644 --- a/library/std/tests/sync/mpsc.rs +++ b/library/std/tests/sync/mpsc.rs @@ -382,34 +382,48 @@ fn oneshot_multi_thread_send_recv_stress() { #[test] fn stream_send_recv_stress() { - for _ in 0..stress_factor() { - let (tx, rx) = channel(); - - send(tx, 0); - recv(rx, 0); + thread::scope(|s| { + for _ in 0..stress_factor() { + let (tx, rx) = channel(); + + send(tx, 0, s); + recv(rx, 0, s); + + fn send<'scope, 'env>( + tx: Sender>, + i: i32, + s: &'scope thread::Scope<'scope, 'env>, + ) where + 'env: 'scope, + { + if i == 10 { + return; + } - fn send(tx: Sender>, i: i32) { - if i == 10 { - return; + s.spawn(move || { + tx.send(Box::new(i)).unwrap(); + send(tx, i + 1, s); + }); } - thread::spawn(move || { - tx.send(Box::new(i)).unwrap(); - send(tx, i + 1); - }); - } + fn recv<'scope, 'env>( + rx: Receiver>, + i: i32, + s: &'scope thread::Scope<'scope, 'env>, + ) where + 'env: 'scope, + { + if i == 10 { + return; + } - fn recv(rx: Receiver>, i: i32) { - if i == 10 { - return; + s.spawn(move || { + assert!(*rx.recv().unwrap() == i); + recv(rx, i + 1, s); + }); } - - thread::spawn(move || { - assert!(*rx.recv().unwrap() == i); - recv(rx, i + 1); - }); } - } + }) } #[test] From 9c426442177112e829e1c9fa17fac3244edd8427 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 9 May 2026 17:19:34 +0200 Subject: [PATCH 29/37] support chmod and fchmod on unix hosts --- .../miri/src/shims/unix/foreign_items.rs | 22 ++++++- src/tools/miri/src/shims/unix/fs.rs | 65 ++++++++++++++++--- .../pass-dep/libc/libc-fs-permissions.rs | 50 ++++++++++++++ .../miri/tests/pass/shims/fs-permissions.rs | 61 +++++++++++++++++ src/tools/miri/tests/pass/shims/fs.rs | 1 + 5 files changed, 190 insertions(+), 9 deletions(-) create mode 100644 src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs create mode 100644 src/tools/miri/tests/pass/shims/fs-permissions.rs diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 0766352bab208..748987360be98 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -362,6 +362,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.stat(path, buf)?; this.write_scalar(result, dest)?; } + "chmod" => { + let [path, mode] = this.check_shim_sig( + shim_sig!(extern "C" fn(*const _, libc::mode_t) -> i32), + link_name, + abi, + args, + )?; + let result = this.chmod(path, mode)?; + this.write_scalar(result, dest)?; + } + "fchmod" => { + let [fd, mode] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, libc::mode_t) -> i32), + link_name, + abi, + args, + )?; + let result = this.fchmod(fd, mode)?; + this.write_scalar(result, dest)?; + } "rename" => { // FIXME: This does not have a direct test (#3179). let [oldpath, newpath] = this.check_shim_sig( @@ -536,7 +556,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } - // Unnamed sockets and pipes + // Sockets and pipes "socketpair" => { let [domain, type_, protocol, sv] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, i32, i32, *mut _) -> i32), diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index fcd0cb2617421..8df54616b5625 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -2,10 +2,7 @@ use std::borrow::Cow; use std::ffi::OsString; -use std::fs::{ - self, DirBuilder, File, FileType, OpenOptions, TryLockError, read_dir, remove_dir, remove_file, - rename, -}; +use std::fs::{self, DirBuilder, File, FileType, OpenOptions, TryLockError}; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; use std::path::{self, Path, PathBuf}; use std::time::SystemTime; @@ -341,6 +338,17 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { }, }) } + + #[cfg(unix)] + fn host_permissions_from_mode(&self, mode: u32) -> InterpResult<'tcx, fs::Permissions> { + use std::os::unix::fs::PermissionsExt; + interp_ok(fs::Permissions::from_mode(mode)) + } + + #[cfg(not(unix))] + fn host_permissions_from_mode(&self, _mode: u32) -> InterpResult<'tcx, fs::Permissions> { + throw_unsup_format!("setting file permissions is only supported on Unix hosts") + } } impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} @@ -542,7 +550,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); } - let result = remove_file(path).map(|_| 0); + let result = fs::remove_file(path).map(|_| 0); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) } @@ -844,6 +852,47 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(Scalar::from_i32(0)) } + fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let path_ptr = this.read_pointer(path_op)?; + let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?; + + if this.ptr_is_null(path_ptr)? { + return this.set_last_error_and_return_i32(LibcError("EFAULT")); + } + let path = this.read_path_from_c_str(path_ptr)?; + + let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?; + if let Err(err) = fs::set_permissions(path, permissions) { + return this.set_last_error_and_return_i32(IoError::HostError(err)); + } + + interp_ok(Scalar::from_i32(0)) + } + + fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let fd_num = this.read_scalar(fd_op)?.to_i32()?; + let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?; + + let Some(fd) = this.machine.fds.get(fd_num) else { + return this.set_last_error_and_return_i32(LibcError("EBADF")); + }; + let Some(file) = fd.downcast::() else { + // The docs don't talk about what happens for non-regular files... + throw_unsup_format!("`fchmod` is only supported on regular files") + }; + + let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?; + if let Err(err) = file.file.set_permissions(permissions) { + return this.set_last_error_and_return_i32(IoError::HostError(err)); + } + + interp_ok(Scalar::from_i32(0)) + } + fn rename( &mut self, oldpath_op: &OpTy<'tcx>, @@ -867,7 +916,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); } - let result = rename(oldpath, newpath).map(|_| 0); + let result = fs::rename(oldpath, newpath).map(|_| 0); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) } @@ -917,7 +966,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); } - let result = remove_dir(path).map(|_| 0i32); + let result = fs::remove_dir(path).map(|_| 0i32); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) } @@ -934,7 +983,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return interp_ok(Scalar::null_ptr(this)); } - let result = read_dir(name); + let result = fs::read_dir(name); match result { Ok(dir_iter) => { diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs new file mode 100644 index 0000000000000..f03e1ad8641de --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs @@ -0,0 +1,50 @@ +//@ignore-target: windows # no libc +//@ignore-host: windows # needs unix PermissionExt +//@compile-flags: -Zmiri-disable-isolation + +#![feature(io_error_more)] +#![feature(io_error_uncategorized)] + +use std::ffi::{CStr, CString}; +use std::mem::MaybeUninit; +use std::os::unix::ffi::OsStrExt; + +#[path = "../../utils/mod.rs"] +mod utils; + +#[path = "../../utils/libc.rs"] +mod libc_utils; +use libc_utils::{errno_check, errno_result}; + +fn main() { + test_chmod(); + test_fchmod(); +} + +#[track_caller] +fn getmod(path: &CStr) -> u32 { + let mut stat = MaybeUninit::::uninit(); + unsafe { errno_check(libc::stat(path.as_ptr(), stat.as_mut_ptr())) }; + u32::from(unsafe { stat.assume_init_ref().st_mode & !libc::S_IFMT }) +} + +fn test_chmod() { + let path = utils::prepare_with_content("miri_test_libc_chmod.txt", b"abcdef"); + let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); + + unsafe { errno_check(libc::chmod(c_path.as_ptr(), 0o777)) }; + assert_eq!(getmod(&c_path), 0o777); + unsafe { errno_check(libc::chmod(c_path.as_ptr(), 0o610)) }; + assert_eq!(getmod(&c_path), 0o610); +} + +fn test_fchmod() { + let path = utils::prepare_with_content("miri_test_libc_chmod.txt", b"abcdef"); + let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); + + let fd = unsafe { errno_result(libc::open(c_path.as_ptr(), libc::O_RDONLY)).unwrap() }; + unsafe { errno_check(libc::fchmod(fd, 0o777)) }; + assert_eq!(getmod(&c_path), 0o777); + unsafe { errno_check(libc::fchmod(fd, 0o610)) }; + assert_eq!(getmod(&c_path), 0o610); +} diff --git a/src/tools/miri/tests/pass/shims/fs-permissions.rs b/src/tools/miri/tests/pass/shims/fs-permissions.rs new file mode 100644 index 0000000000000..51b371e83ba55 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/fs-permissions.rs @@ -0,0 +1,61 @@ +//@compile-flags: -Zmiri-disable-isolation +//@ignore-target: windows # shim not supported +//@ignore-host: windows # needs unix PermissionExt + +use std::fs::{self, File}; + +#[path = "../../utils/mod.rs"] +mod utils; + +macro_rules! check { + ($e:expr) => { + match $e { + Ok(t) => t, + Err(e) => panic!("{} failed with: {e}", stringify!($e)), + } + }; +} + +fn main() { + chmod_works(); + fchmod_works(); +} + +fn chmod_works() { + let tmpdir = utils::tmp(); + let file = tmpdir.join("miri_test_fs_set_permissions.txt"); + + check!(File::create(&file)); + let attr = check!(fs::metadata(&file)); + assert!(!attr.permissions().readonly()); + let mut p = attr.permissions(); + p.set_readonly(true); + check!(fs::set_permissions(&file, p.clone())); + let attr = check!(fs::metadata(&file)); + assert!(attr.permissions().readonly()); + + match fs::set_permissions(&tmpdir.join("foo"), p.clone()) { + Ok(..) => panic!("wanted an error"), + Err(..) => {} + } + + p.set_readonly(false); + check!(fs::set_permissions(&file, p)); +} + +fn fchmod_works() { + let tmpdir = utils::tmp(); + let path = tmpdir.join("miri_test_file_set_permissions.txt"); + + let file = check!(File::create(&path)); + let attr = check!(fs::metadata(&path)); + assert!(!attr.permissions().readonly()); + let mut p = attr.permissions(); + p.set_readonly(true); + check!(file.set_permissions(p.clone())); + let attr = check!(fs::metadata(&path)); + assert!(attr.permissions().readonly()); + + p.set_readonly(false); + check!(file.set_permissions(p)); +} diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index e6c15c81d9fd4..e0da9f63876f1 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -54,6 +54,7 @@ fn test_file() { // Test creating, writing and closing a file (closing is tested when `file` is dropped). let mut file = File::create(&path).unwrap(); + assert!(!file.metadata().unwrap().permissions().readonly()); // new file shouldn't be read-only // Writing 0 bytes should not change the file contents. file.write(&mut []).unwrap(); assert_eq!(file.metadata().unwrap().len(), 0); From e549493e0f92844a73520a095ef12797a6922189 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 9 May 2026 22:33:50 +0200 Subject: [PATCH 30/37] try_errnum_to_io_error: better support for unix targets on unix hosts --- src/tools/miri/src/shims/io_error.rs | 41 ++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/tools/miri/src/shims/io_error.rs b/src/tools/miri/src/shims/io_error.rs index cc9459318d7f7..8bdbb5522d875 100644 --- a/src/tools/miri/src/shims/io_error.rs +++ b/src/tools/miri/src/shims/io_error.rs @@ -246,8 +246,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.read_scalar(&errno_place) } - /// This function tries to produce the most similar OS error from the `std::io::ErrorKind` - /// as a platform-specific errnum. + /// This function converts host errors to target errors. It tries to produce the most similar OS + /// error from the `std::io::ErrorKind` as a platform-specific errnum. fn io_error_to_errnum(&self, err: std::io::Error) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_ref(); let target = &this.tcx.sess.target; @@ -274,27 +274,46 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } - /// The inverse of `io_error_to_errnum`. + /// The inverse of `io_error_to_errnum`: it converts target errors to host errors. + /// This is done in a best-effort way. #[expect(clippy::needless_return)] fn try_errnum_to_io_error( &self, - errnum: Scalar, - ) -> InterpResult<'tcx, Option> { + target_errnum: Scalar, + ) -> InterpResult<'tcx, Option> { let this = self.eval_context_ref(); let target = &this.tcx.sess.target; if target.families.iter().any(|f| f == "unix") { - let errnum = errnum.to_i32()?; + let target_errnum = target_errnum.to_i32()?; + // If the host is also unix, we try to translate the errno directly. + // That lets us use `Error::from_raw_os_error`, which has a much better `Display` + // impl than what we get by going through `ErrorKind`. + #[cfg(unix)] + { + // For now, we only add the constants we need to make `std` tests happy. + const ERRNOS: &[(&str, libc::c_int)] = &[ + ("ENOENT", libc::ENOENT), + ("ENOTDIR", libc::ENOTDIR), + ("ENOTSOCK", libc::ENOTSOCK), + ]; + for &(name, errno) in ERRNOS { + if target_errnum == this.eval_libc_i32(name) { + return interp_ok(Some(io::Error::from_raw_os_error(errno))); + } + } + } + // For other hosts or other constants, we fall back to translating via `ErrorKind`. for &(name, kind) in UNIX_IO_ERROR_TABLE { - if errnum == this.eval_libc_i32(name) { - return interp_ok(Some(kind)); + if target_errnum == this.eval_libc_i32(name) { + return interp_ok(Some(kind.into())); } } return interp_ok(None); } else if target.families.iter().any(|f| f == "windows") { - let errnum = errnum.to_u32()?; + let target_errnum = target_errnum.to_u32()?; for &(name, kind) in WINDOWS_IO_ERROR_TABLE { - if errnum == this.eval_windows("c", name).to_u32()? { - return interp_ok(Some(kind)); + if target_errnum == this.eval_windows("c", name).to_u32()? { + return interp_ok(Some(kind.into())); } } return interp_ok(None); From ef282ff86b7cb244b027761768f8a0fb33940812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Thu, 23 Apr 2026 10:57:57 +0200 Subject: [PATCH 31/37] rustdoc: Reify emission types --- src/librustdoc/config.rs | 108 +++++++++++------- src/librustdoc/formats/renderer.rs | 20 ++-- src/librustdoc/html/render/context.rs | 13 +-- src/librustdoc/html/render/write_shared.rs | 6 +- src/librustdoc/json/mod.rs | 9 +- src/librustdoc/lib.rs | 4 +- ...output-format-doctest-emit.dep-info.stderr | 2 + ...rmat-doctest-emit.html-static-files.stderr | 2 + .../rustdoc-ui/output-format-doctest-emit.rs | 8 ++ .../output-format-json-emit-html.rs | 9 +- 10 files changed, 109 insertions(+), 72 deletions(-) create mode 100644 tests/rustdoc-ui/output-format-doctest-emit.dep-info.stderr create mode 100644 tests/rustdoc-ui/output-format-doctest-emit.html-static-files.stderr create mode 100644 tests/rustdoc-ui/output-format-doctest-emit.rs diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index d726c612acf68..3cd34f24c6e3d 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -18,6 +18,7 @@ use rustc_session::{EarlyDiagCtxt, getopts}; use rustc_span::edition::Edition; use rustc_span::{FileName, RemapPathScopeComponents}; use rustc_target::spec::TargetTuple; +use smallvec::SmallVec; use crate::core::new_dcx; use crate::externalfiles::ExternalHtml; @@ -293,7 +294,7 @@ pub(crate) struct RenderOptions { /// Note: this field is duplicated in `Options` because it's useful to have /// it in both places. pub(crate) unstable_features: rustc_feature::UnstableFeatures, - pub(crate) emit: Vec, + pub(crate) emit: SmallVec<[EmitType; 2]>, /// If `true`, HTML source pages will generate links for items to their definition. pub(crate) generate_link_to_definition: bool, /// Set of function-call locations to include as examples @@ -327,9 +328,22 @@ pub(crate) enum ModuleSorting { pub(crate) enum EmitType { HtmlStaticFiles, HtmlNonStaticFiles, + // not explicitly nameable by the user for now + JsonFiles, DepInfo(Option), } +impl fmt::Display for EmitType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::HtmlStaticFiles => "html-static-files", + Self::HtmlNonStaticFiles => "html-non-static-files", + Self::JsonFiles => "json-files", + Self::DepInfo(_) => "dep-info", + }) + } +} + impl FromStr for EmitType { type Err = (); @@ -352,17 +366,11 @@ impl FromStr for EmitType { } impl RenderOptions { - pub(crate) fn should_emit_crate(&self) -> bool { - self.emit.is_empty() || self.emit.contains(&EmitType::HtmlNonStaticFiles) - } - pub(crate) fn dep_info(&self) -> Option> { - for emit in &self.emit { - if let EmitType::DepInfo(file) = emit { - return Some(file.as_ref()); - } - } - None + self.emit.iter().find_map(|emit| match emit { + EmitType::DepInfo(file) => Some(file.as_ref()), + _ => None, + }) } } @@ -469,26 +477,6 @@ impl Options { let should_test = matches.opt_present("test"); - let mut emit = FxIndexMap::<_, EmitType>::default(); - for list in matches.opt_strs("emit") { - if should_test { - dcx.fatal("the `--test` flag and the `--emit` flag are not supported together"); - } - for kind in list.split(',') { - match kind.parse() { - Ok(kind) => { - // De-duplicate emit types and the last wins. - // Only one instance for each type is allowed - // regardless the actual data it carries. - // This matches rustc's `--emit` behavior. - emit.insert(std::mem::discriminant(&kind), kind); - } - Err(()) => dcx.fatal(format!("unrecognized emission type: {kind}")), - } - } - } - let emit = emit.into_values().collect::>(); - let show_coverage = matches.opt_present("show-coverage"); let output_format_s = matches.opt_str("output-format"); let output_format = match output_format_s { @@ -527,15 +515,55 @@ impl Options { } } - if output_format == OutputFormat::Json { - if let Some(emit_flag) = emit.iter().find_map(|emit| match emit { - EmitType::HtmlStaticFiles => Some("html-static-files"), - EmitType::HtmlNonStaticFiles => Some("html-non-static-files"), - EmitType::DepInfo(_) => None, - }) { - dcx.fatal(format!( - "the `--emit={emit_flag}` flag is not supported with `--output-format=json`", - )); + let mut emit = FxIndexMap::default(); + for list in matches.opt_strs("emit") { + if should_test { + dcx.fatal("the `--test` flag and the `--emit` flag are not supported together"); + } + if let OutputFormat::Doctest = output_format { + dcx.fatal("the `--emit` flag is not supported with `--output-format=doctest`"); + } + + for typ in list.split(',') { + let Ok(typ) = typ.parse::() else { + dcx.fatal(format!("unrecognized emission type: {typ}")) + }; + + match typ { + EmitType::DepInfo(_) => match output_format { + OutputFormat::Json | OutputFormat::Html => {} + OutputFormat::Doctest => unreachable!(), + }, + EmitType::HtmlStaticFiles | EmitType::HtmlNonStaticFiles => match output_format + { + OutputFormat::Html => {} + OutputFormat::Json => dcx.fatal(format!( + "the `--emit={typ}` flag is not supported with `--output-format=json`", + )), + OutputFormat::Doctest => unreachable!(), + }, + EmitType::JsonFiles => unreachable!(), + } + + // De-duplicate emit types and the last wins. + // Only one instance for each type is allowed + // regardless the actual data it carries. + // This matches rustc's `--emit` behavior. + emit.insert(std::mem::discriminant(&typ), typ); + } + } + let mut emit: SmallVec<[_; 2]> = emit.into_values().collect(); + // If `--emit` is absent we'll register default emission types depending on the requested + // output format. We can safely use `is_empty` for this since `--emit=` ("truly empty") + // will have already been rejected above. + if emit.is_empty() { + match output_format { + OutputFormat::Json => emit.push(EmitType::JsonFiles), + OutputFormat::Html => { + emit.push(EmitType::HtmlStaticFiles); + emit.push(EmitType::HtmlNonStaticFiles); + } + OutputFormat::Doctest => {} } } diff --git a/src/librustdoc/formats/renderer.rs b/src/librustdoc/formats/renderer.rs index 305c8c39ba7f9..5c458232f8f9c 100644 --- a/src/librustdoc/formats/renderer.rs +++ b/src/librustdoc/formats/renderer.rs @@ -2,7 +2,7 @@ use rustc_data_structures::profiling::SelfProfilerRef; use rustc_middle::ty::TyCtxt; use crate::clean; -use crate::config::RenderOptions; +use crate::config::{EmitType, RenderOptions}; use crate::error::Error; use crate::formats::cache::Cache; @@ -10,14 +10,16 @@ use crate::formats::cache::Cache; /// backend renderer has hooks for initialization, documenting an item, entering and exiting a /// module, and cleanup/finalizing output. pub(crate) trait FormatRenderer<'tcx>: Sized { - /// Gives a description of the renderer. Used for performance profiling. - fn descr() -> &'static str; + /// A description of the renderer. Used for performance profiling. + const DESCR: &'static str; - /// Whether to call `item` recursively for modules + /// Whether to call `item` recursively for modules. /// - /// This is true for html, and false for json. See #80664 + /// See [#80664](https://github.com/rust-lang/rust/issues/80664). const RUN_ON_MODULE: bool; + const NON_STATIC_FILE_EMIT_TYPE: EmitType; + /// This associated type is the type where the current module information is stored. /// /// For each module, we go through their items by calling for each item: @@ -109,18 +111,18 @@ pub(crate) fn run_format< ) -> Result<(), Error> { let prof = &tcx.sess.prof; - let emit_crate = options.should_emit_crate(); + let emit_non_static_files = options.emit.contains(&T::NON_STATIC_FILE_EMIT_TYPE); let (mut format_renderer, krate) = prof - .verbose_generic_activity_with_arg("create_renderer", T::descr()) + .verbose_generic_activity_with_arg("create_renderer", T::DESCR) .run(|| init(krate, options, cache, tcx))?; - if !emit_crate { + if !emit_non_static_files { return Ok(()); } // Render the crate documentation run_format_inner(&mut format_renderer, &krate.module, prof)?; - prof.verbose_generic_activity_with_arg("renderer_after_krate", T::descr()) + prof.verbose_generic_activity_with_arg("renderer_after_krate", T::DESCR) .run(|| format_renderer.after_krate()) } diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 4a7b1d1d6c563..6c02eecbd06ee 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -23,7 +23,7 @@ use super::{AllTypes, LinkFromSrc, StylePath, collect_spans_and_sources, scrape_ use crate::clean::types::ExternalLocation; use crate::clean::utils::has_doc_flag; use crate::clean::{self, ExternalCrate}; -use crate::config::{ModuleSorting, RenderOptions, ShouldMerge}; +use crate::config::{EmitType, ModuleSorting, RenderOptions, ShouldMerge}; use crate::docfs::{DocFS, PathError}; use crate::error::Error; use crate::formats::FormatRenderer; @@ -481,7 +481,6 @@ impl<'tcx> Context<'tcx> { ) -> Result<(Self, clean::Crate), Error> { // need to save a copy of the options for rendering the index page let md_opts = options.clone(); - let emit_crate = options.should_emit_crate(); let RenderOptions { output, external_html, @@ -495,6 +494,7 @@ impl<'tcx> Context<'tcx> { static_root_path, generate_redirect_map, show_type_layout, + emit, generate_link_to_definition, call_locations, no_emit_shared, @@ -605,7 +605,7 @@ impl<'tcx> Context<'tcx> { info: ContextInfo::new(include_sources), }; - if emit_crate { + if emit.contains(&EmitType::HtmlNonStaticFiles) { sources::render(&mut cx, &krate)?; } @@ -619,11 +619,10 @@ impl<'tcx> Context<'tcx> { /// Generates the documentation for `crate` into the directory `dst` impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { - fn descr() -> &'static str { - "html" - } - + const DESCR: &'static str = "html"; const RUN_ON_MODULE: bool = true; + const NON_STATIC_FILE_EMIT_TYPE: EmitType = EmitType::HtmlNonStaticFiles; + type ModuleData = ContextInfo; fn save_module_data(&mut self) -> Self::ModuleData { diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 8de899ea0eef9..26894a64c6c14 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -165,7 +165,7 @@ fn write_rendered_cross_crate_info( resource_suffix: &str, ) -> Result<(), Error> { let m = &opt.should_merge; - if opt.should_emit_crate() { + if opt.emit.contains(&EmitType::HtmlNonStaticFiles) { if include_sources { write_rendered_cci::(SourcesPart::blank, dst, crates, m)?; } @@ -190,7 +190,7 @@ fn write_resources( css_file_extension: Option<&Path>, resource_suffix: &str, ) -> Result<(), Error> { - if opt.emit.is_empty() || opt.emit.contains(&EmitType::HtmlNonStaticFiles) { + if opt.emit.contains(&EmitType::HtmlNonStaticFiles) { // Handle added third-party themes for entry in style_files { let theme = entry.basename()?; @@ -218,7 +218,7 @@ fn write_resources( } } - if opt.emit.is_empty() || opt.emit.contains(&EmitType::HtmlStaticFiles) { + if opt.emit.contains(&EmitType::HtmlStaticFiles) { let static_dir = dst.join("static.files"); try_err!(fs::create_dir_all(&static_dir), &static_dir); diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index 7c8e7b7669cd9..e33d2d44cffd3 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -27,7 +27,7 @@ use tracing::{debug, trace}; use crate::clean::ItemKind; use crate::clean::types::{ExternalCrate, ExternalLocation}; -use crate::config::RenderOptions; +use crate::config::{EmitType, RenderOptions}; use crate::docfs::PathError; use crate::error::Error; use crate::formats::FormatRenderer; @@ -132,11 +132,10 @@ impl<'tcx> JsonRenderer<'tcx> { } impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { - fn descr() -> &'static str { - "json" - } - + const DESCR: &'static str = "json"; const RUN_ON_MODULE: bool = false; + const NON_STATIC_FILE_EMIT_TYPE: EmitType = EmitType::JsonFiles; + type ModuleData = (); fn save_module_data(&mut self) -> Self::ModuleData { diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 87de4244b5c85..d51405d352549 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -869,9 +869,7 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) { }; rustc_interface::create_and_enter_global_ctxt(compiler, krate, |tcx| { let has_dep_info = render_options.dep_info().is_some(); - if render_options.emit.contains(&EmitType::HtmlNonStaticFiles) - || render_options.emit.is_empty() - { + if render_options.emit.contains(&EmitType::HtmlNonStaticFiles) { markdown::render_and_write(file, render_options, edition)?; } if has_dep_info { diff --git a/tests/rustdoc-ui/output-format-doctest-emit.dep-info.stderr b/tests/rustdoc-ui/output-format-doctest-emit.dep-info.stderr new file mode 100644 index 0000000000000..e09774d9a03bc --- /dev/null +++ b/tests/rustdoc-ui/output-format-doctest-emit.dep-info.stderr @@ -0,0 +1,2 @@ +error: the `--emit` flag is not supported with `--output-format=doctest` + diff --git a/tests/rustdoc-ui/output-format-doctest-emit.html-static-files.stderr b/tests/rustdoc-ui/output-format-doctest-emit.html-static-files.stderr new file mode 100644 index 0000000000000..e09774d9a03bc --- /dev/null +++ b/tests/rustdoc-ui/output-format-doctest-emit.html-static-files.stderr @@ -0,0 +1,2 @@ +error: the `--emit` flag is not supported with `--output-format=doctest` + diff --git a/tests/rustdoc-ui/output-format-doctest-emit.rs b/tests/rustdoc-ui/output-format-doctest-emit.rs new file mode 100644 index 0000000000000..b590d9057bb6e --- /dev/null +++ b/tests/rustdoc-ui/output-format-doctest-emit.rs @@ -0,0 +1,8 @@ +// Ensure that `--output-format=doctest` is incompatible with the `--emit` flag (for now at least). + +//@ revisions: html-static-files dep-info +//@ compile-flags: -Zunstable-options --output-format doctest +//@[html-static-files] compile-flags: --emit html-static-files +//@[dep-info] compile-flags: --emit dep-info + +//~? ERROR the `--emit` flag is not supported with `--output-format=doctest` diff --git a/tests/rustdoc-ui/output-format-json-emit-html.rs b/tests/rustdoc-ui/output-format-json-emit-html.rs index 7a99cbd91ba6e..7ce3bf756ec92 100644 --- a/tests/rustdoc-ui/output-format-json-emit-html.rs +++ b/tests/rustdoc-ui/output-format-json-emit-html.rs @@ -1,8 +1,7 @@ //@ revisions: html_static html_non_static -//@ check-fail -//@[html_static] compile-flags: -Z unstable-options --output-format=json --emit=html-static-files -//@[html_non_static] compile-flags: -Z unstable-options --output-format=json --emit=html-non-static-files +//@ compile-flags: -Zunstable-options --output-format json +//@[html_static] compile-flags: --emit html-static-files +//@[html_non_static] compile-flags: --emit html-non-static-files + //[html_static]~? ERROR the `--emit=html-static-files` flag is not supported with `--output-format=json` //[html_non_static]~? ERROR the `--emit=html-non-static-files` flag is not supported with `--output-format=json` - -pub struct Foo; From 498c8509b6b9dbb8f1336fa2286dcb04ef6d3628 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 10 May 2026 09:49:58 +0200 Subject: [PATCH 32/37] std_miri_test hack is not needed any more --- src/tools/miri/src/helpers.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 512ea6ab1a2ff..de94f4a27a20b 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -905,8 +905,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let frame_crate = this.tcx.def_path(instance.def_id()).krate; let crate_name = this.tcx.crate_name(frame_crate); let crate_name = crate_name.as_str(); - // On miri-test-libstd, the name of the crate is different. - crate_name == "std" || crate_name == "std_miri_test" + crate_name == "std" } /// Mark a machine allocation that was just created as immutable. From 6f0005e206cb548a32ffe8abc822b0a2e88634df Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 10 May 2026 09:40:43 +0200 Subject: [PATCH 33/37] add gnu_get_libc_version stub for getaddrinfo error path --- .../src/shims/unix/linux/foreign_items.rs | 11 ++++++++ .../miri/tests/pass/shims/socket-address.rs | 7 +++++- .../tests/pass/shims/socket-address.stderr | 25 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/tools/miri/tests/pass/shims/socket-address.stderr diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs index 502e4e9307a8e..e42bf900aceff 100644 --- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -246,6 +246,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; this.write_null(dest)?; } + "gnu_get_libc_version" + if this.frame_in_std() + && this.tcx.sess.target.env == rustc_target::spec::Env::Gnu => + { + let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + // We have to be at least version 2.26 so that std does not call `res_init`. + // This returns a C string, so we have to add a null terminator. + let version = "2.26\0"; + let version = this.allocate_str_dedup(version)?; + this.write_pointer(version.ptr(), dest)?; + } _ => return interp_ok(EmulateItemResult::NotSupported), }; diff --git a/src/tools/miri/tests/pass/shims/socket-address.rs b/src/tools/miri/tests/pass/shims/socket-address.rs index af1283739df54..144584b47d91f 100644 --- a/src/tools/miri/tests/pass/shims/socket-address.rs +++ b/src/tools/miri/tests/pass/shims/socket-address.rs @@ -1,5 +1,6 @@ //@ignore-target: windows # No socket address support on Windows //@compile-flags: -Zmiri-disable-isolation +//@normalize-stderr-test: "address resolution failed: .*" -> "address resolution failed: $$MSG" use std::net::{ IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, TcpListener, ToSocketAddrs, @@ -29,5 +30,9 @@ fn test_address_resolution() { } } // We expect an IPv4 and an IPv6 address. - assert!(addr_count == 2) + assert!(addr_count == 2); + + // Resolving an invalid name should error. Needs the port to even hit `getaddrinfo`. + let addr_str = "this-is-not-a-valid-address:80"; + addr_str.to_socket_addrs().unwrap_err(); } diff --git a/src/tools/miri/tests/pass/shims/socket-address.stderr b/src/tools/miri/tests/pass/shims/socket-address.stderr new file mode 100644 index 0000000000000..7091c3b6c6d5a --- /dev/null +++ b/src/tools/miri/tests/pass/shims/socket-address.stderr @@ -0,0 +1,25 @@ +warning: address resolution failed: $MSG + --> RUSTLIB/std/src/sys/net/PLATFORM/socket/mod.rs:LL:CC + | +LL | cvt_gai(c::getaddrinfo(c_host.as_ptr(), ptr::null(), &hints, &mut res)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error during address resolution + | + = note: Miri cannot return proper error information from this call; only a generic error code is being returned + = note: stack backtrace: + 0: std::sys::net::PLATFORM::socket::lookup_host::{closure#0} + at RUSTLIB/std/src/sys/net/PLATFORM/socket/mod.rs:LL:CC + 1: std::sys::helpers::small_c_string::run_with_cstr_stack:: + at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC + 2: std::sys::helpers::small_c_string::run_with_cstr:: + at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC + 3: std::sys::net::PLATFORM::socket::lookup_host + at RUSTLIB/std/src/sys/net/PLATFORM/socket/mod.rs:LL:CC + 4: std::sys::net::PLATFORM::lookup_host_string + at RUSTLIB/std/src/sys/net/PLATFORM/mod.rs:LL:CC + 5: ::to_socket_addrs + at RUSTLIB/std/src/net/socket_addr.rs:LL:CC + 6: test_address_resolution + at tests/pass/shims/socket-address.rs:LL:CC + 7: main + at tests/pass/shims/socket-address.rs:LL:CC + From 2c1e114291964d65beb496903fb4ed530292d237 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 10 May 2026 09:36:44 +0200 Subject: [PATCH 34/37] ./miri install: add --locked by default --- src/tools/miri/miri-script/src/util.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/miri-script/src/util.rs b/src/tools/miri/miri-script/src/util.rs index 6121096f82305..fd8b1958689df 100644 --- a/src/tools/miri/miri-script/src/util.rs +++ b/src/tools/miri/miri-script/src/util.rs @@ -157,7 +157,11 @@ impl MiriEnv { let features = features_to_args(features); // Install binaries to the miri toolchain's `sysroot` so they do not interact with other toolchains. // (Not using `cargo_cmd` as `install` is special and doesn't use `--manifest-path`.) - cmd!(self.sh, "{cargo_bin} +{toolchain} install {cargo_extra_flags...} --path {path} --force --root {sysroot} {features...} {args...}").run()?; + // Adding `--locked` so that behavior is closer to `./miri build`. However, cargo doesn't + // like `--locked --locked` so we need extra logic to avoid that. + let locked_flag = + if cargo_extra_flags.iter().any(|f| f == "--locked") { None } else { Some("--locked") }; + cmd!(self.sh, "{cargo_bin} +{toolchain} install {locked_flag...} {cargo_extra_flags...} --path {path} --force --root {sysroot} {features...} {args...}").run()?; Ok(()) } From 5c0718be117950d24f6652d26ff5756a3cfad19e Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sat, 9 May 2026 20:31:54 +0300 Subject: [PATCH 35/37] fix(reborrow): invalid unreachable in is_known_valid_scrutinee --- .../src/thir/pattern/check_match.rs | 9 ++------- tests/ui/reborrow/reborrow_let_match.rs | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 tests/ui/reborrow/reborrow_let_match.rs diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index 5b786f5a710f8..395eaf55265c0 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -363,13 +363,8 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> { | UpvarRef { .. } | VarRef { .. } | ZstLiteral { .. } - | Yield { .. } => true, - ExprKind::Reborrow { .. } => { - // FIXME(reborrow): matching on a Reborrow expression should be impossible - // currently. Whether this remains to be true, and if the reborrow result then is a - // known valid scrutinee requires further thought. - unreachable!("Reborrow expression in match") - } + | Yield { .. } + | Reborrow { .. } => true, } } diff --git a/tests/ui/reborrow/reborrow_let_match.rs b/tests/ui/reborrow/reborrow_let_match.rs new file mode 100644 index 0000000000000..fac9fba7b0332 --- /dev/null +++ b/tests/ui/reborrow/reborrow_let_match.rs @@ -0,0 +1,14 @@ +//@ check-pass +#![feature(reborrow)] + +use std::marker::Reborrow; + +#[allow(unused)] +struct Thing<'a>(&'a ()); + +impl<'a> Reborrow for Thing<'a> {} + +fn main() { + let x = Thing(&()); + let _y: Thing<'_> = x; +} From a0298aa7e5bccd8cb1a00073ad6ed6cb413eb3a8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 10 May 2026 11:15:38 +0200 Subject: [PATCH 36/37] std fs tests: avoid matching on OS-provided error string --- library/std/src/fs/tests.rs | 55 +++++++------------------------------ 1 file changed, 10 insertions(+), 45 deletions(-) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 7b53f2ead1c0d..facf74f10fb29 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -31,25 +31,6 @@ macro_rules! check { }; } -#[cfg(windows)] -macro_rules! error { - ($e:expr, $s:expr) => { - match $e { - Ok(_) => panic!("Unexpected success. Should've been: {:?}", $s), - Err(ref err) => { - assert!(err.raw_os_error() == Some($s), "`{}` did not have a code of `{}`", err, $s) - } - } - }; -} - -#[cfg(unix)] -macro_rules! error { - ($e:expr, $s:expr) => { - error_contains!($e, $s) - }; -} - macro_rules! error_contains { ($e:expr, $s:expr) => { match $e { @@ -105,14 +86,8 @@ fn file_test_io_smoke_test() { fn invalid_path_raises() { let tmpdir = tmpdir(); let filename = &tmpdir.join("file_that_does_not_exist.txt"); - let result = File::open(filename); - - #[cfg(all(unix, not(target_os = "vxworks")))] - error!(result, "No such file or directory"); - #[cfg(target_os = "vxworks")] - error!(result, "no such file or directory"); - #[cfg(windows)] - error!(result, 2); // ERROR_FILE_NOT_FOUND + let err = File::open(filename).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::NotFound); } #[test] @@ -120,14 +95,8 @@ fn file_test_iounlinking_invalid_path_should_raise_condition() { let tmpdir = tmpdir(); let filename = &tmpdir.join("file_another_file_that_does_not_exist.txt"); - let result = fs::remove_file(filename); - - #[cfg(all(unix, not(target_os = "vxworks")))] - error!(result, "No such file or directory"); - #[cfg(target_os = "vxworks")] - error!(result, "no such file or directory"); - #[cfg(windows)] - error!(result, 2); // ERROR_FILE_NOT_FOUND + let err = fs::remove_file(filename).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::NotFound); } #[test] @@ -939,12 +908,8 @@ fn recursive_rmdir_of_file_fails() { let tmpdir = tmpdir(); let canary = tmpdir.join("do_not_delete"); check!(check!(File::create(&canary)).write(b"foo")); - let result = fs::remove_dir_all(&canary); - #[cfg(unix)] - error!(result, "Not a directory"); - #[cfg(windows)] - error!(result, 267); // ERROR_DIRECTORY - The directory name is invalid. - assert!(result.is_err()); + let err = fs::remove_dir_all(&canary).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::NotADirectory); assert!(canary.exists()); } @@ -1404,6 +1369,7 @@ fn open_flavors() { let mut ra = OO::new(); ra.read(true).append(true); + // This error string is set by std itself so we are not at the whim of the OS here. let invalid_options = "creating or truncating a file requires write or append access"; // Test various combinations of creation modes and access modes. @@ -2148,7 +2114,6 @@ fn test_hidden_file_truncation() { // See https://github.com/rust-lang/rust/pull/131072 for more details about why // these two tests are disabled under Windows 7 here. -#[cfg(windows)] #[test] #[cfg_attr(target_vendor = "win7", ignore = "Unsupported under Windows 7.")] fn test_rename_file_over_open_file() { @@ -2174,10 +2139,9 @@ fn test_rename_file_over_open_file() { } #[test] -#[cfg(windows)] #[cfg_attr(target_vendor = "win7", ignore = "Unsupported under Windows 7.")] fn test_rename_directory_to_non_empty_directory() { - // Renaming a directory over a non-empty existing directory should fail on Windows. + // Renaming a directory over a non-empty existing directory should fail. let tmpdir: TempDir = tmpdir(); let source_path = tmpdir.join("source_directory"); @@ -2188,7 +2152,8 @@ fn test_rename_directory_to_non_empty_directory() { fs::write(target_path.join("target_file.txt"), b"target hello world").unwrap(); - error!(fs::rename(source_path, target_path), 145); // ERROR_DIR_NOT_EMPTY + let err = fs::rename(source_path, target_path).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::DirectoryNotEmpty); } #[test] From f83b976ec4cc1dcd3e90c2350ea77a04ecf6d961 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 10 May 2026 11:24:57 +0200 Subject: [PATCH 37/37] try_errnum_to_io_error: support all POSIX error codes --- src/tools/miri/src/shims/io_error.rs | 97 ++++++++++++++++--- .../tests/pass-dep/libc/libc-strerror_r.rs | 2 +- 2 files changed, 87 insertions(+), 12 deletions(-) diff --git a/src/tools/miri/src/shims/io_error.rs b/src/tools/miri/src/shims/io_error.rs index 8bdbb5522d875..2e5410429efee 100644 --- a/src/tools/miri/src/shims/io_error.rs +++ b/src/tools/miri/src/shims/io_error.rs @@ -103,6 +103,89 @@ const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { ("EAGAIN", WouldBlock), ] }; +// On Unix hosts are can avoid round-tripping via `ErrorKind`, which can preserve more +// details and leads to nicer output in `strerror_r`. +#[cfg(unix)] +const UNIX_ERRNO_TABLE: &[(&str, libc::c_int)] = &[ + ("E2BIG", libc::E2BIG), + ("EACCES", libc::EACCES), + ("EADDRINUSE", libc::EADDRINUSE), + ("EADDRNOTAVAIL", libc::EADDRNOTAVAIL), + ("EAFNOSUPPORT", libc::EAFNOSUPPORT), + ("EAGAIN", libc::EAGAIN), + ("EALREADY", libc::EALREADY), + ("EBADF", libc::EBADF), + ("EBADMSG", libc::EBADMSG), + ("EBUSY", libc::EBUSY), + ("ECANCELED", libc::ECANCELED), + ("ECHILD", libc::ECHILD), + ("ECONNABORTED", libc::ECONNABORTED), + ("ECONNREFUSED", libc::ECONNREFUSED), + ("ECONNRESET", libc::ECONNRESET), + ("EDEADLK", libc::EDEADLK), + ("EDESTADDRREQ", libc::EDESTADDRREQ), + ("EDOM", libc::EDOM), + ("EDQUOT", libc::EDQUOT), + ("EEXIST", libc::EEXIST), + ("EFAULT", libc::EFAULT), + ("EFBIG", libc::EFBIG), + ("EHOSTUNREACH", libc::EHOSTUNREACH), + ("EIDRM", libc::EIDRM), + ("EILSEQ", libc::EILSEQ), + ("EINPROGRESS", libc::EINPROGRESS), + ("EINTR", libc::EINTR), + ("EINVAL", libc::EINVAL), + ("EIO", libc::EIO), + ("EISCONN", libc::EISCONN), + ("EISDIR", libc::EISDIR), + ("ELOOP", libc::ELOOP), + ("EMFILE", libc::EMFILE), + ("EMLINK", libc::EMLINK), + ("EMSGSIZE", libc::EMSGSIZE), + ("EMULTIHOP", libc::EMULTIHOP), + ("ENAMETOOLONG", libc::ENAMETOOLONG), + ("ENETDOWN", libc::ENETDOWN), + ("ENETRESET", libc::ENETRESET), + ("ENETUNREACH", libc::ENETUNREACH), + ("ENFILE", libc::ENFILE), + ("ENOBUFS", libc::ENOBUFS), + ("ENODEV", libc::ENODEV), + ("ENOENT", libc::ENOENT), + ("ENOEXEC", libc::ENOEXEC), + ("ENOLCK", libc::ENOLCK), + ("ENOLINK", libc::ENOLINK), + ("ENOMEM", libc::ENOMEM), + ("ENOMSG", libc::ENOMSG), + ("ENOPROTOOPT", libc::ENOPROTOOPT), + ("ENOSPC", libc::ENOSPC), + ("ENOSYS", libc::ENOSYS), + ("ENOTCONN", libc::ENOTCONN), + ("ENOTDIR", libc::ENOTDIR), + ("ENOTEMPTY", libc::ENOTEMPTY), + ("ENOTRECOVERABLE", libc::ENOTRECOVERABLE), + ("ENOTSOCK", libc::ENOTSOCK), + ("ENOTSUP", libc::ENOTSUP), + ("ENOTTY", libc::ENOTTY), + ("ENXIO", libc::ENXIO), + ("EOPNOTSUPP", libc::EOPNOTSUPP), + ("EOVERFLOW", libc::EOVERFLOW), + ("EOWNERDEAD", libc::EOWNERDEAD), + ("EPERM", libc::EPERM), + ("EPIPE", libc::EPIPE), + ("EPROTO", libc::EPROTO), + ("EPROTONOSUPPORT", libc::EPROTONOSUPPORT), + ("EPROTOTYPE", libc::EPROTOTYPE), + ("ERANGE", libc::ERANGE), + ("EROFS", libc::EROFS), + ("ESOCKTNOSUPPORT", libc::ESOCKTNOSUPPORT), + ("ESPIPE", libc::ESPIPE), + ("ESRCH", libc::ESRCH), + ("ESTALE", libc::ESTALE), + ("ETIMEDOUT", libc::ETIMEDOUT), + ("ETXTBSY", libc::ETXTBSY), + ("EWOULDBLOCK", libc::EWOULDBLOCK), + ("EXDEV", libc::EXDEV), +]; // This mapping should match `decode_error_kind` in // . const WINDOWS_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { @@ -289,17 +372,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // That lets us use `Error::from_raw_os_error`, which has a much better `Display` // impl than what we get by going through `ErrorKind`. #[cfg(unix)] - { - // For now, we only add the constants we need to make `std` tests happy. - const ERRNOS: &[(&str, libc::c_int)] = &[ - ("ENOENT", libc::ENOENT), - ("ENOTDIR", libc::ENOTDIR), - ("ENOTSOCK", libc::ENOTSOCK), - ]; - for &(name, errno) in ERRNOS { - if target_errnum == this.eval_libc_i32(name) { - return interp_ok(Some(io::Error::from_raw_os_error(errno))); - } + for &(name, errno) in UNIX_ERRNO_TABLE { + if target_errnum == this.eval_libc_i32(name) { + return interp_ok(Some(io::Error::from_raw_os_error(errno))); } } // For other hosts or other constants, we fall back to translating via `ErrorKind`. diff --git a/src/tools/miri/tests/pass-dep/libc/libc-strerror_r.rs b/src/tools/miri/tests/pass-dep/libc/libc-strerror_r.rs index 09885ce839d0f..d356a1c642454 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-strerror_r.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-strerror_r.rs @@ -2,7 +2,7 @@ fn main() { unsafe { - let mut buf = vec![0u8; 32]; + let mut buf = vec![0u8; 64]; assert_eq!(libc::strerror_r(libc::EPERM, buf.as_mut_ptr().cast(), buf.len()), 0); let mut buf2 = vec![0u8; 64]; assert_eq!(libc::strerror_r(-1i32, buf2.as_mut_ptr().cast(), buf2.len()), 0);