From c13ed31ac67ed09cc1073069fe5bbc538e10eb58 Mon Sep 17 00:00:00 2001 From: Maximilian Azendorf Date: Tue, 9 Jun 2026 14:19:14 +0200 Subject: [PATCH] Keep rename-imported main alive in dead-code analysis under --test --- .../rustc_builtin_macros/src/test_harness.rs | 6 +++-- compiler/rustc_passes/src/dead.rs | 16 ++++++++++++++ compiler/rustc_session/src/session.rs | 5 +++++ .../imported_main_dead_code_under_test.rs | 13 +++++++++++ .../imported_main_glob_under_test.rs | 10 +++++++++ .../imported_main_path_under_test.rs | 10 +++++++++ ...ported_main_renamed_from_mod_under_test.rs | 11 ++++++++++ .../dead-code/lint-dead-code-2-under-test.rs | 22 +++++++++++++++++++ .../lint-dead-code-2-under-test.stderr | 20 +++++++++++++++++ .../submodule_main_not_entry_under_test.rs | 9 ++++++++ ...submodule_main_not_entry_under_test.stderr | 14 ++++++++++++ 11 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 tests/ui/lint/dead-code/imported_main_dead_code_under_test.rs create mode 100644 tests/ui/lint/dead-code/imported_main_glob_under_test.rs create mode 100644 tests/ui/lint/dead-code/imported_main_path_under_test.rs create mode 100644 tests/ui/lint/dead-code/imported_main_renamed_from_mod_under_test.rs create mode 100644 tests/ui/lint/dead-code/lint-dead-code-2-under-test.rs create mode 100644 tests/ui/lint/dead-code/lint-dead-code-2-under-test.stderr create mode 100644 tests/ui/lint/dead-code/submodule_main_not_entry_under_test.rs create mode 100644 tests/ui/lint/dead-code/submodule_main_not_entry_under_test.stderr diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs index 0b68b44769987..e08fb81d4de68 100644 --- a/compiler/rustc_builtin_macros/src/test_harness.rs +++ b/compiler/rustc_builtin_macros/src/test_harness.rs @@ -1,6 +1,7 @@ // Code that generates a test runner to run all the tests in a crate use std::mem; +use std::sync::atomic::Ordering; use rustc_ast as ast; use rustc_ast::attr::contains_name; @@ -202,7 +203,7 @@ impl<'a> MutVisitor for EntryPointCleaner<'a> { // clash with the one we're going to add, but mark it as // #[allow(dead_code)] to avoid printing warnings. match entry_point_type(&item, self.depth == 0) { - EntryPointType::MainNamed | EntryPointType::RustcMainAttr => { + EntryPointType::RustcMainAttr => { let allow_dead_code = attr::mk_attr_nested_word( &self.sess.psess.attr_id_generator, ast::AttrStyle::Outer, @@ -213,8 +214,9 @@ impl<'a> MutVisitor for EntryPointCleaner<'a> { ); item.attrs.retain(|attr| !attr.has_name(sym::rustc_main)); item.attrs.push(allow_dead_code); + self.sess.removed_rustc_main_attr.store(true, Ordering::Relaxed); } - EntryPointType::None | EntryPointType::OtherMain => {} + EntryPointType::None | EntryPointType::MainNamed | EntryPointType::OtherMain => {} }; } } diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs index b79460afad99e..59a347c9042bf 100644 --- a/compiler/rustc_passes/src/dead.rs +++ b/compiler/rustc_passes/src/dead.rs @@ -5,6 +5,7 @@ use std::mem; use std::ops::ControlFlow; +use std::sync::atomic::Ordering; use hir::def_id::{LocalDefIdMap, LocalDefIdSet}; use rustc_abi::FieldIdx; @@ -930,6 +931,21 @@ fn create_and_seed_worklist(tcx: TyCtxt<'_>) -> SeedWorklists { }); } + // Under `--test`, what `main` resolves to is the would-be entry point of a normal build, + // so keep it live, unless a stripped user `#[rustc_main]` would have been the entry instead. + if tcx.sess.is_test_crate() + && !tcx.sess.removed_rustc_main_attr.load(Ordering::Relaxed) + && let Some(main_def) = tcx.resolutions(()).main_def + && let Some(def_id) = main_def.opt_fn_def_id() + && let Some(local_def_id) = def_id.as_local() + { + worklist.push(WorkItem { + id: local_def_id, + propagated: ComesFromAllowExpect::No, + own: ComesFromAllowExpect::No, + }); + } + for (id, effective_vis) in tcx.effective_visibilities(()).iter() { if effective_vis.is_public_at_level(Level::Reachable) { deferred_seeds.push(WorkItem { diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 64818f0114b61..9f88161073b60 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -181,6 +181,10 @@ pub struct Session { /// /// The value is the `DepNodeIndex` of the node encodes the used feature. pub used_features: Lock>, + + /// Whether the test harness removed a user-written `#[rustc_main]` attribute + /// while generating the synthetic test entry point. + pub removed_rustc_main_attr: AtomicBool, } #[derive(Clone, Copy)] @@ -1133,6 +1137,7 @@ pub fn build_session( thin_lto_supported: true, // filled by `run_compiler` mir_opt_bisect_eval_count: AtomicUsize::new(0), used_features: Lock::default(), + removed_rustc_main_attr: AtomicBool::new(false), }; validate_commandline_args_with_session_available(&sess); diff --git a/tests/ui/lint/dead-code/imported_main_dead_code_under_test.rs b/tests/ui/lint/dead-code/imported_main_dead_code_under_test.rs new file mode 100644 index 0000000000000..ce8de2839780e --- /dev/null +++ b/tests/ui/lint/dead-code/imported_main_dead_code_under_test.rs @@ -0,0 +1,13 @@ +//@ check-pass +//@ compile-flags: --test + +// Regression test for https://github.com/rust-lang/rust/issues/157608: a function used +// as `main` via a rename import was wrongly reported as dead code under `--test`. + +#![deny(dead_code)] + +fn different_main() { + println!("Hello from different_main"); +} + +use different_main as main; diff --git a/tests/ui/lint/dead-code/imported_main_glob_under_test.rs b/tests/ui/lint/dead-code/imported_main_glob_under_test.rs new file mode 100644 index 0000000000000..95ed510e95e6f --- /dev/null +++ b/tests/ui/lint/dead-code/imported_main_glob_under_test.rs @@ -0,0 +1,10 @@ +//@ check-pass +//@ compile-flags: --test + +#![deny(dead_code)] + +mod m { + pub fn main() {} +} + +use m::*; diff --git a/tests/ui/lint/dead-code/imported_main_path_under_test.rs b/tests/ui/lint/dead-code/imported_main_path_under_test.rs new file mode 100644 index 0000000000000..70731cc0765b3 --- /dev/null +++ b/tests/ui/lint/dead-code/imported_main_path_under_test.rs @@ -0,0 +1,10 @@ +//@ check-pass +//@ compile-flags: --test + +#![deny(dead_code)] + +mod m { + pub fn main() {} +} + +use m::main; diff --git a/tests/ui/lint/dead-code/imported_main_renamed_from_mod_under_test.rs b/tests/ui/lint/dead-code/imported_main_renamed_from_mod_under_test.rs new file mode 100644 index 0000000000000..0e8875791950e --- /dev/null +++ b/tests/ui/lint/dead-code/imported_main_renamed_from_mod_under_test.rs @@ -0,0 +1,11 @@ +//@ check-pass +//@ compile-flags: --test + + +#![deny(dead_code)] + +mod m { + pub fn other() {} +} + +use m::other as main; diff --git a/tests/ui/lint/dead-code/lint-dead-code-2-under-test.rs b/tests/ui/lint/dead-code/lint-dead-code-2-under-test.rs new file mode 100644 index 0000000000000..1939070daac5a --- /dev/null +++ b/tests/ui/lint/dead-code/lint-dead-code-2-under-test.rs @@ -0,0 +1,22 @@ +//@ compile-flags: --test + +// A `fn main` demoted by an explicit `#[rustc_main]` on another function is dead code and +// must be flagged under `--test` just like in a normal build. + +#![allow(unused_variables)] +#![deny(dead_code)] +#![feature(rustc_attrs)] + +fn dead_fn() {} //~ ERROR: function `dead_fn` is never used + +fn used_fn() {} + +#[rustc_main] +fn actual_main() { + used_fn(); +} + +// this is not main +fn main() { //~ ERROR: function `main` is never used + dead_fn(); +} diff --git a/tests/ui/lint/dead-code/lint-dead-code-2-under-test.stderr b/tests/ui/lint/dead-code/lint-dead-code-2-under-test.stderr new file mode 100644 index 0000000000000..2c5fa7ea0aed6 --- /dev/null +++ b/tests/ui/lint/dead-code/lint-dead-code-2-under-test.stderr @@ -0,0 +1,20 @@ +error: function `dead_fn` is never used + --> $DIR/lint-dead-code-2-under-test.rs:10:4 + | +LL | fn dead_fn() {} + | ^^^^^^^ + | +note: the lint level is defined here + --> $DIR/lint-dead-code-2-under-test.rs:7:9 + | +LL | #![deny(dead_code)] + | ^^^^^^^^^ + +error: function `main` is never used + --> $DIR/lint-dead-code-2-under-test.rs:20:4 + | +LL | fn main() { + | ^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/lint/dead-code/submodule_main_not_entry_under_test.rs b/tests/ui/lint/dead-code/submodule_main_not_entry_under_test.rs new file mode 100644 index 0000000000000..3f46e6fa4601b --- /dev/null +++ b/tests/ui/lint/dead-code/submodule_main_not_entry_under_test.rs @@ -0,0 +1,9 @@ +//@ compile-flags: --test + +#![deny(dead_code)] + +fn main() {} + +mod m { + pub fn main() {} //~ ERROR: function `main` is never used +} diff --git a/tests/ui/lint/dead-code/submodule_main_not_entry_under_test.stderr b/tests/ui/lint/dead-code/submodule_main_not_entry_under_test.stderr new file mode 100644 index 0000000000000..3c68a3bb0a682 --- /dev/null +++ b/tests/ui/lint/dead-code/submodule_main_not_entry_under_test.stderr @@ -0,0 +1,14 @@ +error: function `main` is never used + --> $DIR/submodule_main_not_entry_under_test.rs:8:12 + | +LL | pub fn main() {} + | ^^^^ + | +note: the lint level is defined here + --> $DIR/submodule_main_not_entry_under_test.rs:3:9 + | +LL | #![deny(dead_code)] + | ^^^^^^^^^ + +error: aborting due to 1 previous error +