From 9c8eb44b27ed4d368f55d15eee3546b523812712 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Mon, 24 Aug 2026 13:35:15 +1000 Subject: [PATCH 01/13] Flatten and rename `compute_src_directory_via_git` --- src/bootstrap/src/core/config/config.rs | 102 ++++++++++++------------ 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index d96300e0789aa..4997e9649b455 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -443,11 +443,14 @@ impl Config { // Undo `src/bootstrap` manifest_dir.parent().unwrap().parent().unwrap().to_owned() }; - let src = if let Some(s) = compute_src_directory(flags_src, &exec_ctx) { - s - } else { - default_src_dir.clone() - }; + + // Determine the root of the `rust-lang/rust` source directory from one of: + // - An explicit command-line argument `--src=PATH`. + // - Running git to find a checkout directory from the current working directory. + // - The source directory that this bootstrap executable was built from. + let src = flags_src + .or_else(|| compute_src_directory_via_git(&exec_ctx)) + .unwrap_or_else(|| default_src_dir.clone()); #[cfg(test)] { @@ -2063,54 +2066,49 @@ fn reconcile_jemalloc( } } -fn compute_src_directory(src_dir: Option, exec_ctx: &ExecutionContext) -> Option { - if let Some(src) = src_dir { - return Some(src); - } else { - // Infer the source directory. This is non-trivial because we want to support a downloaded bootstrap binary, - // running on a completely different machine from where it was compiled. - let mut cmd = helpers::git(None); - // NOTE: we cannot support running from outside the repository because the only other path we have available - // is set at compile time, which can be wrong if bootstrap was downloaded rather than compiled locally. - // We still support running outside the repository if we find we aren't in a git directory. - - // NOTE: We get a relative path from git to work around an issue on MSYS/mingw. If we used an absolute path, - // and end up using MSYS's git rather than git-for-windows, we would get a unix-y MSYS path. But as bootstrap - // has already been (kinda-cross-)compiled to Windows land, we require a normal Windows path. - cmd.arg("rev-parse").arg("--show-cdup"); - // Discard stderr because we expect this to fail when building from a tarball. - let output = cmd.allow_failure().run_capture_stdout(exec_ctx); - if output.is_success() { - let git_root_relative = output.stdout(); - // We need to canonicalize this path to make sure it uses backslashes instead of forward slashes, - // and to resolve any relative components. - let git_root = env::current_dir() - .unwrap() - .join(PathBuf::from(git_root_relative.trim())) - .canonicalize() - .unwrap(); - let s = git_root.to_str().unwrap(); - - // Bootstrap is quite bad at handling /? in front of paths - let git_root = match s.strip_prefix("\\\\?\\") { - Some(p) => PathBuf::from(p), - None => git_root, - }; - // If this doesn't have at least `stage0`, we guessed wrong. This can happen when, - // for example, the build directory is inside of another unrelated git directory. - // In that case keep the original `CARGO_MANIFEST_DIR` handling. - // - // NOTE: this implies that downloadable bootstrap isn't supported when the build directory is outside - // the source directory. We could fix that by setting a variable from all three of python, ./x, and x.ps1. - if git_root.join("src").join("stage0").exists() { - return Some(git_root); - } - } else { - // We're building from a tarball, not git sources. - // We don't support pre-downloaded bootstrap in this case. - } +fn compute_src_directory_via_git(exec_ctx: &ExecutionContext) -> Option { + // Infer the source directory. This is non-trivial because we want to support a downloaded bootstrap binary, + // running on a completely different machine from where it was compiled. + // NOTE: we cannot support running from outside the repository because the only other path we have available + // is set at compile time, which can be wrong if bootstrap was downloaded rather than compiled locally. + // We still support running outside the repository if we find we aren't in a git directory. + + // NOTE: We get a relative path from git (`--show-cdup`) to work around an issue on MSYS/mingw. + // If we used an absolute path, and end up using MSYS's git rather than git-for-windows, we would + // get a unix-y MSYS path. But as bootstrap has already been (kinda-cross-)compiled to Windows land, + // we require a normal Windows path. + + // Ask git to print the path of the repository root, relative to the working directory. + // If the working directory is the repo root, the output will be empty, which is fine. + let mut cmd = helpers::git(None); + cmd.arg("rev-parse").arg("--show-cdup"); + // Discard stderr because we expect this to fail when building from a tarball. + let output = cmd.allow_failure().run_capture_stdout(exec_ctx); + if output.is_failure() { + // We're building from a tarball, not git sources. + // We don't support pre-downloaded bootstrap in this case. + return None; + } + + // We need to canonicalize this path to make sure it uses backslashes instead of forward slashes, + // and to resolve any relative components. + let stdout = output.stdout(); + let relative_root = stdout.trim(); + let git_root = env::current_dir().unwrap().join(relative_root).canonicalize().unwrap(); + + // Bootstrap is quite bad at handling /? in front of paths + let git_root = match git_root.to_str().unwrap().strip_prefix("\\\\?\\") { + Some(p) => PathBuf::from(p), + None => git_root, }; - None + + // If this doesn't have at least `./src/stage0`, we guessed wrong. This can happen when, + // for example, the build directory is inside of another unrelated git directory. + // In that case keep the original `CARGO_MANIFEST_DIR` handling. + // + // NOTE: this implies that downloadable bootstrap isn't supported when the build directory is outside + // the source directory. We could fix that by setting a variable from all three of python, ./x, and x.ps1. + if git_root.join("src").join("stage0").exists() { Some(git_root) } else { None } } #[derive(Clone)] From 7faee83f1f74c81d369bdce958f1ae06dc7fbe27 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Mon, 24 Aug 2026 13:56:59 +1000 Subject: [PATCH 02/13] Replace another `#[cfg(test)]` with `if cfg!(test)` --- src/bootstrap/src/core/config/config.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 4997e9649b455..aadd2ebad0163 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -452,15 +452,13 @@ impl Config { .or_else(|| compute_src_directory_via_git(&exec_ctx)) .unwrap_or_else(|| default_src_dir.clone()); - #[cfg(test)] - { - if let Some(config_path) = flags_config.as_ref() { - assert!( + if cfg!(test) { + match flags_config.as_deref() { + Some(config_path) => assert!( !config_path.starts_with(&src), "Path {config_path:?} should not be inside or equal to src dir {src:?}" - ); - } else { - panic!("During test the config should be explicitly added"); + ), + None => panic!("During test the config should be explicitly added"), } } From dbea26933ed18a856c612a2157e8cb3babb88c37 Mon Sep 17 00:00:00 2001 From: Jonathan Keller Date: Tue, 25 Aug 2026 11:22:24 -0700 Subject: [PATCH 03/13] Check to ensure we're running against the correct LLVM version --- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 2 ++ compiler/rustc_codegen_llvm/src/llvm_util.rs | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 8c9bf55b14e45..3c2dc810704d9 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -894,6 +894,8 @@ unsafe extern "C" { SLen: c_uint, ) -> MetadataKindId; + pub(crate) fn LLVMGetVersion(major: &mut c_uint, minor: &mut c_uint, patch: &mut c_uint); + pub(crate) fn LLVMDisposeTargetMachine(T: ptr::NonNull); // Create modules. diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 298b58dd0007f..64b166113e42d 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -48,6 +48,24 @@ unsafe fn configure_llvm(sess: &Session) { let mut llvm_c_strs = Vec::with_capacity(n_args + 1); let mut llvm_args = Vec::with_capacity(n_args + 1); + // Check to ensure we're running against the correct LLVM version. + unsafe { + let mut llvm_major = 0; + let mut llvm_minor = 0; + let mut llvm_patch = 0; + llvm::LLVMGetVersion(&mut llvm_major, &mut llvm_minor, &mut llvm_patch); + let expected_version = llvm::LLVMRustVersionMajor(); + if llvm_major != expected_version { + panic!( + concat!( + "LLVM version mismatch: this compiler was built for LLVM {}, ", + "but LLVM {}.{}.{} is loaded" + ), + expected_version, llvm_major, llvm_minor, llvm_patch + ); + } + } + unsafe { llvm::LLVMRustInstallErrorHandlers(); } From 5841e102da7f55ae38a35aaaa8ab3dd4a4b29d0d Mon Sep 17 00:00:00 2001 From: Jonathan Keller Date: Tue, 25 Aug 2026 11:46:16 -0700 Subject: [PATCH 04/13] Look up and print path to wrong LLVM version --- compiler/rustc_codegen_llvm/src/llvm_util.rs | 11 +- compiler/rustc_session/src/filesearch.rs | 153 ++++++++++--------- 2 files changed, 86 insertions(+), 78 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 64b166113e42d..9819699ca5228 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -59,9 +59,16 @@ unsafe fn configure_llvm(sess: &Session) { panic!( concat!( "LLVM version mismatch: this compiler was built for LLVM {}, ", - "but LLVM {}.{}.{} is loaded" + "but LLVM {}.{}.{} was found{}" ), - expected_version, llvm_major, llvm_minor, llvm_patch + expected_version, + llvm_major, + llvm_minor, + llvm_patch, + match rustc_session::filesearch::dll_path(llvm::LLVMGetVersion as *mut _) { + Ok(path) => format!(" at {}", path.display()), + Err(_) => String::new(), + } ); } } diff --git a/compiler/rustc_session/src/filesearch.rs b/compiler/rustc_session/src/filesearch.rs index d88fed2f84ab8..6ec1466500a86 100644 --- a/compiler/rustc_session/src/filesearch.rs +++ b/compiler/rustc_session/src/filesearch.rs @@ -146,86 +146,78 @@ pub fn make_target_bin_path(sysroot: &Path, target_triple: &str) -> PathBuf { sysroot.join(rustlib_path).join("bin") } +/// Attempts to find the path to the dynamic library containing a function. +/// +/// SAFETY: `function` must be a valid pointer to some function. #[cfg(unix)] -fn current_dll_path() -> Result { - use std::sync::OnceLock; +pub unsafe fn dll_path(function: *mut std::ffi::c_void) -> Result { + use std::ffi::{CStr, OsStr}; + use std::os::unix::prelude::*; - // This is somewhat expensive relative to other work when compiling `fn main() {}` as `dladdr` - // needs to iterate over the symbol table of librustc_driver.so until it finds a match. - // As such cache this to avoid recomputing if we try to get the sysroot in multiple places. - static CURRENT_DLL_PATH: OnceLock> = OnceLock::new(); - CURRENT_DLL_PATH - .get_or_init(|| { - use std::ffi::{CStr, OsStr}; - use std::os::unix::prelude::*; - - #[cfg(not(target_os = "aix"))] - unsafe { - let addr = current_dll_path as fn() -> Result as *mut _; - let mut info = std::mem::zeroed(); - if libc::dladdr(addr, &mut info) == 0 { - return Err("dladdr failed".into()); + #[cfg(not(target_os = "aix"))] + unsafe { + let mut info = std::mem::zeroed(); + if libc::dladdr(function, &mut info) == 0 { + return Err("dladdr failed".into()); + } + #[cfg(target_os = "cygwin")] + let fname_ptr = info.dli_fname.as_ptr(); + #[cfg(not(target_os = "cygwin"))] + let fname_ptr = { + assert!(!info.dli_fname.is_null(), "dli_fname cannot be null"); + info.dli_fname + }; + let bytes = CStr::from_ptr(fname_ptr).to_bytes(); + let os = OsStr::from_bytes(bytes); + try_canonicalize(Path::new(os)).map_err(|e| e.to_string()) + } + + #[cfg(target_os = "aix")] + unsafe { + // On AIX, the symbol references a function descriptor. + // A function descriptor is consisted of (See https://reviews.llvm.org/D62532) + // * The address of the entry point of the function. + // * The TOC base address for the function. + // * The environment pointer. + // The function descriptor is in the data section. + let addr = function as u64; + let mut buffer = vec![std::mem::zeroed::(); 64]; + loop { + if libc::loadquery( + libc::L_GETINFO, + buffer.as_mut_ptr() as *mut libc::c_void, + (size_of::() * buffer.len()) as u32, + ) >= 0 + { + break; + } else { + if std::io::Error::last_os_error().raw_os_error().unwrap() != libc::ENOMEM { + return Err("loadquery failed".into()); } - #[cfg(target_os = "cygwin")] - let fname_ptr = info.dli_fname.as_ptr(); - #[cfg(not(target_os = "cygwin"))] - let fname_ptr = { - assert!(!info.dli_fname.is_null(), "dli_fname cannot be null"); - info.dli_fname - }; - let bytes = CStr::from_ptr(fname_ptr).to_bytes(); + buffer.resize(buffer.len() * 2, std::mem::zeroed::()); + } + } + let mut current = buffer.as_mut_ptr() as *mut libc::ld_info; + loop { + let data_base = (*current).ldinfo_dataorg as u64; + let data_end = data_base + (*current).ldinfo_datasize; + if (data_base..data_end).contains(&addr) { + let bytes = CStr::from_ptr(&(*current).ldinfo_filename[0]).to_bytes(); let os = OsStr::from_bytes(bytes); - try_canonicalize(Path::new(os)).map_err(|e| e.to_string()) + return try_canonicalize(Path::new(os)).map_err(|e| e.to_string()); } - - #[cfg(target_os = "aix")] - unsafe { - // On AIX, the symbol `current_dll_path` references a function descriptor. - // A function descriptor is consisted of (See https://reviews.llvm.org/D62532) - // * The address of the entry point of the function. - // * The TOC base address for the function. - // * The environment pointer. - // The function descriptor is in the data section. - let addr = current_dll_path as u64; - let mut buffer = vec![std::mem::zeroed::(); 64]; - loop { - if libc::loadquery( - libc::L_GETINFO, - buffer.as_mut_ptr() as *mut libc::c_void, - (size_of::() * buffer.len()) as u32, - ) >= 0 - { - break; - } else { - if std::io::Error::last_os_error().raw_os_error().unwrap() != libc::ENOMEM { - return Err("loadquery failed".into()); - } - buffer.resize(buffer.len() * 2, std::mem::zeroed::()); - } - } - let mut current = buffer.as_mut_ptr() as *mut libc::ld_info; - loop { - let data_base = (*current).ldinfo_dataorg as u64; - let data_end = data_base + (*current).ldinfo_datasize; - if (data_base..data_end).contains(&addr) { - let bytes = CStr::from_ptr(&(*current).ldinfo_filename[0]).to_bytes(); - let os = OsStr::from_bytes(bytes); - return try_canonicalize(Path::new(os)).map_err(|e| e.to_string()); - } - if (*current).ldinfo_next == 0 { - break; - } - current = (current as *mut i8).offset((*current).ldinfo_next as isize) - as *mut libc::ld_info; - } - return Err(format!("current dll's address {} is not in the load map", addr)); + if (*current).ldinfo_next == 0 { + break; } - }) - .clone() + current = + (current as *mut i8).offset((*current).ldinfo_next as isize) as *mut libc::ld_info; + } + return Err(format!("current dll's address {} is not in the load map", addr)); + } } #[cfg(windows)] -fn current_dll_path() -> Result { +pub unsafe fn dll_path(function: *mut std::ffi::c_void) -> Result { use std::ffi::OsString; use std::io; use std::os::windows::prelude::*; @@ -240,10 +232,7 @@ fn current_dll_path() -> Result { unsafe { GetModuleHandleExW( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, - PCWSTR( - current_dll_path as fn() -> Result - as *mut u16, - ), + PCWSTR(function as *mut u16), &mut module, ) } @@ -269,8 +258,20 @@ fn current_dll_path() -> Result { } #[cfg(target_os = "wasi")] +pub unsafe fn dll_path(function: *mut std::ffi::c_void) -> Result { + Err("dll_path is not supported on WASI".to_string()) +} + fn current_dll_path() -> Result { - Err("current_dll_path is not supported on WASI".to_string()) + use std::sync::OnceLock; + + // This is somewhat expensive relative to other work when compiling `fn main() {}` as `dladdr` + // needs to iterate over the symbol table of librustc_driver.so until it finds a match. + // As such cache this to avoid recomputing if we try to get the sysroot in multiple places. + static CURRENT_DLL_PATH: OnceLock> = OnceLock::new(); + CURRENT_DLL_PATH + .get_or_init(|| unsafe { dll_path(current_dll_path as fn() -> _ as *mut _) }) + .clone() } /// This function checks if sysroot is found using env::args().next(), and if it From 3ff39765b2649118d8a55d8cee79f0152ded6c53 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Fri, 28 Aug 2026 15:58:08 +0200 Subject: [PATCH 05/13] Add regression test for "use of an internal attribute" --- tests/ui/proc-macro/auxiliary/test-re-emit.rs | 8 ++++++++ tests/ui/proc-macro/test-re-emit.rs | 15 +++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 tests/ui/proc-macro/auxiliary/test-re-emit.rs create mode 100644 tests/ui/proc-macro/test-re-emit.rs diff --git a/tests/ui/proc-macro/auxiliary/test-re-emit.rs b/tests/ui/proc-macro/auxiliary/test-re-emit.rs new file mode 100644 index 0000000000000..4b500c4a66166 --- /dev/null +++ b/tests/ui/proc-macro/auxiliary/test-re-emit.rs @@ -0,0 +1,8 @@ +extern crate proc_macro; +use proc_macro::TokenStream; + +#[proc_macro_attribute] +pub fn remove_span(_attr: TokenStream, item: TokenStream) -> TokenStream { + // `.to_string().parse()` will lose the span of the token stream + item.to_string().parse().unwrap() +} diff --git a/tests/ui/proc-macro/test-re-emit.rs b/tests/ui/proc-macro/test-re-emit.rs new file mode 100644 index 0000000000000..c01df98e79a6e --- /dev/null +++ b/tests/ui/proc-macro/test-re-emit.rs @@ -0,0 +1,15 @@ +//@ check-pass +//@ proc-macro: test-re-emit.rs +//@ compile-flags: --test +// Test that we can pass a test through a proc macro that removes the span of the item +// Regression test for https://github.com/rust-lang/rust/issues/161917 + +#[test] +#[test_re_emit::remove_span] +fn meow1() {} + +#[test_re_emit::remove_span] +#[test] +fn meow2() {} + +fn main() {} From ca095daf5fb08010cf044a4253d8d74586f9a354 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Fri, 28 Aug 2026 16:11:46 +0200 Subject: [PATCH 06/13] Add regression test for "expected item after attributes" --- .../auxiliary/test-count-attributes.rs | 23 ++++++++++++++ tests/ui/proc-macro/test-count-attributes.rs | 30 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 tests/ui/proc-macro/auxiliary/test-count-attributes.rs create mode 100644 tests/ui/proc-macro/test-count-attributes.rs diff --git a/tests/ui/proc-macro/auxiliary/test-count-attributes.rs b/tests/ui/proc-macro/auxiliary/test-count-attributes.rs new file mode 100644 index 0000000000000..c5658f445df2d --- /dev/null +++ b/tests/ui/proc-macro/auxiliary/test-count-attributes.rs @@ -0,0 +1,23 @@ +extern crate proc_macro; +use proc_macro::TokenStream; + +#[proc_macro_attribute] +pub fn assert_no_attributes(_attr: TokenStream, item: TokenStream) -> TokenStream { + // This will count the "attributes" (in reality the number of hash symbols) on the item. + assert_eq!(item.to_string().chars().filter(|c| *c == '#').count(), 0); + item +} + +#[proc_macro_attribute] +pub fn assert_one_attribute(_attr: TokenStream, item: TokenStream) -> TokenStream { + // This will count the "attributes" (in reality the number of hash symbols) on the item. + assert_eq!(item.to_string().chars().filter(|c| *c == '#').count(), 1); + item +} + +#[proc_macro_attribute] +pub fn assert_two_attributes(_attr: TokenStream, item: TokenStream) -> TokenStream { + // This will count the "attributes" (in reality the number of hash symbols) on the item. + assert_eq!(item.to_string().chars().filter(|c| *c == '#').count(), 2); + item +} diff --git a/tests/ui/proc-macro/test-count-attributes.rs b/tests/ui/proc-macro/test-count-attributes.rs new file mode 100644 index 0000000000000..930e69cc6381f --- /dev/null +++ b/tests/ui/proc-macro/test-count-attributes.rs @@ -0,0 +1,30 @@ +//@ check-pass +//@ proc-macro: test-count-attributes.rs +//@ compile-flags: --test +// Tests whether attributes on tests can be observed by proc macros +// Regression test for https://github.com/rust-lang/rust/issues/161920 + +#[test] +#[test_count_attributes::assert_no_attributes] +fn meow1() {} + +#[test_count_attributes::assert_one_attribute] +#[test] +fn meow2() {} + +#[test] +#[should_panic] +#[test_count_attributes::assert_one_attribute] +fn meow3() {} + +#[test] +#[test_count_attributes::assert_one_attribute] +#[should_panic] +fn meow4() {} + +#[test_count_attributes::assert_two_attributes] +#[test] +#[should_panic] +fn meow5() {} + +fn main() {} From bf56fa46ed986c79bb0d63e6659941783ab2b803 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 11 Aug 2026 17:20:08 +0100 Subject: [PATCH 07/13] Handle multiple action records in EH personality function --- library/std/src/sys/personality/dwarf/eh.rs | 25 +++++++++++++++++- tests/ui/panics/lsda-multiple-action.rs | 29 +++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 tests/ui/panics/lsda-multiple-action.rs diff --git a/library/std/src/sys/personality/dwarf/eh.rs b/library/std/src/sys/personality/dwarf/eh.rs index ef5112ad74f13..c23e0afe6c979 100644 --- a/library/std/src/sys/personality/dwarf/eh.rs +++ b/library/std/src/sys/personality/dwarf/eh.rs @@ -48,9 +48,20 @@ pub struct EHContext<'a> { type LPad = *const u8; pub enum EHAction { None, + /// Destructors should be executed when stack unwinds. Cleanup(LPad), + /// Stack unwind should be stopped as the exception is going to be caught by `catch_unwind`. Catch(LPad), + /// Stack unwind should be stopped for termination (`UnwindAction::Terminate`). + /// + /// Note that due to inlining the landing pad can execute destructors before terminating. So + /// this is different from `Terminate`. + /// + /// Handling of this is mostly identical to `Catch`; except that Rust frames that have no + /// destructors but only `UnwindAction::Terminate` is considered as plain-old-frame (POF) and + /// forced unwind is allowed to unwind past it; so this is treated as `None` during forced unwind. Filter(LPad), + /// Process should be terminated as the call site does not permit unwinding. Terminate, } @@ -160,7 +171,19 @@ unsafe fn interpret_cs_action( let action_record = unsafe { action_table.offset(cs_action_entry as isize - 1) }; let mut action_reader = DwarfReader::new(action_record); let ttype_index = unsafe { action_reader.read_sleb128() }; - if ttype_index == 0 { + let next_action = unsafe { action_reader.read_sleb128() }; + if next_action != 0 { + // We observed multiple actions. Action records contain no duplicates (at least that is + // true for both LLVM/GCC), and as Rust does not have exception specification, this + // indicates that we have at least 2 of "cleanup", "catch" and "filter", so we should + // catch all exceptions. + // + // Note that even for the case of "cleanup" + "filter", decoding them as "catch" is + // fine: "filter" behaves identically to "catch" except for forced unwind; in case of + // forced unwind, hitting a "cleanup" landing pad is UB as it indicates that we're + // unwinding past a non-POF Rust frame. + EHAction::Catch(lpad) + } else if ttype_index == 0 { EHAction::Cleanup(lpad) } else if ttype_index > 0 { // Stop unwinding Rust panics at catch_unwind. diff --git a/tests/ui/panics/lsda-multiple-action.rs b/tests/ui/panics/lsda-multiple-action.rs new file mode 100644 index 0000000000000..236e8b90ca5b1 --- /dev/null +++ b/tests/ui/panics/lsda-multiple-action.rs @@ -0,0 +1,29 @@ +//@ run-pass +//@ needs-unwind +//@ ignore-backends: gcc +//@ compile-flags: -Copt-level=3 + +struct Guard; + +impl Drop for Guard { + fn drop(&mut self) { + core::hint::black_box(()); + } +} + +#[inline(never)] +fn unwind() { + if core::hint::black_box(true) { + std::panic::resume_unwind(Box::new(())); + } +} + +fn main() { + // The `catch_unwind` will generate `landingpad catch` and the destructor will generate + // `landingpad cleanup`; after LLVM inlining it will become `landingpad cleanup catch`, and this + // is translated to action record chains in LSDA. + let _ = std::panic::catch_unwind(|| { + let _guard = Guard; + unwind(); + }); +} From 133ea4b7454b2455c4939894f9b78a727fb9dfb4 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 6 Aug 2026 19:15:04 +0200 Subject: [PATCH 08/13] attach naked function target features to module assembly --- .../rustc_codegen_cranelift/src/global_asm.rs | 1 + compiler/rustc_codegen_gcc/src/asm.rs | 1 + compiler/rustc_codegen_llvm/src/asm.rs | 8 +- compiler/rustc_codegen_ssa/src/base.rs | 9 +- .../rustc_codegen_ssa/src/mir/naked_asm.rs | 4 +- compiler/rustc_codegen_ssa/src/traits/asm.rs | 1 + .../naked-functions/target-feature.rs | 165 ++++++++++++++++++ .../naked-functions/target-feature-aarch64.rs | 47 +++++ .../target-feature-aarch64.sha3.stderr | 10 ++ .../target-feature-aarch64.vanilla.stderr | 18 ++ .../naked-functions/target-feature-s390x.rs | 30 ++++ .../target-feature-s390x.stderr | 10 ++ 12 files changed, 297 insertions(+), 7 deletions(-) create mode 100644 tests/assembly-llvm/naked-functions/target-feature.rs create mode 100644 tests/ui/asm/naked-functions/target-feature-aarch64.rs create mode 100644 tests/ui/asm/naked-functions/target-feature-aarch64.sha3.stderr create mode 100644 tests/ui/asm/naked-functions/target-feature-aarch64.vanilla.stderr create mode 100644 tests/ui/asm/naked-functions/target-feature-s390x.rs create mode 100644 tests/ui/asm/naked-functions/target-feature-s390x.stderr diff --git a/compiler/rustc_codegen_cranelift/src/global_asm.rs b/compiler/rustc_codegen_cranelift/src/global_asm.rs index 9763b0c0fa867..ecf7c4f9b30c9 100644 --- a/compiler/rustc_codegen_cranelift/src/global_asm.rs +++ b/compiler/rustc_codegen_cranelift/src/global_asm.rs @@ -30,6 +30,7 @@ impl<'tcx> AsmCodegenMethods<'tcx> for GlobalAsmContext<'_, 'tcx> { operands: &[GlobalAsmOperandRef<'tcx>], options: InlineAsmOptions, _line_spans: &[Span], + _target_features: &[String], ) { codegen_global_asm_inner(self.tcx, self.global_asm, template, operands, options); } diff --git a/compiler/rustc_codegen_gcc/src/asm.rs b/compiler/rustc_codegen_gcc/src/asm.rs index ac86fbe7428b0..3017751666fb4 100644 --- a/compiler/rustc_codegen_gcc/src/asm.rs +++ b/compiler/rustc_codegen_gcc/src/asm.rs @@ -928,6 +928,7 @@ impl<'gcc, 'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { operands: &[GlobalAsmOperandRef<'tcx>], options: InlineAsmOptions, line_spans: &[Span], + _target_features: &[String], ) { let asm_arch = self.tcx.sess.asm_arch.unwrap(); diff --git a/compiler/rustc_codegen_llvm/src/asm.rs b/compiler/rustc_codegen_llvm/src/asm.rs index 549769547da78..9fa4ae19cd6f0 100644 --- a/compiler/rustc_codegen_llvm/src/asm.rs +++ b/compiler/rustc_codegen_llvm/src/asm.rs @@ -414,6 +414,7 @@ impl<'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'_, 'tcx> { operands: &[GlobalAsmOperandRef<'tcx>], options: InlineAsmOptions, _line_spans: &[Span], + target_features: &[String], ) { let asm_arch = self.tcx.sess.asm_arch.unwrap(); @@ -499,14 +500,11 @@ impl<'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'_, 'tcx> { template_str.push_str("\n.att_syntax\n"); } - let target_features = self.tcx.global_backend_features(()).join(","); - let target_cpu = llvm_util::target_cpu(self.tcx.sess); - llvm::append_module_inline_asm( self.llmod, template_str.as_bytes(), - &target_features, - target_cpu, + &target_features.join(","), + llvm_util::target_cpu(self.tcx.sess), ); } diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 9eb4fd510fd7f..d66dec54237dd 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -490,7 +490,14 @@ where }) .collect(); - cx.codegen_global_asm(asm.template, &operands, asm.options, asm.line_spans); + let target_features = cx.tcx().global_backend_features(()); + cx.codegen_global_asm( + asm.template, + &operands, + asm.options, + asm.line_spans, + &target_features, + ); } else { span_bug!(item.span, "Mismatch between hir::Item type and MonoItem type") } diff --git a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs index 05b87bb6d7159..b3ea2409762fb 100644 --- a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs +++ b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs @@ -54,7 +54,9 @@ pub fn codegen_naked_asm< template_vec.extend(template.iter().cloned()); template_vec.push(rustc_ast::ast::InlineAsmTemplatePiece::String(end.into())); - cx.codegen_global_asm(&template_vec, &operands, options, line_spans); + let target_features: Vec<_> = + cx.tcx().asm_target_features(instance.def_id()).iter().map(|s| format!("+{s}")).collect(); + cx.codegen_global_asm(&template_vec, &operands, options, line_spans, &target_features); } fn inline_to_global_operand<'a, 'tcx, Cx: LayoutOf<'tcx, LayoutOfResult = TyAndLayout<'tcx>>>( diff --git a/compiler/rustc_codegen_ssa/src/traits/asm.rs b/compiler/rustc_codegen_ssa/src/traits/asm.rs index 85a2fe09ba414..554e7efaf7c3a 100644 --- a/compiler/rustc_codegen_ssa/src/traits/asm.rs +++ b/compiler/rustc_codegen_ssa/src/traits/asm.rs @@ -72,6 +72,7 @@ pub trait AsmCodegenMethods<'tcx> { operands: &[GlobalAsmOperandRef<'tcx>], options: InlineAsmOptions, line_spans: &[Span], + target_features: &[String], ); /// The mangled name of this instance diff --git a/tests/assembly-llvm/naked-functions/target-feature.rs b/tests/assembly-llvm/naked-functions/target-feature.rs new file mode 100644 index 0000000000000..500e2a778e475 --- /dev/null +++ b/tests/assembly-llvm/naked-functions/target-feature.rs @@ -0,0 +1,165 @@ +//@ revisions: aarch64-elf aarch64-macho aarch64-coff x86_64 s390x riscv64 powerpc64 loongarch64 +//@ add-minicore +//@ assembly-output: emit-asm +//@ min-llvm-version: 23 +// +//@ [x86_64] compile-flags: --target x86_64-unknown-linux-gnu +//@ [x86_64] needs-llvm-components: x86 +// +//@ [aarch64-elf] compile-flags: --target aarch64-unknown-linux-gnu +//@ [aarch64-elf] needs-llvm-components: aarch64 +//@ [aarch64-macho] compile-flags: --target aarch64-apple-darwin +//@ [aarch64-macho] needs-llvm-components: aarch64 +//@ [aarch64-coff] compile-flags: --target aarch64-pc-windows-gnullvm +//@ [aarch64-coff] needs-llvm-components: aarch64 +// +//@ [s390x] compile-flags: --target s390x-unknown-linux-gnu +//@ [s390x] needs-llvm-components: systemz +// +//@ [powerpc64] compile-flags: --target powerpc64-unknown-linux-gnu +//@ [powerpc64] needs-llvm-components: powerpc +// +//@ [riscv64] compile-flags: --target riscv64gc-unknown-linux-gnu +//@ [riscv64] needs-llvm-components: riscv +// +// NOTE: loongarch64 does not error when using an instruction without enabling the corresponding +// target feature. +//@ [loongarch64] compile-flags: --target loongarch64-unknown-linux-gnu +//@ [loongarch64] needs-llvm-components: loongarch + +// Test that the #[target_feature(enable = ...)]` works on naked functions. + +#![crate_type = "lib"] +#![feature(no_core, naked_functions_target_feature)] +#![feature(s390x_target_feature, powerpc_target_feature, loongarch_target_feature)] +#![no_core] + +extern crate minicore; +use minicore::*; + +// x86_64-LABEL: vpclmulqdq: +// x86_64: vpclmulqdq +#[no_mangle] +#[unsafe(naked)] +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "vpclmulqdq")] +unsafe extern "C" fn vpclmulqdq() { + naked_asm!("vpclmulqdq zmm1, zmm2, zmm3, 4") +} + +// i8mm is not enabled by default +// +// note that aarch64-apple-darwin enables more features than aarch64-unknown-linux-gnu +// +// aarch64-elf-LABEL: i8mm: +// aarch64-elf: usdot +// aarch64-macho-LABEL: i8mm: +// aarch64-macho: usdot +// aarch64-coff-LABEL: i8mm: +// aarch64-coff: usdot +#[no_mangle] +#[unsafe(naked)] +#[cfg(target_arch = "aarch64")] +#[target_feature(enable = "i8mm")] +unsafe extern "C" fn i8mm() { + naked_asm!("usdot v0.4s, v1.16b, v2.4b[3]") +} + +// riscv64: sh1add: +// riscv64: sh1add +#[no_mangle] +#[unsafe(naked)] +#[cfg(target_arch = "riscv64")] +#[target_feature(enable = "zba")] +unsafe extern "C" fn sh1add() { + naked_asm!("sh1add a0, a1, a2", "ret"); +} + +#[cfg(target_arch = "s390x")] +mod s390x { + use super::*; + + // s390x: vector: + // s390x: vavglg + #[no_mangle] + #[unsafe(naked)] + #[target_feature(enable = "vector")] + unsafe extern "C" fn vector() { + naked_asm!("vavglg %v0, %v0, %v0") + } + + // s390x: vector_enhancements_1: + // s390x: vfcesbs + #[no_mangle] + #[unsafe(naked)] + #[target_feature(enable = "vector-enhancements-1")] + unsafe extern "C" fn vector_enhancements_1() { + naked_asm!("vfcesbs %v0, %v0, %v0") + } + + // s390x: vector_enhancements_2: + // s390x: vclfp + #[no_mangle] + #[unsafe(naked)] + #[target_feature(enable = "vector-enhancements-2")] + unsafe extern "C" fn vector_enhancements_2() { + naked_asm!("vclfp %v0, %v0, 0, 0, 0") + } + + // s390x: vector_packed_decimal: + // s390x: vlrlr + #[no_mangle] + #[unsafe(naked)] + #[target_feature(enable = "vector-packed-decimal")] + unsafe extern "C" fn vector_packed_decimal() { + naked_asm!("vlrlr %v24, %r3, 0(%r2)", "br %r14") + } + + // s390x: vector_packed_decimal_enhancement: + // s390x: vcvbg + #[no_mangle] + #[unsafe(naked)] + #[target_feature(enable = "vector-packed-decimal-enhancement")] + unsafe extern "C" fn vector_packed_decimal_enhancement() { + naked_asm!("vcvbg %r0, %v0, 0, 1") + } + + // s390x: vector_packed_decimal_enhancement_2: + // s390x: vupkzl + #[no_mangle] + #[unsafe(naked)] + #[target_feature(enable = "vector-packed-decimal-enhancement-2")] + unsafe extern "C" fn vector_packed_decimal_enhancement_2() { + naked_asm!("vupkzl %v0, %v0, 0") + } +} + +// powerpc64: power10_vector: +// powerpc64: xxpermx +#[no_mangle] +#[unsafe(naked)] +#[cfg(target_arch = "powerpc64")] +#[target_feature(enable = "power10-vector")] +unsafe extern "C" fn power10_vector() { + naked_asm!("xxpermx 34, 0, 1, 2, 0", "blr") +} + +// loongarch64: lasx: +// loongarch64: xvadd.b +#[no_mangle] +#[unsafe(naked)] +#[cfg(target_arch = "loongarch64")] +#[target_feature(enable = "lasx")] +unsafe extern "C" fn lasx() { + naked_asm!("xvadd.b $xr0, $xr0, $xr1", "ret") +} + +// wasm32: simd128: +// wasm32: i8x16.shuffle +#[no_mangle] +#[unsafe(naked)] +#[cfg(target_arch = "wasm32")] +#[target_feature(enable = "simd128")] +unsafe extern "C" fn simd128() { + naked_asm!("i8x16.shuffle 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15", "return"); +} diff --git a/tests/ui/asm/naked-functions/target-feature-aarch64.rs b/tests/ui/asm/naked-functions/target-feature-aarch64.rs new file mode 100644 index 0000000000000..f82122f773ca0 --- /dev/null +++ b/tests/ui/asm/naked-functions/target-feature-aarch64.rs @@ -0,0 +1,47 @@ +//@ add-minicore +//@ build-fail +//@ revisions: vanilla sha3 +//@ compile-flags: --target aarch64-unknown-linux-gnu -Z deduplicate-diagnostics=yes +//@[sha3] compile-flags: -Ctarget-feature=+sha3 +//@ needs-llvm-components: aarch64 +//@ min-llvm-version: 23 + +#![crate_type = "lib"] +#![feature(no_core, naked_functions_target_feature)] +#![no_core] + +extern crate minicore; +use minicore::*; + +// check that a naked function using target features does not keep these features enabled +// for subsequent asm blocks. + +#[no_mangle] +#[unsafe(naked)] +#[target_feature(enable = "i8mm")] +unsafe extern "C" fn a() { + naked_asm!("usdot v0.4s, v1.16b, v2.4b[3]") +} + +//~? ERROR instruction requires: i8mm + +#[no_mangle] +#[unsafe(naked)] +unsafe extern "C" fn c() { + naked_asm!("usdot v0.4s, v2.16b, v2.4b[3]") +} + +#[no_mangle] +#[unsafe(naked)] +#[target_feature(enable = "sha3")] +unsafe extern "C" fn d() { + naked_asm!("eor3 v0.16b, v1.16b, v2.16b, v3.16b") +} + +//[vanilla]~? ERROR instruction requires: sha3 + +#[no_mangle] +#[unsafe(naked)] +unsafe extern "C" fn b() { + naked_asm!("eor3 v0.16b, v1.16b, v2.16b, v3.16b") +} diff --git a/tests/ui/asm/naked-functions/target-feature-aarch64.sha3.stderr b/tests/ui/asm/naked-functions/target-feature-aarch64.sha3.stderr new file mode 100644 index 0000000000000..49a65eaadb904 --- /dev/null +++ b/tests/ui/asm/naked-functions/target-feature-aarch64.sha3.stderr @@ -0,0 +1,10 @@ +error: instruction requires: i8mm + | +note: instantiated into assembly here + --> :15:1 + | +LL | usdot v0.4s, v2.16b, v2.4b[3] + | ^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/asm/naked-functions/target-feature-aarch64.vanilla.stderr b/tests/ui/asm/naked-functions/target-feature-aarch64.vanilla.stderr new file mode 100644 index 0000000000000..8ac31d19f5e3e --- /dev/null +++ b/tests/ui/asm/naked-functions/target-feature-aarch64.vanilla.stderr @@ -0,0 +1,18 @@ +error: instruction requires: sha3 + | +note: instantiated into assembly here + --> :6:1 + | +LL | eor3 v0.16b, v1.16b, v2.16b, v3.16b + | ^ + +error: instruction requires: i8mm + | +note: instantiated into assembly here + --> :15:1 + | +LL | usdot v0.4s, v2.16b, v2.4b[3] + | ^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/asm/naked-functions/target-feature-s390x.rs b/tests/ui/asm/naked-functions/target-feature-s390x.rs new file mode 100644 index 0000000000000..b0f806c4c0a16 --- /dev/null +++ b/tests/ui/asm/naked-functions/target-feature-s390x.rs @@ -0,0 +1,30 @@ +//@ add-minicore +//@ build-fail +//@ compile-flags: --target s390x-unknown-linux-gnu -Z deduplicate-diagnostics=yes +//@ needs-llvm-components: systemz +//@ min-llvm-version: 23 + +#![crate_type = "lib"] +#![feature(no_core, naked_functions_target_feature)] +#![no_core] + +extern crate minicore; +use minicore::*; + +// check that a naked function using target features does not keep these features enabled +// for subsequent asm blocks. + +#[no_mangle] +#[unsafe(naked)] +#[target_feature(enable = "vector-packed-decimal")] +unsafe extern "C" fn a() { + naked_asm!("vlrlr %v24, %r3, 0(%r2)") +} + +//~? ERROR instruction requires: vector-packed-decimal + +#[no_mangle] +#[unsafe(naked)] +unsafe extern "C" fn b() { + naked_asm!("vlrlr %v24, %r3, 0(%r3)") +} diff --git a/tests/ui/asm/naked-functions/target-feature-s390x.stderr b/tests/ui/asm/naked-functions/target-feature-s390x.stderr new file mode 100644 index 0000000000000..84d60c43bc765 --- /dev/null +++ b/tests/ui/asm/naked-functions/target-feature-s390x.stderr @@ -0,0 +1,10 @@ +error: instruction requires: vector-packed-decimal + | +note: instantiated into assembly here + --> :6:1 + | +LL | vlrlr %v24, %r3, 0(%r3) + | ^ + +error: aborting due to 1 previous error + From 4f404c1f7e1de1ec058a7729568aaa310a063959 Mon Sep 17 00:00:00 2001 From: Pavel Grigorenko Date: Mon, 24 Aug 2026 21:08:28 +0300 Subject: [PATCH 09/13] Use `drop_guard` in some places in {core,alloc,std} --- library/alloc/src/boxed/thin.rs | 40 +-- .../alloc/src/collections/binary_heap/mod.rs | 14 +- library/alloc/src/collections/btree/map.rs | 18 +- library/alloc/src/collections/btree/mem.rs | 10 +- library/alloc/src/collections/btree/node.rs | 17 +- library/alloc/src/collections/linked_list.rs | 22 +- .../alloc/src/collections/vec_deque/drain.rs | 247 +++++++++--------- .../src/collections/vec_deque/into_iter.rs | 54 ++-- .../alloc/src/collections/vec_deque/mod.rs | 30 +-- library/alloc/src/rc.rs | 41 +-- library/alloc/src/slice.rs | 35 ++- library/alloc/src/string.rs | 33 +-- library/alloc/src/sync.rs | 56 ++-- library/alloc/src/vec/drain.rs | 43 ++- library/alloc/src/vec/into_iter.rs | 20 +- library/std/src/sys/fs/unix.rs | 36 +-- library/std/src/sys/pal/unix/sync/condvar.rs | 21 +- library/std/src/sys/process/unix/unix.rs | 70 ++--- library/std/src/sys/process/windows/tests.rs | 12 +- 19 files changed, 313 insertions(+), 506 deletions(-) diff --git a/library/alloc/src/boxed/thin.rs b/library/alloc/src/boxed/thin.rs index 1e60107a1d15c..98904aa500669 100644 --- a/library/alloc/src/boxed/thin.rs +++ b/library/alloc/src/boxed/thin.rs @@ -11,7 +11,7 @@ use core::marker::PhantomData; use core::marker::Unsize; #[cfg(not(no_global_oom_handling))] use core::mem; -use core::mem::SizedTypeProperties; +use core::mem::{DropGuard, SizedTypeProperties}; use core::ops::{Deref, DerefMut}; use core::ptr::{self, NonNull, Pointee}; @@ -364,38 +364,24 @@ impl WithHeader { // - Assumes that either `value` can be dereferenced, or is the // `NonNull::dangling()` we use when both `T` and `H` are ZSTs. unsafe fn drop(&self, value: *mut T) { - struct DropGuard { - ptr: NonNull, - value_layout: Layout, - _marker: PhantomData, - } - - impl Drop for DropGuard { - fn drop(&mut self) { - // All ZST are allocated statically. - if self.value_layout.size() == 0 { - return; - } + // SAFETY: Caller ensures `value` is valid. + let value_layout = unsafe { Layout::for_value_raw(value) }; - let (layout, value_offset) = - // SAFETY: Layout must have been computable if we're in drop - unsafe { WithHeader::::alloc_layout(self.value_layout).unwrap_unchecked() }; + let _guard; + // All ZST are allocated statically. + if value_layout.size() != 0 { + _guard = DropGuard::new(self.0, |ptr| { + let layout = WithHeader::::alloc_layout(value_layout); + // SAFETY: Layout must have been computable if we're in this callback + let (layout, value_offset) = unsafe { layout.unwrap_unchecked() }; // Since we only allocate for non-ZSTs, the layout size cannot be zero. - debug_assert!(layout.size() != 0); + debug_assert_ne!(layout.size(), 0); // SAFETY: We own the allocation with `layout` at `ptr - value_offset`. - unsafe { alloc::dealloc(self.ptr.as_ptr().sub(value_offset), layout) }; - } + unsafe { alloc::dealloc(ptr.as_ptr().sub(value_offset), layout) }; + }); } - // `_guard` will deallocate the memory when dropped, even if `drop_in_place` unwinds. - let _guard = DropGuard { - ptr: self.0, - // SAFETY: Caller ensures `value` is valid. - value_layout: unsafe { Layout::for_value_raw(value) }, - _marker: PhantomData::, - }; - // We only drop the value because the Pointee trait requires that the metadata is copy // aka trivially droppable. // SAFETY: We're the only droppers of `value` and it's not dropped again. diff --git a/library/alloc/src/collections/binary_heap/mod.rs b/library/alloc/src/collections/binary_heap/mod.rs index cf6018f917a54..0fc83871c815f 100644 --- a/library/alloc/src/collections/binary_heap/mod.rs +++ b/library/alloc/src/collections/binary_heap/mod.rs @@ -145,7 +145,7 @@ use core::alloc::Allocator; use core::iter::{FusedIterator, InPlaceIterable, SourceIter, TrustedFused, TrustedLen}; -use core::mem::{self, ManuallyDrop, swap}; +use core::mem::{DropGuard, ManuallyDrop, swap}; use core::num::NonZero; use core::ops::{Deref, DerefMut}; use core::{fmt, ptr}; @@ -1914,18 +1914,10 @@ impl<'a, T: Ord, A: Allocator> DrainSorted<'a, T, A> { impl<'a, T: Ord, A: Allocator> Drop for DrainSorted<'a, T, A> { /// Removes heap elements in heap order. fn drop(&mut self) { - struct DropGuard<'r, 'a, T: Ord, A: Allocator>(&'r mut DrainSorted<'a, T, A>); - - impl<'r, 'a, T: Ord, A: Allocator> Drop for DropGuard<'r, 'a, T, A> { - fn drop(&mut self) { - while self.0.inner.pop().is_some() {} - } - } - while let Some(item) = self.inner.pop() { - let guard = DropGuard(self); + let guard = DropGuard::new(&mut *self, |this| while this.inner.pop().is_some() {}); drop(item); - mem::forget(guard); + DropGuard::dismiss(guard); } } } diff --git a/library/alloc/src/collections/btree/map.rs b/library/alloc/src/collections/btree/map.rs index e8832fd6e27ca..da08f9bfa36ed 100644 --- a/library/alloc/src/collections/btree/map.rs +++ b/library/alloc/src/collections/btree/map.rs @@ -5,7 +5,7 @@ use core::fmt::{self, Debug}; use core::hash::{Hash, Hasher}; use core::iter::{FusedIterator, TrustedLen}; use core::marker::PhantomData; -use core::mem::{self, ManuallyDrop}; +use core::mem::{self, DropGuard, ManuallyDrop}; use core::ops::{Bound, Index, RangeBounds}; use core::ptr; @@ -1912,24 +1912,18 @@ impl IntoIterator for BTreeMap { #[stable(feature = "btree_drop", since = "1.7.0")] impl Drop for IntoIter { fn drop(&mut self) { - struct DropGuard<'a, K, V, A: AllocatorClone>(&'a mut IntoIter); - - impl<'a, K, V, A: AllocatorClone> Drop for DropGuard<'a, K, V, A> { - fn drop(&mut self) { + while let Some(kv) = self.dying_next() { + let guard = DropGuard::new(&mut *self, |this| { // Continue the same loop we perform below. This only runs when unwinding, so we // don't have to care about panics this time (they'll abort). - while let Some(kv) = self.0.dying_next() { + while let Some(kv) = this.dying_next() { // SAFETY: we consume the dying handle immediately. unsafe { kv.drop_key_val() }; } - } - } - - while let Some(kv) = self.dying_next() { - let guard = DropGuard(self); + }); // SAFETY: we don't touch the tree before consuming the dying handle. unsafe { kv.drop_key_val() }; - mem::forget(guard); + DropGuard::dismiss(guard); } } } diff --git a/library/alloc/src/collections/btree/mem.rs b/library/alloc/src/collections/btree/mem.rs index ad86e9422d974..9734649fd5adc 100644 --- a/library/alloc/src/collections/btree/mem.rs +++ b/library/alloc/src/collections/btree/mem.rs @@ -16,13 +16,7 @@ pub(super) fn take_mut(v: &mut T, change: impl FnOnce(T) -> T) { /// If a panic occurs in the `change` closure, the entire process will be aborted. #[inline] pub(super) fn replace(v: &mut T, change: impl FnOnce(T) -> (T, R)) -> R { - struct PanicGuard; - impl Drop for PanicGuard { - fn drop(&mut self) { - intrinsics::abort() - } - } - let guard = PanicGuard; + let guard = mem::DropGuard::new((), |()| intrinsics::abort()); // SAFETY: v is valid for reads and we write a new value before returning. let value = unsafe { ptr::read(v) }; let (new_value, ret) = change(value); @@ -30,6 +24,6 @@ pub(super) fn replace(v: &mut T, change: impl FnOnce(T) -> (T, R)) -> R { unsafe { ptr::write(v, new_value); } - mem::forget(guard); + mem::DropGuard::dismiss(guard); ret } diff --git a/library/alloc/src/collections/btree/node.rs b/library/alloc/src/collections/btree/node.rs index aa38d17bb6dbc..c97f7ac00474a 100644 --- a/library/alloc/src/collections/btree/node.rs +++ b/library/alloc/src/collections/btree/node.rs @@ -32,7 +32,7 @@ // an edge both identifies a position and contains a pointer to a child node. use core::marker::PhantomData; -use core::mem::{self, MaybeUninit}; +use core::mem::{self, DropGuard, MaybeUninit}; use core::num::NonZero; use core::ptr::{self, NonNull}; use core::slice::SliceIndex; @@ -1237,25 +1237,14 @@ impl Handle, marker::KV> /// The node that the handle refers to must not yet have been deallocated. #[inline] pub(super) unsafe fn drop_key_val(mut self) { - // Run the destructor of the value even if the destructor of the key panics. - struct Dropper<'a, T>(&'a mut MaybeUninit); - impl Drop for Dropper<'_, T> { - #[inline] - fn drop(&mut self) { - // ignore-tidy-undocumented-unsafe - unsafe { - self.0.assume_init_drop(); - } - } - } - debug_assert!(self.idx < self.node.len()); let leaf = self.node.as_leaf_dying(); // ignore-tidy-undocumented-unsafe unsafe { let key = leaf.keys.get_unchecked_mut(self.idx); let val = leaf.vals.get_unchecked_mut(self.idx); - let _guard = Dropper(val); + // Run the destructor of the value even if the destructor of the key panics. + let _guard = DropGuard::new(val, |val| val.assume_init_drop()); key.assume_init_drop(); // dropping the guard will drop the value } diff --git a/library/alloc/src/collections/linked_list.rs b/library/alloc/src/collections/linked_list.rs index 1417f56e46cf9..953cffc1c396d 100644 --- a/library/alloc/src/collections/linked_list.rs +++ b/library/alloc/src/collections/linked_list.rs @@ -17,6 +17,7 @@ use core::cmp::Ordering; use core::hash::{Hash, Hasher}; use core::iter::{FusedIterator, TrustedLen}; use core::marker::PhantomData; +use core::mem::DropGuard; use core::ptr::NonNull; use core::{fmt, mem}; @@ -1192,20 +1193,15 @@ impl LinkedList { #[stable(feature = "rust1", since = "1.0.0")] unsafe impl<#[may_dangle] T, A: Allocator> Drop for LinkedList { fn drop(&mut self) { - struct DropGuard<'a, T, A: Allocator>(&'a mut LinkedList); - - impl<'a, T, A: Allocator> Drop for DropGuard<'a, T, A> { - fn drop(&mut self) { - // Continue the same loop we do below. This only runs when a destructor has - // panicked. If another one panics this will abort. - while self.0.pop_front_node().is_some() {} - } - } - // Wrap self so that if a destructor panics, we can try to keep looping - let guard = DropGuard(self); - while guard.0.pop_front_node().is_some() {} - mem::forget(guard); + let mut guard = DropGuard::new(self, |this| { + // Continue the same loop we do below. This only runs when a destructor has + // panicked. If another one panics this will abort. + while this.pop_front_node().is_some() {} + }); + + while guard.pop_front_node().is_some() {} + DropGuard::dismiss(guard); } } diff --git a/library/alloc/src/collections/vec_deque/drain.rs b/library/alloc/src/collections/vec_deque/drain.rs index b56af5f0e85b6..48955361e7675 100644 --- a/library/alloc/src/collections/vec_deque/drain.rs +++ b/library/alloc/src/collections/vec_deque/drain.rs @@ -1,6 +1,6 @@ use core::iter::FusedIterator; use core::marker::PhantomData; -use core::mem::{self, SizedTypeProperties}; +use core::mem::{self, DropGuard, SizedTypeProperties}; use core::ptr::NonNull; use core::{fmt, ptr}; @@ -94,144 +94,137 @@ unsafe impl Send for Drain<'_, T, A> {} #[stable(feature = "drain", since = "1.6.0")] impl Drop for Drain<'_, T, A> { fn drop(&mut self) { - struct DropGuard<'r, 'a, T, A: Allocator>(&'r mut Drain<'a, T, A>); - - let guard = DropGuard(self); - - if mem::needs_drop::() && guard.0.remaining != 0 { - // SAFETY: We just checked that `self.remaining != 0`. - let (front, back) = unsafe { guard.0.as_slices() }; - // since idx is a logical index, we don't need to worry about wrapping. - guard.0.idx += front.len(); - guard.0.remaining -= front.len(); - // SAFETY: This can't have been dropped before since - // `idx` & `remaining` track what's been dropped. - unsafe { ptr::drop_in_place(front) }; - guard.0.remaining = 0; - // SAFETY: Ditto. - unsafe { ptr::drop_in_place(back) }; - } - // Dropping `guard` handles moving the remaining elements into place. - impl<'r, 'a, T, A: Allocator> Drop for DropGuard<'r, 'a, T, A> { - #[inline] - fn drop(&mut self) { - if mem::needs_drop::() && self.0.remaining != 0 { - // SAFETY: We just checked that `self.remaining != 0`. - unsafe { - let (front, back) = self.0.as_slices(); - ptr::drop_in_place(front); - ptr::drop_in_place(back); - } + let mut guard = DropGuard::new(self, |drain| { + if mem::needs_drop::() && drain.remaining != 0 { + // SAFETY: We just checked that `self.remaining != 0`. + unsafe { + let (front, back) = drain.as_slices(); + ptr::drop_in_place(front); + ptr::drop_in_place(back); } + } - // ignore-tidy-undocumented-unsafe - let source_deque = unsafe { self.0.deque.as_mut() }; + // ignore-tidy-undocumented-unsafe + let source_deque = unsafe { drain.deque.as_mut() }; - let drain_len = self.0.drain_len; - let head_len = source_deque.len; // #elements in front of the drain - let tail_len = self.0.tail_len; // #elements behind the drain - let new_len = head_len + tail_len; + let drain_len = drain.drain_len; + let head_len = source_deque.len; // #elements in front of the drain + let tail_len = drain.tail_len; // #elements behind the drain + let new_len = head_len + tail_len; - if T::IS_ZST { - // no need to copy around any memory if T is a ZST - source_deque.len = new_len; - return; - } + if T::IS_ZST { + // no need to copy around any memory if T is a ZST + source_deque.len = new_len; + return; + } - // Next, we will fill the hole left by the drain with as few writes as possible. - // The code below handles the following control flow and reduces the amount of - // branches under the assumption that `head_len == 0 || tail_len == 0`, i.e. - // draining at the front or at the back of the dequeue is especially common. - // - // H = "head index" = `deque.head` - // h = elements in front of the drain - // d = elements in the drain - // t = elements behind the drain - // - // Note that the buffer may wrap at any point and the wrapping is handled by - // `wrap_copy` and `to_physical_idx`. - // - // Case 1: if `head_len == 0 && tail_len == 0` - // Everything was drained, reset the head index back to 0. - // H - // [ . . . . . d d d d . . . . . ] - // H - // [ . . . . . . . . . . . . . . ] - // - // Case 2: else if `tail_len == 0` - // Don't move data or the head index. - // H - // [ . . . h h h h d d d d . . . ] - // H - // [ . . . h h h h . . . . . . . ] - // - // Case 3: else if `head_len == 0` - // Don't move data, but move the head index. - // H - // [ . . . d d d d t t t t . . . ] - // H - // [ . . . . . . . t t t t . . . ] - // - // Case 4: else if `tail_len <= head_len` - // Move data, but not the head index. - // H - // [ . . h h h h d d d d t t . . ] - // H - // [ . . h h h h t t . . . . . . ] - // - // Case 5: else - // Move data and the head index. - // H - // [ . . h h d d d d t t t t . . ] - // H - // [ . . . . . . h h t t t t . . ] + // Next, we will fill the hole left by the drain with as few writes as possible. + // The code below handles the following control flow and reduces the amount of + // branches under the assumption that `head_len == 0 || tail_len == 0`, i.e. + // draining at the front or at the back of the dequeue is especially common. + // + // H = "head index" = `deque.head` + // h = elements in front of the drain + // d = elements in the drain + // t = elements behind the drain + // + // Note that the buffer may wrap at any point and the wrapping is handled by + // `wrap_copy` and `to_physical_idx`. + // + // Case 1: if `head_len == 0 && tail_len == 0` + // Everything was drained, reset the head index back to 0. + // H + // [ . . . . . d d d d . . . . . ] + // H + // [ . . . . . . . . . . . . . . ] + // + // Case 2: else if `tail_len == 0` + // Don't move data or the head index. + // H + // [ . . . h h h h d d d d . . . ] + // H + // [ . . . h h h h . . . . . . . ] + // + // Case 3: else if `head_len == 0` + // Don't move data, but move the head index. + // H + // [ . . . d d d d t t t t . . . ] + // H + // [ . . . . . . . t t t t . . . ] + // + // Case 4: else if `tail_len <= head_len` + // Move data, but not the head index. + // H + // [ . . h h h h d d d d t t . . ] + // H + // [ . . h h h h t t . . . . . . ] + // + // Case 5: else + // Move data and the head index. + // H + // [ . . h h d d d d t t t t . . ] + // H + // [ . . . . . . h h t t t t . . ] - // When draining at the front (`.drain(..n)`) or at the back (`.drain(n..)`), - // we don't need to copy any data. The number of elements copied would be 0. - if head_len != 0 && tail_len != 0 { - join_head_and_tail_wrapping(source_deque, drain_len, head_len, tail_len); - // Marking this function as cold helps LLVM to eliminate it entirely if - // this branch is never taken. - // We use `#[cold]` instead of `#[inline(never)]`, because inlining this - // function into the general case (`.drain(n..m)`) is fine. - // See `tests/codegen-llvm/vecdeque-drain.rs` for a test. - #[cold] - fn join_head_and_tail_wrapping( - source_deque: &mut VecDeque, - drain_len: usize, - head_len: usize, - tail_len: usize, - ) { - // Pick whether to move the head or the tail here. - let (src, dst, len); - if head_len < tail_len { - src = source_deque.head; - dst = source_deque.to_wrapped_index(drain_len); - len = head_len; - } else { - src = source_deque.to_wrapped_index(head_len + drain_len); - dst = source_deque.to_wrapped_index(head_len); - len = tail_len; - }; + // When draining at the front (`.drain(..n)`) or at the back (`.drain(n..)`), + // we don't need to copy any data. The number of elements copied would be 0. + if head_len != 0 && tail_len != 0 { + join_head_and_tail_wrapping(source_deque, drain_len, head_len, tail_len); + // Marking this function as cold helps LLVM to eliminate it entirely if + // this branch is never taken. + // We use `#[cold]` instead of `#[inline(never)]`, because inlining this + // function into the general case (`.drain(n..m)`) is fine. + // See `tests/codegen-llvm/vecdeque-drain.rs` for a test. + #[cold] + fn join_head_and_tail_wrapping( + source_deque: &mut VecDeque, + drain_len: usize, + head_len: usize, + tail_len: usize, + ) { + // Pick whether to move the head or the tail here. + let (src, dst, len); + if head_len < tail_len { + src = source_deque.head; + dst = source_deque.to_wrapped_index(drain_len); + len = head_len; + } else { + src = source_deque.to_wrapped_index(head_len + drain_len); + dst = source_deque.to_wrapped_index(head_len); + len = tail_len; + }; - // ignore-tidy-undocumented-unsafe - unsafe { - source_deque.wrap_copy(src, dst, len); - } + // ignore-tidy-undocumented-unsafe + unsafe { + source_deque.wrap_copy(src, dst, len); } } + } - if new_len == 0 { - // Special case: If the entire deque was drained, reset the head back to 0, - // like `.clear()` does. - source_deque.head = WrappedIndex::zero(); - } else if head_len < tail_len { - // If we moved the head above, then we need to adjust the head index here. - source_deque.head = source_deque.to_wrapped_index(drain_len); - } - source_deque.len = new_len; + if new_len == 0 { + // Special case: If the entire deque was drained, reset the head back to 0, + // like `.clear()` does. + source_deque.head = WrappedIndex::zero(); + } else if head_len < tail_len { + // If we moved the head above, then we need to adjust the head index here. + source_deque.head = source_deque.to_wrapped_index(drain_len); } + source_deque.len = new_len; + }); + + if mem::needs_drop::() && guard.remaining != 0 { + // SAFETY: We just checked that `self.remaining != 0`. + let (front, back) = unsafe { guard.as_slices() }; + // since idx is a logical index, we don't need to worry about wrapping. + guard.idx += front.len(); + guard.remaining -= front.len(); + // SAFETY: This can't have been dropped before since + // `idx` & `remaining` track what's been dropped. + unsafe { ptr::drop_in_place(front) }; + guard.remaining = 0; + // SAFETY: Ditto. + unsafe { ptr::drop_in_place(back) }; } } } diff --git a/library/alloc/src/collections/vec_deque/into_iter.rs b/library/alloc/src/collections/vec_deque/into_iter.rs index e18b85dd4b694..7c83fff6c4ab1 100644 --- a/library/alloc/src/collections/vec_deque/into_iter.rs +++ b/library/alloc/src/collections/vec_deque/into_iter.rs @@ -1,5 +1,5 @@ use core::iter::{FusedIterator, TrustedLen}; -use core::mem::MaybeUninit; +use core::mem::{DropGuard, MaybeUninit}; use core::num::NonZero; use core::ops::Try; use core::{array, fmt, ptr}; @@ -78,28 +78,20 @@ impl Iterator for IntoIter { F: FnMut(B, Self::Item) -> R, R: Try, { - struct Guard<'a, T, A: Allocator> { - deque: &'a mut VecDeque, - // `consumed <= deque.len` always holds. - consumed: usize, - } - - impl<'a, T, A: Allocator> Drop for Guard<'a, T, A> { - fn drop(&mut self) { - self.deque.len -= self.consumed; - self.deque.head = self.deque.to_wrapped_index(self.consumed); - } - } - - let mut guard = Guard { deque: &mut self.inner, consumed: 0 }; + // `consumed <= deque.len` always holds. + let mut guard = DropGuard::new((&mut self.inner, 0), |(deque, consumed)| { + deque.len -= consumed; + deque.head = deque.to_wrapped_index(consumed); + }); - let (head, tail) = guard.deque.as_slices(); + let (deque, consumed) = &mut *guard; + let (head, tail) = deque.as_slices(); init = head .iter() .map(|elem| { - guard.consumed += 1; - // SAFETY: Because we incremented `guard.consumed`, the + *consumed += 1; + // SAFETY: Because we incremented `consumed`, the // deque effectively forgot the element, so we can take // ownership unsafe { ptr::read(elem) } @@ -108,7 +100,7 @@ impl Iterator for IntoIter { tail.iter() .map(|elem| { - guard.consumed += 1; + *consumed += 1; // SAFETY: Same as above. unsafe { ptr::read(elem) } }) @@ -201,26 +193,18 @@ impl DoubleEndedIterator for IntoIter { F: FnMut(B, Self::Item) -> R, R: Try, { - struct Guard<'a, T, A: Allocator> { - deque: &'a mut VecDeque, - // `consumed <= deque.len` always holds. - consumed: usize, - } - - impl<'a, T, A: Allocator> Drop for Guard<'a, T, A> { - fn drop(&mut self) { - self.deque.len -= self.consumed; - } - } - - let mut guard = Guard { deque: &mut self.inner, consumed: 0 }; + // `consumed <= deque.len` always holds. + let mut guard = DropGuard::new((&mut self.inner, 0), |(deque, consumed)| { + deque.len -= consumed; + }); - let (head, tail) = guard.deque.as_slices(); + let (deque, consumed) = &mut *guard; + let (head, tail) = deque.as_slices(); init = tail .iter() .map(|elem| { - guard.consumed += 1; + *consumed += 1; // SAFETY: See `try_fold`'s safety comment. unsafe { ptr::read(elem) } }) @@ -228,7 +212,7 @@ impl DoubleEndedIterator for IntoIter { head.iter() .map(|elem| { - guard.consumed += 1; + *consumed += 1; // SAFETY: Same as above. unsafe { ptr::read(elem) } }) diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index 385e172b23207..08abf0e5c5a68 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -17,7 +17,7 @@ use core::iter::{ByRefSized, repeat_n, repeat_with}; // failures in linkchecker even though rustdoc built the docs just fine. #[allow(unused_imports)] use core::mem; -use core::mem::{ManuallyDrop, SizedTypeProperties}; +use core::mem::{DropGuard, ManuallyDrop, SizedTypeProperties}; use core::ops::{Index, IndexMut, Range, RangeBounds}; use core::{fmt, ptr, slice}; @@ -653,37 +653,25 @@ impl VecDeque { mut iter: impl Iterator, len: usize, ) -> usize { - struct Guard<'a, T, A: Allocator> { - deque: &'a mut VecDeque, - written: usize, - } - - impl<'a, T, A: Allocator> Drop for Guard<'a, T, A> { - fn drop(&mut self) { - self.deque.len += self.written; - } - } - let head_room = self.capacity() - dst.as_index(); - let mut guard = Guard { deque: self, written: 0 }; + let mut guard = DropGuard::new((self, 0), |(deque, written)| { + deque.len += written; + }); + let (deque, written) = &mut *guard; if head_room >= len { // ignore-tidy-undocumented-unsafe - unsafe { guard.deque.write_iter(dst, iter, &mut guard.written) }; + unsafe { deque.write_iter(dst, iter, written) }; } else { // ignore-tidy-undocumented-unsafe unsafe { - guard.deque.write_iter( - dst, - ByRefSized(&mut iter).take(head_room), - &mut guard.written, - ); - guard.deque.write_iter(WrappedIndex::zero(), iter, &mut guard.written) + deque.write_iter(dst, ByRefSized(&mut iter).take(head_room), written); + deque.write_iter(WrappedIndex::zero(), iter, written) }; } - guard.written + *written } /// Frobs the head and tail sections around to handle the fact that we diff --git a/library/alloc/src/rc.rs b/library/alloc/src/rc.rs index 09540183c488f..b4822d98bb45a 100644 --- a/library/alloc/src/rc.rs +++ b/library/alloc/src/rc.rs @@ -2450,47 +2450,32 @@ impl Rc<[T]> { /// Behavior is undefined should the size be wrong. #[cfg(not(no_global_oom_handling))] unsafe fn from_iter_exact(iter: impl Iterator, len: usize) -> Rc<[T]> { - // Panic guard while cloning T elements. - // In the event of a panic, elements that have been written - // into the new RcInner will be dropped, then the memory freed. - struct Guard { - mem: NonNull, - elems: *mut T, - layout: Layout, - n_elems: usize, - } - - impl Drop for Guard { - fn drop(&mut self) { - // ignore-tidy-undocumented-unsafe - unsafe { - let slice = from_raw_parts_mut(self.elems, self.n_elems); - ptr::drop_in_place(slice); - - Global.deallocate(self.mem, self.layout); - } - } - } + use core::mem::DropGuard; // ignore-tidy-undocumented-unsafe unsafe { let ptr = Self::allocate_for_slice(len); - - let mem = ptr as *mut _ as *mut u8; let layout = Layout::for_value_raw(ptr); // Pointer to first element - let elems = (&raw mut (*ptr).value) as *mut T; + let elems = (&raw mut (*ptr).value).as_mut_ptr(); - let mut guard = Guard { mem: NonNull::new_unchecked(mem), elems, layout, n_elems: 0 }; + // Panic guard while cloning T elements. + // In the event of a panic, elements that have been written + // into the new RcInner will be dropped, then the memory freed. + let mut guard = DropGuard::new(0, |n_elems| { + let slice = from_raw_parts_mut(elems, n_elems); + ptr::drop_in_place(slice); + Global.deallocate(NonNull::new_unchecked(ptr.cast()), layout); + }); for (i, item) in iter.enumerate() { ptr::write(elems.add(i), item); - guard.n_elems += 1; + *guard += 1; } - // All clear. Forget the guard so it doesn't free the new RcInner. - mem::forget(guard); + // All clear. Dismiss the guard so it doesn't free the new RcInner. + DropGuard::dismiss(guard); Self::from_ptr(ptr) } diff --git a/library/alloc/src/slice.rs b/library/alloc/src/slice.rs index c950569e9838b..541b3413f71ba 100644 --- a/library/alloc/src/slice.rs +++ b/library/alloc/src/slice.rs @@ -408,35 +408,30 @@ impl [T] { impl ConvertVec for T { #[inline] default fn to_vec(s: &[Self], alloc: A) -> Vec { - struct DropGuard<'a, T, A: Allocator> { - vec: &'a mut Vec, - num_init: usize, - } - impl<'a, T, A: Allocator> Drop for DropGuard<'a, T, A> { - #[inline] - fn drop(&mut self) { + use core::mem::DropGuard; + + let mut guard = DropGuard::new( + (0, Vec::with_capacity_in(s.len(), alloc)), + |(num_init, mut vec)| { // SAFETY: // items were marked initialized in the loop below - unsafe { - self.vec.set_len(self.num_init); - } - } - } - let mut vec = Vec::with_capacity_in(s.len(), alloc); - let mut guard = DropGuard { vec: &mut vec, num_init: 0 }; - let slots = guard.vec.spare_capacity_mut(); + unsafe { vec.set_len(num_init) } + }, + ); + let (num_init, vec) = &mut *guard; + + let slots = vec.spare_capacity_mut(); // .take(slots.len()) is necessary for LLVM to remove bounds checks // and has better codegen than zip. for (i, b) in s.iter().enumerate().take(slots.len()) { - guard.num_init = i; + *num_init = i; slots[i].write(b.clone()); } - core::mem::forget(guard); + + let (_, mut vec) = DropGuard::dismiss(guard); // SAFETY: // the vec was allocated and initialized above to at least this length. - unsafe { - vec.set_len(s.len()); - } + unsafe { vec.set_len(s.len()) }; vec } } diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 38c36fa25e41e..d2b5a5a53a34a 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -46,6 +46,7 @@ use core::error::Error; use core::iter::FusedIterator; #[cfg(not(no_global_oom_handling))] use core::iter::from_fn; +use core::mem::DropGuard; #[cfg(not(no_global_oom_handling))] use core::num::Saturating; #[cfg(not(no_global_oom_handling))] @@ -1689,20 +1690,6 @@ impl String { return; } - struct PanicGuard<'a> { - s: &'a mut String, - write: usize, - } - - impl Drop for PanicGuard<'_> { - fn drop(&mut self) { - debug_assert!(self.write <= self.s.len()); - debug_assert!(str::from_utf8(&self.s.vec[..self.write]).is_ok()); - // SAFETY: Restore the string length to the number of bytes written so far. - unsafe { self.s.vec.set_len(self.write) } - } - } - // Fast path: find the first character that should be removed or return early. let mut chars = self.char_indices(); let (mut read, write) = loop { @@ -1714,26 +1701,32 @@ impl String { drop(chars); // Slow path: at least one character is going to be removed. - let mut g = PanicGuard { s: self, write }; + let mut guard = DropGuard::new((self, write), |(s, write)| { + debug_assert!(write <= s.len()); + debug_assert!(str::from_utf8(&s.vec[..write]).is_ok()); + // SAFETY: Restore the string length to the number of bytes written so far. + unsafe { s.vec.set_len(write) } + }); + let (s, write) = &mut *guard; while read < len { // SAFETY: `read` is within bound because `read` < `len`, so taking // a slice with `len` is safe. - let ch = unsafe { g.s.get_unchecked(read..len).chars().next().unwrap_unchecked() }; + let ch = unsafe { s.get_unchecked(read..len).chars().next().unwrap_unchecked() }; let ch_len = ch.len_utf8(); if f(ch) { // SAFETY: `read` is on a char boundary, as guaranteed above; `g.write` is // within bounds because it is always behind `read`. unsafe { - let ptr = g.s.vec.as_mut_ptr(); - ptr::copy(ptr.add(read), ptr.add(g.write), ch_len); + let ptr = s.vec.as_mut_ptr(); + ptr::copy(ptr.add(read), ptr.add(*write), ch_len); } - g.write += ch_len; + *write += ch_len; } read += ch_len; } // All bytes processed; commit the final length by dropping the guard. - drop(g); + drop(guard); } /// Inserts a character into this `String` at byte position `idx`. diff --git a/library/alloc/src/sync.rs b/library/alloc/src/sync.rs index 5754e48a41e0a..27192cf686c12 100644 --- a/library/alloc/src/sync.rs +++ b/library/alloc/src/sync.rs @@ -19,6 +19,8 @@ use core::intrinsics::abort; #[cfg(not(no_global_oom_handling))] use core::iter; use core::marker::{PhantomData, Unsize}; +#[cfg(not(no_global_oom_handling))] +use core::mem::DropGuard; use core::mem::{self, Alignment, ManuallyDrop}; use core::num::NonZeroUsize; use core::ops::{CoerceUnsized, Deref, DerefMut, DerefPure, DispatchFromDyn, LegacyReceiver}; @@ -2417,47 +2419,31 @@ impl Arc<[T]> { /// Behavior is undefined should the size be wrong. #[cfg(not(no_global_oom_handling))] unsafe fn from_iter_exact(iter: impl Iterator, len: usize) -> Arc<[T]> { - // Panic guard while cloning T elements. - // In the event of a panic, elements that have been written - // into the new ArcInner will be dropped, then the memory freed. - struct Guard { - mem: NonNull, - elems: *mut T, - layout: Layout, - n_elems: usize, - } - - impl Drop for Guard { - fn drop(&mut self) { - // ignore-tidy-undocumented-unsafe - unsafe { - let slice = from_raw_parts_mut(self.elems, self.n_elems); - ptr::drop_in_place(slice); - - Global.deallocate(self.mem, self.layout); - } - } - } - // ignore-tidy-undocumented-unsafe unsafe { let ptr = Self::allocate_for_slice(len); - - let mem = ptr as *mut _ as *mut u8; let layout = Layout::for_value_raw(ptr); // Pointer to first element - let elems = (&raw mut (*ptr).data) as *mut T; + let elems = (&raw mut (*ptr).data).as_mut_ptr(); + + // Panic guard while cloning T elements. + // In the event of a panic, elements that have been written + // into the new ArcInner will be dropped, then the memory freed. + let mut guard = DropGuard::new(0, |n_elems| { + let slice = from_raw_parts_mut(elems, n_elems); + ptr::drop_in_place(slice); - let mut guard = Guard { mem: NonNull::new_unchecked(mem), elems, layout, n_elems: 0 }; + Global.deallocate(NonNull::new_unchecked(ptr.cast()), layout); + }); for (i, item) in iter.enumerate() { ptr::write(elems.add(i), item); - guard.n_elems += 1; + *guard += 1; } - // All clear. Forget the guard so it doesn't free the new ArcInner. - mem::forget(guard); + // All clear. Dismiss the guard so it doesn't free the new ArcInner. + DropGuard::dismiss(guard); Self::from_ptr(ptr) } @@ -2678,15 +2664,7 @@ impl Arc { // If we unwind before the Arc is overwritten, we expose a strong // count of 0, resulting in a UAF (#155746, #157203). // Until the new Arc is written, the old Arc must remain valid - struct Guard<'a, T: ?Sized> { - inner: &'a ArcInner, - } - impl<'a, T: ?Sized> Drop for Guard<'a, T> { - fn drop(&mut self) { - self.inner.strong.store(1, Release); - } - } - let guard = Guard { inner: this.inner() }; + let guard = DropGuard::new(this.inner(), |inner| inner.strong.store(1, Release)); // Can just steal the data, all that's left is Weaks // Note that this can panic in two ways: @@ -2707,7 +2685,7 @@ impl Arc { ); // We are now safe from panics. - mem::forget(guard); + DropGuard::dismiss(guard); // Materialize our own implicit weak pointer, so that it can clean // up the ArcInner as needed. diff --git a/library/alloc/src/vec/drain.rs b/library/alloc/src/vec/drain.rs index df3ff0a9b769f..8dae87b480256 100644 --- a/library/alloc/src/vec/drain.rs +++ b/library/alloc/src/vec/drain.rs @@ -1,5 +1,5 @@ use core::iter::{FusedIterator, TrustedLen}; -use core::mem::{self, ManuallyDrop, SizedTypeProperties}; +use core::mem::{self, DropGuard, ManuallyDrop, SizedTypeProperties}; use core::ptr::{self, NonNull}; use core::{fmt, slice}; @@ -176,29 +176,6 @@ impl DoubleEndedIterator for Drain<'_, T, A> { #[stable(feature = "drain", since = "1.6.0")] impl Drop for Drain<'_, T, A> { fn drop(&mut self) { - /// Moves back the un-`Drain`ed elements to restore the original `Vec`. - struct DropGuard<'r, 'a, T, A: Allocator>(&'r mut Drain<'a, T, A>); - - impl<'r, 'a, T, A: Allocator> Drop for DropGuard<'r, 'a, T, A> { - fn drop(&mut self) { - if self.0.tail_len > 0 { - // ignore-tidy-undocumented-unsafe - unsafe { - let source_vec = self.0.vec.as_mut(); - // memmove back untouched tail, update to new length - let start = source_vec.len(); - let tail = self.0.tail_start; - if tail != start { - let src = source_vec.as_ptr().add(tail); - let dst = source_vec.as_mut_ptr().add(start); - ptr::copy(src, dst, self.0.tail_len); - } - source_vec.set_len(start + self.0.tail_len); - } - } - } - } - let iter = mem::take(&mut self.iter); let drop_len = iter.len(); @@ -219,7 +196,23 @@ impl Drop for Drain<'_, T, A> { } // ensure elements are moved back into their appropriate places, even when drop_in_place panics - let _guard = DropGuard(self); + let _guard = DropGuard::new(self, |this| { + if this.tail_len > 0 { + // ignore-tidy-undocumented-unsafe + unsafe { + let source_vec = this.vec.as_mut(); + // memmove back untouched tail, update to new length + let start = source_vec.len(); + let tail = this.tail_start; + if tail != start { + let src = source_vec.as_ptr().add(tail); + let dst = source_vec.as_mut_ptr().add(start); + ptr::copy(src, dst, this.tail_len); + } + source_vec.set_len(start + this.tail_len); + } + } + }); if drop_len == 0 { return; diff --git a/library/alloc/src/vec/into_iter.rs b/library/alloc/src/vec/into_iter.rs index 46874ff76c093..fd19585a680bb 100644 --- a/library/alloc/src/vec/into_iter.rs +++ b/library/alloc/src/vec/into_iter.rs @@ -3,7 +3,7 @@ use core::iter::{ TrustedRandomAccessNoCoerce, }; use core::marker::PhantomData; -use core::mem::{ManuallyDrop, MaybeUninit, SizedTypeProperties}; +use core::mem::{DropGuard, ManuallyDrop, MaybeUninit, SizedTypeProperties}; use core::num::NonZero; #[cfg(not(no_global_oom_handling))] use core::ops::Deref; @@ -589,23 +589,11 @@ impl Clone for IntoIter { #[stable(feature = "rust1", since = "1.0.0")] unsafe impl<#[may_dangle] T, A: Allocator> Drop for IntoIter { fn drop(&mut self) { - struct DropGuard<'a, T, A: Allocator>(&'a mut IntoIter); - - impl Drop for DropGuard<'_, T, A> { - fn drop(&mut self) { - // ignore-tidy-undocumented-unsafe - unsafe { - self.0.dealloc_only(); - } - } - } - - let guard = DropGuard(self); + // ignore-tidy-undocumented-unsafe + let mut guard = DropGuard::new(self, |this| unsafe { this.dealloc_only() }); // destroy the remaining elements // ignore-tidy-undocumented-unsafe - unsafe { - ptr::drop_in_place(guard.0.as_raw_mut_slice()); - } + unsafe { ptr::drop_in_place(guard.as_raw_mut_slice()) } // now `guard` will be dropped and do the rest } } diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 74b4322d027c5..1045a7b7e2f56 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -2299,19 +2299,6 @@ pub fn copy(from: &Path, to: &Path) -> io::Result { #[cfg(target_vendor = "apple")] pub fn copy(from: &Path, to: &Path) -> io::Result { const COPYFILE_ALL: libc::copyfile_flags_t = libc::COPYFILE_METADATA | libc::COPYFILE_DATA; - - struct FreeOnDrop(libc::copyfile_state_t); - impl Drop for FreeOnDrop { - fn drop(&mut self) { - // The code below ensures that `FreeOnDrop` is never a null pointer - unsafe { - // `copyfile_state_free` returns -1 if the `to` or `from` files - // cannot be closed. However, this is not considered an error. - libc::copyfile_state_free(self.0); - } - } - } - let (reader, reader_metadata) = open_from(from)?; let clonefile_result = run_path_with_cstr(to, &|to| { @@ -2332,24 +2319,29 @@ pub fn copy(from: &Path, to: &Path) -> io::Result { // Fall back to using `fcopyfile` if `fclonefileat` does not succeed. let (writer, writer_metadata) = open_to_and_set_permissions(to, &reader_metadata)?; - // We ensure that `FreeOnDrop` never contains a null pointer so it is + let state = unsafe { libc::copyfile_state_alloc() }; + // We ensure that the guard never contains a null pointer so it is // always safe to call `copyfile_state_free` - let state = unsafe { - let state = libc::copyfile_state_alloc(); - if state.is_null() { - return Err(crate::io::Error::last_os_error()); + if state.is_null() { + return Err(crate::io::Error::last_os_error()); + } + let state = crate::mem::DropGuard::new(state, |state| { + // SAFETY: just checked it's not null + unsafe { + // `copyfile_state_free` returns -1 if the `to` or `from` files + // cannot be closed. However, this is not considered an error. + libc::copyfile_state_free(state); } - FreeOnDrop(state) - }; + }); let flags = if writer_metadata.is_file() { COPYFILE_ALL } else { libc::COPYFILE_DATA }; - cvt(unsafe { libc::fcopyfile(reader.as_raw_fd(), writer.as_raw_fd(), state.0, flags) })?; + cvt(unsafe { libc::fcopyfile(reader.as_raw_fd(), writer.as_raw_fd(), *state, flags) })?; let mut bytes_copied: libc::off_t = 0; cvt(unsafe { libc::copyfile_state_get( - state.0, + *state, libc::COPYFILE_STATE_COPIED as u32, (&raw mut bytes_copied) as *mut libc::c_void, ) diff --git a/library/std/src/sys/pal/unix/sync/condvar.rs b/library/std/src/sys/pal/unix/sync/condvar.rs index 7c9dcdc8b7375..e3294a0051d34 100644 --- a/library/std/src/sys/pal/unix/sync/condvar.rs +++ b/library/std/src/sys/pal/unix/sync/condvar.rs @@ -151,28 +151,23 @@ impl Condvar { /// # Safety /// May only be called once per instance of `Self`. pub unsafe fn init(self: Pin<&mut Self>) { + use crate::mem::DropGuard; use crate::pin::pin; - struct AttrGuard<'a>(Pin<&'a COpaque>); - impl Drop for AttrGuard<'_> { - fn drop(&mut self) { - unsafe { - let result = libc::pthread_condattr_destroy(self.0.get()); - assert_eq!(result, 0); - } - } - } - unsafe { let attr = pin!(COpaque::::uninit()); + // FIXME(pin-ergonomics): remove the next line. let attr = attr.into_ref(); let r = libc::pthread_condattr_init(attr.get()); assert_eq!(r, 0); - let attr = AttrGuard(attr); - let r = libc::pthread_condattr_setclock(attr.0.get(), Self::CLOCK); + let attr = DropGuard::new(attr, |attr| { + let result = libc::pthread_condattr_destroy(attr.get()); + assert_eq!(result, 0); + }); + let r = libc::pthread_condattr_setclock(attr.get(), Self::CLOCK); assert_eq!(r, 0); - let r = libc::pthread_cond_init(self.as_ref().raw(), attr.0.get()); + let r = libc::pthread_cond_init(self.as_ref().raw(), attr.get()); assert_eq!(r, 0); } } diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs index aa47fcb3360c1..ba04471631be9 100644 --- a/library/std/src/sys/process/unix/unix.rs +++ b/library/std/src/sys/process/unix/unix.rs @@ -394,19 +394,11 @@ impl Command { // want to be sure to restore the global environment back to what it // once was, ensuring that our temporary override, when free'd, doesn't // corrupt our process's environment. - let mut _reset = None; + let _reset; if let Some(envp) = maybe_envp { - struct Reset(*const *const libc::c_char); - - impl Drop for Reset { - fn drop(&mut self) { - unsafe { - *sys::env::environ() = self.0; - } - } - } - - _reset = Some(Reset(*sys::env::environ())); + _reset = core::mem::DropGuard::new(*sys::env::environ(), |prev| { + *sys::env::environ() = prev; + }); *sys::env::environ() = envp.as_ptr(); } @@ -461,8 +453,8 @@ impl Command { #[cfg(target_os = "linux")] use core::sync::atomic::{Atomic, AtomicU8, Ordering}; - use crate::mem::MaybeUninit; - use crate::pin::{Pin, pin}; + use crate::mem::{DropGuard, MaybeUninit}; + use crate::pin::pin; use crate::sys::helpers::COpaque; use crate::sys::{self, cvt_nz, on_broken_pipe_used}; @@ -679,68 +671,52 @@ impl Command { let pgroup = self.get_pgroup(); - struct PosixSpawnFileActions<'a>(Pin<&'a COpaque>); - - impl Drop for PosixSpawnFileActions<'_> { - fn drop(&mut self) { - unsafe { - libc::posix_spawn_file_actions_destroy(self.0.get()); - } - } - } - - struct PosixSpawnattr<'a>(Pin<&'a COpaque>); - - impl Drop for PosixSpawnattr<'_> { - fn drop(&mut self) { - unsafe { - libc::posix_spawnattr_destroy(self.0.get()); - } - } - } - unsafe { let attrs = pin!(COpaque::uninit()); // FIXME(pin-ergonomics): remove the next line. let attrs = attrs.into_ref(); cvt_nz(libc::posix_spawnattr_init(attrs.get()))?; - let attrs = PosixSpawnattr(attrs); + let attrs = DropGuard::new(attrs, |attrs| { + libc::posix_spawnattr_destroy(attrs.get()); + }); let mut flags = 0; let file_actions = pin!(COpaque::uninit()); let file_actions = file_actions.into_ref(); cvt_nz(libc::posix_spawn_file_actions_init(file_actions.get()))?; - let file_actions = PosixSpawnFileActions(file_actions); + let file_actions = DropGuard::new(file_actions, |file_actions| { + libc::posix_spawn_file_actions_destroy(file_actions.get()); + }); if let Some(fd) = stdio.stdin.fd() { cvt_nz(libc::posix_spawn_file_actions_adddup2( - file_actions.0.get(), + file_actions.get(), fd, libc::STDIN_FILENO, ))?; } if let Some(fd) = stdio.stdout.fd() { cvt_nz(libc::posix_spawn_file_actions_adddup2( - file_actions.0.get(), + file_actions.get(), fd, libc::STDOUT_FILENO, ))?; } if let Some(fd) = stdio.stderr.fd() { cvt_nz(libc::posix_spawn_file_actions_adddup2( - file_actions.0.get(), + file_actions.get(), fd, libc::STDERR_FILENO, ))?; } if let Some((f, cwd)) = addchdir { - cvt_nz(f(file_actions.0.get(), cwd.as_ptr()))?; + cvt_nz(f(file_actions.get(), cwd.as_ptr()))?; } if let Some(pgroup) = pgroup { flags |= libc::POSIX_SPAWN_SETPGROUP; - cvt_nz(libc::posix_spawnattr_setpgroup(attrs.0.get(), pgroup))?; + cvt_nz(libc::posix_spawnattr_setpgroup(attrs.get(), pgroup))?; } // Inherit the signal mask from this process rather than resetting it (i.e. do not call @@ -758,7 +734,7 @@ impl Command { { cvt(sigaddset(default_set.as_mut_ptr(), libc::SIGLOST))?; } - cvt_nz(libc::posix_spawnattr_setsigdefault(attrs.0.get(), default_set.as_ptr()))?; + cvt_nz(libc::posix_spawnattr_setsigdefault(attrs.get(), default_set.as_ptr()))?; flags |= libc::POSIX_SPAWN_SETSIGDEF; } @@ -773,7 +749,7 @@ impl Command { } } - cvt_nz(libc::posix_spawnattr_setflags(attrs.0.get(), flags as _))?; + cvt_nz(libc::posix_spawnattr_setflags(attrs.get(), flags as _))?; // Make sure we synchronize access to the global `environ` resource let _env_lock = sys::env::env_read_lock(); @@ -790,8 +766,8 @@ impl Command { let spawn_res = pidfd_spawnp.get().unwrap()( &mut pidfd, self.get_program_cstr().as_ptr(), - file_actions.0.get(), - attrs.0.get(), + file_actions.get(), + attrs.get(), self.get_argv().as_ptr() as *const _, envp as *const _, ); @@ -832,8 +808,8 @@ impl Command { let spawn_res = spawn_fn( &mut p.pid, self.get_program_cstr().as_ptr(), - file_actions.0.get(), - attrs.0.get(), + file_actions.get(), + attrs.get(), self.get_argv().as_ptr() as *const _, envp as *const _, ); diff --git a/library/std/src/sys/process/windows/tests.rs b/library/std/src/sys/process/windows/tests.rs index bc5e0d5c7fc97..4d13f4d9e3b9f 100644 --- a/library/std/src/sys/process/windows/tests.rs +++ b/library/std/src/sys/process/windows/tests.rs @@ -1,6 +1,7 @@ use super::child_pipe::{Pipes, child_pipe}; use super::{Arg, make_command_line}; use crate::ffi::{OsStr, OsString}; +use crate::mem::DropGuard; use crate::os::windows::io::AsHandle; use crate::process::{Command, Stdio}; use crate::time::Duration; @@ -36,14 +37,9 @@ fn test_thread_handle() { assert!(p.is_ok()); // Ensure the process is killed in the event something goes wrong. - struct DropGuard(crate::process::Child); - impl Drop for DropGuard { - fn drop(&mut self) { - let _ = self.0.kill(); - } - } - let mut p = DropGuard(p.unwrap()); - let p = &mut p.0; + let mut p = DropGuard::new(p.unwrap(), |mut p| { + let _: Result<(), crate::io::Error> = p.kill(); + }); unsafe extern "system" { unsafe fn ResumeThread(hHandle: BorrowedHandle<'_>) -> u32; From 7b159d5ff6bf001b2595993d426f88eb49fe6d29 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sun, 30 Aug 2026 17:18:04 +0200 Subject: [PATCH 10/13] touch up "get attribute" docs. --- Cargo.lock | 1 + compiler/rustc_attr_ir/src/lib.rs | 72 +++++++++++++++++----------- compiler/rustc_middle/Cargo.toml | 1 + compiler/rustc_middle/src/queries.rs | 12 +++-- compiler/rustc_middle/src/ty/mod.rs | 48 +++++++++++-------- 5 files changed, 83 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7cb05bce70ec4..2c9dc442663f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4430,6 +4430,7 @@ dependencies = [ "rustc_arena", "rustc_ast", "rustc_ast_ir", + "rustc_attr_ir", "rustc_crate_store", "rustc_data_structures", "rustc_errors", diff --git a/compiler/rustc_attr_ir/src/lib.rs b/compiler/rustc_attr_ir/src/lib.rs index 588bcfafb208d..0b142ca92df6b 100644 --- a/compiler/rustc_attr_ir/src/lib.rs +++ b/compiler/rustc_attr_ir/src/lib.rs @@ -1,7 +1,7 @@ //! Data structures for representing parsed attributes in the Rust compiler. //! //! For detailed documentation about attribute processing, -//! see [rustc_attr_parsing](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_attr_parsing/index.html). +//! see [rustc_attr_parsing](../rustc_attr_parsing/index.html). // tidy-alphabetical-start #![feature(const_default)] @@ -20,7 +20,6 @@ pub use lang_items::*; pub use pretty_printing::PrintAttribute; pub use stability::*; -// FIXME remove pub on some of these modules? It's fairly inconsistent. mod attr; mod canonical_symbols; mod data_structures; @@ -35,40 +34,38 @@ pub mod weak_lang_items; /// A trait for types that can provide a list of attributes given a `TyCtxt`. /// -/// It allows `find_attr!` to accept either a `DefId`, `LocalDefId`, `OwnerId`, or `HirId`. -/// It is defined here with a generic `Tcx` because `rustc_hir` can't depend on `rustc_middle`. -/// The concrete implementations are in `rustc_middle`. +/// It is an implementation detail of the [`find_attr!`] macro to be able to accept either a +/// [`DefId`], [`LocalDefId`], [`OwnerId`], or [`HirId`]. It is defined here with a generic `Tcx` +/// because this crate can't depend on `rustc_middle`. The concrete implementations are in +/// `rustc_middle`. +/// +/// Not to be confused with [`rustc_ast::ast_traits::HasAttrs`]. +/// +/// [`DefId`]: rustc_span::def_id::DefId +/// [`LocalDefId`]: rustc_span::def_id::LocalDefId +/// [`OwnerId`]: ../rustc_hir/struct.OwnerId.html +/// [`HirId`]: ../rustc_hir/struct.HirId.html pub trait HasAttrs<'tcx, Tcx> { - fn get_attrs(self, tcx: &Tcx) -> &'tcx [crate::attr::Attribute]; + fn get_attrs(self, tcx: &Tcx) -> &'tcx [crate::Attribute]; } -/// Finds attributes in sequences of attributes by pattern matching. +/// Finds attributes by pattern matching. /// /// A little like `matches` but for attributes. /// -/// ```rust,ignore (illustrative) -/// // finds the repr attribute -/// if let Some(r) = find_attr!(attrs, AttributeKind::Repr(r) => r) { -/// -/// } -/// -/// // checks if one has matched -/// if find_attr!(attrs, AttributeKind::Repr(_)) { -/// -/// } -/// ``` +/// Note that this macro accepts several "id" types: [`DefId`], [`LocalDefId`], [`OwnerId`] and +/// [`HirId`]. /// -/// Often this requires you to first end up with a list of attributes. -/// Often these are available through the `tcx`. +/// # Examples /// -/// As a convenience, this macro can do that for you! +/// It is most commonly used to check whether something has an attribute or to get its contents +/// if it is present: +/// ```rust,ignore (illustrative) +/// let is_naked: bool = find_attr!(tcx, def_id, Naked(..)); /// -/// Instead of providing an attribute list, provide the `tcx` and an id -/// (a `DefId`, `LocalDefId`, `OwnerId` or `HirId`). +/// let is_visible: bool = find_attr!(tcx, def_id, Doc(doc) if doc.hidden.is_none()); /// -/// ```rust,ignore (illustrative) -/// find_attr!(tcx, def_id, ) -/// find_attr!(tcx, hir_id, ) +/// let link_name: Option = find_attr!(tcx, def_id, LinkName { name, .. } => *name); /// ``` /// /// Another common case is finding attributes applied to the root of the current crate. @@ -77,6 +74,27 @@ pub trait HasAttrs<'tcx, Tcx> { /// ```rust, ignore (illustrative) /// find_attr!(tcx, crate, ) /// ``` +/// +/// If you already have a list of attributes in scope, you can also use that: +/// +/// ```rust,ignore (illustrative) +/// let attrs = ; +/// +/// // finds the repr attribute +/// if let Some(r) = find_attr!(attrs, Repr(r) => r) { +/// +/// } +/// +/// // checks if one has matched +/// if find_attr!(attrs, Repr(_)) { +/// +/// } +/// ``` +/// +/// [`DefId`]: rustc_span::def_id::DefId +/// [`LocalDefId`]: rustc_span::def_id::LocalDefId +/// [`OwnerId`]: ../rustc_hir/struct.OwnerId.html +/// [`HirId`]: ../rustc_hir/struct.HirId.html #[macro_export] macro_rules! find_attr { ($tcx: expr, crate, $pattern: pat $(if $guard: expr)?) => { @@ -89,6 +107,7 @@ macro_rules! find_attr { ($tcx: expr, $id: expr, $pattern: pat $(if $guard: expr)?) => { $crate::find_attr!($tcx, $id, $pattern $(if $guard)? => ()).is_some() }; + ($tcx: expr, $id: expr, $pattern: pat $(if $guard: expr)? => $e: expr) => {{ $crate::find_attr!( $crate::HasAttrs::get_attrs($id, &$tcx), @@ -96,7 +115,6 @@ macro_rules! find_attr { ) }}; - ($attributes_list: expr, $pattern: pat $(if $guard: expr)?) => {{ $crate::find_attr!($attributes_list, $pattern $(if $guard)? => ()).is_some() }}; diff --git a/compiler/rustc_middle/Cargo.toml b/compiler/rustc_middle/Cargo.toml index b26969a830f11..361aa2583fd2b 100644 --- a/compiler/rustc_middle/Cargo.toml +++ b/compiler/rustc_middle/Cargo.toml @@ -15,6 +15,7 @@ rustc_apfloat = "0.2.0" rustc_arena = { path = "../rustc_arena" } rustc_ast = { path = "../rustc_ast" } rustc_ast_ir = { path = "../rustc_ast_ir" } +rustc_attr_ir = { path = "../rustc_attr_ir" } rustc_crate_store = { path = "../rustc_crate_store" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index dcfd7a6e610b8..91f772f15b9ab 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -53,6 +53,8 @@ use rustc_arena::TypedArena; use rustc_ast as ast; use rustc_ast::expand::allocator::AllocatorKind; use rustc_ast::tokenstream::TokenStream; +use rustc_attr_ir::lang_items::{LangItem, LanguageItems}; +use rustc_attr_ir::{CanonicalSymbols, EiiDecl, EiiImpl, StrippedCfgItem}; use rustc_crate_store::{ CrateDepKind, CrateSource, ExternCrate, ForeignModule, LinkagePreference, NativeLib, }; @@ -63,8 +65,6 @@ use rustc_data_structures::svh::Svh; use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_errors::{ErrorGuaranteed, catch_fatal_errors}; use rustc_hir as hir; -use rustc_hir::attrs::lang_items::{LangItem, LanguageItems}; -use rustc_hir::attrs::{CanonicalSymbols, EiiDecl, EiiImpl, StrippedCfgItem}; use rustc_hir::def::{DefKind, DocLinkResMap}; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LocalDefIdSet, LocalModId}; use rustc_hir::{ItemLocalId, PreciseCapturingArgKind}; @@ -1526,8 +1526,12 @@ rustc_queries! { /// Returns the attributes on the item at `def_id`. /// - /// Do not use this directly, use `tcx.get_attrs` instead. - query attrs_for_def(def_id: DefId) -> &'tcx [hir::Attribute] { + ///
+ /// + /// Do not use this directly, use [`rustc_attr_ir::find_attr`] instead. + /// + ///
+ query attrs_for_def(def_id: DefId) -> &'tcx [rustc_attr_ir::Attribute] { desc { "collecting attributes of `{}`", tcx.def_path_str(def_id) } separate_provide_extern } diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index ddaa01640b64b..0ced7d1ea2bc5 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -31,18 +31,18 @@ use rustc_abi::{ use rustc_ast::node_id::NodeMap; use rustc_ast::{self as ast, NodeId}; pub use rustc_ast_ir::{Movability, Mutability, try_visit}; +use rustc_attr_ir::lang_items::LangItem; +use rustc_attr_ir::{self as attr, StrippedCfgItem, find_attr}; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::intern::Interned; use rustc_data_structures::stable_hash::{StableHash, StableHashCtxt, StableHasher}; use rustc_data_structures::steal::Steal; use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_errors::{Diag, ErrorGuaranteed, LintBuffer}; -use rustc_hir::attrs::StrippedCfgItem; -use rustc_hir::attrs::lang_items::LangItem; +use rustc_hir as hir; use rustc_hir::def::{CtorKind, CtorOf, DefKind, DocLinkResMap, LifetimeRes, Res}; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LocalDefIdMap}; use rustc_hir::definitions::PerParentDisambiguatorState; -use rustc_hir::{self as hir, MissingLifetimeKind, attrs as attr, find_attr}; use rustc_index::bit_set::BitMatrix; use rustc_index::{IndexVec, static_assert_size}; pub use rustc_lint_defs::RegisteredTools; @@ -221,7 +221,7 @@ pub struct PerOwnerResolverData<'tcx> { /// Resolution for import nodes, which have multiple resolutions in different namespaces. pub import_res: hir::def::PerNS>> = Default::default(), /// Lifetime parameters that lowering will have to introduce. - pub extra_lifetime_params_map: NodeMap> = Default::default(), + pub extra_lifetime_params_map: NodeMap> = Default::default(), /// The id of the owner pub id: ast::NodeId, @@ -251,7 +251,10 @@ impl<'tcx> PerOwnerResolverData<'tcx> { /// /// The extra lifetimes that appear from the parenthesized `Fn`-trait desugaring /// should appear at the enclosing `PolyTraitRef`. - pub fn extra_lifetime_params(&self, id: NodeId) -> &[(Ident, NodeId, MissingLifetimeKind)] { + pub fn extra_lifetime_params( + &self, + id: NodeId, + ) -> &[(Ident, NodeId, hir::MissingLifetimeKind)] { self.extra_lifetime_params_map.get(&id).map_or(&[], |v| &v[..]) } } @@ -2008,17 +2011,22 @@ impl<'tcx> TyCtxt<'tcx> { self, did: impl Into, attr: Symbol, - ) -> impl Iterator { + ) -> impl Iterator { #[expect(deprecated)] - self.get_all_attrs(did).iter().filter(move |a: &&hir::Attribute| a.has_name(attr)) + self.get_all_attrs(did).iter().filter(move |a: &&rustc_attr_ir::Attribute| a.has_name(attr)) } /// Gets all attributes. /// + ///
+ /// /// To see if an item has a specific attribute, you should use - /// [`rustc_hir::find_attr!`] so you can use matching. + /// [`rustc_attr_ir::find_attr!`] so you can use matching. + /// + ///
+ /// #[deprecated = "Though there are valid usecases for this method, especially when your attribute is not a parsed attribute, usually you want to call rustc_hir::find_attr! instead."] - pub fn get_all_attrs(self, did: impl Into) -> &'tcx [hir::Attribute] { + pub fn get_all_attrs(self, did: impl Into) -> &'tcx [rustc_attr_ir::Attribute] { let did: DefId = did.into(); if let Some(did) = did.as_local() { self.hir_attrs(self.local_def_id_to_hir_id(did)) @@ -2031,8 +2039,8 @@ impl<'tcx> TyCtxt<'tcx> { self, did: DefId, attr: &[Symbol], - ) -> impl Iterator { - let filter_fn = move |a: &&hir::Attribute| a.path_matches(attr); + ) -> impl Iterator { + let filter_fn = move |a: &&rustc_attr_ir::Attribute| a.path_matches(attr); if let Some(did) = did.as_local() { self.hir_attrs(self.local_def_id_to_hir_id(did)).iter().filter(filter_fn) } else { @@ -2474,8 +2482,8 @@ impl<'tcx> TyCtxt<'tcx> { // `HasAttrs` impls: allow `find_attr!(tcx, id, ...)` to work with both DefId-like types and HirId. -impl<'tcx> hir::attrs::HasAttrs<'tcx, TyCtxt<'tcx>> for DefId { - fn get_attrs(self, tcx: &TyCtxt<'tcx>) -> &'tcx [hir::Attribute] { +impl<'tcx> rustc_attr_ir::HasAttrs<'tcx, TyCtxt<'tcx>> for DefId { + fn get_attrs(self, tcx: &TyCtxt<'tcx>) -> &'tcx [rustc_attr_ir::Attribute] { if let Some(did) = self.as_local() { tcx.hir_attrs(tcx.local_def_id_to_hir_id(did)) } else { @@ -2484,20 +2492,20 @@ impl<'tcx> hir::attrs::HasAttrs<'tcx, TyCtxt<'tcx>> for DefId { } } -impl<'tcx> hir::attrs::HasAttrs<'tcx, TyCtxt<'tcx>> for LocalDefId { - fn get_attrs(self, tcx: &TyCtxt<'tcx>) -> &'tcx [hir::Attribute] { +impl<'tcx> rustc_attr_ir::HasAttrs<'tcx, TyCtxt<'tcx>> for LocalDefId { + fn get_attrs(self, tcx: &TyCtxt<'tcx>) -> &'tcx [rustc_attr_ir::Attribute] { tcx.hir_attrs(tcx.local_def_id_to_hir_id(self)) } } -impl<'tcx> hir::attrs::HasAttrs<'tcx, TyCtxt<'tcx>> for hir::OwnerId { - fn get_attrs(self, tcx: &TyCtxt<'tcx>) -> &'tcx [hir::Attribute] { - hir::attrs::HasAttrs::get_attrs(self.def_id, tcx) +impl<'tcx> rustc_attr_ir::HasAttrs<'tcx, TyCtxt<'tcx>> for hir::OwnerId { + fn get_attrs(self, tcx: &TyCtxt<'tcx>) -> &'tcx [rustc_attr_ir::Attribute] { + rustc_attr_ir::HasAttrs::get_attrs(self.def_id, tcx) } } -impl<'tcx> hir::attrs::HasAttrs<'tcx, TyCtxt<'tcx>> for hir::HirId { - fn get_attrs(self, tcx: &TyCtxt<'tcx>) -> &'tcx [hir::Attribute] { +impl<'tcx> rustc_attr_ir::HasAttrs<'tcx, TyCtxt<'tcx>> for hir::HirId { + fn get_attrs(self, tcx: &TyCtxt<'tcx>) -> &'tcx [rustc_attr_ir::Attribute] { tcx.hir_attrs(self) } } From 9b62f138245b4a9370cd37a45dc65c1cf6b3b1f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Kr=C3=BCger?= Date: Sun, 30 Aug 2026 18:58:01 +0200 Subject: [PATCH 11/13] remove a couple of redundant clones, thanks clippy --- .../rustc_attr_parsing/src/attributes/diagnostic/mod.rs | 2 +- compiler/rustc_builtin_macros/src/env.rs | 2 +- compiler/rustc_builtin_macros/src/offload.rs | 6 +++--- compiler/rustc_resolve/src/late/diagnostics.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index 220d1376ccc6f..53114d0ca9d8a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -361,7 +361,7 @@ fn parse_directive_items<'p>( WrappedParserError { description: e.description, label: e.label, - span: slice_span(input.span, e.span.clone(), is_snippet), + span: slice_span(input.span, e.span, is_snippet), }, input.span, ); diff --git a/compiler/rustc_builtin_macros/src/env.rs b/compiler/rustc_builtin_macros/src/env.rs index 38077109b7811..74653139fec02 100644 --- a/compiler/rustc_builtin_macros/src/env.rs +++ b/compiler/rustc_builtin_macros/src/env.rs @@ -40,7 +40,7 @@ pub(crate) fn expand_option_env<'cx>( Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)), }; let ExpandResult::Ready(mac) = - expr_to_string(cx, var_expr.clone(), "argument must be a string literal") + expr_to_string(cx, var_expr, "argument must be a string literal") else { return ExpandResult::Retry(()); }; diff --git a/compiler/rustc_builtin_macros/src/offload.rs b/compiler/rustc_builtin_macros/src/offload.rs index d9304a978ccd4..b75131db68207 100644 --- a/compiler/rustc_builtin_macros/src/offload.rs +++ b/compiler/rustc_builtin_macros/src/offload.rs @@ -134,9 +134,9 @@ pub(crate) fn expand_kernel( // host function let mut host_fn = Box::new(ast::Fn { defaultness: ast::Defaultness::Implicit, - sig: sig.clone(), + sig, ident, - generics: generics.clone(), + generics, contract: None, body: Some(body), define_opaque: None, @@ -176,7 +176,7 @@ pub(crate) fn expand_kernel( thin_vec![rustc_offload_kernel, inline_never], ast::ItemKind::Fn(host_fn), ); - item.vis = vis.clone(); + item.vis = vis; Annotatable::Item(item) }; diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 0046ccdba6ec4..f5f40a641b66e 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -4273,7 +4273,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { // we identified that the return expression references only one argument, we // would suggest borrowing only that argument, and we'd skip the prior // "use `'static`" suggestion entirely. - let mut lifetime_refs = lifetime_refs.clone().into_iter(); + let mut lifetime_refs = lifetime_refs.into_iter(); if let Some(lt) = lifetime_refs.next() && lifetime_refs.next().is_none() && (lt.kind == MissingLifetimeKind::Ampersand From 58f825e7e69b482da18a8ea83cb3f8f43415f996 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 30 Aug 2026 19:04:16 +0200 Subject: [PATCH 12/13] Add regression test for "use of an internal attribute" with a `macro_rules!` macro --- tests/ui/macros/parse-test.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/ui/macros/parse-test.rs diff --git a/tests/ui/macros/parse-test.rs b/tests/ui/macros/parse-test.rs new file mode 100644 index 0000000000000..a60d196805cd3 --- /dev/null +++ b/tests/ui/macros/parse-test.rs @@ -0,0 +1,18 @@ +//@ check-pass +//@ compile-flags: --test +// Test that we can pass a test through a macro_rules! macro that removes the span of the item +// Regression test for https://github.com/rust-lang/rust/issues/161917 +#![feature(macro_attr)] + +macro_rules! ohno { + attr() { $(#[$a:meta])* fn $name:ident () $body: block } => { + $(#[$a])* + fn $name () $body + } +} + +#[test] +#[ohno] +fn my_test() {} + +fn main() {} From 9b70ab051c8a6937e6e86b1bdc715d6896c2d18d Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Fri, 28 Aug 2026 15:04:51 +0200 Subject: [PATCH 13/13] Revert "Add `rustc_test_entrypoint_marker`" --- compiler/rustc_attr_ir/src/data_structures.rs | 3 - .../rustc_attr_ir/src/encode_cross_crate.rs | 1 - .../src/attributes/test_attrs.rs | 11 --- compiler/rustc_attr_parsing/src/context.rs | 1 - compiler/rustc_builtin_macros/src/test.rs | 9 -- compiler/rustc_feature/src/builtin_attrs.rs | 1 - compiler/rustc_passes/src/check_attr.rs | 1 - compiler/rustc_span/src/symbol.rs | 1 - tests/pretty/tests-are-sorted.pp | 3 - tests/ui-fulldeps/test_entrypoint_attrs.rs | 84 ------------------- 10 files changed, 115 deletions(-) delete mode 100644 tests/ui-fulldeps/test_entrypoint_attrs.rs diff --git a/compiler/rustc_attr_ir/src/data_structures.rs b/compiler/rustc_attr_ir/src/data_structures.rs index 3d715c6a8a291..c03b2d0246686 100644 --- a/compiler/rustc_attr_ir/src/data_structures.rs +++ b/compiler/rustc_attr_ir/src/data_structures.rs @@ -1462,9 +1462,6 @@ pub enum AttributeKind { /// Represents `#[rustc_strict_coherence]`. RustcStrictCoherence(Span), - /// Represents `#[rustc_test_entrypoint_marker]` - RustcTestEntrypointMarker, - /// Represents `#[rustc_test_marker]` RustcTestMarker(Symbol), diff --git a/compiler/rustc_attr_ir/src/encode_cross_crate.rs b/compiler/rustc_attr_ir/src/encode_cross_crate.rs index 164ab3c5822d0..205a9603ea28b 100644 --- a/compiler/rustc_attr_ir/src/encode_cross_crate.rs +++ b/compiler/rustc_attr_ir/src/encode_cross_crate.rs @@ -192,7 +192,6 @@ impl AttributeKind { RustcSpecializationTrait => No, RustcStdInternalSymbol => No, RustcStrictCoherence(..) => Yes, - RustcTestEntrypointMarker => No, RustcTestMarker(..) => No, RustcThenThisWouldNeed(..) => No, RustcTrivialFieldReads => Yes, diff --git a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs index d804c1dd78e32..9f2f7613b1c27 100644 --- a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs @@ -214,14 +214,3 @@ impl SingleAttributeParser for RustcTestMarkerParser { Some(AttributeKind::RustcTestMarker(value_str)) } } - -pub(crate) struct RustcTestEntrypointMarkerParser; - -impl NoArgsAttributeParser for RustcTestEntrypointMarkerParser { - const PATH: &[Symbol] = &[sym::rustc_test_entrypoint_marker]; - const ALLOWED_TARGETS: AllowedTargets<'_> = - AllowedTargets::AllowList(&[Allow(Target::Fn), Allow(Target::Closure)]); - const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; - const STABILITY: AttributeStability = unstable!(rustc_attrs); - const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcTestEntrypointMarker; -} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 7fe799a027c54..97e0321fe1def 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -354,7 +354,6 @@ attribute_parsers!( Single>, Single>, Single>, - Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs index a00023d8eb884..b27bf8c3f2a20 100644 --- a/compiler/rustc_builtin_macros/src/test.rs +++ b/compiler/rustc_builtin_macros/src/test.rs @@ -23,9 +23,6 @@ use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute}; /// /// We mark item with an inert attribute "rustc_test_marker" which the test generation /// logic will pick up on. -/// -/// The test function also gains a `#[rustc_test_entrypoint_marker]` attribute for tools to pick up -/// on. This behavior is *unstable*. pub(crate) fn expand_test_case( ecx: &mut ExtCtxt<'_>, attr_sp: Span, @@ -380,12 +377,6 @@ pub(crate) fn expand_test_or_bench( let test_extern = cx.item(sp, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None, test_ident)); - let item = { - let mut item = item; - item.attrs.push(cx.attr_word(sym::rustc_test_entrypoint_marker, attr_sp)); - item - }; - debug!("synthetic test item:\n{}\n", pprust::item_to_string(&test_const)); if is_stmt { diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 85a7c5aca0970..61778e9a56b31 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -364,7 +364,6 @@ pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[ sym::prelude_import, sym::rustc_paren_sugar, sym::rustc_inherit_overflow_checks, - sym::rustc_test_entrypoint_marker, sym::rustc_test_marker, sym::rustc_allow_lifetime_dependent_specialization, sym::rustc_specialization_trait, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 2f69823d54afd..f99f921d2ad02 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -400,7 +400,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::RustcSpecializationTrait => (), AttributeKind::RustcStdInternalSymbol => (), AttributeKind::RustcStrictCoherence(..) => (), - AttributeKind::RustcTestEntrypointMarker => (), AttributeKind::RustcTestMarker(..) => (), AttributeKind::RustcThenThisWouldNeed(..) => (), AttributeKind::RustcTrivialFieldReads => (), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 6376fe032c64e..6d564c9cf224a 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1879,7 +1879,6 @@ symbols! { rustc_splat, rustc_std_internal_symbol, rustc_strict_coherence, - rustc_test_entrypoint_marker, rustc_test_marker, rustc_then_this_would_need, rustc_trivial_field_reads, diff --git a/tests/pretty/tests-are-sorted.pp b/tests/pretty/tests-are-sorted.pp index f49c79f31a5ec..43f9838e68ce9 100644 --- a/tests/pretty/tests-are-sorted.pp +++ b/tests/pretty/tests-are-sorted.pp @@ -30,7 +30,6 @@ testfn: test::StaticTestFn(#[coverage(off)] || test::assert_test_result(m_test())), }; -#[rustc_test_entrypoint_marker] fn m_test() {} extern crate test; @@ -56,7 +55,6 @@ test::assert_test_result(z_test())), }; #[ignore = "not yet implemented"] -#[rustc_test_entrypoint_marker] fn z_test() {} extern crate test; @@ -81,7 +79,6 @@ testfn: test::StaticTestFn(#[coverage(off)] || test::assert_test_result(a_test())), }; -#[rustc_test_entrypoint_marker] fn a_test() {} #[rustc_main] #[coverage(off)] diff --git a/tests/ui-fulldeps/test_entrypoint_attrs.rs b/tests/ui-fulldeps/test_entrypoint_attrs.rs deleted file mode 100644 index dac7406337c57..0000000000000 --- a/tests/ui-fulldeps/test_entrypoint_attrs.rs +++ /dev/null @@ -1,84 +0,0 @@ -//@ run-pass -//@ ignore-cross-compile -//@ ignore-remote -//@ edition: 2024 -//@ ignore-stage1 -//! Uses a rustc driver to check that test entrypoints get a `#[rustc_test_entrypoint_marker]` -//! and can be found using that attribute in rustc drivers (the main use for this attribute). - -#![feature(rustc_private)] - -extern crate rustc_driver; -extern crate rustc_interface; -extern crate rustc_middle; -#[macro_use] -extern crate rustc_hir; - -use interface::Compiler; -use rustc_driver::Compilation; -use rustc_interface::interface; -use rustc_middle::ty::TyCtxt; -use std::io::Write; - -const CRATE_NAME: &str = "input"; - -struct TestAttr { - expected_tests: usize, -} - -impl rustc_driver::Callbacks for TestAttr { - fn after_analysis<'tcx>(&mut self, _compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation { - let mut tests = Vec::new(); - for did in tcx.hir_crate_items(()).definitions() { - if find_attr!(tcx, did, RustcTestEntrypointMarker) { - tests.push(did); - } - } - - // the file contains one test, so we should find one entrypoint marker. - assert_eq!(tests.len(), self.expected_tests); - - Compilation::Stop - } -} - -fn count_tests(src: &str, expected_tests: usize) { - let path = "test_input.rs"; - let mut file = std::fs::File::create(path).unwrap(); - file.write_all(src.as_bytes()).unwrap(); - - let args = [ - "rustc".to_string(), - "--test".to_string(), - "--crate-type=lib".to_string(), - "--crate-name".to_string(), - CRATE_NAME.to_string(), - path.to_string(), - ]; - rustc_driver::catch_fatal_errors(|| -> interface::Result<()> { - rustc_driver::run_compiler(&args, &mut TestAttr { expected_tests }); - Ok(()) - }) - .unwrap() - .unwrap(); -} - -fn main() { - count_tests( - r#" - #[test] - fn meow() {{ }} - "#, - 1, - ); - count_tests( - r#" - #[test] - fn one() {{ }} - - #[test] - fn two() {{ }} - "#, - 2, - ); -}