From 5d2de1b7c35ded9d24959de00f55659d9c7d6399 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 2 May 2026 14:23:15 +0200 Subject: [PATCH 01/12] miri: require (almost) all 1-ZST arguments to be actually passed --- .../rustc_const_eval/src/interpret/call.rs | 86 +++++++++++-------- .../tests/fail/c-variadic-ignored-argument.rs | 13 +++ .../fail/c-variadic-ignored-argument.stderr | 18 ++++ .../abi_mismatch_closure_non_capturing.rs | 19 ++++ .../abi_mismatch_closure_non_capturing.stderr | 26 ++++++ .../tests/fail/validity/fn_arg_never_type.rs | 13 +++ .../fail/validity/fn_arg_never_type.stderr | 18 ++++ .../tests/pass/c-variadic-ignored-argument.rs | 21 ----- .../tests/pass/function_calls/abi_compat.rs | 5 ++ 9 files changed, 164 insertions(+), 55 deletions(-) create mode 100644 src/tools/miri/tests/fail/c-variadic-ignored-argument.rs create mode 100644 src/tools/miri/tests/fail/c-variadic-ignored-argument.stderr create mode 100644 src/tools/miri/tests/fail/function_pointers/abi_mismatch_closure_non_capturing.rs create mode 100644 src/tools/miri/tests/fail/function_pointers/abi_mismatch_closure_non_capturing.stderr create mode 100644 src/tools/miri/tests/fail/validity/fn_arg_never_type.rs create mode 100644 src/tools/miri/tests/fail/validity/fn_arg_never_type.stderr delete mode 100644 src/tools/miri/tests/pass/c-variadic-ignored-argument.rs diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 9fa10a4f0ebbe..6c70cedef708e 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -273,8 +273,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { caller_args: &mut impl Iterator< Item = (&'x FnArg<'tcx, M::Provenance>, &'y ArgAbi<'tcx, Ty<'tcx>>), >, - callee_abi: &ArgAbi<'tcx, Ty<'tcx>>, - callee_arg_idx: usize, + callee_args_abis: &mut impl Iterator>)>, callee_arg: &mir::Place<'tcx>, callee_ty: Ty<'tcx>, already_live: bool, @@ -283,15 +282,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { 'tcx: 'x, 'tcx: 'y, { + // Get next callee arg. + let (callee_arg_idx, callee_abi) = callee_args_abis.next().unwrap(); assert_eq!(callee_ty, callee_abi.layout.ty); - if callee_abi.is_ignore() { - // This one is skipped. Still must be made live though! - if !already_live { - self.storage_live(callee_arg.as_local().unwrap())?; - } - return interp_ok(()); - } - // Find next caller arg. + // Get next caller arg. let Some((caller_arg, caller_abi)) = caller_args.next() else { throw_ub_format!("calling a function with fewer arguments than it requires"); }; @@ -349,6 +343,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { mut cont: ReturnContinuation, ) -> InterpResult<'tcx> { let _trace = enter_trace_span!(M, step::init_stack_frame, %instance, tracing_separate_thread = Empty); + let def_id = instance.def_id(); // The first order of business is to figure out the callee signature. // However, that requires the list of variadic arguments. @@ -424,10 +419,25 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { "spread_arg: {:?}, locals: {:#?}", body.spread_arg, body.args_iter() - .map(|local| (local, self.layout_of_local(self.frame(), local, None).unwrap().ty,)) + .map(|local| (local, self.layout_of_local(self.frame(), local, None).unwrap().ty)) .collect::>() ); + // Determine whether there is a special VaList argument. This is always the + // last argument, and since arguments start at index 1 that's `arg_count`. + let va_list_arg = callee_fn_abi.c_variadic.then(|| mir::Local::from_usize(body.arg_count)); + // Determine whether this is a non-capturing closure. That's relevant as their first + // argument can be skipped (and that's the only kind of argument skipping we allow). + let is_non_capturing_closure = + (matches!(instance.def, ty::InstanceKind::ClosureOnceShim { .. }) + || self.tcx.is_closure_like(def_id)) + && { + let arg = &callee_fn_abi.args[0]; + matches!(arg.layout.ty.kind(), ty::Closure (_def, closure_args) if { + closure_args.as_closure().upvar_tys().is_empty() + }) + }; + // In principle, we have two iterators: Where the arguments come from, and where // they go to. @@ -440,21 +450,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { caller_fn_abi.args.len(), "mismatch between caller ABI and caller arguments", ); - let mut caller_args = args - .iter() - .zip(caller_fn_abi.args.iter()) - .filter(|arg_and_abi| !arg_and_abi.1.is_ignore()); + let mut caller_args = args.iter().zip(caller_fn_abi.args.iter()); // Now we have to spread them out across the callee's locals, // taking into account the `spread_arg`. If we could write // this is a single iterator (that handles `spread_arg`), then - // `pass_argument` would be the loop body. It takes care to - // not advance `caller_iter` for ignored arguments. + // `pass_argument` would be the loop body. let mut callee_args_abis = callee_fn_abi.args.iter().enumerate(); - // Determine whether there is a special VaList argument. This is always the - // last argument, and since arguments start at index 1 that's `arg_count`. - let va_list_arg = callee_fn_abi.c_variadic.then(|| mir::Local::from_usize(body.arg_count)); - // During argument passing, we want retagging with protectors. M::with_retag_mode(self, RetagMode::FnEntry, |ecx| { for local in body.args_iter() { @@ -467,7 +469,32 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // type, but the result gets cached so this avoids calling the instantiation // query *again* the next time this local is accessed. let ty = ecx.layout_of_local(ecx.frame(), local, None)?.ty; - if Some(local) == va_list_arg { + + // Some arguments are special: the first (`self`) argument of a non-capturing + // closure; the va_list argument; and the spread_arg. + if is_non_capturing_closure && local == mir::Local::arg(0) { + assert!(va_list_arg.is_none()); + assert!(Some(local) != body.spread_arg); + // This argument might be missing on the caller side. So just initialize it in + // the callee. + let (callee_arg_idx, callee_abi) = callee_args_abis.next().unwrap(); + assert!(callee_abi.layout.is_1zst() && callee_abi.is_ignore()); + ecx.storage_live(local)?; + // And skip it in the caller, if present. We can tell whether it is present by + // comparing the number of arguments on the caller and callee side. + if caller_fn_abi.args.len() == callee_fn_abi.args.len() { + let (_caller_arg, caller_abi) = caller_args.next().unwrap(); + if !caller_abi.layout.is_1zst() { + // The caller gave us some other, non-ignorable argument. + throw_ub!(AbiMismatchArgument { + arg_idx: callee_arg_idx, + caller_ty: caller_abi.layout.ty, + callee_ty: callee_abi.layout.ty + }); + } + assert!(caller_abi.is_ignore()); + } + } else if Some(local) == va_list_arg { // This is the last callee-side argument of a variadic function. // This argument is a VaList holding the remaining caller-side arguments. ecx.storage_live(local)?; @@ -477,12 +504,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Consume the remaining arguments by putting them into the variable argument // list. - let varargs = ecx.allocate_varargs( - &mut caller_args, - // "Ignored" arguments aren't actually passed, so the callee should also - // ignore them. (`pass_argument` does this for regular arguments.) - (&mut callee_args_abis).filter(|(_, abi)| !abi.is_ignore()), - )?; + let varargs = ecx.allocate_varargs(&mut caller_args, &mut callee_args_abis)?; // When the frame is dropped, these variable arguments are deallocated. ecx.frame_mut().va_list = varargs.clone(); let key = ecx.va_list_ptr(varargs.into()); @@ -508,11 +530,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { &[mir::ProjectionElem::Field(FieldIdx::from_usize(i), field_ty)], *ecx.tcx, ); - let (idx, callee_abi) = callee_args_abis.next().unwrap(); ecx.pass_argument( &mut caller_args, - callee_abi, - idx, + &mut callee_args_abis, &dest, field_ty, /* already_live */ true, @@ -520,11 +540,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } } else { // Normal argument. Cannot mark it as live yet, it might be unsized! - let (idx, callee_abi) = callee_args_abis.next().unwrap(); ecx.pass_argument( &mut caller_args, - callee_abi, - idx, + &mut callee_args_abis, &dest, ty, /* already_live */ false, diff --git a/src/tools/miri/tests/fail/c-variadic-ignored-argument.rs b/src/tools/miri/tests/fail/c-variadic-ignored-argument.rs new file mode 100644 index 0000000000000..77dab904fdbba --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic-ignored-argument.rs @@ -0,0 +1,13 @@ +#![feature(c_variadic)] + +// While 1-ZST are currently ignored on most ABIs, we don't guarantee that, and it's UB to +// rely on it. + +fn main() { + unsafe extern "C" fn variadic(mut ap: ...) { + ap.next_arg::(); + ap.next_arg::(); //~ERROR: requested `i32` is incompatible with next argument of type `()` + } + + unsafe { variadic(0i32, (), 1i32) } +} diff --git a/src/tools/miri/tests/fail/c-variadic-ignored-argument.stderr b/src/tools/miri/tests/fail/c-variadic-ignored-argument.stderr new file mode 100644 index 0000000000000..6c1291611c4ae --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic-ignored-argument.stderr @@ -0,0 +1,18 @@ +error: Undefined Behavior: va_arg type mismatch: requested `i32` is incompatible with next argument of type `()` + --> tests/fail/c-variadic-ignored-argument.rs:LL:CC + | +LL | ap.next_arg::(); + | ^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = 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: main::variadic + at tests/fail/c-variadic-ignored-argument.rs:LL:CC + 1: main + at tests/fail/c-variadic-ignored-argument.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_closure_non_capturing.rs b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_closure_non_capturing.rs new file mode 100644 index 0000000000000..064b5a510f3ea --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_closure_non_capturing.rs @@ -0,0 +1,19 @@ +#![feature(fn_traits, unboxed_closures)] + +use std::mem::transmute; + +#[repr(align(4))] +struct Zst; + +fn foo(_: F) { + // Calls the given F FnOnce, but passing an over-aligned ZST instead of the closure / function item + let f = unsafe { transmute::(F::call_once) }; + f(Zst) +} + +fn main() { + foo(move || { + //~^ERROR: /calling a function whose parameter #1 has type .* passing argument of type Zst/ + println!("non-capturing closure"); + }); +} diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_closure_non_capturing.stderr b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_closure_non_capturing.stderr new file mode 100644 index 0000000000000..53a155b8f8c7b --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_closure_non_capturing.stderr @@ -0,0 +1,26 @@ +error: Undefined Behavior: calling a function whose parameter #1 has type {closure@tests/fail/function_pointers/abi_mismatch_closure_non_capturing.rs:LL:CC} passing argument of type Zst + --> tests/fail/function_pointers/abi_mismatch_closure_non_capturing.rs:LL:CC + | +LL | foo(move || { + | _________^ +LL | | +LL | | println!("non-capturing closure"); +LL | | }); + | |_____^ Undefined Behavior occurred here + | + = 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 + = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets + = help: if you think this code should be accepted anyway, please report an issue with Miri + = note: stack backtrace: + 0: main::{closure#0} + at tests/fail/function_pointers/abi_mismatch_closure_non_capturing.rs:LL:CC + 1: foo + at tests/fail/function_pointers/abi_mismatch_closure_non_capturing.rs:LL:CC + 2: main + at tests/fail/function_pointers/abi_mismatch_closure_non_capturing.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/validity/fn_arg_never_type.rs b/src/tools/miri/tests/fail/validity/fn_arg_never_type.rs new file mode 100644 index 0000000000000..a6ffb325191b0 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/fn_arg_never_type.rs @@ -0,0 +1,13 @@ +use std::mem::transmute; + +enum Never {} + +fn foo(x: Never) { //~ERROR: invalid value of type Never + let ptr = &raw const x; + println!("{ptr:p}"); +} + +fn main() { + let f = unsafe { transmute::(foo) }; + f(()); +} diff --git a/src/tools/miri/tests/fail/validity/fn_arg_never_type.stderr b/src/tools/miri/tests/fail/validity/fn_arg_never_type.stderr new file mode 100644 index 0000000000000..36ffcf24790a3 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/fn_arg_never_type.stderr @@ -0,0 +1,18 @@ +error: Undefined Behavior: constructing invalid value of type Never: encountered a value of uninhabited type `Never` + --> tests/fail/validity/fn_arg_never_type.rs:LL:CC + | +LL | fn foo(x: Never) { + | ^ Undefined Behavior occurred here + | + = 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: foo + at tests/fail/validity/fn_arg_never_type.rs:LL:CC + 1: main + at tests/fail/validity/fn_arg_never_type.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/pass/c-variadic-ignored-argument.rs b/src/tools/miri/tests/pass/c-variadic-ignored-argument.rs deleted file mode 100644 index fe09a03edfffc..0000000000000 --- a/src/tools/miri/tests/pass/c-variadic-ignored-argument.rs +++ /dev/null @@ -1,21 +0,0 @@ -//@ ignore-target: windows # does not ignore ZST arguments -//@ ignore-target: powerpc # does not ignore ZST arguments -//@ ignore-target: s390x # does not ignore ZST arguments -//@ ignore-target: sparc # does not ignore ZST arguments -#![feature(c_variadic)] - -// Some platforms ignore ZSTs, meaning that the argument is not passed, even though it is part -// of the callee's ABI. Test that this doesn't trip any asserts. -// -// NOTE: this test only succeeds when the `()` argument uses `Passmode::Ignore`. For some targets, -// notably msvc, such arguments are not ignored, which would cause UB when attempting to read the -// second `i32` argument while the next item in the variable argument list is `()`. - -fn main() { - unsafe extern "C" fn variadic(mut ap: ...) { - ap.next_arg::(); - ap.next_arg::(); - } - - unsafe { variadic(0i32, (), 1i32) } -} 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..2aea068e0d52c 100644 --- a/src/tools/miri/tests/pass/function_calls/abi_compat.rs +++ b/src/tools/miri/tests/pass/function_calls/abi_compat.rs @@ -151,4 +151,9 @@ fn main() { let rc = Rc::new(0); let rc_ptr: *mut i32 = unsafe { mem::transmute_copy(&rc) }; test_abi_compat(rc, rc_ptr); + + // Non-capturing closures are special because we rely on them being `PassMode::Ignore`. + // Make sure that does not break newtype wrapping for them. + let non_capturing_closure = || {}; + test_abi_compat(non_capturing_closure.clone(), Wrapper(non_capturing_closure)); } From 54ee0eec44b99d32ad4c7aa164d3aaa3748c472b Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Wed, 13 May 2026 14:11:04 +0800 Subject: [PATCH 02/12] Force copy `rustc-dev` artifacts for rustfmt/clippy under download-rustc `compile::Rustc`'s sysroot copying logic intentionally does not implicitly copy `rustc-dev` artifacts unless explicitly requested through `builder.ensure(compile::Rustc)`, due to a previous issue RUST-108767: ```text // NOTE(#108767): We intentionally don't copy `rustc-dev` artifacts until they're // requested with `builder.ensure(Rustc)`. This fixes an issue where we'd have multiple // copies of libc in the sysroot with no way to tell which to load. There are a few // quirks of bootstrap that interact to make this reliable: // 1. The order `Step`s are run is hard-coded in `builder.rs` and not configurable. This // avoids e.g. reordering `test::UiFulldeps` before `test::Ui` and causing the latter // to fail because of duplicate metadata. // 2. The sysroot is deleted and recreated between each invocation, so running `x test // ui-fulldeps && x test ui` can't cause failures. ``` So, for rustfmt/clippy, we insert intentionally explicit `builder.ensure(compile::Rustc)` as a short-term band-aid, leaving FIXMEs pointing to RUST-156525 to investigate if the multiple libc copies is still a problem and if that can be fixed properly. This is by no means a proper fix, but it should unblock local tool profile workflows trying to use `download-rustc` with {rustfmt,clippy}. --- src/bootstrap/src/core/build_steps/compile.rs | 14 +++++++++----- src/bootstrap/src/core/build_steps/test.rs | 10 ++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 68a4f928464f1..3b497e1db923c 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -1999,12 +1999,16 @@ impl Step for Sysroot { } // Copy the compiler into the correct sysroot. - // NOTE(#108767): We intentionally don't copy `rustc-dev` artifacts until they're requested with `builder.ensure(Rustc)`. - // This fixes an issue where we'd have multiple copies of libc in the sysroot with no way to tell which to load. - // There are a few quirks of bootstrap that interact to make this reliable: + // + // FIXME(#156525): investigate if this is still needed. + // + // NOTE(#108767): We intentionally don't copy `rustc-dev` artifacts until they're + // requested with `builder.ensure(Rustc)`. This fixes an issue where we'd have multiple + // copies of libc in the sysroot with no way to tell which to load. There are a few + // quirks of bootstrap that interact to make this reliable: // 1. The order `Step`s are run is hard-coded in `builder.rs` and not configurable. This - // avoids e.g. reordering `test::UiFulldeps` before `test::Ui` and causing the latter to - // fail because of duplicate metadata. + // avoids e.g. reordering `test::UiFulldeps` before `test::Ui` and causing the latter + // to fail because of duplicate metadata. // 2. The sysroot is deleted and recreated between each invocation, so running `x test // ui-fulldeps && x test ui` can't cause failures. let mut filtered_files = Vec::new(); diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index c28fe4ad832d3..fb45fcf5dcc42 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -579,6 +579,11 @@ impl Step for Rustfmt { let build_compiler = self.compilers.build_compiler(); let target = self.compilers.target(); + // FIXME(#156525): `compile::Sysroot::run` intentionally do not copy `rustc-dev` artifacts + // until they're requested with `builder.ensure(Rustc)`, relevant for `download-rustc` + // flows. + builder.ensure(compile::Rustc::new(build_compiler, target)); + let mut cargo = tool::prepare_tool_cargo( builder, build_compiler, @@ -941,6 +946,11 @@ impl Step for Clippy { let target_compiler = self.compilers.target_compiler(); let build_compiler = self.compilers.build_compiler(); + // FIXME(#156525): `compile::Sysroot::run` intentionally do not copy `rustc-dev` artifacts + // until they're requested with `builder.ensure(Rustc)`, relevant for `download-rustc` + // flows. + builder.ensure(compile::Rustc::new(build_compiler, target)); + let mut cargo = tool::prepare_tool_cargo( builder, build_compiler, From 9e3e1ca9abbeb3bc46f132a90ee307c9705ae213 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Sun, 24 May 2026 14:23:13 +0200 Subject: [PATCH 03/12] Rename a confusing local --- src/bootstrap/src/core/build_steps/run.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/run.rs b/src/bootstrap/src/core/build_steps/run.rs index 2329bb93b4d3d..935844daa9d7c 100644 --- a/src/bootstrap/src/core/build_steps/run.rs +++ b/src/bootstrap/src/core/build_steps/run.rs @@ -510,7 +510,7 @@ impl Step for Rustfmt { let compilers = RustcPrivateCompilers::new(builder, stage, host); let rustfmt_build = builder.ensure(tool::Rustfmt::from_compilers(compilers)); - let mut rustfmt = tool::prepare_tool_cargo( + let mut cargo = tool::prepare_tool_cargo( builder, rustfmt_build.build_compiler, Mode::ToolRustcPrivate, @@ -521,10 +521,10 @@ impl Step for Rustfmt { &[], ); - rustfmt.args(["--bin", "rustfmt", "--"]); - rustfmt.args(builder.config.args()); + cargo.args(["--bin", "rustfmt", "--"]); + cargo.args(builder.config.args()); - rustfmt.into_cmd().run(builder); + cargo.into_cmd().run(builder); } } From 71487e8d2199a765b7460c6bfc5ffe41b59f57a8 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Sun, 24 May 2026 14:23:53 +0200 Subject: [PATCH 04/12] Explicitly add rustc libs to path for `RustcPrivate` tools Centrally in `tool::prepare_tool_cargo`. So that `./x run rustfmt` + `download-rustc` can find the correct rustc libs. --- src/bootstrap/src/core/build_steps/tool.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index ed5c2586a5ed6..a75d5e4db8998 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -224,6 +224,12 @@ pub fn prepare_tool_cargo( // avoid rebuilding when running tests. cargo.env("SYSROOT", builder.sysroot(compiler)); + // Make sure we explicitly add rustc_private libs to path centrally here so that + // RustcPrivate tools can pick them up. + if mode == Mode::ToolRustcPrivate { + cargo.add_rustc_lib_path(builder); + } + // if tools are using lzma we want to force the build script to build its // own copy cargo.env("LZMA_API_STATIC", "1"); From fed6279325de02dfa753b9e9a6d968f5a4ef428f Mon Sep 17 00:00:00 2001 From: Hanna Kruppe Date: Fri, 22 May 2026 14:45:46 +0200 Subject: [PATCH 05/12] library: use strict provenance lints consistently The `fuzzy_provenance_casts` lint is enabled in most of the standard library, but its identical twin `lossy_provenance_casts` was not. As discussed in the tracking issue for those lints, there doesn't seem to be any good reason to enable one without the other. This PR applies this principle and as a result removes some unnecessary ptr->int `as` casts. It's also preparation for merging the two lints, which removes the option of only enabling `fuzzy_provenance_casts`. --- library/alloc/src/lib.rs | 1 + library/alloctests/benches/lib.rs | 1 + library/alloctests/tests/boxed.rs | 4 ++-- library/alloctests/tests/heap.rs | 2 +- library/alloctests/tests/lib.rs | 1 + library/alloctests/tests/sort/tests.rs | 2 +- library/alloctests/tests/vec.rs | 8 ++++---- library/core/src/lib.rs | 1 + library/core/src/ptr/const_ptr.rs | 1 + library/core/src/ptr/mod.rs | 6 +++--- library/core/src/ptr/mut_ptr.rs | 1 + library/coretests/tests/char.rs | 4 ++-- library/coretests/tests/lib.rs | 1 + library/coretests/tests/ptr.rs | 2 +- library/coretests/tests/slice.rs | 2 +- library/coretests/tests/waker.rs | 6 +++--- library/std/src/lib.rs | 9 ++++++++- library/std/src/sync/mpmc/select.rs | 2 +- library/std/src/sync/mpmc/zero.rs | 2 +- library/std/src/sys/args/sgx.rs | 3 ++- library/std/src/sys/env/sgx.rs | 3 ++- library/std/src/sys/pal/sgx/mod.rs | 3 ++- library/std/src/sys/thread_local/key/tests.rs | 4 ++-- library/std/src/thread/tests.rs | 6 +++--- 24 files changed, 46 insertions(+), 29 deletions(-) diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 5fe5464ab2cdd..e93c1336a7b53 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -73,6 +73,7 @@ // Lints: #![deny(unsafe_op_in_unsafe_fn)] #![deny(fuzzy_provenance_casts)] +#![deny(lossy_provenance_casts)] #![warn(deprecated_in_future)] #![warn(missing_debug_implementations)] #![warn(missing_docs)] diff --git a/library/alloctests/benches/lib.rs b/library/alloctests/benches/lib.rs index b7e09fc2e162b..2be7a24d2de7b 100644 --- a/library/alloctests/benches/lib.rs +++ b/library/alloctests/benches/lib.rs @@ -7,6 +7,7 @@ #![feature(strict_provenance_lints)] #![feature(test)] #![deny(fuzzy_provenance_casts)] +#![deny(lossy_provenance_casts)] extern crate test; diff --git a/library/alloctests/tests/boxed.rs b/library/alloctests/tests/boxed.rs index 83fd1ef7449a3..41722155f864b 100644 --- a/library/alloctests/tests/boxed.rs +++ b/library/alloctests/tests/boxed.rs @@ -47,9 +47,9 @@ fn box_clone_from_ptr_stability() { for size in (0..8).map(|i| 2usize.pow(i)) { let control = vec![Dummy { _data: 42 }; size].into_boxed_slice(); let mut copy = vec![Dummy { _data: 84 }; size].into_boxed_slice(); - let copy_raw = copy.as_ptr() as usize; + let copy_raw = copy.as_ptr(); copy.clone_from(&control); - assert_eq!(copy.as_ptr() as usize, copy_raw); + assert_eq!(copy.as_ptr(), copy_raw); } } diff --git a/library/alloctests/tests/heap.rs b/library/alloctests/tests/heap.rs index 246b341eeb387..8eb562622c0a4 100644 --- a/library/alloctests/tests/heap.rs +++ b/library/alloctests/tests/heap.rs @@ -25,7 +25,7 @@ fn check_overalign_requests(allocator: T) { .collect(); for &ptr in &pointers { assert_eq!( - (ptr.as_non_null_ptr().as_ptr() as usize) % align, + ptr.as_non_null_ptr().as_ptr().addr() % align, 0, "Got a pointer less aligned than requested" ) diff --git a/library/alloctests/tests/lib.rs b/library/alloctests/tests/lib.rs index 699a5010282b0..01dd7a6e3f48d 100644 --- a/library/alloctests/tests/lib.rs +++ b/library/alloctests/tests/lib.rs @@ -43,6 +43,7 @@ #![feature(vec_try_remove)] #![allow(internal_features)] #![deny(fuzzy_provenance_casts)] +#![deny(lossy_provenance_casts)] #![deny(unsafe_op_in_unsafe_fn)] extern crate alloc; diff --git a/library/alloctests/tests/sort/tests.rs b/library/alloctests/tests/sort/tests.rs index 09b76773d6b24..ec4c4fa619ddf 100644 --- a/library/alloctests/tests/sort/tests.rs +++ b/library/alloctests/tests/sort/tests.rs @@ -746,7 +746,7 @@ fn self_cmp( pattern_fn(len).into_iter().map(|val| type_into_fn(val)).collect::>(); let comparison_fn = |a: &T, b: &T| { - assert_ne!(a as *const T as usize, b as *const T as usize); + assert_ne!(a as *const T, b as *const T); a.cmp(b) }; diff --git a/library/alloctests/tests/vec.rs b/library/alloctests/tests/vec.rs index d85d2e44cd2ba..7ae8c80963319 100644 --- a/library/alloctests/tests/vec.rs +++ b/library/alloctests/tests/vec.rs @@ -1110,7 +1110,7 @@ fn test_into_iter_zst() { struct AlignedZstWithDrop([u64; 0]); impl Drop for AlignedZstWithDrop { fn drop(&mut self) { - let addr = self as *mut _ as usize; + let addr = (self as *mut Self).addr(); assert!(hint::black_box(addr) % align_of::() == 0); } } @@ -1356,10 +1356,10 @@ fn overaligned_allocations() { for i in 0..0x1000 { v.reserve_exact(i); assert!(v[0].0 == 273); - assert!(v.as_ptr() as usize & 0xff == 0); + assert!(v.as_ptr().addr() & 0xff == 0); v.shrink_to_fit(); assert!(v[0].0 == 273); - assert!(v.as_ptr() as usize & 0xff == 0); + assert!(v.as_ptr().addr() & 0xff == 0); } } @@ -2574,7 +2574,7 @@ fn test_box_zero_allocator() { unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { if layout.size() == 0 { - let addr = ptr.as_ptr() as usize; + let addr = ptr.as_ptr().addr(); let mut state = self.state.borrow_mut(); std::println!("freeing {addr}"); assert!(state.0.remove(&addr), "ZST free that wasn't allocated"); diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index acc758a75e77b..9d9837c846de3 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -80,6 +80,7 @@ #![deny(rust_2021_incompatible_or_patterns)] #![deny(unsafe_op_in_unsafe_fn)] #![deny(fuzzy_provenance_casts)] +#![deny(lossy_provenance_casts)] #![warn(deprecated_in_future)] #![warn(missing_debug_implementations)] #![warn(missing_docs)] diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs index 8b7b08bf82317..8504d9b5e217d 100644 --- a/library/core/src/ptr/const_ptr.rs +++ b/library/core/src/ptr/const_ptr.rs @@ -183,6 +183,7 @@ impl *const T { /// [`with_exposed_provenance`]: with_exposed_provenance #[inline(always)] #[stable(feature = "exposed_provenance", since = "1.84.0")] + #[expect(lossy_provenance_casts, reason = "this *is* the replacement")] pub fn expose_provenance(self) -> usize { self.cast::<()>() as usize } diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index 15fc36332baca..89a054b7272d8 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -2588,21 +2588,21 @@ impl Ord for F { #[stable(feature = "fnptr_impls", since = "1.4.0")] impl hash::Hash for F { fn hash(&self, state: &mut HH) { - state.write_usize(self.addr() as _) + state.write_usize(self.addr().addr()) } } #[stable(feature = "fnptr_impls", since = "1.4.0")] impl fmt::Pointer for F { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::pointer_fmt_inner(self.addr() as _, f) + fmt::pointer_fmt_inner(self.addr().addr(), f) } } #[stable(feature = "fnptr_impls", since = "1.4.0")] impl fmt::Debug for F { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::pointer_fmt_inner(self.addr() as _, f) + fmt::pointer_fmt_inner(self.addr().addr(), f) } } diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs index 98b70a77fad7b..b9a1381c4ed8f 100644 --- a/library/core/src/ptr/mut_ptr.rs +++ b/library/core/src/ptr/mut_ptr.rs @@ -174,6 +174,7 @@ impl *mut T { /// [`with_exposed_provenance_mut`]: with_exposed_provenance_mut #[inline(always)] #[stable(feature = "exposed_provenance", since = "1.84.0")] + #[expect(lossy_provenance_casts, reason = "this *is* the replacement")] pub fn expose_provenance(self) -> usize { self.cast::<()>() as usize } diff --git a/library/coretests/tests/char.rs b/library/coretests/tests/char.rs index 877017f682c97..43372005ad5f3 100644 --- a/library/coretests/tests/char.rs +++ b/library/coretests/tests/char.rs @@ -318,7 +318,7 @@ fn test_encode_utf8() { let mut buf = [0; char::MAX_LEN_UTF8]; let ptr = buf.as_ptr(); let s = input.encode_utf8(&mut buf); - assert_eq!(s.as_ptr() as usize, ptr as usize); + assert_eq!(s.as_ptr(), ptr); assert!(str::from_utf8(s.as_bytes()).is_ok()); assert_eq!(s.as_bytes(), expect); } @@ -335,7 +335,7 @@ fn test_encode_utf16() { let mut buf = [0; 2]; let ptr = buf.as_mut_ptr(); let b = input.encode_utf16(&mut buf); - assert_eq!(b.as_mut_ptr() as usize, ptr as usize); + assert_eq!(b.as_mut_ptr(), ptr); assert_eq!(b, expect); } diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 18e09c707ebad..64c53756acead 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -129,6 +129,7 @@ // tidy-alphabetical-end #![allow(internal_features)] #![deny(fuzzy_provenance_casts)] +#![deny(lossy_provenance_casts)] #![deny(unsafe_op_in_unsafe_fn)] /// Version of `assert_matches` that ignores fancy runtime printing in const context and uses structural equality. diff --git a/library/coretests/tests/ptr.rs b/library/coretests/tests/ptr.rs index 93f9454d71378..9dbaf6690c81c 100644 --- a/library/coretests/tests/ptr.rs +++ b/library/coretests/tests/ptr.rs @@ -384,7 +384,7 @@ fn align_offset_stride_one() { #[test] fn align_offset_various_strides() { unsafe fn test_stride(ptr: *const T, align: usize) -> bool { - let numptr = ptr as usize; + let numptr = ptr.addr(); let mut expected = usize::MAX; // Naive but definitely correct way to find the *first* aligned element of stride::. for el in 0..align { diff --git a/library/coretests/tests/slice.rs b/library/coretests/tests/slice.rs index 2bb62f36bb0e6..a4db7304fff90 100644 --- a/library/coretests/tests/slice.rs +++ b/library/coretests/tests/slice.rs @@ -1886,7 +1886,7 @@ fn test_align_to_empty_mid() { type Chunk = u32; for offset in 0..4 { let (_, mid, _) = unsafe { bytes[offset..offset + 1].align_to::() }; - assert_eq!(mid.as_ptr() as usize % align_of::(), 0); + assert_eq!(mid.as_ptr().addr() % align_of::(), 0); } } diff --git a/library/coretests/tests/waker.rs b/library/coretests/tests/waker.rs index 4889b8959ece4..be8b07b8ad009 100644 --- a/library/coretests/tests/waker.rs +++ b/library/coretests/tests/waker.rs @@ -5,16 +5,16 @@ use std::task::{RawWaker, RawWakerVTable, Waker}; fn test_waker_getters() { let raw_waker = RawWaker::new(ptr::without_provenance_mut(42usize), &WAKER_VTABLE); let waker = unsafe { Waker::from_raw(raw_waker) }; - assert_eq!(waker.data() as usize, 42); + assert_eq!(waker.data().addr(), 42); assert!(ptr::eq(waker.vtable(), &WAKER_VTABLE)); let waker2 = waker.clone(); - assert_eq!(waker2.data() as usize, 43); + assert_eq!(waker2.data().addr(), 43); assert!(ptr::eq(waker2.vtable(), &WAKER_VTABLE)); } static WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new( - |data| RawWaker::new(ptr::without_provenance_mut(data as usize + 1), &WAKER_VTABLE), + |data| RawWaker::new(ptr::without_provenance_mut(data.addr() + 1), &WAKER_VTABLE), |_| {}, |_| {}, |_| {}, diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index cb0f8edb7b852..e2fe7d4aa5448 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -246,6 +246,7 @@ #![allow(unused_lifetimes)] #![allow(internal_features)] #![deny(fuzzy_provenance_casts)] +#![deny(lossy_provenance_casts)] #![deny(unsafe_op_in_unsafe_fn)] #![allow(rustdoc::redundant_explicit_links)] #![warn(rustdoc::unescaped_backticks)] @@ -714,7 +715,13 @@ pub mod alloc; mod panicking; #[path = "../../backtrace/src/lib.rs"] -#[allow(dead_code, unused_attributes, fuzzy_provenance_casts, unsafe_op_in_unsafe_fn)] +#[allow( + dead_code, + unused_attributes, + fuzzy_provenance_casts, + lossy_provenance_casts, + unsafe_op_in_unsafe_fn +)] mod backtrace_rs; #[stable(feature = "cfg_select", since = "1.95.0")] diff --git a/library/std/src/sync/mpmc/select.rs b/library/std/src/sync/mpmc/select.rs index 56a83fee2e119..ff537aa686157 100644 --- a/library/std/src/sync/mpmc/select.rs +++ b/library/std/src/sync/mpmc/select.rs @@ -22,7 +22,7 @@ impl Operation { /// and is alive for the entire duration of a blocking operation. #[inline] pub fn hook(r: &mut T) -> Operation { - let val = r as *mut T as usize; + let val = (r as *mut T).addr(); // Make sure that the pointer address doesn't equal the numerical representation of // `Selected::{Waiting, Aborted, Disconnected}`. assert!(val > 2); diff --git a/library/std/src/sync/mpmc/zero.rs b/library/std/src/sync/mpmc/zero.rs index c743462501922..4f645e16fbb6f 100644 --- a/library/std/src/sync/mpmc/zero.rs +++ b/library/std/src/sync/mpmc/zero.rs @@ -25,7 +25,7 @@ impl Default for ZeroToken { impl fmt::Debug for ZeroToken { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&(self.0 as usize), f) + fmt::Debug::fmt(&self.0.addr(), f) } } diff --git a/library/std/src/sys/args/sgx.rs b/library/std/src/sys/args/sgx.rs index 6ff94f5681b6f..9403059e7c607 100644 --- a/library/std/src/sys/args/sgx.rs +++ b/library/std/src/sys/args/sgx.rs @@ -1,4 +1,5 @@ -#![allow(fuzzy_provenance_casts)] // FIXME: this module systematically confuses pointers and integers +// FIXME: this module systematically confuses pointers and integers +#![allow(fuzzy_provenance_casts, lossy_provenance_casts)] use crate::ffi::OsString; use crate::num::NonZero; diff --git a/library/std/src/sys/env/sgx.rs b/library/std/src/sys/env/sgx.rs index 09090ec7cf0dd..0c19fcc848f4d 100644 --- a/library/std/src/sys/env/sgx.rs +++ b/library/std/src/sys/env/sgx.rs @@ -1,4 +1,5 @@ -#![allow(fuzzy_provenance_casts)] // FIXME: this module systematically confuses pointers and integers +// FIXME: this module systematically confuses pointers and integers +#![allow(fuzzy_provenance_casts, lossy_provenance_casts)] pub use super::common::Env; use crate::collections::HashMap; diff --git a/library/std/src/sys/pal/sgx/mod.rs b/library/std/src/sys/pal/sgx/mod.rs index 2b284cc40b94b..7b2c8e5a8024a 100644 --- a/library/std/src/sys/pal/sgx/mod.rs +++ b/library/std/src/sys/pal/sgx/mod.rs @@ -3,7 +3,8 @@ //! This module contains the facade (aka platform-specific) implementations of //! OS level functionality for Fortanix SGX. #![deny(unsafe_op_in_unsafe_fn)] -#![allow(fuzzy_provenance_casts)] // FIXME: this entire module systematically confuses pointers and integers +// FIXME: this entire module systematically confuses pointers and integers +#![allow(fuzzy_provenance_casts, lossy_provenance_casts)] use crate::io; use crate::sync::atomic::{Atomic, AtomicBool, Ordering}; diff --git a/library/std/src/sys/thread_local/key/tests.rs b/library/std/src/sys/thread_local/key/tests.rs index c7d2c8e6301ef..5e5243d9835ed 100644 --- a/library/std/src/sys/thread_local/key/tests.rs +++ b/library/std/src/sys/thread_local/key/tests.rs @@ -18,8 +18,8 @@ fn smoke() { assert!(get(k2).is_null()); set(k1, ptr::without_provenance_mut(1)); set(k2, ptr::without_provenance_mut(2)); - assert_eq!(get(k1) as usize, 1); - assert_eq!(get(k2) as usize, 2); + assert_eq!(get(k1).addr(), 1); + assert_eq!(get(k2).addr(), 2); } } diff --git a/library/std/src/thread/tests.rs b/library/std/src/thread/tests.rs index 4b934c039a36f..78b6f7c35e8db 100644 --- a/library/std/src/thread/tests.rs +++ b/library/std/src/thread/tests.rs @@ -152,11 +152,11 @@ where { let (tx, rx) = channel(); - let x: Box<_> = Box::new(1); - let x_in_parent = (&*x) as *const i32 as usize; + let x: Box = Box::new(1); + let x_in_parent = (&raw const *x).addr(); spawnfn(Box::new(move || { - let x_in_child = (&*x) as *const i32 as usize; + let x_in_child = (&raw const *x).addr(); tx.send(x_in_child).unwrap(); })); From ecdf68305bd9b2e0b9cdd9c92e3df344910e85f8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 28 May 2026 14:07:42 +0200 Subject: [PATCH 06/12] update TargetFeature::Forbidden docs --- compiler/rustc_target/src/target_features.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index b009c42eb2302..cafc914cfe51c 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -32,9 +32,12 @@ pub enum Stability { Symbol, ), /// This feature can not be set via `-Ctarget-feature` or `#[target_feature]`, it can only be - /// set in the target spec. It is never set in `cfg(target_feature)`. Used in - /// particular for features are actually ABI configuration flags (not all targets are as nice as - /// RISC-V and have an explicit way to set the ABI separate from target features). + /// set in the target spec. It is never set in `cfg(target_feature)`. Used in particular for + /// features are actually ABI configuration flags (such as "soft-float" on many targets). + /// However, "forbidden" target features can still sometimes be enabled via `-Ctarget-cpu` or + /// target feature implications (on the Rust/LLVM level). To prevent that, ABI-relevant target + /// features are ideally pinned down (required or forbidden) in + /// [`Target::abi_required_features`]. Forbidden { reason: &'static str, /// True if this is always an error, false if this can be reported as a warning when set via @@ -128,9 +131,9 @@ impl Stability { // It is important for soundness to consider the interaction of targets features and the function // call ABI. For example, disabling the `x87` feature on x86 changes how scalar floats are passed as // arguments, so letting people toggle that feature would be unsound. To this end, the -// `abi_required_features` function computes which target features must and must not be enabled for -// any given target, and individual features can also be marked as `Forbidden`. -// See https://github.com/rust-lang/rust/issues/116344 for some more context. +// [`Target::abi_required_features`] function computes which target features must and must not be +// enabled for any given target, and individual features can also be marked as `Forbidden`. See +// https://github.com/rust-lang/rust/issues/116344 for some more context. // // The one exception to features that change the ABI is features that enable larger vector // registers. Those are permitted to be listed here. The `*_FOR_CORRECT_VECTOR_ABI` arrays store From 375c51c0a0265e9c1ed56ac28dac9381131744ac Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 28 May 2026 22:48:04 +0200 Subject: [PATCH 07/12] move target feature list explanation to module-level doc comment --- compiler/rustc_target/src/target_features.rs | 87 ++++++++++---------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index cafc914cfe51c..bbecb68f20eef 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -1,6 +1,44 @@ //! Declares Rust's target feature names for each target. //! Note that these are similar to but not always identical to LLVM's feature names, //! and Rust adds some features that do not correspond to LLVM features at all. +//! +//! The target features listed here can be used in `#[target_feature]` and `#[cfg(target_feature)]`. +//! They also do not trigger any warnings when used with `-Ctarget-feature`. +//! +//! Note that even unstable (and even entirely unlisted) features can be used with `-Ctarget-feature` +//! on stable. Using a feature not on the list of Rust target features only emits a warning. +//! Only `cfg(target_feature)` and `#[target_feature]` actually do any stability gating. +//! `cfg(target_feature)` for unstable features just works on nightly without any feature gate. +//! `#[target_feature]` requires a feature gate. +//! +//! When adding features to the below lists +//! check whether they're named already elsewhere in rust +//! e.g. in stdarch and whether the given name matches LLVM's +//! if it doesn't, to_llvm_feature in llvm_util in rustc_codegen_llvm needs to be adapted. +//! Additionally, if the feature is not available in older version of LLVM supported by the current +//! rust, the same function must be updated to filter out these features to avoid triggering +//! warnings. +//! +//! Also note that all target features listed here must be purely additive: for target_feature 1.1 to +//! be sound, we can never allow features like `+soft-float` (on x86) to be controlled on a +//! per-function level, since we would then allow safe calls from functions with `+soft-float` to +//! functions without that feature! +//! +//! It is important for soundness to consider the interaction of target features and the function +//! call ABI. For example, disabling the `x87` feature on x86 changes how scalar floats are passed as +//! arguments, so letting people toggle that feature would be unsound. To this end, the +//! [`Target::abi_required_features`] function computes which target features must and must not be +//! enabled for any given target, and individual features can also be marked as [`Forbidden`]. See +//! for some more context. +//! +//! The one exception to features that change the ABI is features that enable larger vector +//! registers. Those are permitted to be listed here. The `*_FOR_CORRECT_VECTOR_ABI` arrays store +//! information about which target feature is ABI-required for which vector size; this is used to +//! ensure that vectors can only be passed via `extern "C"` when the right feature is enabled. (For +//! the "Rust" ABI we generally pass vectors by-ref exactly to avoid these issues.) +//! Also see . +//! +//! Stabilizing a target feature requires t-lang approval. use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_macros::StableHash; use rustc_span::{Symbol, sym}; @@ -105,50 +143,11 @@ impl Stability { } } -// Here we list target features that rustc "understands": they can be used in `#[target_feature]` -// and `#[cfg(target_feature)]`. They also do not trigger any warnings when used with -// `-Ctarget-feature`. -// -// Note that even unstable (and even entirely unlisted) features can be used with `-Ctarget-feature` -// on stable. Using a feature not on the list of Rust target features only emits a warning. -// Only `cfg(target_feature)` and `#[target_feature]` actually do any stability gating. -// `cfg(target_feature)` for unstable features just works on nightly without any feature gate. -// `#[target_feature]` requires a feature gate. -// -// When adding features to the below lists -// check whether they're named already elsewhere in rust -// e.g. in stdarch and whether the given name matches LLVM's -// if it doesn't, to_llvm_feature in llvm_util in rustc_codegen_llvm needs to be adapted. -// Additionally, if the feature is not available in older version of LLVM supported by the current -// rust, the same function must be updated to filter out these features to avoid triggering -// warnings. -// -// Also note that all target features listed here must be purely additive: for target_feature 1.1 to -// be sound, we can never allow features like `+soft-float` (on x86) to be controlled on a -// per-function level, since we would then allow safe calls from functions with `+soft-float` to -// functions without that feature! -// -// It is important for soundness to consider the interaction of targets features and the function -// call ABI. For example, disabling the `x87` feature on x86 changes how scalar floats are passed as -// arguments, so letting people toggle that feature would be unsound. To this end, the -// [`Target::abi_required_features`] function computes which target features must and must not be -// enabled for any given target, and individual features can also be marked as `Forbidden`. See -// https://github.com/rust-lang/rust/issues/116344 for some more context. -// -// The one exception to features that change the ABI is features that enable larger vector -// registers. Those are permitted to be listed here. The `*_FOR_CORRECT_VECTOR_ABI` arrays store -// information about which target feature is ABI-required for which vector size; this is used to -// ensure that vectors can only be passed via `extern "C"` when the right feature is enabled. (For -// the "Rust" ABI we generally pass vectors by-ref exactly to avoid these issues.) -// Also see https://github.com/rust-lang/rust/issues/116558. -// -// Stabilizing a target feature requires t-lang approval. - -// If feature A "implies" feature B, then: -// - when A gets enabled (via `-Ctarget-feature` or `#[target_feature]`), we also enable B -// - when B gets disabled (via `-Ctarget-feature`), we also disable A -// -// Both of these are also applied transitively. +/// If feature A "implies" feature B, then: +/// - when A gets enabled (via `-Ctarget-feature` or `#[target_feature]`), we also enable B +/// - when B gets disabled (via `-Ctarget-feature`), we also disable A +/// +/// Both of these are also applied transitively. type ImpliedFeatures = &'static [&'static str]; static ARM_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ From 80a8b217a385ac06e8be6cd002326898055e1fa3 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 30 May 2026 07:37:05 -0700 Subject: [PATCH 08/12] Fix CI free-disk-space-linux script Occasionally we've been having jobs running on systems with limited free disk space, triggering this script to run. However, it has recently been failing with the error: E: Failed to fetch mirror+file:/etc/apt/apt-mirrors.txt/pool/main/o/openjdk-21/openjdk-21-jre-headless_21.0.10%2b7-1%7e24.04_amd64.deb 404 Not Found [IP: 52.252.163.49 80] E: Unable to correct problems, you have held broken packages. E: Aborting install. This adds an `apt-get update` to try to repair the index before trying to remove any packages. This seems to work in my testing, but I am far from an expert on apt. --- src/ci/scripts/free-disk-space-linux.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ci/scripts/free-disk-space-linux.sh b/src/ci/scripts/free-disk-space-linux.sh index 590e594e6aef4..167abf01ac880 100755 --- a/src/ci/scripts/free-disk-space-linux.sh +++ b/src/ci/scripts/free-disk-space-linux.sh @@ -277,6 +277,10 @@ cleanPackages() { fi WAIT_DPKG_LOCK="-o DPkg::Lock::Timeout=60" + # This update is intended to fix any broken state of the index and make + # sure it is fresh. Otherwise we've had problems with missing mirror + # entries. + sudo apt-get update -qq sudo apt-get ${WAIT_DPKG_LOCK} -qq remove -y --fix-missing "${packages[@]}" sudo apt-get ${WAIT_DPKG_LOCK} autoremove -y \ From 732998ab95c0179eb2b588acf7b05f68f35c9829 Mon Sep 17 00:00:00 2001 From: Hanna Kruppe Date: Sun, 31 May 2026 09:54:38 +0200 Subject: [PATCH 09/12] allow target-dependent int2ptr cast for pthread_t --- library/std/src/os/unix/thread.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/std/src/os/unix/thread.rs b/library/std/src/os/unix/thread.rs index 32085e525942e..efb349b18d6b5 100644 --- a/library/std/src/os/unix/thread.rs +++ b/library/std/src/os/unix/thread.rs @@ -31,10 +31,15 @@ pub trait JoinHandleExt { #[stable(feature = "thread_extensions", since = "1.9.0")] impl JoinHandleExt for JoinHandle { + // This is an int2ptr cast on some platforms (e.g., *-musl) where RawPthread + // is an integer but libc::pthread_t is a pointer. Exposed provenance is the + // safe choice here, but `as` also works when it's int2int or ptr2ptr. + #[allow(lossy_provenance_casts)] fn as_pthread_t(&self) -> RawPthread { self.as_inner().id() as RawPthread } + #[allow(lossy_provenance_casts)] // see above for why fn into_pthread_t(self) -> RawPthread { self.into_inner().into_id() as RawPthread } From 9c25035440c31a32f52a794412e154448a192cc3 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 31 May 2026 13:54:23 -0700 Subject: [PATCH 10/12] Mention how to fill a buffer with random bytes --- library/std/src/random.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/std/src/random.rs b/library/std/src/random.rs index a18dcf98ec7fc..5daedf42a3591 100644 --- a/library/std/src/random.rs +++ b/library/std/src/random.rs @@ -16,6 +16,8 @@ use crate::sys::random as sys; /// security is not a concern, consider using an alternative random number /// generator (potentially seeded from this one). /// +/// If you need to fill a buffer with random bytes, use `DefaultRandomSource.fill_bytes(&mut buf)`. +/// /// # Underlying sources /// /// Platform | Source From f65c5656ff068ec5dcbfa26bce42e91b20c4f604 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 31 May 2026 13:54:35 -0700 Subject: [PATCH 11/12] Add doc aliases for `DefaultRandomSource`, for people looking for randomness `getrandom` is both a crate for this and the name of the underlying syscall on various OSes. `getentropy` and `arc4random` are common library calls on various OSes. --- library/std/src/random.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/library/std/src/random.rs b/library/std/src/random.rs index 5daedf42a3591..8274060e5cedf 100644 --- a/library/std/src/random.rs +++ b/library/std/src/random.rs @@ -56,6 +56,7 @@ use crate::sys::random as sys; /// /// [`getrandom`]: https://www.man7.org/linux/man-pages/man2/getrandom.2.html /// [`/dev/urandom`]: https://www.man7.org/linux/man-pages/man4/random.4.html +#[doc(alias = "getrandom", alias = "getentropy", alias = "arc4random")] #[derive(Default, Debug, Clone, Copy)] #[unstable(feature = "random", issue = "130703")] pub struct DefaultRandomSource; From 3467b6fc2039d983bce762032717c8730b934510 Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Sun, 31 May 2026 18:08:27 -0300 Subject: [PATCH 12/12] Clean resolved signature for delegated functions in rustdoc A delegation item (`reuse path::method`) has an unresolved HIR signature: its inputs and return type are `InferDelegation` nodes that clean to `_`. When the delegated function is async, the `async` header over that inferred return type made `clean_fn_decl_with_params` call `sugared_async_return_type` on a non-`impl Future` type, panicking with "unexpected async fn return type". Clean the resolved ty-side signature for delegation items instead, the same way inlined items are cleaned. This avoids the ICE and renders the real return type and `self` parameter rather than `-> _` / `self: _`. --- src/librustdoc/clean/mod.rs | 49 ++++++++++++++----- .../rustdoc-html/async/async-fn-delegation.rs | 42 ++++++++++++++++ 2 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 tests/rustdoc-html/async/async-fn-delegation.rs diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index cf6deebe8e1d7..236d959b7e190 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -1089,7 +1089,13 @@ fn clean_fn_or_proc_macro<'tcx>( match macro_kind { Some(kind) => clean_proc_macro(item, name, kind, cx.tcx), None => { - let mut func = clean_function(cx, sig, generics, ParamsSrc::Body(body_id)); + let mut func = clean_function( + cx, + sig, + generics, + ParamsSrc::Body(body_id), + item.owner_id.to_def_id(), + ); clean_fn_decl_legacy_const_generics(&mut func, attrs); FunctionItem(func) } @@ -1127,18 +1133,30 @@ fn clean_function<'tcx>( sig: &hir::FnSig<'tcx>, generics: &hir::Generics<'tcx>, params: ParamsSrc<'tcx>, + def_id: DefId, ) -> Box { let (generics, decl) = enter_impl_trait(cx, |cx| { // NOTE: Generics must be cleaned before params. let generics = clean_generics(generics, cx); - let params = match params { - ParamsSrc::Body(body_id) => clean_params_via_body(cx, sig.decl.inputs, body_id), - // Let's not perpetuate anon params from Rust 2015; use `_` for them. - ParamsSrc::Idents(idents) => clean_params(cx, sig.decl.inputs, idents, |ident| { - Some(ident.map_or(kw::Underscore, |ident| ident.name)) - }), + let decl = if sig.decl.opt_delegation_sig_id().is_some() { + // A delegation item (`reuse path::method`) has no resolved signature in the + // HIR: its inputs and return type are `InferDelegation` nodes that clean to + // `_`, and an `async` header over that inferred return type would panic in + // `sugared_async_return_type`. The resolved signature only exists on the ty + // side, so clean that instead, exactly like an inlined item. This both fixes + // the rendered `-> _` / `self: _` and makes the async sugaring well-defined. + let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); + clean_poly_fn_sig(cx, Some(def_id), sig) + } else { + let params = match params { + ParamsSrc::Body(body_id) => clean_params_via_body(cx, sig.decl.inputs, body_id), + // Let's not perpetuate anon params from Rust 2015; use `_` for them. + ParamsSrc::Idents(idents) => clean_params(cx, sig.decl.inputs, idents, |ident| { + Some(ident.map_or(kw::Underscore, |ident| ident.name)) + }), + }; + clean_fn_decl_with_params(cx, sig.decl, Some(&sig.header), params) }; - let decl = clean_fn_decl_with_params(cx, sig.decl, Some(&sig.header), params); (generics, decl) }); Box::new(Function { decl, generics }) @@ -1270,11 +1288,18 @@ fn clean_trait_item<'tcx>(trait_item: &hir::TraitItem<'tcx>, cx: &mut DocContext RequiredAssocConstItem(generics, Box::new(clean_ty(ty, cx))) } hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(body)) => { - let m = clean_function(cx, sig, trait_item.generics, ParamsSrc::Body(body)); + let m = + clean_function(cx, sig, trait_item.generics, ParamsSrc::Body(body), local_did); MethodItem(m, Defaultness::from_trait_item(trait_item.defaultness)) } hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(idents)) => { - let m = clean_function(cx, sig, trait_item.generics, ParamsSrc::Idents(idents)); + let m = clean_function( + cx, + sig, + trait_item.generics, + ParamsSrc::Idents(idents), + local_did, + ); RequiredMethodItem(m, Defaultness::from_trait_item(trait_item.defaultness)) } hir::TraitItemKind::Type(bounds, Some(default)) => { @@ -1315,7 +1340,7 @@ pub(crate) fn clean_impl_item<'tcx>( type_: clean_ty(ty, cx), })), hir::ImplItemKind::Fn(ref sig, body) => { - let m = clean_function(cx, sig, impl_.generics, ParamsSrc::Body(body)); + let m = clean_function(cx, sig, impl_.generics, ParamsSrc::Body(body), local_did); let defaultness = match impl_.impl_kind { hir::ImplItemImplKind::Inherent { .. } => hir::Defaultness::Final, hir::ImplItemImplKind::Trait { defaultness, .. } => defaultness, @@ -3254,7 +3279,7 @@ fn clean_maybe_renamed_foreign_item<'tcx>( cx.with_param_env(def_id, |cx| { let kind = match item.kind { hir::ForeignItemKind::Fn(sig, idents, generics) => ForeignFunctionItem( - clean_function(cx, &sig, generics, ParamsSrc::Idents(idents)), + clean_function(cx, &sig, generics, ParamsSrc::Idents(idents), def_id), sig.header.safety(), ), hir::ForeignItemKind::Static(ty, mutability, safety) => ForeignStaticItem( diff --git a/tests/rustdoc-html/async/async-fn-delegation.rs b/tests/rustdoc-html/async/async-fn-delegation.rs new file mode 100644 index 0000000000000..7d891ac8be637 --- /dev/null +++ b/tests/rustdoc-html/async/async-fn-delegation.rs @@ -0,0 +1,42 @@ +//@ edition: 2021 + +// Regression test for . +// +// rustdoc used to ICE with "unexpected async fn return type" when cleaning a +// delegated (`reuse`) async fn: the delegation's HIR signature is unresolved +// (`InferDelegation`), so its return type cleaned to `_` even though the header +// is `async`, and unconditionally sugaring that inferred type panicked. +// +// We now clean the resolved (ty-side) signature for delegation items, like we +// already do for inlined items. That both avoids the ICE and renders the real +// return type and `self` parameter instead of `-> _` / `self: _`. +// +// Note: the `` generic on the free-function variants is a pre-existing +// quirk of how delegation generics are rendered (plain sync delegation prints it +// too); it is tracked separately and is not what this test is about. + +#![feature(fn_delegation)] +#![allow(incomplete_features)] +#![crate_name = "async_delegation"] + +pub trait Trait { + async fn unit(&self) {} + async fn nonunit(&self) -> i32 { + 0 + } +} + +//@ has async_delegation/fn.unit.html '//pre[@class="rust item-decl"]' 'pub async fn unit(&self)' +pub reuse Trait::unit; +//@ has async_delegation/fn.nonunit.html '//pre[@class="rust item-decl"]' 'pub async fn nonunit(&self) -> i32' +pub reuse Trait::nonunit; + +pub struct S; +impl Trait for S {} + +//@ has async_delegation/struct.S.html '//*[@class="code-header"]' 'pub async fn unit(self: &S)' +//@ has async_delegation/struct.S.html '//*[@class="code-header"]' 'pub async fn nonunit(self: &S) -> i32' +impl S { + pub reuse Trait::unit { self } + pub reuse Trait::nonunit { self } +}